commit:     d16f69568285d76dfa8a367f2458bc16f9e049e4
Author:     Fabian Groffen <grobian <AT> gentoo <DOT> org>
AuthorDate: Mon May 20 17:53:48 2019 +0000
Commit:     Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Mon May 20 17:53:48 2019 +0000
URL:        https://gitweb.gentoo.org/proj/portage-utils.git/commit/?id=d16f6956

qmanifest: new applet to verify and generate thick Manifests

This incorporates https://github.com/grobian/hashgen into portage-utils
as qmanifest.

Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org>

 Makefile.am                |   10 +
 applets.h                  |   24 +-
 configure.ac               |   73 ++
 libq/Makefile.am           |    2 +
 libq/hash.c                |  196 +++++
 libq/hash.h                |   26 +
 man/include/qmanifest.desc |   29 +
 man/qmanifest.1            |   94 +++
 qmanifest.c                | 1695 ++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 2140 insertions(+), 9 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index b36173c..f28a073 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,7 @@ APPLETS = \
        qkeyword \
        qlist \
        qlop \
+       qmanifest \
        qmerge \
        qpkg \
        qsearch \
@@ -37,6 +38,7 @@ dist_man_MANS = \
        man/qkeyword.1 \
        man/qlist.1 \
        man/qlop.1 \
+       man/qmanifest.1 \
        man/qmerge.1 \
        man/qpkg.1 \
        man/qsearch.1 \
@@ -59,6 +61,7 @@ q_SOURCES = \
        qkeyword.c \
        qlist.c \
        qlop.c \
+       qmanifest.c \
        qmerge.c \
        qpkg.c \
        qsearch.c \
@@ -69,14 +72,21 @@ q_SOURCES = \
        qxpak.c \
        $(NULL)
 q_CPPFLAGS = \
+       $(OPENMP_CFLAGS) \
+       $(GPGME_CFLAGS) \
        -I$(top_srcdir)/libq \
        -I$(top_builddir)/autotools/gnulib \
        -I$(top_srcdir)/autotools/gnulib \
        $(NULL)
 q_LDADD = \
+       $(OPENMP_CFLAGS) \
        $(top_builddir)/libq/libq.la \
        $(top_builddir)/autotools/gnulib/libgnu.a \
        -liniparser \
+       $(LIBSSL) \
+       $(LIBBL2) \
+       $(LIBZ) \
+       $(GPGME_LIBS) \
        $(LIB_CLOCK_GETTIME) \
        $(LIB_EACCESS) \
        $(NULL)

diff --git a/applets.h b/applets.h
index fbb4e8c..902e664 100644
--- a/applets.h
+++ b/applets.h
@@ -38,23 +38,24 @@ typedef int (*APPLET)(int, char **);
 #define DECLARE_APPLET(applet) \
        extern int applet##_main(int, char **) __attribute__((weak));
 DECLARE_APPLET(q)
+DECLARE_APPLET(qatom)
 DECLARE_APPLET(qcheck)
 DECLARE_APPLET(qdepends)
 DECLARE_APPLET(qfile)
+/*DECLARE_APPLET(qglsa) disable */
+DECLARE_APPLET(qgrep)
+DECLARE_APPLET(qkeyword)
 DECLARE_APPLET(qlist)
 DECLARE_APPLET(qlop)
+DECLARE_APPLET(qmanifest)
+DECLARE_APPLET(qmerge)
+DECLARE_APPLET(qpkg)
 DECLARE_APPLET(qsearch)
 DECLARE_APPLET(qsize)
 DECLARE_APPLET(qtbz2)
+DECLARE_APPLET(qtegrity)
 DECLARE_APPLET(quse)
 DECLARE_APPLET(qxpak)
