hammer2 - Flesh out hardlink infrastructure
authorMatthew Dillon <dillon@apollo.backplane.com>
Thu, 17 May 2012 04:19:00 +0000 (21:19 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Thu, 17 May 2012 04:19:00 +0000 (21:19 -0700)
* Create the API function skeletons which will be used to implement the
  hardlink support infrastructure.

* Add vfs.hammer2.hardlink_enable (default 0=disabled).  1=enable, -1=faked.
  This is mainly for debugging, hardlinks are not yet implemented as of this
  commit.

sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_inode.c
sys/vfs/hammer2/hammer2_ioctl.c
sys/vfs/hammer2/hammer2_vfsops.c
sys/vfs/hammer2/hammer2_vnops.c

index b14e789..a8c1274 100644 (file)
@@ -210,11 +210,18 @@ SPLAY_PROTOTYPE(hammer2_chain_splay, hammer2_chain, snode, hammer2_chain_cmp);
 
 /*
  * A hammer2 inode.
+ *
+ * NOTE: Hardlinks are usually resolved through its forwarding inode(s)
+ *      but fwd will be non-NULL if the related inode/vnode is resolved
+ *      prior to the hardlink being created, or if a hardlink's real
+ *      inode had to be moved.  In these situations the old inode pointer
+ *      will get a (fwd) entry.  All vnops must forward through it.
  */
 struct hammer2_inode {
        struct hammer2_mount    *hmp;           /* Global mount */
        struct hammer2_pfsmount *pmp;           /* PFS mount */
        struct hammer2_inode    *pip;           /* parent inode */
+       struct hammer2_inode    *fwd;           /* forwarding inode */
        struct vnode            *vp;
        hammer2_chain_t         chain;
        struct hammer2_inode_data ip_data;
@@ -309,6 +316,7 @@ extern struct vop_ops hammer2_fifo_vops;
 
 extern int hammer2_debug;
 extern int hammer2_cluster_enable;
+extern int hammer2_hardlink_enable;
 extern long hammer2_iod_file_read;
 extern long hammer2_iod_meta_read;
 extern long hammer2_iod_indr_read;
@@ -360,6 +368,8 @@ void hammer2_update_time(uint64_t *timep);
  */
 struct vnode *hammer2_igetv(hammer2_inode_t *ip, int *errorp);
 
+void hammer2_inode_lock_nlinks(hammer2_inode_t *ip);
+void hammer2_inode_unlock_nlinks(hammer2_inode_t *ip);
 hammer2_inode_t *hammer2_inode_alloc(hammer2_pfsmount_t *pmp, void *data);
 void hammer2_inode_free(hammer2_inode_t *ip);
 void hammer2_inode_ref(hammer2_inode_t *ip);
@@ -367,19 +377,21 @@ void hammer2_inode_drop(hammer2_inode_t *ip);
 int hammer2_inode_calc_alloc(hammer2_key_t filesize);
 
 int hammer2_inode_create(hammer2_inode_t *dip,
-                        struct vattr *vap, struct ucred *cred,
-                        const uint8_t *name, size_t name_len,
-                        hammer2_inode_t **nipp);
+                       struct vattr *vap, struct ucred *cred,
+                       const uint8_t *name, size_t name_len,
+                       hammer2_inode_t **nipp);
 
 int hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *nip,
                        const uint8_t *name, size_t name_len);
 
-int hammer2_hardlink_create(hammer2_inode_t *ip, hammer2_inode_t *dip,
-                       const uint8_t *name, size_t name_len);
-
 int hammer2_unlink_file(hammer2_inode_t *dip,
-                       const uint8_t *name, size_t name_len,
-                       int isdir, int adjlinks);
+                       const uint8_t *name, size_t name_len, int isdir);
+int hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip,
+                       int bumpnlinks);
+int hammer2_hardlink_deconsolidate(hammer2_inode_t *dip,
+                       hammer2_chain_t **chainp, hammer2_inode_t **ipp);
+int hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp,
+                       hammer2_inode_t **ipp);
 
 /*
  * hammer2_chain.c
index a1c682c..903acf3 100644 (file)
@@ -323,6 +323,9 @@ hammer2_inode_create(hammer2_inode_t *dip,
 /*
  * Connect inode (ip) to the specified directory using the specified name.
  * (ip) must be locked.
+ *
+ * If (ip) represents a hidden hardlink target file then the inode we create
+ * for the directory entry will be setup as OBJTYPE_HARDLINK.
  */
 int
 hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
