Snap for 8730993 from 596ec1420a085648278e390b61ed1cadb5d08fc3 to mainline-tzdata3-release

Change-Id: I85efe739cc509903ddc7d490dbadfc75958428ee
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 309013a..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,169 +0,0 @@
-# SPDX-License-Identifier: MIT
-# Copyright 2021 Google LLC
-#
-# Use of this source code is governed by an MIT-style
-# license that can be found in the LICENSE file or at
-# https://opensource.org/licenses/MIT.
-
-name: CI
-on: [pull_request]
-
-jobs:
-  static-linking-test:
-    name: Test building static library
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh static_linking
-
-  dynamic-linking-test:
-    name: Test building dynamic library
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh dynamic_linking
-
-  cplusplus-test:
-    name: Test using library from C++ program
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh cplusplus
-
-  uninstall-test:
-    name: Test uninstalling
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh uninstall
-
-  dash-test:
-    name: Test building using the dash shell
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh dash
-
-  license-test:
-    name: Test for correct license info
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh license
-
-  gcc-test:
-    name: Test with gcc
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh gcc
-
-  clang-test:
-    name: Test with clang
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y clang
-    - run: scripts/run-tests.sh clang
-
-  _32bit-test:
-    name: Test building 32-bit binaries
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo dpkg --add-architecture i386
-        sudo apt-get update
-        sudo apt-get install -y gcc-multilib libssl-dev:i386
-    - run: scripts/run-tests.sh 32bit
-
-  sanitizers-test:
-    name: Test with sanitizers enabled
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y clang llvm
-    - run: scripts/run-tests.sh sanitizers
-
-  valgrind-test:
-    name: Test with valgrind enabled
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y valgrind
-    - run: scripts/run-tests.sh valgrind
-
-  boringssl-test:
-    name: Test with BoringSSL
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Cache BoringSSL build
-      uses: actions/cache@v2
-      with:
-        key: boringssl
-        path: boringssl
-    - run: make boringssl
-    - run: scripts/run-tests.sh boringssl
-
-  char-test:
-    name: Test with unsigned/signed char
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - run: scripts/run-tests.sh unsigned_char signed_char
-
-  # FIXME: need a Windows build of libcrypto for this to work
-  #windows-build-test:
-    #name: Windows build tests
-    #runs-on: ubuntu-latest
-    #steps:
-    #- uses: actions/checkout@v2
-    #- name: Install dependencies
-      #run: |
-        #sudo apt-get update
-        #sudo apt-get install -y gcc-mingw-w64-i686 gcc-mingw-w64-x86-64
-    # - run: scripts/run-tests.sh windows_build
-
-  sparse-test:
-    name: Run sparse
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y sparse
-    - run: scripts/run-tests.sh sparse
-
-  clang-analyzer-test:
-    name: Run clang static analyzer
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y clang-tools
-    - run: scripts/run-tests.sh clang_analyzer
-
-  shellcheck-test:
-    name: Run shellcheck
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install dependencies
-      run: |
-        sudo apt-get update
-        sudo apt-get install -y shellcheck
-    - run: scripts/run-tests.sh shellcheck
diff --git a/.gitignore b/.gitignore
index 3ea5ca6..04f9a6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,9 @@
-*.[1-9]
 *.a
-*.exe
 *.o
 *.patch
 *.so
 *.so.*
 /.build-config
-/boringssl
-/boringssl.tar.gz
 /fsverity
 /fsverity.sig
 /run-tests.log
diff --git a/Android.bp b/Android.bp
index d7abc42..025de57 100644
--- a/Android.bp
+++ b/Android.bp
@@ -63,12 +63,6 @@
 
     export_include_dirs: ["include"],
 
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.compos",
-    ],
-    recovery_available: true,
-
     srcs: [
         "lib/*.c",
     ],
diff --git a/METADATA b/METADATA
index 14e8928..6fb345f 100644
--- a/METADATA
+++ b/METADATA
@@ -5,12 +5,12 @@
     type: GIT
     value: "https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git"
   }
-  version: "20e87c13075a8e5660a8d69fd6c93d4f7c5f01a5"
-  license_note: "would be NOTICE save for common/fsverity_uapi.h"
+  version: "v1.3"
+  # would be NOTICE save for common/fsverity_uapi.h
   license_type: RESTRICTED
   last_upgrade_date {
-    year: 2022
-    month: 2
-    day: 7
+    year: 2021
+    month: 1
+    day: 19
   }
 }
diff --git a/Makefile b/Makefile
index 2304a21..0354f62 100644
--- a/Makefile
+++ b/Makefile
@@ -25,9 +25,6 @@
 # Define LIBDIR to override where to install libraries, like './configure
 # --libdir' in autotools-based projects (default: PREFIX/lib)
 #
-# Define MANDIR to override where to install man pages, like './configure
-# --mandir' in autotools-based projects (default: PREFIX/share/man)
-#
 # Define DESTDIR to override the installation destination directory
 # (default: empty string)
 #
@@ -44,13 +41,8 @@
 MINGW = 1
 endif
 
-# Set the CFLAGS.  First give the warning-related flags (unconditionally, though
-# the user can override any of them by specifying the opposite flag); then give
-# the user-specified CFLAGS, defaulting to -O2 if none were specified.
-#
-# Use -Wno-deprecated-declarations to avoid warnings about the Engine API having
-# been deprecated in OpenSSL 3.0; the replacement isn't ready yet.
 CFLAGS ?= -O2
+
 override CFLAGS := -Wall -Wundef				\
 	$(call cc-option,-Wdeclaration-after-statement)		\
 	$(call cc-option,-Wimplicit-fallthrough)		\
@@ -59,7 +51,6 @@
 	$(call cc-option,-Wstrict-prototypes)			\
 	$(call cc-option,-Wunused-parameter)			\
 	$(call cc-option,-Wvla)					\
-	$(call cc-option,-Wno-deprecated-declarations)		\
 	$(CFLAGS)
 
 override CPPFLAGS := -Iinclude -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(CPPFLAGS)
@@ -70,14 +61,12 @@
 QUIET_AR        = @echo '  AR      ' $@;
 QUIET_LN        = @echo '  LN      ' $@;
 QUIET_GEN       = @echo '  GEN     ' $@;
-QUIET_PANDOC    = @echo '  PANDOC  ' $@;
 endif
 USE_SHARED_LIB  ?=
 PREFIX          ?= /usr/local
 BINDIR          ?= $(PREFIX)/bin
 INCDIR          ?= $(PREFIX)/include
 LIBDIR          ?= $(PREFIX)/lib
-MANDIR          ?= $(PREFIX)/share/man
 DESTDIR         ?=
 ifneq ($(MINGW),1)
 PKGCONF         ?= pkg-config
@@ -103,7 +92,6 @@
 	fi
 
 DEFAULT_TARGETS :=
