summaryrefslogtreecommitdiff
path: root/trans/fakeroot.c
diff options
context:
space:
mode:
authorRoland McGrath <roland@gnu.org>2002-05-03 12:53:14 +0000
committerRoland McGrath <roland@gnu.org>2002-05-03 12:53:14 +0000
commit4f1647c0e5dc7d5ae14c0132c757e621a662fc2c (patch)
tree32e26c4e4bbb1ecefc2457bab5300d3a6f9a2bbf /trans/fakeroot.c
parente7ba4f841707b0586192f47837fca7725c7acab5 (diff)
2002-05-03 Roland McGrath <roland@frob.com>
* fakeroot.c: New file.
Diffstat (limited to 'trans/fakeroot.c')
-rw-r--r--trans/fakeroot.c569
1 files changed, 569 insertions, 0 deletions
diff --git a/trans/fakeroot.c b/trans/fakeroot.c
new file mode 100644
index 00000000..3683b122
--- /dev/null
+++ b/trans/fakeroot.c
@@ -0,0 +1,569 @@
+/* fakeroot -- a translator for faking actions that aren't really permitted
+ Copyright (C) 2002 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 <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 <cthreads.h>
+#include <hurd/ihash.h>
+#include <hurd/paths.h>
+
+#include <version.h>
+
+const char *argp_program_version = STANDARD_HURD_VERSION (fakeroot);
+
+int netfs_maxsymlinks = 16; /* arbitrary */
+
+struct netnode
+{
+ void **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_REFERENCE (1 << 4) /* got node_norefs with st_nlink > 0 */
+
+struct mutex idport_ihash_lock = MUTEX_INITIALIZER;
+struct ihash idport_ihash;
+
+
+static error_t
+new_node (file_t file, mach_port_t idport, int openmodes, struct node **np)
+{
+ error_t err;
+ struct netnode *nn = calloc (1, sizeof *nn);
+ if (nn == 0)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ if (idport != MACH_PORT_NULL)
+ mach_port_deallocate (mach_task_self (), idport);
+ return ENOMEM;
+ }
+ nn->file = file;
+ nn->openmodes = openmodes;
+ if (idport != MACH_PORT_NULL)
+ nn->idport = idport;
+ else
+ {
+ int fileno;
+ mach_port_t fsidport;
+ err = io_identity (file, &nn->idport, &fsidport, &fileno);
+ if (err)
+ {
+ mach_port_deallocate (mach_task_self (), file);
+ free (nn);
+ return err;
+ }
+ }
+ *np = netfs_make_node (nn);
+ if (*np == 0)
+ err = ENOMEM;
+ else
+ {
+ mutex_lock (&idport_ihash_lock);
+ err = ihash_add (&idport_ihash, nn->idport, *np, &nn->idport_locp);
+ mutex_unlock (&idport_ihash_lock);
+ }
+ if (err)
+ {
+ mach_port_deallocate (mach_task_self (), nn->idport);
+ mach_port_deallocate (mach_task_self (), file);
+ free (nn);
+ }
+ return err;
+}
+
+/* Node NP has no more references; free all its associated storage. */
+void
+netfs_node_norefs (struct node *np)
+{
+ if (np->nn->faked != 0
+ && netfs_validate_stat (np, 0) == 0 && np->nn_stat.st_nlink > 0)
+ {
+ /* The real node still exists and we have faked some attributes.
+ We must keep our node alive in core to retain those values.
+ XXX
+ For now, we will leak the node if it gets deleted later.
+ That will keep the underlying file alive with st_nlink=0
+ until this fakeroot filesystem dies. One easy solution
+ would be to scan nodes with references=1 for st_nlink=0
+ at some convenient time, periodically or in syncfs. */
+ if ((np->nn->faked & FAKE_REFERENCE) == 0)
+ {
+ np->nn->faked |= FAKE_REFERENCE;
+ ++np->references;
+ }
+ return;
+ }
+
+ spin_unlock (&netfs_node_refcnt_lock); /* Avoid deadlock. */
+ mutex_lock (&idport_ihash_lock);
+ spin_lock (&netfs_node_refcnt_lock);
+ /* Previous holder of this lock might have just got a reference. */
+ if (np->references > 0)
+ {
+ mutex_unlock (&idport_ihash_lock);
+ return;
+ }
+ ihash_locp_remove (&idport_ihash, np->nn->idport_locp);
+ mutex_unlock (&idport_ihash_lock);
+ mach_port_deallocate (mach_task_self (), np->nn->file);
+ mach_port_deallocate (mach_task_self (), np->nn->idport);
+ free (np->nn);
+ free (np);
+}
+
+/* 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 (np->nn->file, &st);
+ if (err)
+ return err;
+
+ if (np->nn->faked & FAKE_UID)
+ st.st_uid = np->nn_stat.st_uid;
+ if (np->nn->faked & FAKE_GID)
+ st.st_gid = np->nn_stat.st_gid;
+ if (np->nn->faked & FAKE_AUTHOR)
+ st.st_author = np->nn_stat.st_author;
+ if (np->nn->faked & FAKE_MODE)
+ st.st_mode = np->nn_stat.st_mode;
+
+ np->nn_stat = st;
+ return 0;
+}
+
+error_t
+netfs_attempt_chown (struct iouser *cred, struct node *np,
+ uid_t uid, uid_t gid)
+{
+ if (uid != -1)
+ {
+ np->nn->faked |= FAKE_UID;
+ np->nn_stat.st_uid = uid;
+ }
+ if (gid != -1)
+ {
+ np->nn->faked |= FAKE_GID;
+ np->nn_stat.st_gid = gid;
+ }
+ return 0;
+}
+
+error_t
+netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author)
+{
+ np->nn->faked |= 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) != (np->nn_stat.st_mode & S_IFMT))
+ return EOPNOTSUPP;
+ if (((mode | (mode << 3) | (mode << 6))
+ ^ (np->nn_stat.st_mode | (np->nn_stat.st_mode << 3)
+ | (np->nn_stat.st_mode << 6)))
+ & S_IEXEC)
+ {
+ /* We are changing the executable bit, so this is not all fake. 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 (np->nn->file, real_from_fake_mode (mode));
+ }
+ np->nn->faked |= 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)
+{
+ char trans[sizeof _HURD_SYMLINK + np->nn_stat.st_size + 1];
+ memcpy (trans, _HURD_SYMLINK, sizeof _HURD_SYMLINK);
+ memcpy (&trans[sizeof _HURD_SYMLINK], name, np->nn_stat.st_size + 1);
+ return file_set_translator (np->nn->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 (np->nn->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 (np->nn->file, flags);
+}
+
+error_t
+netfs_attempt_utimes (struct iouser *cred, struct node *np,
+ struct timespec *atime, struct timespec *mtime)
+{
+ struct timeval a, m;
+ if (atime)
+ {
+ TIMESPEC_TO_TIMEVAL (&a, atime);
+ }
+ else
+ a.tv_sec = a.tv_usec = -1;
+ if (mtime)
+ {
+ TIMESPEC_TO_TIMEVAL (&m, mtime);
+ }
+ else
+ m.tv_sec = m.tv_usec = -1;
+
+ return file_utimes (np->nn->file,
+ *(time_value_t *) &a, *(time_value_t *) &m);
+}
+
+error_t
+netfs_attempt_set_size (struct iouser *cred, struct node *np, off_t size)
+{
+ return file_set_size (np->nn->file, size);
+}
+
+error_t
+netfs_attempt_statfs (struct iouser *cred, struct node *np, struct statfs *st)
+{
+ return file_statfs (np->nn->file, st);
+}
+
+error_t
+netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
+{
+ return file_sync (np->nn->file, wait, 0);
+}
+
+error_t
+netfs_attempt_syncfs (struct iouser *cred, int wait)
+{
+ return 0;
+}
+
+/* Lookup NAME in DIR (which is locked) for USER; set *NP to the found name
+ upon return. If the name was not found, then return ENOENT. On any
+ error, clear *NP. (*NP, if found, should be locked and a reference to
+ it generated. This call should unlock DIR no matter what.) */
+error_t
+netfs_attempt_lookup (struct iouser *user, struct node *dir,
+ char *name, struct node **np)
+{
+ error_t err;
+ int flags = O_RDWR|O_EXEC;
+ file_t file = file_name_lookup_under (dir->nn->file, name,
+ flags | O_NOLINK, 0);
+ if (file == MACH_PORT_NULL && errno == EACCES)
+ {
+ flags = O_RDWR;
+ file = file_name_lookup_under (dir->nn->file, name, flags | O_NOLINK, 0);
+ }
+ if (file == MACH_PORT_NULL && errno == EACCES)
+ {
+ flags = O_READ|O_EXEC;
+ file = file_name_lookup_under (dir->nn->file, name, flags | O_NOLINK, 0);
+ }
+ if (file == MACH_PORT_NULL && errno == EACCES)
+ {
+ flags = O_READ;
+ file = file_name_lookup_under (dir->nn->file, name, flags | O_NOLINK, 0);
+ }
+ *np = 0;
+ if (file == MACH_PORT_NULL)
+ err = errno;
+ else
+ {
+ mach_port_t idport, fsidport;
+ int fileno;
+ err = io_identity (file, &idport, &fsidport, &fileno);
+ if (err)
+ mach_port_deallocate (mach_task_self (), file);
+ else
+ {
+ mach_port_deallocate (mach_task_self (), fsidport);
+ mutex_lock (&idport_ihash_lock);
+ *np = ihash_find (&idport_ihash, idport);
+ if (*np != 0)
+ {
+ /* We already know about this node. */
+ mach_port_deallocate (mach_task_self (), idport);
+ mach_port_deallocate (mach_task_self (), file);
+ netfs_nref (*np);
+ mutex_unlock (&idport_ihash_lock);
+ }
+ else
+ {
+ mutex_unlock (&idport_ihash_lock);
+ err = new_node (file, idport, flags, np);
+ }
+ }
+ }
+ mutex_unlock (&dir->lock);
+ if (*np)
+ mutex_lock (&(*np)->lock);
+ return err;
+}
+
+error_t
+netfs_attempt_mkdir (struct iouser *user, struct node *dir,
+ char *name, mode_t mode)
+{
+ return dir_mkdir (dir->nn->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 (dir->nn->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 (fromdir->nn->file, fromname,
+ todir->nn->file, toname, excl);
+}
+
+error_t
+netfs_attempt_rmdir (struct iouser *user,
+ struct node *dir, char *name)
+{
+ return dir_rmdir (dir->nn->file, name);
+}
+
+error_t
+netfs_attempt_link (struct iouser *user, struct node *dir,
+ struct node *file, char *name, int excl)
+{
+ return dir_link (dir->nn->file, file->nn->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 (dir->nn->file, O_RDWR|O_EXEC,
+ real_from_fake_mode (mode), &newfile);
+ if (err == 0)
+ err = new_node (newfile, MACH_PORT_NULL, O_RDWR|O_EXEC, np);
+ return err;
+}
+
+error_t
+netfs_attempt_create_file (struct iouser *user, struct node *dir,
+ char *name, mode_t mode, struct node **np)
+{
+ file_t newfile = file_name_lookup_under (dir->nn->file, name,
+ O_CREAT|O_RDWR|O_EXEC,
+ real_from_fake_mode (mode));
+ if (newfile == MACH_PORT_NULL)
+ return errno;
+ return new_node (newfile, MACH_PORT_NULL, O_RDWR|O_EXEC, np);
+}
+
+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 (np->nn->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_check_open_permissions (struct iouser *user, struct node *np,
+ int flags, int newnode)
+{
+ return (flags & (O_READ|O_WRITE|O_EXEC) & ~np->nn->openmodes) ? EACCES : 0;
+}
+
+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 (np->nn->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 (np->nn->file, data, *len, offset, len);
+}
+
+error_t
+netfs_report_access (struct iouser *cred, struct node *np, int *types)
+{
+ *types = np->nn->openmodes
+ & ((np->nn_stat.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ? ~0 : ~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 (dir->nn->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 (np->nn->file,
+ ports, num_ports,
+ ints, num_ints,
+ offsets, num_offsets,
+ data, data_len);
+}
+
+int
+main (int argc, char **argv)
+{
+ error_t err;
+ mach_port_t bootstrap;
+
+ struct argp argp = { NULL, NULL, NULL, "\
+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);
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap);
+ netfs_init ();
+
+ /* 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, O_READ,
+ &netfs_root_node);
+ if (err)
+ error (5, err, "Cannot create root node");
+
+ netfs_server_loop (); /* Never returns. */
+
+ /*NOTREACHED*/
+ return 0;
+}