@@ -396,64 +399,6 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
 }
 
 /*
- * Create a hardlink forwarding entry (dip, name) to the specified (ip).
- *
- * This is one of the more complex implementations in HAMMER2.  The
- * filesystem strictly updates its chains bottom-up in a copy-on-write
- * fashion.  This makes hardlinks difficult to implement but we've come up
- * with a dandy solution.
- *
- * When a file has more than one link the actual inode is created as a
- * hidden directory entry (indexed by inode number) in a common parent of
- * all hardlinks which reference the file.  The hardlinks in each directory
- * are merely forwarding entries to the hidden inode.
- *
- * Implementation:
- *
- *     Most VOPs can be blissfully unaware of the forwarding entries.
- *     nresolve, nlink, and remove code have to be forwarding-aware
- *     in order to return the (ip/vp) for the actual file (and otherwise do
- *     the right thing).
- *
- *     (1) If the ip we are linking to is a normal embedded inode (nlinks==1)
- *         we have to replace the directory entry with a forwarding inode
- *         and move the normal ip/vp to a hidden entry indexed by the inode
- *         number in a common parent directory.
- *
- *     (2) If the ip we are linking to is already a hidden entry but is not
- *         a common parent we have to move its entry to a common parent by
- *         moving the entry upward.
- *
- *     (3) The trivial case is the entry is already hidden and already a
- *         common parent.  We adjust nlinks for the entry and are done.
- *         (this is the fall-through case).
- */
-int
-hammer2_hardlink_create(hammer2_inode_t *ip, hammer2_inode_t *dip,
-                       const uint8_t *name, size_t name_len)
-{
-       return ENOTSUP;
-#if 0
-       hammer2_inode_t *nip;
-       hammer2_inode_t *xip;
-
-
-       hammer2_inode_t *nip;   /* hardlink forwarding inode */
-        error = hammer2_inode_create(hmp, NULL, ap->a_cred,
-                                     dip, name, name_len, &nip);
-        if (error) {
-                KKASSERT(nip == NULL);
-                return error;
-        }
-        KKASSERT(nip->ip_data.type == HAMMER2_OBJTYPE_HARDLINK);
-        hammer2_chain_modify(&nip->chain, 0);
-        nip->ip_data.inum = ip->ip_data.inum;
-       hammer2_chain_unlock(hmp, &nip->chain);
-       /
-#endif
-}
-
-/*
  * Unlink the file from the specified directory inode.  The directory inode
  * does not need to be locked.
  *
@@ -469,8 +414,8 @@ hammer2_hardlink_create(hammer2_inode_t *ip, hammer2_inode_t *dip,
  * is non-zero, we have to locate the hardlink and adjust its nlinks field.
  */
 int
-hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
-                   int isdir, int adjlinks)
+hammer2_unlink_file(hammer2_inode_t *dip,
+                   const uint8_t *name, size_t name_len, int isdir)
 {
        hammer2_mount_t *hmp;
        hammer2_chain_t *parent;
@@ -546,23 +491,6 @@ hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
                /* dchain NULL */
        }
 
-#if 0
-       /*
-        * If adjlinks is non-zero this is a real deletion (otherwise it is
-        * probably a rename).  XXX
-        */
-       if (adjlinks) {
-               if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) {
-                       /*hammer2_adjust_hardlink(chain->u.ip, -1);*/
-                       /* error handling */
-               } else {
-                       waslastlink = 1;
-               }
-       } else {
-               waslastlink = 0;
-       }
-#endif
-
        /*
         * Found, the chain represents the inode.  Remove the parent reference
         * to the chain.  The chain itself is no longer referenced and will
@@ -573,22 +501,6 @@ hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len,
        /* XXX nlinks (hardlink special case) */
        /* XXX nlinks (parent directory) */
 
