From owner-p4-projects@FreeBSD.ORG Wed Sep 14 21:35:40 2005 Return-Path: X-Original-To: p4-projects@freebsd.org Delivered-To: p4-projects@freebsd.org Received: by hub.freebsd.org (Postfix, from userid 32767) id 56E3216A421; Wed, 14 Sep 2005 21:35:40 +0000 (GMT) X-Original-To: perforce@freebsd.org Delivered-To: perforce@freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 131E016A41F for ; Wed, 14 Sep 2005 21:35:40 +0000 (GMT) (envelope-from soc-chenk@freebsd.org) Received: from repoman.freebsd.org (repoman.freebsd.org [216.136.204.115]) by mx1.FreeBSD.org (Postfix) with ESMTP id A85FB43D46 for ; Wed, 14 Sep 2005 21:35:39 +0000 (GMT) (envelope-from soc-chenk@freebsd.org) Received: from repoman.freebsd.org (localhost [127.0.0.1]) by repoman.freebsd.org (8.13.1/8.13.1) with ESMTP id j8ELZdqp099552 for ; Wed, 14 Sep 2005 21:35:39 GMT (envelope-from soc-chenk@freebsd.org) Received: (from perforce@localhost) by repoman.freebsd.org (8.13.1/8.13.1/Submit) id j8ELZdXs099549 for perforce@freebsd.org; Wed, 14 Sep 2005 21:35:39 GMT (envelope-from soc-chenk@freebsd.org) Date: Wed, 14 Sep 2005 21:35:39 GMT Message-Id: <200509142135.j8ELZdXs099549@repoman.freebsd.org> X-Authentication-Warning: repoman.freebsd.org: perforce set sender to soc-chenk@freebsd.org using -f From: soc-chenk To: Perforce Change Reviews Cc: Subject: PERFORCE change 83620 for review X-BeenThere: p4-projects@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: p4 projects tree changes List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 14 Sep 2005 21:35:41 -0000 http://perforce.freebsd.org/chv.cgi?CH=83620 Change 83620 by soc-chenk@soc-chenk_leavemealone on 2005/09/14 21:34:51 implemented shared mounting Upgraded lib to 2.4.0-pre2 Submitted by: soc-chenk Affected files ... .. //depot/projects/soc2005/fuse4bsd2/Changelog#5 edit .. //depot/projects/soc2005/fuse4bsd2/IMPLEMENTATION_NOTES#4 edit .. //depot/projects/soc2005/fuse4bsd2/README.html#2 edit .. //depot/projects/soc2005/fuse4bsd2/fuse_module/Makefile#3 edit .. //depot/projects/soc2005/fuse4bsd2/fuse_module/fuse.c#5 edit .. //depot/projects/soc2005/fuse4bsd2/fuse_module/fuse.h#3 edit .. //depot/projects/soc2005/fuse4bsd2/fuselib/fuselib-2.4.0-pre1.diff#2 delete .. //depot/projects/soc2005/fuse4bsd2/fuselib/fuselib-2.4.0-pre2.diff#1 add .. //depot/projects/soc2005/fuse4bsd2/mount_fusefs/mount_fusefs.c#4 edit Differences ... ==== //depot/projects/soc2005/fuse4bsd2/Changelog#5 (text+ko) ==== @@ -1,3 +1,14 @@ +Wed Sep 14 22:06:48 CEST 2005 at node: creo.hu, nick: csaba + * implemented shared mounting + + implemented shared mounting + + added subchapter about "allow other" to IMPLEMENTATION_NOTES + + +Wed Sep 14 22:00:04 CEST 2005 at node: creo.hu, nick: csaba + * Upgraded lib to 2.4.0-pre2 + Mon Sep 12 14:28:34 CEST 2005 at node: creo.hu, nick: csaba * Added Messaging chapter to IMPLEMENTATION_NOTES ==== //depot/projects/soc2005/fuse4bsd2/IMPLEMENTATION_NOTES#4 (text+ko) ==== @@ -65,7 +65,8 @@ 1) Mounting * 1a -- interface * 1b -- security -* 1c -- anything else +* 1c -- dealing with the "allow other" misery +* 1d -- anything else 2) [vi]node operations 3) Syncing 4) Messaging @@ -214,23 +215,73 @@ introduce non-orthogonal access policies in parallell, we tend to be liberal in respect of fuse devices. Yet we also have to make our commitment to the "it's better to show up as one who prefers to play on -the safe side" idea. As a compromise, fuse devices are set to be -readable/writable for members of the operator group (the same group -which is usually used for controlling access to devices like sound cards -ands optical drives). One might argue that it's nonsense that mounting a -Fuse filesystem is harder than mounting a traditional, disk based file -system, as the latter requires only vfs.usermount == 1, while the former -requires membership in operator, too. Well, the conscious admin who -takes the effort to set vfs.usermount to 1, can as well take the effort -to adjust devfs(8) rules so that fuse devices become world usable -("devfs rule add path 'fuse*' mode 666"). Hey, conscious admins, hear my -word, I hereby claim thou shalt not fear to do so. And, concerning -paranoiac admins, these defaults save them from a heart attack upon -seeing world writable entries under /dev (though they might better go -and suffer a heart attack when kldstat reports that my module is -loaded...). +the safe side" idea. + +As a compromise, fuse devices are set to be readable/writable for +members of the operator group (the same group which is usually used for +controlling access to devices like sound cards ands optical drives). One +might argue that it's nonsense that mounting a Fuse filesystem is harder +than mounting a traditional, disk based file system, as the latter +requires only vfs.usermount == 1, while the former requires membership +in operator, too. Well, the conscious admin who takes the effort to set +vfs.usermount to 1, can as well take the effort to adjust devfs(8) rules +so that fuse devices become world usable +("devfs rule add path 'fuse*' mode 666"). Hey, conscious admins, +hear my word, I hereby claim thou shalt not fear to do so. And, +concerning paranoiac admins, these defaults save them from a heart +attack upon seeing world writable entries under /dev (though they might +better go and suffer a heart attack when kldstat reports that my module +is loaded...). + +* 1c -- dealing with the "allow other" misery + +This is related to security, too, but deserves an dedicated subchapter. + +The problem is that the Fuse daemon sees I/O activity of the users of the +filesystem driven by her. As usually no privileges are needed for using +Fuse, we can't guarantee that the daemon belongs to a trusted user account. +Moreover, mounts are transparent for normal filesystem related activities, +so the user of a filesystem might be unaware of the fact that a given path +belongs to a Fuse mount. + +How is this handled in Linux? By default a user is denied from usage +of a Fuse mount unless the daemon is "more privileged" than the user +(that is, the daemon's credentials allow her trace the user's processes). + +There is a mount option, "allow_other", which removes this limitation. +Of course, if anyone could use this option, that would pretty much defeat +the whole purpose of its existence. So by default, only root can use this. +However, the final decision is made by the setuid dispatcher; and his decision +is based upon settings in the respective config file /etc/fuse.conf. + +The problem with this approach is that it's pretty hard for the root to make +sure that no user of the system would mind an "everyone can allow_other" +policy. + +And in FreeBSD, we couldn't even follow this approach, as we don't have a +setuid dispatcher for deciding about the fate of "allow_other" attempts. + +So, what do we about it in FreeBSD? + +The basic setup is the same as in Linux: there is an "allow_other" mount +option, useable only by root. But we don't make exceptions: "allow_other" +can be used only by root, period. + +Yet we have our own ways to not be so draconian. We have an explicit +global unique userspace identifier of daemons in work. + +This allows the introduction of shared daemons. When the first (primary) +mount of a daemon has been completed, other users can do secondary mounts +of the same daemon. A secondary mount works like a symlink -- it forwards +all requests to the primary mount. So it is a very lightweight mechanism. + +And what's most important, doing a secondary mount can be viewed as signing +"agreement of traceability". The secondary mounter can be expected to have +the necessary knowledge about the primary mount to which she or he joins. +Hence while she or he posessess the secondary mount, s/he will be allowed +to use the filesystem -- either via its primary or secondary mounts. -* 1c -- anything else +* 1d -- anything else Not specific to Fuse, but it's a great revelation that one doesn't need to fight with the dreaded /etc/mtab file under FreeBSD. Even under @@ -275,9 +326,7 @@ and credentials are also given with a FUSE_READ, these parameters might better match, but apart from that, it should be the indifferent which filehandle is used. ("Highly synthetic" filesystem daemons might opt to -serve data in a filehandle specific manner, but that's an extremity; if -such a daemon is ever needed to be used under FreeBSD, the close -correspondence can still be enforced by using direct io.) +serve data in a filehandle specific manner, but that's an extremity.) So far, it's been explained why is it possible to alter from the "strict correspondence between file structures and daemon's filehandles" model. @@ -460,10 +509,17 @@ (with some exceptions, when new ones are created), in FreeBSD, tickets are created on demand. +* Unlike Linux, messaging facilites are unconditionally avaliable in FreeBSD. + That is, there are no forced delays depending on the intensity of filesystem + usage. (This is not because of a forced-delays-are-evil policy; it's rather + due to a + better-to-do-nothing-than-to-do-something-without-understanding-it + standpoint.) + * In Linux, the buffers hosting the fields of a request are allocated on the stack (that is, these fields are pointers to structures held in variables of syscall handlers), in FreeBSD, they are allocated - dynamically (they are not freed when the ticket get dropped, they are + dynamically (they are not freed when the ticket gets dropped, they are kept, reused, and reallocated on demand). * In Linux, the unique field of the request is filled with a really ==== //depot/projects/soc2005/fuse4bsd2/README.html#2 (text+ko) ==== @@ -50,18 +50,18 @@
  • - Fuse itself. Get Fuse 2.4.0-pre1 from their [WWW]homepage. + Fuse itself. Get Fuse 2.4.0-pre2 from their [WWW]Sourceforge project page.

  • The FreeBSD module. Source tarballs are provided at [WWW]http://creo.hu/~csaba/projects/fuse4bsd/downloads/ under the name fuse4bsd-<version>.tar.* The current code is available via Darcs, you can fetch it by -

     darcs get http://creo.hu/~csaba/darcs-repos/fuse4bsd
    command, or via [WWW]Perforce. +
     darcs get http://creo.hu/~csaba/darcs-repos/fuse4bsd
    command, or via [WWW]Perforce (you can use this latter link for online source code browsing).

  • - Fuse sshfs. The latest release (1.2) will do, fetch the sshfs-fuse package from [WWW]http://sourceforge.net/projects/fuse. + Fuse sshfs. The latest release (1.2) will do, fetch the sshfs-fuse package also from the [WWW]Fuse SF project page.

  • @@ -104,8 +104,7 @@
  • Assuming that you have my code at ../fuse4bsd, do -

     cp ../fuse4bsd/fuselib/fusermount.c util/ &&
    - cp ../fuse4bsd/fuse_module/fuse_kernel.h include/ &&
    +
     cp ../fuse4bsd/fuse_module/fuse_kernel.h include/ &&
      cp ../fuse4bsd/fuse_module/linux_compat.h include/
    (the first command replaces fusermount.c with a trimmed down version without the mount support code, and the other two dynamically customize the header file defining the kernel-userland interface; as these are needed in the module as well, they are handled separately from the userspace patch).

  • @@ -116,8 +115,8 @@
  • - We will do a non-privileged install, I'll use ~/meta/fuse-2.4.0-pre1 as the prefix. Configure fuse with -

     ./configure --prefix ~/meta/fuse-2.4.0-pre1 --bindir=/tmp --disable-kernel-module MOUNT_FUSE_PATH=/tmp
    + We will do a non-privileged install (I'd say that's easier than set up a jail), I'll use ~/meta/fuse-2.4.0-pre2 as the prefix. Configure fuse with +
     ./configure --prefix ~/meta/fuse-2.4.0-pre2 --bindir=/tmp --disable-kernel-module MOUNT_FUSE_PATH=/tmp

  • @@ -148,7 +147,7 @@
  • Type -

     env PKG_CONFIG_PATH=~/meta/fuse-2.4.0-pre1/lib/pkgconfig/ ./configure && make
    +
     env PKG_CONFIG_PATH=~/meta/fuse-2.4.0-pre2/lib/pkgconfig/ ./configure && make

  • @@ -171,7 +170,7 @@

    Go to sshfs' directory. First prepare the mount:

    mkdir -p ~/fuse &&
    -export LD_LIBRARY_PATH=~/meta/fuse-2.4.0-pre1/lib/
    and also make sure that mount_fusefs (of FreeBSD Fuse) is in your path. Then do: +export LD_LIBRARY_PATH=~/meta/fuse-2.4.0-pre2/lib/and also make sure that mount_fusefs (of FreeBSD Fuse) is in your path. Then do:
    mount_fusefs auto ~/fuse ./sshfs foo@bar.baz: ""

    @@ -191,7 +190,7 @@

  • - You have to use forced unmount for a Fuse filesystem. The reason for this as follows: + You have to use forced unmount for a Fuse filesystem. The reason for this is as follows:

    With traditional filesystems, relations between fs entities are permanently stored in some background storage; but when one uses the filesystem, the fs hierarchy is built up and maintained by the kernel, and this what's transported to the userspace so that we get the usual "filesystem feeling". ==== //depot/projects/soc2005/fuse4bsd2/fuse_module/Makefile#3 (text+ko) ==== @@ -6,11 +6,11 @@ .endif .if defined(DEBUG) -CFLAGS+= -D_DEBUG -g +DEBUG_FLAGS+= -D_DEBUG -g .endif .if defined(DEBUG2G) -CFLAGS+= -D_DEBUG2G -g +DEBUG_FLAGS+= -D_DEBUG2G -g .endif .if defined(DEBUG_MSGING) @@ -27,7 +27,7 @@ KMOD=fuse -CLEANFILES+= fuse_kernel.h fuse_opnames.c fuse_opnames.o fmaster.o vnode_if.h vnode_if_typedef.h vnode_if_newproto.h +CLEANFILES+= fuse_kernel.h fuse_opnames.c fuse_opnames.o fmaster.o vnode_if.h vnode_if_typedef.h vnode_if_newproto.h fuse.ko.debug fuse.ko: fuse_kernel.h vnode_if.h vnode_if_typedef.h vnode_if_newproto.h @@ -47,7 +47,4 @@ vnode_if_newproto.h: awk -f /sys/tools/vnode_if.awk /sys/kern/vnode_if.src -p -nonstripped: fuse.ko - ld -Bshareable -d -warn-common -o fuse.ko fuse.kld - .include ==== //depot/projects/soc2005/fuse4bsd2/fuse_module/fuse.c#5 (text+ko) ==== @@ -47,6 +47,10 @@ #define __static static #endif +#ifndef ROOTLESS_SHARES +#define ROOTLESS_SHARES 1 +#endif + MALLOC_DEFINE(M_FUSEMSG, "fuse messaging", "buffer for fuse messaging related things"); @@ -137,17 +141,30 @@ static __inline int fuse_ohead_audit(struct fuse_out_header *ohead, struct uio *uio); __static int fuse_body_audit(struct fuse_callback_node *caliban, size_t blen); -static __inline void fuse_setup_ihead(struct fuse_in_header *ihead, struct fuse_ticket *tick, uint64_t nid, enum fuse_opcode op, struct thread* td, struct ucred *cred); +static __inline void fuse_setup_ihead(struct fuse_in_header *ihead, + struct fuse_ticket *tick, uint64_t nid, + enum fuse_opcode op, size_t blen, + struct thread* td, struct ucred *cred); __static __inline struct sx *fusedev_get_lock(struct cdev *fdev); __static __inline struct fuse_data *fusedev_get_data(struct cdev *fdev); static int fdisp_get_vopdata(struct fuse_dispatcher *fdip, struct mount *mp); -static void fdisp_prepare_msg(struct fuse_dispatcher *fdip, enum fuse_opcode op, uint64_t nid, struct thread *td, struct ucred *cred); -static __inline int fdisp_prepare_all(struct fuse_dispatcher *fdip, enum fuse_opcode op, struct vnode *vp, struct thread *td, struct ucred *cred); +static void fdisp_prepare_msg(struct fuse_dispatcher *fdip, + enum fuse_opcode op, uint64_t nid, + struct thread *td, struct ucred *cred); +static __inline int fdisp_prepare_all(struct fuse_dispatcher *fdip, + enum fuse_opcode op, struct vnode *vp, + struct thread *td, struct ucred *cred); __static int fdisp_wait_answ(struct fuse_dispatcher *fdip); -static __inline int fdisp_simple_putget(struct fuse_dispatcher *fdip, enum fuse_opcode op, struct vnode *vp, struct thread *td, struct ucred *cred); +static __inline int fdisp_simple_putget(struct fuse_dispatcher *fdip, + enum fuse_opcode op, struct vnode *vp, + struct thread *td, struct ucred *cred); static __inline int fuse_send_init(struct fuse_data *data, struct thread *td); -static __inline int fuse_send_forget(struct mount *mp, struct thread *td, struct ucred *cred, uint64_t nodeid, uint64_t nlookup, struct fuse_dispatcher *fdip); +static __inline int fuse_send_forget(struct mount *mp, struct thread *td, + struct ucred *cred, uint64_t nodeid, + uint64_t nlookup, + + struct fuse_dispatcher *fdip); /*************************************** @@ -462,6 +479,9 @@ data->freeticket_counter = 0; data->daemoncred = crhold(cred); + /* sx_init(&data->shareslock, "lock for fuse shares consistency"); */ + LIST_INIT(&data->fuse_shares_head); + return (data); } @@ -484,6 +504,8 @@ crfree(data->daemoncred); + /* sx_destroy(&data->shareslock); */ + FREE(data,M_FUSEMSG); } @@ -491,7 +513,7 @@ fdata_kick_set(struct fuse_data *data) { mtx_lock(&data->msg_mtx); - data->dataflag |= FU_KICK; + data->dataflag |= FDAT_KICK; cv_signal(&data->msg_cv); mtx_unlock(&data->msg_mtx); } @@ -499,7 +521,7 @@ static __inline int fdata_kick_get(struct fuse_data *data) { - return (data->dataflag & FU_KICK); + return (data->dataflag & FDAT_KICK); } /* <== fuse_data methods */ @@ -702,7 +724,7 @@ err = blen == sizeof(struct fuse_entry_out) ? 0 : EINVAL; break; case FUSE_FORGET: - KASSERT(0, ("a handler has been intalled for FUSE_FORGET")); + panic("a handler has been intalled for FUSE_FORGET"); break; case FUSE_GETATTR: err = blen == sizeof(struct fuse_attr_out) ? 0 : EINVAL; @@ -764,16 +786,16 @@ err = blen == 0 ? 0 : EINVAL; break; case FUSE_SETXATTR: - KASSERT(0, ("FUSE_SETXATTR implementor has forgotten to define a response body format check")); + panic("FUSE_SETXATTR implementor has forgotten to define a response body format check"); break; case FUSE_GETXATTR: - KASSERT(0, ("FUSE_GETXATTR implementor has forgotten to define a response body format check")); + panic("FUSE_GETXATTR implementor has forgotten to define a response body format check"); break; case FUSE_LISTXATTR: - KASSERT(0, ("FUSE_LISTXATTR implementor has forgotten to define a response body format check")); + panic("FUSE_LISTXATTR implementor has forgotten to define a response body format check"); break; case FUSE_REMOVEXATTR: - KASSERT(0, ("FUSE_REMOVEXATTR implementor has forgotten to define a response body format check")); + panic("FUSE_REMOVEXATTR implementor has forgotten to define a response body format check"); break; case FUSE_INIT: err = blen == sizeof(struct fuse_init_in_out) ? 0 : EINVAL; @@ -793,20 +815,28 @@ case FUSE_FSYNCDIR: err = blen == 0 ? 0 : EINVAL; break; +#ifdef FUSE_GETLK case FUSE_GETLK: - KASSERT(0, ("FUSE_GETLK implementor has forgotten to define a response body format check")); + panic("FUSE_GETLK implementor has forgotten to define a response body format check"); break; +#endif +#ifdef FUSE_SETLK case FUSE_SETLK: - KASSERT(0, ("FUSE_SETLK implementor has forgotten to define a response body format check")); + panic("FUSE_SETLK implementor has forgotten to define a response body format check"); break; +#endif +#ifdef FUSE_SETLKW case FUSE_SETLKW: - KASSERT(0, ("FUSE_SETLKW implementor has forgotten to define a response body format check")); + panic("FUSE_SETLKW implementor has forgotten to define a response body format check"); break; +#endif +#ifdef FUSE_ACCESS case FUSE_ACCESS: - KASSERT(0, ("FUSE_ACCESS implementor has forgotten to define a response body format check")); + panic("FUSE_ACCESS implementor has forgotten to define a response body format check"); break; +#endif default: - KASSERT(0, ("fuse opcodes out of sync")); + panic("fuse opcodes out of sync"); } if (err) @@ -824,10 +854,10 @@ static __inline void fuse_setup_ihead(struct fuse_in_header *ihead, struct fuse_ticket *tick, - uint64_t nid, enum fuse_opcode op, struct thread* td, - struct ucred *cred) + uint64_t nid, enum fuse_opcode op, size_t blen, + struct thread* td, struct ucred *cred) { - ihead->len = 0; /* actually not used by lib */ + ihead->len = sizeof(ihead) + blen; /* actually not used by lib */ ihead->unique = tick->unique; ihead->nodeid = nid; ihead->opcode = op; @@ -905,6 +935,18 @@ * ****************************/ +#define FUSEREF \ +do { \ + mtx_lock(&fuse_useco_mtx); \ + if (fuse_useco < 0) { \ + /* Module unload is going on */ \ + mtx_unlock(&fuse_useco_mtx); \ + return (ENOENT); \ + } else \ + fuse_useco++; \ + mtx_unlock(&fuse_useco_mtx); \ +} while (0) + /* * Resources are set up on a per-open basis */ @@ -917,14 +959,7 @@ if (dev->si_usecount > 1) return (EBUSY); - mtx_lock(&fuse_useco_mtx); - if (fuse_useco < 0) { - /* Module unload is going on */ - mtx_unlock(&fuse_useco_mtx); - return (ENOENT); - } else - fuse_useco++; - mtx_unlock(&fuse_useco_mtx); + FUSEREF; data = fdata_alloc(td->td_ucred); @@ -1023,7 +1058,7 @@ * (There is no much use of a partial read here...) */ if (uio->uio_resid < fmsgn->msg.len) { - data->dataflag |= FU_KICK; + data->dataflag |= FDAT_KICK; err = ENODEV; } @@ -1194,7 +1229,7 @@ FUSE_DIMALLOC(&fdip->tick->msgn.msg, fdip->finh, fdip->indata, fdip->iosize); - fuse_setup_ihead(fdip->finh, fdip->tick, nid, op, td, cred); + fuse_setup_ihead(fdip->finh, fdip->tick, nid, op, fdip->iosize, td, cred); } /* prev. two in one */ @@ -1565,7 +1600,8 @@ fuse_mount(struct mount *mp, struct thread *td) { int err = 0; - int len; + int len, sharecount = 0; + int sharing = 0; char *fspec; struct vnode *devvp; struct vfsoptlist *opts; @@ -1577,6 +1613,10 @@ struct vnode *rvp; struct fuse_vnode_data *fvdat; +#define SHAREDMOUNT 0x1 +#define PRIVMOUNT 0x2 +#define IS_SHARED(sh) (!((sh) & PRIVMOUNT)) + if (mp->mnt_flag & MNT_UPDATE) { uprintf("fuse: updating mounts is not supported\n"); return (EOPNOTSUPP); @@ -1606,6 +1646,14 @@ return (err); if (!fspec || fspec[len - 1] != '\0') return (EINVAL); + +/* + vfs_flagopt(opts, "shared", sharing, SHAREDMOUNT); + */ + vfs_flagopt(opts, "private", &sharing, PRIVMOUNT); + + FUSEREF; + /* * Not an update, or updating the name: look up the name * and verify that it refers to a sensible disk device. @@ -1613,13 +1661,14 @@ NDINIT(ndp, LOOKUP, FOLLOW, UIO_SYSSPACE, fspec, td); if ((err = namei(ndp)) != 0) - return (err); + goto out; NDFREE(ndp, NDF_ONLY_PNBUF); devvp = ndp->ni_vp; if (devvp->v_type != VCHR) { vrele(devvp); - return (ENXIO); + err = ENXIO; + goto out; } fdev = devvp->v_rdev; @@ -1632,7 +1681,8 @@ if (! fdev->si_devsw || strcmp("fuse", fdev->si_devsw->d_name) || ! (slock = fusedev_get_lock(fdev))) { - return (ENXIO); + err = ENXIO; + goto out; } MALLOC(fmnt, struct fuse_mnt_data *, sizeof(*fmnt), M_FUSEFS, @@ -1643,17 +1693,13 @@ if (fmnt->mntopts & FUSEFS_DAEMON_CAN_SPY && suser(td)) { uprintf("only root can use \"allow_other\"\n"); free(fmnt, M_FUSEFS); - return (EPERM); + err = EPERM; + goto out; } -#if ! REALTIME_TRACK_UNPRIVPROCDBG - fmnt->mntopts &= ~FUSEFS_UNPRIVPROCDBG; - fmnt->mntopts |= get_unprivileged_proc_debug(td) ? FUSEFS_UNPRIVPROCDBG : 0; -#endif - DEBUG2G("mntopts 0x%x\n", fmnt->mntopts); - sx_slock(slock); + sx_xlock(slock); /* Sanity + permission checks */ @@ -1670,42 +1716,80 @@ uprintf("fuse daemon found, but has been backlisted\n"); } - if ((! err) && suser(td)) { - if (td->td_ucred->cr_uid != data->daemoncred->cr_uid) { - err = EPERM; - uprintf("attempt to mount other user's daemon\n"); + if (!err) { + if (data->mp) { + if (! (data->dataflag & FDAT_SHARED && + IS_SHARED(sharing))) + /* + * device is owned and either us or owner + * insits on a private mount + */ + goto deny; + } else { + if (suser(td) && + td->td_ucred->cr_uid != data->daemoncred->cr_uid) + /* we are not allowed to do the first mount */ + goto deny; } + goto allow; +deny: + err = EPERM; } +allow: if (err) { - sx_sunlock(slock); + sx_xunlock(slock); FREE(fmnt, M_FUSEFS); - return (err); + goto out; } - /* Now handshaking with daemon */ + if (data->mp) { + struct fuse_share *fsh; + MALLOC(fsh, struct fuse_share *, sizeof(*fsh), + M_FUSEFS, M_WAITOK); + fsh->uid = td->td_ucred->cr_uid; + fsh->master = data->mp; + LIST_INSERT_HEAD(&data->fuse_shares_head, fsh, + fuse_shares_link); + fmnt->share = fsh; + LIST_FOREACH(fsh, &data->fuse_shares_head, + fuse_shares_link) + sharecount++; + } else { + /* Now handshaking with daemon */ - if ((err = fuse_send_init(data, td))) { - /* - * Something went wrong with sending init -- - * eg., bad version number - * Thus we tell the daemon it's time to get off - */ - fdata_kick_set(data); - - sx_sunlock(slock); - FREE(fmnt, M_FUSEFS); - return (err); + if ((err = fuse_send_init(data, td))) { + /* + * Something went wrong with sending init -- + * eg., bad version number + * Thus we tell the daemon it's time to get off + */ + fdata_kick_set(data); + + sx_xunlock(slock); + FREE(fmnt, M_FUSEFS); + goto out; + } + if (IS_SHARED(sharing)) + data->dataflag |= FDAT_SHARED; } - sx_sunlock(slock); /* We need this here as this slot is used by getnewvnode() */ mp->mnt_stat.f_iosize = PAGE_SIZE; + mp->mnt_data = fmnt; +#if ROOTLESS_SHARES + if (fmnt->share) + goto rootdone; +#endif + /* code stolen from portalfs */ - MALLOC(fvdat, struct fuse_vnode_data *, sizeof(*fvdat), M_FUSEFS, - M_WAITOK | M_ZERO); + if (data->mp) + fvdat = ((struct fuse_mnt_data *)data->mp->mnt_data)->rvp->v_data; + else + MALLOC(fvdat, struct fuse_vnode_data *, sizeof(*fvdat), + M_FUSEFS, M_WAITOK | M_ZERO); #if __FreeBSD_version >= 600000 err = getnewvnode("fuse", mp, &fuse_vnops, &rvp); #else @@ -1713,29 +1797,77 @@ #endif if (err) { - FREE(fmnt, M_FUSEFS); - FREE(fvdat, M_FUSEFS); - return (err); + if (data->mp) { + fdata_kick_set(data); + FREE(fmnt, M_FUSEFS); + FREE(fvdat, M_FUSEFS); + } + sx_xunlock(slock); + goto out; } /* * FUSE_ROOT_INODE as an inode number will be resolved directly. - * without resorting to the vfs hashing mechanism, thus it also can - * be inserted directly to the v_hash slot. + * without resorting to the vfs hashing mechanism, thus it also + * can be inserted directly to the v_hash slot. */ rvp->v_hash = FUSE_ROOT_INODE; + fmnt->rvp = rvp; fuse_vnode_init(rvp, fvdat, VDIR); rvp->v_vflag |= VV_ROOT; - fmnt->rvp = rvp; +#if ROOTLESS_SHARES +rootdone: +#endif + + if (! data->mp) { + data->mp = mp; +#if ! REALTIME_TRACK_UNPRIVPROCDBG + fmnt->mntopts &= ~FUSEFS_UNPRIVPROCDBG; + fmnt->mntopts |= get_unprivileged_proc_debug(td) ? FUSEFS_UNPRIVPROCDBG : 0; +#endif + } + + sx_xunlock(slock); + vfs_getnewfsid(mp); - mp->mnt_data = fmnt; mp->mnt_flag |= MNT_LOCAL; copystr(fspec, mp->mnt_stat.f_mntfromname, MNAMELEN - 1, &len); + if (fmnt->share && len >= 1) { + /* + * I've considered using s1, s2,... for shares, instead + * #1, #2,... as s* is more conventional... + * Finally I chose #* as I feel it's more expressive + * and s* can be misleading (slices are strung up + * serially, shares exist in parallell) + */ + mp->mnt_stat.f_mntfromname[len-1] = '#'; + if (len < MNAMELEN - 1) { + /* Duhh, ain't there a better way of doing this? */ + int log = 0, lim = 1; + while (sharecount >= lim) { + lim *= 10; + log++; + } + if (log >= MNAMELEN - len) { + mp->mnt_stat.f_mntfromname[len] = '?'; + len++; + } else { + sprintf(mp->mnt_stat.f_mntfromname + len, "%d", + sharecount); + len += log; + } + } + } bzero(mp->mnt_stat.f_mntfromname + len, MNAMELEN - len); + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); - return (0); +out: + if (err) + fuse_useco--; + + return (err); } /* @@ -1744,34 +1876,57 @@ static int fuse_unmount(struct mount *mp, int mntflags, struct thread *td) { - int err, flags = 0; + int flags = 0, err = 0; struct sx *slock; struct fuse_data *data; struct fuse_mnt_data *fmnt; + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); /* Flag handling */ if (mntflags & MNT_FORCE) flags |= FORCECLOSE; - /* Flush files -> vflush */ - /* There is 1 extra root vnode reference (mp->mnt_data). */ - if ((err = vflush(mp, 1, flags, td))) - return (err); - fmnt = mp->mnt_data; slock = fusedev_get_lock(fmnt->fdev); - sx_slock(slock); - if ((data = fusedev_get_data(fmnt->fdev))) + sx_xlock(slock); + if (! (data = fusedev_get_data(fmnt->fdev))) + goto out; + if (fmnt->share) + LIST_REMOVE(fmnt->share, fuse_shares_link); + else { + if (! LIST_EMPTY(&data->fuse_shares_head)) { + sx_xunlock(slock); + return (EBUSY); + } fdata_kick_set(data); - sx_sunlock(slock); + } + +out: + sx_xunlock(slock); + + + if ( +#if ROOTLESS_SHARES + ! fmnt->share +#else + 1 +#endif + ) { + /* Flush files -> vflush */ + /* There is 1 extra root vnode reference (mp->mnt_data). */ + if ((err = vflush(mp, 1, flags, td))) + return (err); + } mp->mnt_data = NULL; + FREE(fmnt->share, M_FUSEFS); FREE(fmnt, M_FUSEFS); - /* Other guys do this, I don't know what is it good for... */ + /* Other guys do this, I don't know what it is good for... */ mp->mnt_flag &= ~MNT_LOCAL; + fuse_useco--; return (0); } @@ -1787,9 +1942,17 @@ /* * Return locked reference to root. */ + struct fuse_mnt_data *fmnt = mp->mnt_data; struct vnode *vp; - vp = ((struct fuse_mnt_data *)mp->mnt_data)->rvp; + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); + +#if ROOTLESS_SHARES + if (fmnt->share) + return fuse_root(fmnt->share->master, flags, vpp, td); +#endif + + vp = fmnt->rvp; vref(vp); VOP_UNLOCK(vp, 0, td); vn_lock(vp, flags | LK_RETRY, td); @@ -1802,10 +1965,18 @@ { struct fuse_dispatcher fdi; struct fuse_statfs_out *fsfo; + struct fuse_mnt_data *fmnt; int err = 0; - if ((err = fdisp_simple_putget(&fdi, FUSE_STATFS, - ((struct fuse_mnt_data *)mp->mnt_data)->rvp, td, NULL))) + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); + fmnt = mp->mnt_data; + +#if ROOTLESS_SHARES + if (fmnt->share) + fmnt = (struct fuse_mnt_data *)fmnt->share->master->mnt_data; +#endif + + if ((err = fdisp_simple_putget(&fdi, FUSE_STATFS, fmnt->rvp, td, NULL))) return (err); fsfo = fdi.answ; @@ -1834,10 +2005,12 @@ enum vtype vtyp, struct vnode **vpp) { int err = 0; + struct fuse_mnt_data *fmnt; struct fuse_vnode_data *fvdat; struct vnode *vp2; int myflags = LK_EXCLUSIVE; + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); DEBUG("been asked for vno #%llu\n", nodeid); if (nodeid == FUSE_ROOT_INODE) { @@ -1845,6 +2018,13 @@ return (err); } + fmnt = mp->mnt_data; +#if ! ROOTLESS_SHARES + if (fmnt->share) + mp = fmnt->share->master; +#endif + DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); + /* XXX nodeid: cast from 64 bytes to 32 */ if ((err = vfs_hash_get(mp, nodeid, /*flags*/ myflags, td, vpp, NULL, NULL))) return (err); @@ -1921,8 +2101,17 @@ vp->v_data = fvdat; SETPARENT(vp, (VTOI(vp) == FUSE_ROOT_INODE) ? vp : NULL); vp->v_type = vtyp; - sx_init(&fvdat->fh_lock, "lock for fuse filehandles"); - LIST_INIT(&fvdat->fh_head); + if ( +#if ROOTLESS_SHARES + 1 +#else + ! ((struct fuse_mnt_data *)vp->v_mount->mnt_data)->share +#endif + ) { + + sx_init(&fvdat->fh_lock, "lock for fuse filehandles"); + LIST_INIT(&fvdat->fh_head); + } vp->v_bufobj.bo_ops = &fuse_bufops; vp->v_bufobj.bo_private = vp; @@ -1967,7 +2156,11 @@ * Taking down fuse_vnode_data structures is just hooked in here... * no separate destructor. */ - if (fvdat) { + if ( +#if ! ROOTLESS_SHARES + ! ((struct fuse_mnt_data *)vp->v_mount->mnt_data)->share && +#endif + fvdat) { sx_destroy(&fvdat->fh_lock); FREE(fvdat, M_FUSEFS); } @@ -2143,6 +2336,15 @@ struct fuse_dispatcher fdi; int err = 0; +#if ! ROOTLESS_SHARES + if (VTOI(vp) == FUSE_ROOT_INODE) { + if (fmnt->share) { + fmnt = fmnt->share->master->mnt_data; + vp = fmnt->rvp; + } + } +#endif + if ((err = fdisp_simple_putget(&fdi, FUSE_GETATTR, vp, td, cred))) return (err); @@ -2160,26 +2362,34 @@ * if she is *more* privileged than the daemon. This is to * protect the power user from the daemon spying on her I/O * operations. - * Yes, it is a crude and blunt protection... - * XXX there should be a sysctl interface to this. - * Ideally not just a "daemon can spy bit", but some clever - * data which determines in a compact but flexible way whose - * daemon can spy on whom. - * (Though controlling it via a mount option seems to be good - * enough...) + * + * If a user wouldn't mind this, she can relax this check + * by doing a shared mount. */ - if (cr_candebug( + int denied; + + if ((denied = cr_candebug( #if REALTIME_TRACK_UNPRIVPROCDBG get_unprivileged_proc_debug(td), #else ((struct fuse_mnt_data *)vp->v_mount->mnt_data)->mntopts & FUSEFS_UNPRIVPROCDBG, #endif - fusedev_get_data(fdi.fdev)->daemoncred, cred)) + fusedev_get_data(fdi.fdev)->daemoncred, cred))) { - sx_sunlock(fdi.slock); - uprintf("Your access is blocked in order to prevent the fuse daemon spying on you\n (consider mounting with \"allow_other\")\n"); - return (EPERM); + struct fuse_share *fsh; + + LIST_FOREACH(fsh, &fdi.data->fuse_shares_head, fuse_shares_link) { + if (! (denied = (fsh->uid != cred->cr_uid))) + break; + } + + if (denied) { + sx_sunlock(fdi.slock); + uprintf("Your access is blocked in order to prevent the Fuse daemon spying on you.\n" + "To get further, you can do a shared mount, cf. mount_fusefs(8).\n"); + return (EPERM); + } } } sx_sunlock(fdi.slock); @@ -2751,6 +2961,23 @@ struct file *fp = NULL; >>> TRUNCATED FOR MAIL (1000 lines) <<<