-DECLARE_APPLET(qpkg)
-DECLARE_APPLET(qgrep)
-DECLARE_APPLET(qatom)
-DECLARE_APPLET(qmerge)
-DECLARE_APPLET(qkeyword)
-/*DECLARE_APPLET(qglsa) disable */
-DECLARE_APPLET(qtegrity)
 #undef DECLARE_APPLET
 
 static const struct applet_t {
@@ -66,7 +67,6 @@ static const struct applet_t {
        /* q must always be the first applet */
        {"q",         q_main,         "<applet> <args>", "virtual applet"},
        {"qatom",     qatom_main,     "<pkg>",           "split atom strings"},
-       {"qkeyword",  qkeyword_main,  "<action> <args>", "list packages based 
on keywords"},
        {"qcheck",    qcheck_main,    "<pkgname>",       "verify integrity of 
installed packages"},
        {"qdepends",  qdepends_main,  "<pkgname>",       "show dependency 
info"},
        {"qfile",     qfile_main,     "<filename>",      "list all pkgs owning 
files"},
@@ -74,16 +74,18 @@ static const struct applet_t {
        {"qglsa",     qglsa_main,     "<action> <list>", "check GLSAs against 
system"},
        */
        {"qgrep",     qgrep_main,     "<expr> [pkg ...]", "grep in ebuilds"},
+       {"qkeyword",  qkeyword_main,  "<action> <args>", "list packages based 
on keywords"},
        {"qlist",     qlist_main,     "<pkgname>",       "list files owned by 
pkgname"},
        {"qlop",      qlop_main,      "<pkgname>",       "emerge log analyzer"},
+       {"qmanifest", qmanifest_main, "<misc args>",     "verify or generate 
thick Manifest files"},
        {"qmerge",    qmerge_main,    "<pkgnames>",      "fetch and merge 
binary package"},
        {"qpkg",      qpkg_main,      "<misc args>",     "manipulate Gentoo 
binpkgs"},
        {"qsearch",   qsearch_main,   "<regex>",         "search pkgname/desc"},
        {"qsize",     qsize_main,     "<pkgname>",       "calculate size 
usage"},
        {"qtbz2",     qtbz2_main,     "<misc args>",     "manipulate tbz2 
packages"},
+       {"qtegrity",  qtegrity_main,  "<misc args>",     "verify files with 
IMA"},
        {"quse",      quse_main,      "<useflag>",       "find pkgs using 
useflags"},
        {"qxpak",     qxpak_main,     "<misc args>",     "manipulate xpak 
archives"},
-       {"qtegrity",  qtegrity_main,  "<misc args>",     "verify files with 
IMA"},
 
        /* aliases for equery compatibility */
        {"belongs",   qfile_main,     NULL, NULL},
@@ -107,6 +109,10 @@ static const struct applet_t {
        /* alias for qtegrity */
        {"integrity", qtegrity_main,  NULL, NULL},
 
+       /* old hashgen */
+       {"hashgen",   qmanifest_main, NULL, NULL},
+       {"hashverify",qmanifest_main, NULL, NULL},
+
        {NULL, NULL, NULL, NULL}
 };
 

diff --git a/configure.ac b/configure.ac
index 43a4609..2a39df8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,6 +8,7 @@ AC_CONFIG_MACRO_DIR([autotools/m4])
 
 AC_PROG_CC_C99
 AM_PROG_CC_C_O
+AC_OPENMP
 AC_USE_SYSTEM_EXTENSIONS
 
 gl_EARLY
@@ -31,6 +32,78 @@ AC_DEFINE_UNQUOTED([CONFIG_EPREFIX], ["$with_eprefix"],
                                   [Gentoo Prefix offset path])
 AC_SUBST([CONFIG_EPREFIX], ["$with_eprefix"])
 
+AC_ARG_ENABLE([qmanifest], [AS_HELP_STRING([--enable-qmanifest],
+                         [support qmanifest applet])],
+                         [], [enable_qmanifest=auto])
+LIBSSL=
+LIBBL2=
+LIBZ=
+HASGPGME=
+AS_IF([test "x$enable_qmanifest" != xno],
+         [AC_CHECK_HEADERS([openssl/err.h \
+                            openssl/ssl.h], [], [LIBSSL=_missing_header])
+          AC_CHECK_LIB([ssl${LIBSSL}], [SSL_connect],
+                                       [LIBSSL="-lssl"
+                                        AC_DEFINE([HAVE_SSL], [1], [Define if 
you have ssl])
+                                        AC_CHECK_LIB([crypto],
+                                                                 
[ERR_reason_error_string],
+                                                                 
[LIBSSL="${LIBSSL} -lcrypto"],
+                                                                 [])
+                                        AC_SUBST([LIBSSL], ["${LIBSSL}"])
+                                       ],
+                                       [if test "x$enable_qmanifest" != xauto; 
then
+                                        AC_MSG_FAILURE(
+                                          [--enable-qmanifest was given, but 
test for ssl failed])
+                                        fi
+                                        LIBSSL=
+                                       ])
+          AC_CHECK_HEADERS([blake2.h], [], [LIBBL2=_missing_header])
+          AC_CHECK_LIB([b2${LIBBL2}], [blake2b_update],
+                                       [LIBBL2="-lb2"
+                                        AC_DEFINE([HAVE_BLAKE2B], [1],
+                                                          [Define if you have 
blake2b])
+                                        AC_SUBST([LIBBL2], ["${LIBBL2}"])
+                                       ],
+                                       [if test "x$enable_qmanifest" != xauto; 
then
+                                        AC_MSG_FAILURE(
+                                          [--enable-qmanifest was given, but 
test for blake2b failed])
+                                        fi
+                                        LIBBL2=
+                                       ])
+          AC_CHECK_HEADERS([zlib.h], [], [LIBZ=_missing_header])
+          AC_CHECK_LIB([z${LIBZ}], [gzopen],
+                                       [LIBZ="-lz"
+                                        AC_DEFINE([HAVE_LIBZ], [1],
+                                                          [Define if you have 
zlib])
+                                        AC_SUBST([LIBZ], ["${LIBZ}"])
+                                       ],
+                                       [if test "x$enable_qmanifest" != xauto; 
then
+                                        AC_MSG_FAILURE(
+                                          [--enable-qmanifest was given, but 
test for libz failed])
+                                        fi
+                                        LIBZ=
+                                       ])
+          AM_PATH_GPGME([], [HASGPGME=yes])
+          AC_MSG_CHECKING([whether to enable qmanifest])
+          case "x${LIBSSL}${LIBBL2}${LIBZ}-${HASGPGME}" in
+                  "x-lssl"*"-lb2-lz-yes")
+                               AC_MSG_RESULT([yes])
+                               ;;
+                  *)
+                          enable_qmanifest=no
+                          AC_MSG_RESULT([no: missing dependencies])
+                          ;;
+          esac
+          if test "x$enable_qmanifest" != xno ; then
+                  AC_DEFINE([ENABLE_QMANIFEST], [1],
+                                        [Define if qmanifest should be 
compiled])
+          fi
+       ],
+       [
+          AC_MSG_CHECKING([whether to enable qmanifest])
+          AC_MSG_RESULT([no: disabled by configure argument])
+       ])
+
 AX_CFLAGS_WARN_ALL
 AC_DEFUN([PT_CHECK_CFLAG],[AX_CHECK_COMPILER_FLAGS([$1],[CFLAGS="$CFLAGS 
$1"])])
 m4_foreach_w([flag], [

diff --git a/libq/Makefile.am b/libq/Makefile.am
index 62ffb83..9cb19a2 100644
--- a/libq/Makefile.am
+++ b/libq/Makefile.am
@@ -8,6 +8,7 @@ QFILES = \
        copy_file.c copy_file.h \
        dep.c dep.h \
        eat_file.c eat_file.h \
+       hash.c hash.h \
        hash_fd.c hash_fd.h \
        human_readable.c human_readable.h \
        i18n.h \
@@ -31,5 +32,6 @@ QFILES = \
 noinst_LTLIBRARIES = libq.la
 libq_la_SOURCES = $(QFILES)
 libq_la_CPPFLAGS = \
+       $(OPENMP_CFLAGS) \
        -I$(top_builddir)/autotools/gnulib \
        -I$(top_srcdir)/autotools/gnulib

diff --git a/libq/hash.c b/libq/hash.c
new file mode 100644
index 0000000..5b13d49
--- /dev/null
+++ b/libq/hash.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2018-2019 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ *
+ * Copyright 2018-     Fabian Groffen  - <grob...@gentoo.org>
+ *
+ * The contents of this file was taken from:
+ *   https://github.com/grobian/hashgen
+ * which was discontinued at the time the sources were incorporated into
+ * portage-utils as qmanifest.
+ */
+
+#include "main.h"
+
+#ifdef HAVE_SSL
+#include <openssl/sha.h>
+#include <openssl/whrlpool.h>
+#endif
+#ifdef HAVE_BLAKE2B
+#include <blake2.h>
+#endif
+
+#include "hash.h"
+
+static int hashes = HASH_DEFAULT;
+
+void
+hash_hex(char *out, const unsigned char *buf, const int length)
+{
+       switch (length) {
+               /* SHA256_DIGEST_LENGTH */
+               case 32:
+                       snprintf(out, 64 + 1,
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       "%02x%02x",
+                                       buf[ 0], buf[ 1], buf[ 2], buf[ 3], 
buf[ 4],
+                                       buf[ 5], buf[ 6], buf[ 7], buf[ 8], 
buf[ 9],
+                                       buf[10], buf[11], buf[12], buf[13], 
buf[14],
+                                       buf[15], buf[16], buf[17], buf[18], 
buf[19],
+                                       buf[20], buf[21], buf[22], buf[23], 
buf[24],
+                                       buf[25], buf[26], buf[27], buf[28], 
buf[29],
+                                       buf[30], buf[31]
+                                       );
+                       break;
+               /* SHA512_DIGEST_LENGTH, WHIRLPOOL_DIGEST_LENGTH, 
BLAKE2B_OUTBYTES */
+               case 64:
+                       snprintf(out, 128 + 1,
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+                                       "%02x%02x%02x%02x",
+                                       buf[ 0], buf[ 1], buf[ 2], buf[ 3], 
buf[ 4],
+                                       buf[ 5], buf[ 6], buf[ 7], buf[ 8], 
buf[ 9],
+                                       buf[10], buf[11], buf[12], buf[13], 
buf[14],
+                                       buf[15], buf[16], buf[17], buf[18], 
buf[19],
+                                       buf[20], buf[21], buf[22], buf[23], 
buf[24],
+                                       buf[25], buf[26], buf[27], buf[28], 
buf[29],
+                                       buf[30], buf[31], buf[32], buf[33], 
buf[34],
+                                       buf[35], buf[36], buf[37], buf[38], 
buf[39],
+                                       buf[40], buf[41], buf[42], buf[43], 
buf[44],
+                                       buf[45], buf[46], buf[47], buf[48], 
buf[49],
+                                       buf[50], buf[51], buf[52], buf[53], 
buf[54],
+                                       buf[55], buf[56], buf[57], buf[58], 
buf[59],
+                                       buf[60], buf[61], buf[62], buf[63]
+                                       );
+                       break;
+               /* fallback case, should never be necessary */
+               default:
+                       {
+                               int i;
+                               for (i = 0; i < length; i++) {
+                                       snprintf(&out[i * 2], 3, "%02x", 
buf[i]);
+                               }
+                       }
+                       break;
+       }
+}
+
+/**
+ * Computes the hashes for file fname and writes the hex-representation
+ * for those hashes into the address space pointed to by the return
+ * pointers for these hashes.  The caller should ensure enough space is
+ * available.  Only those hashes which are in the global hashes variable
+ * are computed, the address space pointed to for non-used hashes are
+ * left untouched, e.g. they can be NULL.  The number of bytes read from
+ * the file pointed to by fname is returned in the flen argument.
+ */
+void
+hash_compute_file(
+               const char *fname,
+               char *sha256,
+               char *sha512,
+               char *whrlpl,
+               char *blak2b,
+               size_t *flen)
+{
+       FILE *f;
+       char data[8192];
+       size_t len;
+#ifdef HAVE_SSL
+       SHA256_CTX s256;
+       SHA512_CTX s512;
+       WHIRLPOOL_CTX whrl;
+#endif
+#ifdef HAVE_BLAKE2B
+       blake2b_state bl2b;
+#endif
+
+       if ((f = fopen(fname, "r")) == NULL)
+               return;
+
+#ifdef HAVE_SSL
+       SHA256_Init(&s256);
+       SHA512_Init(&s512);
+       WHIRLPOOL_Init(&whrl);
+#endif
+#ifdef HAVE_BLAKE2B
+       blake2b_init(&bl2b, BLAKE2B_OUTBYTES);
+#endif
+
+       while ((len = fread(data, 1, sizeof(data), f)) > 0) {
+               *flen += len;
+#pragma omp parallel sections
+               {
+#ifdef HAVE_SSL
+#pragma omp section
+                       {
+                               if (hashes & HASH_SHA256)
+                                       SHA256_Update(&s256, data, len);
+                       }
+#pragma omp section
+                       {
+                               if (hashes & HASH_SHA512)
+                                       SHA512_Update(&s512, data, len);
+                       }
+#pragma omp section
+                       {
+                               if (hashes & HASH_WHIRLPOOL)
+                                       WHIRLPOOL_Update(&whrl, data, len);
+                       }
+#endif
+#ifdef HAVE_BLAKE2B
+#pragma omp section
+                       {
+                               if (hashes & HASH_BLAKE2B)
+                                       blake2b_update(&bl2b, (unsigned char 
*)data, len);
+                       }
+#endif
+               }
+       }
+       fclose(f);
+
+#pragma omp parallel sections
+       {
+#ifdef HAVE_SSL
+               {
+                       if (hashes & HASH_SHA256) {
+                               unsigned char sha256buf[SHA256_DIGEST_LENGTH];
+                               SHA256_Final(sha256buf, &s256);
+                               hash_hex(sha256, sha256buf, 
SHA256_DIGEST_LENGTH);
+                       }
+               }
+#pragma omp section
+               {
+                       if (hashes & HASH_SHA512) {
+                               unsigned char sha512buf[SHA512_DIGEST_LENGTH];
+                               SHA512_Final(sha512buf, &s512);
+                               hash_hex(sha512, sha512buf, 
SHA512_DIGEST_LENGTH);
+                       }
+               }
+#pragma omp section
+               {
+                       if (hashes & HASH_WHIRLPOOL) {
+                               unsigned char 
whrlplbuf[WHIRLPOOL_DIGEST_LENGTH];
+                               WHIRLPOOL_Final(whrlplbuf, &whrl);
+                               hash_hex(whrlpl, whrlplbuf, 
WHIRLPOOL_DIGEST_LENGTH);
+                       }
+               }
+#endif
+#ifdef HAVE_BLAKE2B
+#pragma omp section
+               {
+                       if (hashes & HASH_BLAKE2B) {
+                               unsigned char blak2bbuf[BLAKE2B_OUTBYTES];
+                               blake2b_final(&bl2b, blak2bbuf, 
BLAKE2B_OUTBYTES);
+                               hash_hex(blak2b, blak2bbuf, BLAKE2B_OUTBYTES);
+                       }
+               }
+#endif
+       }
+}

diff --git a/libq/hash.h b/libq/hash.h
new file mode 100644
index 0000000..157c454
--- /dev/null
+++ b/libq/hash.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-2019 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ *
+ * Copyright 2018-     Fabian Groffen  - <grob...@gentoo.org>
+ */
+
+#ifndef _HASH_H
+#define _HASH_H 1
+
+enum hash_impls {
+       HASH_SHA256    = 1<<0,
+       HASH_SHA512    = 1<<1,
+       HASH_WHIRLPOOL = 1<<2,
+       HASH_BLAKE2B   = 1<<3
+};
+
+/* default changed from sha256, sha512, whirlpool
+ * to blake2b, sha512 on 2017-11-21 */
+#define HASH_DEFAULT  (HASH_BLAKE2B | HASH_SHA512);
+
+void hash_hex(char *out, const unsigned char *buf, const int length);
+void hash_compute_file(const char *fname, char *sha256, char *sha512,
+               char *whrlpl, char *blak2b, size_t *flen);
+
+#endif

diff --git a/man/include/qmanifest.desc b/man/include/qmanifest.desc
new file mode 100644
index 0000000..b873419
--- /dev/null
+++ b/man/include/qmanifest.desc
@@ -0,0 +1,29 @@
+\fIqmanifest\fR allows to verify or generate thick signed Manifests in
+an ebuild tree.  By default, \fIqmanifest\fR will verify the main tree
+as specified by Portage's configuration (one can check which using
+\fIq\fR \fB-o\fR).  In this mode, it will output some information about
+the GPG signature of the top-level Manifest file, and further reports on
+any problems it encounters.
+.P
+This applet was originally a standalone program \fIhashgen\fR and its
+alias \fIhashverify\fR.  Aliases for these names are still available for
+historical reasons.  With the incorporation of \fIhashgen\fR in
+\fBportage-utils\fR, development on the former has stopped in favour of
+the latter.
+.P
+The arguments to \fIqmanifest\fR can be directories or names of
+overlays.  By default, each argument is attempted to be matched against
+all overlay names, and if that fails, treated as directory to presume a
+tree is in.  This behaviour can be overridden with the \fB-d\fR and
+\fB-o\fR flags to force treating the arguments as directories or
+overlay names respectively.  Note that overlay names are those as
+defined in \fIrepos.conf\fR from Portage's configuration.  The
+\fIrepo_name\fR files from the overlays themselves (if present) are
+ignored.
+.P
+This applet does similar things as \fIapp-portage/gemato\fR.  However,
+the output and implemented strategies are completely different.  When
+compiled with \fBUSE=openmp\fR, this applet will exploit parallelism
+where possible to traverse a tree.  Should you want to limit the number
+of parallel threads, export \fBOMP_NUM_THREADS\fR in your environment
+with the desired maximum amount of threads in use by \fIqmanifest\fR.

diff --git a/man/qmanifest.1 b/man/qmanifest.1
new file mode 100644
index 0000000..e223122
--- /dev/null
+++ b/man/qmanifest.1
@@ -0,0 +1,94 @@
+.\" generated by mkman.py, please do NOT edit!
+.TH qmanifest "1" "May 2019" "Gentoo Foundation" "qmanifest"
+.SH NAME
+qmanifest \- verify or generate thick Manifest files
+.SH SYNOPSIS
+.B qmanifest
+\fI[opts] <misc args>\fR
+.SH DESCRIPTION
+\fIqmanifest\fR allows to verify or generate thick signed Manifests in
+an ebuild tree.  By default, \fIqmanifest\fR will verify the main tree
+as specified by Portage's configuration (one can check which using
+\fIq\fR \fB-o\fR).  In this mode, it will output some information about
+the GPG signature of the top-level Manifest file, and further reports on
+any problems it encounters.
+.P
+This applet was originally a standalone program \fIhashgen\fR and its
+alias \fIhashverify\fR.  Aliases for these names are still available for
+historical reasons.  With the incorporation of \fIhashgen\fR in
+\fBportage-utils\fR, development on the former has stopped in favour of
+the latter.
+.P
+The arguments to \fIqmanifest\fR can be directories or names of
+overlays.  By default, each argument is attempted to be matched against
+all overlay names, and if that fails, treated as directory to presume a
+tree is in.  This behaviour can be overridden with the \fB-d\fR and
+\fB-o\fR flags to force treating the arguments as directories or
+overlay names respectively.  Note that overlay names are those as
+defined in \fIrepos.conf\fR from Portage's configuration.  The
+\fIrepo_name\fR files from the overlays themselves (if present) are
+ignored.
+.P
+This applet does similar things as \fIapp-portage/gemato\fR.  However,
+the output and implemented strategies are completely different.  When
+compiled with \fBUSE=openmp\fR, this applet will exploit parallelism
+where possible to traverse a tree.  Should you want to limit the number
+of parallel threads, export \fBOMP_NUM_THREADS\fR in your environment
+with the desired maximum amount of threads in use by \fIqmanifest\fR.
+.SH OPTIONS
+.TP
+\fB\-g\fR, \fB\-\-generate\fR
+Generate thick Manifests and sign.
+.TP
+\fB\-d\fR, \fB\-\-dir\fR
+Treat arguments as directories.
+.TP
+\fB\-o\fR, \fB\-\-overlay\fR
+Treat arguments as overlay names.
+.TP
+\fB\-\-root\fR \fI<arg>\fR
+Set the ROOT env var.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Make a lot of noise.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Tighter output; suppress warnings.
+.TP
+\fB\-C\fR, \fB\-\-nocolor\fR
+Don't output color.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Print this help and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print version and exit.
+
+.SH "REPORTING BUGS"
+Please report bugs via http://bugs.gentoo.org/
+.br
+Product: Portage Development; Component: Tools
+.SH AUTHORS
+.nf
+Ned Ludd <so...@gentoo.org>
+Mike Frysinger <vap...@gentoo.org>
+Fabian Groffen <grob...@gentoo.org>
+.fi
+.SH "SEE ALSO"
+.BR q (1),
+.BR qatom (1),
+.BR qcheck (1),
+.BR qdepends (1),
+.BR qfile (1),
+.BR qgrep (1),
+.BR qkeyword (1),
+.BR qlist (1),
+.BR qlop (1),
+.BR qmerge (1),
+.BR qpkg (1),
+.BR qsearch (1),
+.BR qsize (1),
+.BR qtbz2 (1),
+.BR qtegrity (1),
+.BR quse (1),
+.BR qxpak (1)

diff --git a/qmanifest.c b/qmanifest.c
new file mode 100644
index 0000000..09e7881
--- /dev/null
+++ b/qmanifest.c
@@ -0,0 +1,1695 @@
+/*
+ * Copyright 2018-2019 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ *
+ * Copyright 2018-     Fabian Groffen  - <grob...@gentoo.org>
+ *
+ * The contents of this file was taken from:
+ *   https://github.com/grobian/hashgen
+ * which was discontinued at the time the sources were incorporated into
+ * portage-utils as qmanifest.
+ */
+
+#include "main.h"
+
+#ifdef ENABLE_QMANIFEST
+
+#include "applets.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <time.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <openssl/sha.h>
+#include <openssl/whrlpool.h>
+#include <blake2.h>
+#include <zlib.h>
+#include <gpgme.h>
+
+#include "eat_file.h"
+#include "hash.h"
+
+#define QMANIFEST_FLAGS "gdo" COMMON_FLAGS
+static struct option const qmanifest_long_opts[] = {
+       {"generate",   no_argument, NULL, 'g'},
+       {"dir",        no_argument, NULL, 'd'},
+       {"overlay",    no_argument, NULL, 'o'},
+       COMMON_LONG_OPTS
+};
+static const char * const qmanifest_opts_help[] = {
+       "Generate thick Manifests and sign",
+       "Treat arguments as directories",
+       "Treat arguments as overlay names",
+       COMMON_OPTS_HELP
+};
+#define qmanifest_usage(ret) usage(ret, QMANIFEST_FLAGS, qmanifest_long_opts, 
qmanifest_opts_help, NULL, lookup_applet_idx("qmanifest"))
+
+static int hashes = HASH_DEFAULT;
+
+/* linked list structure to hold verification complaints */
+typedef struct verify_msg {
+       char *msg;
+       struct verify_msg *next;
+} verify_msg;
+
+typedef struct _gpg_signature {
+       char *algo;
+       char *fingerprint;
+       char isgood:1;
+       char *timestamp;
+       char *signer;
+       char *pkfingerprint;
+       char *reason;
+} gpg_sig;
+
+gpg_sig *verify_gpg_sig(const char *path, verify_msg **msgs);
+char *verify_timestamp(const char *ts);
+char verify_manifest(const char *dir, const char *manifest, verify_msg **msgs);
+
+/* Generate thick Manifests based on thin Manifests, or verify a tree. */
+
+/* In order to build this program, the following packages are required:
+ * - sys-libs/zlib (for compressing/decompressing Manifest files)
+ * - app-crypt/gpgme (for signing/verifying the top level manifest)
+ */
+
+static inline void
+update_times(struct timeval *tv, struct stat *s)
+{
+#ifdef __MACH__
+# define st_mtim st_mtimespec
+# define st_atim st_atimespec
+#endif
+       if (tv[1].tv_sec < s->st_mtim.tv_sec ||
+                       (tv[1].tv_sec == s->st_mtim.tv_sec &&
+                        tv[1].tv_usec < s->st_mtim.tv_nsec / 1000))
+       {
+               tv[0].tv_sec = s->st_atim.tv_sec;
+               tv[0].tv_usec = s->st_atim.tv_nsec / 1000;
+               tv[1].tv_sec = s->st_mtim.tv_sec;
+               tv[1].tv_usec = s->st_mtim.tv_nsec / 1000;
+       }
+}
+
+#define LISTSZ 64
+
+/**
+ * qsort comparator which runs strcmp.
+ */
+static int
+compare_strings(const void *l, const void *r)
+{
+       const char **strl = (const char **)l;
+       const char **strr = (const char **)r;
+       return strcmp(*strl, *strr);
+}
+
+/**
+ * Return a sorted list of entries in the given directory.  All entries
+ * starting with a dot are ignored, and not present in the returned
+ * list.  The list and all entries are allocated using xmalloc() and need
+ * to be freed.
+ * This function returns 0 when everything is fine, non-zero otherwise.
+ */
+static char
+list_dir(char ***retlist, size_t *retcnt, const char *path)
+{
+       DIR *d;
+       struct dirent *e;
+       size_t rlen = 0;
+       size_t rsize = 0;
+       char **rlist = NULL;
+
+       if ((d = opendir(path)) != NULL) {
+               while ((e = readdir(d)) != NULL) {
+                       /* skip all dotfiles */
+                       if (e->d_name[0] == '.')
+                               continue;
+
+                       if (rlen == rsize) {
+                               rsize += LISTSZ;
+                               rlist = realloc(rlist,
+                                               rsize * sizeof(rlist[0]));
+                               if (rlist == NULL) {
+                                       fprintf(stderr, "out of memory\n");
+                                       return 1;
+                               }
+                       }
+                       rlist[rlen] = xstrdup(e->d_name);
+                       if (rlist[rlen] == NULL) {
+                               fprintf(stderr, "out of memory\n");
+                               return 1;
+                       }
+                       rlen++;
+               }
+               closedir(d);
+
+               qsort(rlist, rlen, sizeof(rlist[0]), compare_strings);
+
+               *retlist = rlist;
+               *retcnt = rlen;
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+/**
+ * Write hashes in Manifest format to the file open for writing m, or
+ * gzipped file open for writing gm.  The hashes written are for a file
+ * in root found by name.  The Manifest entry will be using type as
+ * first component.
+ */
+static void
+write_hashes(
+               struct timeval *tv,
+               const char *root,
+               const char *name,
+               const char *type,
+               FILE *m,
+               gzFile gm)
+{
+       size_t flen = 0;
+       char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
+       char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
+       char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
+       char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
+       char data[8192];
+       char fname[8192];
+       size_t len;
+       struct stat s;
+
+       snprintf(fname, sizeof(fname), "%s/%s", root, name);
+
+       if (stat(fname, &s) != 0)
+               return;
+
+       update_times(tv, &s);
+
+       hash_compute_file(fname, sha256, sha512, whrlpl, blak2b, &flen);
+
+       len = snprintf(data, sizeof(data), "%s %s %zd", type, name, flen);
+       if (hashes & HASH_BLAKE2B)
+               len += snprintf(data + len, sizeof(data) - len,
+                               " BLAKE2B %s", blak2b);
+       if (hashes & HASH_SHA256)
+               len += snprintf(data + len, sizeof(data) - len,
+                               " SHA256 %s", sha256);
+       if (hashes & HASH_SHA512)
+               len += snprintf(data + len, sizeof(data) - len,
+                               " SHA512 %s", sha512);
+       if (hashes & HASH_WHIRLPOOL)
+               len += snprintf(data + len, sizeof(data) - len,
+                               " WHIRLPOOL %s", whrlpl);
+       len += snprintf(data + len, sizeof(data) - len, "\n");
+
+       if (m != NULL)
+               fwrite(data, len, 1, m);
+       if (gm != NULL)
+               gzwrite(gm, data, len);
+}
+
+/**
+ * Walk through a directory recursively and write hashes for each file
+ * found to the gzipped open stream for writing zm.  The Manifest
+ * entries generated will all be of DATA type.
+ */
+static char
+write_hashes_dir(
+               struct timeval *tv,
+               const char *root,
+               const char *name,
+               gzFile zm)
+{
+       char path[8192];
+       char **dentries;
+       size_t dentrieslen;
+       size_t i;
+
+       snprintf(path, sizeof(path), "%s/%s", root, name);
+       if (list_dir(&dentries, &dentrieslen, path) == 0) {
+               for (i = 0; i < dentrieslen; i++) {
+                       snprintf(path, sizeof(path), "%s/%s", name, 
dentries[i]);
+                       free(dentries[i]);
+                       if (write_hashes_dir(tv, root, path, zm) == 0)
+                               continue;
+                       /* regular file */
+                       write_hashes(tv, root, path, "DATA", NULL, zm);
+               }
+               free(dentries);
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+/**
+ * Walk through directory recursively and write hashes for each file
+ * found to the open stream for writing m.  All files will not use the
+ * "files/" prefix and Manifest entries will be of AUX type.
+ */
+static char
+process_files(struct timeval *tv, const char *dir, const char *off, FILE *m)
+{
+       char path[8192];
+       char **dentries;
+       size_t dentrieslen;
+       size_t i;
+
+       snprintf(path, sizeof(path), "%s/%s", dir, off);
+       if (list_dir(&dentries, &dentrieslen, path) == 0) {
+               for (i = 0; i < dentrieslen; i++) {
+                       snprintf(path, sizeof(path), "%s%s%s",
+                                       off, *off == '\0' ? "" : "/", 
dentries[i]);
+                       free(dentries[i]);
+                       if (process_files(tv, dir, path, m) == 0)
+                               continue;
+                       /* regular file */
+                       write_hashes(tv, dir, path, "AUX", m, NULL);
+               }
+               free(dentries);
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+/**
+ * Read layout.conf file specified by path and extract the
+ * manifest-hashes property from this file.  The hash set specified for
+ * this property will be returned.  When the property isn't found the
+ * returned hash set will be the HASH_DEFAULT set.  When the file isn't
+ * found, 0 will be returned (which means /no/ hashes).
+ */
+static int
+parse_layout_conf(const char *path)
+{
+       FILE *f;
+       char buf[8192];
+       size_t len = 0;
+       size_t sz;
+       char *p;
+       char *q;
+       char *tok;
+       char *last_nl;
+       char *start;
+       int ret = 0;
+
+       if ((f = fopen(path, "r")) == NULL)
+               return 0;
+
+       /* read file, examine lines after encountering a newline, that is,
+        * if the file doesn't end with a newline, the final bit is ignored */
+       while ((sz = fread(buf + len, 1, sizeof(buf) - len, f)) > 0) {
+               len += sz;
+               start = buf;
+               last_nl = NULL;
+               for (p = buf; (size_t)(p - buf) < len; p++) {
+                       if (*p == '\n') {
+                               if (last_nl != NULL)
+                                       start = last_nl + 1;
+                               last_nl = p;
+                               do {
+                                       sz = strlen("manifest-hashes");
+                                       if (strncmp(start, "manifest-hashes", 
sz))
+                                               break;
+                                       if ((q = strchr(start + sz, '=')) == 
NULL)
+                                               break;
+                                       q++;
+                                       while (isspace((int)*q))
+                                               q++;
+                                       /* parse the tokens, whitespace 
separated */
+                                       tok = q;
+                                       do {
+                                               while (!isspace((int)*q))
+                                                       q++;
+                                               sz = q - tok;
+                                               if (strncmp(tok, "SHA256", sz) 
== 0) {
+                                                       ret |= HASH_SHA256;
+                                               } else if (strncmp(tok, 
"SHA512", sz) == 0) {
+                                                       ret |= HASH_SHA512;
+                                               } else if (strncmp(tok, 
"WHIRLPOOL", sz) == 0) {
+                                                       ret |= HASH_WHIRLPOOL;
+                                               } else if (strncmp(tok, 
"BLAKE2B", sz) == 0) {
+                                                       ret |= HASH_BLAKE2B;
+                                               } else {
+                                                       fprintf(stderr, 
"warning: unsupported hash from "
+                                                                       
"layout.conf: %.*s\n", (int)sz, tok);
+                                               }
+                                               while (isspace((int)*q) && *q 
!= '\n')
+                                                       q++;
+                                               tok = q;
+                                       } while (*q != '\n');
+                                       /* got it, expect only once, so stop 
processing */
+                                       fclose(f);
+                                       return ret;
+                               } while (0);
+                       }
+               }
+               if (last_nl != NULL) {
+                       last_nl++;  /* skip \n */
+                       len = last_nl - buf;
+                       memmove(buf, last_nl, len);
+                       last_nl = buf;
+               } else {
+                       /* skip too long line */
+                       len = 0;
+               }
+       }
+
+       fclose(f);
+       /* if we didn't find anything, return the default set */
+       return HASH_DEFAULT;
+}
+
+static const char *str_manifest = "Manifest";
+static const char *str_manifest_gz = "Manifest.gz";
+static const char *str_manifest_files_gz = "Manifest.files.gz";
+enum type_manifest {
+       GLOBAL_MANIFEST,   /* Manifest.files.gz + Manifest */
+       SUBTREE_MANIFEST,  /* Manifest.gz for recursive list of files */
+       EBUILD_MANIFEST,   /* Manifest thick from thin */
+       CATEGORY_MANIFEST  /* Manifest.gz with Manifest entries */
+};
+static const char *
+generate_dir(const char *dir, enum type_manifest mtype)
+{
+       FILE *f;
+       char path[8192];
+       struct stat s;
+       struct timeval tv[2];
+       char **dentries;
+       size_t dentrieslen;
+       size_t i;
+
+       /* our timestamp strategy is as follows:
+        * - when a Manifest exists, use its timestamp
+        * - when a meta-Manifest is written (non-ebuilds) use the timestamp
+        *   of the latest Manifest referenced
+        * - when a Manifest is written for something like eclasses, use the
+        *   timestamp of the latest file in the dir
+        * this way we should keep updates limited to where changes are, and
+        * also get reproducible mtimes. */
+       tv[0].tv_sec = 0;
+       tv[0].tv_usec = 0;
+       tv[1].tv_sec = 0;
+       tv[1].tv_usec = 0;
+
+       if (mtype == GLOBAL_MANIFEST) {
+               const char *mfest;
+               size_t len;
+               gzFile mf;
+               time_t rtime;
+
+               snprintf(path, sizeof(path), "%s/%s", dir, 
str_manifest_files_gz);
+               if ((mf = gzopen(path, "wb9")) == NULL) {
+                       fprintf(stderr, "failed to open file '%s' for writing: 
%s\n",
+                                       path, strerror(errno));
+                       return NULL;
+               }
+
+               /* These "IGNORE" entries are taken from gx86, there is no
+                * standardisation on this, on purpose, apparently. */
+               len = snprintf(path, sizeof(path),
+                               "IGNORE distfiles\n"
+                               "IGNORE local\n"
+                               "IGNORE lost+found\n"
+                               "IGNORE packages\n"
+                               "IGNORE snapshots\n");
+               gzwrite(mf, path, len);
+
+               if (list_dir(&dentries, &dentrieslen, dir) != 0)
+                       return NULL;
+
+               for (i = 0; i < dentrieslen; i++) {
+                       /* ignore existing Manifests */
+                       if (strcmp(dentries[i], str_manifest_files_gz) == 0 ||
+                                       strcmp(dentries[i], str_manifest) == 0)
+                       {
+                               free(dentries[i]);
+                               continue;
+                       }
+
+                       snprintf(path, sizeof(path), "%s/%s", dir, dentries[i]);
+
+                       mfest = NULL;
+                       if (!stat(path, &s)) {
+                               if (s.st_mode & S_IFDIR) {
+                                       if (
+                                                       strcmp(dentries[i], 
"eclass")   == 0 ||
+                                                       strcmp(dentries[i], 
"licenses") == 0 ||
+                                                       strcmp(dentries[i], 
"metadata") == 0 ||
+                                                       strcmp(dentries[i], 
"profiles") == 0 ||
+                                                       strcmp(dentries[i], 
"scripts")  == 0
+                                          )
+                                       {
+                                               mfest = generate_dir(path, 
SUBTREE_MANIFEST);
+                                       } else {
+                                               mfest = generate_dir(path, 
CATEGORY_MANIFEST);
+                                       }
+
+                                       if (mfest == NULL) {
+                                               fprintf(stderr, "generating 
Manifest for %s failed!\n",
+                                                               path);
+                                               gzclose(mf);
+                                               return NULL;
+                                       }
+
+                                       snprintf(path, sizeof(path), "%s/%s",
+                                                       dentries[i], mfest);
+                                       write_hashes(tv, dir, path, "MANIFEST", 
NULL, mf);
+                               } else if (s.st_mode & S_IFREG) {
+                                       write_hashes(tv, dir, dentries[i], 
"DATA", NULL, mf);
+                               } /* ignore other "things" (like symlinks) as 
they
+                                        don't belong in a tree */
+                       } else {
+                               fprintf(stderr, "stat(%s) failed: %s\n",
+                                               path, strerror(errno));
+                       }
+                       free(dentries[i]);
+               }
+               free(dentries);
+               gzclose(mf);
+
+               if (tv[0].tv_sec != 0) {
+                       snprintf(path, sizeof(path), "%s/%s", dir, 
str_manifest_files_gz);
+                       utimes(path, tv);
+               }
+
+               /* create global Manifest */
+               snprintf(path, sizeof(path), "%s/%s", dir, str_manifest);
+               if ((f = fopen(path, "w")) == NULL) {
+                       fprintf(stderr, "failed to open file '%s' for writing: 
%s\n",
+                                       path, strerror(errno));
+                       return NULL;
+               }
+
+               write_hashes(tv, dir, str_manifest_files_gz, "MANIFEST", f, 
NULL);
+               time(&rtime);
+               len = strftime(path, sizeof(path),
+                               "TIMESTAMP %Y-%m-%dT%H:%M:%SZ\n", 
gmtime(&rtime));
+               fwrite(path, len, 1, f);
+               fflush(f);
+               fclose(f);
+
+               /* because we write a timestamp in Manifest, we don't mess with
+                * its mtime, else it would obviously lie */
+               return str_manifest_files_gz;
+       } else if (mtype == SUBTREE_MANIFEST) {
+               const char *ldir;
+               gzFile mf;
+
+               snprintf(path, sizeof(path), "%s/%s", dir, str_manifest_gz);
+               if ((mf = gzopen(path, "wb9")) == NULL) {
+                       fprintf(stderr, "failed to open file '%s' for writing: 
%s\n",
+                                       path, strerror(errno));
+                       return NULL;
+               }
+
+               ldir = strrchr(dir, '/');
+               if (ldir == NULL)
+                       ldir = dir;
+               if (strcmp(ldir, "metadata") == 0) {
+                       size_t len;
+                       len = snprintf(path, sizeof(path),
+                                       "IGNORE timestamp\n"
+                                       "IGNORE timestamp.chk\n"
+                                       "IGNORE timestamp.commit\n"
+                                       "IGNORE timestamp.x\n");
+                       gzwrite(mf, path, len);
+               }
+
+               if (list_dir(&dentries, &dentrieslen, dir) != 0)
+                       return NULL;
+
+               for (i = 0; i < dentrieslen; i++) {
+                       /* ignore existing Manifests */
+                       if (strcmp(dentries[i], str_manifest_gz) == 0) {
+                               free(dentries[i]);
+                               continue;
+                       }
+
+                       if (write_hashes_dir(tv, dir, dentries[i], mf) != 0)
+                               write_hashes(tv, dir, dentries[i], "DATA", 
NULL, mf);
+                       free(dentries[i]);
+               }
+
+               free(dentries);
+               gzclose(mf);
+
+               if (tv[0].tv_sec != 0) {
+                       /* set Manifest and dir mtime to most recent file found 
*/
+                       snprintf(path, sizeof(path), "%s/%s", dir, 
str_manifest_gz);
+                       utimes(path, tv);
+                       utimes(dir, tv);
+               }
+
+               return str_manifest_gz;
+       } else if (mtype == CATEGORY_MANIFEST) {
+               const char *mfest;
+               gzFile mf;
+
+               snprintf(path, sizeof(path), "%s/%s", dir, str_manifest_gz);
+               if ((mf = gzopen(path, "wb9")) == NULL) {
+                       fprintf(stderr, "failed to open file '%s' for writing: 
%s\n",
+                                       path, strerror(errno));
+                       return NULL;
+               }
+
+               if (list_dir(&dentries, &dentrieslen, dir) != 0)
+                       return NULL;
+
+               for (i = 0; i < dentrieslen; i++) {
+                       /* ignore existing Manifests */
+                       if (strcmp(dentries[i], str_manifest_gz) == 0) {
+                               free(dentries[i]);
+                               continue;
+                       }
+
+                       snprintf(path, sizeof(path), "%s/%s", dir, dentries[i]);
+                       if (!stat(path, &s)) {
+                               if (s.st_mode & S_IFDIR) {
+                                       mfest = generate_dir(path, 
EBUILD_MANIFEST);
+
+                                       if (mfest == NULL) {
+                                               fprintf(stderr, "generating 
Manifest for %s failed!\n",
+                                                               path);
+                                               gzclose(mf);
+                                               return NULL;
+                                       }
+
+                                       snprintf(path, sizeof(path), "%s/%s",
+                                                       dentries[i], mfest);
+                                       write_hashes(tv, dir, path, "MANIFEST", 
NULL, mf);
+                               } else if (s.st_mode & S_IFREG) {
+                                       write_hashes(tv, dir, dentries[i], 
"DATA", NULL, mf);
+                               } /* ignore other "things" (like symlinks) as 
they
+                                        don't belong in a tree */
+                       } else {
+                               fprintf(stderr, "stat(%s) failed: %s\n",
+                                               path, strerror(errno));
+                       }
+                       free(dentries[i]);
+               }
+
+               free(dentries);
+               gzclose(mf);
+
+               if (tv[0].tv_sec != 0) {
+                       /* set Manifest and dir mtime to most ebuild dir found 
*/
+                       snprintf(path, sizeof(path), "%s/%s", dir, 
str_manifest_gz);
+                       utimes(path, tv);
+                       utimes(dir, tv);
+               }
+
+               return str_manifest_gz;
+       } else if (mtype == EBUILD_MANIFEST) {
+               char newmanifest[8192];
+               FILE *m;
+
+               snprintf(newmanifest, sizeof(newmanifest), "%s/.Manifest.new", 
dir);
+               if ((m = fopen(newmanifest, "w")) == NULL) {
+                       fprintf(stderr, "failed to open file '%s' for writing: 
%s\n",
+                                       newmanifest, strerror(errno));
+                       return NULL;
+               }
+
+               /* we know the Manifest is sorted, and stuff in files/ is
+                * prefixed with AUX, hence, if it exists, we need to do it
+                * first */
+               snprintf(path, sizeof(path), "%s/files", dir);
+               process_files(tv, path, "", m);
+
+               /* the Manifest file may be missing in case there are no DIST
+                * entries to be stored */
+               snprintf(path, sizeof(path), "%s/%s", dir, str_manifest);
+               if (!stat(path, &s))
+                       update_times(tv, &s);
+               f = fopen(path, "r");
+               if (f != NULL) {
+                       /* copy the DIST entries, we could do it unconditional, 
but this
+                        * way we can re-run without producing invalid 
Manifests */
+                       while (fgets(path, sizeof(path), f) != NULL) {
+                               if (strncmp(path, "DIST ", 5) == 0)
+                                       if (fwrite(path, strlen(path), 1, m) != 
1) {
+                                               fprintf(stderr, "failed to 
write to "
+                                                               
"%s/.Manifest.new: %s\n",
+                                                               dir, 
strerror(errno));
+                                               fclose(f);
+                                               fclose(m);
+                                               return NULL;
+                                       }
+                       }
+                       fclose(f);
+               }
+
+               if (list_dir(&dentries, &dentrieslen, dir) == 0) {
+                       for (i = 0; i < dentrieslen; i++) {
+                               if (strcmp(dentries[i] + strlen(dentries[i]) - 
7,
+                                                       ".ebuild") != 0)
+                               {
+                                       free(dentries[i]);
+                                       continue;
+                               }
+                               write_hashes(tv, dir, dentries[i], "EBUILD", m, 
NULL);
+                               free(dentries[i]);
+                       }
+                       free(dentries);
+               }
+
+               write_hashes(tv, dir, "ChangeLog", "MISC", m, NULL);
+               write_hashes(tv, dir, "metadata.xml", "MISC", m, NULL);
+
+               fflush(m);
+               fclose(m);
+
+               snprintf(path, sizeof(path), "%s/%s", dir, str_manifest);
+               rename(newmanifest, path);
+
+               if (tv[0].tv_sec != 0) {
+                       /* set Manifest and dir mtime to most recent file we 
found */
+                       utimes(path, tv);
+                       utimes(dir, tv);
+               }
+
+               return str_manifest;
+       } else {
+               return NULL;
+       }
+}
+
+static const char *
+process_dir_gen(const char *dir)
+{
+       char path[8192];
+       int newhashes;
+       int curdirfd;
+
+       snprintf(path, sizeof(path), "%s/metadata/layout.conf", dir);
+       if ((newhashes = parse_layout_conf(path)) != 0) {
+               hashes = newhashes;
+       } else {
+               return "generation must be done on a full tree";
+       }
+
+       if ((curdirfd = open(".", O_RDONLY)) < 0) {
+               fprintf(stderr, "cannot open current directory?!? %s\n",
+                               strerror(errno));
+       }
+       if (chdir(dir) != 0) {
+               fprintf(stderr, "cannot chdir() to %s: %s\n", dir, 
strerror(errno));
+               return "not a directory";
+       }
+
+       if (generate_dir(".\0", GLOBAL_MANIFEST) == NULL)
+               return "generation failed";
+
+       /* return to where we were before we called this function */
+       fchdir(curdirfd);
+       close(curdirfd);
+
+       return NULL;
+}
+
+static void
+msgs_add(
+               verify_msg **msgs,
+               const char *manifest,
+               const char *ebuild,
+               const char *fmt, ...)
+{
+       char buf[4096];
+       int len;
+       va_list ap;
+       verify_msg *msg;
+
+       if (msgs == NULL || *msgs == NULL)
+               return;
+
+       msg = (*msgs)->next = xmalloc(sizeof(verify_msg));
+
+       len = snprintf(buf, sizeof(buf), "%s:%s:",
+                       manifest ? manifest : "",
+                       ebuild   ? ebuild   : "");
+
+       va_start(ap, fmt);
+       vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
+       va_end(ap);
+
+       msg->msg = xstrdup(buf);
+       msg->next = NULL;
+       *msgs = msg;
+}
+
+gpg_sig *
+verify_gpg_sig(const char *path, verify_msg **msgs)
+{
+       gpgme_ctx_t g_ctx;
+       gpgme_data_t manifest;
+       gpgme_data_t out;
+       gpgme_verify_result_t vres;
+       gpgme_signature_t sig;
+       gpgme_key_t key;
+       char buf[64];
+       FILE *f;
+       struct tm *ctime;
+       gpg_sig *ret = NULL;
+
+       if ((f = fopen(path, "r")) == NULL) {
+               msgs_add(msgs, path, NULL, "failed to open: %s", 
strerror(errno));
+               return NULL;
+       }
+
+       if (gpgme_new(&g_ctx) != GPG_ERR_NO_ERROR) {
+               msgs_add(msgs, path, NULL, "failed to create gpgme context");
+               return NULL;
+       }
+
+       if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR) {
+               msgs_add(msgs, path, NULL, "failed to create gpgme data");
+               return NULL;
+       }
+
+       if (gpgme_data_new_from_stream(&manifest, f) != GPG_ERR_NO_ERROR) {
+               msgs_add(msgs, path, NULL,
+                               "failed to create new gpgme data from stream");
+               return NULL;
+       }
+
+       if (gpgme_op_verify(g_ctx, manifest, NULL, out) != GPG_ERR_NO_ERROR) {
+               msgs_add(msgs, path, NULL, "failed to verify signature");
+               return NULL;
+       }
+
+       vres = gpgme_op_verify_result(g_ctx);
+       fclose(f);
+
+       if (vres == NULL || vres->signatures == NULL) {
+               msgs_add(msgs, path, NULL,
+                               "verification failed due to a missing gpg 
keyring");
+               return NULL;
+       }
+
+       /* we only check/return the first signature */
+       if ((sig = vres->signatures) != NULL) {
+               ret = xmalloc(sizeof(gpg_sig));
+
+               if (sig->status != GPG_ERR_NO_PUBKEY) {
+                       ret->algo = 
xstrdup(gpgme_pubkey_algo_name(sig->pubkey_algo));
+                       snprintf(buf, sizeof(buf),
+                                       "%.4s %.4s %.4s %.4s %.4s  %.4s %.4s 
%.4s %.4s %.4s",
+                                       sig->fpr +  0, sig->fpr +  4, sig->fpr 
+  8, sig->fpr + 12,
+                                       sig->fpr + 16, sig->fpr + 20, sig->fpr 
+ 24, sig->fpr + 28,
+                                       sig->fpr + 32, sig->fpr + 36);
+                       ret->fingerprint = xstrdup(buf);
+                       ret->isgood = sig->status == GPG_ERR_NO_ERROR ? 1 : 0;
+                       ctime = gmtime((time_t *)&sig->timestamp);
+                       strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", 
ctime);
+                       ret->timestamp = xstrdup(buf);
+
+                       if (gpgme_get_key(g_ctx, sig->fpr, &key, 0) == 
GPG_ERR_NO_ERROR) {
+                               if (key->uids != NULL)
+                                       ret->signer = xstrdup(key->uids->uid);
+                               if (key->subkeys != NULL) {
+                                       snprintf(buf, sizeof(buf),
+                                                       "%.4s %.4s %.4s %.4s 
%.4s  "
+                                                       "%.4s %.4s %.4s %.4s 
%.4s",
+                                                       key->subkeys->fpr +  0, 
key->subkeys->fpr +  4,
+                                                       key->subkeys->fpr +  8, 
key->subkeys->fpr + 12,
+                                                       key->subkeys->fpr + 16, 
key->subkeys->fpr + 20,
+                                                       key->subkeys->fpr + 24, 
key->subkeys->fpr + 28,
+                                                       key->subkeys->fpr + 32, 
key->subkeys->fpr + 36);
+                                       ret->pkfingerprint = xstrdup(buf);
+                               }
+                               gpgme_key_release(key);
+                       }
+               }
+
+               switch (sig->status) {
+                       case GPG_ERR_NO_ERROR:
+                               /* nothing */
+                               ret->reason = NULL;
+                               break;
+                       case GPG_ERR_SIG_EXPIRED:
+                               ret->reason = xstrdup("the signature is valid 
but expired");
+                               break;
+                       case GPG_ERR_KEY_EXPIRED:
+                               ret->reason = xstrdup("the signature is valid 
but the key "
+                                               "used to verify the signature 
has expired");
+                               break;
+                       case GPG_ERR_CERT_REVOKED:
+                               ret->reason = xstrdup("the signature is valid 
but the key "
+                                               "used to verify the signature 
has been revoked");
+                               break;
+                       case GPG_ERR_BAD_SIGNATURE:
+                               printf("the signature is invalid\n");
+                               break;
+                       case GPG_ERR_NO_PUBKEY:
+                               printf("the signature could not be verified due 
to a "
+                                               "missing key\n");
+                               break;
+                       default:
+                               printf("there was some other error which 
prevented the "
+                                               "signature verification\n");
+                               break;
+               }
+       }
+
+       gpgme_release(g_ctx);
+
+       return ret;
+}
+
+static size_t checked_manifests = 0;
+static size_t checked_files = 0;
+static size_t failed_files = 0;
+static char strict = 0;
+
+static char
+verify_file(const char *dir, char *mfline, const char *mfest, verify_msg 
**msgs)
+{
+       char *path;
+       char *size;
+       long long int fsize;
+       char *hashtype;
+       char *hash;
+       char *p;
+       char buf[8192];
+       size_t flen = 0;
+       char sha256[(SHA256_DIGEST_LENGTH * 2) + 1];
+       char sha512[(SHA512_DIGEST_LENGTH * 2) + 1];
+       char whrlpl[(WHIRLPOOL_DIGEST_LENGTH * 2) + 1];
+       char blak2b[(BLAKE2B_OUTBYTES * 2) + 1];
+       char ret = 0;
+
+       /* mfline is a Manifest file line with type and leading path
+        * stripped, something like:
+        * file <SIZE> <HASHTYPE HASH ...>
+        * we parse this, and verify the size and hashes */
+
+       path = mfline;
+       p = strchr(path, ' ');
+       if (p == NULL) {
+               msgs_add(msgs, mfest, NULL, "corrupt manifest line: %s", path);
+               return 1;
+       }
+       *p++ = '\0';
+
+       size = p;
+       p = strchr(size, ' ');
+       if (p == NULL) {
+               msgs_add(msgs, mfest, NULL, "corrupt manifest line, need size");
+               return 1;
+       }
+       *p++ = '\0';
+       fsize = strtoll(size, NULL, 10);
+       if (fsize == 0 && errno == EINVAL) {
+               msgs_add(msgs, mfest, NULL, "corrupt manifest line, "
+                               "size is not a number: %s", size);
+               return 1;
+       }
+
+       sha256[0] = sha512[0] = whrlpl[0] = blak2b[0] = '\0';
+       snprintf(buf, sizeof(buf), "%s/%s", dir, path);
+       hash_compute_file(buf, sha256, sha512, whrlpl, blak2b, &flen);
+
+       if (flen == 0) {
+               msgs_add(msgs, mfest, path, "cannot open file!");
+               return 1;
+       }
+
+       checked_files++;
+
+       if (flen != (size_t)fsize) {
+               msgs_add(msgs, mfest, path,
+                               "file size mismatch\n"
+                               "     got: %zd\n"
+                               "expected: %lld",
+                               flen, fsize);
+               failed_files++;
+               return 1;
+       }
+
+       /* now we are in free territory, we read TYPE HASH pairs until we
+        * drained the string, and match them against what we computed */
+       while (p != NULL && *p != '\0') {
+               hashtype = p;
+               p = strchr(hashtype, ' ');
+               if (p == NULL) {
+                       msgs_add(msgs, mfest, path,
+                                       "corrupt manifest line, missing hash 
type");
+                       return 1;
+               }
+               *p++ = '\0';
+
+               hash = p;
+               p = strchr(hash, ' ');
+               if (p != NULL)
+                       *p++ = '\0';
+
+               if (strcmp(hashtype, "SHA256") == 0) {
+                       if (!(hashes & HASH_SHA256)) {
+                               if (strict)
+                                       msgs_add(msgs, mfest, path,
+                                                       "hash SHA256 is not "
+                                                       "enabled for this 
repository");
+                       } else if (strcmp(hash, sha256) != 0) {
+                               msgs_add(msgs, mfest, path,
+                                               "SHA256 hash mismatch\n"
+                                               "computed: '%s'\n"
+                                               "Manifest: '%s'",
+                                               sha256, hash);
+                               ret = 1;
+                       }
+                       sha256[0] = '\0';
+               } else if (strcmp(hashtype, "SHA512") == 0) {
+                       if (!(hashes & HASH_SHA512)) {
+                               if (strict)
+                                       msgs_add(msgs, mfest, path,
+                                                       "hash SHA512 is not "
+                                                       "enabled for this 
repository");
+                       } else if (strcmp(hash, sha512) != 0) {
+                               msgs_add(msgs, mfest, path,
+                                               "SHA512 hash mismatch\n"
+                                               "computed: '%s'\n"
+                                               "Manifest: '%s'",
+                                               sha512, hash);
+                               ret = 1;
+                       }
+                       sha512[0] = '\0';
+               } else if (strcmp(hashtype, "WHIRLPOOL") == 0) {
+                       if (!(hashes & HASH_WHIRLPOOL)) {
+                               if (strict)
+                                       msgs_add(msgs, mfest, path,
+                                                       "hash WHIRLPOOL is not "
+                                                       "enabled for this 
repository");
+                       } else if (strcmp(hash, whrlpl) != 0) {
+                               msgs_add(msgs, mfest, path,
+                                               "WHIRLPOOL hash mismatch\n"
+                                               "computed: '%s'\n"
+                                               "Manifest: '%s'",
+                                               whrlpl, hash);
+                               ret = 1;
+                       }
+                       whrlpl[0] = '\0';
+               } else if (strcmp(hashtype, "BLAKE2B") == 0) {
+                       if (!(hashes & HASH_BLAKE2B)) {
+                               if (strict)
+                                       msgs_add(msgs, mfest, path,
+                                                       "hash BLAKE2B is not "
+                                                       "enabled for this 
repository");
+                       } else if (strcmp(hash, blak2b) != 0) {
+                               msgs_add(msgs, mfest, path,
+                                               "BLAKE2B hash mismatch\n"
+                                               "computed: '%s'\n"
+                                               "Manifest: '%s'",
+                                               blak2b, hash);
+                               ret = 1;
+                       }
+                       blak2b[0] = '\0';
+               } else {
+                       msgs_add(msgs, mfest, path, "unsupported hash: %s", 
hashtype);
+                       ret = 1;
+               }
+       }
+
+       if (sha256[0] != '\0') {
+               msgs_add(msgs, mfest, path, "missing hash: SHA256");
+               ret = 1;
+       }
+       if (sha512[0] != '\0') {
+               msgs_add(msgs, mfest, path, "missing hash: SHA512");
+               ret = 1;
+       }
+       if (whrlpl[0] != '\0') {
+               msgs_add(msgs, mfest, path, "missing hash: WHIRLPOOL");
+               ret = 1;
+       }
+       if (blak2b[0] != '\0') {
+               msgs_add(msgs, mfest, path, "missing hash: BLAKE2B");
+               ret = 1;
+       }
+
+       failed_files += ret;
+       return ret;
+}
+
+static int
+compare_elems(const void *l, const void *r)
+{
+       const char *strl = *((const char **)l) + 2;
+       const char *strr = *((const char **)r) + 2;
+       unsigned char cl;
+       unsigned char cr;
+       /* compare treating / as end of string */
+       while ((cl = *strl++) == (cr = *strr++))
+               if (cl == '\0')
+                       return 0;
+       if (cl == '/')
+               cl = '\0';
+       if (cr == '/')
+               cr = '\0';
+       return cl - cr;
+}
+
+struct subdir_workload {
+       size_t subdirlen;
+       size_t elemslen;
+       char **elems;
+};
+
+static char
+verify_dir(
+               const char *dir,
+               char **elems,
+               size_t elemslen,
+               size_t skippath,
+               const char *mfest,
+               verify_msg **msgs)
+{
+       char **dentries = NULL;
+       size_t dentrieslen = 0;
+       size_t curelem = 0;
+       size_t curdentry = 0;
+       char *entry;
+       char *slash;
+       char etpe;
+       char ret = 0;
+       int cmp;
+       struct subdir_workload **subdir = NULL;
+       size_t subdirsize = 0;
+       size_t subdirlen = 0;
+       size_t elem;
+
+       /* shortcut a single Manifest entry pointing to the same dir
+        * (happens at top-level) */
+       if (elemslen == 1 && skippath == 0 &&
+                       **elems == 'M' && strchr(*elems + 2, '/') == NULL)
+       {
+               if ((ret = verify_file(dir, *elems + 2, mfest, msgs)) == 0) {
+                       slash = strchr(*elems + 2, ' ');
+                       if (slash != NULL)
+                               *slash = '\0';
+                       /* else, verify_manifest will fail, so ret will be 
handled */
+                       ret = verify_manifest(dir, *elems + 2, msgs);
+               }
+               return ret;
+       }
+
+       /*
+        * We have a list of entries from the manifest just read, now we
+        * need to match these onto the directory layout.  From what we got
+        * - we can ignore TIMESTAMP and DIST entries
+        * - IGNOREs need to be handled separate (shortcut)
+        * - MANIFESTs need to be handled on their own, for memory
+        *   consumption reasons, we defer them to until we've verified
+        *   what's left, we treat the path the Manifest refers to as IGNORE
+        * - DATAs, EBUILDs and MISCs needs verifying
+        * - AUXs need verifying, but in files/ subdir
+        * If we sort both lists, we should be able to do a merge-join, to
+        * easily flag missing entries in either list without hashing or
+        * anything.
+        */
+       if (list_dir(&dentries, &dentrieslen, dir) == 0) {
+               while (curdentry < dentrieslen) {
+                       if (strcmp(dentries[curdentry], str_manifest) == 0 ||
+                                       strcmp(dentries[curdentry], 
str_manifest_gz) == 0 ||
+                                       strcmp(dentries[curdentry], 
str_manifest_files_gz) == 0)
+                       {
+                               curdentry++;
+                               continue;
+                       }
+
+                       if (curelem < elemslen) {
+                               entry = elems[curelem] + 2 + skippath;
+                               etpe = *elems[curelem];
+                       } else {
+                               entry = (char *)"";
+                               etpe = 'I';
+                       }
+
+                       /* handle subdirs first */
+                       if ((slash = strchr(entry, '/')) != NULL) {
+                               size_t sublen = slash - entry;
+                               int elemstart = curelem;
+                               char **subelems = &elems[curelem];
+
+                               /* collect all entries like this one (same 
subdir) into
+                                * a sub-list that we can verify */
+                               curelem++;
+                               while (curelem < elemslen &&
+                                               strncmp(entry, elems[curelem] + 
2 + skippath,
+                                                       sublen + 1) == 0)
+                                       curelem++;
+
+                               if (subdirlen == subdirsize) {
+                                       subdirsize += LISTSZ;
+                                       subdir = realloc(subdir,
+                                                       subdirsize * 
sizeof(subdir[0]));
+                                       if (subdir == NULL) {
+                                               msgs_add(msgs, mfest, NULL, 
"out of memory allocating "
+                                                               "sublist for 
%.*s", (int)sublen, entry);
+                                               return 1;
+                                       }
+                               }
+                               subdir[subdirlen] = xmalloc(sizeof(struct 
subdir_workload));
+                               subdir[subdirlen]->subdirlen = sublen;
+                               subdir[subdirlen]->elemslen = curelem - 
elemstart;
+                               subdir[subdirlen]->elems = subelems;
+                               subdirlen++;
+
+                               curelem--; /* move back, see below */
+
+                               /* modify the last entry to be the subdir, such 
that we
+                                * can let the code below synchronise with 
dentries */
+                               elems[curelem][2 + skippath + sublen] = ' ';
+                               entry = elems[curelem] + 2 + skippath;
+                               etpe = 'S';  /* flag this was a subdir */
+                       }
+
+                       /* does this entry exist in list? */
+                       if (*entry == '\0') {
+                               /* end of list reached, force dir to catch up */
+                               cmp = 1;
+                       } else {
+                               slash = strchr(entry, ' ');
+                               if (slash != NULL)
+                                       *slash = '\0';
+                               cmp = strcmp(entry, dentries[curdentry]);
+                               if (slash != NULL)
+                                       *slash = ' ';
+                       }
+                       if (cmp == 0) {
+                               /* equal, so yay */
+                               if (etpe == 'D') {
+                                       ret |= verify_file(dir, entry, mfest, 
msgs);
+                               }
+                               /* else this is I(GNORE) or S(ubdir), which 
means it is
+                                * ok in any way (M shouldn't happen) */
+                               curelem++;
+                               curdentry++;
+                       } else if (cmp < 0) {
+                               /* entry is missing from dir */
+                               if (etpe == 'I') {
+                                       /* right, we can ignore this */
+                               } else {
+                                       ret |= 1;
+                                       slash = strchr(entry, ' ');
+                                       if (slash != NULL)
+                                               *slash = '\0';
+                                       msgs_add(msgs, mfest, entry, "%s file 
listed in Manifest, "
+                                                       "but not found", etpe 
== 'M' ? "MANIFEST" : "DATA");
+                                       if (slash != NULL)
+                                               *slash = ' ';
+                                       failed_files++;
+                               }
+                               curelem++;
+                       } else if (cmp > 0) {
+                               /* dir has extra element */
+                               ret |= 1;
+                               msgs_add(msgs, mfest, NULL,
+                                               "file not listed: %s", 
dentries[curdentry]);
+                               curdentry++;
+                               failed_files++;
+                       }
+               }
+
+               while (dentrieslen-- > 0)
+                       free(dentries[dentrieslen]);
+               free(dentries);
+
+#pragma omp parallel for shared(ret) private(entry, etpe, slash)
+               for (elem = 0; elem < subdirlen; elem++) {
+                       char ndir[8192];
+
+                       entry = subdir[elem]->elems[0] + 2 + skippath;
+                       etpe = subdir[elem]->elems[0][0];
+
+                       /* restore original entry format */
+                       subdir[elem]->elems[subdir[elem]->elemslen - 1]
+                               [2 + skippath + subdir[elem]->subdirlen] = '/';
+
+                       if (etpe == 'M') {
+                               size_t skiplen = strlen(dir) + 1 + 
subdir[elem]->subdirlen;
+                               /* sub-Manifest, we need to do a proper recurse 
*/
+                               slash = strrchr(entry, '/');  /* cannot be NULL 
*/
+                               snprintf(ndir, sizeof(ndir), "%s/%s", dir, 
entry);
+                               ndir[skiplen] = '\0';
+                               slash = strchr(ndir + skiplen + 1, ' ');
+                               if (slash != NULL)  /* path should fit in ndir 
... */
+                                       *slash = '\0';
+                               if (verify_file(dir, entry, mfest, msgs) != 0 ||
+                                               verify_manifest(ndir, ndir + 
skiplen + 1, msgs) != 0)
+                                       ret |= 1;
+                       } else {
+                               snprintf(ndir, sizeof(ndir), "%s/%.*s", dir,
+                                               (int)subdir[elem]->subdirlen, 
entry);
+                               ret |= verify_dir(ndir, subdir[elem]->elems,
+                                               subdir[elem]->elemslen,
+                                               skippath + 
subdir[elem]->subdirlen + 1, mfest, msgs);
+                       }
+
+                       free(subdir[elem]);
+               }
+
+               if (subdir)
+                       free(subdir);
+
+               return ret;
+       } else {
+               return 1;
+       }
+}
+
+char
+verify_manifest(
+               const char *dir,
+               const char *manifest,
+               verify_msg **msgs)
+{
+       char buf[8192];
+       FILE *f;
+       gzFile mf;
+       char ret = 0;
+
+       size_t elemssize = 0;
+       size_t elemslen = 0;
+       char **elems = NULL;
+#define append_list(STR) \
+       if (strncmp(STR, "TIMESTAMP ", 10) != 0 || strncmp(STR, "DIST ", 5) != 
0) {\
+               char *endp = STR + strlen(STR) - 1;\
+               while (isspace(*endp))\
+                       *endp-- = '\0';\
+               if (elemslen == elemssize) {\
+                       elemssize += LISTSZ;\
+                       elems = xrealloc(elems, elemssize * sizeof(elems[0]));\
+               }\
+               if (strncmp(STR, "IGNORE ", 7) == 0) {\
+                       STR[5] = 'I';\
+                       elems[elemslen] = xstrdup(STR + 5);\
+                       elemslen++;\
+               } else if (strncmp(STR, "MANIFEST ", 9) == 0) {\
+                       STR[7] = 'M';\
+                       elems[elemslen] = xstrdup(STR + 7);\
+                       elemslen++;\
+               } else if (strncmp(STR, "DATA ", 5) == 0 ||\
+                               strncmp(STR, "MISC ", 5) == 0 ||\
+                               strncmp(STR, "EBUILD ", 7) == 0)\
+               {\
+                       if (*STR == 'E') {\
+                               STR[5] = 'D';\
+                               elems[elemslen] = xstrdup(STR + 5);\
+                       } else {\
+                               STR[3] = 'D';\
+                               elems[elemslen] = xstrdup(STR + 3);\
+                       }\
+                       elemslen++;\
+               } else if (strncmp(STR, "AUX ", 4) == 0) {\
+                       /* translate directly into what it is: DATA in files/ 
*/\
+                       size_t slen = strlen(STR + 2) + sizeof("files/");\
+                       elems[elemslen] = xmalloc(slen);\
+                       snprintf(elems[elemslen], slen, "D files/%s", STR + 4);\
+                       elemslen++;\
+               }\
+       }
+
+       snprintf(buf, sizeof(buf), "%s/%s", dir, manifest);
+       if (strcmp(manifest, str_manifest) == 0) {
+               if ((f = fopen(buf, "r")) == NULL) {
+                       msgs_add(msgs, buf, NULL, "failed to open %s: %s\n",
+                                       manifest, strerror(errno));
+                       return 1;
+               }
+               while (fgets(buf, sizeof(buf), f) != NULL) {
+                       append_list(buf);
+               }
+               fclose(f);
+       } else if (strcmp(manifest, str_manifest_files_gz) == 0 ||
+                       strcmp(manifest, str_manifest_gz) == 0)
+       {
+               if ((mf = gzopen(buf, "rb9")) == NULL) {
+                       msgs_add(msgs, buf, NULL, "failed to open %s: %s\n",
+                                       manifest, strerror(errno));
+                       return 1;
+               }
+               while (gzgets(mf, buf, sizeof(buf)) != NULL) {
+                       append_list(buf);
+               }
+               gzclose(mf);
+       }
+
+       /* The idea:
+        * - Manifest without MANIFEST entries, we need to scan the entire
+        *   subtree
+        * - Manifest with MANIFEST entries, assume they are just one level
+        *   deeper, thus ignore that subdir, further like above
+        * - Manifest at top-level, needs to be igored as it only points to
+        *   the larger Manifest.files.gz
+        */
+       qsort(elems, elemslen, sizeof(elems[0]), compare_elems);
+       snprintf(buf, sizeof(buf), "%s/%s", dir, manifest);
+       ret = verify_dir(dir, elems, elemslen, 0, buf + 2, msgs);
+       checked_manifests++;
+
+       while (elemslen-- > 0)
+               free(elems[elemslen]);
+       free(elems);
+
+       return ret;
+}
+
+char *
+verify_timestamp(const char *ts)
+{
+       char buf[8192];
+       FILE *f;
+       char *ret = NULL;
+
+       if ((f = fopen(ts, "r")) != NULL) {
+               while (fgets(buf, sizeof(buf), f) != NULL) {
+                       if (strncmp(buf, "TIMESTAMP ", 10) == 0) {
+                               char *endp = buf + strlen(buf) - 1;
+                               while (isspace(*endp))
+                                       *endp-- = '\0';
+                               ret = xstrdup(buf + 10);
+                               break;
+                       }
+               }
+               fclose(f);
+       }
+
+       return ret;
+}
+
+static void
+format_line(const char *pfx, const char *msg, int twidth)
+{
+       size_t msglen = strlen(pfx) + strlen(msg);
+
+       if (*pfx == '-') {
+               fprintf(stdout, "%s%s%s%s\n", pfx, RED, msg, NORM);
+       } else {
+               if (!verbose && msglen > (size_t)twidth) {
+                       int to_remove = 3 + (msglen - twidth);
+                       int first_half = msglen / 2 - to_remove / 2;
+                       int remainder = msglen / 2 + (to_remove + 1) / 2;
+                       fprintf(stdout, "%s%.*s...%s\n",
+                                       pfx, first_half, msg, msg + remainder);
+               } else {
+                       fprintf(stdout, "%s%s\n", pfx, msg);
+               }
+       }
+}
+
+static const char *
+process_dir_vrfy(const char *dir)
+{
+       char buf[8192];
+       int newhashes;
+       const char *ret = NULL;
+       struct timeval startt;
+       struct timeval finisht;
+       double etime;
+       int curdirfd;
+       char *timestamp;
+       verify_msg topmsg;
+       verify_msg *walk = &topmsg;
+       gpg_sig *gs;
+       struct winsize winsz;
+       int twidth = 80;
+
+       ioctl(0, TIOCGWINSZ, &winsz);
+       if (winsz.ws_col > 0)
+               twidth = (int)winsz.ws_col;
+
+       gettimeofday(&startt, NULL);
+
+       fprintf(stdout, "verifying %s...\n", dir);
+       snprintf(buf, sizeof(buf), "%s/metadata/layout.conf", dir);
+       if ((newhashes = parse_layout_conf(buf)) != 0) {
+               hashes = newhashes;
+       } else {
+               return "verification must be done on a full tree";
+       }
+
+       if ((curdirfd = open(".", O_RDONLY)) < 0) {
+               fprintf(stderr, "cannot open current directory?!? %s\n",
+                               strerror(errno));
+       }
+       if (chdir(dir) != 0) {
+               fprintf(stderr, "cannot chdir() to %s: %s\n", dir, 
strerror(errno));
+               return "not a directory";
+       }
+
+       if ((gs = verify_gpg_sig(str_manifest, &walk)) == NULL) {
+               ret = "gpg signature invalid";
+       } else {
+               fprintf(stdout,
+                               "%s%s%s signature made %s by\n"
+                               "%s\n"
+                               "primary key fingerprint %s\n"
+                               "%4s subkey fingerprint %s\n",
+                               gs->isgood ? GREEN : RED,
+                               gs->isgood ? "good": "BAD",
+                               NORM, gs->timestamp,
+                               gs->signer,
+                               gs->pkfingerprint,
+                               gs->algo, gs->fingerprint);
+               if (!gs->isgood)
+                       fprintf(stdout, "reason: %s%s%s\n", RED, gs->reason, 
NORM);
+               free(gs->algo);
+               free(gs->fingerprint);
+               free(gs->timestamp);
+               free(gs->signer);
+               free(gs->pkfingerprint);
+               if (!gs->isgood)
+                       free(gs->reason);
+               free(gs);
+       }
+
+       if ((timestamp = verify_timestamp(str_manifest)) != NULL) {
+               fprintf(stdout, "%s timestamp: %s\n", str_manifest, timestamp);
+               free(timestamp);
+       } else {
+               ret = "manifest timestamp entry missing";
+       }
+
+       /* verification goes like this:
+        * - verify the signature of the top-level Manifest file (done
+        *   above)
+        * - read the contents of the Manifest file, and process the
+        *   entries - verify them, check there are no files which shouldn't
+        *   be there
+        * - recurse into directories for which Manifest files are defined
+        */
+       walk->next = NULL;
+       if (verify_manifest(".\0", str_manifest, &walk) != 0)
+               ret = "manifest verification failed";
+
+       {
+               char *mfest;
+               char *ebuild;
+               char *msg;
+               char *lastmfest = (char *)"-";
+               char *lastebuild = (char *)"-";
+               char *msgline;
+               const char *pfx;
+               verify_msg *next;
+
+               for (walk = topmsg.next; walk != NULL; walk = walk->next) {
+                       mfest = walk->msg;
+                       ebuild = strchr(mfest, ':');
+                       if (ebuild != NULL) {
+                               *ebuild++ = '\0';
+                               msg = strchr(ebuild, ':');
+                               if (msg != NULL)
+                                       *msg++ = '\0';
+                       }
+                       if (ebuild != NULL && msg != NULL) {
+                               if (strcmp(mfest, lastmfest) != 0 ||
+                                               strcmp(ebuild, lastebuild) != 0)
+                               {
+                                       char *mycat = mfest;
+                                       char *mypkg = NULL;
+
+                                       if ((mfest = strchr(mycat, '/')) != 
NULL) {
+                                               *mfest++ = '\0';
+                                               mypkg = mfest;
+                                               if ((mfest = strchr(mypkg, 
'/')) != NULL) {
+                                                       *mfest++ = '\0';
+                                               } else {
+                                                       mfest = mypkg;
+                                                       mypkg = NULL;
+                                               }
+                                       } else {
+                                               mfest = mycat;
+                                               mycat = NULL;
+                                       }
+
+                                       fprintf(stdout, "%s%s%s" "%s%s%s%s" 
"%s%s" "%s%s%s%s\n",
+                                                       mycat == NULL ? "" : 
BOLD,
+                                                       mycat == NULL ? "" : 
mycat,
+                                                       mycat == NULL ? "" : 
"/",
+                                                       mypkg == NULL ? "" : 
BLUE,
+                                                       mypkg == NULL ? "" : 
mypkg,
+                                                       mypkg == NULL ? "" : 
NORM,
+                                                       mypkg == NULL ? "" : 
"/",
+                                                       mfest, *ebuild == '\0' 
? ":" : "::",
+                                                       CYAN, ebuild, NORM, 
*ebuild == '\0' ? "" : ":");
+                               }
+
+                               lastmfest = mfest;
+                               lastebuild = ebuild;
+
+                               pfx = "- ";
+                               msgline = msg;
+                               while ((msgline = strchr(msgline, '\n')) != 
NULL) {
+                                       *msgline++ = '\0';
+                                       format_line(pfx, msg, twidth);
+                                       pfx = "  ";
+                                       msg = msgline;
+                               }
+                               format_line(pfx, msg, twidth);
+                       }
+               }
+
+               walk = topmsg.next;
+               while (walk != NULL) {
+                       next = walk->next;
+                       free(walk);
+                       walk = next;
+               }
+       }
+
+       gettimeofday(&finisht, NULL);
+
+       /* return to where we were before we called this function */
+       fchdir(curdirfd);
+       close(curdirfd);
+
+       etime = ((double)((finisht.tv_sec - startt.tv_sec) * 1000000 +
+                               finisht.tv_usec) - (double)startt.tv_usec) / 
1000000.0;
+       printf("checked %zd Manifests, %zd files, %zd failures in %.02fs\n",
+                       checked_manifests, checked_files, failed_files, etime);
+       return ret;
+}
+
+int
+qmanifest_main(int argc, char **argv)
+{
+       char *prog;
+       const char *(*runfunc)(const char *);
+       int ret = 0;
+       const char *rsn;
+       bool isdir = false;
+       bool isoverlay = false;
+       char *overlay;
+       size_t n;
+       int i;
+
+       if ((prog = strrchr(argv[0], '/')) == NULL) {
+               prog = argv[0];
+       } else {
+               prog++;
+       }
+       if (*prog == 'q')
+               prog++;
+
+       runfunc = NULL;
+       if (strcmp(prog, "hashverify") == 0) {
+               runfunc = process_dir_vrfy;
+       } else if (strcmp(prog, "hashgen") == 0) {
+               runfunc = process_dir_gen;
+       }
+
+       while ((ret = GETOPT_LONG(QMANIFEST, qmanifest, "")) != -1) {
+               switch (ret) {
+                       COMMON_GETOPTS_CASES(qmanifest)
+                       case 'g': runfunc = process_dir_gen;  break;
+                       case 'd': isdir = true;               break;
+                       case 'o': isoverlay = true;           break;
+               }
+       }
+
+       if (isdir && isoverlay) {
+               warn("cannot specify both directories (-d) and overlays (-o), "
+                               "continuing using overlays only");
+               isdir = false;
+       }
+
+       if (runfunc == NULL)
+               /* default mode: verify */
+               runfunc = process_dir_vrfy;
+
+       gpgme_check_version(NULL);
+
+       if (isoverlay || (!isdir && !isoverlay)) {
+               char buf[_Q_PATH_MAX];
+               char *repo;
+               size_t repolen;
+
+               array_for_each(overlays, n, overlay) {
+                       repo = xarrayget(overlay_names, n);
+                       if (strcmp(repo, "<PORTDIR>") == 0) {
+                               snprintf(buf, sizeof(buf), 
"%s/profiles/repo_name", overlay);
+                               if (eat_file(buf, &repo, &repolen)) {
+                                       free(array_get_elem(overlays, n));
+                                       array_get_elem(overlays, n) = repo;
+                               }
+                       }
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       for (i = 0; i < argc; i++) {
+               array_for_each(overlay_names, n, overlay) {
+                       if (strcmp(overlay, argv[i]) == 0) {
+                               overlay = xarrayget(overlays, n);
+                               break;
+                       }
+                       overlay = NULL;
+               }
+
+               /* behaviour:
+                * if isdir is set: treat argument as directory
+                * if isoverlay is set: treat argument as overlay (look it up)
+                * if neither is set: treat argument as overlay if it exists,
+                *                    else as directory */
+
+               if (isoverlay && overlay == NULL) {
+                       warn("no such overlay: %s", argv[i]);
+                       continue;
+               }
+               if (isdir || (!isoverlay && overlay == NULL)) /* !isdir && 
!isoverlay */
+                       overlay = argv[i];
+
+               rsn = runfunc(overlay);
+               if (rsn != NULL) {
+                       printf("%s%s%s\n", RED, rsn, NORM);
+                       ret |= 1;
+               }
+       }
+
+       if (i == 0) {
+               rsn = runfunc(main_overlay);
+               if (rsn != NULL) {
+                       printf("%s%s%s\n", RED, rsn, NORM);
+                       ret |= 1;
+               }
+       }
+
+       return ret;
+}
+
+#endif

Reply via email to