summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--9bind.c45
-rw-r--r--9mount.1115
-rw-r--r--9mount.c273
-rw-r--r--9umount.c140
-rw-r--r--COPYING13
-rw-r--r--ChangeLog21
-rw-r--r--Makefile28
-rw-r--r--test.sh61
8 files changed, 696 insertions, 0 deletions
diff --git a/9bind.c b/9bind.c
new file mode 100644
index 0000000..620d092
--- /dev/null
+++ b/9bind.c
@@ -0,0 +1,45 @@
+/* © 2008 sqweek <sqweek@gmail.com>
+ * See COPYING for details.
+ */
+#include <err.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/mount.h>
+
+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;
+}
diff --git a/9mount.1 b/9mount.1
new file mode 100644
index 0000000..a4884b0
--- /dev/null
+++ b/9mount.1
@@ -0,0 +1,115 @@
+.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)
diff --git a/9mount.c b/9mount.c
new file mode 100644
index 0000000..65bccac
--- /dev/null
+++ b/9mount.c
@@ -0,0 +1,273 @@
+/* © 2008 sqweek <sqweek@gmail.com>
+ * See COPYING for details.
+ */
+#include <err.h>
+#include <mntent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <arpa/inet.h>
+#include <pwd.h>
+#include <netdb.h>
+
+#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); ++i) {
+ if (strcmp(cp, debug_flags[i].mnemonic) == 0) {
+ debug |= debug_flags[i].mask;
+ break;
+ }
+ }
+ if (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;
+}
diff --git a/9umount.c b/9umount.c
new file mode 100644
index 0000000..1adc9fe
--- /dev/null
+++ b/9umount.c
@@ -0,0 +1,140 @@
+/* © 2008 sqweek <sqweek@gmail.com>
+ * See COPYING for details.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <mntent.h>
+#include <pwd.h>
+
+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;
+}
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..fc19dbb
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2008, sqweek <sqweek@gmail.com>
+
+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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..7701503
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,21 @@
+2008-07-22 sqweek <sqweek@gmail.com>
+ * 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 <sqweek@gmail.com>
+ * 1.2 release
+ * 9mount: support for linux kernels >= 2.6.24
+ * 9umount: allow unmounting of anything in your home dir
+
+2007-10-16 sqweek <sqweek@gmail.com>
+ * 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 <sqweek@gmail.com>
+ * 1.0 release
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e7a9948
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+prefix=/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
diff --git a/test.sh b/test.sh
new file mode 100644
index 0000000..7b58498
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,61 @@
+#!/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