-EXTRA_TARGETS   :=
 COMMON_HEADERS  := $(wildcard common/*.h)
 LDLIBS          := $(shell "$(PKGCONF)" libcrypto --libs 2>/dev/null || echo -lcrypto)
 CFLAGS          += $(shell "$(PKGCONF)" libcrypto --cflags 2>/dev/null || echo)
@@ -172,7 +160,6 @@
 		     programs/fsverity.o
 ifneq ($(MINGW),1)
 FSVERITY_PROG_OBJ += \
-		     programs/cmd_dump_metadata.o \
 		     programs/cmd_enable.o	\
 		     programs/cmd_measure.o
 endif
@@ -199,37 +186,9 @@
 $(TEST_PROGRAMS): %$(EXEEXT): programs/%.o $(PROG_COMMON_OBJ) libfsverity.a
 	$(QUIET_CCLD) $(CC) -o $@ $+ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
 
-EXTRA_TARGETS += $(TEST_PROGRAMS)
-
 ##############################################################################
 
-#### Manual pages
-
-man/fsverity.1:man/fsverity.1.md
-	$(QUIET_PANDOC) pandoc $+ -s -t man > $@
-
-MAN_PAGES := man/fsverity.1
-EXTRA_TARGETS += $(MAN_PAGES)
-
-##############################################################################
-
-# Support for downloading and building BoringSSL.  The purpose of this is to
-# allow testing builds of fsverity-utils that link to BoringSSL instead of
-# OpenSSL, without having to use a system that uses BoringSSL natively.
-
-boringssl:
-	rm -rf boringssl boringssl.tar.gz
-	curl -s -o boringssl.tar.gz \
-		https://boringssl.googlesource.com/boringssl/+archive/refs/heads/master.tar.gz
-	mkdir boringssl
-	tar xf boringssl.tar.gz -C boringssl
-	cmake -B boringssl/build boringssl
-	$(MAKE) -C boringssl/build $(MAKEFLAGS)
-
-##############################################################################
-
-SPECIAL_TARGETS := all test_programs check install install-man uninstall \
-		   help clean
+SPECIAL_TARGETS := all test_programs check install uninstall help clean
 
 FORCE:
 
@@ -273,10 +232,6 @@
 		> $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
 	chmod 644 $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
 
-install-man:$(MAN_PAGES)
-	install -d $(DESTDIR)$(MANDIR)/man1
-	install -m644 $+ $(DESTDIR)$(MANDIR)/man1/
-
 uninstall:
 	rm -f $(DESTDIR)$(BINDIR)/$(FSVERITY)
 	rm -f $(DESTDIR)$(LIBDIR)/libfsverity.a
@@ -284,18 +239,15 @@
 	rm -f $(DESTDIR)$(LIBDIR)/libfsverity.so
 	rm -f $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
 	rm -f $(DESTDIR)$(INCDIR)/libfsverity.h
-	for page in $(notdir $(MAN_PAGES)); do \
-		rm -f $(DESTDIR)$(MANDIR)/man1/$$page; \
-	done
 
 help:
 	@echo "Available targets:"
 	@echo "------------------"
-	@for target in $(DEFAULT_TARGETS) $(EXTRA_TARGETS) $(SPECIAL_TARGETS); \
+	@for target in $(DEFAULT_TARGETS) $(TEST_PROGRAMS) $(SPECIAL_TARGETS); \
 	do \
 		echo $$target; \
 	done
 
 clean:
-	rm -f $(DEFAULT_TARGETS) $(EXTRA_TARGETS) \
+	rm -f $(DEFAULT_TARGETS) $(TEST_PROGRAMS) \
 		lib/*.o programs/*.o .build-config fsverity.sig
diff --git a/NEWS.md b/NEWS.md
index c63dff9..8745745 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,27 +1,5 @@
 # fsverity-utils release notes
 
-## Version 1.5
-
-* Made the `fsverity sign` command and the `libfsverity_sign_digest()` function
-  support PKCS#11 tokens.
-
-* Avoided a compiler error when building with musl libc.
-
-* Avoided compiler warnings when building with OpenSSL 3.0.
-
-* Improved documentation and test scripts.
-
-## Version 1.4
-
-* Added a manual page for the `fsverity` utility.
-
-* Added the `fsverity dump_metadata` subcommand.
-
-* Added the `--out-merkle-tree` and `--out-descriptor` options to
-  `fsverity digest` and `fsverity sign`.
-
-* Added metadata callbacks support to `libfsverity_compute_digest()`.
-
 ## Version 1.3
 
 * Added a `fsverity digest` subcommand.
diff --git a/README.md b/README.md
index ffa25fd..2b63488 100644
--- a/README.md
+++ b/README.md
@@ -24,22 +24,20 @@
 
 ## Building and installing
 
-To build fsverity-utils, first install the needed build dependencies.  For
-example, on Debian-based systems, run:
+fsverity-utils uses the OpenSSL library, so you first must install the
+needed development files.  For example, on Debian-based systems, run:
 
 ```bash
     sudo apt-get install libssl-dev
-    sudo apt-get install pandoc  # optional
 ```
 
-OpenSSL must be version 1.0.0 or later.  This is the only runtime dependency.
+OpenSSL must be version 1.0.0 or later.
 
 Then, to build and install fsverity-utils:
 
 ```bash
     make
     sudo make install
-    sudo make install-man  # optional
 ```
 
 By default, the following targets are built and installed: the program
@@ -47,9 +45,6 @@
 `libfsverity.so`.  You can also run `make check` to build and run the
 tests, or `make help` to display all available build targets.
 
-`make install-man` installs the `fsverity.1` manual page.  This step requires
-that `pandoc` be installed.
-
 By default, `fsverity` is statically linked to `libfsverity`.  You can
 use `make USE_SHARED_LIB=1` to use dynamic linking instead.
 
@@ -68,9 +63,6 @@
 
 ## Examples
 
-Full usage information for `fsverity` can be found in the manual page
-(`man fsverity`).  Here, we just show some typical examples.
-
 ### Basic use
 
 ```bash
@@ -102,44 +94,11 @@
 
 ### Using builtin signatures
 
-First, note that fs-verity is essentially just a way of hashing a
-file; it doesn't mandate a specific way of handling signatures.
-There are several possible ways that signatures could be handled:
-
-* Do it entirely in userspace
-* Use IMA appraisal (work-in-progress)
-* Use fs-verity built-in signatures
-
-Any such solution needs two parts: (a) a policy that determines which
-files are required to have fs-verity enabled and have a valid
-signature, and (b) enforcement of the policy.  Each part could happen
-either in a trusted userspace program(s) or in the kernel.
-
-fs-verity built-in signatures (which are supported when the kernel was
-built with `CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y`) are a hybrid
-solution where the policy of which files are required to be signed is
-determined and enforced by a trusted userspace program, but the actual
-signature verification happens in the kernel.  Specifically, with
-built-in signatures, the filesystem supports storing a signed file
-digest in each file's verity metadata.  Before allowing access to the
-file, the filesystem will automatically verify the signature against
-the set of X.509 certificates in the ".fs-verity" kernel keyring.  If
-set, the sysctl `fs.verity.require_signatures=1` will make the kernel
-enforce that every verity file has a valid built-in signature.
-
-fs-verity built-in signatures are primarily intended as a
-proof-of-concept; they reuse the kernel code that verifies the
-signatures of loadable kernel modules.  This solution still requires a
-trusted userspace program to enforce that particular files have
-fs-verity enabled.  Also, this solution uses PKCS#7 signatures, which
-are complex and prone to security bugs.
-
-Thus, if possible one of the other solutions should be used instead.
-For example, the trusted userspace program could verify signatures
-itself, using a simple signature format using a modern algorithm such
-as Ed25519.
-
-That being said, here are some examples of using built-in signatures:
+With `CONFIG_FS_VERITY_BUILTIN_SIGNATURES=y`, the filesystem supports
+automatically verifying a signed file digest that has been included in
+the verity metadata.  The signature is verified against the set of
+X.509 certificates that have been loaded into the ".fs-verity" kernel
+keyring.  Here's an example:
 
 ```bash
     # Generate a new certificate and private key:
@@ -170,6 +129,15 @@
     fsverity digest file --compact --for-builtin-sig | tr -d '\n' | xxd -p -r | openssl smime -sign -in /dev/stdin ...
 ```
 
+By default, it's not required that verity files have a signature.
+This can be changed with `sysctl fs.verity.require_signatures=1`.
+When set, it's guaranteed that the contents of every verity file has
+been signed by one of the certificates in the keyring.
+
+Note: applications generally still need to check whether the file
+they're accessing really is a verity file, since an attacker could
+replace a verity file with a regular one.
+
 ### With IMA
 
 IMA support for fs-verity is planned.
diff --git a/common/fsverity_uapi.h b/common/fsverity_uapi.h
index c59a897..a739c9a 100644
--- a/common/fsverity_uapi.h
+++ b/common/fsverity_uapi.h
@@ -85,21 +85,7 @@
 	__u8 digest[];
 };
 
-#define FS_VERITY_METADATA_TYPE_MERKLE_TREE	1
-#define FS_VERITY_METADATA_TYPE_DESCRIPTOR	2
-#define FS_VERITY_METADATA_TYPE_SIGNATURE	3
-
-struct fsverity_read_metadata_arg {
-	__u64 metadata_type;
-	__u64 offset;
-	__u64 length;
-	__u64 buf_ptr;
-	__u64 __reserved;
-};
-
 #define FS_IOC_ENABLE_VERITY	_IOW('f', 133, struct fsverity_enable_arg)
 #define FS_IOC_MEASURE_VERITY	_IOWR('f', 134, struct fsverity_digest)
-#define FS_IOC_READ_VERITY_METADATA \
-	_IOWR('f', 135, struct fsverity_read_metadata_arg)
 
 #endif /* _UAPI_LINUX_FSVERITY_H */
diff --git a/include/libfsverity.h b/include/libfsverity.h
index a0a1527..6c42e5e 100644
--- a/include/libfsverity.h
+++ b/include/libfsverity.h
@@ -22,7 +22,7 @@
 #include <stdint.h>
 
 #define FSVERITY_UTILS_MAJOR_VERSION	1
-#define FSVERITY_UTILS_MINOR_VERSION	5
+#define FSVERITY_UTILS_MINOR_VERSION	3
 
 #define FS_VERITY_HASH_ALG_SHA256       1
 #define FS_VERITY_HASH_ALG_SHA512       2
@@ -61,18 +61,8 @@
 	/** @reserved1: must be 0 */
 	uint64_t reserved1[8];
 
-	/**
-	 * @metadata_callbacks: if non-NULL, this gives a set of callback
-	 * functions to which libfsverity_compute_digest() will pass the Merkle
-	 * tree blocks and fs-verity descriptor after they are computed.
-	 * Normally this isn't useful, but this can be needed in rare cases
-	 * where the metadata needs to be consumed by something other than one
-	 * of the native Linux kernel implementations of fs-verity.
-	 */
-	const struct libfsverity_metadata_callbacks *metadata_callbacks;
-
 	/** @reserved2: must be 0 */
-	uintptr_t reserved2[7];
+	uintptr_t reserved2[8];
 };
 
 struct libfsverity_digest {
@@ -81,75 +71,11 @@
 	uint8_t digest[];		/* the actual digest */
 };
 
-/**
- * struct libfsverity_signature_params - certificate and private key information
- *
- * Zero this, then set @certfile.  Then, to specify the private key by key file,
- * set @keyfile.  Alternatively, to specify the private key by PKCS#11 token,
- * set @pkcs11_engine, @pkcs11_module, and optionally @pkcs11_keyid.
- *
- * Support for PKCS#11 tokens is unavailable when libfsverity was linked to
- * BoringSSL rather than OpenSSL.
- */
 struct libfsverity_signature_params {
-
-	/** @keyfile: the path to the key file in PEM format, when applicable */
-	const char *keyfile;
-
-	/** @certfile: the path to the certificate file in PEM format */
-	const char *certfile;
-
-	/** @reserved1: must be 0 */
-	uint64_t reserved1[8];
-
-	/**
-	 * @pkcs11_engine: the path to the PKCS#11 engine .so file, when
-	 * applicable
-	 */
-	const char *pkcs11_engine;
-
-	/**
-	 * @pkcs11_module: the path to the PKCS#11 module .so file, when
-	 * applicable
-	 */
-	const char *pkcs11_module;
-
-	/** @pkcs11_keyid: the PKCS#11 key identifier, when applicable */
-	const char *pkcs11_keyid;
-
-	/** @reserved2: must be 0 */
-	uintptr_t reserved2[5];
-};
-
-struct libfsverity_metadata_callbacks {
-
-	/** @ctx: context passed to the below callbacks (opaque to library) */
-	void *ctx;
-
-	/**
-	 * @merkle_tree_size: if non-NULL, called with the total size of the
-	 * Merkle tree in bytes, prior to any call to @merkle_tree_block.  Must
-	 * return 0 on success, or a negative errno value on failure.
-	 */
-	int (*merkle_tree_size)(void *ctx, uint64_t size);
-
-	/**
-	 * @merkle_tree_block: if non-NULL, called with each block of the
-	 * Merkle tree after it is computed.  The offset is the offset in bytes
-	 * to the block within the Merkle tree, using the Merkle tree layout
-	 * used by FS_IOC_READ_VERITY_METADATA.  The offsets won't necessarily
-	 * be in increasing order.  Must return 0 on success, or a negative
-	 * errno value on failure.
-	 */
-	int (*merkle_tree_block)(void *ctx, const void *block, size_t size,
-				 uint64_t offset);
-
-	/**
-	 * @descriptor: if non-NULL, called with the fs-verity descriptor after
-	 * it is computed.  Must return 0 on success, or a negative errno value
-	 * on failure.
-	 */
-	int (*descriptor)(void *ctx, const void *descriptor, size_t size);
+	const char *keyfile;		/* path to key file (PEM format) */
+	const char *certfile;		/* path to certificate (PEM format) */
+	uint64_t reserved1[8];		/* must be 0 */
+	uintptr_t reserved2[8];		/* must be 0 */
 };
 
 /*
@@ -175,8 +101,7 @@
  *
  * Returns:
  * * 0 for success, -EINVAL for invalid input arguments, -ENOMEM if libfsverity
- *   failed to allocate memory, or an error returned by @read_fn or by one of
- *   the @params->metadata_callbacks.
+ *   failed to allocate memory, or an error returned by @read_fn.
  * * digest_ret returns a pointer to the digest on success. The digest object
  *   is allocated by libfsverity and must be freed by the caller using free().
  */
@@ -186,15 +111,16 @@
 			   struct libfsverity_digest **digest_ret);
 
 /**
- * libfsverity_sign_digest() - Sign a file for built-in signature verification
- *	    Sign a file digest in a way that is compatible with the Linux
- *	    kernel's fs-verity built-in signature verification support.  The
- *	    resulting signature will be a PKCS#7 message in DER format.  Note
- *	    that this is not the only way to do signatures with fs-verity.  For
- *	    more details, refer to the fsverity-utils README and to
- *	    Documentation/filesystems/fsverity.rst in the kernel source tree.
+ * libfsverity_sign_digest() - Sign previously computed digest of a file
+ *          This signature is used by the filesystem to validate the signed file
+ *          digest against a public key loaded into the .fs-verity kernel
+ *          keyring, when CONFIG_FS_VERITY_BUILTIN_SIGNATURES is enabled. The
+ *          signature is formatted as PKCS#7 stored in DER format. See
+ *          Documentation/filesystems/fsverity.rst in the kernel source tree for
+ *          further details.
  * @digest: pointer to previously computed digest
- * @sig_params: pointer to the certificate and private key information
+ * @sig_params: struct libfsverity_signature_params providing filenames of
+ *          the keyfile and certificate file. Reserved fields must be zero.
  * @sig_ret: Pointer to pointer for signed digest
  * @sig_size_ret: Pointer to size of signed return digest
  *
diff --git a/lib/compute_digest.c b/lib/compute_digest.c
index c5b0100..a4f649c 100644
--- a/lib/compute_digest.c
+++ b/lib/compute_digest.c
@@ -24,8 +24,9 @@
 
 /*
  * Hash a block, writing the result to the next level's pending block buffer.
+ * Returns true if the next level's block became full, else false.
  */
-static void hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
+static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
 			   u32 block_size, const u8 *salt, u32 salt_size)
 {
 	struct block_buffer *next = cur + 1;
@@ -40,60 +41,8 @@
 
 	next->filled += hash->alg->digest_size;
 	cur->filled = 0;
-}
 
