/* * Copyright (c) 2011-2015 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon * by Venkatesh Srinivas * by Daniel Flores (GSOC 2013 - mentored by Matthew Dillon, compression) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Per-node backend for kernel filesystem interface. * * This executes a VOP concurrently on multiple nodes, each node via its own * thread, and competes to advance the original request. The original * request is retired the moment all requirements are met, even if the * operation is still in-progress on some nodes. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hammer2.h" /* * Determine if the specified directory is empty. Returns 0 on success. */ static int checkdirempty(hammer2_chain_t *oparent, hammer2_chain_t *ochain, int clindex) { hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_key_t key_next; hammer2_key_t inum; int error; error = 0; chain = hammer2_chain_lookup_init(ochain, 0); if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT) { if (oparent) hammer2_chain_unlock(oparent); inum = chain->bref.embed.dirent.inum; parent = NULL; error = hammer2_chain_inode_find(chain->pmp, inum, clindex, 0, &parent, &chain); if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } if (oparent) { hammer2_chain_lock(oparent, HAMMER2_RESOLVE_ALWAYS); if (ochain->parent != oparent) { if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } return HAMMER2_ERROR_EAGAIN; } } } /* * Determine if the directory is empty or not by checking its * visible namespace (the area which contains directory entries). */ parent = chain; chain = NULL; if (parent) { chain = hammer2_chain_lookup(&parent, &key_next, HAMMER2_DIRHASH_VISIBLE, HAMMER2_KEY_MAX, &error, 0); } if (chain) { error = HAMMER2_ERROR_ENOTEMPTY; hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } hammer2_chain_lookup_done(parent); return error; } /* * Backend for hammer2_vfs_root() * * This is called when a newly mounted PFS has not yet synchronized * to the inode_tid and modify_tid. */ void hammer2_xop_ipcluster(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_ipcluster_t *xop = &arg->xop_ipcluster; hammer2_chain_t *chain; int error; chain = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); if (chain) error = chain->error; else error = HAMMER2_ERROR_EIO; hammer2_xop_feed(&xop->head, chain, thr->clindex, error); if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } } /* * Backend for hammer2_vop_readdir() */ void hammer2_xop_readdir(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_readdir_t *xop = &arg->xop_readdir; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_key_t key_next; hammer2_key_t lkey; int error = 0; lkey = xop->lkey; if (hammer2_debug & 0x0020) kprintf("xop_readdir %p lkey=%016jx\n", xop, lkey); /* * The inode's chain is the iterator. If we cannot acquire it our * contribution ends here. */ parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); if (parent == NULL) { kprintf("xop_readdir: NULL parent\n"); goto done; } /* * Directory scan [re]start and loop, the feed inherits the chain's * lock so do not unlock it on the iteration. */ chain = hammer2_chain_lookup(&parent, &key_next, lkey, lkey, &error, HAMMER2_LOOKUP_SHARED); if (chain == NULL) { chain = hammer2_chain_lookup(&parent, &key_next, lkey, HAMMER2_KEY_MAX, &error, HAMMER2_LOOKUP_SHARED); } while (chain) { error = hammer2_xop_feed(&xop->head, chain, thr->clindex, 0); if (error) goto break2; chain = hammer2_chain_next(&parent, chain, &key_next, key_next, HAMMER2_KEY_MAX, &error, HAMMER2_LOOKUP_SHARED); } break2: if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } hammer2_chain_unlock(parent); hammer2_chain_drop(parent); done: hammer2_xop_feed(&xop->head, NULL, thr->clindex, error); } /* * Backend for hammer2_vop_nresolve() */ void hammer2_xop_nresolve(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_nresolve_t *xop = &arg->xop_nresolve; hammer2_chain_t *parent; hammer2_chain_t *chain; const char *name; size_t name_len; hammer2_key_t key_next; hammer2_key_t lhc; int error; parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); if (parent == NULL) { kprintf("xop_nresolve: NULL parent\n"); chain = NULL; error = HAMMER2_ERROR_EIO; goto done; } name = xop->head.name1; name_len = xop->head.name1_len; /* * Lookup the directory entry */ lhc = hammer2_dirhash(name, name_len); chain = hammer2_chain_lookup(&parent, &key_next, lhc, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS | HAMMER2_LOOKUP_SHARED); while (chain) { if (hammer2_chain_dirent_test(chain, name, name_len)) break; chain = hammer2_chain_next(&parent, chain, &key_next, key_next, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS | HAMMER2_LOOKUP_SHARED); } /* * If the entry is a hardlink pointer, resolve it. */ if (chain && chain->error == 0) { if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT) { lhc = chain->bref.embed.dirent.inum; error = hammer2_chain_inode_find(chain->pmp, lhc, thr->clindex, HAMMER2_LOOKUP_SHARED, &parent, &chain); } } else if (chain && error == 0) { error = chain->error; } done: error = hammer2_xop_feed(&xop->head, chain, thr->clindex, error); if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } } /* * Backend for hammer2_vop_nremove(), hammer2_vop_nrmdir(), and * backend for pfs_delete. * * This function locates and removes a directory entry, and will lookup * and return the underlying inode. For directory entries the underlying * inode is not removed. If the directory entry is the actual inode itself, * it may be conditonally removed and returned. * * WARNING! Any target inode's nlinks may not be synchronized to the * in-memory inode. The frontend's hammer2_inode_unlink_finisher() * is responsible for the final disposition of the actual inode. */ void hammer2_xop_unlink(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_unlink_t *xop = &arg->xop_unlink; hammer2_chain_t *parent; hammer2_chain_t *chain; const char *name; size_t name_len; hammer2_key_t key_next; hammer2_key_t lhc; int error; again: /* * Requires exclusive lock */ parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS); chain = NULL; if (parent == NULL) { kprintf("xop_nresolve: NULL parent\n"); error = HAMMER2_ERROR_EIO; goto done; } name = xop->head.name1; name_len = xop->head.name1_len; /* * Lookup the directory entry */ lhc = hammer2_dirhash(name, name_len); chain = hammer2_chain_lookup(&parent, &key_next, lhc, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); while (chain) { if (hammer2_chain_dirent_test(chain, name, name_len)) break; chain = hammer2_chain_next(&parent, chain, &key_next, key_next, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); } /* * The directory entry will either be a BREF_TYPE_DIRENT or a * BREF_TYPE_INODE. We always permanently delete DIRENTs, but * must go by xop->dopermanent for BREF_TYPE_INODE. * * Note that the target chain's nlinks may not be synchronized with * the in-memory hammer2_inode_t structure, so we don't try to do * anything fancy here. The frontend deals with nlinks * synchronization. */ if (chain && chain->error == 0) { int dopermanent = xop->dopermanent & H2DOPERM_PERMANENT; int doforce = xop->dopermanent & H2DOPERM_FORCE; uint8_t type; /* * If the directory entry is the actual inode then use its * type for the directory typing tests, otherwise if it is * a directory entry, pull the type field from the entry. * * Directory entries are always permanently deleted * (because they aren't the actual inode). */ if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT) { type = chain->bref.embed.dirent.type; dopermanent |= HAMMER2_DELETE_PERMANENT; } else { type = chain->data->ipdata.meta.type; } /* * Check directory typing and delete the entry. Note that * nlinks adjustments are made on the real inode by the * frontend, not here. * * Unfortunately, checkdirempty() may have to unlock (parent). * If it no longer matches chain->parent after re-locking, * EAGAIN is returned. */ if (type == HAMMER2_OBJTYPE_DIRECTORY && doforce) { /* * If doforce then execute the operation even if * the directory is not empty or errored. We * ignore chain->error here, allowing an errored * chain (aka directory entry) to still be deleted. */ error = hammer2_chain_delete(parent, chain, xop->head.mtid, dopermanent); } else if (type == HAMMER2_OBJTYPE_DIRECTORY && (error = checkdirempty(parent, chain, thr->clindex)) != 0) { /* * error may be EAGAIN or ENOTEMPTY */ if (error == HAMMER2_ERROR_EAGAIN) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); hammer2_chain_unlock(parent); hammer2_chain_drop(parent); goto again; } } else if (type == HAMMER2_OBJTYPE_DIRECTORY && xop->isdir == 0) { error = HAMMER2_ERROR_ENOTDIR; } else if (type != HAMMER2_OBJTYPE_DIRECTORY && xop->isdir >= 1) { error = HAMMER2_ERROR_EISDIR; } else { /* * Delete the directory entry. chain might also * be a directly-embedded inode. * * Allow the deletion to proceed even if the chain * is errored. Give priority to error-on-delete over * chain->error. */ error = hammer2_chain_delete(parent, chain, xop->head.mtid, dopermanent); if (error == 0) error = chain->error; } } else { if (chain && error == 0) error = chain->error; } /* * If chain is a directory entry we must resolve it. We do not try * to manipulate the contents as it might not be synchronized with * the frontend hammer2_inode_t, nor do we try to lookup the * frontend hammer2_inode_t here (we are the backend!). */ if (chain && chain->bref.type == HAMMER2_BREF_TYPE_DIRENT && (xop->dopermanent & H2DOPERM_IGNINO) == 0) { int error2; lhc = chain->bref.embed.dirent.inum; error2 = hammer2_chain_inode_find(chain->pmp, lhc, thr->clindex, 0, &parent, &chain); if (error2) { kprintf("inode_find: %016jx %p failed\n", lhc, chain); error2 = 0; /* silently ignore */ } if (error == 0) error = error2; } /* * Return the inode target for further action. Typically used by * hammer2_inode_unlink_finisher(). */ done: hammer2_xop_feed(&xop->head, chain, thr->clindex, error); if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); chain = NULL; } if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); parent = NULL; } } /* * Backend for hammer2_vop_nrename() * * This handles the backend rename operation. Typically this renames * directory entries but can also be used to rename embedded inodes. * * NOTE! The frontend is responsible for updating the inode meta-data in * the file being renamed and for decrementing the target-replaced * inode's nlinks, if present. */ void hammer2_xop_nrename(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_nrename_t *xop = &arg->xop_nrename; hammer2_pfs_t *pmp; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_chain_t *tmp; hammer2_inode_t *ip; hammer2_key_t key_next; int error; /* * We need the precise parent chain to issue the deletion. * * If this is a directory entry we must locate the underlying * inode. If it is an embedded inode we can act directly on it. */ ip = xop->head.ip2; pmp = ip->pmp; chain = NULL; error = 0; if (xop->ip_key & HAMMER2_DIRHASH_VISIBLE) { /* * Find ip's direct parent chain. */ chain = hammer2_inode_chain(ip, thr->clindex, HAMMER2_RESOLVE_ALWAYS); if (chain == NULL) { error = HAMMER2_ERROR_EIO; parent = NULL; goto done; } parent = hammer2_chain_getparent(chain, HAMMER2_RESOLVE_ALWAYS); if (parent == NULL) { error = HAMMER2_ERROR_EIO; goto done; } } else { /* * The directory entry for the head.ip1 inode * is in fdip, do a namespace search. */ hammer2_key_t lhc; const char *name; size_t name_len; parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS); if (parent == NULL) { kprintf("xop_nrename: NULL parent\n"); error = HAMMER2_ERROR_EIO; goto done; } name = xop->head.name1; name_len = xop->head.name1_len; /* * Lookup the directory entry */ lhc = hammer2_dirhash(name, name_len); chain = hammer2_chain_lookup(&parent, &key_next, lhc, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); while (chain) { if (hammer2_chain_dirent_test(chain, name, name_len)) break; chain = hammer2_chain_next(&parent, chain, &key_next, key_next, lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); } } if (chain == NULL) { /* XXX shouldn't happen, but does under fsstress */ kprintf("hammer2_xop_rename: \"%s\" -> \"%s\" ENOENT\n", xop->head.name1, xop->head.name2); if (error == 0) error = HAMMER2_ERROR_ENOENT; goto done; } if (chain->error) { error = chain->error; goto done; } /* * Delete it, then create it in the new namespace. * * An error can occur if the chain being deleted requires * modification and the media is full. */ error = hammer2_chain_delete(parent, chain, xop->head.mtid, 0); hammer2_chain_unlock(parent); hammer2_chain_drop(parent); parent = NULL; /* safety */ if (error) goto done; /* * Adjust fields in the deleted chain appropriate for the rename * operation. * * NOTE! For embedded inodes, the frontend will officially replicate * the field adjustments, but we also do it here to maintain * consistency in case of a crash. */ if (chain->bref.key != xop->lhc || xop->head.name1_len != xop->head.name2_len || bcmp(xop->head.name1, xop->head.name2, xop->head.name1_len) != 0) { if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) { hammer2_inode_data_t *wipdata; error = hammer2_chain_modify(chain, xop->head.mtid, 0, 0); if (error == 0) { wipdata = &chain->data->ipdata; bzero(wipdata->filename, sizeof(wipdata->filename)); bcopy(xop->head.name2, wipdata->filename, xop->head.name2_len); wipdata->meta.name_key = xop->lhc; wipdata->meta.name_len = xop->head.name2_len; } } if (chain->bref.type == HAMMER2_BREF_TYPE_DIRENT) { if (xop->head.name2_len <= sizeof(chain->bref.check.buf)) { /* * Remove any related data buffer, we can * embed the filename in the bref itself. */ error = hammer2_chain_resize( chain, xop->head.mtid, 0, 0, 0); if (error == 0) { error = hammer2_chain_modify( chain, xop->head.mtid, 0, 0); } if (error == 0) { bzero(chain->bref.check.buf, sizeof(chain->bref.check.buf)); bcopy(xop->head.name2, chain->bref.check.buf, xop->head.name2_len); } } else { /* * Associate a data buffer with the bref. * Zero it for consistency. Note that the * data buffer is not 64KB so use chain->bytes * instead of sizeof(). */ error = hammer2_chain_resize( chain, xop->head.mtid, 0, hammer2_getradix(HAMMER2_ALLOC_MIN), 0); if (error == 0) { error = hammer2_chain_modify( chain, xop->head.mtid, 0, 0); } if (error == 0) { bzero(chain->data->buf, chain->bytes); bcopy(xop->head.name2, chain->data->buf, xop->head.name2_len); } } if (error == 0) { chain->bref.embed.dirent.namlen = xop->head.name2_len; } } } /* * The frontend will replicate this operation and is the real final * authority, but adjust the inode's iparent field too if the inode * is embedded in the directory. */ if (chain->bref.type == HAMMER2_BREF_TYPE_INODE && chain->data->ipdata.meta.iparent != xop->head.ip3->meta.inum) { hammer2_inode_data_t *wipdata; error = hammer2_chain_modify(chain, xop->head.mtid, 0, 0); if (error == 0) { wipdata = &chain->data->ipdata; wipdata->meta.iparent = xop->head.ip3->meta.inum; } } /* * Destroy any matching target(s) before creating the new entry. * This will result in some ping-ponging of the directory key * iterator but that is ok. */ parent = hammer2_inode_chain(xop->head.ip3, thr->clindex, HAMMER2_RESOLVE_ALWAYS); if (parent == NULL) { error = HAMMER2_ERROR_EIO; goto done; } /* * Delete all matching directory entries. That is, get rid of * multiple duplicates if present, as a self-healing mechanism. */ if (error == 0) { tmp = hammer2_chain_lookup(&parent, &key_next, xop->lhc & ~HAMMER2_DIRHASH_LOMASK, xop->lhc | HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); while (tmp) { int e2; if (hammer2_chain_dirent_test(tmp, xop->head.name2, xop->head.name2_len)) { e2 = hammer2_chain_delete(parent, tmp, xop->head.mtid, 0); if (error == 0 && e2) error = e2; } tmp = hammer2_chain_next(&parent, tmp, &key_next, key_next, xop->lhc | HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS); } } if (error == 0) { /* * A relookup is required before the create to properly * position the parent chain. */ tmp = hammer2_chain_lookup(&parent, &key_next, xop->lhc, xop->lhc, &error, 0); KKASSERT(tmp == NULL); error = hammer2_chain_create(&parent, &chain, pmp, HAMMER2_METH_DEFAULT, xop->lhc, 0, HAMMER2_BREF_TYPE_INODE, HAMMER2_INODE_BYTES, xop->head.mtid, 0, 0); } done: hammer2_xop_feed(&xop->head, NULL, thr->clindex, error); if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } } /* * Directory collision resolver scan helper (backend, threaded). * * Used by the inode create code to locate an unused lhc. */ void hammer2_xop_scanlhc(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_scanlhc_t *xop = &arg->xop_scanlhc; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_key_t key_next; int error = 0; parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); if (parent == NULL) { kprintf("xop_nresolve: NULL parent\n"); chain = NULL; error = HAMMER2_ERROR_EIO; goto done; } /* * Lookup all possibly conflicting directory entries, the feed * inherits the chain's lock so do not unlock it on the iteration. */ chain = hammer2_chain_lookup(&parent, &key_next, xop->lhc, xop->lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS | HAMMER2_LOOKUP_SHARED); while (chain) { error = hammer2_xop_feed(&xop->head, chain, thr->clindex, 0); if (error) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); chain = NULL; /* safety */ goto done; } chain = hammer2_chain_next(&parent, chain, &key_next, key_next, xop->lhc + HAMMER2_DIRHASH_LOMASK, &error, HAMMER2_LOOKUP_ALWAYS | HAMMER2_LOOKUP_SHARED); } done: hammer2_xop_feed(&xop->head, NULL, thr->clindex, error); if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } } /* * Generic lookup of a specific key. * * Used by the inode hidden directory code to find the hidden directory. */ void hammer2_xop_lookup(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_scanlhc_t *xop = &arg->xop_scanlhc; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_key_t key_next; int error = 0; parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, HAMMER2_RESOLVE_ALWAYS | HAMMER2_RESOLVE_SHARED); chain = NULL; if (parent == NULL) { error = HAMMER2_ERROR_EIO; goto done; } /* * Lookup all possibly conflicting directory entries, the feed * inherits the chain's lock so do not unlock it on the iteration. */ chain = hammer2_chain_lookup(&parent, &key_next, xop->lhc, xop->lhc, &error, HAMMER2_LOOKUP_ALWAYS | HAMMER2_LOOKUP_SHARED); if (error == 0) { if (chain) error = chain->error; else error = HAMMER2_ERROR_ENOENT; } hammer2_xop_feed(&xop->head, chain, thr->clindex, error); done: if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } if (parent) { hammer2_chain_unlock(parent); hammer2_chain_drop(parent); } } /* * Generic scan * * WARNING! Fed chains must be locked shared so ownership can be transfered * and to prevent frontend/backend stalls that would occur with an * exclusive lock. The shared lock also allows chain->data to be * retained. */ void hammer2_xop_scanall(hammer2_thread_t *thr, hammer2_xop_t *arg) { hammer2_xop_scanall_t *xop = &arg->xop_scanall; hammer2_chain_t *parent; hammer2_chain_t *chain; hammer2_key_t key_next; int error = 0; /* * Assert required flags. */ KKASSERT(xop->resolve_flags & HAMMER2_RESOLVE_SHARED); KKASSERT(xop->lookup_flags & HAMMER2_LOOKUP_SHARED); /* * The inode's chain is the iterator. If we cannot acquire it our * contribution ends here. */ parent = hammer2_inode_chain(xop->head.ip1, thr->clindex, xop->resolve_flags); if (parent == NULL) { kprintf("xop_readdir: NULL parent\n"); goto done; } /* * Generic scan of exact records. Note that indirect blocks are * automatically recursed and will not be returned. */ chain = hammer2_chain_lookup(&parent, &key_next, xop->key_beg, xop->key_end, &error, xop->lookup_flags); while (chain) { error = hammer2_xop_feed(&xop->head, chain, thr->clindex, 0); if (error) goto break2; chain = hammer2_chain_next(&parent, chain, &key_next, key_next, xop->key_end, &error, xop->lookup_flags); } break2: if (chain) { hammer2_chain_unlock(chain); hammer2_chain_drop(chain); } hammer2_chain_unlock(parent); hammer2_chain_drop(parent); done: hammer2_xop_feed(&xop->head, NULL, thr->clindex, error); }