9ns: Support rename
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 14 Mar 2018 17:27:37 +0000 (13:27 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Mon, 30 Apr 2018 18:33:44 +0000 (14:33 -0400)
Devices without a rename operation just get an EXDEV.  Usually userspace
can handle that (e.g. busybox's mv).

I added rename to the struct dev ops since it should be a 9p operation,
allowing renames across directories.  9p wstat does allow an
intra-directory rename, but that's not really what you want.  By giving
the device the old chan, the parent chan, and the name, it can
atomically perform the rename.  Atomicity is a critical part of rename.

Previously, we tried mucking around with rename and wstat, and it was
missing some of the namec-magic associated with Acreate (findmounts and
other stuff).  The device wasn't given the parent chan - it had to find
that on its own, and I was worried that there were some TOCTTOU races if
we just gave it the path.

Additionally, the device may need to do various "version" checks, which
can be done in a dev.rename op, but are harder to do generically.  See
tmpfs for an example.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/drivers/dev/kfs.c
kern/drivers/dev/tmpfs.c
kern/include/ns.h
kern/src/ns/chan.c
kern/src/ns/sysfile.c
kern/src/syscall.c

index 2419fba..72870a3 100644 (file)
@@ -292,6 +292,7 @@ struct dev kfs_devtab __devtab = {
        .write = tree_chan_write,
        .bwrite = devbwrite,
        .remove = tree_chan_remove,
+       .rename = tree_chan_rename,
        .wstat = tree_chan_wstat,
        .power = devpower,
        .chaninfo = devchaninfo,
index ce0bb90..f4e6389 100644 (file)
@@ -233,6 +233,19 @@ static void tmpfs_remove(struct chan *c)
        poperror();
 }
 
+void tmpfs_rename(struct chan *c, struct chan *new_p_c, const char *name,
+                  int flags)
+{
+       struct tmpfs *tmpfs_old = chan_to_tmpfs(c);
+       struct tmpfs *tmpfs_new = chan_to_tmpfs(new_p_c);
+
+       /* namec checked that both were from the same device.  It is our
+        * responsibility to make sure they are the same version. */
+       if (tmpfs_old != tmpfs_new)
+               error(EXDEV, "can't rename across tmpfs instances");
+       tree_chan_rename(c, new_p_c, name, flags);
+}
+
 struct dev tmpfs_devtab __devtab = {
        .name = "tmpfs",
        .reset = devreset,
@@ -249,6 +262,7 @@ struct dev tmpfs_devtab __devtab = {
        .write = tree_chan_write,
        .bwrite = devbwrite,
        .remove = tmpfs_remove,
+       .rename = tmpfs_rename,
        .wstat = tree_chan_wstat,
        .power = devpower,
        .chaninfo = devchaninfo,
index f0acfb3..aa82108 100644 (file)
@@ -380,6 +380,7 @@ enum {
        Amount,                                         /* to be mounted or mounted upon */
        Acreate,                                        /* is to be created */
        Aremove,                                        /* will be removed by caller */
+       Arename,                                        /* new_path of a rename */
 
        /* internal chan flags, used by the kernel only */
        COPEN =                 0x0001, /* for i/o */
@@ -519,6 +520,7 @@ struct dev {
        size_t (*write)(struct chan *, void *, size_t, off64_t);
        size_t (*bwrite)(struct chan *, struct block *, off64_t);
        void (*remove)(struct chan *);
+       void (*rename)(struct chan *, struct chan *, const char *, int);
        size_t (*wstat)(struct chan *, uint8_t *, size_t);
        void (*power)(int);             /* power mgt: power(1) → on, power (0) → off */
 //  int (*config)( int unused_int, char *unused_char_p_t, DevConf*);
@@ -1089,6 +1091,7 @@ void read_exactly_n(struct chan *c, void *vp, long n);
 long sysread(int fd, void *va, long n);
 long syspread(int fd, void *va, long n, int64_t off);
 int sysremove(char *path);
+int sysrename(char *from_path, char *to_path);
 int64_t sysseek(int fd, int64_t off, int whence);
 void validstat(uint8_t * s, int n, int slashok);
 int sysstat(char *path, uint8_t*, int n);
index 0d47c8c..d80ee3f 100644 (file)
@@ -1065,7 +1065,7 @@ static struct chan *__namec_from(struct chan *c, char *aname, int amode,
 {
        ERRSTACK(2);
        int len, npath;
-       struct chan *cnew;
+       struct chan *cnew, *renamee;
        struct cname *cname;
        Elemlist e;
        struct mhead *m;
@@ -1095,16 +1095,14 @@ static struct chan *__namec_from(struct chan *c, char *aname, int amode,
 
        if (e.mustbedir)
                omode &= ~O_NOFOLLOW;
-       /*
-        * On create, ....
-        */
-       if (amode == Acreate) {
+
+       switch (amode) {
+       case Acreate:
                /* perm must have DMDIR if last element is / or /. */
                if (e.mustbedir && !(perm & DMDIR)) {
                        npath = e.ARRAY_SIZEs;
                        error(EINVAL, "create without DMDIR");
                }
-
                /* don't try to walk the last path element just yet. */
                if (e.ARRAY_SIZEs == 0)
                        error(EEXIST, ERROR_FIXME);
@@ -1112,8 +1110,13 @@ static struct chan *__namec_from(struct chan *c, char *aname, int amode,
                /* We're dropping the last element, which O_NOFOLLOW applied to.  Not
                 * sure if there are any legit reasons to have O_NOFOLLOW with create.*/
                omode &= ~O_NOFOLLOW;
-       }
-       switch (amode) {
+               break;
+       case Arename:
+               if (e.ARRAY_SIZEs == 0)
+                       error(EINVAL, "rename needs at least one name");
+               e.ARRAY_SIZEs--;
+               omode &= ~O_NOFOLLOW;
+               break;
        /* the difference for stat and lstat (Aaccess) are handled in sysfile.c */
        case Abind:
        case Amount:
@@ -1121,6 +1124,7 @@ static struct chan *__namec_from(struct chan *c, char *aname, int amode,
                omode |= O_NOFOLLOW;
                break;
        }
+
        if (omode & O_NOFOLLOW)
                wh->no_follow = true;
 
@@ -1249,6 +1253,50 @@ Open:
                         */
                        break;
 
+               case Arename:
+                       /* We already walked to the parent of new_path, which is in c.
+                        * We're a lot like create here - need to find mounts, etc.  On the
+                        * way out, we putmhead if we have an m, and clean up our chans.  On
+                        * success, c becomes cnew (thus close the old c).  On failure, we
+                        * just close cnew. */
+                       e.ARRAY_SIZEs++;
+                       m = NULL;
+                       cnew = NULL;
+                       if (waserror()) {
+                               /* rename or createdir failed */
+                               cclose(cnew);
+                               if (m)
+                                       putmhead(m);
+                               nexterror();    /* safe since we're in a waserror() */
+                       }
+                       if (wh->can_mount && findmount(&cnew, &m, c->type, c->dev,
+                                                      c->qid)) {
+                               cnew = createdir(cnew, m);
+                       } else {
+                               cnew = c;
+                               chan_incref(cnew);
+                       }
+                       cnew = cunique(cnew);
+                       cnameclose(cnew->name);
+                       cnew->name = c->name;
+                       kref_get(&cnew->name->ref, 1);
+                       /* At this point, we have our new_path parent chan (cnew) and the
+                        * renamee chan */
+                       renamee = ext;
+                       if (cnew->type != renamee->type)
+                               error(EXDEV, "can't rename across device types");
+
+                       devtab[cnew->type].rename(renamee, cnew,
+                                                 e.elems[e.ARRAY_SIZEs - 1], 0);
+                       poperror();
+
+                       if (m)
+                               putmhead(m);
+                       cclose(c);
+                       c = cnew;
+                       c->name = addelem(c->name, e.elems[e.ARRAY_SIZEs - 1]);
+                       break;
+
                case Acreate:
                        /*
                         * We've already walked all but the last element.
index eff6c77..0f44cc4 100644 (file)
@@ -835,6 +835,35 @@ int sysremove(char *path)
        return 0;
 }
 
+int sysrename(char *from_path, char *to_path)
+{
+       ERRSTACK(1);
+       struct chan *volatile renamee = NULL;
+       struct chan *parent_chan;
+
+       if (waserror()) {
+               cclose(renamee);
+               poperror();
+               return -1;
+       }
+       renamee = namec(from_path, Aremove, 0, 0, NULL);
+       /* We might need to support wstat for 'short' rename (intra-directory, with
+        * no slashes).  Til then, we can just go with EXDEV. */
+       if (!devtab[renamee->type].rename)
+               error(EXDEV, "device does not support rename");
+       parent_chan = namec(to_path, Arename, 0, 0, (char*)renamee);
+       /* When we're done, renamee still points to the file, but it's in the new
+        * location.  Its cname is still the old location, similar to remove.  If
+        * anyone cares, we can change it.  parent_chan still points to the parent -
+        * it didn't get moved like create does.  Though it does have the name of
+        * the new location.  If we want, we can hand that to renamee.  It's a moot
+        * point, since they are both getting closed. */
+       cclose(renamee);
+       cclose(parent_chan);
+       poperror();
+       return 0;
+}
+
 int64_t sysseek(int fd, int64_t off, int whence)
 {
        ERRSTACK(2);
index 852adb1..e4194a9 100644 (file)
@@ -2238,14 +2238,14 @@ intreg_t sys_rename(struct proc *p, char *old_path, size_t old_path_l,
 {
        char *from_path = copy_in_path(p, old_path, old_path_l);
        char *to_path = copy_in_path(p, new_path, new_path_l);
-       int retval = -1;
+       int ret;
 
        if ((!from_path) || (!to_path))
                return -1;
-       set_error(EXDEV, "no 9ns rename yet");
+       ret = sysrename(from_path, to_path);
        free_path(p, from_path);
        free_path(p, to_path);
-       return retval;
+       return ret;
 }
 
 /* Careful: if an FD is busy, we don't close the old object, it just fails */