aboutsummaryrefslogtreecommitdiff
path: root/trans/fakeroot.c
diff options
context:
space:
mode:
Diffstat (limited to 'trans/fakeroot.c')
-rw-r--r--trans/fakeroot.c1041
1 files changed, 1041 insertions, 0 deletions
diff --git a/trans/fakeroot.c b/trans/fakeroot.c
new file mode 100644
index 00000000..32a34ec4
--- /dev/null
+++ b/trans/fakeroot.c
@@ -0,0 +1,1041 @@
+/* fakeroot -- a translator for faking actions that aren't really permitted
+ Copyright (C) 2002, 2003, 2008, 2013 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 <hurd/netfs.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <argp.h>
+#include <error.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 <version.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 (fakeroot);
+
+char *netfs_server_name = "fakeroot";
+char *netfs_server_version = HURD_VERSION;
+int netfs_maxsymlinks = 16; /* arbitrary */
+
+static auth_t fakeroot_auth_port;
+
+struct netnode
+{
+ hurd_ihash_locp_t idport_locp;/* easy removal pointer in idport ihash */
+ mach_port_t idport; /* port from io_identity */
+ int openmodes; /* O_READ | O_WRITE | O_EXEC */
+ file_t file; /* port on real file */
+
+ unsigned int faked;
+};
+
+#define FAKE_UID (1 << 0)
+#define FAKE_GID (1 << 1)
+#define FAKE_AUTHOR (1 << 2)
+#define FAKE_MODE (1 << 3)
+#define FAKE_DEFAULT (1 << 4)
+
+pthread_mutex_t idport_ihash_lock = PTHREAD_MUTEX_INITIALIZER;
+struct hurd_ihash idport_ihash
+= HURD_IHASH_INITIALIZER (sizeof (struct node)
+ + offsetof (struct netnode, idport_locp));
+
+
+/* Make a new virtual node. Always consumes the ports. If
+ successful, NP will be locked. */
+static error_t
+new_node (file_t file, mach_port_t idport, int locked, int openmodes,
+ struct node **np)
+{
+ error_t err;
+ struct netnode *nn;
+ *np = netfs_make_node_alloc (sizeof *nn);
+ if (*np == 0)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ if (idport != MACH_PORT_NULL)
+ mach_port_deallocate (mach_task_self (), idport);
+ if (locked)
+ pthread_mutex_unlock (&idport_ihash_lock);
+ return ENOMEM;
+ }
+ nn = netfs_node_netnode (*np);
+ nn->file = file;
+ nn->openmodes = openmodes;
+ if (idport != MACH_PORT_NULL)
+ nn->idport = idport;
+ else
+ {
+ ino_t fileno;
+ mach_port_t fsidport;
+ assert (!locked);
+ err = io_identity (file, &nn->idport, &fsidport, &fileno);
+ if (err)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ free (*np);
+ return err;
+ }
+ }
+
+ if (!locked)
+ pthread_mutex_lock (&idport_ihash_lock);
+ err = hurd_ihash_add (&idport_ihash, nn->idport, *np);
+ if (err)
+ goto lose;
+
+ pthread_mutex_lock (&(*np)->lock);
+ pthread_mutex_unlock (&idport_ihash_lock);
+ return 0;
+
+ lose:
+ pthread_mutex_unlock (&idport_ihash_lock);
+ mach_port_deallocate (mach_task_self (), nn->idport);
+ mach_port_deallocate (mach_task_self (), file);
+ free (*np);
+ return err;
+}
+
+static void
+set_default_attributes (struct node *np)
+{
+ netfs_node_netnode (np)->faked = FAKE_UID | FAKE_GID | FAKE_DEFAULT;
+ np->nn_stat.st_uid = 0;
+ np->nn_stat.st_gid = 0;
+}
+
+static void
+set_faked_attribute (struct node *np, unsigned int faked)
+{
+ netfs_node_netnode (np)->faked |= faked;
+
+ if (netfs_node_netnode (np)->faked & FAKE_DEFAULT)
+ {
+ /* Now that the node has non-default faked attributes, they have to be
+ retained for future accesses. Account for the hash table reference.
+
+ XXX This means such nodes are currently leaked. Hopefully, there
+ won't be too many of them until the translator is shut down, and
+ the data structures should make implementing garbage collection
+ easy enough if it's ever needed, although scalability could be
+ improved. */
+ netfs_nref (np);
+ netfs_node_netnode (np)->faked &= ~FAKE_DEFAULT;
+ }
+}
+
+/* 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);
+
+ pthread_mutex_lock (&idport_ihash_lock);
+ hurd_ihash_locp_remove (&idport_ihash, netfs_node_netnode (np)->idport_locp);
+ pthread_mutex_unlock (&idport_ihash_lock);
+
+ mach_port_deallocate (mach_task_self (), netfs_node_netnode (np)->file);
+ mach_port_deallocate (mach_task_self (), netfs_node_netnode (np)->idport);
+ 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
+fakeroot_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");
+ }
+}
+
+/* Given an existing node, make sure it has NEWMODES in its openmodes.
+ If not null, FILE is a port with those openmodes. */
+static error_t
+check_openmodes (struct netnode *nn, int newmodes, file_t file)
+{
+ error_t err = 0;
+
+ if (newmodes &~ nn->openmodes)
+ {
+ /* The user wants openmodes we haven't tried before. */
+
+ if (file != MACH_PORT_NULL && (nn->openmodes & ~newmodes))
+ {
+ /* Intersecting sets.
+ We need yet another new peropen on this node. */
+ mach_port_deallocate (mach_task_self (), file);
+ file = MACH_PORT_NULL;
+ }
+ if (file == MACH_PORT_NULL)
+ {
+ enum retry_type bad_retry;
+ char bad_retryname[1024]; /* XXX */
+ err = dir_lookup (nn->file, "", nn->openmodes | newmodes, 0,
+ &bad_retry, bad_retryname, &file);
+ if (!err && (bad_retry != FS_RETRY_NORMAL
+ || bad_retryname[0] != '\0'))
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ err = EGRATUITOUS;
+ }
+ }
+ if (! err)
+ {
+ /* The new port has more openmodes than
+ the old one. We can just use it now. */
+ mach_port_deallocate (mach_task_self (), nn->file);
+ nn->file = file;
+ nn->openmodes = newmodes;
+ }
+ }
+ else if (file != MACH_PORT_NULL)
+ mach_port_deallocate (mach_task_self (), file);
+
+ return err;
+}
+
+/* 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 check_openmodes (netfs_node_netnode (np),
+ flags & (O_RDWR|O_EXEC), MACH_PORT_NULL);
+}
+
+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;
+ mach_port_t idport, fsidport;
+ ino_t fileno;
+
+ if (!diruser)
+ return EOPNOTSUPP;
+
+ dnp = diruser->po->np;
+
+ mach_port_t dir = netfs_node_netnode (dnp)->file;
+ redo_lookup:
+ 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 (dir != netfs_node_netnode (dnp)->file)
+ mach_port_deallocate (mach_task_self (), dir);
+ if (err)
+ return err;
+
+ switch (*do_retry)
+ {
+ case FS_RETRY_REAUTH:
+ {
+ mach_port_t ref = mach_reply_port ();
+ err = io_reauthenticate (file, ref, MACH_MSG_TYPE_MAKE_SEND);
+ if (! err)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ err = auth_user_authenticate (fakeroot_auth_port, ref,
+ MACH_MSG_TYPE_MAKE_SEND,
+ &dir);
+ }
+ mach_port_destroy (mach_task_self (), ref);
+ if (err)
+ return err;
+ }
+ filename = retry_name;
+ goto redo_lookup;
+
+ case FS_RETRY_NORMAL:
+ if (retry_name[0] != '\0')
+ {
+ dir = file;
+ filename = retry_name;
+ goto redo_lookup;
+ }
+ break;
+
+ case FS_RETRY_MAGICAL:
+ if (file == MACH_PORT_NULL)
+ {
+ *retry_port = MACH_PORT_NULL;
+ *retry_port_type = MACH_MSG_TYPE_COPY_SEND;
+ return 0;
+ }
+ /* Fallthrough. */
+
+ default:
+ /* Invalid response to our dir_lookup request. */
+ if (file != MACH_PORT_NULL)
+ 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;
+ err = io_identity (file, &idport, &fsidport, &fileno);
+ if (err)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ return err;
+ }
+
+ mach_port_deallocate (mach_task_self (), fsidport);
+
+ redo_hash_lookup:
+ pthread_mutex_lock (&idport_ihash_lock);
+ pthread_mutex_lock (&dnp->lock);
+ /* The hashtable may not hold a true reference on the node. Acquire the
+ refcount lock so that, if a node is found, its reference counter cannot
+ drop to 0 before we get our own reference. */
+ pthread_spin_lock (&netfs_node_refcnt_lock);
+ np = hurd_ihash_find (&idport_ihash, idport);
+ if (np != NULL)
+ {
+ /* We already know about this node. */
+
+ if (np->references == 0)
+ {
+ /* But it might be in the process of being released. If so,
+ unlock the hash table to give the node a chance to actually
+ be removed and retry. */
+ pthread_spin_unlock (&netfs_node_refcnt_lock);
+ pthread_mutex_unlock (&dnp->lock);
+ pthread_mutex_unlock (&idport_ihash_lock);
+ goto redo_hash_lookup;
+ }
+
+ /* Otherwise, reference it right away. */
+ np->references++;
+ pthread_spin_unlock (&netfs_node_refcnt_lock);
+
+ mach_port_deallocate (mach_task_self (), idport);
+
+ if (np == dnp)
+ {
+ /* dnp is already locked. */
+ }
+ else
+ {
+ pthread_mutex_lock (&np->lock);
+ pthread_mutex_unlock (&dnp->lock);
+ }
+
+ err = check_openmodes (netfs_node_netnode (np),
+ (flags & (O_RDWR|O_EXEC)), file);
+ pthread_mutex_unlock (&idport_ihash_lock);
+ }
+ else
+ {
+ pthread_spin_unlock (&netfs_node_refcnt_lock);
+ err = new_node (file, idport, 1, flags, &np);
+ pthread_mutex_unlock (&dnp->lock);
+ if (!err)
+ {
+ set_default_attributes (np);
+ err = netfs_validate_stat (np, diruser->user);
+ }
+ }
+ if (err)
+ goto lose;
+
+ assert (retry_name[0] == '\0' && *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 (np != NULL)
+ netfs_nput (np);
+ return err;
+}
+
+/* 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;
+
+ if (netfs_node_netnode (np)->faked & FAKE_UID)
+ st.st_uid = np->nn_stat.st_uid;
+ if (netfs_node_netnode (np)->faked & FAKE_GID)
+ st.st_gid = np->nn_stat.st_gid;
+ if (netfs_node_netnode (np)->faked & FAKE_AUTHOR)
+ st.st_author = np->nn_stat.st_author;
+ if (netfs_node_netnode (np)->faked & FAKE_MODE)
+ st.st_mode = np->nn_stat.st_mode;
+
+ np->nn_stat = st;
+ np->nn_translated = S_ISLNK (st.st_mode) ? S_IFLNK : 0;
+
+ return 0;
+}
+
+/* Various netfs functions will call fshelp_isowner to check whether
+ USER is allowed to do some operation. As fakeroot is not running
+ within the fakeauth'ed environment, USER contains the real
+ user. Hence, we override this check. */
+error_t
+fshelp_isowner (struct stat *st, struct iouser *user)
+{
+ return 0;
+}
+
+error_t
+netfs_attempt_chown (struct iouser *cred, struct node *np,
+ uid_t uid, uid_t gid)
+{
+ if (uid != ~0U)
+ {
+ set_faked_attribute (np, FAKE_UID);
+ np->nn_stat.st_uid = uid;
+ }
+ if (gid != ~0U)
+ {
+ set_faked_attribute (np, FAKE_GID);
+ np->nn_stat.st_gid = gid;
+ }
+ return 0;
+}
+
+error_t
+netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author)
+{
+ set_faked_attribute (np, FAKE_AUTHOR);
+ np->nn_stat.st_author = author;
+ return 0;
+}
+
+/* Return the mode that the real underlying file should have if the
+ fake mode is being set to MODE. We always give ourselves read and
+ write permission so that we can open the file as root would be able
+ to. We give ourselves execute permission iff any execute bit is
+ set in the fake mode. */
+static inline mode_t
+real_from_fake_mode (mode_t mode)
+{
+ return mode | S_IREAD | S_IWRITE | (((mode << 3) | (mode << 6)) & S_IEXEC);
+}
+
+/* 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 ((mode & S_IFMT) == 0)
+ mode |= np->nn_stat.st_mode & S_IFMT;
+ if ((mode & S_IFMT) != (np->nn_stat.st_mode & S_IFMT))
+ return EOPNOTSUPP;
+
+ /* We don't bother with error checking since the fake mode change should
+ always succeed--worst case a later open will get EACCES. */
+ (void) file_chmod (netfs_node_netnode (np)->file, mode);
+ set_faked_attribute (np, FAKE_MODE);
+ np->nn_stat.st_mode = mode;
+ return 0;
+}
+
+/* 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
+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
+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
+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,
+ real_from_fake_mode (mode), &newfile);
+ pthread_mutex_unlock (&dir->lock);
+ if (err == 0)
+ err = new_node (newfile, MACH_PORT_NULL, 0, O_RDWR|O_EXEC, np);
+ if (err == 0)
+ 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
+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
+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)
+{
+ *types = O_RDWR|O_EXEC;
+ return 0;
+}
+
+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)
+{
+ return dir_readdir (netfs_node_netnode (dir)->file, data, datacnt,
+ entry, nentries, bufsize, amt);
+}
+
+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);
+ err = check_openmodes (netfs_node_netnode (user->po->np),
+ O_EXEC, MACH_PORT_NULL);
+ file = netfs_node_netnode (user->po->np)->file;
+ if (!err)
+ 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 = 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);
+ 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;
+ }
+ }
+}
+
+
+int
+main (int argc, char **argv)
+{
+ error_t err;
+ mach_port_t bootstrap;
+
+ struct argp argp = { .doc = "\
+A translator for faking privileged access to an underlying filesystem.\v\
+This translator appears to give transparent access to the underlying \
+directory node. However, all accesses are made using the credentials \
+of the translator regardless of the client and the translator fakes \
+success for chown and chmod operations that only root could actually do, \
+reporting the faked IDs and modes in later stat calls, and allows \
+any user to open nodes regardless of permissions as is done for root." };
+
+ /* Parse our command line arguments (all none of them). */
+ argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0);
+
+ fakeroot_auth_port = getauth ();
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap);
+ netfs_init ();
+
+ /* Install our own clean routine. */
+ netfs_protid_class->clean_routine = fakeroot_netfs_release_protid;
+
+ /* Get our underlying node (we presume it's a directory) and use
+ that to make the root node of the filesystem. */
+ err = new_node (netfs_startup (bootstrap, O_READ), MACH_PORT_NULL, 0, O_READ,
+ &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;
+ set_faked_attribute (netfs_root_node, FAKE_MODE);
+ pthread_mutex_unlock (&netfs_root_node->lock);
+
+ netfs_server_loop (); /* Never returns. */
+
+ /*NOTREACHED*/
+ return 0;
+}