-#if 0
-       /*
-        * Destroy any associated vnode, but only if this was the last
-        * link.  XXX this might not be needed.
-        */
-       if (chain->u.ip->vp) {
-               struct vnode *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);
-               }
-       }
-#endif
        error = 0;
 
 done:
@@ -613,3 +525,65 @@ hammer2_inode_calc_alloc(hammer2_key_t filesize)
                ;
        return (radix);
 }
+
+void
+hammer2_inode_lock_nlinks(hammer2_inode_t *ip)
+{
+       hammer2_chain_ref(ip->hmp, &ip->chain);
+}
+
+void
+hammer2_inode_unlock_nlinks(hammer2_inode_t *ip)
+{
+       hammer2_chain_drop(ip->hmp, &ip->chain);
+}
+
+/*
+ * Consolidate for hard link creation.  This moves the specified terminal
+ * hardlink inode to a directory common to its current directory and tdip
+ * if necessary, replacing *ipp with the new inode chain element and
+ * modifying the original inode chain element to OBJTYPE_HARDLINK.
+ *
+ * The link count is bumped if requested.
+ */
+int
+hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip,
+                            int bumpnlinks)
+{
+       if (hammer2_hardlink_enable < 0)
+               return (0);
+       if (hammer2_hardlink_enable == 0)
+               return (ENOTSUP);
+       /* XXX */
+       return (0);
+}
+
+/*
+ * If (*ipp) is non-NULL it points to the forward OBJTYPE_HARDLINK inode while
+ * (*chainp) points to the resolved (hidden hardlink target) inode.  In this
+ * situation when nlinks is 1 we wish to deconsolidate the hardlink, moving
+ * it back to the directory that now represents the only remaining link.
+ */
+int
+hammer2_hardlink_deconsolidate(hammer2_inode_t *dip, hammer2_chain_t **chainp,
+                              hammer2_inode_t **ipp)
+{
+       if (*ipp == NULL)
+               return (0);
+       /* XXX */
+       return (0);
+}
+
+/*
+ * When presented with a (*chainp) representing an inode of type
+ * OBJTYPE_HARDLINK this code will save the original inode (with a ref)
+ * in (*ipp), and then locate the hidden hardlink target in (dip) or
+ * any parent directory above (dip).  The locked (*chainp) is replaced
+ * with a new locked (*chainp) representing the hardlink target.
+ */
+int
+hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp,
+                     hammer2_inode_t **ipp)
+{
+       return (EIO);
+}
index 74ab131..3683e1a 100644 (file)
@@ -379,7 +379,6 @@ hammer2_ioctl_pfs_delete(hammer2_inode_t *ip, void *data)
        int error;
 
        error = hammer2_unlink_file(hmp->schain->u.ip,
-                                   pfs->name, strlen(pfs->name),
-                                   0, 1);
+                                   pfs->name, strlen(pfs->name), 0);
        return (error);
 }
index 180da1f..69acd68 100644 (file)
@@ -58,6 +58,7 @@ static struct lock hammer2_mntlk;
 
 int hammer2_debug;
 int hammer2_cluster_enable = 1;
+int hammer2_hardlink_enable = 0;
 long hammer2_iod_file_read;
 long hammer2_iod_meta_read;
 long hammer2_iod_indr_read;
@@ -79,6 +80,8 @@ SYSCTL_INT(_vfs_hammer2, OID_AUTO, debug, CTLFLAG_RW,
           &hammer2_debug, 0, "");
 SYSCTL_INT(_vfs_hammer2, OID_AUTO, cluster_enable, CTLFLAG_RW,
           &hammer2_cluster_enable, 0, "");