-static bool block_is_full(const struct block_buffer *block, u32 block_size,
-			  struct hash_ctx *hash)
-{
-	/* Would the next hash put us over the limit? */
-	return block->filled + hash->alg->digest_size > block_size;
-}
-
-static int report_merkle_tree_size(const struct libfsverity_metadata_callbacks *cbs,
-				   u64 size)
-{
-	if (cbs && cbs->merkle_tree_size) {
-		int err = cbs->merkle_tree_size(cbs->ctx, size);
-
-		if (err) {
-			libfsverity_error_msg("error processing Merkle tree size");
-			return err;
-		}
-	}
-	return 0;
-}
-
-static int report_merkle_tree_block(const struct libfsverity_metadata_callbacks *cbs,
-				    const struct block_buffer *block,
-				    u32 block_size, u64 *level_offset)
-{
-
-	if (cbs && cbs->merkle_tree_block) {
-		int err = cbs->merkle_tree_block(cbs->ctx, block->data,
-						 block_size,
-						 *level_offset * block_size);
-
-		if (err) {
-			libfsverity_error_msg("error processing Merkle tree block");
-			return err;
-		}
-		(*level_offset)++;
-	}
-	return 0;
-}
-
-static int report_descriptor(const struct libfsverity_metadata_callbacks *cbs,
-			     const void *descriptor, size_t size)
-{
-	if (cbs && cbs->descriptor) {
-		int err = cbs->descriptor(cbs->ctx, descriptor, size);
-
-		if (err) {
-			libfsverity_error_msg("error processing fs-verity descriptor");
-			return err;
-		}
-	}
-	return 0;
+	return next->filled + hash->alg->digest_size > block_size;
 }
 
 /*
@@ -103,7 +52,6 @@
 static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
 			     u64 file_size, struct hash_ctx *hash,
 			     u32 block_size, const u8 *salt, u32 salt_size,
-			     const struct libfsverity_metadata_callbacks *metadata_cbs,
 			     u8 *root_hash)
 {
 	const u32 hashes_per_block = block_size / hash->alg->digest_size;
@@ -112,7 +60,6 @@
 	u64 blocks;
 	int num_levels = 0;
 	int level;
-	u64 level_offset[FS_VERITY_MAX_LEVELS];
 	struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};
 	struct block_buffer *buffers = &_buffers[1];
 	u64 offset;
@@ -121,7 +68,7 @@
 	/* Root hash of empty file is all 0's */
 	if (file_size == 0) {
 		memset(root_hash, 0, hash->alg->digest_size);
-		return report_merkle_tree_size(metadata_cbs, 0);
+		return 0;
 	}
 
 	if (salt_size != 0) {
@@ -131,41 +78,17 @@
 		memcpy(padded_salt, salt, salt_size);
 	}
 
-	/* Compute number of levels and the number of blocks in each level. */
-	blocks = DIV_ROUND_UP(file_size, block_size);
-	while (blocks > 1)  {
+	/* Compute number of levels */
+	for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1;
+	     blocks = DIV_ROUND_UP(blocks, hashes_per_block)) {
 		if (WARN_ON(num_levels >= FS_VERITY_MAX_LEVELS)) {
 			err = -EINVAL;
 			goto out;
 		}
-		blocks = DIV_ROUND_UP(blocks, hashes_per_block);
-		/*
-		 * Temporarily use level_offset[] to store the number of blocks
-		 * in each level.  It will be overwritten later.
-		 */
-		level_offset[num_levels++] = blocks;
+		num_levels++;
 	}
 
 	/*
-	 * Compute the starting block of each level, using the convention where
-	 * the root level is first, i.e. the convention used by
-	 * FS_IOC_READ_VERITY_METADATA.  At the same time, compute the total
-	 * size of the Merkle tree.  These values are only needed for the
-	 * metadata callbacks (if they were given), as the hash computation
-	 * itself doesn't prescribe an ordering of the levels and doesn't
-	 * prescribe any special meaning to the total size of the Merkle tree.
-	 */
-	offset = 0;
-	for (level = num_levels - 1; level >= 0; level--) {
-		blocks = level_offset[level];
-		level_offset[level] = offset;
-		offset += blocks;
-	}
-	err = report_merkle_tree_size(metadata_cbs, offset * block_size);
-	if (err)
-		goto out;
-
-	/*
 	 * Allocate the block buffers.  Buffer "-1" is for data blocks.
 	 * Buffers 0 <= level < num_levels are for the actual tree levels.
 	 * Buffer 'num_levels' is for the root hash.
@@ -189,33 +112,21 @@
 			goto out;
 		}
 
-		hash_one_block(hash, &buffers[-1], block_size,
-			       padded_salt, padded_salt_size);
-		for (level = 0; level < num_levels; level++) {
-			if (!block_is_full(&buffers[level], block_size, hash))
-				break;
-			hash_one_block(hash, &buffers[level], block_size,
-				       padded_salt, padded_salt_size);
-			err = report_merkle_tree_block(metadata_cbs,
-						       &buffers[level],
-						       block_size,
-						       &level_offset[level]);
-			if (err)
+		level = -1;
+		while (hash_one_block(hash, &buffers[level], block_size,
+				      padded_salt, padded_salt_size)) {
+			level++;
+			if (WARN_ON(level >= num_levels)) {
+				err = -EINVAL;
 				goto out;
+			}
 		}
 	}
 	/* Finish all nonempty pending tree blocks */
 	for (level = 0; level < num_levels; level++) {
-		if (buffers[level].filled != 0) {
+		if (buffers[level].filled != 0)
 			hash_one_block(hash, &buffers[level], block_size,
 				       padded_salt, padded_salt_size);
-			err = report_merkle_tree_block(metadata_cbs,
-						       &buffers[level],
-						       block_size,
-						       &level_offset[level]);
-			if (err)
-				goto out;
-		}
 	}
 
 	/* Root hash was filled by the last call to hash_one_block() */
@@ -306,13 +217,8 @@
 	}
 
 	err = compute_root_hash(fd, read_fn, params->file_size, hash,
-				block_size, params->salt, params->salt_size,
-				params->metadata_callbacks, desc.root_hash);
-	if (err)
-		goto out;
-
-	err = report_descriptor(params->metadata_callbacks,
-				&desc, sizeof(desc));
+				block_size, params->salt,
+				params->salt_size, desc.root_hash);
 	if (err)
 		goto out;
 
diff --git a/lib/lib_private.h b/lib/lib_private.h
index 8532636..7768eea 100644
--- a/lib/lib_private.h
+++ b/lib/lib_private.h
@@ -58,11 +58,14 @@
 void *libfsverity_memdup(const void *mem, size_t size);
 
 __cold void
-libfsverity_do_error_msg(const char *format, va_list va);
+libfsverity_do_error_msg(const char *format, va_list va, int err);
 
 __printf(1, 2) __cold void
 libfsverity_error_msg(const char *format, ...);
 
+__printf(1, 2) __cold void
+libfsverity_error_msg_errno(const char *format, ...);
+
 __cold void
 libfsverity_warn_on(const char *condition, const char *file, int line);
 
diff --git a/lib/libfsverity.pc.in b/lib/libfsverity.pc.in
index 4c9bb20..be3ef44 100644
--- a/lib/libfsverity.pc.in
+++ b/lib/libfsverity.pc.in
@@ -4,7 +4,7 @@
 
 Name: libfsverity
 Description: fs-verity library
-Version: 1.5
+Version: 1.3
 Libs: -L${libdir} -lfsverity
 Requires.private: libcrypto
 Cflags: -I${includedir}
diff --git a/lib/sign_digest.c b/lib/sign_digest.c
index d726772..9a35256 100644
--- a/lib/sign_digest.c
+++ b/lib/sign_digest.c
@@ -19,10 +19,6 @@
 #include <openssl/pkcs7.h>
 #include <string.h>
 
-#ifndef OPENSSL_IS_BORINGSSL
-#include <openssl/engine.h>
-#endif
-
 static int print_openssl_err_cb(const char *str,
 				size_t len __attribute__((unused)),
 				void *u __attribute__((unused)))
@@ -38,7 +34,7 @@
 	va_list va;
 
 	va_start(va, format);
-	libfsverity_do_error_msg(format, va);
+	libfsverity_do_error_msg(format, va, 0);
 	va_end(va);
 
 	if (ERR_peek_error() == 0)
@@ -85,11 +81,6 @@
 	X509 *cert;
 	int err;
 
-	if (!certfile) {
-		libfsverity_error_msg("no certificate specified");
-		return -EINVAL;
-	}
-
 	errno = 0;
 	bio = BIO_new_file(certfile, "r");
 	if (!bio) {
@@ -221,15 +212,6 @@
 	return err;
 }
 
-static int
-load_pkcs11_private_key(const struct libfsverity_signature_params *sig_params
-			__attribute__((unused)),
-			EVP_PKEY **pkey_ret __attribute__((unused)))
-{
-	libfsverity_error_msg("BoringSSL doesn't support PKCS#11 tokens");
-	return -EINVAL;
-}
-
 #else /* OPENSSL_IS_BORINGSSL */
 
 static BIO *new_mem_buf(const void *buf, size_t size)
@@ -333,79 +315,16 @@
 	return err;
 }
 
-static int
-load_pkcs11_private_key(const struct libfsverity_signature_params *sig_params,
-			EVP_PKEY **pkey_ret)
-{
-	ENGINE *engine;
-
-	if (!sig_params->pkcs11_engine) {
-		libfsverity_error_msg("no PKCS#11 engine specified");
-		return -EINVAL;
-	}
-	if (!sig_params->pkcs11_module) {
-		libfsverity_error_msg("no PKCS#11 module specified");
-		return -EINVAL;
-	}
-	ENGINE_load_dynamic();
-	engine = ENGINE_by_id("dynamic");
-	if (!engine) {
-		error_msg_openssl("failed to initialize OpenSSL PKCS#11 engine");
-		return -EINVAL;
-	}
-	if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH",
-				    sig_params->pkcs11_engine, 0) ||
-	    !ENGINE_ctrl_cmd_string(engine, "ID", "pkcs11", 0) ||
-	    !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) ||
-	    !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0) ||
-	    !ENGINE_ctrl_cmd_string(engine, "MODULE_PATH",
-				    sig_params->pkcs11_module, 0) ||
-	    !ENGINE_init(engine)) {
-		error_msg_openssl("failed to initialize OpenSSL PKCS#11 engine");
-		ENGINE_free(engine);
-		return -EINVAL;
-	}
-	*pkey_ret = ENGINE_load_private_key(engine, sig_params->pkcs11_keyid,
-					    NULL, NULL);
-	ENGINE_finish(engine);
-	ENGINE_free(engine);
-	if (!*pkey_ret) {
-		error_msg_openssl("failed to load private key from PKCS#11 token");
-		return -EINVAL;
-	}
-	return 0;
-}
-
 #endif /* !OPENSSL_IS_BORINGSSL */
 
-/* Get a private key, either from disk or from a PKCS#11 token. */
-static int
-get_private_key(const struct libfsverity_signature_params *sig_params,
-		EVP_PKEY **pkey_ret)
-{
-	if (sig_params->pkcs11_engine || sig_params->pkcs11_module ||
-	    sig_params->pkcs11_keyid) {
-		if (sig_params->keyfile) {
-			libfsverity_error_msg("private key must be specified either by file or by PKCS#11 token, not both");
-			return -EINVAL;
-		}
-		return load_pkcs11_private_key(sig_params, pkey_ret);
-	}
-	if (!sig_params->keyfile) {
-		libfsverity_error_msg("no private key specified");
-		return -EINVAL;
-	}
-	return read_private_key(sig_params->keyfile, pkey_ret);
-}
-
 LIBEXPORT int
 libfsverity_sign_digest(const struct libfsverity_digest *digest,
 			const struct libfsverity_signature_params *sig_params,
 			u8 **sig_ret, size_t *sig_size_ret)
 {
 	const struct fsverity_hash_alg *hash_alg;
-	X509 *cert = NULL;
 	EVP_PKEY *pkey = NULL;
+	X509 *cert = NULL;
 	const EVP_MD *md;
 	struct fsverity_formatted_digest *d = NULL;
 	int err;
@@ -415,6 +334,11 @@
 		return -EINVAL;
 	}
 
