Example #1
0
/*
 * 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);
	}
}
Example #2
0
/*
 * Synchronize the inode's frontend state with the chain state prior
 * to any explicit flush of the inode or any strategy write call.
 *
 * Called with a locked inode.
 */
void
hammer2_inode_fsync(hammer2_trans_t *trans, hammer2_inode_t *ip, 
		    hammer2_chain_t **chainp)
{
	hammer2_inode_data_t *ipdata;
	hammer2_chain_t *parent;
	hammer2_chain_t *chain;
	hammer2_key_t lbase;
	hammer2_key_t key_next;
	int cache_index;

	ipdata = &ip->chain->data->ipdata;

	if (ip->flags & HAMMER2_INODE_MTIME) {
		ipdata = hammer2_chain_modify_ip(trans, ip, chainp, 0);
		atomic_clear_int(&ip->flags, HAMMER2_INODE_MTIME);
		ipdata->mtime = ip->mtime;
	}
	if ((ip->flags & HAMMER2_INODE_RESIZED) && ip->size < ipdata->size) {
		ipdata = hammer2_chain_modify_ip(trans, ip, chainp, 0);
		ipdata->size = ip->size;
		atomic_clear_int(&ip->flags, HAMMER2_INODE_RESIZED);

		/*
		 * We must delete any chains beyond the EOF.  The chain
		 * straddling the EOF will be pending in the bioq.
		 */
		lbase = (ipdata->size + HAMMER2_PBUFMASK64) &
			~HAMMER2_PBUFMASK64;
		parent = hammer2_chain_lookup_init(ip->chain, 0);
		chain = hammer2_chain_lookup(&parent, &key_next,
					     lbase, (hammer2_key_t)-1,
					     &cache_index,
					     HAMMER2_LOOKUP_NODATA);
		while (chain) {
			/*
			 * Degenerate embedded case, nothing to loop on
			 */
			if (chain->bref.type == HAMMER2_BREF_TYPE_INODE) {
				hammer2_chain_unlock(chain);
				break;
			}
			if (chain->bref.type == HAMMER2_BREF_TYPE_DATA) {
				hammer2_chain_delete(trans, chain, 0);
			}
			chain = hammer2_chain_next(&parent, chain, &key_next,
						   key_next, (hammer2_key_t)-1,
						   &cache_index,
						   HAMMER2_LOOKUP_NODATA);
		}
		hammer2_chain_lookup_done(parent);
	} else
	if ((ip->flags & HAMMER2_INODE_RESIZED) && ip->size > ipdata->size) {
		ipdata = hammer2_chain_modify_ip(trans, ip, chainp, 0);
		ipdata->size = ip->size;
		atomic_clear_int(&ip->flags, HAMMER2_INODE_RESIZED);

		/*
		 * When resizing larger we may not have any direct-data
		 * available.
		 */
		if ((ipdata->op_flags & HAMMER2_OPFLAG_DIRECTDATA) &&
		    ip->size > HAMMER2_EMBEDDED_BYTES) {
			ipdata->op_flags &= ~HAMMER2_OPFLAG_DIRECTDATA;
			bzero(&ipdata->u.blockset, sizeof(ipdata->u.blockset));
		}
	}
}
Example #3
0
/*
 * 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;
}
Example #4
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);
}
Example #5
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);
}
Example #6
0
/*
 * 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;
}