From: Matthew Dillon Date: Thu, 17 May 2012 08:36:51 +0000 (-0700) Subject: hammer2 - Complete core hardlink support work X-Git-Tag: v3.4.0rc~1102 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/99535653ccb7716d134cce6e0a9c44ddb4e193d6 hammer2 - Complete core hardlink support work This implements core hardlink support for hammer2. In order to maintain the strict bottom-up block modification hierarchy for the chains hardlinks must be implemented with special forwarding inodes. When a hardlink is created (nlinks 1->2) the file is replaced with a forwarding entry and then recreated as a special hidden directory entry indexed by its inode number at a higher directory level which is common to all hardlinks to that file. The forwarding entry simply specifies the inode number, thus our ability to trivially snapshot a PFS is retained. Since the real inode is indexed at a higher common directory locating the real inode simply requires iterating parent directories until we find a match. * Default vfs.hammer2.hardlink_enable to 1 (enabled). * Track and adjust nlinks. * Implement OBJTYPE_HARDLINK forwarding directory entry, hidden inode, vnode->v_data inode replacement for the nlinks 1->2 case, and hidden inode deletion for the nlinks 1->0 case. * The deconsolidation for the nlinks 2->1 case is not yet implemented. --- diff --git a/sys/vfs/hammer2/hammer2.h b/sys/vfs/hammer2/hammer2.h index a8c127492e..352b9704a2 100644 --- a/sys/vfs/hammer2/hammer2.h +++ b/sys/vfs/hammer2/hammer2.h @@ -210,22 +210,16 @@ SPLAY_PROTOTYPE(hammer2_chain_splay, hammer2_chain, snode, hammer2_chain_cmp); /* * A hammer2 inode. - * - * NOTE: Hardlinks are usually resolved through its forwarding inode(s) - * but fwd will be non-NULL if the related inode/vnode is resolved - * prior to the hardlink being created, or if a hardlink's real - * inode had to be moved. In these situations the old inode pointer - * will get a (fwd) entry. All vnops must forward through it. */ struct hammer2_inode { struct hammer2_mount *hmp; /* Global mount */ struct hammer2_pfsmount *pmp; /* PFS mount */ struct hammer2_inode *pip; /* parent inode */ - struct hammer2_inode *fwd; /* forwarding inode */ struct vnode *vp; hammer2_chain_t chain; struct hammer2_inode_data ip_data; struct lockf advlock; + u_int depth; /* directory depth */ }; typedef struct hammer2_inode hammer2_inode_t; @@ -381,13 +375,15 @@ int hammer2_inode_create(hammer2_inode_t *dip, const uint8_t *name, size_t name_len, hammer2_inode_t **nipp); -int hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *nip, +int hammer2_inode_duplicate(hammer2_inode_t *dip, + hammer2_inode_t *oip, hammer2_inode_t **nipp, + const uint8_t *name, size_t name_len); +int hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip, const uint8_t *name, size_t name_len); int hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len, int isdir); -int hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip, - int bumpnlinks); +int hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip); int hammer2_hardlink_deconsolidate(hammer2_inode_t *dip, hammer2_chain_t **chainp, hammer2_inode_t **ipp); int hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp, diff --git a/sys/vfs/hammer2/hammer2_chain.c b/sys/vfs/hammer2/hammer2_chain.c index 46000e91c8..8df9803018 100644 --- a/sys/vfs/hammer2/hammer2_chain.c +++ b/sys/vfs/hammer2/hammer2_chain.c @@ -953,6 +953,7 @@ hammer2_chain_get(hammer2_mount_t *hmp, hammer2_chain_t *parent, if (parent->bref.type == HAMMER2_BREF_TYPE_INODE) { chain->u.ip->pip = parent->u.ip; chain->u.ip->pmp = parent->u.ip->pmp; + chain->u.ip->depth = parent->u.ip->depth + 1; } } @@ -1517,6 +1518,7 @@ again: if (scan->bref.type == HAMMER2_BREF_TYPE_INODE) { chain->u.ip->pip = scan->u.ip; chain->u.ip->pmp = scan->u.ip->pmp; + chain->u.ip->depth = scan->u.ip->depth + 1; } } @@ -2010,8 +2012,10 @@ hammer2_chain_delete(hammer2_mount_t *hmp, hammer2_chain_t *parent, /* * If this is an inode clear the pip. */ - if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) + if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) { chain->u.ip->pip = NULL; + chain->u.ip->depth = 0; + } /* * The chain is still likely referenced, possibly even by a vnode diff --git a/sys/vfs/hammer2/hammer2_disk.h b/sys/vfs/hammer2/hammer2_disk.h index 81b86428b9..ccc8dca7fe 100644 --- a/sys/vfs/hammer2/hammer2_disk.h +++ b/sys/vfs/hammer2/hammer2_disk.h @@ -451,7 +451,7 @@ struct hammer2_inode_data { * a separate node. {pfs_fsid, pfs_id} must be used for * registration in the cluster. */ - uint8_t reserved84; /* 0084 */ + uint8_t target_type; /* 0084 hardlink target type */ uint8_t reserved85; /* 0085 */ uint8_t reserved86; /* 0086 */ uint8_t pfs_type; /* 0087 (if PFSROOT) node type */ diff --git a/sys/vfs/hammer2/hammer2_inode.c b/sys/vfs/hammer2/hammer2_inode.c index 903acf3160..d5ae60d560 100644 --- a/sys/vfs/hammer2/hammer2_inode.c +++ b/sys/vfs/hammer2/hammer2_inode.c @@ -320,24 +320,132 @@ hammer2_inode_create(hammer2_inode_t *dip, return (0); } +/* + * Duplicate the specified existing inode in the specified target directory. + * If name is NULL the inode is duplicated as a hidden directory entry. + * + * Returns the new inode. The old inode is left alone. + */ +int +hammer2_inode_duplicate(hammer2_inode_t *dip, hammer2_inode_t *oip, + hammer2_inode_t **nipp, + const uint8_t *name, size_t name_len) +{ + hammer2_mount_t *hmp = dip->hmp; + hammer2_inode_t *nip; + hammer2_chain_t *chain; + hammer2_chain_t *parent; + hammer2_key_t lhc; + int error; + + if (name) { + lhc = hammer2_dirhash(name, name_len); + } else { + lhc = oip->ip_data.inum; + KKASSERT((lhc & HAMMER2_DIRHASH_VISIBLE) == 0); + } + + /* + * Locate the inode or indirect block to create the new + * entry in. At the same time check for key collisions + * and iterate until we don't get one. + */ + nip = NULL; + parent = &dip->chain; + hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS); + + error = 0; + while (error == 0) { + chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0); + if (chain == NULL) + break; + if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK) + error = ENOSPC; + hammer2_chain_unlock(hmp, chain); + chain = NULL; + ++lhc; + } + + /* + * Passing a non-NULL chain to hammer2_chain_create() reconnects the + * existing chain instead of creating a new one. The chain's bref + * will be properly updated. + */ + if (error == 0) { + chain = hammer2_chain_create(hmp, parent, NULL, lhc, 0, + HAMMER2_BREF_TYPE_INODE /* n/a */, + HAMMER2_INODE_BYTES); /* n/a */ + if (chain == NULL) + error = EIO; + } + hammer2_chain_unlock(hmp, parent); + + /* + * Handle the error case + */ + if (error) { + KKASSERT(chain == NULL); + return (error); + } + nip = chain->u.ip; + hammer2_chain_modify(hmp, chain, 0); + nip->ip_data = oip->ip_data; + + if (name) { + /* + * Directory entries are inodes so if the name has changed + * we have to update the inode. + */ + KKASSERT(name_len < HAMMER2_INODE_MAXNAME); + bcopy(name, nip->ip_data.filename, name_len); + nip->ip_data.name_key = lhc; + nip->ip_data.name_len = name_len; + } else { + /* + * Directory entries are inodes but this is a hidden hardlink + * target. The name isn't used but to ease debugging give it + * a name after its inode number. + */ + nip->ip_data = oip->ip_data; + ksnprintf(nip->ip_data.filename, sizeof(nip->ip_data.filename), + "0x%016jx", (intmax_t)nip->ip_data.inum); + nip->ip_data.name_len = strlen(nip->ip_data.filename); + nip->ip_data.name_key = lhc; + } + *nipp = nip; + + return (0); +} + + /* * Connect inode (ip) to the specified directory using the specified name. * (ip) must be locked. * - * If (ip) represents a hidden hardlink target file then the inode we create - * for the directory entry will be setup as OBJTYPE_HARDLINK. + * If (ip) is not currently connected we simply connect it up. + * + * If (ip) is already connected we create a OBJTYPE_HARDLINK entry which + * points to (ip)'s inode number. (ip) is expected to be the terminus of + * the hardlink sitting as a hidden file in a common parent directory + * in this situation. + * + * If (nipp) is non-NULL then (*nip) is set to point at the new inode + * and returned locked. */ int -hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip, +hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip, const uint8_t *name, size_t name_len) { hammer2_mount_t *hmp = dip->hmp; hammer2_chain_t *chain; hammer2_chain_t *parent; + hammer2_inode_t *nip; hammer2_key_t lhc; int error; + int hlink; lhc = hammer2_dirhash(name, name_len); + hlink = ((oip->chain.flags & HAMMER2_CHAIN_DELETED) == 0); /* * Locate the inode or indirect block to create the new @@ -365,9 +473,19 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip, * will be properly updated. */ if (error == 0) { - chain = hammer2_chain_create(hmp, parent, &ip->chain, lhc, 0, - HAMMER2_BREF_TYPE_INODE /* n/a */, - HAMMER2_INODE_BYTES); /* n/a */ + if (hlink) { + chain = hammer2_chain_create(hmp, parent, + NULL, lhc, 0, + HAMMER2_BREF_TYPE_INODE, + HAMMER2_INODE_BYTES); + } else { + chain = hammer2_chain_create(hmp, parent, + &oip->chain, lhc, 0, + HAMMER2_BREF_TYPE_INODE, + HAMMER2_INODE_BYTES); + if (chain) + KKASSERT(chain == &oip->chain); + } if (chain == NULL) error = EIO; } @@ -384,14 +502,33 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip, /* * Directory entries are inodes so if the name has changed we have * to update the inode. + * + * When creating an OBJTYPE_HARDLINK entry remember to unlock the + * chain, the caller will access the hardlink via the actual hardlink + * target file and not the hardlink pointer entry. */ - if (ip->ip_data.name_len != name_len || - bcmp(ip->ip_data.filename, name, name_len) != 0) { + if (hlink) { + nip = chain->u.ip; hammer2_chain_modify(hmp, chain, 0); KKASSERT(name_len < HAMMER2_INODE_MAXNAME); - bcopy(name, ip->ip_data.filename, name_len); - ip->ip_data.name_key = lhc; - ip->ip_data.name_len = name_len; + bcopy(name, nip->ip_data.filename, name_len); + nip->ip_data.name_key = lhc; + nip->ip_data.name_len = name_len; + nip->ip_data.target_type = oip->ip_data.type; + nip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK; + nip->ip_data.inum = oip->ip_data.inum; + kprintf("created hardlink %*.*s\n", + (int)name_len, (int)name_len, name); + hammer2_chain_unlock(hmp, chain); + } else { + if (oip->ip_data.name_len != name_len || + bcmp(oip->ip_data.filename, name, name_len) != 0) { + hammer2_chain_modify(hmp, chain, 0); + KKASSERT(name_len < HAMMER2_INODE_MAXNAME); + bcopy(name, oip->ip_data.filename, name_len); + oip->ip_data.name_key = lhc; + oip->ip_data.name_len = name_len; + } } /*nip->ip_data.nlinks = 1;*/ @@ -404,14 +541,6 @@ hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *ip, * * isdir determines whether a directory/non-directory check should be made. * No check is made if isdir is set to -1. - * - * adjlinks tells unlink that we want to adjust the nlinks count of the - * inode. When removing the last link for a NON forwarding entry we can - * just ignore the link count... no point updating the inode that we are - * about to dereference, it would just result in a lot of wasted I/O. - * - * However, if the entry is a forwarding entry (aka a hardlink), and adjlinks - * is non-zero, we have to locate the hardlink and adjust its nlinks field. */ int hammer2_unlink_file(hammer2_inode_t *dip, @@ -423,10 +552,13 @@ hammer2_unlink_file(hammer2_inode_t *dip, hammer2_chain_t *dparent; hammer2_chain_t *dchain; hammer2_key_t lhc; + hammer2_inode_t *ip; + hammer2_inode_t *oip; int error; + uint8_t type; error = 0; - + oip = NULL; hmp = dip->hmp; lhc = hammer2_dirhash(name, name_len); @@ -457,24 +589,34 @@ hammer2_unlink_file(hammer2_inode_t *dip, hammer2_chain_unlock(hmp, parent); return ENOENT; } - if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY && - isdir == 0) { + if ((type = chain->data->ipdata.type) == HAMMER2_OBJTYPE_HARDLINK) + type = chain->data->ipdata.target_type; + + if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) { error = ENOTDIR; goto done; } - if (chain->data->ipdata.type != HAMMER2_OBJTYPE_DIRECTORY && - isdir == 1) { + if (type != HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) { error = EISDIR; goto done; } + /* + * Hardlink must be resolved. We can't hold parent locked while we + * do this or we could deadlock. + */ + if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) { + hammer2_chain_unlock(hmp, parent); + parent = NULL; + error = hammer2_hardlink_find(dip, &chain, &oip); + } + /* * If this is a directory the directory must be empty. However, if * isdir < 0 we are doing a rename and the directory does not have * to be empty. */ - if (chain->data->ipdata.type == HAMMER2_OBJTYPE_DIRECTORY && - isdir >= 0) { + if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir >= 0) { dparent = chain; hammer2_chain_lock(hmp, dparent, HAMMER2_RESOLVE_ALWAYS); dchain = hammer2_chain_lookup(hmp, &dparent, @@ -492,20 +634,67 @@ hammer2_unlink_file(hammer2_inode_t *dip, } /* - * Found, the chain represents the inode. Remove the parent reference - * to the chain. The chain itself is no longer referenced and will - * be marked unmodified by hammer2_chain_delete(), avoiding unnecessary - * I/O. + * Ok, we can now unlink the chain. We always decrement nlinks even + * if the entry can be deleted in case someone has the file open and + * does an fstat(). + * + * The chain itself will no longer have a media reference. When the + * last vnode/ip ref goes away the chain will be marked unmodified, + * avoiding unnecessary I/O. */ - hammer2_chain_delete(hmp, parent, chain); - /* XXX nlinks (hardlink special case) */ - /* XXX nlinks (parent directory) */ + if (oip) { + /* + * If this was a hardlink we first delete the hardlink + * pointer entry. + */ + parent = oip->chain.parent; + hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_lock(hmp, &oip->chain, HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_delete(hmp, parent, &oip->chain); + hammer2_chain_unlock(hmp, &oip->chain); + hammer2_chain_unlock(hmp, parent); + parent = NULL; + + /* + * Then decrement nlinks on hardlink target. + */ + ip = chain->u.ip; + if (ip->ip_data.nlinks == 1) { + dparent = chain->parent; + hammer2_chain_ref(hmp, chain); + hammer2_chain_unlock(hmp, chain); + hammer2_chain_lock(hmp, dparent, + HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_lock(hmp, chain, HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_drop(hmp, chain); + hammer2_chain_modify(hmp, chain, 0); + --ip->ip_data.nlinks; + hammer2_chain_delete(hmp, dparent, chain); + hammer2_chain_unlock(hmp, dparent); + } else { + hammer2_chain_modify(hmp, chain, 0); + --ip->ip_data.nlinks; + } + } else { + /* + * Otherwise this was not a hardlink and we can just + * remove the entry and decrement nlinks. + */ + ip = chain->u.ip; + hammer2_chain_modify(hmp, chain, 0); + --ip->ip_data.nlinks; + hammer2_chain_delete(hmp, parent, chain); + } error = 0; done: - hammer2_chain_unlock(hmp, chain); - hammer2_chain_unlock(hmp, parent); + if (chain) + hammer2_chain_unlock(hmp, chain); + if (parent) + hammer2_chain_unlock(hmp, parent); + if (oip) + hammer2_chain_drop(oip->hmp, &oip->chain); return error; } @@ -544,18 +733,145 @@ hammer2_inode_unlock_nlinks(hammer2_inode_t *ip) * if necessary, replacing *ipp with the new inode chain element and * modifying the original inode chain element to OBJTYPE_HARDLINK. * + * If the original inode chain element was a prior incarnation of a hidden + * inode it can simply be deleted instead of converted. + * + * (*ipp)'s nlinks field is locked on entry and the new (*ipp)'s nlinks + * field will be locked on return (with the original's unlocked). + * * The link count is bumped if requested. */ int -hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip, - int bumpnlinks) +hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip) { + hammer2_mount_t *hmp; + hammer2_inode_t *oip = *ipp; + hammer2_inode_t *nip = NULL; + hammer2_inode_t *fdip; + hammer2_chain_t *parent; + int error; + + hmp = tdip->hmp; + if (hammer2_hardlink_enable < 0) return (0); if (hammer2_hardlink_enable == 0) return (ENOTSUP); - /* XXX */ - return (0); + + /* + * Find the common parent directory + */ + fdip = oip->pip; + while (fdip->depth > tdip->depth) { + fdip = fdip->pip; + KKASSERT(fdip != NULL); + } + while (tdip->depth > fdip->depth) { + tdip = tdip->pip; + KKASSERT(tdip != NULL); + } + while (fdip != tdip) { + fdip = fdip->pip; + tdip = tdip->pip; + KKASSERT(fdip != NULL); + KKASSERT(tdip != NULL); + } + + /* + * Nothing to do (except bump the link count) if the hardlink has + * already been consolidated in the correct place. + */ + if (oip->pip == fdip && + (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) { + kprintf("hardlink already consolidated correctly\n"); + nip = oip; + hammer2_inode_lock_ex(nip); + hammer2_chain_modify(hmp, &nip->chain, 0); + ++nip->ip_data.nlinks; + hammer2_inode_unlock_ex(nip); + return (0); + } + + /* + * Create a hidden inode directory entry in the parent, copying + * (*oip)'s state. Then replace oip with OBJTYPE_HARDLINK. + */ + error = hammer2_inode_duplicate(fdip, oip, &nip, NULL, 0); + if (error == 0) { + /* + * Bump nlinks on duplicated hidden inode. + */ + kprintf("hardlink consolidation success in parent dir %s\n", + fdip->ip_data.filename); + hammer2_inode_lock_nlinks(nip); + hammer2_inode_unlock_nlinks(oip); + hammer2_chain_modify(hmp, &nip->chain, 0); + ++nip->ip_data.nlinks; + hammer2_inode_unlock_ex(nip); + + if (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) { + /* + * Replace the old inode with an OBJTYPE_HARDLINK + * pointer. + */ + hammer2_inode_lock_ex(oip); + hammer2_chain_modify(hmp, &oip->chain, 0); + oip->ip_data.target_type = oip->ip_data.type; + oip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK; + oip->ip_data.uflags = 0; + oip->ip_data.rmajor = 0; + oip->ip_data.rminor = 0; + oip->ip_data.ctime = 0; + oip->ip_data.mtime = 0; + oip->ip_data.atime = 0; + oip->ip_data.btime = 0; + bzero(&oip->ip_data.uid, sizeof(oip->ip_data.uid)); + bzero(&oip->ip_data.gid, sizeof(oip->ip_data.gid)); + oip->ip_data.op_flags = HAMMER2_OPFLAG_DIRECTDATA; + oip->ip_data.cap_flags = 0; + oip->ip_data.mode = 0; + oip->ip_data.size = 0; + oip->ip_data.nlinks = 1; + oip->ip_data.iparent = 0; /* XXX */ + oip->ip_data.pfs_type = 0; + oip->ip_data.pfs_inum = 0; + bzero(&oip->ip_data.pfs_id, + sizeof(oip->ip_data.pfs_id)); + bzero(&oip->ip_data.pfs_fsid, + sizeof(oip->ip_data.pfs_fsid)); + oip->ip_data.data_quota = 0; + oip->ip_data.data_count = 0; + oip->ip_data.inode_quota = 0; + oip->ip_data.inode_count = 0; + oip->ip_data.attr_tid = 0; + oip->ip_data.dirent_tid = 0; + bzero(&oip->ip_data.u, sizeof(oip->ip_data.u)); + /* XXX transaction ids */ + + hammer2_inode_unlock_ex(oip); + } else { + /* + * The old inode was a hardlink target, which we + * have now moved. We must delete it so the new + * hardlink target at a higher directory level + * becomes the only hardlink target for this inode. + */ + kprintf("DELETE INVISIBLE\n"); + parent = oip->chain.parent; + hammer2_chain_lock(hmp, parent, + HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_lock(hmp, &oip->chain, + HAMMER2_RESOLVE_ALWAYS); + hammer2_chain_delete(hmp, parent, &oip->chain); + hammer2_chain_unlock(hmp, &oip->chain); + hammer2_chain_unlock(hmp, parent); + } + *ipp = nip; + } else { + KKASSERT(nip == NULL); + } + + return (error); } /* @@ -585,5 +901,41 @@ int hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp, hammer2_inode_t **ipp) { - return (EIO); + hammer2_mount_t *hmp = dip->hmp; + hammer2_chain_t *chain = *chainp; + hammer2_chain_t *parent; + hammer2_inode_t *pip; + hammer2_key_t lhc; + + *ipp = chain->u.ip; + hammer2_inode_ref(chain->u.ip); + lhc = chain->u.ip->ip_data.inum; + + hammer2_inode_unlock_ex(chain->u.ip); + pip = chain->u.ip->pip; + + chain = NULL; + while (pip) { + parent = &pip->chain; + KKASSERT(parent->bref.type == HAMMER2_BREF_TYPE_INODE); + + hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS); + chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0); + if (chain) { + kprintf("found hardlink in dir %s\n", + pip->ip_data.filename); + } + hammer2_chain_unlock(hmp, parent); + if (chain) + break; + pip = pip->pip; + } + *chainp = chain; + if (chain) { + KKASSERT(chain->bref.type == HAMMER2_BREF_TYPE_INODE); + /* already locked */ + return (0); + } else { + return (EIO); + } } diff --git a/sys/vfs/hammer2/hammer2_subr.c b/sys/vfs/hammer2/hammer2_subr.c index ffe75bcfe1..51ed554ae1 100644 --- a/sys/vfs/hammer2/hammer2_subr.c +++ b/sys/vfs/hammer2/hammer2_subr.c @@ -146,7 +146,12 @@ hammer2_voldata_unlock(hammer2_mount_t *hmp) int hammer2_get_dtype(hammer2_inode_t *ip) { - switch(ip->ip_data.type) { + uint8_t type; + + if ((type = ip->ip_data.type) == HAMMER2_OBJTYPE_HARDLINK) + type = ip->ip_data.target_type; + + switch(type) { case HAMMER2_OBJTYPE_UNKNOWN: return (DT_UNKNOWN); case HAMMER2_OBJTYPE_DIRECTORY: diff --git a/sys/vfs/hammer2/hammer2_vfsops.c b/sys/vfs/hammer2/hammer2_vfsops.c index 69acd68dae..81b15d02b6 100644 --- a/sys/vfs/hammer2/hammer2_vfsops.c +++ b/sys/vfs/hammer2/hammer2_vfsops.c @@ -58,7 +58,7 @@ static struct lock hammer2_mntlk; int hammer2_debug; int hammer2_cluster_enable = 1; -int hammer2_hardlink_enable = 0; +int hammer2_hardlink_enable = 1; long hammer2_iod_file_read; long hammer2_iod_meta_read; long hammer2_iod_indr_read; diff --git a/sys/vfs/hammer2/hammer2_vnops.c b/sys/vfs/hammer2/hammer2_vnops.c index 8020b4eb43..b0066ac0a1 100644 --- a/sys/vfs/hammer2/hammer2_vnops.c +++ b/sys/vfs/hammer2/hammer2_vnops.c @@ -508,7 +508,8 @@ hammer2_vop_readdir(struct vop_readdir_args *ap) * or some entries will be missed. */ chain = hammer2_chain_next(hmp, &parent, chain, - 0, (hammer2_key_t)-1, 0); + HAMMER2_DIRHASH_VISIBLE, + (hammer2_key_t)-1, 0); if (chain) { saveoff = (chain->bref.key & HAMMER2_DIRHASH_USERMSK) + 1; @@ -1576,6 +1577,7 @@ hammer2_vop_nlink(struct vop_nlink_args *ap) { hammer2_inode_t *dip; /* target directory to create link in */ hammer2_inode_t *ip; /* inode we are hardlinking to */ + hammer2_inode_t *oip; hammer2_mount_t *hmp; struct namecache *ncp; const uint8_t *name; @@ -1590,7 +1592,7 @@ hammer2_vop_nlink(struct vop_nlink_args *ap) /* * (ip) is the inode we are linking to. */ - ip = VTOI(ap->a_vp); + ip = oip = VTOI(ap->a_vp); hammer2_inode_lock_nlinks(ip); ncp = ap->a_nch->ncp; @@ -1602,22 +1604,35 @@ hammer2_vop_nlink(struct vop_nlink_args *ap) * and move the nlinks lock if necessary. Tell the function to * bump the hardlink count on the consolidated file. */ - error = hammer2_hardlink_consolidate(&ip, dip, 1); + error = hammer2_hardlink_consolidate(&ip, dip); if (error) goto done; /* - * When creating a hardlink we have to bump the nlinks count - * in ip - * in ip after successfully connecting the directory entry to - * (ip). + * If the consolidation changed ip to a HARDLINK pointer we have + * to adjust the vnode to point to the actual ip. + */ + if (oip != ip) { + hammer2_chain_ref(hmp, &ip->chain); + hammer2_inode_lock_ex(ip); + hammer2_inode_lock_ex(oip); + ip->vp = ap->a_vp; + ap->a_vp->v_data = ip; + oip->vp = NULL; + hammer2_inode_unlock_ex(oip); + hammer2_inode_unlock_ex(ip); + hammer2_chain_drop(hmp, &oip->chain); + } + + /* + * The act of connecting the existing (ip) will properly bump the + * nlinks count. However, vp will incorrectly point at the old + * inode which has now been turned into a OBJTYPE_HARDLINK pointer. + * + * We must reconnect the vp. */ hammer2_chain_lock(hmp, &ip->chain, HAMMER2_RESOLVE_ALWAYS); error = hammer2_inode_connect(dip, ip, name, name_len); - if (error == 0) { - hammer2_chain_modify(ip->hmp, &ip->chain, 0); - ++ip->ip_data.nlinks; - } hammer2_chain_unlock(hmp, &ip->chain); if (error == 0) { cache_setunresolved(ap->a_nch); @@ -1880,7 +1895,10 @@ hammer2_vop_nrename(struct vop_nrename_args *ap) cache_setvp(ap->a_tnch, NULL); /* - * Disconnect ip from the source directory. + * Disconnect (fdip, fname) from the source directory. This will + * disconnect (ip) if it represents a direct file. If (ip) represents + * a hardlink the HARDLINK pointer object will be removed but the + * hardlink will stay intact. * * If (ip) is already hardlinked we have to resolve to a consolidated * file but we do not bump the nlinks count. (ip) must hold the nlinks @@ -1892,7 +1910,7 @@ hammer2_vop_nrename(struct vop_nrename_args *ap) * contents of the inode. */ if (ip->ip_data.nlinks > 1) { - error = hammer2_hardlink_consolidate(&ip, tdip, 0); + error = hammer2_hardlink_consolidate(&ip, tdip); if (error) goto done; } @@ -1900,9 +1918,6 @@ hammer2_vop_nrename(struct vop_nrename_args *ap) if (error) goto done; - if (ip->chain.parent != NULL) - panic("hammer2_vop_nrename(): rename source != ip!"); - /* * Reconnect ip to target directory. *