hammer2 - Complete core hardlink support work
authorMatthew Dillon <dillon@apollo.backplane.com>
Thu, 17 May 2012 08:36:51 +0000 (01:36 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Thu, 17 May 2012 08:53:05 +0000 (01:53 -0700)
This implements core hardlink support for hammer2.  In order to maintain the
strict bottom-up block modification hierarchy for the chains hardlinks must
be implemented with special forwarding inodes.

When a hardlink is created (nlinks 1->2) the file is replaced with a
forwarding entry and then recreated as a special hidden directory entry
indexed by its inode number at a higher directory level which is common
to all hardlinks to that file.

The forwarding entry simply specifies the inode number, thus our ability to
trivially snapshot a PFS is retained.

Since the real inode is indexed at a higher common directory locating the
real inode simply requires iterating parent directories until we find a
match.

* Default vfs.hammer2.hardlink_enable to 1 (enabled).

* Track and adjust nlinks.

* Implement OBJTYPE_HARDLINK forwarding directory entry, hidden inode,
  vnode->v_data inode replacement for the nlinks 1->2 case, and hidden
  inode deletion for the nlinks 1->0 case.

* The deconsolidation for the nlinks 2->1 case is not yet implemented.

sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_chain.c
sys/vfs/hammer2/hammer2_disk.h
sys/vfs/hammer2/hammer2_inode.c
sys/vfs/hammer2/hammer2_subr.c
sys/vfs/hammer2/hammer2_vfsops.c
sys/vfs/hammer2/hammer2_vnops.c

index a8c1274..352b970 100644 (file)
@@ -210,22 +210,16 @@ 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;
        struct lockf            advlock;
+       u_int                   depth;          /* directory depth */
 };
 
 typedef struct hammer2_inode hammer2_inode_t;
@@ -381,13 +375,15 @@ int hammer2_inode_create(hammer2_inode_t *dip,
                        const uint8_t *name, size_t name_len,
                        hammer2_inode_t **nipp);
 
-int hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *nip,
+int hammer2_inode_duplicate(hammer2_inode_t *dip,
+                       hammer2_inode_t *oip, hammer2_inode_t **nipp,
+                       const uint8_t *name, size_t name_len);
+int hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip,
                        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 hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip,
-                       int bumpnlinks);
+int hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip);
 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,
index 46000e9..8df9803 100644 (file)
@@ -953,6 +953,7 @@ hammer2_chain_get(hammer2_mount_t *hmp, hammer2_chain_t *parent,
                if (parent->bref.type == HAMMER2_BREF_TYPE_INODE) {
                        chain->u.ip->pip = parent->u.ip;
                        chain->u.ip->pmp = parent->u.ip->pmp;
+                       chain->u.ip->depth = parent->u.ip->depth + 1;
                }
        }
 
@@ -1517,6 +1518,7 @@ again:
                if (scan->bref.type == HAMMER2_BREF_TYPE_INODE) {
                        chain->u.ip->pip = scan->u.ip;
                        chain->u.ip->pmp = scan->u.ip->pmp;
+                       chain->u.ip->depth = scan->u.ip->depth + 1;
                }
        }
 
@@ -2010,8 +2012,10 @@ hammer2_chain_delete(hammer2_mount_t *hmp, hammer2_chain_t *parent,
        /*
         * If this is an inode clear the pip.
         */
-       if (chain->bref.type == HAMMER2_BREF_TYPE_INODE)
+       if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) {
                chain->u.ip->pip = NULL;
+               chain->u.ip->depth = 0;
+       }
 
        /*
         * The chain is still likely referenced, possibly even by a vnode
index 81b8642..ccc8dca 100644 (file)
@@ -451,7 +451,7 @@ struct hammer2_inode_data {
         *       a separate node.  {pfs_fsid, pfs_id} must be used for
         *       registration in the cluster.
         */
