hammer2 - Implement rename
authorMatthew Dillon <dillon@apollo.backplane.com>
Wed, 29 Feb 2012 02:43:15 +0000 (18:43 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Wed, 29 Feb 2012 02:43:15 +0000 (18:43 -0800)
* Implement hammer2_vop_nrename().

sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_chain.c
sys/vfs/hammer2/hammer2_inode.c
sys/vfs/hammer2/hammer2_vnops.c

index 9870f50..ade2d8f 100644 (file)
@@ -131,6 +131,7 @@ SPLAY_PROTOTYPE(hammer2_chain_splay, hammer2_chain, snode, hammer2_chain_cmp);
 #define HAMMER2_CHAIN_FLUSHED          0x00000040      /* flush on unlock */
 #define HAMMER2_CHAIN_MOVED            0x00000080      /* moved */
 #define HAMMER2_CHAIN_IOFLUSH          0x00000100      /* bawrite on put */
+#define HAMMER2_CHAIN_WAS_MODIFIED     0x00000200      /* used w/rename */
 
 /*
  * Flags passed to hammer2_chain_lookup() and hammer2_chain_next()
@@ -278,11 +279,15 @@ hammer2_inode_t *hammer2_inode_alloc(hammer2_mount_t *hmp, void *data);
 void hammer2_inode_free(hammer2_inode_t *ip);
 void hammer2_inode_ref(hammer2_inode_t *ip);
 void hammer2_inode_drop(hammer2_inode_t *ip);
+
 int hammer2_create_inode(hammer2_mount_t *hmp,
-                        struct vattr *vap, struct ucred *cred,
-                        hammer2_inode_t *dip,
-                        const uint8_t *name, size_t name_len,
-                        hammer2_inode_t **nipp);
+                       struct vattr *vap, struct ucred *cred,
+                       hammer2_inode_t *dip,
+                       const uint8_t *name, size_t name_len,
+                       hammer2_inode_t **nipp);
+
+int hammer2_connect_inode(hammer2_inode_t *dip, hammer2_inode_t *nip,
+                       const uint8_t *name, size_t name_len);
 
 /*
  * hammer2_chain.c
@@ -312,6 +317,7 @@ hammer2_chain_t *hammer2_chain_next(hammer2_mount_t *hmp,
                                int flags);
 hammer2_chain_t *hammer2_chain_create(hammer2_mount_t *hmp,
                                hammer2_chain_t *parent,
+                               hammer2_chain_t *chain,
                                hammer2_key_t key, int keybits,
                                int type, size_t bytes);
 void hammer2_chain_delete(hammer2_mount_t *hmp, hammer2_chain_t *parent,
index 47494fd..3ad3dc1 100644 (file)
@@ -114,7 +114,6 @@ hammer2_chain_alloc(hammer2_mount_t *hmp, hammer2_blockref_t *bref)
        }
        chain->bref = *bref;
        chain->index = -1;      /* not yet assigned */
-
        chain->refs = 1;
        lockmgr(&chain->lk, LK_EXCLUSIVE);
 
@@ -189,13 +188,20 @@ hammer2_chain_drop(hammer2_mount_t *hmp, hammer2_chain_t *chain)
                                if (!(chain->flags & HAMMER2_CHAIN_DELETED)) {
                                        SPLAY_REMOVE(hammer2_chain_splay,
                                                     &parent->shead, chain);
+                                       atomic_set_int(&chain->flags,
+                                                      HAMMER2_CHAIN_DELETED);
+                                       /* parent refs dropped via recursion */
                                }
                                chain->parent = NULL;
-                               lockmgr(&parent->lk, LK_RELEASE);
+                               if (parent)
+                                       lockmgr(&parent->lk, LK_RELEASE);
                                hammer2_chain_free(hmp, chain);
                                chain = parent;
-                       } else if (parent) {
-                               lockmgr(&parent->lk, LK_RELEASE);
+                               /* recurse on parent */
+                       } else {
+                               if (parent)
+                                       lockmgr(&parent->lk, LK_RELEASE);
+                               /* retry the same chain */
                        }
                } else {
                        if (atomic_cmpset_int(&chain->refs, refs, refs - 1)) {
@@ -205,6 +211,7 @@ hammer2_chain_drop(hammer2_mount_t *hmp, hammer2_chain_t *chain)
                                 */
                                break;
                        }
+                       /* retry the same chain */
                }
        }
 }
