/* * Destroy an extranious chain. * * Both *parentp and *chainp are locked shared. * * On return, *chainp will be adjusted to point to the next element in the * iteration and locked shared. */ static int hammer2_sync_destroy(hammer2_thread_t *thr, hammer2_chain_t **parentp, hammer2_chain_t **chainp, hammer2_tid_t mtid, int idx) { hammer2_chain_t *chain; hammer2_chain_t *parent; hammer2_key_t key_next; hammer2_key_t save_key; int cache_index = -1; chain = *chainp; #if HAMMER2_THREAD_DEBUG if (hammer2_debug & 1) kprintf("destroy rec %p/%p slave %d %d.%016jx\n", *parentp, chain, idx, chain->bref.type, chain->bref.key); #endif save_key = chain->bref.key; if (save_key != HAMMER2_KEY_MAX) ++save_key; /* * Try to avoid unnecessary I/O. * * XXX accounting not propagated up properly. We might have to do * a RESOLVE_MAYBE here and pass 0 for the flags. */ hammer2_chain_unlock(chain); /* relock exclusive */ hammer2_chain_unlock(*parentp); hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_ALWAYS); hammer2_chain_lock(chain, HAMMER2_RESOLVE_NEVER); hammer2_chain_delete(*parentp, chain, mtid, HAMMER2_DELETE_PERMANENT); hammer2_chain_unlock(chain); hammer2_chain_drop(chain); chain = NULL; /* safety */ hammer2_chain_unlock(*parentp); /* relock shared */ hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_SHARED | HAMMER2_RESOLVE_ALWAYS); *chainp = hammer2_chain_lookup(&parent, &key_next, save_key, HAMMER2_KEY_MAX, &cache_index, HAMMER2_LOOKUP_SHARED | HAMMER2_LOOKUP_NODIRECT | HAMMER2_LOOKUP_NODATA); return 0; }
hammer2_chain_t * hammer2_inode_chain_and_parent(hammer2_inode_t *ip, int clindex, hammer2_chain_t **parentp, int how) { hammer2_chain_t *chain; hammer2_chain_t *parent; for (;;) { hammer2_spin_sh(&ip->cluster_spin); if (clindex >= ip->cluster.nchains) chain = NULL; else chain = ip->cluster.array[clindex].chain; if (chain) { hammer2_chain_ref(chain); hammer2_spin_unsh(&ip->cluster_spin); hammer2_chain_lock(chain, how); } else { hammer2_spin_unsh(&ip->cluster_spin); } /* * Get parent, lock order must be (parent, chain). */ parent = chain->parent; if (parent) { hammer2_chain_ref(parent); hammer2_chain_unlock(chain); hammer2_chain_lock(parent, how); hammer2_chain_lock(chain, how); } if (ip->cluster.array[clindex].chain == chain && chain->parent == parent) { break; } /* * Retry */ hammer2_chain_unlock(chain); hammer2_chain_drop(chain); if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } } *parentp = parent; return chain; }
/* * Replace the contents of the locked destination with the contents of the * locked source. Destination must have one ref. * * Returns with the destination still with one ref and the copied chains * with an additional lock (representing their state on the destination). * The original chains associated with the destination are unlocked. */ void hammer2_cluster_replace_locked(hammer2_cluster_t *dst, hammer2_cluster_t *src) { hammer2_chain_t *chain; int i; KKASSERT(dst->refs == 1); dst->focus = NULL; for (i = 0; i < src->nchains; ++i) { chain = src->array[i]; if (chain) { hammer2_chain_lock(chain, 0); if (i < dst->nchains && dst->array[i]) hammer2_chain_unlock(dst->array[i]); dst->array[i] = src->array[i]; if (dst->focus == NULL) dst->focus = chain; } } while (i < dst->nchains) { chain = dst->array[i]; if (chain) { hammer2_chain_unlock(chain); dst->array[i] = NULL; } ++i; } dst->nchains = src->nchains; }
/* * NOTE: We don't combine the inode/chain lock because putting away an * inode would otherwise confuse multiple lock holders of the inode. * * Shared locks are especially sensitive to having too many shared * lock counts (from the same thread) on certain paths which might * need to upgrade them. Only one count of a shared lock can be * upgraded. */ hammer2_chain_t * hammer2_inode_lock_sh(hammer2_inode_t *ip) { hammer2_chain_t *chain; hammer2_inode_ref(ip); for (;;) { ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED); chain = ip->chain; KKASSERT(chain != NULL); /* for now */ hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); /* * Resolve duplication races, resolve hardlinks by giving * up and cycling an exclusive lock. */ if ((chain->flags & HAMMER2_CHAIN_DUPLICATED) == 0 && chain->data->ipdata.type != HAMMER2_OBJTYPE_HARDLINK) { break; } hammer2_chain_unlock(chain); ccms_thread_unlock(&ip->topo_cst); chain = hammer2_inode_lock_ex(ip); hammer2_inode_unlock_ex(ip, chain); } return (chain); }
/* * NOTE: We don't combine the inode/chain lock because putting away an * inode would otherwise confuse multiple lock holders of the inode. * * Shared locks are especially sensitive to having too many shared * lock counts (from the same thread) on certain paths which might * need to upgrade them. Only one count of a shared lock can be * upgraded. */ hammer2_chain_t * hammer2_inode_lock_sh(hammer2_inode_t *ip) { hammer2_chain_t *chain; hammer2_inode_ref(ip); again: ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED); chain = ip->chain; KKASSERT(chain != NULL); /* for now */ hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); /* * Resolve duplication races */ if (hammer2_chain_refactor_test(chain, 1)) { hammer2_chain_unlock(chain); ccms_thread_unlock(&ip->topo_cst); chain = hammer2_inode_lock_ex(ip); hammer2_inode_unlock_ex(ip, chain); goto again; } return (chain); }
/* * NOTE: We don't combine the inode/chain lock because putting away an * inode would otherwise confuse multiple lock holders of the inode. * * Shared locks are especially sensitive to having too many shared * lock counts (from the same thread) on certain paths which might * need to upgrade them. Only one count of a shared lock can be * upgraded. */ hammer2_chain_t * hammer2_inode_lock_sh(hammer2_inode_t *ip) { hammer2_chain_t *chain; hammer2_inode_ref(ip); ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED); chain = ip->chain; KKASSERT(chain != NULL); /* for now */ hammer2_chain_lock(ip->hmp, chain, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); return (chain); }
/* * HAMMER2 inode locks * * HAMMER2 offers shared locks and exclusive locks on inodes. * * An inode's ip->chain pointer is resolved and stable while an inode is * locked, and can be cleaned out at any time (become NULL) when an inode * is not locked. * * The underlying chain is also locked and returned. * * NOTE: We don't combine the inode/chain lock because putting away an * inode would otherwise confuse multiple lock holders of the inode. */ hammer2_chain_t * hammer2_inode_lock_ex(hammer2_inode_t *ip) { hammer2_chain_t *chain; hammer2_inode_ref(ip); ccms_thread_lock(&ip->topo_cst, CCMS_STATE_EXCLUSIVE); /* * ip->chain fixup. Certain duplications used to move inodes * into indirect blocks (for example) can cause ip->chain to * become stale. */ again: chain = ip->chain; if (hammer2_chain_refactor_test(chain, 1)) { spin_lock(&chain->core->cst.spin); while (hammer2_chain_refactor_test(chain, 1)) chain = chain->next_parent; if (ip->chain != chain) { hammer2_chain_ref(chain); spin_unlock(&chain->core->cst.spin); hammer2_inode_repoint(ip, NULL, chain); hammer2_chain_drop(chain); } else { spin_unlock(&chain->core->cst.spin); } } KKASSERT(chain != NULL); /* for now */ hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS); /* * Resolve duplication races */ if (hammer2_chain_refactor_test(chain, 1)) { hammer2_chain_unlock(chain); goto again; } return (chain); }
/* * Select a chain out of an inode's cluster and lock it. * * The inode does not have to be locked. */ hammer2_chain_t * hammer2_inode_chain(hammer2_inode_t *ip, int clindex, int how) { hammer2_chain_t *chain; hammer2_cluster_t *cluster; hammer2_spin_sh(&ip->cluster_spin); cluster = &ip->cluster; if (clindex >= cluster->nchains) chain = NULL; else chain = cluster->array[clindex].chain; if (chain) { hammer2_chain_ref(chain); hammer2_spin_unsh(&ip->cluster_spin); hammer2_chain_lock(chain, how); } else { hammer2_spin_unsh(&ip->cluster_spin); } return chain; }
/* * chain may have been moved around by the create. */ void hammer2_chain_refactor(hammer2_chain_t **chainp) { hammer2_chain_t *chain = *chainp; hammer2_chain_core_t *core; core = chain->core; while (chain->flags & HAMMER2_CHAIN_DUPLICATED) { spin_lock(&core->cst.spin); chain = TAILQ_NEXT(chain, core_entry); while (chain->flags & HAMMER2_CHAIN_DUPLICATED) chain = TAILQ_NEXT(chain, core_entry); hammer2_chain_ref(chain); spin_unlock(&core->cst.spin); KKASSERT(chain->core == core); hammer2_chain_unlock(*chainp); hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_NOREF); /* eat ref */ *chainp = chain; } }
/* * 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) { 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); 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); } }
/* * Lock and ref a cluster. This adds a ref to the cluster and its chains * and then locks them. */ int hammer2_cluster_lock(hammer2_cluster_t *cluster, int how) { hammer2_chain_t *chain; int i; int error; error = 0; atomic_add_int(&cluster->refs, 1); for (i = 0; i < cluster->nchains; ++i) { chain = cluster->array[i]; if (chain) { error = hammer2_chain_lock(chain, how); if (error) { while (--i >= 0) hammer2_chain_unlock(cluster->array[i]); atomic_add_int(&cluster->refs, -1); break; } } } return error; }
/* * HAMMER2 inode locks * * HAMMER2 offers shared locks and exclusive locks on inodes. * * An inode's ip->chain pointer is resolved and stable while an inode is * locked, and can be cleaned out at any time (become NULL) when an inode * is not locked. * * This function handles duplication races and hardlink replacement races * which can cause ip's cached chain to become stale. * * The underlying chain is also locked and returned. * * NOTE: We don't combine the inode/chain lock because putting away an * inode would otherwise confuse multiple lock holders of the inode. */ hammer2_chain_t * hammer2_inode_lock_ex(hammer2_inode_t *ip) { hammer2_chain_t *chain; hammer2_chain_t *ochain; hammer2_chain_core_t *core; int error; hammer2_inode_ref(ip); ccms_thread_lock(&ip->topo_cst, CCMS_STATE_EXCLUSIVE); chain = ip->chain; core = chain->core; for (;;) { if (chain->flags & HAMMER2_CHAIN_DUPLICATED) { spin_lock(&core->cst.spin); while (chain->flags & HAMMER2_CHAIN_DUPLICATED) chain = TAILQ_NEXT(chain, core_entry); hammer2_chain_ref(chain); spin_unlock(&core->cst.spin); hammer2_inode_repoint(ip, NULL, chain); hammer2_chain_drop(chain); } hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS); if ((chain->flags & HAMMER2_CHAIN_DUPLICATED) == 0) break; hammer2_chain_unlock(chain); } if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK && (chain->flags & HAMMER2_CHAIN_DELETED) == 0) { error = hammer2_hardlink_find(ip->pip, &chain, &ochain); hammer2_chain_drop(ochain); KKASSERT(error == 0); /* XXX error handling */ } return (chain); }
/* * chain may have been moved around by the create. */ static void hammer2_chain_refactor(hammer2_chain_t **chainp) { hammer2_chain_t *chain = *chainp; hammer2_chain_core_t *core; core = chain->core; spin_lock(&core->cst.spin); while (hammer2_chain_refactor_test(chain, 1)) { chain = chain->next_parent; while (hammer2_chain_refactor_test(chain, 1)) chain = chain->next_parent; hammer2_chain_ref(chain); spin_unlock(&core->cst.spin); hammer2_chain_unlock(*chainp); hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_NOREF); /* eat ref */ *chainp = chain; spin_lock(&core->cst.spin); } spin_unlock(&core->cst.spin); }
/* * Create a new inode in the specified directory using the vattr to * figure out the type of inode. * * If no error occurs the new inode with its chain locked is returned in * *nipp, otherwise an error is returned and *nipp is set to NULL. * * If vap and/or cred are NULL the related fields are not set and the * inode type defaults to a directory. This is used when creating PFSs * under the super-root, so the inode number is set to 1 in this case. */ 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) { hammer2_mount_t *hmp = dip->hmp; hammer2_chain_t *chain; hammer2_chain_t *parent; hammer2_inode_t *nip; hammer2_key_t lhc; int error; uid_t xuid; 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. */ 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_VISIBLE) == 0) error = ENOSPC; if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK) error = ENOSPC; hammer2_chain_unlock(hmp, chain); chain = NULL; ++lhc; } if (error == 0) { chain = hammer2_chain_create(hmp, parent, NULL, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); if (chain == NULL) error = EIO; } hammer2_chain_unlock(hmp, parent); /* * Handle the error case */ if (error) { KKASSERT(chain == NULL); *nipp = NULL; return (error); } /* * Set up the new inode */ nip = chain->u.ip; *nipp = nip; hammer2_voldata_lock(hmp); if (vap) { nip->ip_data.type = hammer2_get_obj_type(vap->va_type); nip->ip_data.inum = hmp->voldata.alloc_tid++; /* XXX modify/lock */ } else { nip->ip_data.type = HAMMER2_OBJTYPE_DIRECTORY; nip->ip_data.inum = 1; } hammer2_voldata_unlock(hmp); nip->ip_data.version = HAMMER2_INODE_VERSION_ONE; hammer2_update_time(&nip->ip_data.ctime); nip->ip_data.mtime = nip->ip_data.ctime; if (vap) nip->ip_data.mode = vap->va_mode; nip->ip_data.nlinks = 1; if (vap) { if (dip) { xuid = hammer2_to_unix_xid(&dip->ip_data.uid); xuid = vop_helper_create_uid(dip->pmp->mp, dip->ip_data.mode, xuid, cred, &vap->va_mode); } else { xuid = 0; } /* XXX if (vap->va_vaflags & VA_UID_UUID_VALID) nip->ip_data.uid = vap->va_uid_uuid; else if (vap->va_uid != (uid_t)VNOVAL) hammer2_guid_to_uuid(&nip->ip_data.uid, vap->va_uid); else */ hammer2_guid_to_uuid(&nip->ip_data.uid, xuid); /* XXX if (vap->va_vaflags & VA_GID_UUID_VALID) nip->ip_data.gid = vap->va_gid_uuid; else if (vap->va_gid != (gid_t)VNOVAL) hammer2_guid_to_uuid(&nip->ip_data.gid, vap->va_gid); else */ if (dip) nip->ip_data.gid = dip->ip_data.gid; } /* * Regular files and softlinks allow a small amount of data to be * directly embedded in the inode. This flag will be cleared if * the size is extended past the embedded limit. */ if (nip->ip_data.type == HAMMER2_OBJTYPE_REGFILE || nip->ip_data.type == HAMMER2_OBJTYPE_SOFTLINK) { nip->ip_data.op_flags |= HAMMER2_OPFLAG_DIRECTDATA; } 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; return (0); }
/* * Connect inode (oip) to the specified directory using the specified name. * (oip) must be locked. * * If (oip) is not currently connected we simply connect it up. * * If (oip) is already connected we create a OBJTYPE_HARDLINK entry which * points to (oip)'s inode number. (oip) is expected to be the terminus of * the hardlink sitting as a hidden file in a common parent directory * in this situation (thus the lock order is correct). */ int 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.parent != NULL); /* * In fake mode flush oip so we can just snapshot it downbelow. */ if (hlink && hammer2_hardlink_enable < 0) hammer2_chain_flush(hmp, &oip->chain, 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. */ 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) { 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; } hammer2_chain_unlock(hmp, parent); /* * Handle the error case */ if (error) { KKASSERT(chain == NULL); return (error); } /* * 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 (hlink && hammer2_hardlink_enable >= 0) { /* * Create the HARDLINK pointer. oip represents the hardlink * target in this situation. */ nip = chain->u.ip; hammer2_chain_modify(hmp, chain, 0); 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; nip->ip_data.target_type = oip->ip_data.type; nip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK; nip->ip_data.inum = oip->ip_data.inum; nip->ip_data.nlinks = 1; kprintf("created hardlink %*.*s\n", (int)name_len, (int)name_len, name); hammer2_chain_unlock(hmp, chain); } else if (hlink && hammer2_hardlink_enable < 0) { /* * Create a snapshot (hardlink fake mode for debugging). */ nip = chain->u.ip; nip->ip_data = oip->ip_data; hammer2_chain_modify(hmp, chain, 0); 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; kprintf("created fake hardlink %*.*s\n", (int)name_len, (int)name_len, name); hammer2_chain_unlock(hmp, chain); } else { /* * Normally disconnected inode (e.g. during a rename) that * was reconnected. We must fixup the name stored in * oip. * * We are using oip as chain, already locked by caller, * do not unlock it. */ hammer2_chain_modify(hmp, chain, 0); if (oip->ip_data.name_len != name_len || bcmp(oip->ip_data.filename, name, name_len) != 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; } oip->ip_data.nlinks = 1; } return (0); }
/* * Given an exclusively locked inode and chain we consolidate its chain * for hardlink creation, adding (nlinks) to the file's link count and * potentially relocating the inode to a directory common to ip->pip and tdip. * * Replaces (*chainp) if consolidation occurred, unlocking the old chain * and returning a new locked chain. * * NOTE! This function will also replace ip->chain. */ int hammer2_hardlink_consolidate(hammer2_trans_t *trans, hammer2_inode_t *ip, hammer2_chain_t **chainp, hammer2_inode_t *cdip, hammer2_chain_t **cdchainp, int nlinks) { hammer2_inode_data_t *ipdata; hammer2_chain_t *chain; hammer2_chain_t *nchain; int error; chain = *chainp; if (nlinks == 0 && /* no hardlink needed */ (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE)) { return (0); } if (hammer2_hardlink_enable < 0) { /* fake hardlinks */ return (0); } if (hammer2_hardlink_enable == 0) { /* disallow hardlinks */ hammer2_chain_unlock(chain); *chainp = NULL; return (ENOTSUP); } /* * 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 && (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) { if (nlinks) { hammer2_chain_modify(trans, &chain, 0); chain->data->ipdata.nlinks += nlinks; } error = 0; goto done; } /* * chain is the real inode. If it's visible we have to convert it * to a hardlink pointer. If it is not visible then it is already * a hardlink target and only needs to be deleted. */ KKASSERT((chain->flags & HAMMER2_CHAIN_DELETED) == 0); KKASSERT(chain->data->ipdata.type != HAMMER2_OBJTYPE_HARDLINK); if (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE) { /* * We are going to duplicate chain later, causing its * media block to be shifted to the duplicate. Even though * we are delete-duplicating nchain here it might decide not * to reallocate the block. Set FORCECOW to force it to. */ nchain = chain; hammer2_chain_lock(nchain, HAMMER2_RESOLVE_ALWAYS); atomic_set_int(&nchain->flags, HAMMER2_CHAIN_FORCECOW); hammer2_chain_delete_duplicate(trans, &nchain, HAMMER2_DELDUP_RECORE); KKASSERT((chain->flags & HAMMER2_CHAIN_DUPLICATED) == 0); ipdata = &nchain->data->ipdata; ipdata->target_type = ipdata->type; ipdata->type = HAMMER2_OBJTYPE_HARDLINK; ipdata->uflags = 0; ipdata->rmajor = 0; ipdata->rminor = 0; ipdata->ctime = 0; ipdata->mtime = 0; ipdata->atime = 0; ipdata->btime = 0; bzero(&ipdata->uid, sizeof(ipdata->uid)); bzero(&ipdata->gid, sizeof(ipdata->gid)); ipdata->op_flags = HAMMER2_OPFLAG_DIRECTDATA; ipdata->cap_flags = 0; ipdata->mode = 0; ipdata->size = 0; ipdata->nlinks = 1; ipdata->iparent = 0; /* XXX */ ipdata->pfs_type = 0; ipdata->pfs_inum = 0; bzero(&ipdata->pfs_clid, sizeof(ipdata->pfs_clid)); bzero(&ipdata->pfs_fsid, sizeof(ipdata->pfs_fsid)); ipdata->data_quota = 0; ipdata->data_count = 0; ipdata->inode_quota = 0; ipdata->inode_count = 0; ipdata->attr_tid = 0; ipdata->dirent_tid = 0; bzero(&ipdata->u, sizeof(ipdata->u)); /* XXX transaction ids */ } else { hammer2_chain_delete(trans, chain, 0); nchain = NULL; } /* * chain represents the hardlink target and is now flagged deleted. * duplicate it to the parent directory and adjust nlinks. * * WARNING! The shiftup() call can cause nchain to be moved into * an indirect block, and our nchain will wind up pointing * to the older/original version. */ KKASSERT(chain->flags & HAMMER2_CHAIN_DELETED); hammer2_hardlink_shiftup(trans, &chain, cdip, cdchainp, nlinks, &error); if (error == 0) hammer2_inode_repoint(ip, cdip, chain); /* * Unlock the original chain last as the lock blocked races against * the creation of the new hardlink target. */ if (nchain) hammer2_chain_unlock(nchain); done: /* * Cleanup, chain/nchain already dealt with. */ *chainp = chain; hammer2_inode_drop(cdip); return (error); }
/* * Unlink the file from the specified directory inode. 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. */ int hammer2_unlink_file(hammer2_inode_t *dip, const uint8_t *name, size_t name_len, int isdir, hammer2_inode_t *retain_ip) { hammer2_mount_t *hmp; hammer2_chain_t *parent; hammer2_chain_t *chain; 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); /* * Search for the filename in the directory */ parent = &dip->chain; hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS); chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0); while (chain) { if (chain->bref.type == HAMMER2_BREF_TYPE_INODE && chain->u.ip && name_len == chain->data->ipdata.name_len && bcmp(name, chain->data->ipdata.filename, name_len) == 0) { break; } chain = hammer2_chain_next(hmp, &parent, chain, lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0); } /* * Not found or wrong type (isdir < 0 disables the type check). */ if (chain == NULL) { hammer2_chain_unlock(hmp, parent); return ENOENT; } 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 (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. * * 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. */ if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir >= 0) { dparent = chain; hammer2_chain_lock(hmp, dparent, HAMMER2_RESOLVE_ALWAYS); dchain = hammer2_chain_lookup(hmp, &dparent, 0, (hammer2_key_t)-1, HAMMER2_LOOKUP_NODATA); if (dchain) { hammer2_chain_unlock(hmp, dchain); hammer2_chain_unlock(hmp, dparent); error = ENOTEMPTY; goto done; } hammer2_chain_unlock(hmp, dparent); dparent = NULL; /* dchain NULL */ } /* * 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 be in the on-media topology but * can still be flushed to the media (e.g. if an open descriptor * remains). When the last vnode/ip ref goes away the chain will * be marked unmodified, avoiding any further (now unnecesary) I/O. */ 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, (retain_ip == oip)); 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, 0); 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, (retain_ip == ip)); } error = 0; done: 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; }
/* * Unlink the file from the specified directory inode. 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. * * 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. * * NOTE! The underlying file can still be active with open descriptors * or if the chain is being manually held (e.g. for rename). * * The caller is responsible for fixing up ip->chain if e.g. a * rename occurs (see chain_duplicate()). */ int hammer2_unlink_file(hammer2_trans_t *trans, hammer2_inode_t *dip, const uint8_t *name, size_t name_len, int isdir, int *hlinkp, struct nchandle *nch) { hammer2_inode_data_t *ipdata; hammer2_chain_t *parent; hammer2_chain_t *ochain; hammer2_chain_t *chain; hammer2_chain_t *dparent; hammer2_chain_t *dchain; hammer2_key_t key_dummy; hammer2_key_t key_next; hammer2_key_t lhc; int error; int cache_index = -1; uint8_t type; error = 0; ochain = NULL; lhc = hammer2_dirhash(name, name_len); /* * Search for the filename in the directory */ if (hlinkp) *hlinkp = 0; parent = hammer2_inode_lock_ex(dip); chain = hammer2_chain_lookup(&parent, &key_next, lhc, lhc + HAMMER2_DIRHASH_LOMASK, &cache_index, 0); while (chain) { if (chain->bref.type == HAMMER2_BREF_TYPE_INODE && name_len == chain->data->ipdata.name_len && bcmp(name, chain->data->ipdata.filename, name_len) == 0) { break; } chain = hammer2_chain_next(&parent, chain, &key_next, key_next, lhc + HAMMER2_DIRHASH_LOMASK, &cache_index, 0); } hammer2_inode_unlock_ex(dip, NULL); /* retain parent */ /* * Not found or wrong type (isdir < 0 disables the type check). * If a hardlink pointer, type checks use the hardlink target. */ if (chain == NULL) { error = ENOENT; goto done; } if ((type = chain->data->ipdata.type) == HAMMER2_OBJTYPE_HARDLINK) { if (hlinkp) *hlinkp = 1; type = chain->data->ipdata.target_type; } if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) { error = ENOTDIR; goto done; } if (type != HAMMER2_OBJTYPE_DIRECTORY && isdir >= 1) { error = EISDIR; goto done; } /* * Hardlink must be resolved. We can't hold the parent locked * while we do this or we could deadlock. * * On success chain will be adjusted to point at the hardlink target * and ochain will point to the hardlink pointer in the original * directory. Otherwise chain remains pointing to the original. */ if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) { hammer2_chain_unlock(parent); parent = NULL; error = hammer2_hardlink_find(dip, &chain, &ochain); } /* * 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. * * 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. */ if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) { dparent = hammer2_chain_lookup_init(chain, 0); dchain = hammer2_chain_lookup(&dparent, &key_dummy, 0, (hammer2_key_t)-1, &cache_index, HAMMER2_LOOKUP_NODATA); if (dchain) { hammer2_chain_unlock(dchain); hammer2_chain_lookup_done(dparent); error = ENOTEMPTY; goto done; } hammer2_chain_lookup_done(dparent); dparent = NULL; /* dchain NULL */ } /* * 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 be in the on-media topology but * can still be flushed to the media (e.g. if an open descriptor * remains). When the last vnode/ip ref goes away the chain will * be marked unmodified, avoiding any further (now unnecesary) I/O. * * A non-NULL ochain indicates a hardlink. */ if (ochain) { /* * Delete the original hardlink pointer unconditionally. * (any open descriptors will migrate to the hardlink * target and have no affect on this operation). * * NOTE: parent from above is NULL when ochain != NULL * so we can reuse it. */ hammer2_chain_lock(ochain, HAMMER2_RESOLVE_ALWAYS); hammer2_chain_delete(trans, ochain, 0); hammer2_chain_unlock(ochain); } /* * Decrement nlinks on the hardlink target (or original file if * there it was not hardlinked). Delete the target when nlinks * reaches 0 with special handling if (isopen) is set. * * 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. * * NOTE! Files are unlinked by rename and then relinked. nch will be * passed as NULL in this situation. hammer2_inode_connect() * will bump nlinks. */ KKASSERT(chain != NULL); hammer2_chain_modify(trans, &chain, 0); ipdata = &chain->data->ipdata; --ipdata->nlinks; if ((int64_t)ipdata->nlinks < 0) /* XXX debugging */ ipdata->nlinks = 0; if (ipdata->nlinks == 0) { if ((chain->flags & HAMMER2_CHAIN_PFSROOT) && chain->pmp) { error = EINVAL; kprintf("hammer2: PFS \"%s\" cannot be deleted " "while still mounted\n", ipdata->filename); goto done; } if (nch && cache_isopen(nch)) { kprintf("WARNING: unlinking open file\n"); atomic_set_int(&chain->flags, HAMMER2_CHAIN_UNLINKED); hammer2_inode_move_to_hidden(trans, &chain, ipdata->inum); } else { hammer2_chain_delete(trans, chain, 0); } } error = 0; done: if (chain) hammer2_chain_unlock(chain); if (parent) hammer2_chain_lookup_done(parent); if (ochain) hammer2_chain_drop(ochain); return error; }
/* * cparent is locked exclusively, with an extra ref, cluster is not locked. * Replace element [i] in the cluster. */ static int hammer2_sync_replace(hammer2_thread_t *thr, hammer2_chain_t *parent, hammer2_chain_t *chain, hammer2_tid_t mtid, int idx, hammer2_chain_t *focus) { int nradix; uint8_t otype; #if HAMMER2_THREAD_DEBUG if (hammer2_debug & 1) kprintf("replace rec %p slave %d %d.%016jx mod=%016jx\n", chain, idx, focus->bref.type, focus->bref.key, mtid); #endif hammer2_chain_unlock(chain); hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS); if (chain->bytes != focus->bytes) { /* XXX what if compressed? */ nradix = hammer2_getradix(chain->bytes); hammer2_chain_resize(NULL, parent, chain, mtid, nradix, 0); } hammer2_chain_modify(chain, mtid, 0); otype = chain->bref.type; chain->bref.type = focus->bref.type; chain->bref.methods = focus->bref.methods; chain->bref.keybits = focus->bref.keybits; chain->bref.vradix = focus->bref.vradix; /* mirror_tid updated by flush */ KKASSERT(chain->bref.modify_tid == mtid); chain->bref.flags = focus->bref.flags; /* key already present */ /* check code will be recalculated */ chain->error = 0; /* * Copy data body. */ switch(chain->bref.type) { case HAMMER2_BREF_TYPE_INODE: if ((focus->data->ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA) == 0) { /* * If DIRECTDATA is transitioning to 0 or the old * chain is not an inode we have to initialize * the block table. */ if (otype != HAMMER2_BREF_TYPE_INODE || (chain->data->ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA)) { kprintf("chain inode trans away from dd\n"); bzero(&chain->data->ipdata.u, sizeof(chain->data->ipdata.u)); } bcopy(focus->data, chain->data, offsetof(hammer2_inode_data_t, u)); /* XXX setcheck on inode should not be needed */ hammer2_chain_setcheck(chain, chain->data); break; } /* fall through */ case HAMMER2_BREF_TYPE_DATA: bcopy(focus->data, chain->data, chain->bytes); hammer2_chain_setcheck(chain, chain->data); break; default: KKASSERT(0); break; } hammer2_chain_unlock(chain); hammer2_chain_lock(chain, HAMMER2_RESOLVE_SHARED | HAMMER2_RESOLVE_MAYBE); return 0; }
/* * 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. * * 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) { 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) /*XXX return (ENOTSUP); */ /* * 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. * * The duplication function will either flush or move any chains * under oip to the new hardlink target inode, retiring all chains * related to oip before returning. XXX vp->ip races. */ 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_clid, sizeof(oip->ip_data.pfs_clid)); 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, 0); hammer2_chain_unlock(hmp, &oip->chain); hammer2_chain_unlock(hmp, parent); } *ipp = nip; } else { KKASSERT(nip == NULL); } return (error); }
/* * Create a missing chain by copying the focus from another device. * * On entry *parentp and focus are both locked shared. The chain will be * created and returned in *chainp also locked shared. */ static int hammer2_sync_insert(hammer2_thread_t *thr, hammer2_chain_t **parentp, hammer2_chain_t **chainp, hammer2_tid_t mtid, int idx, hammer2_chain_t *focus) { hammer2_chain_t *chain; #if HAMMER2_THREAD_DEBUG if (hammer2_debug & 1) kprintf("insert rec par=%p/%d.%016jx slave %d %d.%016jx mod=%016jx\n", *parentp, (*parentp)->bref.type, (*parentp)->bref.key, idx, focus->bref.type, focus->bref.key, mtid); #endif /* * Create the missing chain. Exclusive locks are needed. * * Have to be careful to avoid deadlocks. */ if (*chainp) hammer2_chain_unlock(*chainp); hammer2_chain_unlock(*parentp); hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_ALWAYS); /* reissue lookup? */ chain = NULL; hammer2_chain_create(parentp, &chain, thr->pmp, focus->bref.key, focus->bref.keybits, focus->bref.type, focus->bytes, mtid, 0); hammer2_chain_modify(chain, mtid, 0); /* * Copy focus to new chain */ /* type already set */ chain->bref.methods = focus->bref.methods; /* keybits already set */ chain->bref.vradix = focus->bref.vradix; /* mirror_tid set by flush */ KKASSERT(chain->bref.modify_tid == mtid); chain->bref.flags = focus->bref.flags; /* key already present */ /* check code will be recalculated */ /* * Copy data body. */ switch(chain->bref.type) { case HAMMER2_BREF_TYPE_INODE: if ((focus->data->ipdata.meta.op_flags & HAMMER2_OPFLAG_DIRECTDATA) == 0) { bcopy(focus->data, chain->data, offsetof(hammer2_inode_data_t, u)); break; } /* fall through */ case HAMMER2_BREF_TYPE_DATA: bcopy(focus->data, chain->data, chain->bytes); hammer2_chain_setcheck(chain, chain->data); break; default: KKASSERT(0); break; } hammer2_chain_unlock(chain); /* unlock, leave ref */ if (*chainp) hammer2_chain_drop(*chainp); *chainp = chain; /* will be returned locked */ /* * Avoid ordering deadlock when relocking. */ hammer2_chain_unlock(*parentp); hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_SHARED | HAMMER2_RESOLVE_ALWAYS); hammer2_chain_lock(chain, HAMMER2_RESOLVE_SHARED | HAMMER2_RESOLVE_ALWAYS); return 0; }
/* * ochain represents the target file inode. We need to move it to the * specified common parent directory (dip) and rename it to a special * invisible "0xINODENUMBER" filename. * * We use chain_duplicate and duplicate ochain at the new location, * renaming it appropriately. We create a temporary chain and * then delete it to placemark where the duplicate will go. Both of * these use the inode number for (lhc) (the key), generating the * invisible filename. */ static hammer2_chain_t * hammer2_hardlink_shiftup(hammer2_trans_t *trans, hammer2_chain_t **ochainp, hammer2_inode_t *dip, int *errorp) { hammer2_inode_data_t *nipdata; hammer2_chain_t *parent; hammer2_chain_t *ochain; hammer2_chain_t *nchain; hammer2_chain_t *tmp; hammer2_key_t lhc; hammer2_blockref_t bref; ochain = *ochainp; *errorp = 0; lhc = ochain->data->ipdata.inum; 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. */ retry: parent = hammer2_chain_lookup_init(dip->chain, 0); nchain = hammer2_chain_lookup(&parent, lhc, lhc, 0); if (nchain) { kprintf("X3 chain %p parent %p dip %p dip->chain %p\n", nchain, parent, dip, dip->chain); hammer2_chain_unlock(nchain); nchain = NULL; *errorp = ENOSPC; #if 1 Debugger("X3"); #endif } /* * Create entry in common parent directory using the seek position * calculated above. */ if (*errorp == 0) { KKASSERT(nchain == NULL); *errorp = hammer2_chain_create(trans, &parent, &nchain, lhc, 0, HAMMER2_BREF_TYPE_INODE,/* n/a */ HAMMER2_INODE_BYTES); /* n/a */ hammer2_chain_refactor(&ochain); *ochainp = ochain; } /* * Cleanup and handle retries. */ if (*errorp == EAGAIN) { hammer2_chain_ref(parent); hammer2_chain_lookup_done(parent); hammer2_chain_wait(parent); hammer2_chain_drop(parent); goto retry; } /* * Handle the error case */ if (*errorp) { KKASSERT(nchain == NULL); hammer2_chain_lookup_done(parent); return (NULL); } /* * Use chain as a placeholder for (lhc), delete it and replace * it with our duplication. * * Gain a second lock on ochain for the duplication function to * unlock, maintain the caller's original lock across the call. * * This is a bit messy. */ hammer2_chain_delete(trans, nchain); hammer2_chain_lock(ochain, HAMMER2_RESOLVE_ALWAYS); tmp = ochain; bref = tmp->bref; bref.key = lhc; /* invisible dir entry key */ bref.keybits = 0; hammer2_chain_duplicate(trans, parent, nchain->index, &tmp, &bref); hammer2_chain_lookup_done(parent); hammer2_chain_unlock(nchain); /* no longer needed */ /* * Now set chain to our duplicate and modify it appropriately. * * 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. */ nchain = tmp; tmp = NULL; /* safety */ hammer2_chain_modify(trans, &nchain, HAMMER2_MODIFY_ASSERTNOCOPY); nipdata = &nchain->data->ipdata; ksnprintf(nipdata->filename, sizeof(nipdata->filename), "0x%016jx", (intmax_t)nipdata->inum); nipdata->name_len = strlen(nipdata->filename); nipdata->name_key = lhc; return (nchain); }
/* * Unlink the file from the specified directory inode. 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. * * NOTE! This function does not prevent the underlying file from still * being used if it has other refs (such as from an inode, or if it's * chain is manually held). However, the caller is responsible for * fixing up ip->chain if e.g. a rename occurs (see chain_duplicate()). */ int hammer2_unlink_file(hammer2_trans_t *trans, hammer2_inode_t *dip, const uint8_t *name, size_t name_len, int isdir, int *hlinkp) { hammer2_inode_data_t *ipdata; hammer2_chain_t *parent; hammer2_chain_t *ochain; hammer2_chain_t *chain; hammer2_chain_t *dparent; hammer2_chain_t *dchain; hammer2_key_t lhc; int error; uint8_t type; error = 0; ochain = NULL; lhc = hammer2_dirhash(name, name_len); /* * Search for the filename in the directory */ if (hlinkp) *hlinkp = 0; parent = hammer2_inode_lock_ex(dip); chain = hammer2_chain_lookup(&parent, lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0); while (chain) { if (chain->bref.type == HAMMER2_BREF_TYPE_INODE && name_len == chain->data->ipdata.name_len && bcmp(name, chain->data->ipdata.filename, name_len) == 0) { break; } chain = hammer2_chain_next(&parent, chain, lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0); } hammer2_inode_unlock_ex(dip, NULL); /* retain parent */ /* * Not found or wrong type (isdir < 0 disables the type check). * If a hardlink pointer, type checks use the hardlink target. */ if (chain == NULL) { error = ENOENT; goto done; } if ((type = chain->data->ipdata.type) == HAMMER2_OBJTYPE_HARDLINK) { if (hlinkp) *hlinkp = 1; type = chain->data->ipdata.target_type; } if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) { error = ENOTDIR; goto done; } 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. * * On success chain will be adjusted to point at the hardlink target * and ochain will point to the hardlink pointer in the original * directory. Otherwise chain remains pointing to the original. */ if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) { hammer2_chain_unlock(parent); parent = NULL; error = hammer2_hardlink_find(dip, &chain, &ochain); } /* * 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. * * 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. */ if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) { dparent = hammer2_chain_lookup_init(chain, 0); dchain = hammer2_chain_lookup(&dparent, 0, (hammer2_key_t)-1, HAMMER2_LOOKUP_NODATA); if (dchain) { hammer2_chain_unlock(dchain); hammer2_chain_lookup_done(dparent); error = ENOTEMPTY; goto done; } hammer2_chain_lookup_done(dparent); dparent = NULL; /* dchain NULL */ } /* * 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 be in the on-media topology but * can still be flushed to the media (e.g. if an open descriptor * remains). When the last vnode/ip ref goes away the chain will * be marked unmodified, avoiding any further (now unnecesary) I/O. * * A non-NULL ochain indicates a hardlink. */ if (ochain) { /* * Delete the original hardlink pointer. * * NOTE: parent from above is NULL when ochain != NULL * so we can reuse it. */ hammer2_chain_lock(ochain, HAMMER2_RESOLVE_ALWAYS); hammer2_chain_delete(trans, ochain); hammer2_chain_unlock(ochain); /* * Then decrement nlinks on hardlink target, deleting * the target when nlinks drops to 0. */ hammer2_chain_modify(trans, &chain, 0); --chain->data->ipdata.nlinks; if (chain->data->ipdata.nlinks == 0) hammer2_chain_delete(trans, chain); } else { /* * Otherwise this was not a hardlink and we can just * remove the entry and decrement nlinks. * * NOTE: *_get() integrates chain's lock into the inode lock. */ hammer2_chain_modify(trans, &chain, 0); ipdata = &chain->data->ipdata; --ipdata->nlinks; hammer2_chain_delete(trans, chain); } error = 0; done: if (chain) hammer2_chain_unlock(chain); if (parent) hammer2_chain_lookup_done(parent); if (ochain) hammer2_chain_drop(ochain); return error; }
/* * 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. * * XXX name needs to be NULL for now. */ 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 *parent; hammer2_chain_t *chain; 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; /* XXX bcmp name if not NULL */ if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK) error = ENOSPC; if ((lhc & HAMMER2_DIRHASH_VISIBLE) == 0) /* shouldn't happen */ error = ENOSPC; hammer2_chain_unlock(hmp, chain); chain = NULL; ++lhc; } /* * Create entry in common parent directory. */ 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); } /* * XXX This is currently a horrible hack. Well, if we wanted to * duplicate a file, i.e. as in a snapshot, we definitely * would have to flush it first. * * For hardlink target generation we can theoretically move any * active chain structures without flushing, but that gets really * iffy for code which follows chain->parent and ip->pip links. * * XXX only works with files. Duplicating a directory hierarchy * requires a flush but doesn't deal with races post-flush. * Well, it would work I guess, but you might catch some files * mid-operation. * * We cannot leave oip with any in-memory chains because (for a * hardlink), oip will become a OBJTYPE_HARDLINK which is just a * pointer to the real hardlink's inum and can't have any sub-chains. * XXX might be 0-ref chains left. */ hammer2_inode_lock_ex(oip); hammer2_chain_flush(hmp, &oip->chain, 0); hammer2_inode_unlock_ex(oip); /*KKASSERT(RB_EMPTY(&oip->chain.rbhead));*/ nip = chain->u.ip; hammer2_chain_modify(hmp, chain, 0); nip->ip_data = oip->ip_data; /* sync media data after flush */ 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. */ 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); }