-       uint8_t         reserved84;     /* 0084 */
+       uint8_t         target_type;    /* 0084 hardlink target type */
        uint8_t         reserved85;     /* 0085 */
        uint8_t         reserved86;     /* 0086 */
        uint8_t         pfs_type;       /* 0087 (if PFSROOT) node type */
index 903acf3..d5ae60d 100644 (file)
@@ -320,24 +320,132 @@ hammer2_inode_create(hammer2_inode_t *dip,
        return (0);
 }
 
+/*
+ * Duplicate the specified existing inode in the specified target directory.
+ * If name is NULL the inode is duplicated as a hidden directory entry.
+ *
+ * Returns the new inode.  The old inode is left alone.
+ */
+int
+hammer2_inode_duplicate(hammer2_inode_t *dip, hammer2_inode_t *oip,
+                       hammer2_inode_t **nipp,
+                       const uint8_t *name, size_t name_len)
+{
+       hammer2_mount_t *hmp = dip->hmp;
+       hammer2_inode_t *nip;
+       hammer2_chain_t *chain;
+       hammer2_chain_t *parent;
+       hammer2_key_t lhc;
+       int error;
+
+       if (name) {
+               lhc = hammer2_dirhash(name, name_len);
+       } else {
+               lhc = oip->ip_data.inum;
+               KKASSERT((lhc & HAMMER2_DIRHASH_VISIBLE) == 0);
+       }
+
+       /*
+        * 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.
+        */
+       nip = NULL;
+       parent = &dip->chain;
+       hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
+
+       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_unlock(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, NULL, lhc, 0,
+                                            HAMMER2_BREF_TYPE_INODE /* n/a */,
+                                            HAMMER2_INODE_BYTES);   /* n/a */
+               if (chain == NULL)
+                       error = EIO;
+       }
+       hammer2_chain_unlock(hmp, parent);
+
+       /*
+        * Handle the error case
+        */
+       if (error) {
+               KKASSERT(chain == NULL);
+               return (error);
+       }
+       nip = chain->u.ip;
+       hammer2_chain_modify(hmp, chain, 0);
+       nip->ip_data = oip->ip_data;
+
+       if (name) {
+               /*
+                * Directory entries are inodes so if the name has changed
+                * we have to update the inode.
+                */
+               KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
+               bcopy(name, nip->ip_data.filename, name_len);
+               nip->ip_data.name_key = lhc;
+               nip->ip_data.name_len = name_len;
+       } else {
+               /*
+                * Directory entries are inodes but this is a hidden hardlink
+                * target.  The name isn't used but to ease debugging give it
+                * a name after its inode number.
+                */
+               nip->ip_data = oip->ip_data;
+               ksnprintf(nip->ip_data.filename, sizeof(nip->ip_data.filename),
+                         "0x%016jx", (intmax_t)nip->ip_data.inum);
+               nip->ip_data.name_len = strlen(nip->ip_data.filename);
+               nip->ip_data.name_key = lhc;
+       }
+       *nipp = nip;
+
+       return (0);
+}
+
+
 /*
  * 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.
+ * If (ip) is not currently connected we simply connect it up.
+ *
+ * If (ip) is already connected we create a OBJTYPE_HARDLINK entry which
+ * points to (ip)'s inode number.  (ip) is expected to be the terminus of
+ * the hardlink sitting as a hidden file in a common parent directory
+ * in this situation.
+ *
+ * If (nipp) is non-NULL then (*nip) is set to point at the new inode
+ * and returned locked.
  */
 int
-hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
+hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip,
                      const uint8_t *name, size_t name_len)
 {
        hammer2_mount_t *hmp = dip->hmp;
        hammer2_chain_t *chain;
        hammer2_chain_t *parent;
+       hammer2_inode_t *nip;
        hammer2_key_t lhc;
        int error;
+       int hlink;
 
        lhc = hammer2_dirhash(name, name_len);
+       hlink = ((oip->chain.flags & HAMMER2_CHAIN_DELETED) == 0);
 
        /*
         * Locate the inode or indirect block to create the new
@@ -365,9 +473,19 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
         * 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 (hlink) {
+                       chain = hammer2_chain_create(hmp, parent,
+                                                    NULL, lhc, 0,
+                                                    HAMMER2_BREF_TYPE_INODE,
+                                                    HAMMER2_INODE_BYTES);
+               } else {
+                       chain = hammer2_chain_create(hmp, parent,
+                                                    &oip->chain, lhc, 0,
+                                                    HAMMER2_BREF_TYPE_INODE,
+                                                    HAMMER2_INODE_BYTES);
+                       if (chain)
+                               KKASSERT(chain == &oip->chain);
+               }
                if (chain == NULL)
                        error = EIO;
        }
@@ -384,14 +502,33 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
        /*
         * Directory entries are inodes so if the name has changed we have
         * to update the inode.
+        *
+        * When creating an OBJTYPE_HARDLINK entry remember to unlock the
+        * chain, the caller will access the hardlink via the actual hardlink
+        * target file and not the hardlink pointer entry.
         */
-       if (ip->ip_data.name_len != name_len ||
-           bcmp(ip->ip_data.filename, name, name_len) != 0) {
+       if (hlink) {
+               nip = chain->u.ip;
                hammer2_chain_modify(hmp, chain, 0);
                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;
+               bcopy(name, nip->ip_data.filename, name_len);
+               nip->ip_data.name_key = lhc;
+               nip->ip_data.name_len = name_len;
+               nip->ip_data.target_type = oip->ip_data.type;
+               nip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
+               nip->ip_data.inum = oip->ip_data.inum;
+               kprintf("created hardlink %*.*s\n",
+                       (int)name_len, (int)name_len, name);
+               hammer2_chain_unlock(hmp, chain);
+       } else {
+               if (oip->ip_data.name_len != name_len ||
+                   bcmp(oip->ip_data.filename, name, name_len) != 0) {
+                       hammer2_chain_modify(hmp, chain, 0);
+                       KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
+                       bcopy(name, oip->ip_data.filename, name_len);
+                       oip->ip_data.name_key = lhc;
+                       oip->ip_data.name_len = name_len;
+               }
        }
        /*nip->ip_data.nlinks = 1;*/
 
@@ -404,14 +541,6 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip,
  *
  * isdir determines whether a directory/non-directory check should be made.
  * No check is made if isdir is set to -1.
- *
- * adjlinks tells unlink that we want to adjust the nlinks count of the
- * inode.  When removing the last link for a NON forwarding entry we can
- * just ignore the link count... no point updating the inode that we are
- * about to dereference, it would just result in a lot of wasted I/O.
- *
- * However, if the entry is a forwarding entry (aka a hardlink), and adjlinks
- * is non-zero, we have to locate the hardlink and adjust its nlinks field.
  */
 int
 hammer2_unlink_file(hammer2_inode_t *dip,
@@ -423,10 +552,13 @@ hammer2_unlink_file(hammer2_inode_t *dip,
        hammer2_chain_t *dparent;
        hammer2_chain_t *dchain;
        hammer2_key_t lhc;
+       hammer2_inode_t *ip;
+       hammer2_inode_t *oip;
        int error;
+       uint8_t type;
 
        error = 0;
-
+       oip = NULL;
        hmp = dip->hmp;
        lhc = hammer2_dirhash(name, name_len);
 
@@ -457,24 +589,34 @@ hammer2_unlink_file(hammer2_inode_t *dip,
                hammer2_chain_unlock(hmp, parent);
                return ENOENT;
        }
-       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY &&
-           isdir == 0) {
+       if ((type = chain->data->ipdata.type) == HAMMER2_OBJTYPE_HARDLINK)
+               type = chain->data->ipdata.target_type;
+
+       if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) {
                error = ENOTDIR;
                goto done;
        }
-       if (chain->data->ipdata.type != HAMMER2_OBJTYPE_DIRECTORY &&
-           isdir == 1) {
+       if (type != HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) {
                error = EISDIR;
                goto done;
        }
 
+       /*
+        * Hardlink must be resolved.  We can't hold parent locked while we
+        * do this or we could deadlock.
+        */
+       if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) {
+               hammer2_chain_unlock(hmp, parent);
+               parent = NULL;
+               error = hammer2_hardlink_find(dip, &chain, &oip);
+       }
+
        /*
         * 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 &&
-           isdir >= 0) {
+       if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir >= 0) {
                dparent = chain;
                hammer2_chain_lock(hmp, dparent, HAMMER2_RESOLVE_ALWAYS);
                dchain = hammer2_chain_lookup(hmp, &dparent,
@@ -492,20 +634,67 @@ hammer2_unlink_file(hammer2_inode_t *dip,
        }
 
        /*
-        * Found, the chain represents the inode.  Remove the parent reference
-        * to the chain.  The chain itself is no longer referenced and will
-        * be marked unmodified by hammer2_chain_delete(), avoiding unnecessary
-        * I/O.
+        * Ok, we can now unlink the chain.  We always decrement nlinks even
+        * if the entry can be deleted in case someone has the file open and
+        * does an fstat().
+        *
+        * The chain itself will no longer have a media reference.  When the
+        * last vnode/ip ref goes away the chain will be marked unmodified,
+        * avoiding unnecessary I/O.
         */
-       hammer2_chain_delete(hmp, parent, chain);
-       /* XXX nlinks (hardlink special case) */
-       /* XXX nlinks (parent directory) */
+       if (oip) {
+               /*
+                * If this was a hardlink we first delete the hardlink
+                * pointer entry.
+                */
+               parent = oip->chain.parent;
+               hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
+               hammer2_chain_lock(hmp, &oip->chain, HAMMER2_RESOLVE_ALWAYS);
+               hammer2_chain_delete(hmp, parent, &oip->chain);
+               hammer2_chain_unlock(hmp, &oip->chain);
+               hammer2_chain_unlock(hmp, parent);
+               parent = NULL;
+
+               /*
+                * Then decrement nlinks on hardlink target.
+                */
+               ip = chain->u.ip;
+               if (ip->ip_data.nlinks == 1) {
+                       dparent = chain->parent;
+                       hammer2_chain_ref(hmp, chain);
+                       hammer2_chain_unlock(hmp, chain);
+                       hammer2_chain_lock(hmp, dparent,
+                                          HAMMER2_RESOLVE_ALWAYS);
+                       hammer2_chain_lock(hmp, chain, HAMMER2_RESOLVE_ALWAYS);
+                       hammer2_chain_drop(hmp, chain);
+                       hammer2_chain_modify(hmp, chain, 0);
+                       --ip->ip_data.nlinks;
+                       hammer2_chain_delete(hmp, dparent, chain);
+                       hammer2_chain_unlock(hmp, dparent);
+               } else {
+                       hammer2_chain_modify(hmp, chain, 0);
+                       --ip->ip_data.nlinks;
+               }
+       } else {
+               /*
+                * Otherwise this was not a hardlink and we can just
+                * remove the entry and decrement nlinks.
+                */
+               ip = chain->u.ip;
+               hammer2_chain_modify(hmp, chain, 0);
+               --ip->ip_data.nlinks;
+               hammer2_chain_delete(hmp, parent, chain);
+       }
 
        error = 0;
 
 done:
-       hammer2_chain_unlock(hmp, chain);
-       hammer2_chain_unlock(hmp, parent);
+       if (chain)
+               hammer2_chain_unlock(hmp, chain);
+       if (parent)
+               hammer2_chain_unlock(hmp, parent);
+       if (oip)
+               hammer2_chain_drop(oip->hmp, &oip->chain);
 
        return error;
 }
@@ -544,18 +733,145 @@ hammer2_inode_unlock_nlinks(hammer2_inode_t *ip)
  * if necessary, replacing *ipp with the new inode chain element and
  * modifying the original inode chain element to OBJTYPE_HARDLINK.
  *
+ * If the original inode chain element was a prior incarnation of a hidden
+ * inode it can simply be deleted instead of converted.
+ *
+ * (*ipp)'s nlinks field is locked on entry and the new (*ipp)'s nlinks
+ * field will be locked on return (with the original's unlocked).
+ *
  * The link count is bumped if requested.
  */
 int
-hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip,
-                            int bumpnlinks)
+hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip)
 {
+       hammer2_mount_t *hmp;
+       hammer2_inode_t *oip = *ipp;
+       hammer2_inode_t *nip = NULL;
+       hammer2_inode_t *fdip;
+       hammer2_chain_t *parent;
+       int error;
+
+       hmp = tdip->hmp;
+
        if (hammer2_hardlink_enable < 0)
                return (0);
        if (hammer2_hardlink_enable == 0)
                return (ENOTSUP);
-       /* XXX */
-       return (0);
+
+       /*
+        * Find the common parent directory
+        */
+       fdip = oip->pip;
+       while (fdip->depth > tdip->depth) {
+               fdip = fdip->pip;
+               KKASSERT(fdip != NULL);
+       }
+       while (tdip->depth > fdip->depth) {
+               tdip = tdip->pip;
+               KKASSERT(tdip != NULL);
+       }
+       while (fdip != tdip) {
+               fdip = fdip->pip;
+               tdip = tdip->pip;
+               KKASSERT(fdip != NULL);
+               KKASSERT(tdip != NULL);
+       }
+
+       /*
+        * Nothing to do (except bump the link count) if the hardlink has
+        * already been consolidated in the correct place.
+        */
+       if (oip->pip == fdip &&
+           (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
+               kprintf("hardlink already consolidated correctly\n");
+               nip = oip;
+               hammer2_inode_lock_ex(nip);
+               hammer2_chain_modify(hmp, &nip->chain, 0);
+               ++nip->ip_data.nlinks;
+               hammer2_inode_unlock_ex(nip);
+               return (0);
+       }
+
+       /*
+        * Create a hidden inode directory entry in the parent, copying
+        * (*oip)'s state.  Then replace oip with OBJTYPE_HARDLINK.
+        */
+       error = hammer2_inode_duplicate(fdip, oip, &nip, NULL, 0);
+       if (error == 0) {
+               /*
+                * Bump nlinks on duplicated hidden inode.
+                */
+               kprintf("hardlink consolidation success in parent dir %s\n",
+                       fdip->ip_data.filename);
+               hammer2_inode_lock_nlinks(nip);
+               hammer2_inode_unlock_nlinks(oip);
+               hammer2_chain_modify(hmp, &nip->chain, 0);
+               ++nip->ip_data.nlinks;
+               hammer2_inode_unlock_ex(nip);
+
+               if (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) {
+                       /*
+                        * Replace the old inode with an OBJTYPE_HARDLINK
+                        * pointer.
+                        */
+                       hammer2_inode_lock_ex(oip);
+                       hammer2_chain_modify(hmp, &oip->chain, 0);
+                       oip->ip_data.target_type = oip->ip_data.type;
+                       oip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
+                       oip->ip_data.uflags = 0;
+                       oip->ip_data.rmajor = 0;
+                       oip->ip_data.rminor = 0;
+                       oip->ip_data.ctime = 0;
+                       oip->ip_data.mtime = 0;
+                       oip->ip_data.atime = 0;
+                       oip->ip_data.btime = 0;
+                       bzero(&oip->ip_data.uid, sizeof(oip->ip_data.uid));
+                       bzero(&oip->ip_data.gid, sizeof(oip->ip_data.gid));
+                       oip->ip_data.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
+                       oip->ip_data.cap_flags = 0;
+                       oip->ip_data.mode = 0;
+                       oip->ip_data.size = 0;
+                       oip->ip_data.nlinks = 1;
+                       oip->ip_data.iparent = 0;       /* XXX */
+                       oip->ip_data.pfs_type = 0;
+                       oip->ip_data.pfs_inum = 0;
+                       bzero(&oip->ip_data.pfs_id,
+                             sizeof(oip->ip_data.pfs_id));
+                       bzero(&oip->ip_data.pfs_fsid,
+                             sizeof(oip->ip_data.pfs_fsid));
+                       oip->ip_data.data_quota = 0;
+                       oip->ip_data.data_count = 0;
+                       oip->ip_data.inode_quota = 0;
+                       oip->ip_data.inode_count = 0;
+                       oip->ip_data.attr_tid = 0;
+                       oip->ip_data.dirent_tid = 0;
+                       bzero(&oip->ip_data.u, sizeof(oip->ip_data.u));
+                       /* XXX transaction ids */
+
+                       hammer2_inode_unlock_ex(oip);
+               } else {
+                       /*
+                        * The old inode was a hardlink target, which we
+                        * have now moved.  We must delete it so the new
+                        * hardlink target at a higher directory level
+                        * becomes the only hardlink target for this inode.
+                        */
+                       kprintf("DELETE INVISIBLE\n");
+                       parent = oip->chain.parent;
+                       hammer2_chain_lock(hmp, parent,
+                                          HAMMER2_RESOLVE_ALWAYS);
+                       hammer2_chain_lock(hmp, &oip->chain,
+                                          HAMMER2_RESOLVE_ALWAYS);
+                       hammer2_chain_delete(hmp, parent, &oip->chain);
+                       hammer2_chain_unlock(hmp, &oip->chain);
+                       hammer2_chain_unlock(hmp, parent);
+               }
+               *ipp = nip;
+       } else {
+               KKASSERT(nip == NULL);
+       }
+
+       return (error);
 }
 
 /*
@@ -585,5 +901,41 @@ int
 hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp,
                      hammer2_inode_t **ipp)
 {
-       return (EIO);
+       hammer2_mount_t *hmp = dip->hmp;
+       hammer2_chain_t *chain = *chainp;
+       hammer2_chain_t *parent;
+       hammer2_inode_t *pip;
+       hammer2_key_t lhc;
+
+       *ipp = chain->u.ip;
+       hammer2_inode_ref(chain->u.ip);
+       lhc = chain->u.ip->ip_data.inum;
+
+       hammer2_inode_unlock_ex(chain->u.ip);
+       pip = chain->u.ip->pip;
+
+       chain = NULL;
+       while (pip) {
+               parent = &pip->chain;
+               KKASSERT(parent->bref.type == HAMMER2_BREF_TYPE_INODE);
+
+               hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
+               chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
+               if (chain) {
+                       kprintf("found hardlink in dir %s\n",
+                               pip->ip_data.filename);
+               }
+               hammer2_chain_unlock(hmp, parent);
+               if (chain)
+                       break;
+               pip = pip->pip;
+       }
+       *chainp = chain;
+       if (chain) {
+               KKASSERT(chain->bref.type == HAMMER2_BREF_TYPE_INODE);
+               /* already locked */
+               return (0);
+       } else {
+               return (EIO);
+       }
 }