@@ -516,6 +523,12 @@ hammer2_chain_get(hammer2_mount_t *hmp, hammer2_chain_t *parent,
                panic("hammer2_chain_get: unrecognized blockref type: %d",
                      parent->bref.type);
        }
+
+       /*
+        * Allocate a chain structure representing the existing media
+        * entry.  Thus the chain is *not* INITIAL and certainly not
+        * MODIFIED (yet).
+        */
        chain = hammer2_chain_alloc(hmp, bref);
 
        /*
@@ -643,7 +656,6 @@ again:
                        hammer2_chain_ref(hmp, parent);
                        if ((flags & HAMMER2_LOOKUP_NOLOCK) == 0)
                                hammer2_chain_lock(hmp, parent);
-                       kprintf("DIRECT DATA RETURNED\n");
                        return (parent);
                }
                base = &parent->data->ipdata.u.blockset.blockref[0];
@@ -899,50 +911,67 @@ again2:
  */
 hammer2_chain_t *
 hammer2_chain_create(hammer2_mount_t *hmp, hammer2_chain_t *parent,
+                    hammer2_chain_t *chain,
                     hammer2_key_t key, int keybits, int type, size_t bytes)
 {
        hammer2_blockref_t dummy;
        hammer2_blockref_t *base;
        hammer2_blockref_t *bref;
-       hammer2_chain_t *chain;
        hammer2_chain_t dummy_chain;
+       int unlock_parent = 0;
+       int allocated = 0;
        int count;
        int i;
-       int unlock_parent = 0;
 
-       /*
-        * First allocate media space and construct the dummy bref, then
-        * allocate the in-memory chain structure.
-        */
-       bzero(&dummy, sizeof(dummy));
-       dummy.type = type;
-       dummy.key = key;
-       dummy.keybits = keybits;
-       dummy.data_off = (hammer2_off_t)hammer2_freemap_bytes_to_radix(bytes);
-       chain = hammer2_chain_alloc(hmp, &dummy);
+       if (chain == NULL) {
+               /*
+                * First allocate media space and construct the dummy bref,
+                * then allocate the in-memory chain structure.
+                */
+               bzero(&dummy, sizeof(dummy));
+               dummy.type = type;
+               dummy.key = key;
+               dummy.keybits = keybits;
+               dummy.data_off = hammer2_freemap_bytes_to_radix(bytes);
+               chain = hammer2_chain_alloc(hmp, &dummy);
+               allocated = 1;
 
-       /*
-        * Recalculate bytes to reflect the actual media block allocation,
-        * then allocate the local memory copy.  This is a new structure
-        * so no I/O is performed.
-        */
-       bytes = (hammer2_off_t)1 <<
-               (int)(chain->bref.data_off & HAMMER2_OFF_MASK_RADIX);
+               /*
+                * We set the WAS_MODIFIED flag here so the chain gets
+                * marked as modified below.
+                */
+               chain->flags |= HAMMER2_CHAIN_INITIAL |
+                               HAMMER2_CHAIN_WAS_MODIFIED;
 
-       switch(type) {
-       case HAMMER2_BREF_TYPE_VOLUME:
-               panic("hammer2_chain_create: called with volume type");
-               break;
-       case HAMMER2_BREF_TYPE_INODE:
-               KKASSERT(bytes == HAMMER2_INODE_BYTES);
-               chain->data = (void *)&chain->u.ip->ip_data;
-               break;
-       default:
-               /* leave chain->data NULL */
-               KKASSERT(chain->data == NULL);
-               break;
+               /*
+                * Recalculate bytes to reflect the actual media block
+                * allocation.
+                */
+               bytes = (hammer2_off_t)1 <<
+                       (int)(chain->bref.data_off & HAMMER2_OFF_MASK_RADIX);
+
+               switch(type) {
+               case HAMMER2_BREF_TYPE_VOLUME:
+                       panic("hammer2_chain_create: called with volume type");
+                       break;
+               case HAMMER2_BREF_TYPE_INODE:
+                       KKASSERT(bytes == HAMMER2_INODE_BYTES);
+                       chain->data = (void *)&chain->u.ip->ip_data;
+                       break;
+               default:
+                       /* leave chain->data NULL */
+                       KKASSERT(chain->data == NULL);
+                       break;
+               }
+       } else {
+               /*
+                * Potentially update the chain's key/keybits, but it will
+                * only be marked modified if WAS_MODIFIED is set (if it
+                * was modified at the time of its removal during a rename).
+                */
+               chain->bref.key = key;
+               chain->bref.keybits = keybits;
        }
-       atomic_set_int(&chain->flags, HAMMER2_CHAIN_INITIAL);
 
 again:
        /*
@@ -1003,7 +1032,8 @@ again:
                nparent = hammer2_chain_create_indirect(hmp, parent,
                                                        key, keybits);
                if (nparent == NULL) {
-                       hammer2_chain_free(hmp, chain);
+                       if (allocated)
+                               hammer2_chain_free(hmp, chain);
                        chain = NULL;
                        goto done;
                }
@@ -1019,10 +1049,14 @@ again:
        /*
         * Link the chain into its parent.
         */
+       if (chain->parent != NULL)
+               panic("hammer2: hammer2_chain_create: chain already connected");
+       KKASSERT(chain->parent == NULL);
        chain->parent = parent;
        chain->index = i;
        if (SPLAY_INSERT(hammer2_chain_splay, &parent->shead, chain))
                panic("hammer2_chain_link: collision");
+       atomic_clear_int(&chain->flags, HAMMER2_CHAIN_DELETED);
        KKASSERT(parent->refs > 1);
        atomic_add_int(&parent->refs, 1);
 
@@ -1039,8 +1073,9 @@ again:
        }
 
        /*
-        * Mark the newly created chain element as modified and fully
-        * resolve the chain->data pointer.
+        * Mark the newly created or previously disconnected chain element
+        * as modified and fully resolve the chain->data pointer.  The
+        * WAS_MODIFIED bit will be set in both cases.
         *
         * Chain elements with embedded data will not issue I/O at this time.
         * A new block will be allocated for the buffer but not instantiated.
@@ -1049,7 +1084,10 @@ again:
         * the new block AND instantiate its buffer cache buffer, pointing
         * the data at the bp.
         */
-       hammer2_chain_modify(hmp, chain);
+       if (chain->flags & HAMMER2_CHAIN_WAS_MODIFIED) {
+               atomic_clear_int(&chain->flags, HAMMER2_CHAIN_WAS_MODIFIED);
+               hammer2_chain_modify(hmp, chain);
+       }
 
 done:
        if (unlock_parent)
@@ -1242,6 +1280,7 @@ hammer2_chain_create_indirect(hammer2_mount_t *hmp, hammer2_chain_t *parent,
        dummy.bref.data_off = (hammer2_off_t)
                            hammer2_freemap_bytes_to_radix(HAMMER2_PBUFSIZE);
        ichain = hammer2_chain_alloc(hmp, &dummy.bref);
+       ichain->flags |= HAMMER2_CHAIN_INITIAL;
 
        /*
         * Iterate the original parent and move the matching brefs into
@@ -1418,12 +1457,23 @@ hammer2_chain_delete(hammer2_mount_t *hmp, hammer2_chain_t *parent,
                count = 0;
                break;
        }
+
+       /*
+        * Disconnect the bref in the parent, remove the chain, and
+        * disconnect in-memory fields from the parent.
+        */
        KKASSERT(chain->index >= 0 && chain->index < count);
        base += chain->index;
        bzero(base, sizeof(*base));
+
        SPLAY_REMOVE(hammer2_chain_splay, &parent->shead, chain);
        atomic_set_int(&chain->flags, HAMMER2_CHAIN_DELETED);
+       atomic_add_int(&parent->refs, -1);      /* for splay entry */
+       chain->index = -1;
+
        chain->parent = NULL;
+       if (chain->bref.type == HAMMER2_BREF_TYPE_INODE)
+               chain->u.ip->pip = NULL;
 
        /*
         * Nobody references the underlying object any more so we can
@@ -1431,9 +1481,13 @@ hammer2_chain_delete(hammer2_mount_t *hmp, hammer2_chain_t *parent,
         * recurse downward but even just clearing the bit on this item
         * will effectively recurse if someone is doing a rm -rf and greatly
         * reduce the I/O required.
+        *
+        * The MODIFIED bit is cleared but we have to remember the old state
+        * in case this deletion is related to a rename.
         */
        if (chain->flags & HAMMER2_CHAIN_MODIFIED) {
                atomic_clear_int(&chain->flags, HAMMER2_CHAIN_MODIFIED);
+               atomic_set_int(&chain->flags, HAMMER2_CHAIN_WAS_MODIFIED);
                hammer2_chain_drop(hmp, chain); /* ref for modified bit */
        }
 }
index c72f957..8a62984 100644 (file)
@@ -231,7 +231,7 @@ hammer2_create_inode(hammer2_mount_t *hmp,
                ++lhc;
        }
        if (error == 0) {
-               chain = hammer2_chain_create(hmp, parent, lhc, 0,
+               chain = hammer2_chain_create(hmp, parent, NULL, lhc, 0,
                                             HAMMER2_BREF_TYPE_INODE,
                                             HAMMER2_INODE_BYTES);
                if (chain == NULL)
@@ -280,3 +280,79 @@ hammer2_create_inode(hammer2_mount_t *hmp,
 
        return (0);
 }
+
+/*
+ * Connect inode (ip) to the specified directory using the specified name.
+ * (ip) must be locked.
+ */
+int
+hammer2_connect_inode(hammer2_inode_t *dip, hammer2_inode_t *ip,
+                     const uint8_t *name, size_t name_len)
+{
+       hammer2_mount_t *hmp = dip->hmp;
+       hammer2_chain_t *chain;
+       hammer2_chain_t *parent;
+       hammer2_key_t lhc;
+       int error;
+
+       lhc = hammer2_dirhash(name, name_len);
+
+       /*
+        * Locate the inode or indirect block to create the new
+        * entry in.  At the same time check for key collisions
+        * and iterate until we don't get one.
+        */
+       parent = &dip->chain;
+       hammer2_chain_ref(hmp, parent);
+       hammer2_chain_lock(hmp, parent);
+
+       error = 0;
+       while (error == 0) {
+               chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
+               if (chain == NULL)
+                       break;
+               if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK)
+                       error = ENOSPC;
+               hammer2_chain_put(hmp, chain);
+               chain = NULL;
+               ++lhc;
+       }
+
+       /*
+        * Passing a non-NULL chain to hammer2_chain_create() reconnects the
+        * existing chain instead of creating a new one.  The chain's bref
+        * will be properly updated.
+        */
+       if (error == 0) {
+               chain = hammer2_chain_create(hmp, parent, &ip->chain, lhc, 0,
+                                            HAMMER2_BREF_TYPE_INODE /* n/a */,
+                                            HAMMER2_INODE_BYTES);   /* n/a */
+               if (chain == NULL)
+                       error = EIO;
+       }
+       hammer2_chain_put(hmp, parent);
+
+       /*
+        * Handle the error case
+        */
+       if (error) {
+               KKASSERT(chain == NULL);
+               return (error);
+       }
+
+       /*
+        * Directory entries are inodes so if the name has changed we have
+        * to update the inode.
+        */
+       if (ip->ip_data.name_len != name_len ||
+           bcmp(ip->ip_data.filename, name, name_len) != 0) {
+               hammer2_chain_modify(hmp, chain);
+               KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
+               bcopy(name, ip->ip_data.filename, name_len);
+               ip->ip_data.name_key = lhc;
+               ip->ip_data.name_len = name_len;
+       }
+       /*nip->ip_data.nlinks = 1;*/
+
+       return (0);
+}
index 25dd6f1..e3be4ac 100644 (file)
@@ -1263,13 +1263,92 @@ hammer2_vop_nrmdir(struct vop_nrmdir_args *ap)
        return (error);
 }
 
+/*
+ * hammer2_vop_nrename { fnch, tnch, fdvp, tdvp, cred }
+ */
 static
 int
 hammer2_vop_nrename(struct vop_nrename_args *ap)
 {
-       return (EINVAL);
+       struct namecache *fncp;
+       struct namecache *tncp;
+       hammer2_inode_t *fdip;
+       hammer2_inode_t *tdip;
+       hammer2_inode_t *ip;
+       hammer2_mount_t *hmp;
+       const uint8_t *fname;
+       size_t fname_len;
+       const uint8_t *tname;
+       size_t tname_len;
+       int error;
+
+       if (ap->a_fdvp->v_mount != ap->a_tdvp->v_mount)
+               return(EXDEV);
+       if (ap->a_fdvp->v_mount != ap->a_fnch->ncp->nc_vp->v_mount)
+               return(EXDEV);
+
+       fdip = VTOI(ap->a_fdvp);        /* source directory */
+       tdip = VTOI(ap->a_tdvp);        /* target directory */
+
+       hmp = fdip->hmp;                /* check read-only filesystem */
+       if (hmp->ronly)
+               return(EROFS);
+
+       fncp = ap->a_fnch->ncp;         /* entry name in source */
+       fname = fncp->nc_name;
+       fname_len = fncp->nc_nlen;
+
+       tncp = ap->a_tnch->ncp;         /* entry name in target */
+       tname = tncp->nc_name;
+       tname_len = tncp->nc_nlen;
+
+       ip = VTOI(fncp->nc_vp);         /* inode being moved */
+
+       /*
+        * Keep a tight grip on the inode as removing it should disconnect
+        * it and we don't want to destroy it.
+        */
+       hammer2_chain_ref(hmp, &ip->chain);
+       hammer2_chain_lock(hmp, &ip->chain);
+
+       /*
+        * Remove target if it exists
+        */
+       error = hammer2_unlink_file(tdip, tname, tname_len, -1);
+       if (error && error != ENOENT)
+               goto done;
+       cache_setunresolved(ap->a_tnch);
+       cache_setvp(ap->a_tnch, NULL);
+
+       /*
+        * Disconnect ip from the source directory.
+        */
+       error = hammer2_unlink_file(fdip, fname, fname_len, -1);
+       if (error)
+               goto done;
+
+       if (ip->chain.parent != NULL)
+               panic("hammer2_vop_nrename(): rename source != ip!");
+
+       /*
+        * Reconnect ip to target directory.
+        */
+       error = hammer2_connect_inode(tdip, ip, tname, tname_len);
+
+       if (error == 0) {
+               cache_rename(ap->a_fnch, ap->a_tnch);
+       }
+done:
+       hammer2_chain_unlock(hmp, &ip->chain);
+       hammer2_chain_drop(hmp, &ip->chain);
+
+       return (error);
 }
 
+/*
+ * Unlink the file from the specified directory inode.  The directory inode
+ * does not need to be locked.
+ */
 static
 int
 hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
@@ -1312,25 +1391,30 @@ hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
        }
 
        /*
-        * Not found or wrong type
+        * Not found or wrong type (isdir < 0 disables the type check).
         */
        if (chain == NULL) {
                hammer2_chain_put(hmp, parent);
                return ENOENT;
        }
