/* * Create a new cluster using the specified key */ int hammer2_cluster_create(hammer2_trans_t *trans, hammer2_cluster_t *cparent, hammer2_cluster_t **clusterp, hammer2_key_t key, int keybits, int type, size_t bytes) { hammer2_cluster_t *cluster; hammer2_pfsmount_t *pmp; int error; int i; pmp = trans->pmp; /* can be NULL */ if ((cluster = *clusterp) == NULL) { cluster = kmalloc(sizeof(*cluster), M_HAMMER2, M_WAITOK | M_ZERO); cluster->pmp = pmp; /* can be NULL */ cluster->refs = 1; } cluster->focus = NULL; cparent->focus = NULL; /* * NOTE: cluster->array[] entries can initially be NULL. If * *clusterp is supplied, skip NULL entries, otherwise * create new chains. */ for (i = 0; i < cparent->nchains; ++i) { if (*clusterp && cluster->array[i] == NULL) { if (cparent->focus == NULL) cparent->focus = cparent->array[i]; continue; } error = hammer2_chain_create(trans, &cparent->array[i], &cluster->array[i], pmp, key, keybits, type, bytes); KKASSERT(error == 0); if (cparent->focus == NULL) cparent->focus = cparent->array[i]; if (cluster->focus == NULL) cluster->focus = cluster->array[i]; } cluster->nchains = i; *clusterp = cluster; return error; }
/* * Connect the target inode represented by (*chainp) to the media topology * at (dip, name, len). The caller can pass a rough *chainp, this function * will issue lookup()s to position the parent chain properly for the * chain insertion. * * If hlink is TRUE this function creates an OBJTYPE_HARDLINK directory * entry instead of connecting (*chainp). * * If hlink is FALSE this function uses chain_duplicate() to make a copy * if (*chainp) in the directory entry. (*chainp) is likely to be deleted * by the caller in this case (e.g. rename). */ int hammer2_inode_connect(hammer2_trans_t *trans, hammer2_chain_t **chainp, int hlink, hammer2_inode_t *dip, hammer2_chain_t **dchainp, const uint8_t *name, size_t name_len, hammer2_key_t lhc) { hammer2_inode_data_t *ipdata; hammer2_chain_t *nchain; hammer2_chain_t *ochain; hammer2_key_t key_dummy; int cache_index = -1; int error; /* * Since ochain is either disconnected from the topology or represents * a hardlink terminus which is always a parent of or equal to dip, * we should be able to safely lock dip->chain for our setup. * * WARNING! Must use inode_lock_ex() on dip to handle a stale * dip->chain cache. */ ochain = *chainp; /* * If name is non-NULL we calculate lhc, else we use the passed-in * lhc. */ if (name) { lhc = hammer2_dirhash(name, name_len); /* * Locate the inode or indirect block to create the new * entry in. At the same time check for key collisions * and iterate until we don't get one. */ error = 0; while (error == 0) { nchain = hammer2_chain_lookup(dchainp, &key_dummy, lhc, lhc, &cache_index, 0); if (nchain == NULL) break; if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK) { error = ENOSPC; } hammer2_chain_unlock(nchain); nchain = NULL; ++lhc; } } else { /* * Reconnect to specific key (used when moving * unlinked-but-open files into the hidden directory). */ nchain = hammer2_chain_lookup(dchainp, &key_dummy, lhc, lhc, &cache_index, 0); KKASSERT(nchain == NULL); } if (error == 0) { if (hlink) { /* * Hardlink pointer needed, create totally fresh * directory entry. * * We must refactor ochain because it might have * been shifted into an indirect chain by the * create. */ KKASSERT(nchain == NULL); error = hammer2_chain_create(trans, dchainp, &nchain, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); hammer2_chain_refactor(&ochain); } else { /* * Reconnect the original chain and rename. Use * chain_duplicate(). The caller will likely delete * or has already deleted the original chain in * this case. * * NOTE: chain_duplicate() generates a new chain * with CHAIN_DELETED cleared (ochain typically * has it set from the file unlink). * * WARNING! Can cause held-over chains to require a * refactor. Fortunately we have none (our * locked chains are passed into and * modified by the call). */ nchain = ochain; ochain = NULL; hammer2_chain_duplicate(trans, NULL, &nchain, NULL, 0, 3); error = hammer2_chain_create(trans, dchainp, &nchain, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); } } /* * Unlock stuff. */ KKASSERT(error != EAGAIN); /* * nchain should be NULL on error, leave ochain (== *chainp) alone. */ if (error) { KKASSERT(nchain == 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, so we must still * return ochain. */ if (hlink && hammer2_hardlink_enable >= 0) { /* * Create the HARDLINK pointer. oip represents the hardlink * target in this situation. * * We will return ochain (the hardlink target). */ hammer2_chain_modify(trans, &nchain, 0); KKASSERT(name_len < HAMMER2_INODE_MAXNAME); ipdata = &nchain->data->ipdata; bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; ipdata->target_type = ochain->data->ipdata.type; ipdata->type = HAMMER2_OBJTYPE_HARDLINK; ipdata->inum = ochain->data->ipdata.inum; ipdata->nlinks = 1; hammer2_chain_unlock(nchain); nchain = ochain; ochain = NULL; } else if (hlink && hammer2_hardlink_enable < 0) { /* * Create a snapshot (hardlink fake mode for debugging). * (ochain already flushed above so we can just copy the * bref XXX). * * Since this is a snapshot we return nchain in the fake * hardlink case. */ hammer2_chain_modify(trans, &nchain, 0); KKASSERT(name_len < HAMMER2_INODE_MAXNAME); ipdata = &nchain->data->ipdata; *ipdata = ochain->data->ipdata; bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; atomic_clear_int(&nchain->core->flags, HAMMER2_CORE_COUNTEDBREFS); kprintf("created fake hardlink %*.*s\n", (int)name_len, (int)name_len, name); } else { /* * nchain is a duplicate of ochain at the new location. * We must fixup the name stored in oip. The bref key * has already been set up. */ hammer2_chain_modify(trans, &nchain, 0); ipdata = &nchain->data->ipdata; KKASSERT(name_len < HAMMER2_INODE_MAXNAME); bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; ipdata->nlinks = 1; } /* * We are replacing ochain with nchain, unlock ochain. In the * case where ochain is left unchanged the code above sets * nchain to ochain and ochain to NULL, resulting in a NOP here. */ if (ochain) hammer2_chain_unlock(ochain); *chainp = nchain; return (0); }
/* * Shift *chainp up to the specified directory, change the filename * to "0xINODENUMBER", and adjust the key. The chain becomes the * invisible hardlink target. * * The original *chainp has already been marked deleted. */ static void hammer2_hardlink_shiftup(hammer2_trans_t *trans, hammer2_chain_t **chainp, hammer2_inode_t *dip, hammer2_chain_t **dchainp, int nlinks, int *errorp) { hammer2_inode_data_t *nipdata; hammer2_chain_t *chain; hammer2_chain_t *xchain; hammer2_key_t key_dummy; hammer2_key_t lhc; hammer2_blockref_t bref; int cache_index = -1; chain = *chainp; lhc = chain->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. * * WARNING! Must use inode_lock_ex() on dip to handle a stale * dip->chain cache. */ retry: *errorp = 0; xchain = hammer2_chain_lookup(dchainp, &key_dummy, lhc, lhc, &cache_index, 0); if (xchain) { kprintf("X3 chain %p dip %p dchain %p dip->chain %p\n", xchain, dip, *dchainp, dip->chain); hammer2_chain_unlock(xchain); xchain = NULL; *errorp = ENOSPC; #if 0 Debugger("X3"); #endif } /* * Create entry in common parent directory using the seek position * calculated above. * * We must refactor chain because it might have been shifted into * an indirect chain by the create. */ if (*errorp == 0) { KKASSERT(xchain == NULL); #if 0 *errorp = hammer2_chain_create(trans, dchainp, &xchain, lhc, 0, HAMMER2_BREF_TYPE_INODE,/* n/a */ HAMMER2_INODE_BYTES); /* n/a */ #endif /*XXX this somehow isn't working on chain XXX*/ /*KKASSERT(xxx)*/ } /* * Cleanup and handle retries. */ if (*errorp == EAGAIN) { kprintf("R"); hammer2_chain_wait(*dchainp); hammer2_chain_drop(*dchainp); goto retry; } /* * Handle the error case */ if (*errorp) { panic("error2"); KKASSERT(xchain == NULL); return; } /* * Use xchain as a placeholder for (lhc). Duplicate chain to the * same target bref as xchain and then delete xchain. The duplication * occurs after xchain in flush order even though xchain is deleted * after the duplication. XXX * * WARNING! Duplications (to a different parent) can cause indirect * blocks to be inserted, refactor xchain. */ bref = chain->bref; bref.key = lhc; /* invisible dir entry key */ bref.keybits = 0; hammer2_chain_duplicate(trans, dchainp, &chain, &bref, 0, 2); /* * chain is now 'live' again.. adjust the filename. * * Directory entries are inodes but this is a hidden hardlink * target. The name isn't used but to ease debugging give it * a name after its inode number. */ hammer2_chain_modify(trans, &chain, 0); nipdata = &chain->data->ipdata; ksnprintf(nipdata->filename, sizeof(nipdata->filename), "0x%016jx", (intmax_t)nipdata->inum); nipdata->name_len = strlen(nipdata->filename); nipdata->name_key = lhc; nipdata->nlinks += nlinks; *chainp = chain; }
/* * 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); }
/* * This is called from the mount code to initialize pmp->ihidden */ void hammer2_inode_install_hidden(hammer2_pfsmount_t *pmp) { hammer2_trans_t trans; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_chain_t *scan; hammer2_inode_data_t *ipdata; hammer2_key_t key_dummy; hammer2_key_t key_next; int cache_index; int error; int count; if (pmp->ihidden) return; /* * Find the hidden directory */ bzero(&key_dummy, sizeof(key_dummy)); hammer2_trans_init(&trans, pmp, NULL, 0); parent = hammer2_inode_lock_ex(pmp->iroot); chain = hammer2_chain_lookup(&parent, &key_dummy, HAMMER2_INODE_HIDDENDIR, HAMMER2_INODE_HIDDENDIR, &cache_index, 0); if (chain) { pmp->ihidden = hammer2_inode_get(pmp, pmp->iroot, chain); hammer2_inode_ref(pmp->ihidden); /* * Remove any unlinked files which were left open as-of * any system crash. */ count = 0; scan = hammer2_chain_lookup(&chain, &key_next, 0, HAMMER2_MAX_TID, &cache_index, HAMMER2_LOOKUP_NODATA); while (scan) { if (scan->bref.type == HAMMER2_BREF_TYPE_INODE) { hammer2_chain_delete(&trans, scan, 0); ++count; } scan = hammer2_chain_next(&chain, scan, &key_next, 0, HAMMER2_MAX_TID, &cache_index, HAMMER2_LOOKUP_NODATA); } hammer2_inode_unlock_ex(pmp->ihidden, chain); hammer2_inode_unlock_ex(pmp->iroot, parent); hammer2_trans_done(&trans); kprintf("hammer2: PFS loaded hidden dir, " "removed %d dead entries\n", count); return; } /* * Create the hidden directory */ error = hammer2_chain_create(&trans, &parent, &chain, HAMMER2_INODE_HIDDENDIR, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); hammer2_inode_unlock_ex(pmp->iroot, parent); hammer2_chain_modify(&trans, &chain, 0); ipdata = &chain->data->ipdata; ipdata->type = HAMMER2_OBJTYPE_DIRECTORY; ipdata->inum = HAMMER2_INODE_HIDDENDIR; ipdata->nlinks = 1; kprintf("hammer2: PFS root missing hidden directory, creating\n"); pmp->ihidden = hammer2_inode_get(pmp, pmp->iroot, chain); hammer2_inode_ref(pmp->ihidden); hammer2_inode_unlock_ex(pmp->ihidden, chain); hammer2_trans_done(&trans); }
/* * 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; }
/* * Connect the target inode represented by (*chainp) to the media topology * at (dip, name, len). * * If hlink is TRUE this function creates an OBJTYPE_HARDLINK directory * entry instead of connecting (*chainp). * * If hlink is FALSE this function uses chain_duplicate() to make a copy * if (*chainp) in the directory entry. (*chainp) is likely to be deleted * by the caller in this case (e.g. rename). */ int hammer2_inode_connect(hammer2_trans_t *trans, int hlink, hammer2_inode_t *dip, hammer2_chain_t **chainp, const uint8_t *name, size_t name_len) { hammer2_inode_data_t *ipdata; hammer2_chain_t *nchain; hammer2_chain_t *parent; hammer2_chain_t *ochain; hammer2_key_t lhc; int error; ochain = *chainp; /* * Since ochain is either disconnected from the topology or represents * a hardlink terminus which is always a parent of or equal to dip, * we should be able to safely lock dip->chain for our setup. */ parent = hammer2_chain_lookup_init(dip->chain, 0); lhc = hammer2_dirhash(name, name_len); /* * Locate the inode or indirect block to create the new * entry in. At the same time check for key collisions * and iterate until we don't get one. */ error = 0; while (error == 0) { nchain = hammer2_chain_lookup(&parent, lhc, lhc, 0); if (nchain == NULL) break; if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK) error = ENOSPC; hammer2_chain_unlock(nchain); nchain = NULL; ++lhc; } if (error == 0) { if (hlink) { /* * Hardlink pointer needed, create totally fresh * directory entry. */ KKASSERT(nchain == NULL); error = hammer2_chain_create(trans, &parent, &nchain, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); hammer2_chain_refactor(&ochain); } else { /* * Reconnect the original chain and rename. Use * chain_duplicate(). The caller will likely delete * or has already deleted the original chain in * this case. * * NOTE: chain_duplicate() generates a new chain * with CHAIN_DELETED cleared (ochain typically * has it set from the file unlink). */ nchain = ochain; ochain = NULL; hammer2_chain_duplicate(trans, NULL, -1, &nchain, NULL); error = hammer2_chain_create(trans, &parent, &nchain, lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES); } } /* * Unlock stuff. */ KKASSERT(error != EAGAIN); hammer2_chain_lookup_done(parent); parent = NULL; /* * nchain should be NULL on error, leave ochain (== *chainp) alone. */ if (error) { KKASSERT(nchain == 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, so we must still * return ochain. */ if (hlink && hammer2_hardlink_enable >= 0) { /* * Create the HARDLINK pointer. oip represents the hardlink * target in this situation. * * We will return ochain (the hardlink target). */ hammer2_chain_modify(trans, &nchain, HAMMER2_MODIFY_ASSERTNOCOPY); KKASSERT(name_len < HAMMER2_INODE_MAXNAME); ipdata = &nchain->data->ipdata; bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; ipdata->target_type = ochain->data->ipdata.type; ipdata->type = HAMMER2_OBJTYPE_HARDLINK; ipdata->inum = ochain->data->ipdata.inum; ipdata->nlinks = 1; hammer2_chain_unlock(nchain); nchain = ochain; ochain = NULL; } else if (hlink && hammer2_hardlink_enable < 0) { /* * Create a snapshot (hardlink fake mode for debugging). * (ochain already flushed above so we can just copy the * bref XXX). * * Since this is a snapshot we return nchain in the fake * hardlink case. */ hammer2_chain_modify(trans, &nchain, HAMMER2_MODIFY_ASSERTNOCOPY); KKASSERT(name_len < HAMMER2_INODE_MAXNAME); ipdata = &nchain->data->ipdata; *ipdata = ochain->data->ipdata; bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; kprintf("created fake hardlink %*.*s\n", (int)name_len, (int)name_len, name); } else { /* * nchain is a duplicate of ochain at the new location. * We must fixup the name stored in oip. The bref key * has already been set up. */ hammer2_chain_modify(trans, &nchain, HAMMER2_MODIFY_ASSERTNOCOPY); ipdata = &nchain->data->ipdata; KKASSERT(name_len < HAMMER2_INODE_MAXNAME); bcopy(name, ipdata->filename, name_len); ipdata->name_key = lhc; ipdata->name_len = name_len; ipdata->nlinks = 1; } /* * We are replacing ochain with nchain, unlock ochain. In the * case where ochain is left unchanged the code above sets * nchain to ochain and ochain to NULL, resulting in a NOP here. */ if (ochain) hammer2_chain_unlock(ochain); *chainp = nchain; 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); }
/* * 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); }
/* * 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); }
/* * 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); }