index ffe75bc..51ed554 100644 (file)
@@ -146,7 +146,12 @@ hammer2_voldata_unlock(hammer2_mount_t *hmp)
 int
 hammer2_get_dtype(hammer2_inode_t *ip)
 {
-       switch(ip->ip_data.type) {
+       uint8_t type;
+
+       if ((type = ip->ip_data.type) == HAMMER2_OBJTYPE_HARDLINK)
+               type = ip->ip_data.target_type;
+
+       switch(type) {
        case HAMMER2_OBJTYPE_UNKNOWN:
                return (DT_UNKNOWN);
        case HAMMER2_OBJTYPE_DIRECTORY:
index 69acd68..81b15d0 100644 (file)
@@ -58,7 +58,7 @@ static struct lock hammer2_mntlk;
 
 int hammer2_debug;
 int hammer2_cluster_enable = 1;
-int hammer2_hardlink_enable = 0;
+int hammer2_hardlink_enable = 1;
 long hammer2_iod_file_read;
 long hammer2_iod_meta_read;
 long hammer2_iod_indr_read;
index 8020b4e..b0066ac 100644 (file)
@@ -508,7 +508,8 @@ hammer2_vop_readdir(struct vop_readdir_args *ap)
                 * or some entries will be missed.
                 */
                chain = hammer2_chain_next(hmp, &parent, chain,
-                                          0, (hammer2_key_t)-1, 0);
+                                          HAMMER2_DIRHASH_VISIBLE,
+                                          (hammer2_key_t)-1, 0);
                if (chain) {
                        saveoff = (chain->bref.key &
                                   HAMMER2_DIRHASH_USERMSK) + 1;
@@ -1576,6 +1577,7 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
 {
        hammer2_inode_t *dip;   /* target directory to create link in */
        hammer2_inode_t *ip;    /* inode we are hardlinking to */
+       hammer2_inode_t *oip;
        hammer2_mount_t *hmp;
        struct namecache *ncp;
        const uint8_t *name;
@@ -1590,7 +1592,7 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
        /*
         * (ip) is the inode we are linking to.
         */
-       ip = VTOI(ap->a_vp);
+       ip = oip = VTOI(ap->a_vp);
        hammer2_inode_lock_nlinks(ip);
 
        ncp = ap->a_nch->ncp;
@@ -1602,22 +1604,35 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
         * 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);
+       error = hammer2_hardlink_consolidate(&ip, dip);
        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).
+        * If the consolidation changed ip to a HARDLINK pointer we have
+        * to adjust the vnode to point to the actual ip.
+        */
+       if (oip != ip) {
+               hammer2_chain_ref(hmp, &ip->chain);
+               hammer2_inode_lock_ex(ip);
+               hammer2_inode_lock_ex(oip);
+               ip->vp = ap->a_vp;
+               ap->a_vp->v_data = ip;
+               oip->vp = NULL;
+               hammer2_inode_unlock_ex(oip);
+               hammer2_inode_unlock_ex(ip);
+               hammer2_chain_drop(hmp, &oip->chain);
+       }
+
+       /*
+        * The act of connecting the existing (ip) will properly bump the
+        * nlinks count.  However, vp will incorrectly point at the old
+        * inode which has now been turned into a OBJTYPE_HARDLINK pointer.
+        *
+        * We must reconnect the vp.
         */
        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);
@@ -1880,7 +1895,10 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
        cache_setvp(ap->a_tnch, NULL);
 
        /*
-        * Disconnect ip from the source directory.
+        * Disconnect (fdip, fname) from the source directory.  This will
+        * disconnect (ip) if it represents a direct file.  If (ip) represents
+        * a hardlink the HARDLINK pointer object will be removed but the
+        * hardlink will stay intact.
         *
         * 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
@@ -1892,7 +1910,7 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
         * contents of the inode.
         */
        if (ip->ip_data.nlinks > 1) {
-               error = hammer2_hardlink_consolidate(&ip, tdip, 0);
+               error = hammer2_hardlink_consolidate(&ip, tdip);
                if (error)
                        goto done;
        }
@@ -1900,9 +1918,6 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
        if (error)
                goto done;
 
-       if (ip->chain.parent != NULL)
-               panic("hammer2_vop_nrename(): rename source != ip!");
-
        /*
         * Reconnect ip to target directory.
         *