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; }
/* * Repoint ip->chain to nchain. Caller must hold the inode exclusively * locked. * * ip->chain is set to nchain. The prior chain in ip->chain is dropped * and nchain is ref'd. */ void hammer2_inode_repoint(hammer2_inode_t *ip, hammer2_inode_t *pip, hammer2_chain_t *nchain) { hammer2_chain_t *ochain; hammer2_inode_t *opip; /* * Repoint ip->chain if requested. */ ochain = ip->chain; ip->chain = nchain; if (nchain) hammer2_chain_ref(nchain); if (ochain) hammer2_chain_drop(ochain); /* * Repoint ip->pip if requested (non-NULL pip). */ if (pip && ip->pip != pip) { opip = ip->pip; hammer2_inode_ref(pip); ip->pip = pip; if (opip) hammer2_inode_drop(opip); } }
/* * Copy a cluster, returned a ref'd cluster. All underlying chains * are also ref'd, but not locked. * * If HAMMER2_CLUSTER_COPY_CHAINS is specified, the chains are copied * to the new cluster and a reference is nominally added to them and to * the cluster. The cluster will have 1 ref. * * If HAMMER2_CLUSTER_COPY_NOREF is specified along with CHAINS, the chains * are copied but no additional references are made and the cluster will have * 0 refs. Callers must ref the cluster and the chains before dropping it * (typically by locking it). * * If flags are passed as 0 the copy is setup as if it contained the chains * but the chains will not be copied over, and the cluster will have 0 refs. * Callers must ref the cluster before dropping it (typically by locking it). */ hammer2_cluster_t * hammer2_cluster_copy(hammer2_cluster_t *ocluster, int copy_flags) { hammer2_pfsmount_t *pmp = ocluster->pmp; hammer2_cluster_t *ncluster; hammer2_chain_t *chain; int i; ncluster = kmalloc(sizeof(*ncluster), M_HAMMER2, M_WAITOK | M_ZERO); ncluster->pmp = pmp; ncluster->nchains = ocluster->nchains; ncluster->focus = ocluster->focus; ncluster->refs = (copy_flags & HAMMER2_CLUSTER_COPY_NOREF) ? 0 : 1; if ((copy_flags & HAMMER2_CLUSTER_COPY_NOCHAINS) == 0) { for (i = 0; i < ocluster->nchains; ++i) { chain = ocluster->array[i]; ncluster->array[i] = chain; if ((copy_flags & HAMMER2_CLUSTER_COPY_NOREF) == 0 && chain) { hammer2_chain_ref(chain); } } } return (ncluster); }
/* * Replace the contents of dst with src, adding a reference to src's chains. * dst is assumed to already have a ref and any chains present in dst are * assumed to be locked and will be unlocked. * * If the chains in src are locked, only one of (src) or (dst) should be * considered locked by the caller after return, not both. */ void hammer2_cluster_replace(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_ref(chain); if (i < dst->nchains && dst->array[i]) hammer2_chain_unlock(dst->array[i]); dst->array[i] = chain; 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; }
/* * The caller presents a locked *chainp pointing to a HAMMER2_BREF_TYPE_INODE * with an obj_type of HAMMER2_OBJTYPE_HARDLINK. This routine will gobble * the *chainp and return a new locked *chainp representing the file target * (the original *chainp will be unlocked). * * When a match is found the chain representing the original HARDLINK * will be returned in *ochainp with a ref, but not locked. * * When no match is found *chainp is set to NULL and EIO is returned. * (*ochainp) will still be set to the original chain with a ref but not * locked. */ int hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp, hammer2_chain_t **ochainp) { hammer2_chain_t *chain = *chainp; hammer2_chain_t *parent; hammer2_inode_t *ip; hammer2_inode_t *pip; hammer2_key_t key_dummy; hammer2_key_t lhc; int cache_index = -1; pip = dip; hammer2_inode_ref(pip); /* for loop */ hammer2_chain_ref(chain); /* for (*ochainp) */ *ochainp = chain; /* * Locate the hardlink. pip is referenced and not locked, * ipp. * * chain is reused. */ lhc = chain->data->ipdata.inum; hammer2_chain_unlock(chain); chain = NULL; while ((ip = pip) != NULL) { parent = hammer2_inode_lock_ex(ip); hammer2_inode_drop(ip); /* loop */ KKASSERT(parent->bref.type == HAMMER2_BREF_TYPE_INODE); chain = hammer2_chain_lookup(&parent, &key_dummy, lhc, lhc, &cache_index, 0); hammer2_chain_lookup_done(parent); /* discard parent */ if (chain) break; pip = ip->pip; /* safe, ip held locked */ if (pip) hammer2_inode_ref(pip); /* loop */ hammer2_inode_unlock_ex(ip, NULL); } /* * chain is locked, ip is locked. Unlock ip, return the locked * chain. *ipp is already set w/a ref count and not locked. * * (parent is already unlocked). */ if (ip) hammer2_inode_unlock_ex(ip, NULL); *chainp = chain; if (chain) { KKASSERT(chain->bref.type == HAMMER2_BREF_TYPE_INODE); /* already locked */ return (0); } else { return (EIO); } }
/* * Add a reference to a cluster. * * We must also ref the underlying chains in order to allow ref/unlock * sequences to later re-lock. */ void hammer2_cluster_ref(hammer2_cluster_t *cluster) { hammer2_chain_t *chain; int i; atomic_add_int(&cluster->refs, 1); for (i = 0; i < cluster->nchains; ++i) { chain = cluster->array[i]; if (chain) hammer2_chain_ref(chain); } }
/* * (Backend) Feed chain data through the cluster validator and back to * the frontend. Chains are fed from multiple nodes concurrently * and pipelined via per-node FIFOs in the XOP. * * No xop lock is needed because we are only manipulating fields under * our direct control. * * Returns 0 on success and a hammer error code if sync is permanently * lost. The caller retains a ref on the chain but by convention * the lock is typically inherited by the xop (caller loses lock). * * Returns non-zero on error. In this situation the caller retains a * ref on the chain but loses the lock (we unlock here). * * WARNING! The chain is moving between two different threads, it must * be locked SHARED to retain its data mapping, not exclusive. * When multiple operations are in progress at once, chains fed * back to the frontend for collection can wind up being locked * in different orders, only a shared lock can prevent a deadlock. * * Exclusive locks may only be used by a XOP backend node thread * temporarily, with no direct or indirect dependencies (aka * blocking/waiting) on other nodes. */ int hammer2_xop_feed(hammer2_xop_head_t *xop, hammer2_chain_t *chain, int clindex, int error) { hammer2_xop_fifo_t *fifo; /* * Multi-threaded entry into the XOP collector. We own the * fifo->wi for our clindex. */ fifo = &xop->collect[clindex]; while (fifo->ri == fifo->wi - HAMMER2_XOPFIFO) { tsleep_interlock(xop, 0); if (hammer2_xop_active(xop) == 0) { error = EINTR; goto done; } if (fifo->ri == fifo->wi - HAMMER2_XOPFIFO) { tsleep(xop, PINTERLOCKED, "h2feed", hz*60); } } if (chain) hammer2_chain_ref(chain); fifo->errors[fifo->wi & HAMMER2_XOPFIFO_MASK] = error; fifo->array[fifo->wi & HAMMER2_XOPFIFO_MASK] = chain; cpu_sfence(); ++fifo->wi; atomic_add_int(&xop->check_counter, 1); wakeup(&xop->check_counter); /* XXX optimize */ error = 0; /* * Cleanup. If an error occurred we eat the lock. If no error * occurred the fifo inherits the lock and gains an additional ref. * * The caller's ref remains in both cases. */ done: if (error && chain) hammer2_chain_unlock(chain); 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. * * 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; }
/* * Repoint a single element from the cluster to the ip. Used by the * synchronization threads to piecemeal update inodes. Does not change * focus and requires inode to be re-locked to clean-up flags (XXX). */ void hammer2_inode_repoint_one(hammer2_inode_t *ip, hammer2_cluster_t *cluster, int idx) { hammer2_chain_t *ochain; hammer2_chain_t *nchain; int i; hammer2_spin_ex(&ip->cluster_spin); KKASSERT(idx < cluster->nchains); if (idx < ip->cluster.nchains) { ochain = ip->cluster.array[idx].chain; nchain = cluster->array[idx].chain; } else { ochain = NULL; nchain = cluster->array[idx].chain; for (i = ip->cluster.nchains; i <= idx; ++i) { bzero(&ip->cluster.array[i], sizeof(ip->cluster.array[i])); ip->cluster.array[i].flags |= HAMMER2_CITEM_INVALID; } ip->cluster.nchains = idx + 1; } if (ochain != nchain) { /* * Make adjustments. */ ip->cluster.array[idx].chain = nchain; ip->cluster.array[idx].flags &= ~HAMMER2_CITEM_INVALID; ip->cluster.array[idx].flags |= cluster->array[idx].flags & HAMMER2_CITEM_INVALID; } hammer2_spin_unex(&ip->cluster_spin); if (ochain != nchain) { if (nchain) hammer2_chain_ref(nchain); if (ochain) hammer2_chain_drop(ochain); } }
/* * 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; } }
/* * 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); }
/* * Adding a ref to an inode is only legal if the inode already has at least * one ref. */ void hammer2_inode_ref(hammer2_inode_t *ip) { hammer2_chain_ref(ip->hmp, &ip->chain); }
/* * Get the vnode associated with the given inode, allocating the vnode if * necessary. The vnode will be returned exclusively locked. * * The caller must lock the inode (shared or exclusive). * * Great care must be taken to avoid deadlocks and vnode acquisition/reclaim * races. */ struct vnode * hammer2_igetv(hammer2_inode_t *ip, int *errorp) { struct vnode *vp; hammer2_pfsmount_t *pmp; ccms_state_t ostate; pmp = ip->pmp; KKASSERT(pmp != NULL); *errorp = 0; for (;;) { /* * Attempt to reuse an existing vnode assignment. It is * possible to race a reclaim so the vget() may fail. The * inode must be unlocked during the vget() to avoid a * deadlock against a reclaim. */ vp = ip->vp; if (vp) { /* * Inode must be unlocked during the vget() to avoid * possible deadlocks, vnode is held to prevent * destruction during the vget(). The vget() can * still fail if we lost a reclaim race on the vnode. */ /* XXX vhold_interlocked(vp); */ ccms_thread_unlock(&ip->chain.cst); /* XXX if (vget(vp, LK_EXCLUSIVE)) { vdrop(vp); ccms_thread_lock(&ip->chain.cst, CCMS_STATE_EXCLUSIVE); continue; } */ ccms_thread_lock(&ip->chain.cst, CCMS_STATE_EXCLUSIVE); /* XX vdrop(vp); */ /* vp still locked and ref from vget */ *errorp = 0; break; } /* * No vnode exists, allocate a new vnode. Beware of * allocation races. This function will return an * exclusively locked and referenced vnode. */ /* XXX *errorp = getnewvnode(VT_HAMMER2, pmp->mp, &vp, 0, 0); if (*errorp) { vp = NULL; break; } */ /* * Lock the inode and check for an allocation race. */ ostate = ccms_thread_lock_upgrade(&ip->chain.cst); if (ip->vp != NULL) { vp->v_type = VBAD; /* XXX vx_put(vp); */ ccms_thread_lock_restore(&ip->chain.cst, ostate); continue; } switch (ip->ip_data.type) { case HAMMER2_OBJTYPE_DIRECTORY: vp->v_type = VDIR; break; case HAMMER2_OBJTYPE_REGFILE: vp->v_type = VREG; /* XVFS vinitvmio(vp, ip->ip_data.size, HAMMER2_LBUFSIZE, (int)ip->ip_data.size & HAMMER2_LBUFMASK); */ break; case HAMMER2_OBJTYPE_SOFTLINK: /* * XXX for now we are using the generic file_read * and file_write code so we need a buffer cache * association. */ vp->v_type = VLNK; /* XVFS vinitvmio(vp, ip->ip_data.size, HAMMER2_LBUFSIZE, (int)ip->ip_data.size & HAMMER2_LBUFMASK); */ break; /* XXX FIFO */ default: panic("hammer2: unhandled objtype %d", ip->ip_data.type); break; } if (ip == pmp->iroot) /* vsetflags(vp, VROOT); */ vp->v_data = ip; ip->vp = vp; hammer2_chain_ref(ip->hmp, &ip->chain); /* vp association */ ccms_thread_lock_restore(&ip->chain.cst, ostate); break; } /* * Return non-NULL vp and *errorp == 0, or NULL vp and *errorp != 0. */ if (hammer2_debug & 0x0002) { kprintf("igetv vp %p refs %d aux %d\n", vp, vp->v_sysref.refcnt, vp->v_auxrefs); } return (vp); }
/* * 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. * * dip is not locked on entry. */ hammer2_inode_t * hammer2_inode_create(hammer2_trans_t *trans, hammer2_inode_t *dip, struct vattr *vap, struct ucred *cred, const uint8_t *name, size_t name_len, hammer2_chain_t **chainp, int *errorp) { hammer2_inode_data_t *dipdata; hammer2_inode_data_t *nipdata; hammer2_chain_t *chain; hammer2_chain_t *parent; hammer2_inode_t *nip; hammer2_key_t key_dummy; hammer2_key_t lhc; int error; uid_t xuid; uuid_t dip_uid; uuid_t dip_gid; uint32_t dip_mode; uint8_t dip_algo; int cache_index = -1; lhc = hammer2_dirhash(name, name_len); *errorp = 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. * * NOTE: hidden inodes do not have iterators. */ retry: parent = hammer2_inode_lock_ex(dip); dipdata = &dip->chain->data->ipdata; dip_uid = dipdata->uid; dip_gid = dipdata->gid; dip_mode = dipdata->mode; dip_algo = dipdata->comp_algo; error = 0; while (error == 0) { chain = hammer2_chain_lookup(&parent, &key_dummy, lhc, lhc, &cache_index, 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(chain); chain = NULL; ++lhc; } if (error == 0) { error = hammer2_chain_create(trans, &parent, &chain, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); } /* * Cleanup and handle retries. */ if (error == EAGAIN) { hammer2_chain_ref(parent); hammer2_inode_unlock_ex(dip, parent); hammer2_chain_wait(parent); hammer2_chain_drop(parent); goto retry; } hammer2_inode_unlock_ex(dip, parent); if (error) { KKASSERT(chain == NULL); *errorp = error; return (NULL); } /* * Set up the new inode. * * NOTE: *_get() integrates chain's lock into the inode lock. * * NOTE: Only one new inode can currently be created per * transaction. If the need arises we can adjust * hammer2_trans_init() to allow more. * * NOTE: nipdata will have chain's blockset data. */ chain->data->ipdata.inum = trans->inode_tid; nip = hammer2_inode_get(dip->pmp, dip, chain); nipdata = &chain->data->ipdata; if (vap) { KKASSERT(trans->inodes_created == 0); nipdata->type = hammer2_get_obj_type(vap->va_type); nipdata->inum = trans->inode_tid; ++trans->inodes_created; switch (nipdata->type) { case HAMMER2_OBJTYPE_CDEV: case HAMMER2_OBJTYPE_BDEV: nipdata->rmajor = vap->va_rmajor; nipdata->rminor = vap->va_rminor; break; default: break; } } else { nipdata->type = HAMMER2_OBJTYPE_DIRECTORY; nipdata->inum = 1; } /* Inherit parent's inode compression mode. */ nip->comp_heuristic = 0; nipdata->comp_algo = dip_algo; nipdata->version = HAMMER2_INODE_VERSION_ONE; hammer2_update_time(&nipdata->ctime); nipdata->mtime = nipdata->ctime; if (vap) nipdata->mode = vap->va_mode; nipdata->nlinks = 1; if (vap) { if (dip && dip->pmp) { xuid = hammer2_to_unix_xid(&dip_uid); xuid = vop_helper_create_uid(dip->pmp->mp, dip_mode, xuid, cred, &vap->va_mode); } else { /* super-root has no dip and/or pmp */ xuid = 0; } if (vap->va_vaflags & VA_UID_UUID_VALID) nipdata->uid = vap->va_uid_uuid; else if (vap->va_uid != (uid_t)VNOVAL) hammer2_guid_to_uuid(&nipdata->uid, vap->va_uid); else hammer2_guid_to_uuid(&nipdata->uid, xuid); if (vap->va_vaflags & VA_GID_UUID_VALID) nipdata->gid = vap->va_gid_uuid; else if (vap->va_gid != (gid_t)VNOVAL) hammer2_guid_to_uuid(&nipdata->gid, vap->va_gid); else if (dip) nipdata->gid = dip_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 (nipdata->type == HAMMER2_OBJTYPE_REGFILE || nipdata->type == HAMMER2_OBJTYPE_SOFTLINK) { nipdata->op_flags |= HAMMER2_OPFLAG_DIRECTDATA; } KKASSERT(name_len < HAMMER2_INODE_MAXNAME); bcopy(name, nipdata->filename, name_len); nipdata->name_key = lhc; nipdata->name_len = name_len; *chainp = chain; return (nip); }
void hammer2_inode_lock_nlinks(hammer2_inode_t *ip) { hammer2_chain_ref(ip->hmp, &ip->chain); }
/* * Repoint ip->cluster's chains to cluster's chains and fixup the default * focus. All items, valid or invalid, are repointed. hammer2_xop_start() * filters out invalid or non-matching elements. * * Caller must hold the inode and cluster exclusive locked, if not NULL, * must also be locked. * * Cluster may be NULL to clean out any chains in ip->cluster. */ void hammer2_inode_repoint(hammer2_inode_t *ip, hammer2_inode_t *pip, hammer2_cluster_t *cluster) { hammer2_chain_t *dropch[HAMMER2_MAXCLUSTER]; hammer2_chain_t *ochain; hammer2_chain_t *nchain; int i; bzero(dropch, sizeof(dropch)); /* * Replace chains in ip->cluster with chains from cluster and * adjust the focus if necessary. * * NOTE: nchain and/or ochain can be NULL due to gaps * in the cluster arrays. */ hammer2_spin_ex(&ip->cluster_spin); for (i = 0; cluster && i < cluster->nchains; ++i) { /* * Do not replace elements which are the same. Also handle * element count discrepancies. */ nchain = cluster->array[i].chain; if (i < ip->cluster.nchains) { ochain = ip->cluster.array[i].chain; if (ochain == nchain) continue; } else { ochain = NULL; } /* * Make adjustments */ ip->cluster.array[i].chain = nchain; ip->cluster.array[i].flags &= ~HAMMER2_CITEM_INVALID; ip->cluster.array[i].flags |= cluster->array[i].flags & HAMMER2_CITEM_INVALID; if (nchain) hammer2_chain_ref(nchain); dropch[i] = ochain; } /* * Release any left-over chains in ip->cluster. */ while (i < ip->cluster.nchains) { nchain = ip->cluster.array[i].chain; if (nchain) { ip->cluster.array[i].chain = NULL; ip->cluster.array[i].flags |= HAMMER2_CITEM_INVALID; } dropch[i] = nchain; ++i; } /* * Fixup fields. Note that the inode-embedded cluster is never * directly locked. */ if (cluster) { ip->cluster.nchains = cluster->nchains; ip->cluster.focus = cluster->focus; ip->cluster.flags = cluster->flags & ~HAMMER2_CLUSTER_LOCKED; } else { ip->cluster.nchains = 0; ip->cluster.focus = NULL; ip->cluster.flags &= ~HAMMER2_CLUSTER_ZFLAGS; } hammer2_spin_unex(&ip->cluster_spin); /* * Cleanup outside of spinlock */ while (--i >= 0) { if (dropch[i]) hammer2_chain_drop(dropch[i]); } }
/* * 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; }
/* * 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); }