+	if (!sig_params->keyfile || !sig_params->certfile) {
+		libfsverity_error_msg("keyfile and certfile must be specified");
+		return -EINVAL;
+	}
+
 	if (!libfsverity_mem_is_zeroed(sig_params->reserved1,
 				       sizeof(sig_params->reserved1)) ||
 	    !libfsverity_mem_is_zeroed(sig_params->reserved2,
@@ -429,11 +353,11 @@
 		return -EINVAL;
 	}
 
-	err = read_certificate(sig_params->certfile, &cert);
+	err = read_private_key(sig_params->keyfile, &pkey);
 	if (err)
 		goto out;
 
-	err = get_private_key(sig_params, &pkey);
+	err = read_certificate(sig_params->certfile, &cert);
 	if (err)
 		goto out;
 
@@ -459,8 +383,8 @@
 	err = sign_pkcs7(d, sizeof(*d) + digest->digest_size,
 			 pkey, cert, md, sig_ret, sig_size_ret);
  out:
-	X509_free(cert);
 	EVP_PKEY_free(pkey);
+	X509_free(cert);
 	free(d);
 	return err;
 }
diff --git a/lib/utils.c b/lib/utils.c
index d506ef1..036dd60 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -51,7 +51,16 @@
 	libfsverity_error_cb = cb;
 }
 
-void libfsverity_do_error_msg(const char *format, va_list va)
+#ifdef _WIN32
+static char *strerror_r(int errnum, char *buf, size_t buflen)
+{
+	strerror_s(buf, buflen, errnum);
+
+	return buf;
+}
+#endif
+
+void libfsverity_do_error_msg(const char *format, va_list va, int err)
 {
 	int saved_errno = errno;
 	char *msg = NULL;
@@ -62,8 +71,18 @@
 	if (vasprintf(&msg, format, va) < 0)
 		goto out;
 
-	(*libfsverity_error_cb)(msg);
+	if (err) {
+		char *msg2 = NULL;
+		char errbuf[64];
 
+		if (asprintf(&msg2, "%s: %s", msg,
+			     strerror_r(err, errbuf, sizeof(errbuf))) < 0)
+			goto out2;
+		free(msg);
+		msg = msg2;
+	}
+	(*libfsverity_error_cb)(msg);
+out2:
 	free(msg);
 out:
 	errno = saved_errno;
@@ -74,7 +93,16 @@
 	va_list va;
 
 	va_start(va, format);
-	libfsverity_do_error_msg(format, va);
+	libfsverity_do_error_msg(format, va, 0);
+	va_end(va);
+}
+
+void libfsverity_error_msg_errno(const char *format, ...)
+{
+	va_list va;
+
+	va_start(va, format);
+	libfsverity_do_error_msg(format, va, errno);
 	va_end(va);
 }
 
diff --git a/man/fsverity.1.md b/man/fsverity.1.md
deleted file mode 100644
index dd54964..0000000
--- a/man/fsverity.1.md
+++ /dev/null
@@ -1,215 +0,0 @@
-% FSVERITY(1) fsverity-utils v1.5 | User Commands
-%
-% February 2022
-
-# NAME
-
-fsverity - userspace utility for fs-verity
-
-# SYNOPSIS
-**fsverity digest** [*OPTION*...] *FILE*... \
-**fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE* \
-**fsverity enable** [*OPTION*...] *FILE* \
-**fsverity measure** *FILE*... \
-**fsverity sign** [*OPTION*...] *FILE* *OUT_SIGFILE*
-
-# DESCRIPTION
-
-**fsverity** is a userspace utility for fs-verity.  fs-verity is a Linux kernel
-filesystem feature that does transparent on-demand verification of the contents
-of read-only files using Merkle trees.
-
-**fsverity** can enable fs-verity on files, retrieve the digests of fs-verity
-files, and sign files for use with fs-verity (among other things).
-**fsverity**'s functionality is divided among various subcommands.
-
-This manual page focuses on documenting all **fsverity** subcommands and
-options.  For examples and more information about the fs-verity kernel feature,
-see the references at the end of this page.
-
-# OPTIONS
-
-**fsverity** always accepts the following options:
-
-**\-\-help**
-:   Show the help, for either one subcommand or for all subcommands.
-
-**\-\-version**
-:   Show the version of fsverity-utils.
-
-# SUBCOMMANDS
-
-## **fsverity digest** [*OPTION*...] *FILE*...
-
-Compute the fs-verity digest of the given file(s).  This is mainly intended to
-used in preparation for signing the digest.  In some cases **fsverity sign**
-can be used instead to digest and sign the file in one step.
-
-Options accepted by **fsverity digest**:
-
-**\-\-block-size**=*BLOCK_SIZE*
-:   The Merkle tree block size (in bytes) to use.  This must be a power of 2 and
-    at least twice the size of the hash values.  However, note that currently
-    (as of Linux kernel v5.13), the Linux kernel implementations of fs-verity
-    only support the case where the Merkle tree block size is equal to the
-    system page size, usually 4096 bytes.  The default value of this option is
-    4096.
-
-**\-\-compact**
-:   When printing the file digest, only print the actual digest hex string;
-    don't print the algorithm name and filename.
-
-**\-\-for-builtin-sig**
-:   Format the file digest in a way that is compatible with the Linux kernel's
-    fs-verity built-in signature verification support.  This means formatting it
-    as a `struct fsverity_formatted_digest`.  Use this option if you are using
-    built-in signatures but are not using **fsverity sign** to do the signing.
-
-**\-\-hash-alg**=*HASH_ALG*
-:   The hash algorithm to use to build the Merkle tree.  Valid options are
-    sha256 and sha512.  Default is sha256.
-
-**\-\-out-merkle-tree**=*FILE*
-:   Write the computed Merkle tree to the given file.  The Merkle tree layout
-    will be the same as that used by the Linux kernel's
-    `FS_IOC_READ_VERITY_METADATA` ioctl.
-
-    Normally this option isn't useful, but it can be needed in cases where the
-    fs-verity metadata needs to be consumed by something other than one of the
-    native Linux kernel implementations of fs-verity.  This is not needed for
-    file signing.
-
-**\-\-out-descriptor**=*FILE*
-:   Write the computed fs-verity descriptor to the given file.
-
-    Normally this option isn't useful, but it can be needed in cases where the
-    fs-verity metadata needs to be consumed by something other than one of the
-    native Linux kernel implementations of fs-verity.  This is not needed for
-    file signing.
-
-**\-\-salt**=*SALT*
-:   The salt to use in the Merkle tree, as a hex string.  The salt is a value
-    that is prepended to every hashed block; it can be used to personalize the
-    hashing for a particular file or device.  The default is no salt.
-
-## **fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE*
-
-Dump the fs-verity metadata of the given file.  The file must have fs-verity
-enabled, and the filesystem must support the `FS_IOC_READ_VERITY_METADATA` ioctl
-(it was added in Linux v5.12).  This subcommand normally isn't useful, but it
-can be useful in cases where a userspace server program is serving a verity file
-to a client which implements fs-verity compatible verification.
-
-*TYPE* may be "merkle\_tree", "descriptor", or "signature", indicating the type
-of metadata to dump.  "signature" refers to the built-in signature, if present;
-userspace-managed signatures will not be included.
-
-Options accepted by **fsverity dump_metadata**:
-
-**\-\-length**=*LENGTH*
-:   Length in bytes to dump from the specified metadata item.  Only accepted in
-    combination with **\-\-offset**.
-
-**\-\-offset**=*offset*
-:   Offset in bytes into the specified metadata item at which to start dumping.
-    Only accepted in combination with **\-\-length**.
-
-## **fsverity enable** [*OPTION*...] *FILE*
-
-Enable fs-verity on the specified file.  This will only work if the filesystem
-supports fs-verity.
-
-Options accepted by **fsverity enable**:
-
-**\-\-block-size**=*BLOCK_SIZE*
-:   Same as for **fsverity digest**.
-
-**\-\-hash-alg**=*HASH_ALG*
-:   Same as for **fsverity digest**.
-
-**\-\-salt**=*SALT*
-:   Same as for **fsverity digest**.
-
-**\-\-signature**=*SIGFILE*
-:   Specifies the built-in signature to apply to the file.  *SIGFILE* must be a
-    file that contains the signature in PKCS#7 DER format, e.g. as produced by
-    the **fsverity sign** command.
-
-    Note that this option is only needed if the Linux kernel's fs-verity
-    built-in signature verification support is being used.  It is not needed if
-    the signatures will be verified in userspace, as in that case the signatures
-    should be stored separately.
-
-## **fsverity measure** *FILE*...
-
-Display the fs-verity digest of the given file(s).  The files must have
-fs-verity enabled.  The output will be the same as **fsverity digest** with
-the appropriate parameters, but **fsverity measure** will take constant time
-for each file regardless of the size of the file.
-
-**fsverity measure** does not accept any options.
-
-## **fsverity sign** [*OPTION*...] *FILE* *OUT_SIGFILE*
-
-Sign the given file for fs-verity, in a way that is compatible with the Linux
-kernel's fs-verity built-in signature verification support.  The signature will
-be written to *OUT_SIGFILE* in PKCS#7 DER format.
-
-The private key can be specified either by key file or by PKCS#11 token.  To use
-a key file, provide **\-\-key** and optionally **\-\-cert**.  To use a PKCS#11
-token, provide **\-\-pkcs11-engine**, **\-\-pkcs11-module**, **\-\-cert**, and
-optionally **\-\-pkcs11-keyid**.  PKCS#11 token support is unavailable when
-fsverity-utils was built with BoringSSL rather than OpenSSL.
-
-**fsverity sign** should only be used if you need compatibility with fs-verity
-built-in signatures.  It is not the only way to do signatures with fs-verity.
-For more information, see the fsverity-utils README.
-
-Options accepted by **fsverity sign**:
-
-**\-\-block-size**=*BLOCK_SIZE*
-:   Same as for **fsverity digest**.
-
-**\-\-cert**=*CERTFILE*
-:   Specifies the file that contains the certificate, in PEM format.  This
-    option is required if *KEYFILE* contains only the private key and not also
-    the certificate, or if a PKCS#11 token is used.
-
-**\-\-hash-alg**=*HASH_ALG*
-:   Same as for **fsverity digest**.
-
-**\-\-key**=*KEYFILE*
-:   Specifies the file that contains the private key, in PEM format.  This
-    option is required when not using a PKCS#11 token.
-
-**\-\-out-descriptor**=*FILE*
-:   Same as for **fsverity digest**.
-
-**\-\-out-merkle-tree**=*FILE*
-:   Same as for **fsverity digest**.
-
-**\-\-pkcs11-engine**=*SOFILE*
-:   Specifies the path to the OpenSSL PKCS#11 engine file.  This typically will
-    be a path to the libp11 .so file.  This option is required when using a
-    PKCS#11 token.
-
-**\-\-pkcs11-keyid**=*KEYID*
-:   Specifies the key identifier in the form of a PKCS#11 URI.  If not provided,
-    the default key associated with the token is used.  This option is only
-    applicable when using a PKCS#11 token.
-
-**\-\-pkcs11-module**=*SOFILE*
-:   Specifies the path to the PKCS#11 token-specific module library.  This
-    option is required when using a PKCS#11 token.
-
-**\-\-salt**=*SALT*
-:   Same as for **fsverity digest**.
-
-# SEE ALSO
-
-For example commands and more information, see the
-[README file for
-fsverity-utils](https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git/tree/README.md).
-
-Also see the [kernel documentation for
-fs-verity](https://www.kernel.org/doc/html/latest/filesystems/fsverity.html).
diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c
index fd9f4de..1a3c769 100644
--- a/programs/cmd_digest.c
+++ b/programs/cmd_digest.c
@@ -18,8 +18,6 @@
 	{"hash-alg",		required_argument, NULL, OPT_HASH_ALG},
 	{"block-size",		required_argument, NULL, OPT_BLOCK_SIZE},
 	{"salt",		required_argument, NULL, OPT_SALT},
-	{"out-merkle-tree",     required_argument, NULL, OPT_OUT_MERKLE_TREE},
-	{"out-descriptor",      required_argument, NULL, OPT_OUT_DESCRIPTOR},
 	{"compact",		no_argument,	   NULL, OPT_COMPACT},
 	{"for-builtin-sig",	no_argument,	   NULL, OPT_FOR_BUILTIN_SIG},
 	{NULL, 0, NULL, 0}
@@ -42,8 +40,6 @@
 		case OPT_HASH_ALG:
 		case OPT_BLOCK_SIZE:
 		case OPT_SALT:
-		case OPT_OUT_MERKLE_TREE:
-		case OPT_OUT_DESCRIPTOR:
 			if (!parse_tree_param(c, optarg, &tree_params))
 				goto out_usage;
 			break;
@@ -118,8 +114,7 @@
 	}
 	status = 0;
 out:
