hammer2 - Refactor frontend part 14/many
authorMatthew Dillon <dillon@apollo.backplane.com>
Sat, 27 Jun 2015 02:12:26 +0000 (19:12 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Sat, 27 Jun 2015 02:16:13 +0000 (19:16 -0700)
* Implement nlink, nremove, nrmdir, and nrename.

  The hardlink handling in nlink and nremove were the most difficult
  here, but the resulting design is actually cleaner than the complex
  do-everything routines I had before.

* Remove the old hardlink handling clutter in the inode and cluster code.

sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_chain.c
sys/vfs/hammer2/hammer2_cluster.c
sys/vfs/hammer2/hammer2_inode.c
sys/vfs/hammer2/hammer2_ioctl.c
sys/vfs/hammer2/hammer2_thread.c
sys/vfs/hammer2/hammer2_vnops.c
sys/vfs/hammer2/hammer2_xops.c

index 4d75245..2896ead 100644 (file)
@@ -832,6 +832,8 @@ typedef struct hammer2_xop_fifo {
 struct hammer2_xop_head {
        hammer2_xop_func_t      func;
        struct hammer2_inode    *ip;
+       struct hammer2_inode    *ip2;
+       struct hammer2_inode    *ip3;
        struct hammer2_xop_group *xgrp;
        uint32_t                check_counter;
        uint32_t                run_mask;
@@ -840,6 +842,8 @@ struct hammer2_xop_head {
        int                     error;
        char                    *name;
        size_t                  name_len;
+       char                    *name2;
+       size_t                  name2_len;
        hammer2_key_t           lkey;
        hammer2_key_t           nkey;
        hammer2_xop_fifo_t      collect[HAMMER2_MAXCLUSTER];
@@ -863,6 +867,22 @@ struct hammer2_xop_nresolve {
        hammer2_xop_head_t      head;
 };
 
+struct hammer2_xop_nlink {
+       hammer2_xop_head_t      head;
+};
+
+struct hammer2_xop_unlink {
+       hammer2_xop_head_t      head;
+       int                     isdir;
+       int                     dopermanent;
+};
+
+struct hammer2_xop_nrename {
+       hammer2_xop_head_t      head;
+       hammer2_tid_t           lhc;
+       int                     ip_key;
+};
+
 struct hammer2_xop_scanlhc {
        hammer2_xop_head_t      head;
        hammer2_key_t           lhc;
@@ -875,6 +895,11 @@ struct hammer2_xop_create {
        int                     flags;
 };
 
+struct hammer2_xop_connect {
+       hammer2_xop_head_t      head;
+       hammer2_key_t           lhc;
+};
+
 struct hammer2_xop_destroy {
        hammer2_xop_head_t      head;
        hammer2_key_t           lhc;
@@ -887,21 +912,29 @@ struct hammer2_xop_flush {
 
 typedef struct hammer2_xop_readdir hammer2_xop_readdir_t;
 typedef struct hammer2_xop_nresolve hammer2_xop_nresolve_t;
+typedef struct hammer2_xop_nlink hammer2_xop_nlink_t;
+typedef struct hammer2_xop_unlink hammer2_xop_unlink_t;
+typedef struct hammer2_xop_nrename hammer2_xop_nrename_t;
 typedef struct hammer2_xop_strategy hammer2_xop_strategy_t;
 typedef struct hammer2_xop_create hammer2_xop_create_t;
 typedef struct hammer2_xop_destroy hammer2_xop_destroy_t;
 typedef struct hammer2_xop_scanlhc hammer2_xop_scanlhc_t;
+typedef struct hammer2_xop_connect hammer2_xop_connect_t;
 typedef struct hammer2_xop_flush hammer2_xop_flush_t;
 
 union hammer2_xop {
        hammer2_xop_head_t      head;
        hammer2_xop_readdir_t   xop_readdir;
        hammer2_xop_nresolve_t  xop_nresolve;
+       hammer2_xop_nlink_t     xop_nlink;
+       hammer2_xop_unlink_t    xop_unlink;
+       hammer2_xop_nrename_t   xop_nrename;
        hammer2_xop_strategy_t  xop_strategy;
        hammer2_xop_create_t    xop_create;
        hammer2_xop_destroy_t   xop_destroy;
        hammer2_xop_scanlhc_t   xop_scanlhc;
        hammer2_xop_flush_t     xop_flush;
+       hammer2_xop_connect_t   xop_connect;
 };
 
 typedef union hammer2_xop hammer2_xop_t;
@@ -1237,27 +1270,15 @@ void hammer2_run_unlinkq(hammer2_pfs_t *pmp);
 hammer2_inode_t *hammer2_inode_create(hammer2_inode_t *dip,
                        struct vattr *vap, struct ucred *cred,
                        const uint8_t *name, size_t name_len,
+                       hammer2_key_t inum, uint8_t type, uint8_t target_type,
                        int flags, int *errorp);
-int hammer2_inode_connect(hammer2_inode_t *ip, hammer2_cluster_t **clusterp,
-                       int hlink,
-                       hammer2_inode_t *dip, hammer2_cluster_t *dcluster,
-                       const uint8_t *name, size_t name_len,
-                       hammer2_key_t key);
+int hammer2_inode_connect_simple(hammer2_inode_t *dip, hammer2_inode_t *ip,
+                       const char *name, size_t name_len,
+                       hammer2_key_t lhc);
 hammer2_inode_t *hammer2_inode_common_parent(hammer2_inode_t *fdip,
                        hammer2_inode_t *tdip);
 void hammer2_inode_fsync(hammer2_inode_t *ip, hammer2_cluster_t *cparent);
-int hammer2_unlink_file(hammer2_inode_t *dip, hammer2_inode_t *ip,
-                       const uint8_t *name, size_t name_len, int isdir,
-                       int *hlinkp, int isopen, int isrename);
-int hammer2_cluster_hardlink_consolidate(hammer2_inode_t *ip,
-                       hammer2_cluster_t **clusterp,
-                       hammer2_inode_t *cdip, hammer2_cluster_t *cdcluster,
-                       int nlinks);
-int hammer2_cluster_hardlink_deconsolidate(hammer2_inode_t *dip,
-                       hammer2_chain_t **chainp, hammer2_chain_t **ochainp);
-int hammer2_cluster_hardlink_find(hammer2_inode_t *dip,
-                       hammer2_cluster_t **cparentp,
-                       hammer2_cluster_t **clusterp);
+int hammer2_inode_unlink_finisher(hammer2_inode_t *ip, int isopen);
 int hammer2_parent_find(hammer2_cluster_t **cparentp,
                        hammer2_cluster_t *cluster);
 void hammer2_inode_install_hidden(hammer2_pfs_t *pmp);
@@ -1281,7 +1302,8 @@ hammer2_media_data_t *hammer2_chain_wdata(hammer2_chain_t *chain);
 
 int hammer2_chain_hardlink_find(hammer2_inode_t *dip,
                                hammer2_chain_t **parentp,
-                               hammer2_chain_t **chainp);
+                               hammer2_chain_t **chainp,
+                               int flags);
 
 /*
  * hammer2_cluster.c
@@ -1301,6 +1323,7 @@ hammer2_chain_t *hammer2_chain_get(hammer2_chain_t *parent, int generation,
                                hammer2_blockref_t *bref);
 hammer2_chain_t *hammer2_chain_lookup_init(hammer2_chain_t *parent, int flags);
 void hammer2_chain_lookup_done(hammer2_chain_t *parent);
+hammer2_chain_t *hammer2_chain_getparent(hammer2_chain_t **parentp, int how);
 hammer2_chain_t *hammer2_chain_lookup(hammer2_chain_t **parentp,
                                hammer2_key_t *key_nextp,
                                hammer2_key_t key_beg, hammer2_key_t key_end,
@@ -1394,6 +1417,12 @@ void hammer2_io_bqrelse(hammer2_io_t **diop);
  */
 void hammer2_xop_group_init(hammer2_pfs_t *pmp, hammer2_xop_group_t *xgrp);
 hammer2_xop_t *hammer2_xop_alloc(hammer2_inode_t *ip);
+void hammer2_xop_setname(hammer2_xop_head_t *xop,
+                               const char *name, size_t name_len);
+void hammer2_xop_setname2(hammer2_xop_head_t *xop,
+                               const char *name, size_t name_len);
+void hammer2_xop_setip2(hammer2_xop_head_t *xop, hammer2_inode_t *ip2);
+void hammer2_xop_setip3(hammer2_xop_head_t *xop, hammer2_inode_t *ip3);
 void hammer2_xop_reinit(hammer2_xop_head_t *xop);
 void hammer2_xop_helper_create(hammer2_pfs_t *pmp);
 void hammer2_xop_helper_cleanup(hammer2_pfs_t *pmp);
@@ -1407,9 +1436,13 @@ int hammer2_xop_feed(hammer2_xop_head_t *xop, hammer2_chain_t *chain,
 
 void hammer2_xop_readdir(hammer2_xop_t *xop, int clidx);
 void hammer2_xop_nresolve(hammer2_xop_t *xop, int clidx);
+void hammer2_xop_unlink(hammer2_xop_t *xop, int clidx);
+void hammer2_xop_nrename(hammer2_xop_t *xop, int clidx);
+void hammer2_xop_nlink(hammer2_xop_t *xop, int clidx);
 void hammer2_inode_xop_scanlhc(hammer2_xop_t *xop, int clidx);
 void hammer2_inode_xop_create(hammer2_xop_t *xop, int clidx);
 void hammer2_inode_xop_destroy(hammer2_xop_t *xop, int clidx);
+void hammer2_inode_xop_connect(hammer2_xop_t *xop, int clidx);
 void hammer2_inode_xop_flush(hammer2_xop_t *xop, int clidx);
 #if 0
 int hammer2_xop_nmkdir(struct vop_nmkdir_args *ap);
index cb8ba6c..dcd564a 100644 (file)
@@ -1535,7 +1535,6 @@ hammer2_chain_lookup_done(hammer2_chain_t *parent)
        }
 }
 
-static
 hammer2_chain_t *
 hammer2_chain_getparent(hammer2_chain_t **parentp, int how)
 {
@@ -4033,295 +4032,6 @@ hammer2_chain_testcheck(hammer2_chain_t *chain, void *bdata)
        return r;
 }
 
-#if 0
-/*
- * The chain has been removed from the original directory and replaced
- * with a hardlink pointer.  Move the chain to the specified parent
- * directory, change the filename to "0xINODENUMBER", and adjust the key.
- * The chain becomes our invisible hardlink target.
- *
- * The original chain must be deleted on entry.
- *
- * Caller is responsible for synchronizing ip->meta.name/name_len.  This
- * routine may be called from a XOP, so modifying ip->meta directly is out.
- */
-static
-void
-hammer2_chain_hardlink_shiftup(
-                       hammer2_chain_t *chain,
-                       hammer2_key_t inum,
-                       hammer2_chain_t **dchainp,
-                       int *errorp)
-{
-       hammer2_inode_data_t *nipdata;
-       hammer2_chain_t *xchain;
-       hammer2_key_t key_dummy;
-       hammer2_key_t lhc;
-       hammer2_blockref_t bref;
-
-       lhc = inum;     /* bit 63 not set makes hardlinks invisible */
-       KKASSERT((lhc & HAMMER2_DIRHASH_VISIBLE) == 0);
-
-       /*
-        * Locate the inode or indirect block to create the new
-        * entry in.  lhc represents the inode number so there is
-        * no collision iteration.
-        *
-        * There should be no key collisions with invisible inode keys.
-        */
-       *errorp = 0;
-       xchain = hammer2_chain_lookup(dchainp, &key_dummy, lhc, lhc, 0);
-       if (xchain) {
-               hammer2_chain_unlock(xchain);
-               hammer2_chain_drop(xchain);
-               xchain =NULL;
-               *errorp = ENOSPC;
-#if 0
-               Debugger("X3");
-#endif
-       }
-
-       /*
-        * Handle the error case
-        */
-       if (*errorp) {
-               panic("error2");
-               KKASSERT(xchain == NULL);
-               return;
-       }
-
-       /*
-        * Use xcluster as a placeholder for (lhc).  Duplicate cluster to the
-        * same target bref as xcluster and then delete xcluster.  The
-        * duplication occurs after xcluster in flush order even though
-        * xcluster is deleted after the duplication. XXX
-        *
-        * WARNING! Duplications (to a different parent) can cause indirect
-        *          blocks to be inserted, refactor xcluster.
-        *
-        * WARNING! Only key and keybits is extracted from a passed-in bref.
-        */
-       bref = chain->bref;
-       bref.key = lhc;                 /* invisible dir entry key */
-       bref.keybits = 0;
-       hammer2_chain_rename(&bref, dchainp, chain, 0);
-
-       /*
-        * cluster is now 'live' again.. adjust the filename.
-        *
-        * 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.
-        */
-       hammer2_chain_modify(chain, 0);
-       nipdata = &chain->data->ipdata;
-       ksnprintf(nipdata->filename, sizeof(nipdata->filename),
-                 "0x%016jx", (intmax_t)inum);
-
-       /*
-        * Warning: Caller must adjust ip->meta.name_lne, name_key,
-        *          and nlinks.
-        */
-       nipdata->meta.name_len = strlen(nipdata->filename);
-       nipdata->meta.name_key = lhc;
-       /*ip->meta.nlinks += nlinks;*/
-}
-
-/*
- * Given an exclusively locked inode and cluster we consolidate the cluster
- * for hardlink creation, adding (nlinks) to the file's link count and
- * potentially relocating the inode to (cdip) which is a parent directory
- * common to both the current location of the inode and the intended new
- * hardlink.
- *
- * Replaces (*clusterp) if consolidation occurred, unlocking the old cluster
- * and returning a new locked cluster.
- *
- * NOTE!  This function will also replace ip->cluster.
- */
-int
-hammer2_chain_hardlink_consolidate(
-                       hammer2_inode_t *ip,
-                       hammer2_chain_t **chainp,
-                       hammer2_inode_t *cdip,
-                       hammer2_chain_t *cdchain,
-                       int nlinks)
-{
-       hammer2_chain_t *chain;
-       hammer2_chain_t *parent;
-       int error;
-
-       chain = *chainp;
-       if (nlinks == 0 &&                      /* no hardlink needed */
-           (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
-               return (0);
-       }
-
-       if (hammer2_hardlink_enable == 0) {     /* disallow hardlinks */
-               hammer2_chain_unlock(chain);
-               hammer2_chain_drop(chain);
-               *chainp = NULL;
-               return (ENOTSUP);
-       }
-
-       parent = NULL;
-
-       /*
-        * If no change in the hardlink's target directory is required and
-        * this is already a hardlink target, all we need to do is adjust
-        * the link count.
-        */
-       if (cdip == ip->pip &&
-           (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
-               if (nlinks) {
-                       hammer2_inode_modify(ip);
-                       ip->meta.nlinks += nlinks;
-#if 0
-                       hammer2_cluster_modify(cluster, 0);
-                       wipdata = &hammer2_cluster_wdata(cluster)->ipdata;
-                       wipdata->meta.nlinks += nlinks;
-                       hammer2_cluster_modsync(cluster);
-                       ripdata = wipdata;
-#endif
-               }
-               error = 0;
-               goto done;
-       }
-
-       /*
-        * Cluster is the real inode.  The originating directory is locked
-        * by the caller so we can manipulate it without worrying about races
-        * against other lookups.
-        *
-        * If cluster is visible we need to delete it from the current
-        * location and create a hardlink pointer in its place.  If it is
-        * not visible we need only delete it.  Then later cluster will be
-        * renamed to a parent directory and converted (if necessary) to
-        * a hidden inode (via shiftup).
-        *
-        * NOTE! We must hold cparent locked through the delete/create/rename
-        *       operation to ensure that other threads block resolving to
-        *       the same hardlink, otherwise the other threads may not see
-        *       the hardlink.
-        */
-       KKASSERT((cluster->focus->flags & HAMMER2_CHAIN_DELETED) == 0);
-       cparent = hammer2_cluster_parent(cluster);
-
-       hammer2_cluster_delete(cparent, cluster, 0);
-
-       KKASSERT(ip->meta.type != HAMMER2_OBJTYPE_HARDLINK);
-       if (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) {
-               const hammer2_inode_data_t *ripdata;
-               hammer2_inode_data_t *wipdata;
-               hammer2_cluster_t *ncluster;
-               hammer2_key_t lhc;
-
-               ncluster = NULL;
-               lhc = cluster->focus->bref.key;
-               error = hammer2_cluster_create(cparent, &ncluster,
-                                            lhc, 0,
-                                            HAMMER2_BREF_TYPE_INODE,
-                                            HAMMER2_INODE_BYTES,
-                                            0);
-               hammer2_cluster_modify(ncluster, 0);
-               wipdata = &hammer2_cluster_wdata(ncluster)->ipdata;
-
-               /* wipdata->meta.comp_algo = ip->meta.comp_algo; */
-               wipdata->meta.comp_algo = 0;
-               wipdata->meta.check_algo = 0;
-               wipdata->meta.version = HAMMER2_INODE_VERSION_ONE;
-               wipdata->meta.inum = ip->meta.inum;
-               wipdata->meta.target_type = ip->meta.type;
-               wipdata->meta.type = HAMMER2_OBJTYPE_HARDLINK;
-               wipdata->meta.uflags = 0;
-               wipdata->meta.rmajor = 0;
-               wipdata->meta.rminor = 0;
-               wipdata->meta.ctime = 0;
-               wipdata->meta.mtime = 0;
-               wipdata->meta.atime = 0;
-               wipdata->meta.btime = 0;
-               bzero(&wipdata->meta.uid, sizeof(wipdata->meta.uid));
-               bzero(&wipdata->meta.gid, sizeof(wipdata->meta.gid));
-               wipdata->meta.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
-               wipdata->meta.cap_flags = 0;
-               wipdata->meta.mode = 0;
-               wipdata->meta.size = 0;
-               wipdata->meta.nlinks = 1;
-               wipdata->meta.iparent = 0;      /* XXX */
-               wipdata->meta.pfs_type = 0;
-               wipdata->meta.pfs_inum = 0;
-               bzero(&wipdata->meta.pfs_clid, sizeof(wipdata->meta.pfs_clid));
-               bzero(&wipdata->meta.pfs_fsid, sizeof(wipdata->meta.pfs_fsid));
-               wipdata->meta.data_quota = 0;
-               /* wipdata->data_count = 0; */
-               wipdata->meta.inode_quota = 0;
-               /* wipdata->inode_count = 0; */
-               wipdata->meta.attr_tid = 0;
-               wipdata->meta.dirent_tid = 0;
-               bzero(&wipdata->u, sizeof(wipdata->u));
-               ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-               KKASSERT(ip->meta.name_len <= sizeof(wipdata->filename));
-               bcopy(ripdata->filename, wipdata->filename,
-                     ip->meta.name_len);
-               wipdata->meta.name_key = ncluster->focus->bref.key;
-               wipdata->meta.name_len = ip->meta.name_len;
-               /* XXX transaction ids */
-               hammer2_cluster_modsync(ncluster);
-               hammer2_cluster_unlock(ncluster);
-               hammer2_cluster_drop(ncluster);
-       }
-
-       /*
-        * cluster represents the hardlink target and is now flagged deleted.
-        * duplicate it to the parent directory and adjust nlinks.
-        *
-        * WARNING! The shiftup() call can cause ncluster to be moved into
-        *          an indirect block, and our ncluster will wind up pointing
-        *          to the older/original version.
-        */
-       KKASSERT(cluster->focus->flags & HAMMER2_CHAIN_DELETED);
-       hammer2_chain_hardlink_shiftup(cluster, ip, cdip, cdcluster,
-                                        nlinks, &error);
-
-       if (error == 0)
-               hammer2_inode_repoint(ip, cdip, cluster);
-
-done:
-       /*
-        * Cleanup, cluster/ncluster already dealt with.
-        *
-        * Return the shifted cluster in *clusterp.
-        */
-       if (cparent) {
-               hammer2_cluster_unlock(cparent);
-               hammer2_cluster_drop(cparent);
-       }
-       *clusterp = cluster;
-
-       return (error);
-}
-
-/*
- * If (*ochainp) 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_chain_hardlink_deconsolidate(
-                              hammer2_inode_t *dip,
-                              hammer2_chain_t **chainp,
-                              hammer2_chain_t **ochainp)
-{
-       if (*ochainp == NULL)
-               return (0);
-       /* XXX */
-       return (0);
-}
-
-#endif
-
 /*
  * The caller presents a shared-locked (parent, chain) where the chain
  * is of type HAMMER2_OBJTYPE_HARDLINK.  The caller must hold the ip
@@ -4336,7 +4046,8 @@ hammer2_chain_hardlink_deconsolidate(
 int
 hammer2_chain_hardlink_find(hammer2_inode_t *dip,
                        hammer2_chain_t **parentp,
-                       hammer2_chain_t **chainp)
+                       hammer2_chain_t **chainp,
+                       int flags)
 {
        hammer2_chain_t *parent;
        hammer2_chain_t *rchain;
@@ -4357,8 +4068,7 @@ hammer2_chain_hardlink_find(hammer2_inode_t *dip,
                int nloops;
                rchain = hammer2_chain_lookup(parentp, &key_dummy,
                                              lhc, lhc,
-                                             &cache_index,
-                                             HAMMER2_LOOKUP_SHARED);
+                                             &cache_index, flags);
                if (rchain)
                        break;
 
@@ -4374,13 +4084,15 @@ hammer2_chain_hardlink_find(hammer2_inode_t *dip,
                            parent->bref.type == HAMMER2_BREF_TYPE_INODE) {
                                nloops = 1;
                        }
+                       if (parent->bref.flags & HAMMER2_BREF_FLAG_PFSROOT)
+                               goto done;
+                       if (parent->parent == NULL)
+                               goto done;
                        parent = parent->parent;
-                       if (parent == NULL)
-                               break;
                        hammer2_chain_ref(parent);
                        hammer2_chain_unlock(*parentp);
                        hammer2_chain_lock(parent, HAMMER2_RESOLVE_ALWAYS |
-                                                  HAMMER2_RESOLVE_SHARED);
+                                                  flags);
                        if ((*parentp)->parent == parent) {
                                hammer2_chain_drop(*parentp);
                                *parentp = parent;
@@ -4389,12 +4101,14 @@ hammer2_chain_hardlink_find(hammer2_inode_t *dip,
                                hammer2_chain_drop(parent);
                                hammer2_chain_lock(*parentp,
                                                   HAMMER2_RESOLVE_ALWAYS |
-                                                  HAMMER2_RESOLVE_SHARED);
+                                                  flags);
                                parent = NULL;  /* safety */
+                               /* retry */
                        }
                }
        }
+done:
 
        *chainp = rchain;
-       return (rchain ? EIO : 0);
+       return (rchain ? EINVAL : 0);
 }
index f8f10bd..a634ce3 100644 (file)
@@ -1954,6 +1954,7 @@ hammer2_cluster_snapshot(hammer2_cluster_t *ocluster,
        vat.va_mode = 0755;
        nip = hammer2_inode_create(hmp->spmp->iroot, &vat, proc0.p_ucred,
                                   pmp->name, name_len,
+                                  1, 0, 0,
                                   HAMMER2_INSERT_PFSROOT, &error);
 
        if (nip) {
@@ -2165,390 +2166,3 @@ hammer2_cluster_load_async(hammer2_cluster_t *cluster,
        hammer2_adjreadcounter(&chain->bref, chain->bytes);
        hammer2_io_getblk(hmp, bref->data_off, chain->bytes, iocb);
 }
-
-/*
- * The cluster has been removed from the original directory and replaced
- * with a hardlink pointer.  Move the cluster to the specified parent
- * directory, change the filename to "0xINODENUMBER", and adjust the key.
- * The cluster becomes our invisible hardlink target.
- *
- * The original cluster must be deleted on entry.
- */
-static
-void
-hammer2_cluster_hardlink_shiftup(
-                       hammer2_cluster_t *cluster,
-                       hammer2_inode_t *ip, hammer2_inode_t *dip,
-                       hammer2_cluster_t *dcluster,
-                       int nlinks, int *errorp)
-{
-       hammer2_inode_data_t *nipdata;
-       hammer2_cluster_t *xcluster;
-       hammer2_key_t key_dummy;
-       hammer2_key_t lhc;
-       hammer2_blockref_t bref;
-
-       lhc = ip->meta.inum;
-#if 0
-       iptmp = &hammer2_cluster_rdata(cluster)->ipdata;
-       lhc = iptmp->meta.inum;
-#endif
-       KKASSERT((lhc & HAMMER2_DIRHASH_VISIBLE) == 0);
-
-       /*
-        * Locate the inode or indirect block to create the new
-        * entry in.  lhc represents the inode number so there is
-        * no collision iteration.
-        *
-        * There should be no key collisions with invisible inode keys.
-        *
-        * WARNING! Must use inode_lock_ex() on dip to handle a stale
-        *          dip->cluster cache.
-        */
-       *errorp = 0;
-       xcluster = hammer2_cluster_lookup(dcluster, &key_dummy,
-                                     lhc, lhc, 0);
-       if (xcluster) {
-               kprintf("X3 chain %p dip %p dchain %p dip->chain %p\n",
-                       xcluster->focus, dip, dcluster->focus,
-                       dip->cluster.focus);
-               hammer2_cluster_unlock(xcluster);
-               hammer2_cluster_drop(xcluster);
-               xcluster = NULL;
-               *errorp = ENOSPC;
-#if 0
-               Debugger("X3");
-#endif
-       }
-
-       /*
-        * Handle the error case
-        */
-       if (*errorp) {
-               panic("error2");
-               KKASSERT(xcluster == NULL);
-               return;
-       }
-
-       /*
-        * Use xcluster as a placeholder for (lhc).  Duplicate cluster to the
-        * same target bref as xcluster and then delete xcluster.  The
-        * duplication occurs after xcluster in flush order even though
-        * xcluster is deleted after the duplication. XXX
-        *
-        * WARNING! Duplications (to a different parent) can cause indirect
-        *          blocks to be inserted, refactor xcluster.
-        *
-        * WARNING! Only key and keybits is extracted from a passed-in bref.
-        */
-       hammer2_cluster_bref(cluster, &bref);
-       bref.key = lhc;                 /* invisible dir entry key */
-       bref.keybits = 0;
-       hammer2_cluster_rename(&bref, dcluster, cluster, 0);
-
-       /*
-        * cluster is now 'live' again.. adjust the filename.
-        *
-        * 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.
-        */
-       hammer2_inode_modify(ip);
-       hammer2_cluster_modify(cluster, 0);
-
-       nipdata = &hammer2_cluster_wdata(cluster)->ipdata;
-       ksnprintf(nipdata->filename, sizeof(nipdata->filename),
-                 "0x%016jx", (intmax_t)nipdata->meta.inum);
-       ip->meta.name_len = strlen(nipdata->filename);
-       ip->meta.name_key = lhc;
-       ip->meta.nlinks += nlinks;
-
-       /*
-        * Resync nipdata->meta from the local copy.
-        */
-       nipdata->meta = ip->meta;
-       hammer2_cluster_modsync(cluster);
-}
-
-/*
- * Given an exclusively locked inode and cluster we consolidate the cluster
- * for hardlink creation, adding (nlinks) to the file's link count and
- * potentially relocating the inode to (cdip) which is a parent directory
- * common to both the current location of the inode and the intended new
- * hardlink.
- *
- * Replaces (*clusterp) if consolidation occurred, unlocking the old cluster
- * and returning a new locked cluster.
- *
- * NOTE!  This function will also replace ip->cluster.
- */
-int
-hammer2_cluster_hardlink_consolidate(
-                       hammer2_inode_t *ip,
-                       hammer2_cluster_t **clusterp,
-                       hammer2_inode_t *cdip,
-                       hammer2_cluster_t *cdcluster,
-                       int nlinks)
-{
-       hammer2_cluster_t *cluster;
-       hammer2_cluster_t *cparent;
-       int error;
-
-       cluster = *clusterp;
-       if (nlinks == 0 &&                      /* no hardlink needed */
-           (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
-               return (0);
-       }
-
-       if (hammer2_hardlink_enable == 0) {     /* disallow hardlinks */
-               hammer2_cluster_unlock(cluster);
-               hammer2_cluster_drop(cluster);
-               *clusterp = NULL;
-               return (ENOTSUP);
-       }
-
-       cparent = NULL;
-
-       /*
-        * If no change in the hardlink's target directory is required and
-        * this is already a hardlink target, all we need to do is adjust
-        * the link count.
-        */
-       if (cdip == ip->pip &&
-           (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
-               if (nlinks) {
-                       hammer2_inode_modify(ip);
-                       ip->meta.nlinks += nlinks;
-#if 0
-                       hammer2_cluster_modify(cluster, 0);
-                       wipdata = &hammer2_cluster_wdata(cluster)->ipdata;
-                       wipdata->meta.nlinks += nlinks;
-                       hammer2_cluster_modsync(cluster);
-                       ripdata = wipdata;
-#endif
-               }
-               error = 0;
-               goto done;
-       }
-
-       /*
-        * Cluster is the real inode.  The originating directory is locked
-        * by the caller so we can manipulate it without worrying about races
-        * against other lookups.
-        *
-        * If cluster is visible we need to delete it from the current
-        * location and create a hardlink pointer in its place.  If it is
-        * not visible we need only delete it.  Then later cluster will be
-        * renamed to a parent directory and converted (if necessary) to
-        * a hidden inode (via shiftup).
-        *
-        * NOTE! We must hold cparent locked through the delete/create/rename
-        *       operation to ensure that other threads block resolving to
-        *       the same hardlink, otherwise the other threads may not see
-        *       the hardlink.
-        */
-       KKASSERT((cluster->focus->flags & HAMMER2_CHAIN_DELETED) == 0);
-       cparent = hammer2_cluster_parent(cluster);
-
-       hammer2_cluster_delete(cparent, cluster, 0);
-
-       KKASSERT(ip->meta.type != HAMMER2_OBJTYPE_HARDLINK);
-       if (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) {
-               const hammer2_inode_data_t *ripdata;
-               hammer2_inode_data_t *wipdata;
-               hammer2_cluster_t *ncluster;
-               hammer2_key_t lhc;
-
-               ncluster = NULL;
-               lhc = cluster->focus->bref.key;
-               error = hammer2_cluster_create(ip->pmp, cparent, &ncluster,
-                                            lhc, 0,
-                                            HAMMER2_BREF_TYPE_INODE,
-                                            HAMMER2_INODE_BYTES,
-                                            0);
-               hammer2_cluster_modify(ncluster, 0);
-               wipdata = &hammer2_cluster_wdata(ncluster)->ipdata;
-
-               /* wipdata->meta.comp_algo = ip->meta.comp_algo; */
-               wipdata->meta.comp_algo = 0;
-               wipdata->meta.check_algo = 0;
-               wipdata->meta.version = HAMMER2_INODE_VERSION_ONE;
-               wipdata->meta.inum = ip->meta.inum;
-               wipdata->meta.target_type = ip->meta.type;
-               wipdata->meta.type = HAMMER2_OBJTYPE_HARDLINK;
-               wipdata->meta.uflags = 0;
-               wipdata->meta.rmajor = 0;
-               wipdata->meta.rminor = 0;
-               wipdata->meta.ctime = 0;
-               wipdata->meta.mtime = 0;
-               wipdata->meta.atime = 0;
-               wipdata->meta.btime = 0;
-               bzero(&wipdata->meta.uid, sizeof(wipdata->meta.uid));
-               bzero(&wipdata->meta.gid, sizeof(wipdata->meta.gid));
-               wipdata->meta.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
-               wipdata->meta.cap_flags = 0;
-               wipdata->meta.mode = 0;
-               wipdata->meta.size = 0;
-               wipdata->meta.nlinks = 1;
-               wipdata->meta.iparent = 0;      /* XXX */
-               wipdata->meta.pfs_type = 0;
-               wipdata->meta.pfs_inum = 0;
-               bzero(&wipdata->meta.pfs_clid, sizeof(wipdata->meta.pfs_clid));
-               bzero(&wipdata->meta.pfs_fsid, sizeof(wipdata->meta.pfs_fsid));
-               wipdata->meta.data_quota = 0;
-               /* wipdata->data_count = 0; */
-               wipdata->meta.inode_quota = 0;
-               /* wipdata->inode_count = 0; */
-               wipdata->meta.attr_tid = 0;
-               wipdata->meta.dirent_tid = 0;
-               bzero(&wipdata->u, sizeof(wipdata->u));
-               ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-               KKASSERT(ip->meta.name_len <= sizeof(wipdata->filename));
-               bcopy(ripdata->filename, wipdata->filename,
-                     ip->meta.name_len);
-               wipdata->meta.name_key = ncluster->focus->bref.key;
-               wipdata->meta.name_len = ip->meta.name_len;
-               /* XXX transaction ids */
-               hammer2_cluster_modsync(ncluster);
-               hammer2_cluster_unlock(ncluster);
-               hammer2_cluster_drop(ncluster);
-       }
-
-       /*
-        * cluster represents the hardlink target and is now flagged deleted.
-        * duplicate it to the parent directory and adjust nlinks.
-        *
-        * WARNING! The shiftup() call can cause ncluster to be moved into
-        *          an indirect block, and our ncluster will wind up pointing
-        *          to the older/original version.
-        */
-       KKASSERT(cluster->focus->flags & HAMMER2_CHAIN_DELETED);
-       hammer2_cluster_hardlink_shiftup(cluster, ip, cdip, cdcluster,
-                                        nlinks, &error);
-
-       if (error == 0)
-               hammer2_inode_repoint(ip, cdip, cluster);
-
-done:
-       /*
-        * Cleanup, cluster/ncluster already dealt with.
-        *
-        * Return the shifted cluster in *clusterp.
-        */
-       if (cparent) {
-               hammer2_cluster_unlock(cparent);
-               hammer2_cluster_drop(cparent);
-       }
-       *clusterp = cluster;
-
-       return (error);
-}
-
-/*
- * If (*ochainp) 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_cluster_hardlink_deconsolidate(
-                              hammer2_inode_t *dip,
-                              hammer2_chain_t **chainp,
-                              hammer2_chain_t **ochainp)
-{
-       if (*ochainp == NULL)
-               return (0);
-       /* XXX */
-       return (0);
-}
-
-/*
- * The caller presents a locked cluster with an obj_type of
- * HAMMER2_OBJTYPE_HARDLINK in (*clusterp).  This routine will locate
- * the inode and replace (*clusterp) with a new locked cluster containing
- * the target hardlink, also locked.  The original cluster will be
- * unlocked and released.
- *
- * If cparentp is not NULL a locked cluster representing the hardlink's
- * parent is also returned.
- *
- * If we are unable to locate the hardlink target EIO is returned,
- * (*cparentp) is set to NULL, the original passed-in (*clusterp)
- * will be unlocked and released and (*clusterp) will be set to NULL
- * as well.
- */
-int
-hammer2_cluster_hardlink_find(hammer2_inode_t *dip,
-                     hammer2_cluster_t **cparentp,
-                     hammer2_cluster_t **clusterp)
-{
-       const hammer2_inode_data_t *ipdata;
-       hammer2_cluster_t *cluster;
-       hammer2_cluster_t *cparent;
-       hammer2_cluster_t *rcluster;
-       hammer2_inode_t *ip;
-       hammer2_inode_t *pip;
-       hammer2_key_t key_dummy;
-       hammer2_key_t lhc;
-
-       cluster = *clusterp;
-       pip = dip;
-       hammer2_inode_ref(pip);         /* for loop */
-
-       /*
-        * Locate the hardlink.  pip is referenced and not locked.
-        * Unlock and release (*clusterp) after extracting the needed
-        * data.
-        */
-       ipdata = &hammer2_cluster_rdata(cluster)->ipdata;
-       lhc = ipdata->meta.inum;
-       ipdata = NULL;                  /* safety */
-       hammer2_cluster_unlock(cluster);
-       hammer2_cluster_drop(cluster);
-       *clusterp = NULL;               /* safety */
-
-       rcluster = NULL;
-       cparent = NULL;
-
-       while ((ip = pip) != NULL) {
-               hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
-               cparent = hammer2_inode_cluster(ip, HAMMER2_RESOLVE_ALWAYS);
-               hammer2_inode_drop(ip);                 /* loop */
-               KKASSERT(hammer2_cluster_type(cparent) ==
-                        HAMMER2_BREF_TYPE_INODE);
-               rcluster = hammer2_cluster_lookup(cparent, &key_dummy,
-                                            lhc, lhc, 0);
-               if (rcluster)
-                       break;
-               hammer2_cluster_lookup_done(cparent);   /* discard parent */
-               cparent = NULL;                         /* safety */
-               pip = ip->pip;          /* safe, ip held locked */
-               if (pip)
-                       hammer2_inode_ref(pip);         /* loop */
-               hammer2_inode_unlock(ip, NULL);
-       }
-
-       /*
-        * chain is locked, ip is locked.  Unlock ip, return the locked
-        * chain.  *ipp is already set w/a ref count and not locked.
-        *
-        * (cparent is already unlocked).
-        */
-       *clusterp = rcluster;
-       if (rcluster) {
-               if (cparentp) {
-                       *cparentp = cparent;
-                       hammer2_inode_unlock(ip, NULL);
-               } else {
-                       hammer2_inode_unlock(ip, cparent);
-               }
-               return (0);
-       } else {
-               if (cparentp)
-                       *cparentp = NULL;
-               if (ip)
-                       hammer2_inode_unlock(ip, cparent);
-               return (EIO);
-       }
-}
index ee577a0..1202ba1 100644 (file)
 
 #define INODE_DEBUG    0
 
-static void hammer2_inode_move_to_hidden(hammer2_cluster_t **cparentp,
-                                        hammer2_cluster_t **clusterp,
-                                        hammer2_tid_t inum);
-
 RB_GENERATE2(hammer2_inode_tree, hammer2_inode, rbnode, hammer2_inode_cmp,
             hammer2_tid_t, meta.inum);
 
@@ -620,6 +616,7 @@ hammer2_inode_t *
 hammer2_inode_create(hammer2_inode_t *dip,
                     struct vattr *vap, struct ucred *cred,
                     const uint8_t *name, size_t name_len,
+                    hammer2_key_t inum, uint8_t type, uint8_t target_type,
                     int flags, int *errorp)
 {
        hammer2_xop_scanlhc_t *sxop;
@@ -694,7 +691,6 @@ hammer2_inode_create(hammer2_inode_t *dip,
 
        if (vap) {
                xop->meta.type = hammer2_get_obj_type(vap->va_type);
-               xop->meta.inum = hammer2_trans_newinum(dip->pmp);
 
                switch (xop->meta.type) {
                case HAMMER2_OBJTYPE_CDEV:
@@ -705,10 +701,12 @@ hammer2_inode_create(hammer2_inode_t *dip,
                default:
                        break;
                }
+               type = xop->meta.type;
        } else {
-               xop->meta.type = HAMMER2_OBJTYPE_DIRECTORY;
-               xop->meta.inum = 1;
+               xop->meta.type = type;
+               xop->meta.target_type = target_type;
        }
+       xop->meta.inum = inum;
        
        /* Inherit parent's inode compression mode. */
        xop->meta.comp_algo = dip_comp_algo;
@@ -752,13 +750,11 @@ hammer2_inode_create(hammer2_inode_t *dip,
         * the size is extended past the embedded limit.
         */
        if (xop->meta.type == HAMMER2_OBJTYPE_REGFILE ||
-           xop->meta.type == HAMMER2_OBJTYPE_SOFTLINK) {
+           xop->meta.type == HAMMER2_OBJTYPE_SOFTLINK ||
+           xop->meta.type == HAMMER2_OBJTYPE_HARDLINK) {
                xop->meta.op_flags |= HAMMER2_OPFLAG_DIRECTDATA;
        }
-
-       xop->head.name = kmalloc(name_len + 1, M_HAMMER2, M_WAITOK | M_ZERO);
-       xop->head.name_len = name_len;
-       bcopy(name, xop->head.name, name_len);
+       hammer2_xop_setname(&xop->head, name, name_len);
        xop->meta.name_len = name_len;
        xop->meta.name_key = lhc;
        KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
@@ -777,7 +773,7 @@ hammer2_inode_create(hammer2_inode_t *dip,
        }
 
        /*
-        * Set up the new inode.
+        * Set up the new inode if not a hardlink pointer.
         *
         * NOTE: *_get() integrates chain's lock into the inode lock.
         *
@@ -787,8 +783,12 @@ hammer2_inode_create(hammer2_inode_t *dip,
         *
         * NOTE: nipdata will have chain's blockset data.
         */
-       nip = hammer2_inode_get(dip->pmp, dip, &xop->head.cluster);
-       nip->comp_heuristic = 0;
+       if (type != HAMMER2_OBJTYPE_HARDLINK) {
+               nip = hammer2_inode_get(dip->pmp, dip, &xop->head.cluster);
+               nip->comp_heuristic = 0;
+       } else {
+               nip = NULL;
+       }
 
 done:
        hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
@@ -799,201 +799,90 @@ done2:
 }
 
 /*
- * Connect the target inode represented by (cluster) to the media topology
- * at (dip, name, len).  The caller can pass a rough *chainp, this function
- * will issue lookup()s to position the parent chain properly for the
- * chain insertion.
+ * Connect the disconnected inode (ip) to the directory (dip) with the
+ * specified (name, name_len).  If name is NULL, (lhc) will be used as
+ * the directory key and the inode's embedded name will not be modified
+ * for future recovery purposes.
  *
- * If hlink is TRUE this function creates an OBJTYPE_HARDLINK directory
- * entry instead of connecting (cluster).
- *
- * If hlink is FALSE this function expects (cluster) to be unparented.
+ * dip and ip must both be locked exclusively (dip in particular to avoid
+ * lhc collisions).
  */
 int
-hammer2_inode_connect(hammer2_inode_t *ip, hammer2_cluster_t **clusterp,
-                     int hlink,
-                     hammer2_inode_t *dip, hammer2_cluster_t *dcluster,
-                     const uint8_t *name, size_t name_len,
-                     hammer2_key_t lhc)
+hammer2_inode_connect_simple(hammer2_inode_t *dip, hammer2_inode_t *ip,
+                            const char *name, size_t name_len,
+                            hammer2_key_t lhc)
 {
-       hammer2_inode_data_t *wipdata;
-       hammer2_cluster_t *ocluster;
-       hammer2_cluster_t *ncluster;
-       hammer2_pfs_t *pmp;
-       hammer2_key_t key_dummy;
+       hammer2_xop_scanlhc_t *sxop;
+       hammer2_xop_connect_t *xop;
+       hammer2_inode_t *opip;
+       hammer2_key_t lhcbase;
        int error;
 
        /*
-        * Since ocluster is either disconnected from the topology or
-        * represents a hardlink terminus which is always a parent of or
-        * equal to dip, we should be able to safely lock dip->chain for
-        * our setup.
-        *
-        * WARNING! Must use inode_lock_ex() on dip to handle a stale
-        *          dip->cluster.
-        *
-        * If name is non-NULL we calculate lhc, else we use the passed-in
-        * lhc.
+        * Calculate the lhc and resolve the collision space.
         */
-       ocluster = *clusterp;
-       pmp = dip->pmp;
-
        if (name) {
-               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.
-                */
-               error = 0;
-               while (error == 0) {
-                       ncluster = hammer2_cluster_lookup(dcluster, &key_dummy,
-                                                     lhc, lhc, 0);
-                       if (ncluster == NULL)
+               lhc = lhcbase = hammer2_dirhash(name, name_len);
+               sxop = &hammer2_xop_alloc(dip)->xop_scanlhc;
+               sxop->lhc = lhc;
+               hammer2_xop_start(&sxop->head, hammer2_inode_xop_scanlhc);
+               while ((error = hammer2_xop_collect(&sxop->head, 0)) == 0) {
+                       if (lhc != sxop->head.cluster.focus->bref.key)
                                break;
-                       if ((lhc & HAMMER2_DIRHASH_LOMASK) ==
-                           HAMMER2_DIRHASH_LOMASK) {
-                               error = ENOSPC;
-                       }
-                       hammer2_cluster_unlock(ncluster);
-                       hammer2_cluster_drop(ncluster);
-                       ncluster = NULL;
                        ++lhc;
                }
-       } else {
-               /*
-                * Reconnect to specific key (used when moving
-                * unlinked-but-open files into the hidden directory).
-                */
-               ncluster = hammer2_cluster_lookup(dcluster, &key_dummy,
-                                                 lhc, lhc, 0);
-               KKASSERT(ncluster == NULL);
-               error = 0;
-       }
+               hammer2_xop_retire(&sxop->head, HAMMER2_XOPMASK_VOP);
 
-       if (error == 0) {
-               if (hlink) {
-                       /*
-                        * Hardlink pointer needed, create totally fresh
-                        * directory entry.
-                        *
-                        * We must refactor ocluster because it might have
-                        * been shifted into an indirect cluster by the
-                        * create.
-                        */
-                       KKASSERT(ncluster == NULL);
-                       error = hammer2_cluster_create(pmp,
-                                                      dcluster, &ncluster,
-                                                      lhc, 0,
-                                                      HAMMER2_BREF_TYPE_INODE,
-                                                      HAMMER2_INODE_BYTES,
-                                                      0);
-               } else {
-                       /*
-                        * Reconnect the original cluster under the new name.
-                        * Original cluster must have already been deleted by
-                        * teh caller.
-                        *
-                        * WARNING! Can cause held-over clusters to require a
-                        *          refactor.  Fortunately we have none (our
-                        *          locked clusters are passed into and
-                        *          modified by the call).
-                        */
-                       ncluster = ocluster;
-                       ocluster = NULL;
-                       error = hammer2_cluster_create(pmp, dcluster, &ncluster,
-                                                      lhc, 0,
-                                                      HAMMER2_BREF_TYPE_INODE,
-                                                      HAMMER2_INODE_BYTES,
-                                                      0);
+               if (error) {
+                       if (error != ENOENT)
+                               goto done;
+                       ++lhc;
+                       error = 0;
                }
+               if ((lhcbase ^ lhc) & ~HAMMER2_DIRHASH_LOMASK) {
+                       error = ENOSPC;
+                       goto done;
+               }
+       } else {
+               error = 0;
        }
 
        /*
-        * Unlock stuff.
+        * Formally reconnect the in-memory structure.  ip must
+        * be locked exclusively to safely change ip->pip.
         */
-       KKASSERT(error != EAGAIN);
+       if (ip->pip != dip) {
+               hammer2_inode_ref(dip);
+               opip = ip->pip;
+               ip->pip = dip;
+               if (opip)
+                       hammer2_inode_drop(opip);
+       }
 
        /*
-        * ncluster should be NULL on error, leave ocluster
-        * (ocluster == *clusterp) alone.
+        * Connect her up
         */
-       if (error) {
-               KKASSERT(ncluster == NULL);
-               return (error);
-       }
+       xop = &hammer2_xop_alloc(dip)->xop_connect;
+       if (name)
+               hammer2_xop_setname(&xop->head, name, name_len);
+       hammer2_xop_setip2(&xop->head, ip);
+       xop->lhc = lhc;
+       hammer2_xop_start(&xop->head, hammer2_inode_xop_connect);
+       error = hammer2_xop_collect(&xop->head, 0);
+       hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
 
        /*
-        * 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
-        * cluster, the caller will access the hardlink via the actual hardlink
-        * target file and not the hardlink pointer entry, so we must still
-        * return ocluster.
+        * On success make the same adjustments to ip->meta or the
+        * next flush may blow up the chain.
         */
-       if (hlink && hammer2_hardlink_enable >= 0) {
-               /*
-                * Create the HARDLINK pointer.  oip represents the hardlink
-                * target in this situation.
-                *
-                * We will return ocluster (the hardlink target).
-                */
-               hammer2_cluster_modify(ncluster, 0);
-               KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
-               wipdata = &hammer2_cluster_wdata(ncluster)->ipdata;
-               bcopy(name, wipdata->filename, name_len);
-               wipdata->meta.name_key = lhc;
-               wipdata->meta.name_len = name_len;
-               wipdata->meta.target_type =
-                           hammer2_cluster_rdata(ocluster)->ipdata.meta.type;
-               wipdata->meta.type = HAMMER2_OBJTYPE_HARDLINK;
-               wipdata->meta.inum =
-                           hammer2_cluster_rdata(ocluster)->ipdata.meta.inum;
-               wipdata->meta.version = HAMMER2_INODE_VERSION_ONE;
-               wipdata->meta.nlinks = 1;
-               wipdata->meta.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
-               hammer2_cluster_modsync(ncluster);
-               hammer2_cluster_unlock(ncluster);
-               hammer2_cluster_drop(ncluster);
-               ncluster = ocluster;
-               ocluster = NULL;
-       } else {
-               /*
-                * ncluster is a duplicate of ocluster at the new location.
-                * We must fixup the name stored in the inode data.
-                * The bref key has already been adjusted by inode_connect().
-                */
+       if (error == 0) {
                hammer2_inode_modify(ip);
-               hammer2_cluster_modify(ncluster, 0);
-               wipdata = &hammer2_cluster_wdata(ncluster)->ipdata;
-
-               KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
-               bcopy(name, wipdata->filename, name_len);
                ip->meta.name_key = lhc;
-               ip->meta.name_len = name_len;
-               ip->meta.nlinks = 1;
-
-               /*
-                * Resync wipdata->meta from the local copy.
-                */
-               wipdata->meta = ip->meta;
-               hammer2_cluster_modsync(ncluster);
-       }
-
-       /*
-        * We are replacing ocluster with ncluster, unlock ocluster.  In the
-        * case where ocluster is left unchanged the code above sets
-        * ncluster to ocluster and ocluster to NULL, resulting in a NOP here.
-        */
-       if (ocluster) {
-               hammer2_cluster_unlock(ocluster);
-               hammer2_cluster_drop(ocluster);
+               if (name)
+                       ip->meta.name_len = name_len;
        }
-       *clusterp = ncluster;
-
-       return (0);
+done:
+       return error;
 }
 
 /*
@@ -1156,311 +1045,73 @@ hammer2_inode_repoint_one(hammer2_inode_t *ip, hammer2_cluster_t *cluster,
 }
 
 /*
- * Unlink the file (dip, name, name_len), from the specified directory inode.
- * If the caller also has the hammer2_inode the caller must pass it locked as
- * (ip), but may pass NULL if it does not have the inode in hand.
- *
- * The directory inode does not need to be locked.
- *
- * isdir determines whether a directory/non-directory check should be made.
- * No check is made if isdir is set to -1.
- *
- * isopen specifies whether special unlink-with-open-descriptor handling
- * must be performed.  If set to -1 the caller is deleting a PFS and we
- * check whether the chain is mounted or not (chain->pmp != NULL).  1 is
- * implied if it is mounted.
+ * Called with a locked inode to finish unlinking an inode after xop_unlink
+ * had been run.  This function is responsible for decrementing nlinks and
+ * moving deleted inodes to the hidden directory if they are still open.
  *
- * If isopen is 1 and nlinks drops to 0 this function must move the chain
- * to a special hidden directory until last-close occurs on the file.
+ * We don't bother decrementing nlinks if the file is not open and this was
+ * the last link.
  *
- * NOTE!  The underlying file can still be active with open descriptors
- *       or if the inode is being manually held (e.g. for rename).
+ * If the inode is a hardlink target it's chain has not yet been deleted,
+ * otherwise it's chain has been deleted.
  *
- * NOTE!  When unlinking an open file the inode will be temporarily moved to
- *       a hidden directory, otherwise the inode will be deleted.
+ * If isopen then any prior deletion was not permanent and the inode must
+ * be moved to the hidden directory.
  */
 int
-hammer2_unlink_file(hammer2_inode_t *dip, hammer2_inode_t *ip,
-                   const uint8_t *name, size_t name_len,
-                   int isdir, int *hlinkp,
-                   int isopen, int isrename)
+hammer2_inode_unlink_finisher(hammer2_inode_t *ip, int isopen)
 {
-       const hammer2_inode_data_t *ripdata;
-       hammer2_cluster_t *cparent;
-       hammer2_cluster_t *hcluster;
-       hammer2_cluster_t *hparent;
-       hammer2_cluster_t *cluster;
-       hammer2_cluster_t *dparent;
-       hammer2_cluster_t *dcluster;
-       hammer2_key_t key_dummy;
-       hammer2_key_t key_next;
-       hammer2_key_t lhc;
-       int last_link;
+       hammer2_pfs_t *pmp;
        int error;
-       int hlink;
-       int myip;
-       uint8_t type;
-
-       error = 0;
-       hlink = 0;
-       myip = 0;
-       hcluster = NULL;
-       hparent = NULL;
-       lhc = hammer2_dirhash(name, name_len);
 
-again:
-       /*
-        * Locate the filename in the directory and instantiate the ip
-        * if necessary.  If the ip is already known we must still locate
-        * the filename to adjust cparent for possible deletion.
-        */
-       hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
-       cparent = hammer2_inode_cluster(dip, HAMMER2_RESOLVE_ALWAYS);
-       cluster = hammer2_cluster_lookup(cparent, &key_next,
-                                    lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0);
-       while (cluster) {
-               if (hammer2_cluster_type(cluster) == HAMMER2_BREF_TYPE_INODE) {
-                       ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-                       if (ripdata->meta.name_len == name_len &&
-                           bcmp(ripdata->filename, name, name_len) == 0) {
-                               break;
-                       }
-               }
-               cluster = hammer2_cluster_next(cparent, cluster, &key_next,
-                                              key_next,
-                                              lhc + HAMMER2_DIRHASH_LOMASK,
-                                              0);
-       }
-       hammer2_inode_unlock(dip, NULL);        /* retain cparent */
+       pmp = ip->pmp;
 
        /*
-        * Not found or wrong type (isdir < 0 disables the type check).
-        * If a hardlink pointer, type checks use the hardlink target.
+        * Decrement nlinks.  If this is the last link and the file is
+        * not open, the chain has already been removed and we don't bother
+        * dirtying the inode.
         */
-       if (cluster == NULL) {
-               error = ENOENT;
-               goto done;
+       if (ip->meta.nlinks == 1) {
+               atomic_set_int(&ip->flags, HAMMER2_INODE_ISUNLINKED);
+               if (isopen == 0)
+                       return 0;
        }
 
-       ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-       type = ripdata->meta.type;
-       if (type == HAMMER2_OBJTYPE_HARDLINK) {
-               hlink = 1;
-               type = ripdata->meta.target_type;
-       }
-
-       if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) {
-               error = ENOTDIR;
-               goto done;
-       }
-       if (type != HAMMER2_OBJTYPE_DIRECTORY && isdir >= 1) {
-               error = EISDIR;
-               goto done;
-       }
+       hammer2_inode_modify(ip);
+       --ip->meta.nlinks;
+       if ((int64_t)ip->meta.nlinks < 0)
+               ip->meta.nlinks = 0;    /* safety */
 
        /*
-        * Hardlink must be resolved.  We can't hold the parent locked
-        * while we do this or we could deadlock.  The physical file will
-        * be located at or above the current directory.
-        *
-        * We loop to reacquire the hardlink origination.
-        *
-        * NOTE: hammer2_hardlink_find() will locate the hardlink target,
-        *       returning a modified hparent and hcluster.
+        * If nlinks is not zero we are done.  However, this should only be
+        * possible with a hardlink target.  If the inode is an embedded
+        * hardlink nlinks should have dropped to zero, warn and proceed
+        * with the next step.
         */
-       if (ripdata->meta.type == HAMMER2_OBJTYPE_HARDLINK) {
-               if (hcluster == NULL) {
-                       hcluster = cluster;
-                       cluster = NULL; /* safety */
-                       hammer2_cluster_unlock(cparent);
-                       hammer2_cluster_drop(cparent);
-                       cparent = NULL; /* safety */
-                       ripdata = NULL; /* safety (associated w/cparent) */
-                       error = hammer2_cluster_hardlink_find(dip, &hparent, &hcluster);
-
-                       /*
-                        * If we couldn't find the hardlink target then some
-                        * parent directory containing the hardlink pointer
-                        * probably got renamed to above the original target,
-                        * a case not yet handled by H2.
-                        */
-                       if (error) {
-                               kprintf("H2 unlink_file: hardlink target for "
-                                       "\"%s\" not found\n",
-                                       name);
-                               kprintf("(likely due to known directory "
-                                       "rename bug)\n");
-                               goto done;
-                       }
-                       goto again;
-               }
+       if (ip->meta.nlinks) {
+               if ((ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) == 0)
+                       return 0;
+               kprintf("hammer2_inode_unlink: nlinks was not 0 (%jd)\n",
+                       (intmax_t)ip->meta.nlinks);
+               return 0;
        }
 
        /*
-        * 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, and if isdir > 1 we are deleting a PFS/snapshot
-        * and the directory does not have to be empty.
+        * nlinks is now zero, the inode should have already been deleted.
+        * If the file is open it was deleted non-permanently and must be
+        * moved to the hidden directory.
         *
-        * NOTE: We check the full key range here which covers both visible
-        *       and invisible entries.  Theoretically there should be no
-        *       invisible (hardlink target) entries if there are no visible
-        *       entries.
+        * When moving to the hidden directory we force the name_key to the
+        * inode number to avoid collisions.
         */
-       if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) {
-               dparent = hammer2_cluster_lookup_init(cluster, 0);
-               dcluster = hammer2_cluster_lookup(dparent, &key_dummy,
-                                                 0, (hammer2_key_t)-1,
-                                                 HAMMER2_LOOKUP_NODATA);
-               if (dcluster) {
-                       hammer2_cluster_unlock(dcluster);
-                       hammer2_cluster_drop(dcluster);
-                       hammer2_cluster_lookup_done(dparent);
-                       error = ENOTEMPTY;
-                       goto done;
-               }
-               hammer2_cluster_lookup_done(dparent);
-               dparent = NULL;
-               /* dcluster NULL */
-       }
-
-       /*
-        * If this was a hardlink then (cparent, cluster) is the hardlink
-        * pointer, which we can simply destroy outright.  Discard the
-        * clusters and replace with the hardlink target.
-        */
-       if (hcluster) {
-               hammer2_cluster_delete(cparent, cluster,
-                                      HAMMER2_DELETE_PERMANENT);
-               hammer2_cluster_unlock(cparent);
-               hammer2_cluster_drop(cparent);
-               hammer2_cluster_unlock(cluster);
-               hammer2_cluster_drop(cluster);
-               cparent = hparent;
-               cluster = hcluster;
-               hparent = NULL;
-               hcluster = NULL;
-       }
-
-       /*
-        * This leaves us with the hardlink target or non-hardlinked file
-        * or directory in (cparent, cluster).
-        *
-        * Delete the target when nlinks reaches 0 with special handling
-        * to avoid I/O (to avoid actually updating the inode) for the 1->0
-        * transition, if possible.  This optimization makes rm -rf very
-        * fast.
-        *
-        * NOTE! In DragonFly the vnops function calls cache_unlink() after
-        *       calling us here to clean out the namecache association,
-        *       (which does not represent a ref for the open-test), and to
-        *       force finalization of the vnode if/when the last ref gets
-        *       dropped.
-        */
-       KKASSERT(cluster != NULL);
-
-       /*
-        * Instantiate ip if necessary for ip->meta data consolidation.
-        */
-       if (ip == NULL) {
-               ip = hammer2_inode_get(dip->pmp, dip, cluster);
-               myip = 1;
-       }
-
-
-       /*
-        * Note: nlinks is negative when decrementing, positive when
-        *       incrementing.
-        */
-       last_link = (ip->meta.nlinks - (isrename ? 0 : 1) == 0);
-
-       if (last_link) {
-               /*
-                * Target nlinks has reached 0, file now unlinked (but may
-                * still be open).
-                *
-                * On the last link when not renaming, we have to special
-                * case the file if it is still open to prevent the bulk
-                * free from freeing blocks out from under it.
-                */
-               if (isrename == 0)
-                       atomic_set_int(&ip->flags, HAMMER2_INODE_ISUNLINKED);
-
-               if (isrename == 0 && isopen) {
-                       /*
-                        * If an unlinked file is still open we must update
-                        * the inodes link count.
-                        */
-                       /*hammer2_cluster_modify(cluster, 0);*/
-                       hammer2_inode_modify(ip);
-                       if (isrename == 0)
-                               --ip->meta.nlinks;
-                       if ((int64_t)ip->meta.nlinks < 0)       /* safety */
-                               ip->meta.nlinks = 0;
-                       hammer2_inode_move_to_hidden(&cparent, &cluster,
-                                                    ip->meta.inum);
-                       /* hammer2_cluster_modsync(cluster); */
-               } else {
-                       /*
-                        * This won't get everything if a vnode is still
-                        * present, but the cache_unlink() call the caller
-                        * makes will.
-                        */
-                       hammer2_cluster_delete(cparent, cluster,
-                                              HAMMER2_DELETE_PERMANENT);
-               }
-       } else if (hlink == 0) {
-               /*
-                * In this situation a normal non-hardlinked file (which can
-                * only have nlinks == 1) still has a non-zero nlinks, the
-                * caller must be doing a RENAME operation, and only wishes
-                * to remove file in order to be able to reconnect it under
-                * a different name.
-                *
-                * In this situation we do a temporary deletion of the
-                * chain in order to allow the file to be reconnected in
-                * a different location.
-                */
-               KKASSERT(isrename != 0);
-               hammer2_cluster_delete(cparent, cluster, 0);
+       if (isopen) {
+               hammer2_inode_lock(pmp->ihidden, HAMMER2_RESOLVE_ALWAYS);
+               error = hammer2_inode_connect_simple(pmp->ihidden, ip,
+                                                    NULL, 0, ip->meta.inum);
+               hammer2_inode_unlock(pmp->ihidden, NULL);
        } else {
-               /*
-                * Links remain, must update the inode link count.
-                */
-               /*hammer2_cluster_modify(cluster, 0);*/
-               hammer2_inode_modify(ip);
-               if (isrename == 0)
-                       --ip->meta.nlinks;
-               if ((int64_t)ip->meta.nlinks < 0)
-                       ip->meta.nlinks = 0;
-               /* hammer2_cluster_modsync(cluster); */
-       }
-
-       if (myip) {
-               hammer2_inode_unlock(ip, NULL);
-       }
-
-       error = 0;
-done:
-       if (cparent) {
-               hammer2_cluster_unlock(cparent);
-               hammer2_cluster_drop(cparent);
-       }
-       if (cluster) {
-               hammer2_cluster_unlock(cluster);
-               hammer2_cluster_drop(cluster);
-       }
-       if (hparent) {
-               hammer2_cluster_unlock(hparent);
-               hammer2_cluster_drop(hparent);
-       }
-       if (hcluster) {
-               hammer2_cluster_unlock(hcluster);
-               hammer2_cluster_drop(hcluster);
+               error = 0;
        }
-       if (hlinkp)
-               *hlinkp = hlink;
-
        return error;
 }
 
@@ -1571,6 +1222,7 @@ hammer2_inode_install_hidden(hammer2_pfs_t *pmp)
        hammer2_trans_done(pmp);
 }
 
+#if 0
 /*
  * If an open file is unlinked H2 needs to retain the file in the topology
  * to ensure that its backing store is not recovered by the bulk free scan.
@@ -1602,6 +1254,7 @@ hammer2_inode_move_to_hidden(hammer2_cluster_t **cparentp,
        hammer2_inode_unlock(pmp->ihidden, dcluster);
        KKASSERT(error == 0);
 }
+#endif
 
 /*
  * Find the directory common to both fdip and tdip.
@@ -1773,7 +1426,10 @@ hammer2_inode_fsync(hammer2_inode_t *ip, hammer2_cluster_t *cparent)
 }
 
 /*
- * Inode create helper (threaded, backend).
+ * Inode create helper (threaded, backend)
+ *
+ * Used by ncreate, nmknod, nsymlink, nmkdir.
+ * Used by nlink and rename to create HARDLINK pointers.
  *
  * Frontend holds the parent directory ip locked exclusively.  We
  * create the inode and feed the exclusively locked chain to the
@@ -1831,10 +1487,88 @@ fail:
 }
 
 /*
- * Inode delete helper
+ * Inode delete helper (backend, threaded)
  */
 void
 hammer2_inode_xop_destroy(hammer2_xop_t *arg, int clindex)
 {
        /*hammer2_xop_inode_t *xop = &arg->xop_inode;*/
 }
+
+void
+hammer2_inode_xop_connect(hammer2_xop_t *arg, int clindex)
+{
+       hammer2_xop_connect_t *xop = &arg->xop_connect;
+       hammer2_inode_data_t *wipdata;
+       hammer2_chain_t *parent;
+       hammer2_chain_t *chain;
+       hammer2_pfs_t *pmp;
+       hammer2_key_t key_dummy;
+       int cache_index = -1;
+       int error;
+
+       /*
+        * Get directory, then issue a lookup to prime the parent chain
+        * for the create.  The lookup is expected to fail.
+        */
+       pmp = xop->head.ip->pmp;
+       parent = hammer2_inode_chain(xop->head.ip, clindex,
+                                    HAMMER2_RESOLVE_ALWAYS);
+       if (parent == NULL) {
+               chain = NULL;
+               error = EIO;
+               goto fail;
+       }
+       chain = hammer2_chain_lookup(&parent, &key_dummy,
+                                    xop->lhc, xop->lhc,
+                                    &cache_index, 0);
+       if (chain) {
+               hammer2_chain_unlock(chain);
+               hammer2_chain_drop(chain);
+               chain = NULL;
+               error = EEXIST;
+               goto fail;
+       }
+
+       /*
+        * Adjust the filename in the inode, set the name key.
+        *
+        * NOTE: Frontend must also adjust ip2->meta on success, we can't
+        *       do it here.
+        */
+       chain = hammer2_inode_chain(xop->head.ip2, clindex,
+                                   HAMMER2_RESOLVE_ALWAYS);
+       hammer2_chain_modify(chain, 0);
+       wipdata = &chain->data->ipdata;
+
+       hammer2_inode_modify(xop->head.ip2);
+       if (xop->head.name) {
+               bzero(wipdata->filename, sizeof(wipdata->filename));
+               bcopy(xop->head.name, wipdata->filename, xop->head.name_len);
+               wipdata->meta.name_len = xop->head.name_len;
+       }
+       wipdata->meta.name_key = xop->lhc;
+
+       /*
+        * Reconnect the chain to the new parent directory
+        */
+       error = hammer2_chain_create(&parent, &chain, pmp,
+                                    xop->lhc, 0,
+                                    HAMMER2_BREF_TYPE_INODE,
+                                    HAMMER2_INODE_BYTES,
+                                    0);
+
+       /*
+        * Feed result back.
+        */
+fail:
+       hammer2_xop_feed(&xop->head, NULL, clindex, error);
+       if (parent) {
+               hammer2_chain_unlock(parent);
+               hammer2_chain_drop(parent);
+       }
+       if (chain) {
+               hammer2_chain_unlock(chain);
+               hammer2_chain_drop(chain);
+       }
+}
index baef345..c50b1ba 100644 (file)
@@ -554,6 +554,7 @@ hammer2_ioctl_pfs_create(hammer2_inode_t *ip, void *data)
        hammer2_trans_init(hmp->spmp, 0);
        nip = hammer2_inode_create(hmp->spmp->iroot, NULL, NULL,
                                   pfs->name, strlen(pfs->name),
+                                  1, HAMMER2_OBJTYPE_DIRECTORY, 0,
                                   HAMMER2_INSERT_PFSROOT, &error);
        if (error == 0) {
                hammer2_inode_modify(nip);
@@ -607,16 +608,30 @@ hammer2_ioctl_pfs_create(hammer2_inode_t *ip, void *data)
 static int
 hammer2_ioctl_pfs_delete(hammer2_inode_t *ip, void *data)
 {
-       hammer2_dev_t *hmp;
        hammer2_ioc_pfs_t *pfs = data;
+       hammer2_pfs_t *spmp;
+       hammer2_xop_unlink_t *xop;
+       hammer2_inode_t *dip;
        int error;
 
-       hmp = ip->pmp->iroot->cluster.focus->hmp; /* XXX */
-       hammer2_trans_init(hmp->spmp, 0);
-       error = hammer2_unlink_file(hmp->spmp->iroot, NULL,
-                                   pfs->name, strlen(pfs->name),
-                                   2, NULL, 0, 0);
-       hammer2_trans_done(hmp->spmp);
+       pfs->name[sizeof(pfs->name) - 1] = 0;   /* ensure termination */
+
+       /* XXX */
+       spmp = ip->pmp->iroot->cluster.focus->hmp->spmp;
+       dip = spmp->iroot;
+       hammer2_trans_init(spmp, 0);
+       hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
+
+       xop = &hammer2_xop_alloc(dip)->xop_unlink;
+       hammer2_xop_setname(&xop->head, pfs->name, strlen(pfs->name));
+       xop->isdir = 2;
+       xop->dopermanent = 1;
+       hammer2_xop_start(&xop->head, hammer2_xop_unlink);
+
+       error = hammer2_xop_collect(&xop->head, 0);
+
+       hammer2_inode_unlock(dip, NULL);
+       hammer2_trans_done(spmp);
 
        return (error);
 }
index 95886d0..4f0f750 100644 (file)
@@ -985,6 +985,37 @@ hammer2_xop_alloc(hammer2_inode_t *ip)
        return xop;
 }
 
+void
+hammer2_xop_setname(hammer2_xop_head_t *xop, const char *name, size_t name_len)
+{
+       xop->name = kmalloc(name_len + 1, M_HAMMER2, M_WAITOK | M_ZERO);
+       xop->name_len = name_len;
+       bcopy(name, xop->name, name_len);
+}
+
+void
+hammer2_xop_setname2(hammer2_xop_head_t *xop, const char *name, size_t name_len)
+{
+       xop->name2 = kmalloc(name_len + 1, M_HAMMER2, M_WAITOK | M_ZERO);
+       xop->name2_len = name_len;
+       bcopy(name, xop->name2, name_len);
+}
+
+
+void
+hammer2_xop_setip2(hammer2_xop_head_t *xop, hammer2_inode_t *ip2)
+{
+       xop->ip2 = ip2;
+       hammer2_inode_ref(ip2);
+}
+
+void
+hammer2_xop_setip3(hammer2_xop_head_t *xop, hammer2_inode_t *ip3)
+{
+       xop->ip3 = ip3;
+       hammer2_inode_ref(ip3);
+}
+
 void
 hammer2_xop_reinit(hammer2_xop_head_t *xop)
 {
@@ -1136,11 +1167,24 @@ hammer2_xop_retire(hammer2_xop_head_t *xop, uint32_t mask)
                hammer2_inode_drop(xop->ip);
                xop->ip = NULL;
        }
+       if (xop->ip2) {
+               hammer2_inode_drop(xop->ip2);
+               xop->ip2 = NULL;
+       }
+       if (xop->ip3) {
+               hammer2_inode_drop(xop->ip3);
+               xop->ip3 = NULL;
+       }
        if (xop->name) {
                kfree(xop->name, M_HAMMER2);
                xop->name = NULL;
                xop->name_len = 0;
        }
+       if (xop->name2) {
+               kfree(xop->name2, M_HAMMER2);
+               xop->name2 = NULL;
+               xop->name2_len = 0;
+       }
 
        objcache_put(cache_xops, xop);
 }
index 5e8e704..eacd146 100644 (file)
@@ -1074,10 +1074,7 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
        xop = &hammer2_xop_alloc(dip)->xop_nresolve;
 
        ncp = ap->a_nch->ncp;
-       xop->head.name = kmalloc(ncp->nc_nlen + 1, M_HAMMER2,
-                                M_WAITOK | M_ZERO);
-       xop->head.name_len = ncp->nc_nlen;
-       bcopy(ncp->nc_name, xop->head.name, ncp->nc_nlen);
+       hammer2_xop_setname(&xop->head, ncp->nc_name, ncp->nc_nlen);
 
        /*
         * Note: In DragonFly the kernel handles '.' and '..'.
@@ -1189,7 +1186,9 @@ hammer2_vop_nmkdir(struct vop_nmkdir_args *ap)
        hammer2_pfs_memory_wait(dip->pmp);
        hammer2_trans_init(dip->pmp, 0);
        nip = hammer2_inode_create(dip, ap->a_vap, ap->a_cred,
-                                  name, name_len, 0, &error);
+                                  name, name_len,
+                                  hammer2_trans_newinum(dip->pmp), 0, 0,
+                                  0, &error);
        if (error) {
                KKASSERT(nip == NULL);
                *ap->a_vpp = NULL;
@@ -1253,14 +1252,11 @@ static
 int
 hammer2_vop_nlink(struct vop_nlink_args *ap)
 {
+       hammer2_xop_nlink_t *xop1;
        hammer2_inode_t *fdip;  /* target directory to create link in */
        hammer2_inode_t *tdip;  /* target directory to create link in */
        hammer2_inode_t *cdip;  /* common parent directory */
        hammer2_inode_t *ip;    /* inode we are hardlinking to */
-       hammer2_cluster_t *cluster;
-       hammer2_cluster_t *fdcluster;
-       hammer2_cluster_t *tdcluster;
-       hammer2_cluster_t *cdcluster;
        struct namecache *ncp;
        const uint8_t *name;
        size_t name_len;
@@ -1301,36 +1297,56 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
        hammer2_inode_lock(cdip, HAMMER2_RESOLVE_ALWAYS);
        hammer2_inode_lock(fdip, HAMMER2_RESOLVE_ALWAYS);
        hammer2_inode_lock(tdip, HAMMER2_RESOLVE_ALWAYS);
-       cdcluster = hammer2_inode_cluster(cdip, HAMMER2_RESOLVE_ALWAYS);
-       fdcluster = hammer2_inode_cluster(fdip, HAMMER2_RESOLVE_ALWAYS);
-       tdcluster = hammer2_inode_cluster(tdip, HAMMER2_RESOLVE_ALWAYS);
-
        hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
-       cluster = hammer2_inode_cluster(ip, HAMMER2_RESOLVE_ALWAYS);
+       error = 0;
 
-       error = hammer2_cluster_hardlink_consolidate(ip, &cluster,
-                                                    cdip, cdcluster, 1);
-       if (error)
-               goto done;
+       /*
+        * If ip is not a hardlink target we must convert it to a hardlink.
+        * If fdip != cdip we must shift the inode to cdip.
+        */
+       if (fdip != cdip || (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
+               xop1 = &hammer2_xop_alloc(fdip)->xop_nlink;
+               hammer2_xop_setip2(&xop1->head, ip);
+               hammer2_xop_setip3(&xop1->head, cdip);
+
+               hammer2_xop_start(&xop1->head, hammer2_xop_nlink);
+               error = hammer2_xop_collect(&xop1->head, 0);
+               hammer2_xop_retire(&xop1->head, HAMMER2_XOPMASK_VOP);
+               if (error == ENOENT)
+                       error = 0;
+       }
 
        /*
-        * Create a directory entry connected to the specified cluster.
-        *
-        * WARNING! chain can get moved by the connect (indirectly due to
-        *          potential indirect block creation).
+        * Must synchronize original inode whos chains are now a hardlink
+        * target.  We must match what the backend XOP did to the
+        * chains.
+        */
+       if (error == 0 && (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
+               hammer2_inode_modify(ip);
+               ip->meta.name_key = ip->meta.inum;
+               ip->meta.name_len = 18; /* "0x%016jx" */
+       }
+
+       /*
+        * Create the hardlink target and bump nlinks.
         */
-       error = hammer2_inode_connect(ip, &cluster, 1,
-                                     tdip, tdcluster,
-                                     name, name_len, 0);
+       if (error == 0) {
+               hammer2_inode_create(tdip, NULL, NULL,
+                                    name, name_len,
+                                    ip->meta.inum,
+                                    HAMMER2_OBJTYPE_HARDLINK, ip->meta.type,
+                                    0, &error);
+               hammer2_inode_modify(ip);
+               ++ip->meta.nlinks;
+       }
        if (error == 0) {
                cache_setunresolved(ap->a_nch);
                cache_setvp(ap->a_nch, ap->a_vp);
        }
-done:
-       hammer2_inode_unlock(ip, cluster);
-       hammer2_inode_unlock(tdip, tdcluster);
-       hammer2_inode_unlock(fdip, fdcluster);
-       hammer2_inode_unlock(cdip, cdcluster);
+       hammer2_inode_unlock(ip, NULL);
+       hammer2_inode_unlock(tdip, NULL);
+       hammer2_inode_unlock(fdip, NULL);
+       hammer2_inode_unlock(cdip, NULL);
        hammer2_inode_drop(cdip);
        hammer2_trans_done(ip->pmp);
 
@@ -1369,7 +1385,9 @@ hammer2_vop_ncreate(struct vop_ncreate_args *ap)
        hammer2_trans_init(dip->pmp, 0);
 
        nip = hammer2_inode_create(dip, ap->a_vap, ap->a_cred,
-                                  name, name_len, 0, &error);
+                                  name, name_len,
+                                  hammer2_trans_newinum(dip->pmp), 0, 0,
+                                  0, &error);
        if (error) {
                KKASSERT(nip == NULL);
                *ap->a_vpp = NULL;
@@ -1415,7 +1433,9 @@ hammer2_vop_nmknod(struct vop_nmknod_args *ap)
        hammer2_trans_init(dip->pmp, 0);
 
        nip = hammer2_inode_create(dip, ap->a_vap, ap->a_cred,
-                                  name, name_len, 0, &error);
+                                  name, name_len,
+                                  hammer2_trans_newinum(dip->pmp), 0, 0,
+                                  0, &error);
        if (error) {
                KKASSERT(nip == NULL);
                *ap->a_vpp = NULL;
@@ -1460,7 +1480,9 @@ hammer2_vop_nsymlink(struct vop_nsymlink_args *ap)
        ap->a_vap->va_type = VLNK;      /* enforce type */
 
        nip = hammer2_inode_create(dip, ap->a_vap, ap->a_cred,
-                                  name, name_len, 0, &error);
+                                  name, name_len,
+                                  hammer2_trans_newinum(dip->pmp), 0, 0,
+                                  0, &error);
        if (error) {
                KKASSERT(nip == NULL);
                *ap->a_vpp = NULL;
@@ -1530,11 +1552,12 @@ static
 int
 hammer2_vop_nremove(struct vop_nremove_args *ap)
 {
+       hammer2_xop_unlink_t *xop;
        hammer2_inode_t *dip;
+       hammer2_inode_t *ip;
        struct namecache *ncp;
-       const uint8_t *name;
-       size_t name_len;
        int error;
+       int isopen;
 
        LOCKSTART;
        dip = VTOI(ap->a_dvp);
@@ -1544,14 +1567,42 @@ hammer2_vop_nremove(struct vop_nremove_args *ap)
        }
 
        ncp = ap->a_nch->ncp;
-       name = ncp->nc_name;
-       name_len = ncp->nc_nlen;
 
        hammer2_pfs_memory_wait(dip->pmp);
        hammer2_trans_init(dip->pmp, 0);
-       error = hammer2_unlink_file(dip, NULL, name, name_len,
-                                   0, NULL,
-                                   cache_isopen(ap->a_nch), 0);
+       hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
+
+       /*
+        * The unlink XOP unlinks the path from the directory and
+        * locates and returns the cluster associated with the real inode.
+        * We have to handle nlinks here on the frontend.
+        */
+       xop = &hammer2_xop_alloc(dip)->xop_unlink;
+       hammer2_xop_setname(&xop->head, ncp->nc_name, ncp->nc_nlen);
+       isopen = cache_isopen(ap->a_nch);
+       xop->isdir = 0;
+       xop->dopermanent = isopen ?  0 : HAMMER2_DELETE_PERMANENT;
+       hammer2_xop_start(&xop->head, hammer2_xop_unlink);
+
+       /*
+        * Collect the real inode and adjust nlinks, destroy the real
+        * inode if nlinks transitions to 0 and it was the real inode
+        * (else it has already been removed).
+        */
+       error = hammer2_xop_collect(&xop->head, 0);
+       hammer2_inode_unlock(dip, NULL);
+
+       if (error == 0) {
+               ip = hammer2_inode_get(dip->pmp, dip, &xop->head.cluster);
+               hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
+               if (ip) {
+                       hammer2_inode_unlink_finisher(ip, isopen);
+                       hammer2_inode_unlock(ip, NULL);
+               }
+       } else {
+               hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
+       }
+
        hammer2_run_unlinkq(dip->pmp);
        hammer2_trans_done(dip->pmp);
        if (error == 0)
@@ -1567,10 +1618,11 @@ static
 int
 hammer2_vop_nrmdir(struct vop_nrmdir_args *ap)
 {
+       hammer2_xop_unlink_t *xop;
        hammer2_inode_t *dip;
+       hammer2_inode_t *ip;
        struct namecache *ncp;
-       const uint8_t *name;
-       size_t name_len;
+       int isopen;
        int error;
 
        LOCKSTART;
@@ -1580,16 +1632,38 @@ hammer2_vop_nrmdir(struct vop_nrmdir_args *ap)
                return(EROFS);
        }
 
-       ncp = ap->a_nch->ncp;
-       name = ncp->nc_name;
-       name_len = ncp->nc_nlen;
-
        hammer2_pfs_memory_wait(dip->pmp);
        hammer2_trans_init(dip->pmp, 0);
+       hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
+
+       xop = &hammer2_xop_alloc(dip)->xop_unlink;
+
+       ncp = ap->a_nch->ncp;
+       hammer2_xop_setname(&xop->head, ncp->nc_name, ncp->nc_nlen);
+       isopen = cache_isopen(ap->a_nch);
+       xop->isdir = 1;
+       xop->dopermanent = isopen ?  0 : HAMMER2_DELETE_PERMANENT;
+       hammer2_xop_start(&xop->head, hammer2_xop_unlink);
+
+       /*
+        * Collect the real inode and adjust nlinks, destroy the real
+        * inode if nlinks transitions to 0 and it was the real inode
+        * (else it has already been removed).
+        */
+       error = hammer2_xop_collect(&xop->head, 0);
+       hammer2_inode_unlock(dip, NULL);
+
+       if (error == 0) {
+               ip = hammer2_inode_get(dip->pmp, dip, &xop->head.cluster);
+               hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
+               if (ip) {
+                       hammer2_inode_unlink_finisher(ip, isopen);
+                       hammer2_inode_unlock(ip, NULL);
+               }
+       } else {
+               hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
+       }
        hammer2_run_unlinkq(dip->pmp);
-       error = hammer2_unlink_file(dip, NULL, name, name_len,
-                                   1, NULL,
-                                   cache_isopen(ap->a_nch), 0);
        hammer2_trans_done(dip->pmp);
        if (error == 0)
                cache_unlink(ap->a_nch);
@@ -1610,17 +1684,13 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
        hammer2_inode_t *fdip;
        hammer2_inode_t *tdip;
        hammer2_inode_t *ip;
-       hammer2_cluster_t *cluster;
-       hammer2_cluster_t *fdcluster;
-       hammer2_cluster_t *tdcluster;
-       hammer2_cluster_t *cdcluster;
        const uint8_t *fname;
        size_t fname_len;
        const uint8_t *tname;
        size_t tname_len;
        int error;
        int tnch_error;
-       int hlink;
+       hammer2_key_t tlhc;
 
        if (ap->a_fdvp->v_mount != ap->a_tdvp->v_mount)
                return(EXDEV);
@@ -1650,126 +1720,171 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
         * ip represents the actual file and not the hardlink marker.
         */
        ip = VTOI(fncp->nc_vp);
-       cluster = NULL;
-
 
        /*
         * The common parent directory must be locked first to avoid deadlocks.
         * Also note that fdip and/or tdip might match cdip.
-        *
-        * WARNING! fdip may not match ip->pip.  That is, if the source file
-        *          is already a hardlink then what we are renaming is the
-        *          hardlink pointer, not the hardlink itself.  The hardlink
-        *          directory (ip->pip) will already be at a common parent
-        *          of fdrip.
-        *
-        *          Be sure to use ip->pip when finding the common parent
-        *          against tdip or we might accidently move the hardlink
-        *          target into a subdirectory that makes it inaccessible to
-        *          other pointers.
         */
        cdip = hammer2_inode_common_parent(ip->pip, tdip);
        hammer2_inode_lock(cdip, HAMMER2_RESOLVE_ALWAYS);
        hammer2_inode_lock(fdip, HAMMER2_RESOLVE_ALWAYS);
        hammer2_inode_lock(tdip, HAMMER2_RESOLVE_ALWAYS);
-       cdcluster = hammer2_inode_cluster(cdip, HAMMER2_RESOLVE_ALWAYS);
-       fdcluster = hammer2_inode_cluster(fdip, HAMMER2_RESOLVE_ALWAYS);
-       tdcluster = hammer2_inode_cluster(tdip, HAMMER2_RESOLVE_ALWAYS);
+       hammer2_inode_ref(ip);          /* extra ref */
+       error = 0;
 
        /*
-        * Keep a tight grip on the inode so the temporary unlinking from
-        * the source location prior to linking to the target location
-        * does not cause the cluster to be destroyed.
-        *
-        * NOTE: To avoid deadlocks we cannot lock (ip) while we are
-        *       unlinking elements from their directories.  Locking
-        *       the nlinks field does not lock the whole inode.
+        * If ip is a hardlink target and fdip != cdip we must shift the
+        * inode to cdip.
         */
-       hammer2_inode_ref(ip);
+       if (fdip != cdip &&
+           (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
+               hammer2_xop_nlink_t *xop1;
+
+               xop1 = &hammer2_xop_alloc(fdip)->xop_nlink;
+               hammer2_xop_setip2(&xop1->head, ip);
+               hammer2_xop_setip3(&xop1->head, cdip);
+
+               hammer2_xop_start(&xop1->head, hammer2_xop_nlink);
+               error = hammer2_xop_collect(&xop1->head, 0);
+               hammer2_xop_retire(&xop1->head, HAMMER2_XOPMASK_VOP);
+       }
 
        /*
-        * Remove target if it exists.
+        * Delete the target namespace.
         */
-       error = hammer2_unlink_file(tdip, NULL, tname, tname_len,
-                                   -1, NULL,
-                                   cache_isopen(ap->a_tnch), 0);
-       tnch_error = error;
-       if (error && error != ENOENT)
-               goto done2;
+       {
+               hammer2_xop_unlink_t *xop2;
+               hammer2_inode_t *tip;
+               int isopen;
+
+               /*
+                * The unlink XOP unlinks the path from the directory and
+                * locates and returns the cluster associated with the real
+                * inode.  We have to handle nlinks here on the frontend.
+                */
+               xop2 = &hammer2_xop_alloc(tdip)->xop_unlink;
+               hammer2_xop_setname(&xop2->head, tname, tname_len);
+               isopen = cache_isopen(ap->a_tnch);
+               xop2->isdir = -1;
+               xop2->dopermanent = isopen ?  0 : HAMMER2_DELETE_PERMANENT;
+               hammer2_xop_start(&xop2->head, hammer2_xop_unlink);
+
+               /*
+                * Collect the real inode and adjust nlinks, destroy the real
+                * inode if nlinks transitions to 0 and it was the real inode
+                * (else it has already been removed).
+                */
+               tnch_error = hammer2_xop_collect(&xop2->head, 0);
+               /* hammer2_inode_unlock(tdip, NULL); */
+
+               if (tnch_error == 0) {
+                       tip = hammer2_inode_get(tdip->pmp, NULL,
+                                               &xop2->head.cluster);
+                       hammer2_xop_retire(&xop2->head, HAMMER2_XOPMASK_VOP);
+                       if (tip) {
+                               hammer2_inode_unlink_finisher(tip, isopen);
+                               hammer2_inode_unlock(tip, NULL);
+                       }
+               } else {
+                       hammer2_xop_retire(&xop2->head, HAMMER2_XOPMASK_VOP);
+               }
+               /* hammer2_inode_lock(tdip, HAMMER2_RESOLVE_ALWAYS); */
+
+               if (tnch_error && tnch_error != ENOENT) {
+                       error = tnch_error;
+                       goto done2;
+               }
+       }
 
        /*
-        * When renaming a hardlinked file we may have to re-consolidate
-        * the location of the hardlink target.
-        *
-        * If ip represents a regular file the consolidation code essentially
-        * does nothing other than return the same locked cluster that was
-        * passed in.
+        * Resolve the collision space for (tdip, tname, tname_len)
         *
-        * The returned cluster will be locked.
-        *
-        * WARNING!  We do not currently have a local copy of ipdata but
-        *           we do use one later remember that it must be reloaded
-        *           on any modification to the inode, including connects.
+        * tdip must be held exclusively locked to prevent races.
         */
-       hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
-       cluster = hammer2_inode_cluster(ip, HAMMER2_RESOLVE_ALWAYS);
-       error = hammer2_cluster_hardlink_consolidate(ip, &cluster,
-                                                    cdip, cdcluster, 0);
-       if (error)
-               goto done1;
+       {
+               hammer2_xop_scanlhc_t *sxop;
+               hammer2_tid_t lhcbase;
+
+               tlhc = hammer2_dirhash(tname, tname_len);
+               lhcbase = tlhc;
+               sxop = &hammer2_xop_alloc(tdip)->xop_scanlhc;
+               sxop->lhc = tlhc;
+               hammer2_xop_start(&sxop->head, hammer2_inode_xop_scanlhc);
+               while ((error = hammer2_xop_collect(&sxop->head, 0)) == 0) {
+                       if (tlhc != sxop->head.cluster.focus->bref.key)
+                               break;
+                       ++tlhc;
+               }
+               hammer2_xop_retire(&sxop->head, HAMMER2_XOPMASK_VOP);
+
+               if (error) {
+                       if (error != ENOENT)
+                               goto done2;
+                       ++tlhc;
+                       error = 0;
+               }
+               if ((lhcbase ^ tlhc) & ~HAMMER2_DIRHASH_LOMASK) {
+                       error = ENOSPC;
+                       goto done2;
+               }
+       }
 
        /*
-        * 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.
+        * Everything is setup, do the rename.
         *
-        * Always pass nch as NULL because we intend to reconnect the inode,
-        * so we don't want hammer2_unlink_file() to rename it to the hidden
-        * open-but-unlinked directory.
+        * We have to synchronize ip->meta to the underlying operation.
         *
-        * The target cluster may be marked DELETED but will not be destroyed
-        * since we retain our hold on ip and cluster.
-        *
-        * NOTE: We pass nlinks as 0 (not -1) in order to retain the file's
-        *       link count.
+        * NOTE: To avoid deadlocks we cannot lock (ip) while we are
+        *       unlinking elements from their directories.  Locking
+        *       the nlinks field does not lock the whole inode.
         */
-       error = hammer2_unlink_file(fdip, ip, fname, fname_len,
-                                   -1, &hlink, 0, 1);
-       KKASSERT(error != EAGAIN);
-       if (error)
-               goto done1;
+       hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
+       if (error == 0) {
+               hammer2_xop_nrename_t *xop4;
+
+               xop4 = &hammer2_xop_alloc(fdip)->xop_nrename;
+               xop4->lhc = tlhc;
+               xop4->ip_key = ip->meta.name_key;
+               hammer2_xop_setip2(&xop4->head, ip);
+               hammer2_xop_setip3(&xop4->head, tdip);
+               hammer2_xop_setname(&xop4->head, fname, fname_len);
+               hammer2_xop_setname2(&xop4->head, tname, tname_len);
+               hammer2_xop_start(&xop4->head, hammer2_xop_nrename);
+
+               error = hammer2_xop_collect(&xop4->head, 0);
+               hammer2_xop_retire(&xop4->head, HAMMER2_XOPMASK_VOP);
+
+               if (error == ENOENT)
+                       error = 0;
+               if (error == 0 &&
+                   (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
+                       hammer2_inode_modify(ip);
+                       ip->meta.name_len = tname_len;
+                       ip->meta.name_key = tlhc;
+
+               }
+       }
 
        /*
-        * Reconnect ip to target directory using cluster.  Chains cannot
-        * actually be moved, so this will duplicate the cluster in the new
-        * spot and assign it to the ip, replacing the old cluster.
-        *
-        * WARNING: Because recursive locks are allowed and we unlinked the
-        *          file that we have a cluster-in-hand for just above, the
-        *          cluster might have been delete-duplicated.  We must
-        *          refactor the cluster.
-        *
-        * WARNING: Chain locks can lock buffer cache buffers, to avoid
-        *          deadlocks we want to unlock before issuing a cache_*()
-        *          op (that might have to lock a vnode).
-        *
-        * NOTE:    Pass nlinks as 0 because we retained the link count from
-        *          the unlink, so we do not have to modify it.
+        * Fixup ip->pip if we were renaming the actual file and not a
+        * hardlink pointer.
         */
-       error = hammer2_inode_connect(ip, &cluster, hlink,
-                                     tdip, tdcluster,
-                                     tname, tname_len, 0);
-       if (error == 0) {
-               KKASSERT(cluster != NULL);
-               hammer2_inode_repoint(ip, (hlink ? ip->pip : tdip), cluster);
+       if (error == 0 && (ip->meta.name_key & HAMMER2_DIRHASH_VISIBLE)) {
+               hammer2_inode_t *opip;
+
+               if (ip->pip != tdip) {
+                       hammer2_inode_ref(tdip);
+                       opip = ip->pip;
+                       ip->pip = tdip;
+                       if (opip)
+                               hammer2_inode_drop(opip);
+               }
        }
-done1:
-       hammer2_inode_unlock(ip, cluster);
+       hammer2_inode_unlock(ip, NULL);
 done2:
-       hammer2_inode_unlock(tdip, tdcluster);
-       hammer2_inode_unlock(fdip, fdcluster);
-       hammer2_inode_unlock(cdip, cdcluster);
+       hammer2_inode_unlock(tdip, NULL);
+       hammer2_inode_unlock(fdip, NULL);
+       hammer2_inode_unlock(cdip, NULL);
        hammer2_inode_drop(ip);
        hammer2_inode_drop(cdip);
        hammer2_run_unlinkq(fdip->pmp);
index 9077fc9..b28b808 100644 (file)
@@ -60,6 +60,9 @@
 
 #include "hammer2.h"
 
+/*
+ * Backend for hammer2_vop_readdir()
+ */
 void
 hammer2_xop_readdir(hammer2_xop_t *arg, int clindex)
 {
@@ -117,6 +120,9 @@ done:
        hammer2_xop_feed(&xop->head, NULL, clindex, error);
 }
 
+/*
+ * Backend for hammer2_vop_nresolve()
+ */
 void
 hammer2_xop_nresolve(hammer2_xop_t *arg, int clindex)
 {
@@ -173,9 +179,10 @@ hammer2_xop_nresolve(hammer2_xop_t *arg, int clindex)
        error = 0;
        if (chain) {
                if (chain->data->ipdata.meta.type == HAMMER2_OBJTYPE_HARDLINK) {
-                       error = hammer2_chain_hardlink_find(xop->head.ip,
-                                                           &parent,
-                                                           &chain);
+                       error = hammer2_chain_hardlink_find(
+                                               xop->head.ip,
+                                               &parent, &chain,
+                                               HAMMER2_RESOLVE_SHARED);
                }
        }
 done:
@@ -188,6 +195,441 @@ done:
        }
 }
 
+/*
+ * Backend for hammer2_vop_nremove(), hammer2_vop_nrmdir(), and helper
+ * for hammer2_vop_nrename().
+ *
+ * This function does locates and removes the directory entry.  If the
+ * entry is a hardlink pointer, this function will also remove the
+ * hardlink target if the target's nlinks is 1.
+ *
+ * The frontend is responsible for moving open inodes to the hidden directory
+ * and for decrementing nlinks.
+ */
+void
+hammer2_xop_unlink(hammer2_xop_t *arg, int clindex)
+{
+       hammer2_xop_unlink_t *xop = &arg->xop_unlink;
+       hammer2_chain_t *parent;
+       hammer2_chain_t *chain;
+       const hammer2_inode_data_t *ripdata;
+       const char *name;
+       size_t name_len;
+       uint8_t type;
+       hammer2_key_t key_next;
+       hammer2_key_t lhc;
+       int cache_index = -1;   /* XXX */
+       int error;
+
+       /*
+        * Requires exclusive lock
+        */
+       parent = hammer2_inode_chain(xop->head.ip, clindex,
+                                    HAMMER2_RESOLVE_ALWAYS);
+       if (parent == NULL) {
+               kprintf("xop_nresolve: NULL parent\n");
+               chain = NULL;
+               error = EIO;
+               goto done;
+       }
+       name = xop->head.name;
+       name_len = xop->head.name_len;
+
+       /*
+        * Lookup the directory entry
+        */
+       lhc = hammer2_dirhash(name, name_len);
+       chain = hammer2_chain_lookup(&parent, &key_next,
+                                    lhc, lhc + HAMMER2_DIRHASH_LOMASK,
+                                    &cache_index,
+                                    HAMMER2_LOOKUP_ALWAYS);
+       while (chain) {
+               ripdata = &chain->data->ipdata;
+               if (chain->bref.type == HAMMER2_BREF_TYPE_INODE &&
+                   ripdata->meta.name_len == name_len &&
+                   bcmp(ripdata->filename, name, name_len) == 0) {
+                       break;
+               }
+               chain = hammer2_chain_next(&parent, chain, &key_next,
+                                          key_next,
+                                          lhc + HAMMER2_DIRHASH_LOMASK,
+                                          &cache_index,
+                                          HAMMER2_LOOKUP_ALWAYS);
+       }
+
+       /*
+        * If the directory entry is a HARDLINK pointer then obtain the
+        * underlying file type for the directory typing tests and delete
+        * the HARDLINK pointer chain permanently.  The frontend is left
+        * responsible for handling nlinks on and deleting the actual inode.
+        *
+        * If the directory entry is the actual inode then use its type
+        * for the directory typing tests and delete the chain, permanency
+        * depends on whether the inode is open or not.
+        *
+        * Check directory typing and delete the entry.  Note that
+        * nlinks adjustments are made on the real inode by the frontend,
+        * not here.
+        */
+       error = 0;
+       if (chain) {
+               int dopermanent = xop->dopermanent;
+
+               type = chain->data->ipdata.meta.type;
+               if (type == HAMMER2_OBJTYPE_HARDLINK) {
+                       type = chain->data->ipdata.meta.target_type;
+                       dopermanent |= HAMMER2_DELETE_PERMANENT;
+               }
+               if (type == HAMMER2_OBJTYPE_DIRECTORY &&
+                   xop->isdir == 0) {
+                       error = ENOTDIR;
+               } else 
+               if (type != HAMMER2_OBJTYPE_DIRECTORY &&
+                   xop->isdir >= 1) {
+                       error = EISDIR;
+               } else {
+                       hammer2_chain_delete(parent, chain, xop->dopermanent);
+               }
+       }
+
+       /*
+        * If the entry is a hardlink pointer, resolve it.  If this is the
+        * last link, delete it.  We aren't the frontend so we can't adjust
+        * nlinks.
+        */
+       if (chain) {
+               if (chain->data->ipdata.meta.type == HAMMER2_OBJTYPE_HARDLINK) {
+                       error = hammer2_chain_hardlink_find(
+                                               xop->head.ip,
+                                               &parent, &chain,
+                                               0);
+                       if (chain &&
+                           (int64_t)chain->data->ipdata.meta.nlinks <= 1) {
+                               hammer2_chain_delete(parent, chain,
+                                                    xop->dopermanent);
+                       }
+               }
+       }
+
+       /*
+        * Chains passed to feed are expected to be locked shared.
+        */
+       if (chain) {
+               hammer2_chain_unlock(chain);
+               hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS |
+                                         HAMMER2_RESOLVE_SHARED);
+       }
+
+       /*
+        * We always return the hardlink target (the real inode) for
+        * further action.
+        */
+done:
+       hammer2_xop_feed(&xop->head, chain, clindex, error);
+       if (chain)
+               hammer2_chain_drop(chain);
+       if (parent) {
+               hammer2_chain_unlock(parent);
+               hammer2_chain_drop(parent);
+       }
+}
+
+/*
+ * Backend for hammer2_vop_nlink() and hammer2_vop_nrename()
+ *
+ * Convert the target {dip,ip} to a hardlink target and replace
+ * the original namespace with a hardlink pointer.
+ */
+void
+hammer2_xop_nlink(hammer2_xop_t *arg, int clindex)
+{
+       hammer2_xop_nlink_t *xop = &arg->xop_nlink;
+       hammer2_pfs_t *pmp;
+       hammer2_inode_data_t *wipdata;
+       hammer2_chain_t *parent;
+       hammer2_chain_t *chain;
+       hammer2_chain_t *tmp;
+       hammer2_inode_t *ip;
+       hammer2_key_t key_dummy;
+       int cache_index = -1;
+       int error;
+
+       /*
+        * We need the precise parent chain to issue the deletion.
+        */
+       ip = xop->head.ip2;
+       pmp = ip->pmp;
+       parent = hammer2_inode_chain(ip, clindex, HAMMER2_RESOLVE_ALWAYS);
+       if (parent)
+               hammer2_chain_getparent(&parent, HAMMER2_RESOLVE_ALWAYS);
+       if (parent == NULL) {
+               chain = NULL;
+               error = EIO;
+               goto done;
+       }
+       chain = hammer2_inode_chain(ip, clindex, HAMMER2_RESOLVE_ALWAYS);
+       if (chain == NULL) {
+               error = EIO;
+               goto done;
+       }
+       hammer2_chain_delete(parent, chain, 0);
+
+       /*
+        * Replace the namespace with a hardlink pointer if the chain being
+        * moved is not already a hardlink target.
+        */
+       if (chain->data->ipdata.meta.name_key & HAMMER2_DIRHASH_VISIBLE) {
+               tmp = NULL;
+               error = hammer2_chain_create(&parent, &tmp, pmp,
+                                            chain->bref.key, 0,
+                                            HAMMER2_BREF_TYPE_INODE,
+                                            HAMMER2_INODE_BYTES,
+                                            0);
+               if (error)
+                       goto done;
+               hammer2_chain_modify(tmp, 0);
+               wipdata = &tmp->data->ipdata;
+               bzero(wipdata, sizeof(*wipdata));
+               wipdata->meta.name_key = chain->data->ipdata.meta.name_key;
+               wipdata->meta.name_len = chain->data->ipdata.meta.name_len;
+               bcopy(chain->data->ipdata.filename, wipdata->filename,
+                     chain->data->ipdata.meta.name_len);
+               wipdata->meta.target_type = chain->data->ipdata.meta.type;
+               wipdata->meta.type = HAMMER2_OBJTYPE_HARDLINK;
+               wipdata->meta.inum = ip->meta.inum;
+               wipdata->meta.version = HAMMER2_INODE_VERSION_ONE;
+               wipdata->meta.nlinks = 1;
+               wipdata->meta.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
+
+               hammer2_chain_unlock(tmp);
+               hammer2_chain_drop(tmp);
+       }
+
+       hammer2_chain_unlock(parent);
+       hammer2_chain_drop(parent);
+
+       /*
+        * Ok, back to the deleted chain.  We must reconnect this chain
+        * as a hardlink target to cdir (ip3).
+        *
+        * WARNING! Frontend assumes filename length is 18 bytes.
+        */
+       hammer2_chain_modify(chain, 0);
+       wipdata = &chain->data->ipdata;
+       ksnprintf(wipdata->filename, sizeof(wipdata->filename),
+                 "0x%016jx", (intmax_t)ip->meta.inum);
+       wipdata->meta.name_len = strlen(wipdata->filename);
+       wipdata->meta.name_key = ip->meta.inum;
+
+       /*
+        * We must seek parent properly for the create.
+        */
+       parent = hammer2_inode_chain(xop->head.ip3, clindex,
+                                    HAMMER2_RESOLVE_ALWAYS);
+       if (parent == NULL) {
+               error = EIO;
+               goto done;
+       }
+       tmp = hammer2_chain_lookup(&parent, &key_dummy,
+                                  ip->meta.inum, ip->meta.inum,
+                                  &cache_index, 0);
+       if (tmp) {
+               hammer2_chain_unlock(tmp);
+               hammer2_chain_drop(tmp);
+               error = EEXIST;
+               goto done;
+       }
+       error = hammer2_chain_create(&parent, &chain, pmp,
+                                    wipdata->meta.name_key, 0,
+                                    HAMMER2_BREF_TYPE_INODE,
+                                    HAMMER2_INODE_BYTES,
+                                    0);
+       /*
+        * To avoid having to scan the collision space we can simply
+        * reuse the inode's original name_key.  But ip->meta.name_key
+        * may have already been updated by the front-end, so use xop->lhc.
+        *
+        * (frontend is responsible for fixing up ip->pip).
+        */
+done:
+       hammer2_xop_feed(&xop->head, NULL, clindex, error);
+       if (parent) {
+               hammer2_chain_unlock(parent);
+               hammer2_chain_drop(parent);
+       }
+       if (chain) {
+               hammer2_chain_unlock(chain);
+               hammer2_chain_drop(chain);
+       }
+}
+
+/*
+ * Backend for hammer2_vop_nrename()
+ *
+ * This handles the final step of renaming, either renaming the
+ * actual inode or renaming the hardlink pointer.
+ */
+void
+hammer2_xop_nrename(hammer2_xop_t *arg, int clindex)
+{
+       hammer2_xop_nrename_t *xop = &arg->xop_nrename;
+       hammer2_pfs_t *pmp;
+       hammer2_chain_t *parent;
+       hammer2_chain_t *chain;
+       hammer2_chain_t *tmp;
+       hammer2_inode_t *ip;
+       hammer2_key_t key_dummy;
+       int cache_index = -1;
+       int error;
+
+       /*
+        * We need the precise parent chain to issue the deletion.
+        *
+        * If this is not a hardlink target we can act on the inode,
+        * otherwise we have to locate the hardlink pointer.
+        */
+       ip = xop->head.ip2;
+       pmp = ip->pmp;
+       chain = NULL;
+
+       if (xop->ip_key & HAMMER2_DIRHASH_VISIBLE) {
+               /*
+                * Find ip's direct parent chain.
+                */
+               parent = hammer2_inode_chain(ip, clindex,
+                                            HAMMER2_RESOLVE_ALWAYS);
+               if (parent)
+                       hammer2_chain_getparent(&parent,
+                                               HAMMER2_RESOLVE_ALWAYS);
+               if (parent == NULL) {
+                       error = EIO;
+                       goto done;
+               }
+               chain = hammer2_inode_chain(ip, clindex,
+                                           HAMMER2_RESOLVE_ALWAYS);
+               if (chain == NULL) {
+                       error = EIO;
+                       goto done;
+               }
+       } else {
+               /*
+                * head.ip is fdip, do a namespace search.
+                */
+               const hammer2_inode_data_t *ripdata;
+               hammer2_key_t lhc;
+               hammer2_key_t key_next;
+               const char *name;
+               size_t name_len;
+
+               parent = hammer2_inode_chain(xop->head.ip, clindex,
+                                            HAMMER2_RESOLVE_ALWAYS |
+                                            HAMMER2_RESOLVE_SHARED);
+               if (parent == NULL) {
+                       kprintf("xop_nrename: NULL parent\n");
+                       error = EIO;
+                       goto done;
+               }
+               name = xop->head.name;
+               name_len = xop->head.name_len;
+
+               /*
+                * Lookup the directory entry
+                */
+               lhc = hammer2_dirhash(name, name_len);
+               chain = hammer2_chain_lookup(&parent, &key_next,
+                                            lhc, lhc + HAMMER2_DIRHASH_LOMASK,
+                                            &cache_index,
+                                            HAMMER2_LOOKUP_ALWAYS);
+               while (chain) {
+                       ripdata = &chain->data->ipdata;
+                       if (chain->bref.type == HAMMER2_BREF_TYPE_INODE &&
+                           ripdata->meta.name_len == name_len &&
+                           bcmp(ripdata->filename, name, name_len) == 0) {
+                               break;
+                       }
+                       chain = hammer2_chain_next(&parent, chain, &key_next,
+                                                  key_next,
+                                                  lhc + HAMMER2_DIRHASH_LOMASK,
+                                                  &cache_index,
+                                                  HAMMER2_LOOKUP_ALWAYS);
+               }
+       }
+
+       /*
+        * Delete it, then create it in the new namespace.
+        */
+       hammer2_chain_delete(parent, chain, 0);
+       hammer2_chain_unlock(parent);
+       hammer2_chain_drop(parent);
+       parent = NULL;          /* safety */
+
+
+       /*
+        * Ok, back to the deleted chain.  We must reconnect this chain
+        * to tdir (ip3).  The chain (a real inode or a hardlink pointer)
+        * is not otherwise modified.
+        *
+        * Frontend is expected to replicate the same inode meta data
+        * modifications.
+        *
+        * NOTE!  This chain may not represent the actual inode, it
+        *        can be a hardlink pointer.
+        *
+        * XXX in-inode parent directory specification?
+        */
+       if (chain->data->ipdata.meta.name_key != xop->lhc ||
+           xop->head.name_len != xop->head.name2_len ||
+           bcmp(xop->head.name, xop->head.name2, xop->head.name_len) != 0) {
+               hammer2_inode_data_t *wipdata;
+
+               hammer2_chain_modify(chain, 0);
+               wipdata = &chain->data->ipdata;
+
+               bzero(wipdata->filename, sizeof(wipdata->filename));
+               bcopy(xop->head.name2, wipdata->filename, xop->head.name2_len);
+               wipdata->meta.name_key = xop->lhc;
+               wipdata->meta.name_len = xop->head.name2_len;
+       }
+
+       /*
+        * We must seek parent properly for the create.
+        */
+       parent = hammer2_inode_chain(xop->head.ip3, clindex,
+                                    HAMMER2_RESOLVE_ALWAYS);
+       if (parent == NULL) {
+               error = EIO;
+               goto done;
+       }
+       tmp = hammer2_chain_lookup(&parent, &key_dummy,
+                                  xop->lhc, xop->lhc,
+                                  &cache_index, 0);
+       if (tmp) {
+               hammer2_chain_unlock(tmp);
+               hammer2_chain_drop(tmp);
+               error = EEXIST;
+               goto done;
+       }
+
+       error = hammer2_chain_create(&parent, &chain, pmp,
+                                    xop->lhc, 0,
+                                    HAMMER2_BREF_TYPE_INODE,
+                                    HAMMER2_INODE_BYTES,
+                                    0);
+       /*
+        * (frontend is responsible for fixing up ip->pip).
+        */
+done:
+       hammer2_xop_feed(&xop->head, NULL, clindex, error);
+       if (parent) {
+               hammer2_chain_unlock(parent);
+               hammer2_chain_drop(parent);
+       }
+       if (chain) {
+               hammer2_chain_unlock(chain);
+               hammer2_chain_drop(chain);
+       }
+}
+
 /*
  * Directory collision resolver scan helper (backend, threaded).
  *