9mount-1.3/9bind.c0000644000000000000000000000143611041401173014117 0ustar00usergroup00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include int main(int argc, char **argv) { char *old = NULL, *new = NULL; struct stat stbuf; while (*++argv) { if (!old) { old = *argv; } else if (!new) { new = *argv; } else { errx(1, "%s: too many arguments", *argv); } } if (!old || !new) { errx(1, "usage: 9bind old new"); } /* Make sure mount exists, is writable, and not sticky */ if (stat(new, &stbuf) || access(new, W_OK)) { err(1, "%s", new); } if (stbuf.st_mode & S_ISVTX) { errx(1, "%s: refusing to bind over sticky directory", new); } if (mount(old, new, NULL, MS_BIND, NULL)) { err(1, "mount"); } return 0; } 9mount-1.3/9mount.10000644000000000000000000000560011041401173014260 0ustar00usergroup00000000000000.TH "9mount" "1" "04 September 2007" "9mount" "User commands" .SH NAME 9mount, 9bind, 9umount \- mount/unmount 9p filesystems .SH SYNOPSIS .B 9mount [ insuvx ] [ -a SPEC ] [ -c CACHE ] [ -d DEBUG ] [ -m MSIZE ] DIAL MOUNTPT .PP .B 9bind OLD NEW .PP .B 9umount MOUNTPT .SH DESCRIPTION .B 9mount mounts a 9p filesystem served at DIAL on MOUNTPT. MOUNTPT must be writable by you and not sticky. DIAL is a dial string assuming one of the forms: .PP unix!SOCKET .br tcp!HOST[!PORT] .br virtio!CHANNEL .br - .PP where SOCKET is the name of a file representing a socket, HOST is a hostname, PORT is a port number or service name, and CHANNEL is a virtio channel name (currently ignored). - indicates that 9p messages should be read/written on stdin/stdout. .B 9mount has several options: .TP -i mount the file system with your uid/gid .TP -n dry-run, print mount command to stderr but don't actually mount anything .TP -s single attach mode - all users accessing the mount point see the same filesystem (by default they'll each see a unique attach) .TP -u use the 9P2000.u extensions .TP -v use device mapping .TP -x exclusive access - other users cannot access the mount point .TP -a SPEC SPEC determines which file tree to mount when attaching to file servers that export multiple trees .TP -c CACHE turns on caching using CACHE mode. Currently only .I loose cache mode is available, which is suitable for exclusive read-only mounts. .TP -d DEBUG comma seperated list of channels for which to enable debug output. Possible channels include: err, devel, 9p, vfs, conv, mux, trans, alloc, fcall. .TP -m MSIZE specifies the maximum length of a single 9p message in bytes. .PP .B 9bind performs a bind mount, making the tree visible at directory OLD also visible at mount point NEW. .PP .B 9umount unmounts a 9p filesystem previously mounted by you. .SH ENVIRONMENT .TP $USER the uname to provide to the server. .SH EXAMPLES .TP 9mount -i 'unix!/tmp/ns.'$USER'.:0/factotum' $HOME/n/factotum mount p9p's factotum interface .TP 9mount 'tcp!sources.cs.bell-labs.com' $HOME/n/sources import plan 9's "sources" .TP 9mount -u -a/home/sqweek/mail 'tcp!wren!5640' $HOME/mail import my maildir from my server(wren), being served by ufs .TP 9mount -i 'tcp!wren' $HOME/n/wren; 9bind $HOME/n/wren/home/sqweek/mail $HOME/mail again importing my maildir, this time serving via u9fs .SH BUGS .B 9mount truncates user names and SPECs to 249 characters. .B 9umount doesn't know this, so you won't be able to unmount anything outside your home directory. But you probably never bother logging out if your user name is that long. .PP .B 9mount doesn't update /etc/mtab. .PP .B 9bind only does a "shallow", non-recursive bind - any mounted filesystems under the OLD tree will not appear mounted in the NEW tree. .PP If you .B 9bind a non-9p filesystem outside your home directory, .B 9umount won't let you unmount it. .SH AUTHOR sqweek@gmail.com .SH SEE ALSO .BR mount (1) 9mount-1.3/9mount.c0000644000000000000000000001475411041401173014354 0ustar00usergroup00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define nelem(x) (sizeof(x)/sizeof(*(x))) struct {char *mnemonic; int mask;} debug_flags[] = { {"err", 0x001}, {"devel", 0x002}, {"9p", 0x004}, {"vfs", 0x008}, {"conv", 0x010}, {"mux", 0x020}, {"trans", 0x040}, {"alloc", 0x080}, {"fcall", 0x100} }; char* append(char **dest, char *src, int *destlen) { while (strlen(*dest) + 1 + strlen(src) > *destlen) *destlen *= 2; if (!(*dest=realloc(*dest, *destlen))) errx(1, "out of memory"); if (**dest) strcat(*dest, ","); strcat(*dest, src); return *dest; } char* getarg(char opt, char *cp, char*** argv) { if (*(cp+1)) { return cp+1; } else if (*(*argv+1)) { return *++(*argv); } else { errx(1, "-%c: expected argument", opt); } return NULL; } void parsedial(char *dial, char **network, char **netaddr, int *port) { char *cp; if (!(*network=strtok(dial, "!"))) { errx(1, "empty dial string"); } if (strcmp(*network, "unix") != 0 && strcmp(*network, "tcp") != 0 && strcmp(*network, "virtio") != 0) { errx(1, "%s: unknown network (expecting unix, tcp or virtio)", *network); } if (!(*netaddr=strtok(NULL, "!"))) { errx(1, "missing dial netaddress"); } if (strcmp(*network, "tcp") == 0) { char *service; if ((service=strtok(NULL, "!"))) { if (strspn(service, "0123456789") == strlen(service)) { *port = atoi(service); } else { struct servent *sv; if ((sv=getservbyname(service, *network))) { /* sv->s_port is a 16-bit big endian masquerading as an int */ *port = ntohs((uint16_t)sv->s_port); endservent(); } else { errx(1, "%s: unknown service", service); } } } } if ((cp=strtok(NULL, "!"))) { errx(1, "%s: junk trailing dial string", cp); } } int main(int argc, char **argv) { char buf[256], *opts, *dial = NULL, *mountpt = NULL; int optlen = 64, port = 0, i; struct stat stbuf; struct passwd *pw; int axess = 0, dotu = 0, uidgid = 0, dev = 0, debug = 0, dryrun = 0; char *debugstr = NULL, *msize = NULL, *cache = NULL, *aname = NULL; char *cp, *proto, *addr; if (!(opts=calloc(optlen, 1))) { err(1, "calloc"); } while (*++argv) { if (**argv == '-' && (*argv)[1] != '\0') { for (cp=*argv+1; *cp; ++cp) { switch (*cp) { case 'i': uidgid = 1; break; case 'n': dryrun = 1; break; case 's': axess = -1; break; case 'u': dotu = 1; break; case 'v': dev = 1; break; case 'x': axess = getuid(); break; case 'a': aname = getarg('a', cp, &argv); *cp-- = '\0'; /* breaks out of for loop */ break; case 'c': cache = getarg('c', cp, &argv); *cp-- = '\0'; break; case 'd': debugstr = getarg('d', cp, &argv); *cp-- = '\0'; break; case 'm': msize = getarg('m', cp, &argv); *cp-- = '\0'; break; } } } else if (!dial) { dial = *argv; } else if (!mountpt) { mountpt = *argv; } else { errx(1, "%s: too many arguments", *argv); } } if (!dial || !mountpt) { errx(1, "usage: 9mount [ -insuvx ] [ -a spec ] [ -c cache ] [ -d debug ] [ -m msize ] dial mountpt"); } if(!(pw=getpwuid(getuid()))) { err(1, "who are you?? getpwuid failed"); } /* Make sure mount exists, is writable, and not sticky */ if (stat(mountpt, &stbuf) || access(mountpt, W_OK)) { err(1, "%s", mountpt); } if (stbuf.st_mode & S_ISVTX) { errx(1, "%s: refusing to mount over sticky directory", mountpt); } if (strcmp(dial, "-") == 0) { proto = "fd"; addr = "nodev"; append(&opts, "rfdno=0,wrfdno=1", &optlen); } else { parsedial(dial, &proto, &addr, &port); } /* set up mount options */ append(&opts, proto, &optlen); /* < 2.6.24 */ snprintf(buf, sizeof(buf), "trans=%s", proto); append(&opts, buf, &optlen); /* >= 2.6.24 */ if (aname) { if (strchr(aname, ',')) { errx(1, "%s: spec can't contain commas", aname); } snprintf(buf, sizeof(buf), "aname=%s", aname); append(&opts, buf, &optlen); } if (cache) { if (strcmp(cache, "loose") != 0) { errx(1, "%s: unknown cache mode (expecting loose)", cache); } snprintf(buf, sizeof(buf), "cache=%s", cache); append(&opts, buf, &optlen); } if (debugstr) { for (cp=strtok(debugstr, ","); cp; cp=strtok(NULL, ",")) { for (i=0; i= nelem(debug_flags)) { errx(1, "%s: unrecognised debug channel", cp); } } snprintf(buf, sizeof(buf), "debug=0x%04x", debug); append(&opts, buf, &optlen); } if (msize) { if (strspn(msize, "0123456789") < strlen(msize)) { errx(1, "%s: msize must be an integer", msize); } snprintf(buf, sizeof(buf), "msize=%s", msize); append(&opts, buf, &optlen); } snprintf(buf, sizeof(buf), "name=%s", pw->pw_name); append(&opts, buf, &optlen); if (getenv("USER")) { snprintf(buf, sizeof(buf), "uname=%s", getenv("USER")); } else { snprintf(buf, sizeof(buf), "uname=%s", pw->pw_name); } if (strchr(buf, ',')) { errx(1, "%s: username can't contain commas", buf+6); } append(&opts, buf, &optlen); if (axess == -1) { append(&opts, "access=any", &optlen); } else if (axess) { snprintf(buf, sizeof(buf), "access=%d", axess); append(&opts, buf, &optlen); } if (!dotu) { append(&opts, "noextend", &optlen); } if (!dev) { append(&opts, "nodev", &optlen); } if (uidgid) { snprintf(buf, sizeof(buf), "uid=%d,gid=%d", getuid(), getgid()); append(&opts, buf, &optlen); /* < 2.6.24 */ snprintf(buf, sizeof(buf), "dfltuid=%d,dfltgid=%d", getuid(), getgid()); append(&opts, buf, &optlen); /* >= 2.6.24 */ } if (port) { snprintf(buf, sizeof(buf), "port=%d", port); append(&opts, buf, &optlen); } if (strcmp(proto, "tcp") == 0) { struct addrinfo *ai; int r; if ((r=getaddrinfo(addr, NULL, NULL, &ai))) { errx(1, "getaddrinfo: %s", gai_strerror(r)); } if ((r=getnameinfo(ai->ai_addr, ai->ai_addrlen, buf, sizeof(buf), NULL, 0, NI_NUMERICHOST))) { errx(1, "getnameinfo: %s", gai_strerror(r)); } } else { /* unix socket, virtio device or fd transport */ snprintf(buf, sizeof(buf), "%s", addr); } if(dryrun) { fprintf(stderr, "mount -t 9p -o %s %s %s\n", opts, buf, mountpt); } else if (mount(buf, mountpt, "9p", 0, (void*)opts)) { err(1, "mount"); } return 0; } 9mount-1.3/9umount.c0000644000000000000000000000541111041401173014527 0ustar00usergroup00000000000000/* © 2008 sqweek * See COPYING for details. */ #include #include #include #include #include #include #include #include #include char* canonpath(char *rel) { char *abs, *cp, *prev; if (rel[0] == '/') { if (!(abs=malloc(strlen(rel) + 2))) { return NULL; } abs[0] = '\0'; strcat(abs, rel); strcat(abs, "/"); } else { char *cwd = NULL; if (!(cwd=getcwd(NULL, 0))) { return NULL; } if (!(abs=malloc(strlen(cwd) + 1 + strlen(rel) + 2))) { free(cwd); return NULL; } abs[0] = '\0'; strcat(abs, cwd); strcat(abs, "/"); strcat(abs, rel); strcat(abs, "/"); free(cwd); } /* abs is now an absolute path which begins and ends with a slash */ prev = abs; while ((cp=strchr(prev+1, '/'))) { if (cp == prev+1) { /* remove consecutive slashes */ memmove(prev, cp, strlen(cp)+1); } else if (strncmp(prev, "/./", cp-prev+1) == 0) { memmove(prev, cp, strlen(cp)+1); } else if (strncmp(prev, "/../", cp-prev+1) == 0) { char *parent; if (prev == abs) { parent = abs; /* just eat .. in root dir */ } else { for (parent=prev-1; *parent != '/'; --parent); } memmove(parent, cp, strlen(cp)+1); prev = parent; } else { prev = cp; } } abs[strlen(abs)-1] = '\0'; /* remove trailing slash */ return abs; } int indir(struct mntent *mnt, char *dir) { return (strncmp(mnt->mnt_dir, dir, strlen(dir)) == 0); } int mountedby(struct mntent *mnt, char *username) { char *s, *cp; if (!(s=strdup(mnt->mnt_opts))) { errx(1, "out of memory"); } for (cp=strtok(s, ","); cp; cp=strtok(NULL, ",")) { if (strncmp(cp, "name=", 5) == 0) { int eq = (strcmp(cp+5, username) == 0); free(s); return eq; } } free(s); return 0; } int main(int argc, char **argv) { int ret = 0; char *path; FILE *fp; struct mntent *mnt; struct passwd *pw; if (!(pw=getpwuid(getuid()))) { err(1, "who are you?? getpwuid failed"); } while (*++argv) { if (!(path=canonpath(*argv))) { warn("%s: canonpath", *argv); continue; } if (!(fp=fopen("/proc/mounts", "r"))) { err(1, "couldn't open /proc/mounts"); } while ((mnt=getmntent(fp))) { if (strcmp(path, mnt->mnt_dir) == 0) { int inhomedir; inhomedir = indir(mnt, pw->pw_dir); if (!inhomedir && strcmp(mnt->mnt_type, "9p") != 0) { warnx("%s: refusing to unmount non-9p fs", path); ret = 1; } else if (!inhomedir && !mountedby(mnt, pw->pw_name)) { warnx("%s: not mounted by you", path); ret = 1; } else if (umount(mnt->mnt_dir)) { warn("umount %s", mnt->mnt_dir); ret = 1; } goto done; } } warnx("%s not found in /proc/mounts", path); ret = 1; done: free(path); fclose(fp); } return ret; } 9mount-1.3/COPYING0000644000000000000000000000135011041401173013774 0ustar00usergroup00000000000000Copyright (c) 2008, sqweek Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 9mount-1.3/ChangeLog0000644000000000000000000000126211041401173014515 0ustar00usergroup000000000000002008-07-22 sqweek * 1.3 release * 9mount: support for custom uname, cache, msize, virtio, fd transport, access modes, -d more useful * 9umount: allow unmounting of even non-9p filesystems in your home dir * introduced some test cases * added ISC license 2008-06-20 sqweek * 1.2 release * 9mount: support for linux kernels >= 2.6.24 * 9umount: allow unmounting of anything in your home dir 2007-10-16 sqweek * 1.1 release * 9bind: new SUID utility for bind mounting * Makefile: actually install the man page * 9mount: check that the initial calloc actually succeeds 2007-09-08 sqweek * 1.0 release 9mount-1.3/Makefile0000644000000000000000000000104311041401173014400 0ustar00usergroup00000000000000prefix=/usr/local bindir=$(prefix)/bin mandir=$(prefix)/share/man CFLAGS=-Wall all: 9mount 9umount 9bind 9mount: 9mount.c 9umount: 9umount.c 9bind: 9bind.c %: %.c $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ clean: rm -f 9mount 9umount 9bind install: all mkdir -p $(bindir) cp -f 9mount $(bindir) cp -f 9umount $(bindir) cp -f 9bind $(bindir) chown root:users $(bindir)/9mount $(bindir)/9umount $(bindir)/9bind chmod 4755 $(bindir)/9mount $(bindir)/9umount $(bindir)/9bind mkdir -p $(mandir)/man1 cp -f 9mount.1 $(mandir)/man1 9mount-1.3/test.sh0000644000000000000000000000350411041401173014257 0ustar00usergroup00000000000000#!/bin/sh expect() { addr=$1 opts=$(echo $2 |tr , '\n' |sort |tr '\n' , |sed 's/,$//') mtpt=$(eval 'echo $'$#) expected="mount -t 9p -o $opts $addr $mtpt" shift; shift actual=$(9mount -n "$@" 2>&1) aopts=$(echo $actual |sed 's/.*-o \([^ ]*\) .*/\1/' |tr , '\n' |sort |tr '\n' , |sed 's/,$//') actual=$(echo $actual |sed 's/-o [^ ]*/-o '"$aopts"'/') if [ "$expected" != "$actual" ]; then echo ' '9mount "$@" echo $expected' #expected' echo $actual' #actual' exit 1 fi } mtpt=/tmp/9mount mkdir -p $mtpt trap 'rmdir $mtpt' EXIT dfltopts="name=$USER,uname=$USER,noextend,nodev" expect 127.0.0.1 $dfltopts,tcp,trans=tcp,port=888 tcp!localhost!888 $mtpt expect /tmp/ns.$USER.:0/foo $dfltopts,unix,trans=unix unix!/tmp/ns.$USER.:0/foo $mtpt expect /dev/bar $dfltopts,virtio,trans=virtio virtio!/dev/bar $mtpt expect nodev $dfltopts,fd,trans=fd,rfdno=0,wrfdno=1 - $mtpt dfltopts="tcp,trans=tcp,$dfltopts" testflag() { opts=$1 shift expect 127.0.0.1 $dfltopts,$opts "$@" tcp!localhost $mtpt } testflag "uid=$(id -u),gid=$(id -g),dfltuid=$(id -u),dfltgid=$(id -g)" -i testflag "access=any" -s expect 127.0.0.1 $(echo $dfltopts |sed 's/,noextend//') -u tcp!localhost $mtpt expect 127.0.0.1 $(echo $dfltopts |sed 's/,nodev//') -v tcp!localhost $mtpt testflag "access=$(id -u)" -x testflag "aname=abcdef" -a abcdef testflag "cache=loose" -c loose testflag "debug=0x0130" -d fcall,conv,mux testflag "msize=16384" -m 16384 shouldfail() { output=$(9mount -n "$@" 2>&1) && { echo ' '9mount "$@" echo $output' #should have failed!' } } shouldfail -a tcp!localhost $mtpt shouldfail -a main/active,bwahaha tcp!localhost $mtpt shouldfail -d lol tcp!localhost $mtpt shouldfail -m z tcp!localhost $mtpt shouldfail udp!localhost $mtpt shouldfail unix!/tmp/9mount!qux $mtpt shouldfail virtio!/dev/chan!bar $mtpt shouldfail tcp!localhost!564!foo $mtpt