-	if (!destroy_tree_params(&tree_params) && status == 0)
-		status = 1;
+	destroy_tree_params(&tree_params);
 	return status;
 
 out_err:
diff --git a/programs/cmd_dump_metadata.c b/programs/cmd_dump_metadata.c
deleted file mode 100644
index 94cc8ec..0000000
--- a/programs/cmd_dump_metadata.c
+++ /dev/null
@@ -1,163 +0,0 @@
-// SPDX-License-Identifier: MIT
-/*
- * The 'fsverity dump_metadata' command
- *
- * Copyright 2021 Google LLC
- *
- * Use of this source code is governed by an MIT-style
- * license that can be found in the LICENSE file or at
- * https://opensource.org/licenses/MIT.
- */
-
-#include "fsverity.h"
-
-#include <fcntl.h>
-#include <getopt.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-static const struct option longopts[] = {
-	{"offset",	required_argument, NULL, OPT_OFFSET},
-	{"length",	required_argument, NULL, OPT_LENGTH},
-	{NULL, 0, NULL, 0}
-};
-
-static const struct {
-	const char *name;
-	int val;
-} metadata_types[] = {
-	{"merkle_tree", FS_VERITY_METADATA_TYPE_MERKLE_TREE},
-	{"descriptor", FS_VERITY_METADATA_TYPE_DESCRIPTOR},
-	{"signature", FS_VERITY_METADATA_TYPE_SIGNATURE},
-};
-
-static bool parse_metadata_type(const char *name, __u64 *val_ret)
-{
-	size_t i;
-
-	for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
-		if (strcmp(name, metadata_types[i].name) == 0) {
-			*val_ret = metadata_types[i].val;
-			return true;
-		}
-	}
-	error_msg("unknown metadata type: %s", name);
-	fputs("       Expected", stderr);
-	for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
-		if (i != 0 && ARRAY_SIZE(metadata_types) > 2)
-			putc(',', stderr);
-		putc(' ', stderr);
-		if (i != 0 && i == ARRAY_SIZE(metadata_types) - 1)
-			fputs("or ", stderr);
-		fprintf(stderr, "\"%s\"", metadata_types[i].name);
-	}
-	fprintf(stderr, "\n");
-	return false;
-}
-
-/* Dump the fs-verity metadata of the given file. */
-int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
-			       int argc, char *argv[])
-{
-	bool offset_specified = false;
-	bool length_specified = false;
-	struct filedes file = { .fd = -1 };
-	struct filedes stdout_filedes = { .fd = STDOUT_FILENO,
-					  .name = "stdout" };
-	struct fsverity_read_metadata_arg arg = { .length = 32768 };
-	void *buf = NULL;
-	char *tmp;
-	int c;
-	int status;
-	int bytes_read;
-
-	while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
-		switch (c) {
-		case OPT_OFFSET:
-			if (offset_specified) {
-				error_msg("--offset can only be specified once");
-				goto out_usage;
-			}
-			errno = 0;
-			arg.offset = strtoull(optarg, &tmp, 10);
-			if (errno || *tmp) {
-				error_msg("invalid value for --offset");
-				goto out_usage;
-			}
-			offset_specified = true;
-			break;
-		case OPT_LENGTH:
-			if (length_specified) {
-				error_msg("--length can only be specified once");
-				goto out_usage;
-			}
-			errno = 0;
-			arg.length = strtoull(optarg, &tmp, 10);
-			if (errno || *tmp || arg.length > SIZE_MAX) {
-				error_msg("invalid value for --length");
-				goto out_usage;
-			}
-			length_specified = true;
-			break;
-		default:
-			goto out_usage;
-		}
-	}
-
-	argv += optind;
-	argc -= optind;
-
-	if (argc != 2)
-		goto out_usage;
-
-	if (!parse_metadata_type(argv[0], &arg.metadata_type))
-		goto out_usage;
-
-	if (length_specified && !offset_specified) {
-		error_msg("--length specified without --offset");
-		goto out_usage;
-	}
-	if (offset_specified && !length_specified) {
-		error_msg("--offset specified without --length");
-		goto out_usage;
-	}
-
-	buf = xzalloc(arg.length);
-	arg.buf_ptr = (uintptr_t)buf;
-
-	if (!open_file(&file, argv[1], O_RDONLY, 0))
-		goto out_err;
-
-	/*
-	 * If --offset and --length were specified, then do only the single read
-	 * requested.  Otherwise read until EOF.
-	 */
-	do {
-		bytes_read = ioctl(file.fd, FS_IOC_READ_VERITY_METADATA, &arg);
-		if (bytes_read < 0) {
-			error_msg_errno("FS_IOC_READ_VERITY_METADATA failed on '%s'",
-					file.name);
-			goto out_err;
-		}
-		if (bytes_read == 0)
-			break;
-		if (!full_write(&stdout_filedes, buf, bytes_read))
-			goto out_err;
-		arg.offset += bytes_read;
-	} while (!length_specified);
-
-	status = 0;
-out:
-	free(buf);
-	filedes_close(&file);
-	return status;
-
-out_err:
-	status = 1;
-	goto out;
-
-out_usage:
-	usage(cmd, stderr);
-	status = 2;
-	goto out;
-}
diff --git a/programs/cmd_sign.c b/programs/cmd_sign.c
index aab8f00..47ba6a2 100644
--- a/programs/cmd_sign.c
+++ b/programs/cmd_sign.c
@@ -27,16 +27,11 @@
 }
 
 static const struct option longopts[] = {
-	{"key",		    required_argument, NULL, OPT_KEY},
-	{"cert",	    required_argument, NULL, OPT_CERT},
-	{"pkcs11-engine",   required_argument, NULL, OPT_PKCS11_ENGINE},
-	{"pkcs11-module",   required_argument, NULL, OPT_PKCS11_MODULE},
-	{"pkcs11-keyid",    required_argument, NULL, OPT_PKCS11_KEYID},
-	{"hash-alg",	    required_argument, NULL, OPT_HASH_ALG},
-	{"block-size",	    required_argument, NULL, OPT_BLOCK_SIZE},
-	{"salt",	    required_argument, NULL, OPT_SALT},
-	{"out-merkle-tree", required_argument, NULL, OPT_OUT_MERKLE_TREE},
-	{"out-descriptor",  required_argument, NULL, OPT_OUT_DESCRIPTOR},
+	{"hash-alg",	required_argument, NULL, OPT_HASH_ALG},
+	{"block-size",	required_argument, NULL, OPT_BLOCK_SIZE},
+	{"salt",	required_argument, NULL, OPT_SALT},
+	{"key",		required_argument, NULL, OPT_KEY},
+	{"cert",	required_argument, NULL, OPT_CERT},
 	{NULL, 0, NULL, 0}
 };
 
@@ -56,6 +51,12 @@
 
 	while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
 		switch (c) {
+		case OPT_HASH_ALG:
+		case OPT_BLOCK_SIZE:
+		case OPT_SALT:
+			if (!parse_tree_param(c, optarg, &tree_params))
+				goto out_usage;
+			break;
 		case OPT_KEY:
 			if (sig_params.keyfile != NULL) {
 				error_msg("--key can only be specified once");
@@ -70,35 +71,6 @@
 			}
 			sig_params.certfile = optarg;
 			break;
-		case OPT_PKCS11_ENGINE:
-			if (sig_params.pkcs11_engine != NULL) {
-				error_msg("--pkcs11-engine can only be specified once");
-				goto out_usage;
-			}
-			sig_params.pkcs11_engine = optarg;
-			break;
-		case OPT_PKCS11_MODULE:
-			if (sig_params.pkcs11_module != NULL) {
-				error_msg("--pkcs11-module can only be specified once");
-				goto out_usage;
-			}
-			sig_params.pkcs11_module = optarg;
-			break;
-		case OPT_PKCS11_KEYID:
-			if (sig_params.pkcs11_keyid != NULL) {
-				error_msg("--pkcs11-keyid can only be specified once");
-				goto out_usage;
-			}
-			sig_params.pkcs11_keyid = optarg;
-			break;
-		case OPT_HASH_ALG:
-		case OPT_BLOCK_SIZE:
-		case OPT_SALT:
-		case OPT_OUT_MERKLE_TREE:
-		case OPT_OUT_DESCRIPTOR:
-			if (!parse_tree_param(c, optarg, &tree_params))
-				goto out_usage;
-			break;
 		default:
 			goto out_usage;
 		}
@@ -110,6 +82,10 @@
 	if (argc != 2)
 		goto out_usage;
 
+	if (sig_params.keyfile == NULL) {
+		error_msg("Missing --key argument");
+		goto out_usage;
+	}
 	if (sig_params.certfile == NULL)
 		sig_params.certfile = sig_params.keyfile;
 
@@ -141,8 +117,7 @@
 	status = 0;
 out:
 	filedes_close(&file);
-	if (!destroy_tree_params(&tree_params) && status == 0)
-		status = 1;
+	destroy_tree_params(&tree_params);
 	free(digest);
 	free(sig);
 	return status;
diff --git a/programs/fsverity.c b/programs/fsverity.c
index e4e348b..b911b2e 100644
--- a/programs/fsverity.c
+++ b/programs/fsverity.c
@@ -11,7 +11,6 @@
 
 #include "fsverity.h"
 
-#include <fcntl.h>
 #include <limits.h>
 
 static const struct fsverity_command {
@@ -28,16 +27,9 @@
 		.usage_str =
 "    fsverity digest FILE...\n"
 "               [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
-"               [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
 "               [--compact] [--for-builtin-sig]\n"
 #ifndef _WIN32
 	}, {
-		.name = "dump_metadata",
-		.func = fsverity_cmd_dump_metadata,
-		.short_desc = "Dump the fs-verity metadata of the given file",
-		.usage_str =
-"    fsverity dump_metadata TYPE FILE [--offset=OFFSET] [--length=LENGTH]\n"
-	}, {
 		.name = "enable",
 		.func = fsverity_cmd_enable,
 		.short_desc = "Enable fs-verity on a file",
@@ -56,13 +48,11 @@
 	}, {
 		.name = "sign",
 		.func = fsverity_cmd_sign,
-		.short_desc = "Sign a file for fs-verity built-in signature verification",
+		.short_desc = "Sign a file for fs-verity",
 		.usage_str =
-"    fsverity sign FILE OUT_SIGFILE\n"
-"               [--key=KEYFILE] [--cert=CERTFILE] [--pkcs11-engine=SOFILE]\n"
-"               [--pkcs11-module=SOFILE] [--pkcs11-keyid=KEYID]\n"
+"    fsverity sign FILE OUT_SIGFILE --key=KEYFILE\n"
 "               [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
-"               [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
+"               [--cert=CERTFILE]\n"
 	}
 };
 
