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