-       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY && !isdir) {
+       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY &&
+           isdir == 0) {
                error = ENOTDIR;
                goto done;
        }
-       if (chain->data->ipdata.type != HAMMER2_OBJTYPE_DIRECTORY && isdir) {
+       if (chain->data->ipdata.type != HAMMER2_OBJTYPE_DIRECTORY &&
+           isdir == 1) {
                error = EISDIR;
                goto done;
        }
 
        /*
-        * If this is a directory the directory must be empty
+        * If this is a directory the directory must be empty.  However, if
+        * isdir < 0 we are doing a rename and the directory does not have
+        * to be empty.
         */
-       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY) {
+       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY &&
+           isdir >= 0) {
                dparent = chain;
                hammer2_chain_ref(hmp, dparent);
                hammer2_chain_lock(hmp, dparent);
@@ -1358,12 +1442,14 @@ hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
        /* XXX nlinks (hardlink special case) */
        /* XXX nlinks (parent directory) */
 
-       vp = hammer2_igetv(chain->u.ip, &error);
-       if (error == 0) {
-               vn_unlock(vp);
-               /* hammer2_knote(vp, NOTE_DELETE); */
-               cache_inval_vp(vp, CINV_DESTROY);
-               vrele(vp);
+       if (chain->u.ip->vp) {
+               vp = hammer2_igetv(chain->u.ip, &error);
+               if (error == 0) {
+                       vn_unlock(vp);
+                       /* hammer2_knote(vp, NOTE_DELETE); */
+                       cache_inval_vp(vp, CINV_DESTROY);
+                       vrele(vp);
+               }
        }
        error = 0;
 
@@ -1539,7 +1625,7 @@ hammer2_strategy_write(struct vop_strategy_args *ap)
                /*
                 * A new data block must be allocated.
                 */
-               chain = hammer2_chain_create(hmp, parent,
+               chain = hammer2_chain_create(hmp, parent, NULL,
                                             off_hi, HAMMER2_PBUFRADIX,
                                             HAMMER2_BREF_TYPE_DATA,
                                             HAMMER2_PBUFSIZE);