+SYSCTL_INT(_vfs_hammer2, OID_AUTO, hardlink_enable, CTLFLAG_RW,
+          &hammer2_hardlink_enable, 0, "");
 SYSCTL_LONG(_vfs_hammer2, OID_AUTO, iod_file_read, CTLFLAG_RW,
           &hammer2_iod_file_read, 0, "");
 SYSCTL_LONG(_vfs_hammer2, OID_AUTO, iod_meta_read, CTLFLAG_RW,
index 8c959c0..8020b4e 100644 (file)
@@ -1284,6 +1284,7 @@ int
 hammer2_vop_nresolve(struct vop_nresolve_args *ap)
 {
        hammer2_inode_t *dip;
+       hammer2_inode_t *ip;
        hammer2_mount_t *hmp;
        hammer2_chain_t *parent;
        hammer2_chain_t *chain;
@@ -1322,6 +1323,41 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
        }
        hammer2_chain_unlock(hmp, parent);
 
+       /*
+        * If the inode represents a forwarding entry for a hardlink we have
+        * to locate the actual inode.  The original ip is saved for possible
+        * deconsolidation.  (ip) will only be set to non-NULL when we have
+        * to locate the real file via a hardlink.  ip will be referenced but
+        * not locked in that situation.  chain is passed in locked and
+        * returned locked.
+        */
+       ip = NULL;
+       if (chain && chain->u.ip->ip_data.type == HAMMER2_OBJTYPE_HARDLINK) {
+               kprintf("hammer2: need to find hardlink for %s\n",
+                       chain->u.ip->ip_data.filename);
+               error = hammer2_hardlink_find(dip, &chain, &ip);
+               if (error) {
+                       if (chain) {
+                               hammer2_chain_unlock(hmp, chain);
+                               chain = NULL;
+                       }
+                       goto failed;
+               }
+       }
+
+       /*
+        * Deconsolidate any hardlink whos nlinks == 1.  Ignore errors.
+        * If an error occurs chain and ip are left alone.
+        */
+       if (ip && chain && chain->u.ip->ip_data.nlinks == 1 && !hmp->ronly) {
+               kprintf("hammer2: need to unconsolidate hardlink for %s\n",
+                       chain->u.ip->ip_data.filename);
+               hammer2_hardlink_deconsolidate(dip, &chain, &ip);
+       }
+
+       /*
+        * Acquire the related vnode
+        */
        if (chain) {
                vp = hammer2_igetv(chain->u.ip, &error);
                if (error == 0) {
@@ -1332,8 +1368,11 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
                hammer2_chain_unlock(hmp, chain);
        } else {
                error = ENOENT;
+failed:
                cache_setvp(ap->a_nch, NULL);
        }
+       if (ip)
+               hammer2_inode_drop(ip);
        return error;
 }
 
@@ -1529,13 +1568,13 @@ hammer2_vop_close(struct vop_close_args *ap)
 /*
  * hammer2_vop_nlink { nch, dvp, vp, cred }
  *
- * Create a hardlink to vp.
+ * Create a hardlink from (vp) to {dvp, nch}.
  */
 static
 int
 hammer2_vop_nlink(struct vop_nlink_args *ap)
 {
-       hammer2_inode_t *dip;
+       hammer2_inode_t *dip;   /* target directory to create link in */
        hammer2_inode_t *ip;    /* inode we are hardlinking to */
        hammer2_mount_t *hmp;
        struct namecache *ncp;
@@ -1548,17 +1587,44 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
        if (hmp->ronly)
                return (EROFS);
 
+       /*
+        * (ip) is the inode we are linking to.
+        */
        ip = VTOI(ap->a_vp);
+       hammer2_inode_lock_nlinks(ip);
 
        ncp = ap->a_nch->ncp;
        name = ncp->nc_name;
        name_len = ncp->nc_nlen;
 
-       error = hammer2_hardlink_create(ip, dip, name, name_len);
+       /*
+        * Create a consolidated real file for the hardlink, adjust (ip),
+        * and move the nlinks lock if necessary.  Tell the function to
+        * bump the hardlink count on the consolidated file.
+        */
+       error = hammer2_hardlink_consolidate(&ip, dip, 1);
+       if (error)
+               goto done;
+
+       /*
+        * When creating a hardlink we have to bump the nlinks count
+        * in ip
+        * in ip after successfully connecting the directory entry to
+        * (ip).
+        */
+       hammer2_chain_lock(hmp, &ip->chain, HAMMER2_RESOLVE_ALWAYS);
+       error = hammer2_inode_connect(dip, ip, name, name_len);
+       if (error == 0) {
+               hammer2_chain_modify(ip->hmp, &ip->chain, 0);
+               ++ip->ip_data.nlinks;
+       }
+       hammer2_chain_unlock(hmp, &ip->chain);
        if (error == 0) {
                cache_setunresolved(ap->a_nch);
                cache_setvp(ap->a_nch, ap->a_vp);
        }
+done:
+       hammer2_inode_unlock_nlinks(ip);
        return error;
 }
 
@@ -1708,7 +1774,7 @@ hammer2_vop_nremove(struct vop_nremove_args *ap)
        name = ncp->nc_name;
        name_len = ncp->nc_nlen;
 
-       error = hammer2_unlink_file(dip, name, name_len, 0, 1);
+       error = hammer2_unlink_file(dip, name, name_len, 0);
 
        if (error == 0) {
                cache_setunresolved(ap->a_nch);
@@ -1740,7 +1806,7 @@ hammer2_vop_nrmdir(struct vop_nrmdir_args *ap)
        name = ncp->nc_name;
        name_len = ncp->nc_nlen;
 
-       error = hammer2_unlink_file(dip, name, name_len, 1, 1);
+       error = hammer2_unlink_file(dip, name, name_len, 1);
 
        if (error == 0) {
                cache_setunresolved(ap->a_nch);
@@ -1788,34 +1854,49 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
        tname = tncp->nc_name;
        tname_len = tncp->nc_nlen;
 
-       ip = VTOI(fncp->nc_vp);         /* inode being moved */
+       /*
+        * ip is the inode being removed.  If this is a hardlink then
+        * ip represents the actual file and not the hardlink marker.
+        */
+       ip = VTOI(fncp->nc_vp);
 
        /*
         * Keep a tight grip on the inode as removing it should disconnect
         * it and we don't want to destroy it.
         *
         * NOTE: To avoid deadlocks we cannot lock (ip) while we are
-        *       unlinking elements from their directories.
+        *       unlinking elements from their directories.  Locking
+        *       the nlinks field does not lock the whole inode.
         */
-       hammer2_chain_ref(hmp, &ip->chain);
+       hammer2_inode_lock_nlinks(ip);
 
        /*
         * Remove target if it exists
         */
-       error = hammer2_unlink_file(tdip, tname, tname_len, -1, 1);
+       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, do not adjust
-        * the link count.  Note that rename doesn't need to understand
-        * whether this is a hardlink or not, we can just rename the
-        * forwarding entry and don't even have to adjust the related
-        * hardlink's link count.
+        * Disconnect ip from the source directory.
+        *
+        * If (ip) is already hardlinked we have to resolve to a consolidated
+        * file but we do not bump the nlinks count.  (ip) must hold the nlinks
+        * lock & ref for the operation.  If the consolidated file has been
+        * relocated (ip) will be adjusted and the related nlinks lock moved
+        * along with it.
+        *
+        * If (ip) does not have multiple links we can just copy the physical
+        * contents of the inode.
         */
-       error = hammer2_unlink_file(fdip, fname, fname_len, -1, 0);
+       if (ip->ip_data.nlinks > 1) {
+               error = hammer2_hardlink_consolidate(&ip, tdip, 0);
+               if (error)
+                       goto done;
+       }
+       error = hammer2_unlink_file(fdip, fname, fname_len, -1);
        if (error)
                goto done;
 
@@ -1837,7 +1918,7 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
                cache_rename(ap->a_fnch, ap->a_tnch);
        }
 done:
-       hammer2_chain_drop(hmp, &ip->chain);    /* from ref up top */
+       hammer2_inode_unlock_nlinks(ip);
 
        return (error);
 }