@@ -204,74 +194,6 @@
 	return true;
 }
 
-struct metadata_callback_ctx {
-	struct filedes merkle_tree_file;
-	struct filedes descriptor_file;
-	struct libfsverity_metadata_callbacks callbacks;
-};
-
-static int handle_merkle_tree_size(void *_ctx, u64 size)
-{
-	struct metadata_callback_ctx *ctx = _ctx;
-
-	if (!preallocate_file(&ctx->merkle_tree_file, size))
-		return -EIO;
-	return 0;
-}
-
-static int handle_merkle_tree_block(void *_ctx, const void *block, size_t size,
-				    u64 offset)
-{
-	struct metadata_callback_ctx *ctx = _ctx;
-
-	if (!full_pwrite(&ctx->merkle_tree_file, block, size, offset))
-		return -EIO;
-	return 0;
-}
-
-static int handle_descriptor(void *_ctx, const void *descriptor, size_t size)
-{
-	struct metadata_callback_ctx *ctx = _ctx;
-
-	if (!full_write(&ctx->descriptor_file, descriptor, size))
-		return -EIO;
-	return 0;
-}
-
-static bool parse_out_metadata_option(int opt_char, const char *arg,
-				      const struct libfsverity_metadata_callbacks **cbs)
-{
-	struct metadata_callback_ctx *ctx;
-	struct filedes *file;
-	const char *opt_name;
-
-	if (*cbs) {
-		ctx = (*cbs)->ctx;
-	} else {
-		ctx = xzalloc(sizeof(*ctx));
-		ctx->merkle_tree_file.fd = -1;
-		ctx->descriptor_file.fd = -1;
-		ctx->callbacks.ctx = ctx;
-		*cbs = &ctx->callbacks;
-	}
-
-	if (opt_char == OPT_OUT_MERKLE_TREE) {
-		file = &ctx->merkle_tree_file;
-		opt_name = "--out-merkle-tree";
-		ctx->callbacks.merkle_tree_size = handle_merkle_tree_size;
-		ctx->callbacks.merkle_tree_block = handle_merkle_tree_block;
-	} else {
-		file = &ctx->descriptor_file;
-		opt_name = "--out-descriptor";
-		ctx->callbacks.descriptor = handle_descriptor;
-	}
-	if (file->fd >= 0) {
-		error_msg("%s can only be specified once", opt_name);
-		return false;
-	}
-	return open_file(file, arg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
-}
-
 bool parse_tree_param(int opt_char, const char *arg,
 		      struct libfsverity_merkle_tree_params *params)
 {
@@ -283,30 +205,15 @@
 	case OPT_SALT:
 		return parse_salt_option(arg, (u8 **)&params->salt,
 					 &params->salt_size);
-	case OPT_OUT_MERKLE_TREE:
-	case OPT_OUT_DESCRIPTOR:
-		return parse_out_metadata_option(opt_char, arg,
-						 &params->metadata_callbacks);
 	default:
 		ASSERT(0);
 	}
 }
 
-bool destroy_tree_params(struct libfsverity_merkle_tree_params *params)
+void destroy_tree_params(struct libfsverity_merkle_tree_params *params)
 {
-	bool ok = true;
-
 	free((u8 *)params->salt);
-	if (params->metadata_callbacks) {
-		struct metadata_callback_ctx *ctx =
-			params->metadata_callbacks->ctx;
-
-		ok &= filedes_close(&ctx->merkle_tree_file);
-		ok &= filedes_close(&ctx->descriptor_file);
-		free(ctx);
-	}
 	memset(params, 0, sizeof(*params));
-	return ok;
 }
 
 int main(int argc, char *argv[])
diff --git a/programs/fsverity.h b/programs/fsverity.h
index ad54cc2..45c4fe1 100644
--- a/programs/fsverity.h
+++ b/programs/fsverity.h
@@ -27,13 +27,6 @@
 	OPT_FOR_BUILTIN_SIG,
 	OPT_HASH_ALG,
 	OPT_KEY,
-	OPT_LENGTH,
-	OPT_OFFSET,
-	OPT_OUT_DESCRIPTOR,
-	OPT_OUT_MERKLE_TREE,
-	OPT_PKCS11_ENGINE,
-	OPT_PKCS11_KEYID,
-	OPT_PKCS11_MODULE,
 	OPT_SALT,
 	OPT_SIGNATURE,
 };
@@ -44,10 +37,6 @@
 int fsverity_cmd_digest(const struct fsverity_command *cmd,
 			int argc, char *argv[]);
 
-/* cmd_dump_metadata.c */
-int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
-			       int argc, char *argv[]);
-
 /* cmd_enable.c */
 int fsverity_cmd_enable(const struct fsverity_command *cmd,
 			int argc, char *argv[]);
@@ -64,6 +53,6 @@
 void usage(const struct fsverity_command *cmd, FILE *fp);
 bool parse_tree_param(int opt_char, const char *arg,
 		      struct libfsverity_merkle_tree_params *params);
-bool destroy_tree_params(struct libfsverity_merkle_tree_params *params);
+void destroy_tree_params(struct libfsverity_merkle_tree_params *params);
 
 #endif /* PROGRAMS_FSVERITY_H */
diff --git a/programs/test_compute_digest.c b/programs/test_compute_digest.c
index 67266fa..e7f2645 100644
--- a/programs/test_compute_digest.c
+++ b/programs/test_compute_digest.c
@@ -13,7 +13,6 @@
 
 #include <ctype.h>
 #include <inttypes.h>
-#include <openssl/sha.h>
 
 struct mem_file {
 	u8 *data;
@@ -38,13 +37,6 @@
 	return -EIO;
 }
 
-static int zeroes_read_fn(void *fd __attribute__((unused)),
-			  void *buf, size_t count)
-{
-	memset(buf, 0, count);
-	return 0;
-}
-
 static const struct test_case {
 	u32 hash_algorithm;
 	u32 block_size;
@@ -257,130 +249,6 @@
 	ASSERT(d == NULL);
 }
 
-static struct {
-	u64 merkle_tree_size;
-	u64 merkle_tree_block;
-	u64 descriptor;
-} metadata_callback_counts;
-
-static int handle_merkle_tree_size(void *ctx, u64 size)
-{
-	metadata_callback_counts.merkle_tree_size++;
-
-	/* Test that the ctx argument is passed through correctly. */
-	ASSERT(ctx == (void *)1);
-
-	/* Test that the expected Merkle tree size is reported. */
-	ASSERT(size == 5 * 1024);
-	return 0;
-}
-
-static int handle_merkle_tree_block(void *ctx, const void *block, size_t size,
-				    u64 offset)
-{
-	u8 digest[SHA256_DIGEST_LENGTH];
-	u64 count = metadata_callback_counts.merkle_tree_block++;
-	const char *expected_digest;
-
-	/* Test that ->merkle_tree_size() was called first. */
-	ASSERT(metadata_callback_counts.merkle_tree_size == 1);
-
-	/* Test that the ctx argument is passed through correctly. */
-	ASSERT(ctx == (void *)1);
-
-	/*
-	 * Test that this Merkle tree block has the expected size, offset, and
-	 * contents.  The 4 blocks at "level 0" should be reported first, in
-	 * order; then the 1 block at "level 1" should be reported last (but the
-	 * level 1 block should have the smallest offset).
-	 */
-	ASSERT(size == 1024);
-	SHA256(block, size, digest);
-	if (count == 4) {
-		/* 1 block at level 1 */
-		ASSERT(offset == 0);
-		expected_digest = "\x68\xc5\x38\xe1\x19\x58\xd6\x5d"
-				  "\x68\xb6\xfe\x8e\x9f\xb8\xcc\xab"
-				  "\xec\xfd\x92\x8b\x01\xd0\x63\x44"
-				  "\xe2\x23\xed\x41\xdd\xc4\x54\x4a";
-	} else {
-		/* 4 blocks at level 0 */
-		ASSERT(offset == 1024 + (count * 1024));
-		if (count < 3) {
-			expected_digest = "\xf7\x89\xba\xab\x53\x85\x9f\xaf"
-					  "\x36\xd6\xd7\x5d\x10\x42\x06\x42"
-					  "\x94\x20\x2d\x6e\x13\xe7\x71\x6f"
-					  "\x39\x4f\xba\x43\x4c\xcc\x49\x86";
-		} else {
-			expected_digest = "\x00\xfe\xd0\x3c\x5d\x6e\xab\x21"
-					  "\x31\x43\xf3\xd9\x6a\x5c\xa3\x1c"
-					  "\x2b\x89\xf5\x68\x4e\x6c\x8e\x07"
-					  "\x87\x3e\x5e\x97\x65\x17\xb4\x8f";
-		}
-	}
-	ASSERT(!memcmp(digest, expected_digest, SHA256_DIGEST_LENGTH));
-	return 0;
-}
-
-static const u8 expected_file_digest[SHA256_DIGEST_LENGTH] =
-	"\x09\xcb\xba\xee\xd2\xa0\x4c\x2d\xa2\x42\xc1\x0e\x15\x68\xd9\x6f"
-	"\x35\x8a\x16\xaa\x1e\xbe\x8c\xf0\x28\x61\x20\xc1\x3c\x93\x66\xd1";
-
-static int handle_descriptor(void *ctx, const void *descriptor, size_t size)
-{
-	u8 digest[SHA256_DIGEST_LENGTH];
-
-	metadata_callback_counts.descriptor++;
-	/* Test that the ctx argument is passed through correctly. */
-	ASSERT(ctx == (void *)1);
-
-	/* Test that the fs-verity descriptor is reported correctly. */
-	ASSERT(size == 256);
-	SHA256(descriptor, size, digest);
-	ASSERT(!memcmp(digest, expected_file_digest, SHA256_DIGEST_LENGTH));
-	return 0;
-}
-
-static const struct libfsverity_metadata_callbacks metadata_callbacks = {
-	.ctx = (void *)1, /* arbitrary value for testing purposes */
-	.merkle_tree_size = handle_merkle_tree_size,
-	.merkle_tree_block = handle_merkle_tree_block,
-	.descriptor = handle_descriptor,
-};
-
-/* Test that the libfsverity_metadata_callbacks work correctly. */
-static void test_metadata_callbacks(void)
-{
-	/*
-	 * For a useful test, we want a file whose Merkle tree will have at
-	 * least 2 levels (this one will have exactly 2).  The contents of the
-	 * file aren't too important.
-	 */
-	struct libfsverity_merkle_tree_params params = {
-		.version = 1,
-		.hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
-		.block_size = 1024,
-		.file_size = 100000,
-		.metadata_callbacks = &metadata_callbacks,
-	};
-	struct libfsverity_digest *d;
-
-	ASSERT(libfsverity_compute_digest(NULL, zeroes_read_fn,
-					  &params, &d) == 0);
-
-	/* Test that the callbacks were called the correct number of times. */
-	ASSERT(metadata_callback_counts.merkle_tree_size == 1);
-	ASSERT(metadata_callback_counts.merkle_tree_block == 5);
-	ASSERT(metadata_callback_counts.descriptor == 1);
-
-	/* Test that the computed file digest is as expected. */
-	ASSERT(d->digest_algorithm == FS_VERITY_HASH_ALG_SHA256);
-	ASSERT(d->digest_size == SHA256_DIGEST_LENGTH);
-	ASSERT(!memcmp(d->digest, expected_file_digest, SHA256_DIGEST_LENGTH));
-
-	free(d);
-}
-
 int main(int argc, char *argv[])
 {
 	const bool update = (argc == 2 && !strcmp(argv[1], "--update"));
@@ -437,7 +305,6 @@
 	}
 
 	test_invalid_params();
-	test_metadata_callbacks();
 	printf("test_compute_digest passed\n");
 	return 0;
 }
