summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustus Winter <justus@gnupg.org>2016-03-07 19:35:59 +0100
committerJustus Winter <justus@gnupg.org>2016-03-07 19:35:59 +0100
commit8d00a764c4abbb07fd4656ccaa7cd1deb0a27d41 (patch)
treeb2b7b16e90d627ffedbe95ed9c3f322b229ae95f
parent363dd205c6deb994a578732e095385ef74081da9 (diff)
add patch series
-rw-r--r--debian/patches/gpg0001-trans-add-transparent-GnuPG-translator.patch1826
-rw-r--r--debian/patches/gpg0002-adjust-to-lockless-libnetfs.patch30
-rw-r--r--debian/patches/series2
3 files changed, 1858 insertions, 0 deletions
diff --git a/debian/patches/gpg0001-trans-add-transparent-GnuPG-translator.patch b/debian/patches/gpg0001-trans-add-transparent-GnuPG-translator.patch
new file mode 100644
index 00000000..f55f88bc
--- /dev/null
+++ b/debian/patches/gpg0001-trans-add-transparent-GnuPG-translator.patch
@@ -0,0 +1,1826 @@
+From 211c7648f88dd544ca2d69ed1e46969b3516b4dd Mon Sep 17 00:00:00 2001
+From: Justus Winter <4winter@informatik.uni-hamburg.de>
+Date: Sat, 23 Jan 2016 00:14:56 +0100
+Subject: [PATCH hurd 1/2] trans: add transparent GnuPG translator
+
+* trans/Makefile: Add new files.
+* trans/chroot.c: New file.
+* trans/chroot.h: Likewise.
+* trans/gpg.c: Likewise.
+* trans/identity.c: Likewise.
+* utils/gpg-env.sh: Likewise.
+---
+ trans/Makefile | 8 +-
+ trans/chroot.c | 768 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ trans/chroot.h | 34 +++
+ trans/gpg.c | 679 ++++++++++++++++++++++++++++++++++++++++++++++++
+ trans/identity.c | 113 ++++++++
+ utils/Makefile | 6 +-
+ utils/gpg-env.sh | 121 +++++++++
+ 7 files changed, 1723 insertions(+), 6 deletions(-)
+ create mode 100644 trans/chroot.c
+ create mode 100644 trans/chroot.h
+ create mode 100644 trans/gpg.c
+ create mode 100644 trans/identity.c
+ create mode 100755 utils/gpg-env.sh
+
+diff --git a/trans/Makefile b/trans/Makefile
+index 65b51d1..e3d89c2 100644
+--- a/trans/Makefile
++++ b/trans/Makefile
+@@ -21,10 +21,11 @@ makemode := servers
+
+ targets = symlink firmlink ifsock magic null fifo new-fifo fwd crash \
+ password hello hello-mt streamio fakeroot proxy-defpager remap \
+- mtab
++ mtab gpg identity
+ SRCS = ifsock.c symlink.c magic.c null.c fifo.c new-fifo.c fwd.c \
+ crash.c firmlink.c password.c hello.c hello-mt.c streamio.c \
+- fakeroot.c proxy-defpager.c remap.c mtab.c
++ fakeroot.c proxy-defpager.c remap.c mtab.c gpg.c chroot.c \
++ identity.c
+ OBJS = $(SRCS:.c=.o) fsysServer.o ifsockServer.o passwordServer.o \
+ crashServer.o crash_replyUser.o msgServer.o \
+ default_pagerServer.o default_pagerUser.o \
+@@ -65,7 +66,8 @@ proxy-defpager: default_pagerServer.o default_pagerUser.o
+ streamio: device_replyServer.o
+ symlink: fsysServer.o
+
+-fakeroot: ../libnetfs/libnetfs.a
++gpg identity: chroot.o
++fakeroot gpg identity: ../libnetfs/libnetfs.a
+ fifo new-fifo: ../libpipe/libpipe.a
+ crash fifo firmlink hello hello-mt ifsock magic mtab new-fifo null password proxy-defpager remap streamio: ../libtrivfs/libtrivfs.a
+ $(targets): ../libfshelp/libfshelp.a \
+diff --git a/trans/chroot.c b/trans/chroot.c
+new file mode 100644
+index 0000000..089ad84
+--- /dev/null
++++ b/trans/chroot.c
+@@ -0,0 +1,768 @@
++/* chroot.c -- support for writing chrooting translators
++ Copyright (C) 2002, 2003, 2008, 2013, 2016 Free Software Foundation, Inc.
++
++ This program is free software; you can redistribute it and/or
++ modify it under the terms of the GNU General Public License as
++ published by the Free Software Foundation; either version 2, or (at
++ your option) any later version.
++
++ This program is distributed in the hope that it will be useful, but
++ WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
++
++#include <argz.h>
++#include <argp.h>
++#include <dirent.h>
++#include <error.h>
++#include <hurd/netfs.h>
++#include <stddef.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <fcntl.h>
++#include <sys/mman.h>
++#include <sys/stat.h>
++#include <pthread.h>
++#include <hurd/ihash.h>
++#include <hurd/paths.h>
++#include <sys/types.h>
++#include <sys/wait.h>
++#include <unistd.h>
++
++#include <version.h>
++
++#include "chroot.h"
++
++#include "libnetfs/fs_S.h"
++#include "libnetfs/io_S.h"
++#include "libnetfs/fsys_S.h"
++#include "libports/notify_S.h"
++#include "libports/interrupt_S.h"
++
++/* Make a new virtual node. Always consumes the ports. If
++ successful, NP will be locked. */
++error_t
++chroot_new_node (file_t file, size_t size, struct node **np)
++{
++ error_t err;
++ struct netnode *nn;
++
++ *np = netfs_make_node_alloc (sizeof *nn + size);
++ if (*np == 0)
++ {
++ mach_port_deallocate (mach_task_self (), file);
++ return ENOMEM;
++ }
++ nn = netfs_node_netnode (*np);
++ nn->file = file;
++ return 0;
++
++ lose:
++ mach_port_deallocate (mach_task_self (), file);
++ free (*np);
++ *np = NULL;
++ return err;
++}
++
++/* Node NP has no more references; free all its associated storage. */
++void
++netfs_node_norefs (struct node *np)
++{
++ pthread_mutex_unlock (&np->lock);
++ pthread_spin_unlock (&netfs_node_refcnt_lock);
++
++ chroot_node_norefs (np);
++ mach_port_deallocate (mach_task_self (), netfs_node_netnode (np)->file);
++ free (np);
++
++ pthread_spin_lock (&netfs_node_refcnt_lock);
++}
++
++/* This is the cleanup function we install in netfs_protid_class. If
++ the associated nodes reference count would also drop to zero, and
++ the node has no faked attributes, we destroy it as well. */
++static void
++chroot_netfs_release_protid (void *cookie)
++{
++ netfs_release_protid (cookie);
++
++ int cports = ports_count_class (netfs_control_class);
++ int nports = ports_count_class (netfs_protid_class);
++ ports_enable_class (netfs_control_class);
++ ports_enable_class (netfs_protid_class);
++ if (cports == 0 && nports == 0)
++ {
++ /* The last client is gone. Our job is done. */
++ error_t err = netfs_shutdown (0);
++ if (! err)
++ exit (EXIT_SUCCESS);
++
++ /* If netfs_shutdown returns EBUSY, we lost a race against
++ fsys_goaway. Hence we ignore this error. */
++ if (err != EBUSY)
++ error (1, err, "netfs_shutdown");
++ }
++}
++
++error_t
++chroot_init (void)
++{
++ /* Install our own clean routine. */
++ netfs_protid_class->clean_routine = chroot_netfs_release_protid;
++ return 0;
++}
++
++/* This is called by netfs_S_fsys_getroot. */
++error_t
++netfs_check_open_permissions (struct iouser *user, struct node *np,
++ int flags, int newnode)
++{
++ return 0; /* XXX */
++}
++
++error_t __attribute__ ((weak))
++netfs_S_dir_lookup (struct protid *diruser,
++ char *filename,
++ int flags,
++ mode_t mode,
++ retry_type *do_retry,
++ char *retry_name,
++ mach_port_t *retry_port,
++ mach_msg_type_name_t *retry_port_type)
++{
++ struct node *dnp, *np;
++ error_t err;
++ struct protid *newpi;
++ struct iouser *user;
++ mach_port_t file;
++
++ if (!diruser)
++ return EOPNOTSUPP;
++
++ dnp = diruser->po->np;
++
++ mach_port_t dir = netfs_node_netnode (dnp)->file;
++ err = dir_lookup (dir, filename,
++ flags & (O_NOLINK|O_RDWR|O_EXEC|O_CREAT|O_EXCL|O_NONBLOCK),
++ mode, do_retry, retry_name, &file);
++ if (err)
++ {
++ /* xxx deref stuff */
++ return err;
++ }
++
++ switch (*do_retry)
++ {
++ case FS_RETRY_NORMAL:
++ break;
++
++ case FS_RETRY_REAUTH:
++ error (0, 0, "FS_RETRY_REAUTH");
++ /* Fallthrough. */
++
++ case FS_RETRY_MAGICAL:
++ *retry_port = file;
++ *retry_port_type = MACH_MSG_TYPE_MOVE_SEND;
++ return 0;
++
++ default:
++ /* Invalid response to our dir_lookup request. */
++ if (MACH_PORT_VALID (file))
++ mach_port_deallocate (mach_task_self (), file);
++ *retry_port = MACH_PORT_NULL;
++ *retry_port_type = MACH_MSG_TYPE_COPY_SEND;
++ return EOPNOTSUPP;
++ }
++
++ /* We have a new port to an underlying node.
++ Find or make our corresponding virtual node. */
++
++ np = 0;
++
++ redo_hash_lookup:
++ pthread_mutex_lock (&dnp->lock);
++ err = chroot_new_node (file, 0, &np);
++ pthread_mutex_unlock (&dnp->lock);
++ if (!err)
++ err = netfs_validate_stat (np, diruser->user);
++ if (err)
++ goto lose;
++
++ assert (*do_retry == FS_RETRY_NORMAL);
++ flags &= ~(O_CREAT|O_EXCL|O_NOLINK|O_NOTRANS|O_NONBLOCK);
++
++ err = iohelp_dup_iouser (&user, diruser->user);
++ if (!err)
++ {
++ newpi = netfs_make_protid (netfs_make_peropen (np, flags, diruser->po),
++ user);
++ if (! newpi)
++ {
++ iohelp_free_iouser (user);
++ err = errno;
++ }
++ else
++ {
++ *retry_port = ports_get_right (newpi);
++ *retry_port_type = MACH_MSG_TYPE_MAKE_SEND;
++ ports_port_deref (newpi);
++ }
++ }
++
++ lose:
++ if (dir != netfs_node_netnode (dnp)->file)
++ mach_port_deallocate (mach_task_self (), dir);
++ if (np != NULL)
++ netfs_nput (np);
++ return err;
++}
++
++/* The user may define this function. Attempt to set the passive
++ translator record for FILE to ARGZ (of length ARGZLEN) for user
++ CRED. */
++error_t
++netfs_set_translator (struct iouser *cred, struct node *np,
++ char *argz, size_t argzlen)
++{
++ return file_set_translator (netfs_node_netnode (np)->file,
++ FS_TRANS_EXCL|FS_TRANS_SET,
++ FS_TRANS_EXCL|FS_TRANS_SET, 0,
++ argz, argzlen,
++ MACH_PORT_NULL, MACH_MSG_TYPE_COPY_SEND);
++}
++
++/* These callbacks are used only by the standard netfs_S_dir_lookup,
++ which we do not use. But the shared library requires us to define them. */
++error_t
++netfs_attempt_lookup (struct iouser *user, struct node *dir,
++ char *name, struct node **np)
++{
++ assert (! "should not be here");
++ return EIEIO;
++}
++
++error_t
++netfs_attempt_create_file (struct iouser *user, struct node *dir,
++ char *name, mode_t mode, struct node **np)
++{
++ assert (! "should not be here");
++ return EIEIO;
++}
++
++/* Make sure that NP->nn_stat is filled with the most current information.
++ CRED identifies the user responsible for the operation. NP is locked. */
++error_t
++netfs_validate_stat (struct node *np, struct iouser *cred)
++{
++ struct stat st;
++ error_t err = io_stat (netfs_node_netnode (np)->file, &st);
++ if (err)
++ return err;
++
++ np->nn_stat = st;
++ np->nn_translated = S_ISLNK (st.st_mode) ? S_IFLNK : 0;
++
++ return 0;
++}
++
++error_t
++netfs_attempt_chown (struct iouser *cred, struct node *np,
++ uid_t uid, uid_t gid)
++{
++ if (! cred)
++ return EOPNOTSUPP;
++ return file_chown (netfs_node_netnode (np)->file, uid, gid);
++}
++
++error_t
++netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author)
++{
++ if (! cred)
++ return EOPNOTSUPP;
++ return file_chauthor (netfs_node_netnode (np)->file, author);
++}
++
++/* This should attempt a chmod call for the user specified by CRED on
++ locked node NODE, to change the mode to MODE. Unlike the normal Unix
++ and Hurd meaning of chmod, this function is also used to attempt to
++ change files into other types. If such a transition is attempted which
++ is impossible, then return EOPNOTSUPP. */
++error_t
++netfs_attempt_chmod (struct iouser *cred, struct node *np, mode_t mode)
++{
++ if (! cred)
++ return EOPNOTSUPP;
++ return file_chmod (netfs_node_netnode (np)->file, mode);
++}
++
++/* The user must define this function. Attempt to turn locked node NP
++ (user CRED) into a symlink with target NAME. */
++error_t
++netfs_attempt_mksymlink (struct iouser *cred, struct node *np, char *name)
++{
++ int namelen = strlen (name) + 1;
++ char trans[sizeof _HURD_SYMLINK + namelen];
++ memcpy (trans, _HURD_SYMLINK, sizeof _HURD_SYMLINK);
++ memcpy (&trans[sizeof _HURD_SYMLINK], name, namelen);
++ return file_set_translator (netfs_node_netnode (np)->file,
++ FS_TRANS_EXCL|FS_TRANS_SET,
++ FS_TRANS_EXCL|FS_TRANS_SET, 0,
++ trans, sizeof trans,
++ MACH_PORT_NULL, MACH_MSG_TYPE_COPY_SEND);
++}
++
++error_t
++netfs_attempt_mkdev (struct iouser *cred, struct node *np,
++ mode_t type, dev_t indexes)
++{
++ char *trans = 0;
++ int translen = asprintf (&trans, "%s%c%d%c%d",
++ S_ISCHR (type) ? _HURD_CHRDEV : _HURD_BLKDEV,
++ '\0', major (indexes), '\0', minor (indexes));
++ if (trans == 0)
++ return ENOMEM;
++ else
++ {
++ error_t err = file_set_translator (netfs_node_netnode (np)->file,
++ FS_TRANS_EXCL|FS_TRANS_SET,
++ FS_TRANS_EXCL|FS_TRANS_SET, 0,
++ trans, translen + 1,
++ MACH_PORT_NULL,
++ MACH_MSG_TYPE_COPY_SEND);
++ free (trans);
++ return err;
++ }
++}
++
++error_t
++netfs_attempt_chflags (struct iouser *cred, struct node *np, int flags)
++{
++ return file_chflags (netfs_node_netnode (np)->file, flags);
++}
++
++error_t
++netfs_attempt_utimes (struct iouser *cred, struct node *np,
++ struct timespec *atime, struct timespec *mtime)
++{
++ union tv
++ {
++ struct timeval tv;
++ time_value_t tvt;
++ };
++ union tv a, m;
++ if (atime)
++ {
++ TIMESPEC_TO_TIMEVAL (&a.tv, atime);
++ }
++ else
++ a.tv.tv_sec = a.tv.tv_usec = -1;
++ if (mtime)
++ {
++ TIMESPEC_TO_TIMEVAL (&m.tv, mtime);
++ }
++ else
++ m.tv.tv_sec = m.tv.tv_usec = -1;
++
++ return file_utimes (netfs_node_netnode (np)->file, a.tvt, m.tvt);
++}
++
++error_t
++netfs_attempt_set_size (struct iouser *cred, struct node *np, off_t size)
++{
++ return file_set_size (netfs_node_netnode (np)->file, size);
++}
++
++error_t
++netfs_attempt_statfs (struct iouser *cred, struct node *np, struct statfs *st)
++{
++ return file_statfs (netfs_node_netnode (np)->file, st);
++}
++
++error_t __attribute__ ((weak))
++netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
++{
++ return file_sync (netfs_node_netnode (np)->file, wait, 0);
++}
++
++error_t
++netfs_attempt_syncfs (struct iouser *cred, int wait)
++{
++ return 0;
++}
++
++error_t
++netfs_attempt_mkdir (struct iouser *user, struct node *dir,
++ char *name, mode_t mode)
++{
++ return dir_mkdir (netfs_node_netnode (dir)->file, name, mode | S_IRWXU);
++}
++
++
++/* XXX
++ Removing a node should mark the netnode so that it is GC'd when
++ it has no hard refs.
++ */
++
++error_t
++netfs_attempt_unlink (struct iouser *user, struct node *dir, char *name)
++{
++ return dir_unlink (netfs_node_netnode (dir)->file, name);
++}
++
++error_t
++netfs_attempt_rename (struct iouser *user, struct node *fromdir,
++ char *fromname, struct node *todir,
++ char *toname, int excl)
++{
++ return dir_rename (netfs_node_netnode (fromdir)->file, fromname,
++ netfs_node_netnode (todir)->file, toname, excl);
++}
++
++error_t
++netfs_attempt_rmdir (struct iouser *user,
++ struct node *dir, char *name)
++{
++ return dir_rmdir (netfs_node_netnode (dir)->file, name);
++}
++
++error_t __attribute__ ((weak))
++netfs_attempt_link (struct iouser *user, struct node *dir,
++ struct node *file, char *name, int excl)
++{
++ return dir_link (netfs_node_netnode (dir)->file,
++ netfs_node_netnode (file)->file, name, excl);
++}
++
++error_t __attribute__ ((weak))
++netfs_attempt_mkfile (struct iouser *user, struct node *dir,
++ mode_t mode, struct node **np)
++{
++ file_t newfile;
++ error_t err = dir_mkfile (netfs_node_netnode (dir)->file, O_RDWR|O_EXEC,
++ mode, &newfile);
++ pthread_mutex_unlock (&dir->lock);
++ if (err)
++ return err;
++
++ err = chroot_new_node (newfile, 0, np);
++ if (err)
++ return err;
++
++ pthread_mutex_unlock (&(*np)->lock);
++ return err;
++}
++
++error_t
++netfs_attempt_readlink (struct iouser *user, struct node *np, char *buf)
++{
++ char transbuf[sizeof _HURD_SYMLINK + np->nn_stat.st_size + 1];
++ char *trans = transbuf;
++ size_t translen = sizeof transbuf;
++ error_t err = file_get_translator (netfs_node_netnode (np)->file,
++ &trans, &translen);
++ if (err == 0)
++ {
++ if (translen < sizeof _HURD_SYMLINK
++ || memcmp (trans, _HURD_SYMLINK, sizeof _HURD_SYMLINK) != 0)
++ err = EINVAL;
++ else
++ {
++ assert (translen <= sizeof _HURD_SYMLINK + np->nn_stat.st_size + 1);
++ memcpy (buf, &trans[sizeof _HURD_SYMLINK],
++ translen - sizeof _HURD_SYMLINK);
++ }
++ if (trans != transbuf)
++ munmap (trans, translen);
++ }
++ return err;
++}
++
++error_t __attribute__ ((weak))
++netfs_get_dirents (struct iouser *cred, struct node *dir,
++ int entry, int nentries, char **data,
++ mach_msg_type_number_t *datacnt,
++ vm_size_t bufsize, int *amt)
++{
++ error_t err;
++ err = dir_readdir (netfs_node_netnode (dir)->file, data, datacnt,
++ entry, nentries, bufsize, amt);
++ return err;
++}
++
++error_t __attribute__ ((weak))
++netfs_attempt_read (struct iouser *cred, struct node *np,
++ off_t offset, size_t *len, void *data)
++{
++ char *buf = data;
++ error_t err = io_read (netfs_node_netnode (np)->file,
++ &buf, len, offset, *len);
++ if (err == 0 && buf != data)
++ {
++ memcpy (data, buf, *len);
++ munmap (buf, *len);
++ }
++ return err;
++}
++
++error_t __attribute__ ((weak))
++netfs_attempt_write (struct iouser *cred, struct node *np,
++ off_t offset, size_t *len, void *data)
++{
++ return io_write (netfs_node_netnode (np)->file, data, *len, offset, len);
++}
++
++error_t
++netfs_report_access (struct iouser *cred, struct node *np, int *types)
++{
++ return file_check_access (netfs_node_netnode (np)->file, types);
++}
++
++error_t
++netfs_file_get_storage_info (struct iouser *cred,
++ struct node *np,
++ mach_port_t **ports,
++ mach_msg_type_name_t *ports_type,
++ mach_msg_type_number_t *num_ports,
++ int **ints,
++ mach_msg_type_number_t *num_ints,
++ off_t **offsets,
++ mach_msg_type_number_t *num_offsets,
++ char **data,
++ mach_msg_type_number_t *data_len)
++{
++ *ports_type = MACH_MSG_TYPE_MOVE_SEND;
++ return file_get_storage_info (netfs_node_netnode (np)->file,
++ ports, num_ports,
++ ints, num_ints,
++ offsets, num_offsets,
++ data, data_len);
++}
++
++kern_return_t
++netfs_S_file_exec (struct protid *user,
++ task_t task,
++ int flags,
++ char *argv,
++ size_t argvlen,
++ char *envp,
++ size_t envplen,
++ mach_port_t *fds,
++ size_t fdslen,
++ mach_port_t *portarray,
++ size_t portarraylen,
++ int *intarray,
++ size_t intarraylen,
++ mach_port_t *deallocnames,
++ size_t deallocnameslen,
++ mach_port_t *destroynames,
++ size_t destroynameslen)
++{
++ error_t err;
++ file_t file;
++
++ if (!user)
++ return EOPNOTSUPP;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ file = netfs_node_netnode (user->po->np)->file;
++ err = mach_port_mod_refs (mach_task_self (),
++ file, MACH_PORT_RIGHT_SEND, 1);
++ pthread_mutex_unlock (&user->po->np->lock);
++
++ if (!err)
++ {
++ /* We cannot use MACH_MSG_TYPE_MOVE_SEND because we might need to
++ retry an interrupted call that would have consumed the rights. */
++ err = file_exec (netfs_node_netnode (user->po->np)->file,
++ task, flags, argv, argvlen,
++ envp, envplen, fds, MACH_MSG_TYPE_COPY_SEND, fdslen,
++ portarray, MACH_MSG_TYPE_COPY_SEND, portarraylen,
++ intarray, intarraylen, deallocnames, deallocnameslen,
++ destroynames, destroynameslen);
++ mach_port_deallocate (mach_task_self (), file);
++ }
++
++ if (err == 0)
++ {
++ size_t i;
++ mach_port_deallocate (mach_task_self (), task);
++ for (i = 0; i < fdslen; ++i)
++ mach_port_deallocate (mach_task_self (), fds[i]);
++ for (i = 0; i < portarraylen; ++i)
++ mach_port_deallocate (mach_task_self (), portarray[i]);
++ }
++ return err;
++}
++
++error_t
++netfs_S_io_map (struct protid *user,
++ mach_port_t *rdobj, mach_msg_type_name_t *rdobjtype,
++ mach_port_t *wrobj, mach_msg_type_name_t *wrobjtype)
++{
++ error_t err;
++
++ if (!user)
++ return EOPNOTSUPP;
++ *rdobjtype = *wrobjtype = MACH_MSG_TYPE_MOVE_SEND;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ err = io_map (netfs_node_netnode (user->po->np)->file, rdobj, wrobj);
++ pthread_mutex_unlock (&user->po->np->lock);
++ return err;
++}
++
++error_t
++netfs_S_io_map_cntl (struct protid *user,
++ mach_port_t *obj,
++ mach_msg_type_name_t *objtype)
++{
++ error_t err;
++
++ if (!user)
++ return EOPNOTSUPP;
++ *objtype = MACH_MSG_TYPE_MOVE_SEND;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ err = io_map_cntl (netfs_node_netnode (user->po->np)->file, obj);
++ pthread_mutex_unlock (&user->po->np->lock);
++ return err;
++}
++
++error_t
++netfs_S_io_identity (struct protid *user,
++ mach_port_t *id,
++ mach_msg_type_name_t *idtype,
++ mach_port_t *fsys,
++ mach_msg_type_name_t *fsystype,
++ ino_t *fileno)
++{
++ error_t err;
++
++ if (!user)
++ return EOPNOTSUPP;
++
++ *idtype = *fsystype = MACH_MSG_TYPE_MOVE_SEND;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ err = io_identity (netfs_node_netnode (user->po->np)->file,
++ id, fsys, fileno);
++ pthread_mutex_unlock (&user->po->np->lock);
++ return err;
++}
++
++#define NETFS_S_SIMPLE(name) \
++error_t \
++netfs_S_##name (struct protid *user) \
++{ \
++ error_t err; \
++ \
++ if (!user) \
++ return EOPNOTSUPP; \
++ \
++ pthread_mutex_lock (&user->po->np->lock); \
++ err = name (netfs_node_netnode (user->po->np)->file); \
++ pthread_mutex_unlock (&user->po->np->lock); \
++ return err; \
++}
++
++NETFS_S_SIMPLE (io_get_conch)
++NETFS_S_SIMPLE (io_release_conch)
++NETFS_S_SIMPLE (io_eofnotify)
++NETFS_S_SIMPLE (io_readnotify)
++NETFS_S_SIMPLE (io_readsleep)
++NETFS_S_SIMPLE (io_sigio)
++
++error_t
++netfs_S_io_prenotify (struct protid *user,
++ vm_offset_t start, vm_offset_t stop)
++{
++ error_t err;
++
++ if (!user)
++ return EOPNOTSUPP;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ err = io_prenotify (netfs_node_netnode (user->po->np)->file, start, stop);
++ pthread_mutex_unlock (&user->po->np->lock);
++ return err;
++}
++
++error_t
++netfs_S_io_postnotify (struct protid *user,
++ vm_offset_t start, vm_offset_t stop)
++{
++ error_t err;
++
++ if (!user)
++ return EOPNOTSUPP;
++
++ pthread_mutex_lock (&user->po->np->lock);
++ err = io_postnotify (netfs_node_netnode (user->po->np)->file, start, stop);
++ pthread_mutex_unlock (&user->po->np->lock);
++ return err;
++}
++
++/* This overrides the library's definition. */
++int
++netfs_demuxer (mach_msg_header_t *inp,
++ mach_msg_header_t *outp)
++{
++ mig_routine_t routine;
++ if ((routine = netfs_io_server_routine (inp)) ||
++ (routine = netfs_fs_server_routine (inp)) ||
++ (routine = ports_notify_server_routine (inp)) ||
++ (routine = netfs_fsys_server_routine (inp)) ||
++ /* XXX we should intercept interrupt_operation and do
++ the ports_S_interrupt_operation work as well as
++ sending an interrupt_operation to the underlying file.
++ */
++ (routine = ports_interrupt_server_routine (inp)))
++ {
++ (*routine) (inp, outp);
++ return TRUE;
++ }
++ else
++ {
++ /* We didn't recognize the message ID, so pass the message through
++ unchanged to the underlying file. */
++ struct protid *cred;
++ if (MACH_MSGH_BITS_LOCAL (inp->msgh_bits) ==
++ MACH_MSG_TYPE_PROTECTED_PAYLOAD)
++ cred = ports_lookup_payload (netfs_port_bucket,
++ inp->msgh_protected_payload,
++ netfs_protid_class);
++ else
++ cred = ports_lookup_port (netfs_port_bucket,
++ inp->msgh_local_port,
++ netfs_protid_class);
++ if (cred == 0)
++ /* This must be an unknown message on our fsys control port. */
++ return 0;
++ else
++ {
++ error_t err;
++ assert (MACH_MSGH_BITS_LOCAL (inp->msgh_bits)
++ == MACH_MSG_TYPE_MOVE_SEND
++ || MACH_MSGH_BITS_LOCAL (inp->msgh_bits)
++ == MACH_MSG_TYPE_PROTECTED_PAYLOAD);
++ inp->msgh_bits = (inp->msgh_bits & MACH_MSGH_BITS_COMPLEX)
++ | MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND,
++ MACH_MSGH_BITS_REMOTE (inp->msgh_bits));
++ inp->msgh_local_port = inp->msgh_remote_port; /* reply port */
++ inp->msgh_remote_port = netfs_node_netnode (cred->po->np)->file;
++ err = mach_msg (inp, MACH_SEND_MSG, inp->msgh_size, 0,
++ MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
++ MACH_PORT_NULL);
++ assert_perror (err); /* XXX should synthesize reply */
++ ports_port_deref (cred);
++ /* We already sent the message, so the server loop shouldn't do it again. */
++ ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY;
++ return 1;
++ }
++ }
++}
+diff --git a/trans/chroot.h b/trans/chroot.h
+new file mode 100644
+index 0000000..057a251
+--- /dev/null
++++ b/trans/chroot.h
+@@ -0,0 +1,34 @@
++#ifndef __HURD_CHROOT_H__
++#define __HURD_CHROOT_H__
++
++struct netnode
++{
++ file_t file; /* port on real file */
++};
++
++struct chroot_node;
++
++error_t chroot_init (void);
++
++/* Make a new virtual node. Always consumes the ports. If
++ successful, NP will be locked. */
++error_t chroot_new_node (file_t file, size_t size, struct node **np);
++
++/* Users must implement this. */
++void chroot_node_norefs (struct node *np);
++
++/* Return the address of the chroot_node for NODE. NODE must have been
++ allocated using chroot_new_node. */
++static inline struct chroot_node *
++chroot_node (struct node *node)
++{
++ return (struct chroot_node *) ((char *) netfs_node_netnode (node)
++ + sizeof (struct netnode));
++}
++
++// XXX
++pthread_mutex_t idport_ihash_lock;
++struct hurd_ihash idport_ihash;
++
++
++#endif /* __HURD_CHROOT_H__ */
+diff --git a/trans/gpg.c b/trans/gpg.c
+new file mode 100644
+index 0000000..c974473
+--- /dev/null
++++ b/trans/gpg.c
+@@ -0,0 +1,679 @@
++/* gpg -- a translator for encrypting, decrypting, and verifying files
++ Copyright (C) 2016 Free Software Foundation, Inc.
++
++ This program is free software; you can redistribute it and/or
++ modify it under the terms of the GNU General Public License as
++ published by the Free Software Foundation; either version 2, or (at
++ your option) any later version.
++
++ This program is distributed in the hope that it will be useful, but
++ WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
++
++#include <argz.h>
++#include <argp.h>
++#include <dirent.h>
++#include <error.h>
++#include <hurd/netfs.h>
++#include <stddef.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <fcntl.h>
++#include <sys/mman.h>
++#include <sys/stat.h>
++#include <pthread.h>
++#include <hurd/ihash.h>
++#include <hurd/paths.h>
++#include <sys/types.h>
++#include <sys/wait.h>
++#include <unistd.h>
++
++#include <version.h>
++
++#include "chroot.h"
++
++#include "libnetfs/fs_S.h"
++#include "libnetfs/io_S.h"
++#include "libnetfs/fsys_S.h"
++#include "libports/notify_S.h"
++#include "libports/interrupt_S.h"
++
++const char *argp_program_version = STANDARD_HURD_VERSION (gpg);
++
++char *netfs_server_name = "gpg";
++char *netfs_server_version = HURD_VERSION;
++int netfs_maxsymlinks = 16; /* arbitrary */
++
++struct chroot_node
++{
++ unsigned int encrypt:1;
++ file_t shadow_file;
++};
++
++#define GPG_DEFAULT_FILENAME "/usr/bin/gpg2"
++static char* gpg_filename = GPG_DEFAULT_FILENAME;
++static file_t gpg_file;
++
++int symmetric;
++char *recipients;
++size_t recipients_len;
++
++static error_t
++execute_gpg (mach_port_t in, mach_port_t out, mach_port_t extra,
++ char *argz, size_t argz_len, pid_t *pid)
++{
++ error_t err;
++ task_t task;
++ process_t proc;
++ mach_port_t ports[INIT_PORT_MAX];
++ mach_port_t fds[STDERR_FILENO + 2];
++ int ints[INIT_INT_MAX];
++ int i;
++
++ for (i = 0; i < INIT_PORT_MAX; i++)
++ ports[i] = MACH_PORT_NULL;
++ for (i = 0; i < STDERR_FILENO + 2; i++)
++ fds[i] = MACH_PORT_NULL;
++ memset (ints, 0, INIT_INT_MAX * sizeof(int));
++
++ ports[INIT_PORT_CWDIR] = getcwdir ();
++ ports[INIT_PORT_CRDIR] = getcrdir ();
++ ports[INIT_PORT_AUTH] = getauth ();
++ fds[STDIN_FILENO] = in;
++ fds[STDOUT_FILENO] = out;
++ fds[STDERR_FILENO] = getdport (STDERR_FILENO);
++ fds[STDERR_FILENO+1] = extra;
++
++ err = task_create (mach_task_self (), 0, &task);
++ if (err)
++ goto lose;
++
++ proc = getproc ();
++ proc_child (proc, task);
++
++ /* Try and exec GnuPG in TASK... */
++ err = file_exec (gpg_file, task, EXEC_DEFAULTS,
++ argz, argz_len, 0, 0,
++ fds, MACH_MSG_TYPE_COPY_SEND, STDERR_FILENO + 2,
++ ports, MACH_MSG_TYPE_COPY_SEND, INIT_PORT_MAX,
++ ints, INIT_INT_MAX, 0, 0, 0, 0);
++ if (err)
++ goto lose;
++
++ err = proc_task2pid (proc, task, pid);
++
++ lose:
++ for (i = 0; i < INIT_PORT_MAX; i++)
++ mach_port_deallocate (mach_task_self (), ports[i]);
++ mach_port_deallocate (mach_task_self (), fds[STDERR_FILENO]);
++ return err;
++}
++
++error_t
++clone_file (file_t file, file_t *new_file)
++{
++ error_t err;
++ off_t newp;
++ retry_type do_retry;
++ string_t retry_name;
++ err = dir_lookup (file, "", O_RDONLY, 0, &do_retry, retry_name, new_file);
++ if (err)
++ return err;
++ return io_seek (*new_file, 0, SEEK_SET, &newp);
++}
++
++static int
++gpg_verify (file_t sigfile, file_t orig_file)
++{
++ error_t err;
++ file_t file;
++ pid_t pid;
++ char *argz = NULL;
++ size_t argz_len = 0;
++ err = clone_file (orig_file, &file);
++ if (err)
++ return 0;
++
++ argz_add (&argz, &argz_len, "gpg2");
++ argz_add (&argz, &argz_len, "--verify");
++ argz_add (&argz, &argz_len, "/dev/fd/3");
++ argz_add (&argz, &argz_len, "-");
++ err = execute_gpg (file, getdport (STDERR_FILENO), sigfile, argz, argz_len, &pid);
++ mach_port_deallocate (mach_task_self (), file);
++ if (err)
++ {
++ error (0, err, "execute_gpg");
++ return 0;
++ }
++
++ int status;
++ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
++ { }
++
++ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
++}
++
++static int
++gpg_decrypt (file_t cipherfile, file_t plainfile)
++{
++ error_t err;
++ pid_t pid;
++ char *argz = NULL;
++ size_t argz_len = 0;
++
++ argz_add (&argz, &argz_len, "gpg2");
++ argz_add (&argz, &argz_len, "--output");
++ argz_add (&argz, &argz_len, "-");
++ err = execute_gpg (cipherfile, plainfile, MACH_PORT_NULL,
++ argz, argz_len, &pid);
++ if (err)
++ {
++ error (0, err, "execute_gpg");
++ return 0;
++ }
++
++ int status;
++ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
++ { }
++
++ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
++}
++
++static int
++gpg_encrypt (file_t plainfile, file_t cipherfile)
++{
++ error_t err;
++ pid_t pid;
++ char *argz = NULL;
++ size_t argz_len = 0;
++
++ argz_add (&argz, &argz_len, "gpg2");
++ argz_add (&argz, &argz_len, "--always-trust"); // XXX
++ argz_add (&argz, &argz_len, "--output");
++ argz_add (&argz, &argz_len, "-");
++ if (recipients)
++ {
++ char *recipient;
++ argz_add (&argz, &argz_len, "--encrypt");
++ for (recipient = recipients;
++ recipient;
++ recipient = argz_next (recipients, recipients_len, recipient))
++ {
++ argz_add (&argz, &argz_len, "--recipient");
++ argz_add (&argz, &argz_len, recipient);
++ }
++ }
++ if (symmetric)
++ argz_add (&argz, &argz_len, "--symmetric");
++
++ err = execute_gpg (plainfile, cipherfile, MACH_PORT_NULL,
++ argz, argz_len, &pid);
++ if (err)
++ {
++ error (0, err, "execute_gpg");
++ return 0;
++ }
++
++ int status;
++ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
++ { }
++
++ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
++}
++
++static error_t
++make_tmp_file (file_t *file)
++{
++ char name[] = "/tmp/gpgtmp-XXXXXX";
++ int fd;
++
++ fd = mkstemp (name);
++ if (fd == -1)
++ return errno;
++ if (unlink (name) == -1)
++ return errno;
++ *file = getdport (fd);
++ close (fd);
++ return 0;
++}
++
++error_t
++netfs_S_dir_lookup (struct protid *diruser,
++ char *filename,
++ int flags,
++ mode_t mode,
++ retry_type *do_retry,
++ char *retry_name,
++ mach_port_t *retry_port,
++ mach_msg_type_name_t *retry_port_type)
++{
++ struct node *dnp, *np;
++ error_t err;
++ struct protid *newpi;
++ struct iouser *user;
++ mach_port_t file;
++
++ if (!diruser)
++ return EOPNOTSUPP;
++
++ dnp = diruser->po->np;
++
++ mach_port_t dir = netfs_node_netnode (dnp)->file;
++ err = dir_lookup (dir, filename,
++ flags & (O_NOLINK|O_RDWR|O_EXEC|O_CREAT|O_EXCL|O_NONBLOCK),
++ mode, do_retry, retry_name, &file);
++ if (err == ENOENT)
++ {
++ file_t gpgfile;
++ char *gpgname;
++ asprintf (&gpgname, "%s.gpg", filename);
++ gpgfile = file_name_lookup_under (dir, gpgname, O_RDONLY, 0);
++ if (MACH_PORT_VALID (gpgfile))
++ {
++ file_t tmpfile;
++ int ok;
++ off_t newp;
++
++ err = make_tmp_file (&tmpfile);
++ if (err)
++ return err;
++ ok = gpg_decrypt (gpgfile, tmpfile);
++ mach_port_deallocate (mach_task_self (), gpgfile);
++ if (! ok)
++ {
++ mach_port_deallocate (mach_task_self (), tmpfile);
++ return EIO;
++ }
++
++ err = io_seek (tmpfile, 0, SEEK_SET, &newp);
++ if (err)
++ {
++ mach_port_deallocate (mach_task_self (), tmpfile);
++ return err;
++ }
++
++ *do_retry = FS_RETRY_NORMAL;
++ retry_name[0] = 0;
++ *retry_port = tmpfile;
++ *retry_port_type = MACH_MSG_TYPE_MOVE_SEND;
++ return 0;
++ }
++ free (gpgname);
++ }
++
++ if (err)
++ {
++ /* xxx deref stuff */
++ return err;
++ }
++
++ switch (*do_retry)
++ {
++ case FS_RETRY_NORMAL:
++ break;
++
++ case FS_RETRY_REAUTH:
++ error (0, 0, "FS_RETRY_REAUTH");
++ /* Fallthrough. */
++
++ case FS_RETRY_MAGICAL:
++ *retry_port = file;
++ *retry_port_type = MACH_MSG_TYPE_MOVE_SEND;
++ return 0;
++
++ default:
++ /* Invalid response to our dir_lookup request. */
++ if (MACH_PORT_VALID (file))
++ mach_port_deallocate (mach_task_self (), file);
++ *retry_port = MACH_PORT_NULL;
++ *retry_port_type = MACH_MSG_TYPE_COPY_SEND;
++ return EOPNOTSUPP;
++ }
++
++ /* We have a new port to an underlying node.
++ Find or make our corresponding virtual node. */
++
++ np = 0;
++
++ redo_hash_lookup:
++ pthread_mutex_lock (&dnp->lock);
++ err = chroot_new_node (file, sizeof (struct chroot_node), &np);
++ pthread_mutex_unlock (&dnp->lock);
++ if (!err)
++ {
++ chroot_node (np)->encrypt = chroot_node (dnp)->encrypt;
++ chroot_node (np)->shadow_file = MACH_PORT_NULL;
++ err = netfs_validate_stat (np, diruser->user);
++ }
++ if (err)
++ goto lose;
++
++ {
++ if (chroot_node (np)->encrypt && flags & O_WRITE
++ && strncmp (filename, "dev/", 4) != 0)
++ {
++ char *ciphname;
++ asprintf (&ciphname, "%s.gpg", filename);
++ if (ciphname == NULL)
++ {
++ err = errno;
++ goto lose;
++ }
++
++ /* Fixup name. */
++ err = dir_link (dir, file, ciphname, 0);
++ free (ciphname);
++ if (err)
++ return err;
++
++ err = dir_unlink (dir, filename);
++ if (err)
++ return err;
++
++ err = make_tmp_file (&chroot_node (np)->shadow_file);
++ //err = dir_mkfile (dir, flags, mode, &chroot_node (np)->shadow_file);
++ if (err)
++ return err;
++ }
++
++ if (retry_name[0] == 0)
++ {
++ file_t sig;
++ char *signame;
++ asprintf (&signame, "%s.sig", filename);
++ sig = file_name_lookup_under (dir, signame, O_RDONLY, 0);
++ if (MACH_PORT_VALID (sig))
++ {
++ if (! gpg_verify (sig, file))
++ err = EIO;
++ free (signame);
++ signame = NULL;
++ mach_port_deallocate (mach_task_self (), sig);
++ if (err)
++ goto lose;
++ }
++ free (signame);
++ }
++ }
++
++ assert (*do_retry == FS_RETRY_NORMAL);
++ flags &= ~(O_CREAT|O_EXCL|O_NOLINK|O_NOTRANS|O_NONBLOCK);
++
++ err = iohelp_dup_iouser (&user, diruser->user);
++ if (!err)
++ {
++ newpi = netfs_make_protid (netfs_make_peropen (np, flags, diruser->po),
++ user);
++ if (! newpi)
++ {
++ iohelp_free_iouser (user);
++ err = errno;
++ }
++ else
++ {
++ *retry_port = ports_get_right (newpi);
++ *retry_port_type = MACH_MSG_TYPE_MAKE_SEND;
++ ports_port_deref (newpi);
++ }
++ }
++
++ lose:
++ if (dir != netfs_node_netnode (dnp)->file)
++ mach_port_deallocate (mach_task_self (), dir);
++ if (np != NULL)
++ netfs_nput (np);
++ return err;
++}
++
++error_t
++netfs_attempt_mkfile (struct iouser *user, struct node *dir,
++ mode_t mode, struct node **np)
++{
++ file_t newfile;
++ error_t err = dir_mkfile (netfs_node_netnode (dir)->file, O_RDWR|O_EXEC,
++ mode, &newfile);
++ pthread_mutex_unlock (&dir->lock);
++ if (err)
++ return err;
++
++ err = chroot_new_node (newfile, sizeof (struct chroot_node), np);
++ if (err)
++ return err;
++
++ if (chroot_node (dir)->encrypt)
++ {
++ err = make_tmp_file (&chroot_node (*np)->shadow_file);
++ if (err)
++ return err;
++ }
++ else
++ chroot_node (*np)->shadow_file = MACH_PORT_NULL;
++
++ pthread_mutex_unlock (&(*np)->lock);
++ return err;
++}
++
++error_t
++netfs_attempt_read (struct iouser *cred, struct node *np,
++ off_t offset, size_t *len, void *data)
++{
++ struct chroot_node *n = chroot_node (np);
++ char *buf = data;
++ error_t err = io_read (n->shadow_file ?: netfs_node_netnode (np)->file,
++ &buf, len, offset, *len);
++ if (err == 0 && buf != data)
++ {
++ memcpy (data, buf, *len);
++ munmap (buf, *len);
++ }
++ return err;
++}
++
++error_t
++netfs_attempt_write (struct iouser *cred, struct node *np,
++ off_t offset, size_t *len, void *data)
++{
++ struct chroot_node *n = chroot_node (np);
++ return io_write (n->shadow_file ?: netfs_node_netnode (np)->file,
++ data, *len, offset, len);
++}
++
++error_t
++netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
++{
++ error_t err;
++ file_t gpg_file = netfs_node_netnode (np)->file;
++ struct chroot_node *n = chroot_node (np);
++
++ err = file_sync (n->shadow_file ?: gpg_file, wait, 0);
++ if (err)
++ return err;
++
++ if (wait && MACH_PORT_VALID (n->shadow_file))
++ {
++ file_t shadow_file;
++ off_t newp;
++
++ err = clone_file (n->shadow_file, &shadow_file);
++ if (err)
++ return err;
++
++ err = io_seek (gpg_file, 0, SEEK_SET, &newp);
++ if (err)
++ return err;
++
++ err = gpg_encrypt (shadow_file, gpg_file);
++ if (err)
++ return err;
++
++ mach_port_deallocate (mach_task_self (), shadow_file);
++ err = file_sync (gpg_file, wait, 0);
++ }
++
++ return err;
++}
++
++
++error_t
++netfs_attempt_link (struct iouser *user, struct node *dir,
++ struct node *file, char *name, int excl)
++{
++ /* XXX translate name if file is encrypted. */
++ return dir_link (netfs_node_netnode (dir)->file,
++ netfs_node_netnode (file)->file, name, excl);
++}
++
++void
++chroot_node_norefs (struct node *np)
++{
++ error_t err;
++ file_t gpg_file = netfs_node_netnode (np)->file;
++ struct chroot_node *n = chroot_node (np);
++ if (n->encrypt)
++ {
++ off_t newp;
++
++ err = io_seek (n->shadow_file, 0, SEEK_SET, &newp);
++ if (err)
++ return;
++
++ err = io_seek (gpg_file, 0, SEEK_SET, &newp);
++ if (err)
++ return;
++ error (0, 0, "%s: encrypting %d", __FUNCTION__, n->shadow_file);
++ err = gpg_encrypt (n->shadow_file, gpg_file);
++ if (err)
++ return;
++
++ mach_port_deallocate (mach_task_self (), n->shadow_file);
++ }
++}
++
++
++error_t
++netfs_get_dirents (struct iouser *cred, struct node *dir,
++ int entry, int nentries, char **data,
++ mach_msg_type_number_t *datacnt,
++ vm_size_t bufsize, int *amt)
++{
++ error_t err;
++ char *ptr;
++ struct dirent *dent;
++ err = dir_readdir (netfs_node_netnode (dir)->file, data, datacnt,
++ entry, nentries, bufsize, amt);
++ if (err)
++ return err;
++
++ for (ptr = *data, dent = (struct dirent *) ptr;
++ ptr - *data < *datacnt;
++ ptr += dent->d_reclen, dent = (struct dirent *) ptr)
++ {
++ size_t len = strlen (dent->d_name);
++ if (len > 4 && strcmp (&dent->d_name[len - 4], ".gpg") == 0)
++ memset (&dent->d_name[len - 4], 0, 4);
++ }
++
++ return 0;
++}
++
++#define OPT_GPG_PROGRAM -1
++
++static struct argp_option options[] =
++{
++ {NULL, 0, NULL, 0, "How to encrypt:", 1},
++ {"recipient", 'r', "NAME", 0, "Encrypt for user NAME. "
++ "Can be given multiple times.", 1},
++ {"symmetric", 'c', NULL, 0, "Encrypt with a password.", 1},
++ {NULL, 0, NULL, 0, "Advanced options:", 2},
++ {"gpg-program", OPT_GPG_PROGRAM, "FILENAME", 0,
++ "Path to the GNU Privacy Guard executable ["GPG_DEFAULT_FILENAME"].", 2},
++ {0}
++};
++
++static char *args_doc = "";
++static char *doc = "XXX."
++"\vXXX.";
++
++static error_t
++parse_opt (int key, char *arg, struct argp_state *state)
++{
++ error_t err;
++ switch (key)
++ {
++ case 'r':
++ err = argz_add (&recipients, &recipients_len, arg);
++ if (err)
++ error (1, err, "argz_add");
++ break;
++
++ case 'c':
++ symmetric = 1;
++ break;
++
++ case OPT_GPG_PROGRAM:
++ gpg_filename = arg;
++ break;
++
++ default:
++ return ARGP_ERR_UNKNOWN;
++ }
++ return 0;
++}
++
++int
++main (int argc, char **argv)
++{
++ error_t err;
++ mach_port_t bootstrap;
++
++ struct argp argp =
++ {
++ .options = options,
++ .parser = parse_opt,
++ .args_doc = args_doc,
++ .doc = doc,
++ };
++
++ /* Parse our command line arguments (all none of them). */
++ argp_parse (&argp, argc, argv, 0, 0, 0);
++
++ task_get_bootstrap_port (mach_task_self (), &bootstrap);
++ if (! MACH_PORT_VALID (bootstrap))
++ error (1, 0, "Must be started as a translator");
++
++ gpg_file = file_name_lookup (gpg_filename, O_EXEC, 0);
++ if (! MACH_PORT_VALID (gpg_file))
++ error (1, errno, "%s", gpg_filename);
++
++ netfs_init ();
++ chroot_init ();
++
++ /* Get our underlying node (we presume it's a directory) and use
++ that to make the root node of the filesystem. */
++ err = chroot_new_node (netfs_startup (bootstrap, O_READ),
++ sizeof (struct chroot_node), &netfs_root_node);
++ if (err)
++ error (5, err, "Cannot create root node");
++
++ err = netfs_validate_stat (netfs_root_node, 0);
++ if (err)
++ error (6, err, "Cannot stat underlying node");
++
++ netfs_root_node->nn_stat.st_mode &= ~(S_IPTRANS | S_IATRANS);
++ netfs_root_node->nn_stat.st_mode |= S_IROOT;
++ pthread_mutex_unlock (&netfs_root_node->lock);
++
++ chroot_node (netfs_root_node)->encrypt = 1; // XXX
++ chroot_node (netfs_root_node)->shadow_file = MACH_PORT_NULL;
++
++ netfs_server_loop (); /* Never returns. */
++
++ /*NOTREACHED*/
++ return 0;
++}
+diff --git a/trans/identity.c b/trans/identity.c
+new file mode 100644
+index 0000000..260dd3e
+--- /dev/null
++++ b/trans/identity.c
+@@ -0,0 +1,113 @@
++/* identity -- an translator interposing file RPCs.
++ Copyright (C) 2016 Free Software Foundation, Inc.
++
++ This program is free software; you can redistribute it and/or
++ modify it under the terms of the GNU General Public License as
++ published by the Free Software Foundation; either version 2, or (at
++ your option) any later version.
++
++ This program is distributed in the hope that it will be useful, but
++ WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software
++ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
++
++#include <argp.h>
++#include <error.h>
++#include <fcntl.h>
++#include <hurd/netfs.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#include <version.h>
++
++#include "chroot.h"
++
++const char *argp_program_version = STANDARD_HURD_VERSION (identity);
++
++char *netfs_server_name = "identity";
++char *netfs_server_version = HURD_VERSION;
++int netfs_maxsymlinks = 16; /* arbitrary */
++
++struct chroot_node
++{
++};
++
++void
++chroot_node_norefs (struct node *np)
++{
++}
++
++
++static struct argp_option options[] =
++{
++ {0}
++};
++
++static char *args_doc = "";
++static char *doc =
++ "The identity translator relays file RPCs without changing them."
++ "\v"
++ "It can be used in conjunction with settrans --chroot to implement "
++ "a hurdish chroot utility.";
++
++static error_t
++parse_opt (int key, char *arg, struct argp_state *state)
++{
++ switch (key)
++ {
++ default:
++ return ARGP_ERR_UNKNOWN;
++ }
++ return 0;
++}
++
++int
++main (int argc, char **argv)
++{
++ error_t err;
++ mach_port_t bootstrap;
++
++ struct argp argp =
++ {
++ .options = options,
++ .parser = parse_opt,
++ .args_doc = args_doc,
++ .doc = doc,
++ };
++
++ /* Parse our command line arguments (all none of them). */
++ argp_parse (&argp, argc, argv, 0, 0, 0);
++
++ task_get_bootstrap_port (mach_task_self (), &bootstrap);
++ if (! MACH_PORT_VALID (bootstrap))
++ error (1, 0, "Must be started as a translator");
++
++ netfs_init ();
++ chroot_init ();
++
++ /* Get our underlying node (we presume it's a directory) and use
++ that to make the root node of the filesystem. */
++ err = chroot_new_node (netfs_startup (bootstrap, O_READ),
++ sizeof (struct chroot_node), &netfs_root_node);
++ if (err)
++ error (5, err, "Cannot create root node");
++
++ err = netfs_validate_stat (netfs_root_node, 0);
++ if (err)
++ error (6, err, "Cannot stat underlying node");
++
++ netfs_root_node->nn_stat.st_mode &= ~(S_IPTRANS | S_IATRANS);
++ netfs_root_node->nn_stat.st_mode |= S_IROOT;
++ pthread_mutex_unlock (&netfs_root_node->lock);
++
++ netfs_server_loop (); /* Never returns. */
++
++ /*NOTREACHED*/
++ return 0;
++}
+diff --git a/utils/Makefile b/utils/Makefile
+index d2ef9e8..aa6ff34 100644
+--- a/utils/Makefile
++++ b/utils/Makefile
+@@ -22,16 +22,16 @@ targets = shd ps settrans showtrans syncfs fsysopts \
+ storeinfo login w uptime ids loginpr sush vmstat portinfo \
+ devprobe vminfo addauth rmauth unsu setauth ftpcp ftpdir storecat \
+ storeread msgport rpctrace mount gcore fakeauth fakeroot remap \
+- umount nullauth rpcscan vmallocate
++ umount nullauth rpcscan vmallocate gpg-env
+
+-special-targets = loginpr sush uptime fakeroot remap
++special-targets = loginpr sush uptime fakeroot remap gpg-env
+ SRCS = shd.c ps.c settrans.c syncfs.c showtrans.c addauth.c rmauth.c \
+ fsysopts.c storeinfo.c login.c loginpr.sh sush.sh w.c \
+ uptime.sh psout.c ids.c vmstat.c portinfo.c devprobe.c vminfo.c \
+ parse.c frobauth.c frobauth-mod.c setauth.c pids.c nonsugid.c \
+ unsu.c ftpcp.c ftpdir.c storeread.c storecat.c msgport.c \
+ rpctrace.c mount.c gcore.c fakeauth.c fakeroot.sh remap.sh \
+- nullauth.c match-options.c msgids.c rpcscan.c
++ nullauth.c match-options.c msgids.c rpcscan.c gpg-env.sh
+
+ OBJS = $(filter-out %.sh,$(SRCS:.c=.o))
+ HURDLIBS = ps ihash store fshelp ports ftpconn shouldbeinlibc
+diff --git a/utils/gpg-env.sh b/utils/gpg-env.sh
+new file mode 100755
+index 0000000..4e4128b
+--- /dev/null
++++ b/utils/gpg-env.sh
+@@ -0,0 +1,121 @@
++#!/bin/sh
++# Execute a command in an environment which encrypts, decrypts, and
++# verifies files on demand.
++#
++# Copyright (C) 2002, 2013, 2016 Free Software Foundation, Inc.
++#
++# This file is part of the GNU Hurd.
++#
++# The GNU Hurd is free software; you can redistribute it and/or
++# modify it under the terms of the GNU General Public License as
++# published by the Free Software Foundation; either version 2, or (at
++# your option) any later version.
++#
++# The GNU Hurd is distributed in the hope that it will be useful, but
++# WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++# General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++#
++
++USAGE="Usage:
++ [$0] encrypt for RECIPIENT [RECIPIENT...] -- [OPTION...] [COMMAND...]
++ [$0] encrypt with password [OPTION...] [COMMAND...]
++ [$0] decrypt [OPTION...] [COMMAND...]
++ [$0] decrypt with password [OPTION...] [COMMAND...]
++ [$0] verify [OPTION...] [COMMAND...]"
++DOC="Execute COMMAND in an environment where files are automatically
++encrypted, decrypted and verified."
++
++help()
++{
++ [ "$1" ] && echo "$1
++"
++ echo "$USAGE"
++ echo "$DOC"
++ echo ""
++ echo " -?, --help Give this help list"
++ echo " --usage Give a short usage message"
++ echo " -V, --version Print program version"
++ [ "$1" ] && exit 1 || exit 0
++}
++
++if [ "$(basename $0)" = "gpg-env.sh" ] \
++ || [ "$(basename $0)" = "gpg-env" ]; then
++ ACTION="$1"
++ if [ ! "$ACTION" ]; then
++ help "No action given."
++ fi
++ shift
++else
++ ACTION="$(basename $0)"
++fi
++
++case "$ACTION" in
++ "encrypt") ;;
++ "decrypt") ;;
++ "verify") ;;
++ *)
++ help "Invalid action '$ACTION'."
++esac
++
++ENCRYPT=""
++if [ "$ACTION" = "encrypt" ]; then
++ if [ "$1" = "with" ] && [ "$2" = "password" ]; then
++ ENCRYPT="--symmetric"
++ shift 2
++ elif [ "$1" = "for" ]; then
++ shift
++ while [ "$#" -gt 0 ] && [ "x$1" != "x--" ]; do
++ ENCRYPT="$ENCRYPT --recipient $1"
++ shift
++ done
++ if [ "$ENCRYPT" = "" ]; then
++ echo "No recipients given."
++ exit 1
++ fi
++ if [ "x$1" = "x--" ]; then
++ shift
++ elif [ "$#" -eq 0 ]; then
++ # it's ok if there are no more arguments
++ :
++ else
++ echo "Recipient list must be terminated using '--'."
++ exit 1
++ fi
++ fi
++fi
++
++while [ "$#" -gt 0 ]; do
++ case "$1" in
++ --help|"-?")
++ help
++ ;;
++ --usage)
++ echo "$USAGE"
++ echo "Options: [-V?] [--help] [--usage] [--version]"
++ exit 0;;
++ --version|-V)
++ echo "STANDARD_HURD_VERSION_gpg-env_"; exit 0;;
++ --)
++ shift
++ break
++ ;;
++ *)
++ break
++ esac
++done
++
++if [ $# -eq 0 ]; then
++ set -- ${SHELL:-/bin/sh}
++fi
++
++# We exec settrans, which execs the target command in the chroot
++# context provided by /hurd/gpg.
++exec /bin/settrans \
++ --chroot-chdir "$PWD" \
++ --chroot "$@" -- \
++ / /hurd/gpg $ENCRYPT
+--
+2.1.4
+
diff --git a/debian/patches/gpg0002-adjust-to-lockless-libnetfs.patch b/debian/patches/gpg0002-adjust-to-lockless-libnetfs.patch
new file mode 100644
index 00000000..ba34ab46
--- /dev/null
+++ b/debian/patches/gpg0002-adjust-to-lockless-libnetfs.patch
@@ -0,0 +1,30 @@
+From 097608e592a00ca8382e13e3aaad4be4f28afeb4 Mon Sep 17 00:00:00 2001
+From: Justus Winter <justus@gnupg.org>
+Date: Mon, 7 Mar 2016 19:35:39 +0100
+Subject: [PATCH hurd 2/2] adjust to lockless libnetfs
+
+---
+ trans/chroot.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/trans/chroot.c b/trans/chroot.c
+index 089ad84..8d6f111 100644
+--- a/trans/chroot.c
++++ b/trans/chroot.c
+@@ -74,13 +74,10 @@ void
+ netfs_node_norefs (struct node *np)
+ {
+ pthread_mutex_unlock (&np->lock);
+- pthread_spin_unlock (&netfs_node_refcnt_lock);
+
+ chroot_node_norefs (np);
+ mach_port_deallocate (mach_task_self (), netfs_node_netnode (np)->file);
+ free (np);
+-
+- pthread_spin_lock (&netfs_node_refcnt_lock);
+ }
+
+ /* This is the cleanup function we install in netfs_protid_class. If
+--
+2.1.4
+
diff --git a/debian/patches/series b/debian/patches/series
index 6de31e3f..7dc39d91 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -37,3 +37,5 @@ exec_filename0003-Use-the-new-_hurd_exec_file_name-function.patch
exec_filename0004-This-patch-is-an-amendment-of-exec_filename_exec.pat.patch
crash0001-xxx-crash-logging-works.patch
flavio0001-Remove-global-netfs-lock-and-use-hard-and-soft-refer.patch
+gpg0001-trans-add-transparent-GnuPG-translator.patch
+gpg0002-adjust-to-lockless-libnetfs.patch