diff --git a/programs/utils.c b/programs/utils.c
index 116eb95..ce19b57 100644
--- a/programs/utils.c
+++ b/programs/utils.c
@@ -13,14 +13,10 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <inttypes.h>
 #include <limits.h>
 #include <stdarg.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#ifdef _WIN32
-#  include <windows.h>
-#endif
 
 /* ========== Memory allocation ========== */
 
@@ -130,26 +126,6 @@
 	return true;
 }
 
-bool preallocate_file(struct filedes *file, u64 size)
-{
-	int res;
-
-	if (size == 0)
-		return true;
-#ifdef _WIN32
-	/* Not exactly the same as posix_fallocate(), but good enough... */
-	res = _chsize_s(file->fd, size);
-#else
-	res = posix_fallocate(file->fd, 0, size);
-#endif
-	if (res != 0) {
-		error_msg_errno("preallocating %" PRIu64 "-byte file '%s'",
-				size, file->name);
-		return false;
-	}
-	return true;
-}
-
 bool full_read(struct filedes *file, void *buf, size_t count)
 {
 	while (count) {
@@ -184,41 +160,6 @@
 	return true;
 }
 
-static int raw_pwrite(int fd, const void *buf, int count, u64 offset)
-{
-#ifdef _WIN32
-	HANDLE h = (HANDLE)_get_osfhandle(fd);
-	OVERLAPPED pos = { .Offset = offset, .OffsetHigh = offset >> 32 };
-	DWORD written = 0;
-
-	/* Not exactly the same as pwrite(), but good enough... */
-	if (!WriteFile(h, buf, count, &written, &pos)) {
-		errno = EIO;
-		return -1;
-	}
-	return written;
-#else
-	return pwrite(fd, buf, count, offset);
-#endif
-}
-
-bool full_pwrite(struct filedes *file, const void *buf, size_t count,
-		 u64 offset)
-{
-	while (count) {
-		int n = raw_pwrite(file->fd, buf, min(count, INT_MAX), offset);
-
-		if (n < 0) {
-			error_msg_errno("writing to '%s'", file->name);
-			return false;
-		}
-		buf += n;
-		count -= n;
-		offset += n;
-	}
-	return true;
-}
-
 bool filedes_close(struct filedes *file)
 {
 	int res;
diff --git a/programs/utils.h b/programs/utils.h
index 9a5c97a..ab5005f 100644
--- a/programs/utils.h
+++ b/programs/utils.h
@@ -40,11 +40,8 @@
 
 bool open_file(struct filedes *file, const char *filename, int flags, int mode);
 bool get_file_size(struct filedes *file, u64 *size_ret);
-bool preallocate_file(struct filedes *file, u64 size);
 bool full_read(struct filedes *file, void *buf, size_t count);
 bool full_write(struct filedes *file, const void *buf, size_t count);
-bool full_pwrite(struct filedes *file, const void *buf, size_t count,
-		 u64 offset);
 bool filedes_close(struct filedes *file);
 int read_callback(void *file, void *buf, size_t count);
 
diff --git a/scripts/do-release.sh b/scripts/do-release.sh
index 3f68497..352fcf1 100755
--- a/scripts/do-release.sh
+++ b/scripts/do-release.sh
@@ -9,73 +9,38 @@
 set -e -u -o pipefail
 cd "$(dirname "$0")/.."
 
-usage()
-{
-	echo "Usage: $0 prepare|publish VERS" 1>&2
-	echo "  e.g. $0 prepare 1.0" 1>&2
-	echo "       $0 publish 1.0" 1>&2
+if [ $# != 1 ]; then
+	echo "Usage: $0 VERS" 1>&2
+	echo "  e.g. $0 1.0" 1>&2
 	exit 2
-}
-
-if [ $# != 2 ]; then
-	usage
 fi
 
-PUBLISH=false
-case $1 in
-publish)
-	PUBLISH=true
-	;;
-prepare)
-	;;
-*)
-	usage
-	;;
-esac
-VERS=$2
+VERS=$1
 PKG=fsverity-utils-$VERS
 
-prepare_release()
-{
-	git checkout -f
-	git clean -fdx
-	./scripts/run-tests.sh
-	git clean -fdx
+git checkout -f
+git clean -fdx
+./scripts/run-tests.sh
+git clean -fdx
 
-	major=$(echo "$VERS" | cut -d. -f1)
-	minor=$(echo "$VERS" | cut -d. -f2)
-	month=$(LC_ALL=C date +%B)
-	year=$(LC_ALL=C date +%Y)
+major=$(echo "$VERS" | cut -d. -f1)
+minor=$(echo "$VERS" | cut -d. -f2)
+sed -E -i -e "/FSVERITY_UTILS_MAJOR_VERSION/s/[0-9]+/$major/" \
+	  -e "/FSVERITY_UTILS_MINOR_VERSION/s/[0-9]+/$minor/" \
+	  include/libfsverity.h
+sed -E -i "/Version:/s/[0-9]+\.[0-9]+/$VERS/" \
+	  lib/libfsverity.pc.in
+git commit -a --signoff --message="v$VERS"
+git tag --sign "v$VERS" --message="$PKG"
 
-	sed -E -i -e "/FSVERITY_UTILS_MAJOR_VERSION/s/[0-9]+/$major/" \
-		  -e "/FSVERITY_UTILS_MINOR_VERSION/s/[0-9]+/$minor/" \
-		  include/libfsverity.h
-	sed -E -i "/Version:/s/[0-9]+\.[0-9]+/$VERS/" \
-		  lib/libfsverity.pc.in
-	sed -E -i -e "/^% /s/fsverity-utils v[0-9]+(\.[0-9]+)+/fsverity-utils v$VERS/" \
-		  -e "/^% /s/[a-zA-Z]+ 2[0-9]{3}/$month $year/" \
-		  man/*.[1-9].md
-	git commit -a --signoff --message="v$VERS"
-	git tag --sign "v$VERS" --message="$PKG"
+git archive "v$VERS" --prefix="$PKG/" > "$PKG.tar"
+tar xf "$PKG.tar"
+( cd "$PKG" && make check )
+rm -r "$PKG"
 
-	git archive "v$VERS" --prefix="$PKG/" > "$PKG.tar"
-	tar xf "$PKG.tar"
-	( cd "$PKG" && make check )
-	rm -r "$PKG"
-}
-
-publish_release()
-{
-	gpg --detach-sign --armor "$PKG.tar"
-	DESTDIR=/pub/linux/kernel/people/ebiggers/fsverity-utils/v$VERS
-	kup mkdir "$DESTDIR"
-	kup put "$PKG.tar" "$PKG.tar.asc" "$DESTDIR/$PKG.tar.gz"
-	git push
-	git push --tags
-}
-
-if $PUBLISH; then
-	publish_release
-else
-	prepare_release
-fi
+gpg --detach-sign --armor "$PKG.tar"
+DESTDIR=/pub/linux/kernel/people/ebiggers/fsverity-utils/v$VERS
+kup mkdir "$DESTDIR"
+kup put "$PKG.tar" "$PKG.tar.asc" "$DESTDIR/$PKG.tar.gz"
+git push
+git push --tags
diff --git a/scripts/run-sparse.sh b/scripts/run-sparse.sh
index b8d37c1..f75b837 100755
--- a/scripts/run-sparse.sh
+++ b/scripts/run-sparse.sh
@@ -8,7 +8,7 @@
 
 set -e -u -o pipefail
 
-find programs lib -name '*.c' | while read -r file; do
+find . -name '*.c' | while read -r file; do
 	sparse "$file" -gcc-base-dir "$(gcc --print-file-name=)"	\
 		-Iinclude -D_FILE_OFFSET_BITS=64 -Wbitwise -D_GNU_SOURCE
 done
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index e2a4e38..530fe34 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -17,13 +17,11 @@
 set -e -u -o pipefail
 cd "$(dirname "$0")/.."
 
-log()
-{
+log() {
 	echo "[$(date)] $*" 1>&2
 }
 
-fail()
-{
+fail() {
 	echo "FAIL: $*" 1>&2
 	exit 1
 }
@@ -40,44 +38,31 @@
 
 MAKE="make -j$(getconf _NPROCESSORS_ONLN)"
 
-TEST_FUNCS=()
+log "Build and test with statically linking"
+$MAKE CFLAGS="-Werror"
+if ldd fsverity | grep libfsverity.so; then
+	fail "fsverity binary should be statically linked to libfsverity by default"
+fi
+./fsverity --version
 
-static_linking_test()
-{
-	log "Build and test with statically linking"
-	$MAKE CFLAGS="-Werror"
-	if ldd fsverity | grep libfsverity.so; then
-		fail "fsverity binary should be statically linked to libfsverity by default"
-	fi
-	./fsverity --version
+log "Check that all global symbols are prefixed with \"libfsverity_\""
+if nm libfsverity.a | grep ' T ' | grep -v " libfsverity_"; then
+	fail "Some global symbols are not prefixed with \"libfsverity_\""
+fi
 
-	log "Check that all global symbols are prefixed with \"libfsverity_\""
-	if nm libfsverity.a | grep ' T ' | grep -v " libfsverity_"; then
-		fail "Some global symbols are not prefixed with \"libfsverity_\""
-	fi
-}
-TEST_FUNCS+=(static_linking_test)
+log "Build and test with dynamic linking"
+$MAKE CFLAGS="-Werror" USE_SHARED_LIB=1 check
+if ! ldd fsverity | grep libfsverity.so; then
+	fail "fsverity binary should be dynamically linked to libfsverity when USE_SHARED_LIB=1"
+fi
 
-dynamic_linking_test()
-{
-	log "Build and test with dynamic linking"
-	$MAKE CFLAGS="-Werror" USE_SHARED_LIB=1 check
-	if ! ldd fsverity | grep libfsverity.so; then
-		fail "fsverity binary should be dynamically linked to libfsverity when USE_SHARED_LIB=1"
-	fi
+log "Check that all exported symbols are prefixed with \"libfsverity_\""
+if nm libfsverity.so | grep ' T ' | grep -v " libfsverity_"; then
+	fail "Some exported symbols are not prefixed with \"libfsverity_\""
+fi
 
-	log "Check that all exported symbols are prefixed with \"libfsverity_\""
-	if nm libfsverity.so | grep ' T ' | grep -v " libfsverity_"; then
-		fail "Some exported symbols are not prefixed with \"libfsverity_\""
-	fi
-}
-TEST_FUNCS+=(dynamic_linking_test)
-
-cplusplus_test()
-{
-	$MAKE CFLAGS="-Werror" libfsverity.so
-	log "Test using libfsverity from C++ program"
-	cat > "$TMPDIR/test.cc" <<EOF
+log "Test using libfsverity from C++ program"
+cat > "$TMPDIR/test.cc" <<EOF
 #include <libfsverity.h>
 #include <iostream>
 int main()
@@ -85,231 +70,118 @@
 	std::cout << libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256) << std::endl;
 }
 EOF
-	c++ -Wall -Werror "$TMPDIR/test.cc" -Iinclude -L. -lfsverity -o "$TMPDIR/test"
-	[ "$(LD_LIBRARY_PATH=. "$TMPDIR/test")" = "32" ]
-	rm "${TMPDIR:?}"/*
-}
-TEST_FUNCS+=(cplusplus_test)
+c++ -Wall -Werror "$TMPDIR/test.cc" -Iinclude -L. -lfsverity -o "$TMPDIR/test"
+[ "$(LD_LIBRARY_PATH=. "$TMPDIR/test")" = "32" ]
+rm "${TMPDIR:?}"/*
 
-untracked_files_test()
-{
-	log "Check that build doesn't produce untracked files"
-	$MAKE CFLAGS="-Werror" all test_programs
-	if git status --short | grep -q '^??'; then
-		git status
-		fail "Build produced untracked files (check 'git status').  Missing gitignore entry?"
-	fi
-}
-TEST_FUNCS+=(untracked_files_test)
-
-uninstall_test()
-{
-	log "Test that 'make uninstall' uninstalls all files"
-	make DESTDIR="$TMPDIR" install
-	if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" = 0 ]; then
-		fail "'make install' didn't install any files"
-	fi
-	make DESTDIR="$TMPDIR" uninstall
-	if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" != 0 ]; then
-		fail "'make uninstall' didn't uninstall all files"
-	fi
-	rm -r "${TMPDIR:?}"/*
-}
-TEST_FUNCS+=(uninstall_test)
-
-dash_test()
-{
-	log "Build, install, and uninstall with dash"
-	make clean SHELL=/bin/dash
-	make DESTDIR="$TMPDIR" SHELL=/bin/dash install
-	make DESTDIR="$TMPDIR" SHELL=/bin/dash uninstall
-}
-TEST_FUNCS+=(dash_test)
-
-license_test()
-{
-	log "Check that all files have license and copyright info"
-	list="$TMPDIR/filelist"
-	filter_license_info() {
-		# files to exclude from license and copyright info checks
-		grep -E -v '(\.gitignore|LICENSE|.*\.md|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
-	}
-	git grep -L 'SPDX-License-Identifier: MIT' \
-		| filter_license_info > "$list" || true
-	if [ -s "$list" ]; then
-		fail "The following files are missing an appropriate SPDX license identifier: $(<"$list")"
-	fi
-	# For now some people still prefer a free-form license statement, not just SPDX.
-	git grep -L 'Use of this source code is governed by an MIT-style' \
-		| filter_license_info > "$list" || true
-	if [ -s "$list" ]; then
-		fail "The following files are missing an appropriate license statement: $(<"$list")"
-	fi
-	git grep -L '\<Copyright\>' | filter_license_info > "$list" || true
-	if [ -s "$list" ]; then
-		fail "The following files are missing a copyright statement: $(<"$list")"
-	fi
-	rm "$list"
-}
-TEST_FUNCS+=(license_test)
-
-gcc_test()
-{
-	log "Build and test with gcc (-O2)"
-	$MAKE CC=gcc CFLAGS="-O2 -Werror" check
-
-	log "Build and test with gcc (-O3)"
-	$MAKE CC=gcc CFLAGS="-O3 -Werror" check
-}
-TEST_FUNCS+=(gcc_test)
-
-clang_test()
-{
-	log "Build and test with clang (-O2)"
-	$MAKE CC=clang CFLAGS="-O2 -Werror" check
-
-	log "Build and test with clang (-O3)"
-	$MAKE CC=clang CFLAGS="-O3 -Werror" check
-}
-TEST_FUNCS+=(clang_test)
-
-32bit_test()
-{
-	log "Build and test with gcc (32-bit)"
-	$MAKE CC=gcc CFLAGS="-O2 -Werror -m32" check
-}
-TEST_FUNCS+=(32bit_test)
-
-sanitizers_test()
-{
-	log "Build and test with clang + UBSAN"
-	$MAKE CC=clang \
-		CFLAGS="-O2 -Werror -fsanitize=undefined -fno-sanitize-recover=undefined" \
-		check
-
-	log "Build and test with clang + ASAN"
-	$MAKE CC=clang \
-		CFLAGS="-O2 -Werror -fsanitize=address -fno-sanitize-recover=address" \
-		check
-
-	log "Build and test with clang + unsigned integer overflow sanitizer"
-	$MAKE CC=clang \
-		CFLAGS="-O2 -Werror -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" \
-		check
-
-	log "Build and test with clang + CFI"
-	$MAKE CC=clang CFLAGS="-O2 -Werror -fsanitize=cfi -flto -fvisibility=hidden" \
-		AR=llvm-ar check
-}
-TEST_FUNCS+=(sanitizers_test)
-
-valgrind_test()
-{
-	log "Build and test with valgrind"
-	$MAKE TEST_WRAPPER_PROG="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" \
-		CFLAGS="-O2 -Werror" check
-}
-TEST_FUNCS+=(valgrind_test)
-
-boringssl_test()
-{
-	log "Build and test using BoringSSL instead of OpenSSL"
-	log "-> Building BoringSSL"
-	$MAKE boringssl
-	log "-> Building fsverity-utils linked to BoringSSL"
-	$MAKE CFLAGS="-O2 -Werror" LDFLAGS="-Lboringssl/build/crypto" \
-		CPPFLAGS="-Iboringssl/include" LDLIBS="-lcrypto -lpthread" check
-}
-TEST_FUNCS+=(boringssl_test)
-
-openssl1_test()
-{
-	log "Build and test using OpenSSL 1.0"
-	$MAKE CFLAGS="-O2 -Werror" LDFLAGS="-L/usr/lib/openssl-1.0" \
-		CPPFLAGS="-I/usr/include/openssl-1.0" check
-}
-TEST_FUNCS+=(openssl1_test)
-
-openssl3_test()
-{
-	log "Build and test using OpenSSL 3.0"
-	OSSL3=$HOME/src/openssl/inst/usr/local
-	LD_LIBRARY_PATH="$OSSL3/lib64" $MAKE CFLAGS="-O2 -Werror" \
-		LDFLAGS="-L$OSSL3/lib64" CPPFLAGS="-I$OSSL3/include" check
-}
-TEST_FUNCS+=(openssl3_test)
-
-unsigned_char_test()
-{
-	log "Build and test using -funsigned-char"
-	$MAKE CFLAGS="-O2 -Werror -funsigned-char" check
-}
-TEST_FUNCS+=(unsigned_char_test)
-
-signed_char_test()
-{
-	log "Build and test using -fsigned-char"
-	$MAKE CFLAGS="-O2 -Werror -fsigned-char" check
-}
-TEST_FUNCS+=(signed_char_test)
-
-windows_build_test()
-{
-	log "Cross-compile for Windows (32-bit)"
-	$MAKE CC=i686-w64-mingw32-gcc CFLAGS="-O2 -Werror"
-
-	log "Cross-compile for Windows (64-bit)"
-	$MAKE CC=x86_64-w64-mingw32-gcc CFLAGS="-O2 -Werror"
-}
-TEST_FUNCS+=(windows_build_test)
-
-sparse_test()
-{
-	log "Run sparse"
-	./scripts/run-sparse.sh
-}
-TEST_FUNCS+=(sparse_test)
-
-clang_analyzer_test()
-{
-	log "Run clang static analyzer"
-	scan-build --status-bugs make CFLAGS="-O2 -Werror" all test_programs
-}
-TEST_FUNCS+=(clang_analyzer_test)
-
-shellcheck_test()
-{
-	log "Run shellcheck"
-	shellcheck scripts/*.sh 1>&2
-}
-TEST_FUNCS+=(shellcheck_test)
-
-test_exists()
-{
-	local tst=$1
-	local func
-	for func in "${TEST_FUNCS[@]}"; do
-		if [ "${tst}_test" = "$func" ]; then
-			return 0
-		fi
-	done
-	return 1
-}
-
-if [[ $# == 0 ]]; then
-	for func in "${TEST_FUNCS[@]}"; do
-		eval "$func"
-	done
-else
-	for tst; do
-		if ! test_exists "$tst"; then
-			echo 1>&2 "Unknown test: $tst"
-			exit 2
-		fi
-	done
-	for tst; do
-		eval "${tst}_test"
-	done
+log "Check that build doesn't produce untracked files"
+$MAKE CFLAGS="-Werror" all test_programs
+if git status --short | grep -q '^??'; then
+	git status
+	fail "Build produced untracked files (check 'git status').  Missing gitignore entry?"
 fi
 
+log "Test that 'make uninstall' uninstalls all files"
+make DESTDIR="$TMPDIR" install
+if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" = 0 ]; then
+	fail "'make install' didn't install any files"
+fi
+make DESTDIR="$TMPDIR" uninstall
+if [ "$(find "$TMPDIR" -type f -o -type l | wc -l)" != 0 ]; then
+	fail "'make uninstall' didn't uninstall all files"
+fi
+rm -r "${TMPDIR:?}"/*
+
+log "Build, install, and uninstall with dash"
+make clean SHELL=/bin/dash
+make DESTDIR="$TMPDIR" SHELL=/bin/dash install
+make DESTDIR="$TMPDIR" SHELL=/bin/dash uninstall
+
+log "Check that all files have license and copyright info"
+list="$TMPDIR/filelist"
+filter_license_info() {
+	# files to exclude from license and copyright info checks
+	grep -E -v '(\.gitignore|LICENSE|NEWS|README|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
+}
+git grep -L 'SPDX-License-Identifier: MIT' \
+	| filter_license_info > "$list" || true
+if [ -s "$list" ]; then
+	fail "The following files are missing an appropriate SPDX license identifier: $(<"$list")"
+fi
+# For now some people still prefer a free-form license statement, not just SPDX.
+git grep -L 'Use of this source code is governed by an MIT-style' \
+	| filter_license_info > "$list" || true
+if [ -s "$list" ]; then
+	fail "The following files are missing an appropriate license statement: $(<"$list")"
+fi
+git grep -L '\<Copyright\>' | filter_license_info > "$list" || true
+if [ -s "$list" ]; then
+	fail "The following files are missing a copyright statement: $(<"$list")"
+fi
+rm "$list"
+
+log "Build and test with gcc (-O2)"
+$MAKE CC=gcc CFLAGS="-O2 -Werror" check
+
+log "Build and test with gcc (-O3)"
+$MAKE CC=gcc CFLAGS="-O3 -Werror" check
+
+log "Build and test with gcc (32-bit)"
+$MAKE CC=gcc CFLAGS="-O2 -Werror -m32" check
+
+log "Build and test with clang (-O2)"
+$MAKE CC=clang CFLAGS="-O2 -Werror" check
+
+log "Build and test with clang (-O3)"
+$MAKE CC=clang CFLAGS="-O3 -Werror" check
+
+log "Build and test with clang + UBSAN"
+$MAKE CC=clang \
+	CFLAGS="-O2 -Werror -fsanitize=undefined -fno-sanitize-recover=undefined" \
+	check
+
+log "Build and test with clang + ASAN"
+$MAKE CC=clang \
+	CFLAGS="-O2 -Werror -fsanitize=address -fno-sanitize-recover=address" \
+	check
+
+log "Build and test with clang + unsigned integer overflow sanitizer"
+$MAKE CC=clang \
+	CFLAGS="-O2 -Werror -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" \
+	check
+
+log "Build and test with clang + CFI"
+$MAKE CC=clang CFLAGS="-O2 -Werror -fsanitize=cfi -flto -fvisibility=hidden" \
+	check
+
+log "Build and test with valgrind"
+$MAKE TEST_WRAPPER_PROG="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all" \
+	CFLAGS="-O2 -Werror" check
+
+log "Build and test using BoringSSL instead of OpenSSL"
+BSSL=$HOME/src/boringssl
+$MAKE CFLAGS="-O2 -Werror" LDFLAGS="-L$BSSL/build/crypto" \
+	CPPFLAGS="-I$BSSL/include" LDLIBS="-lcrypto -lpthread" check
+
+log "Build and test using -funsigned-char"
+$MAKE CFLAGS="-O2 -Werror -funsigned-char" check
+
+log "Build and test using -fsigned-char"
+$MAKE CFLAGS="-O2 -Werror -fsigned-char" check
+
+log "Cross-compile for Windows (32-bit)"
+$MAKE CC=i686-w64-mingw32-gcc CFLAGS="-O2 -Werror"
+
+log "Cross-compile for Windows (64-bit)"
+$MAKE CC=x86_64-w64-mingw32-gcc CFLAGS="-O2 -Werror"
+
+log "Run sparse"
+./scripts/run-sparse.sh
+
+log "Run clang static analyzer"
+scan-build --status-bugs make CFLAGS="-O2 -Werror" all test_programs
+
+log "Run shellcheck"
+shellcheck scripts/*.sh 1>&2
+
 log "All tests passed!"