Beispiel #1
0
/*
 * Destroy an extranious chain.
 *
 * Both *parentp and *chainp are locked shared.
 *
 * On return, *chainp will be adjusted to point to the next element in the
 * iteration and locked shared.
 */
static
int
hammer2_sync_destroy(hammer2_thread_t *thr,
		     hammer2_chain_t **parentp, hammer2_chain_t **chainp,
		     hammer2_tid_t mtid, int idx)
{
	hammer2_chain_t *chain;
	hammer2_chain_t *parent;
	hammer2_key_t key_next;
	hammer2_key_t save_key;
	int cache_index = -1;

	chain = *chainp;

#if HAMMER2_THREAD_DEBUG
	if (hammer2_debug & 1)
	kprintf("destroy rec %p/%p slave %d %d.%016jx\n",
		*parentp, chain,
		idx, chain->bref.type, chain->bref.key);
#endif

	save_key = chain->bref.key;
	if (save_key != HAMMER2_KEY_MAX)
		++save_key;

	/*
	 * Try to avoid unnecessary I/O.
	 *
	 * XXX accounting not propagated up properly.  We might have to do
	 *     a RESOLVE_MAYBE here and pass 0 for the flags.
	 */
	hammer2_chain_unlock(chain);	/* relock exclusive */
	hammer2_chain_unlock(*parentp);
	hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_ALWAYS);
	hammer2_chain_lock(chain, HAMMER2_RESOLVE_NEVER);

	hammer2_chain_delete(*parentp, chain, mtid, HAMMER2_DELETE_PERMANENT);
	hammer2_chain_unlock(chain);
	hammer2_chain_drop(chain);
	chain = NULL;			/* safety */

	hammer2_chain_unlock(*parentp);	/* relock shared */
	hammer2_chain_lock(*parentp, HAMMER2_RESOLVE_SHARED |
				     HAMMER2_RESOLVE_ALWAYS);
	*chainp = hammer2_chain_lookup(&parent, &key_next,
				     save_key, HAMMER2_KEY_MAX,
				     &cache_index,
				     HAMMER2_LOOKUP_SHARED |
				     HAMMER2_LOOKUP_NODIRECT |
				     HAMMER2_LOOKUP_NODATA);
	return 0;
}
Beispiel #2
0
hammer2_chain_t *
hammer2_inode_chain_and_parent(hammer2_inode_t *ip, int clindex,
			       hammer2_chain_t **parentp, int how)
{
	hammer2_chain_t *chain;
	hammer2_chain_t *parent;

	for (;;) {
		hammer2_spin_sh(&ip->cluster_spin);
		if (clindex >= ip->cluster.nchains)
			chain = NULL;
		else
			chain = ip->cluster.array[clindex].chain;
		if (chain) {
			hammer2_chain_ref(chain);
			hammer2_spin_unsh(&ip->cluster_spin);
			hammer2_chain_lock(chain, how);
		} else {
			hammer2_spin_unsh(&ip->cluster_spin);
		}

		/*
		 * Get parent, lock order must be (parent, chain).
		 */
		parent = chain->parent;
		if (parent) {
			hammer2_chain_ref(parent);
			hammer2_chain_unlock(chain);
			hammer2_chain_lock(parent, how);
			hammer2_chain_lock(chain, how);
		}
		if (ip->cluster.array[clindex].chain == chain &&
		    chain->parent == parent) {
			break;
		}

		/*
		 * Retry
		 */
		hammer2_chain_unlock(chain);
		hammer2_chain_drop(chain);
		if (parent) {
			hammer2_chain_unlock(parent);
			hammer2_chain_drop(parent);
		}
	}
	*parentp = parent;

	return chain;
}
Beispiel #3
0
/*
 * Replace the contents of the locked destination with the contents of the
 * locked source.  Destination must have one ref.
 *
 * Returns with the destination still with one ref and the copied chains
 * with an additional lock (representing their state on the destination).
 * The original chains associated with the destination are unlocked.
 */
void
hammer2_cluster_replace_locked(hammer2_cluster_t *dst, hammer2_cluster_t *src)
{
	hammer2_chain_t *chain;
	int i;

	KKASSERT(dst->refs == 1);

	dst->focus = NULL;
	for (i = 0; i < src->nchains; ++i) {
		chain = src->array[i];
		if (chain) {
			hammer2_chain_lock(chain, 0);
			if (i < dst->nchains && dst->array[i])
				hammer2_chain_unlock(dst->array[i]);
			dst->array[i] = src->array[i];
			if (dst->focus == NULL)
				dst->focus = chain;
		}
	}
	while (i < dst->nchains) {
		chain = dst->array[i];
		if (chain) {
			hammer2_chain_unlock(chain);
			dst->array[i] = NULL;
		}
		++i;
	}
	dst->nchains = src->nchains;
}
Beispiel #4
0
/*
 * NOTE: We don't combine the inode/chain lock because putting away an
 *       inode would otherwise confuse multiple lock holders of the inode.
 *
 *	 Shared locks are especially sensitive to having too many shared
 *	 lock counts (from the same thread) on certain paths which might
 *	 need to upgrade them.  Only one count of a shared lock can be
 *	 upgraded.
 */
hammer2_chain_t *
hammer2_inode_lock_sh(hammer2_inode_t *ip)
{
	hammer2_chain_t *chain;

	hammer2_inode_ref(ip);
	for (;;) {
		ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED);

		chain = ip->chain;
		KKASSERT(chain != NULL);	/* for now */
		hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS |
					  HAMMER2_RESOLVE_SHARED);

		/*
		 * Resolve duplication races, resolve hardlinks by giving
		 * up and cycling an exclusive lock.
		 */
		if ((chain->flags & HAMMER2_CHAIN_DUPLICATED) == 0 &&
		    chain->data->ipdata.type != HAMMER2_OBJTYPE_HARDLINK) {
			break;
		}
		hammer2_chain_unlock(chain);
		ccms_thread_unlock(&ip->topo_cst);
		chain = hammer2_inode_lock_ex(ip);
		hammer2_inode_unlock_ex(ip, chain);
	}
	return (chain);
}
Beispiel #5
0
/*
 * NOTE: We don't combine the inode/chain lock because putting away an
 *       inode would otherwise confuse multiple lock holders of the inode.
 *
 *	 Shared locks are especially sensitive to having too many shared
 *	 lock counts (from the same thread) on certain paths which might
 *	 need to upgrade them.  Only one count of a shared lock can be
 *	 upgraded.
 */
hammer2_chain_t *
hammer2_inode_lock_sh(hammer2_inode_t *ip)
{
	hammer2_chain_t *chain;

	hammer2_inode_ref(ip);
again:
	ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED);

	chain = ip->chain;
	KKASSERT(chain != NULL);	/* for now */
	hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS |
				  HAMMER2_RESOLVE_SHARED);

	/*
	 * Resolve duplication races
	 */
	if (hammer2_chain_refactor_test(chain, 1)) {
		hammer2_chain_unlock(chain);
		ccms_thread_unlock(&ip->topo_cst);
		chain = hammer2_inode_lock_ex(ip);
		hammer2_inode_unlock_ex(ip, chain);
		goto again;
	}
	return (chain);
}
Beispiel #6
0
/*
 * NOTE: We don't combine the inode/chain lock because putting away an
 *       inode would otherwise confuse multiple lock holders of the inode.
 *
 *	 Shared locks are especially sensitive to having too many shared
 *	 lock counts (from the same thread) on certain paths which might
 *	 need to upgrade them.  Only one count of a shared lock can be
 *	 upgraded.
 */
hammer2_chain_t *
hammer2_inode_lock_sh(hammer2_inode_t *ip)
{
	hammer2_chain_t *chain;

	hammer2_inode_ref(ip);
	ccms_thread_lock(&ip->topo_cst, CCMS_STATE_SHARED);

	chain = ip->chain;
	KKASSERT(chain != NULL);	/* for now */
	hammer2_chain_lock(ip->hmp, chain, HAMMER2_RESOLVE_ALWAYS |
					   HAMMER2_RESOLVE_SHARED);
	return (chain);
}
Beispiel #7
0
/*
 * 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);
}
Beispiel #8
0
/*
 * 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;
}
Beispiel #9
0
/*
 * 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;
	}
}
Beispiel #10
0
/*
 * When presented with a (*chainp) representing an inode of type
 * OBJTYPE_HARDLINK this code will save the original inode (with a ref)
 * in (*ipp), and then locate the hidden hardlink target in (dip) or
 * any parent directory above (dip).  The locked (*chainp) is replaced
 * with a new locked (*chainp) representing the hardlink target.
 */
int
hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp,
		      hammer2_inode_t **ipp)
{
	hammer2_mount_t *hmp = dip->hmp;
	hammer2_chain_t *chain = *chainp;
	hammer2_chain_t *parent;
	hammer2_inode_t *pip;
	hammer2_key_t lhc;

	*ipp = chain->u.ip;
	hammer2_inode_ref(chain->u.ip);
	lhc = chain->u.ip->ip_data.inum;

	hammer2_inode_unlock_ex(chain->u.ip);
	pip = chain->u.ip->pip;

	chain = NULL;
	while (pip) {
		parent = &pip->chain;
		KKASSERT(parent->bref.type == HAMMER2_BREF_TYPE_INODE);

		hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
		chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
		hammer2_chain_unlock(hmp, parent);
		if (chain)
			break;
		pip = pip->pip;
	}
	*chainp = chain;
	if (chain) {
		KKASSERT(chain->bref.type == HAMMER2_BREF_TYPE_INODE);
		/* already locked */
		return (0);
	} else {
		return (EIO);
	}
}
Beispiel #11
0
/*
 * Lock and ref a cluster.  This adds a ref to the cluster and its chains
 * and then locks them.
 */
int
hammer2_cluster_lock(hammer2_cluster_t *cluster, int how)
{
	hammer2_chain_t *chain;
	int i;
	int error;

	error = 0;
	atomic_add_int(&cluster->refs, 1);
	for (i = 0; i < cluster->nchains; ++i) {
		chain = cluster->array[i];
		if (chain) {
			error = hammer2_chain_lock(chain, how);
			if (error) {
				while (--i >= 0)
					hammer2_chain_unlock(cluster->array[i]);
				atomic_add_int(&cluster->refs, -1);
				break;
			}
		}
	}
	return error;
}
Beispiel #12
0
/*
 * 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);
}
Beispiel #13
0
/*
 * 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);
}
Beispiel #14
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);
}
Beispiel #15
0
/*
 * Connect inode (oip) to the specified directory using the specified name.
 * (oip) must be locked.
 *
 * If (oip) is not currently connected we simply connect it up.
 *
 * If (oip) is already connected we create a OBJTYPE_HARDLINK entry which
 * points to (oip)'s inode number.  (oip) is expected to be the terminus of
 * the hardlink sitting as a hidden file in a common parent directory
 * in this situation (thus the lock order is correct).
 */
int
hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip,
		      const uint8_t *name, size_t name_len)
{
	hammer2_mount_t *hmp = dip->hmp;
	hammer2_chain_t *chain;
	hammer2_chain_t *parent;
	hammer2_inode_t *nip;
	hammer2_key_t lhc;
	int error;
	int hlink;

	lhc = hammer2_dirhash(name, name_len);
	hlink = (oip->chain.parent != NULL);

	/*
	 * In fake mode flush oip so we can just snapshot it downbelow.
	 */
	if (hlink && hammer2_hardlink_enable < 0)
		hammer2_chain_flush(hmp, &oip->chain, 0);

	/*
	 * Locate the inode or indirect block to create the new
	 * entry in.  At the same time check for key collisions
	 * and iterate until we don't get one.
	 */
	parent = &dip->chain;
	hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);

	error = 0;
	while (error == 0) {
		chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
		if (chain == NULL)
			break;
		if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK)
			error = ENOSPC;
		hammer2_chain_unlock(hmp, chain);
		chain = NULL;
		++lhc;
	}

	/*
	 * Passing a non-NULL chain to hammer2_chain_create() reconnects the
	 * existing chain instead of creating a new one.  The chain's bref
	 * will be properly updated.
	 */
	if (error == 0) {
		if (hlink) {
			chain = hammer2_chain_create(hmp, parent,
						     NULL, lhc, 0,
						     HAMMER2_BREF_TYPE_INODE,
						     HAMMER2_INODE_BYTES);
		} else {
			chain = hammer2_chain_create(hmp, parent,
						     &oip->chain, lhc, 0,
						     HAMMER2_BREF_TYPE_INODE,
						     HAMMER2_INODE_BYTES);
			if (chain)
				KKASSERT(chain == &oip->chain);
		}
		if (chain == NULL)
			error = EIO;
	}
	hammer2_chain_unlock(hmp, parent);

	/*
	 * Handle the error case
	 */
	if (error) {
		KKASSERT(chain == NULL);
		return (error);
	}

	/*
	 * Directory entries are inodes so if the name has changed we have
	 * to update the inode.
	 *
	 * When creating an OBJTYPE_HARDLINK entry remember to unlock the
	 * chain, the caller will access the hardlink via the actual hardlink
	 * target file and not the hardlink pointer entry.
	 */
	if (hlink && hammer2_hardlink_enable >= 0) {
		/*
		 * Create the HARDLINK pointer.  oip represents the hardlink
		 * target in this situation.
		 */
		nip = chain->u.ip;
		hammer2_chain_modify(hmp, chain, 0);
		KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
		bcopy(name, nip->ip_data.filename, name_len);
		nip->ip_data.name_key = lhc;
		nip->ip_data.name_len = name_len;
		nip->ip_data.target_type = oip->ip_data.type;
		nip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
		nip->ip_data.inum = oip->ip_data.inum;
		nip->ip_data.nlinks = 1;
		kprintf("created hardlink %*.*s\n",
			(int)name_len, (int)name_len, name);
		hammer2_chain_unlock(hmp, chain);
	} else if (hlink && hammer2_hardlink_enable < 0) {
		/*
		 * Create a snapshot (hardlink fake mode for debugging).
		 */
		nip = chain->u.ip;
		nip->ip_data = oip->ip_data;
		hammer2_chain_modify(hmp, chain, 0);
		KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
		bcopy(name, nip->ip_data.filename, name_len);
		nip->ip_data.name_key = lhc;
		nip->ip_data.name_len = name_len;
		kprintf("created fake hardlink %*.*s\n",
			(int)name_len, (int)name_len, name);
		hammer2_chain_unlock(hmp, chain);
	} else {
		/*
		 * Normally disconnected inode (e.g. during a rename) that
		 * was reconnected.  We must fixup the name stored in
		 * oip.
		 *
		 * We are using oip as chain, already locked by caller,
		 * do not unlock it.
		 */
		hammer2_chain_modify(hmp, chain, 0);
		if (oip->ip_data.name_len != name_len ||
		    bcmp(oip->ip_data.filename, name, name_len) != 0) {
			KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
			bcopy(name, oip->ip_data.filename, name_len);
			oip->ip_data.name_key = lhc;
			oip->ip_data.name_len = name_len;
		}
		oip->ip_data.nlinks = 1;
	}

	return (0);
}
Beispiel #16
0
/*
 * Given an exclusively locked inode and chain we consolidate its chain
 * for hardlink creation, adding (nlinks) to the file's link count and
 * potentially relocating the inode to a directory common to ip->pip and tdip.
 *
 * Replaces (*chainp) if consolidation occurred, unlocking the old chain
 * and returning a new locked chain.
 *
 * NOTE!  This function will also replace ip->chain.
 */
int
hammer2_hardlink_consolidate(hammer2_trans_t *trans,
			     hammer2_inode_t *ip, hammer2_chain_t **chainp,
			     hammer2_inode_t *cdip, hammer2_chain_t **cdchainp,
			     int nlinks)
{
	hammer2_inode_data_t *ipdata;
	hammer2_chain_t *chain;
	hammer2_chain_t *nchain;
	int error;

	chain = *chainp;
	if (nlinks == 0 &&			/* no hardlink needed */
	    (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE)) {
		return (0);
	}
	if (hammer2_hardlink_enable < 0) {	/* fake hardlinks */
		return (0);
	}

	if (hammer2_hardlink_enable == 0) {	/* disallow hardlinks */
		hammer2_chain_unlock(chain);
		*chainp = NULL;
		return (ENOTSUP);
	}

	/*
	 * If no change in the hardlink's target directory is required and
	 * this is already a hardlink target, all we need to do is adjust
	 * the link count.
	 */
	if (cdip == ip->pip &&
	    (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
		if (nlinks) {
			hammer2_chain_modify(trans, &chain, 0);
			chain->data->ipdata.nlinks += nlinks;
		}
		error = 0;
		goto done;
	}


	/*
	 * chain is the real inode.  If it's visible we have to convert it
	 * to a hardlink pointer.  If it is not visible then it is already
	 * a hardlink target and only needs to be deleted.
	 */
	KKASSERT((chain->flags & HAMMER2_CHAIN_DELETED) == 0);
	KKASSERT(chain->data->ipdata.type != HAMMER2_OBJTYPE_HARDLINK);
	if (chain->data->ipdata.name_key & HAMMER2_DIRHASH_VISIBLE) {
		/*
		 * We are going to duplicate chain later, causing its
		 * media block to be shifted to the duplicate.  Even though
		 * we are delete-duplicating nchain here it might decide not
		 * to reallocate the block.  Set FORCECOW to force it to.
		 */
		nchain = chain;
		hammer2_chain_lock(nchain, HAMMER2_RESOLVE_ALWAYS);
		atomic_set_int(&nchain->flags, HAMMER2_CHAIN_FORCECOW);
		hammer2_chain_delete_duplicate(trans, &nchain,
					       HAMMER2_DELDUP_RECORE);
		KKASSERT((chain->flags & HAMMER2_CHAIN_DUPLICATED) == 0);

		ipdata = &nchain->data->ipdata;
		ipdata->target_type = ipdata->type;
		ipdata->type = HAMMER2_OBJTYPE_HARDLINK;
		ipdata->uflags = 0;
		ipdata->rmajor = 0;
		ipdata->rminor = 0;
		ipdata->ctime = 0;
		ipdata->mtime = 0;
		ipdata->atime = 0;
		ipdata->btime = 0;
		bzero(&ipdata->uid, sizeof(ipdata->uid));
		bzero(&ipdata->gid, sizeof(ipdata->gid));
		ipdata->op_flags = HAMMER2_OPFLAG_DIRECTDATA;
		ipdata->cap_flags = 0;
		ipdata->mode = 0;
		ipdata->size = 0;
		ipdata->nlinks = 1;
		ipdata->iparent = 0;	/* XXX */
		ipdata->pfs_type = 0;
		ipdata->pfs_inum = 0;
		bzero(&ipdata->pfs_clid, sizeof(ipdata->pfs_clid));
		bzero(&ipdata->pfs_fsid, sizeof(ipdata->pfs_fsid));
		ipdata->data_quota = 0;
		ipdata->data_count = 0;
		ipdata->inode_quota = 0;
		ipdata->inode_count = 0;
		ipdata->attr_tid = 0;
		ipdata->dirent_tid = 0;
		bzero(&ipdata->u, sizeof(ipdata->u));
		/* XXX transaction ids */
	} else {
		hammer2_chain_delete(trans, chain, 0);
		nchain = NULL;
	}

	/*
	 * chain represents the hardlink target and is now flagged deleted.
	 * duplicate it to the parent directory and adjust nlinks.
	 *
	 * WARNING! The shiftup() call can cause nchain to be moved into
	 *	    an indirect block, and our nchain will wind up pointing
	 *	    to the older/original version.
	 */
	KKASSERT(chain->flags & HAMMER2_CHAIN_DELETED);
	hammer2_hardlink_shiftup(trans, &chain, cdip, cdchainp, nlinks, &error);

	if (error == 0)
		hammer2_inode_repoint(ip, cdip, chain);

	/*
	 * Unlock the original chain last as the lock blocked races against
	 * the creation of the new hardlink target.
	 */
	if (nchain)
		hammer2_chain_unlock(nchain);

done:
	/*
	 * Cleanup, chain/nchain already dealt with.
	 */
	*chainp = chain;
	hammer2_inode_drop(cdip);

	return (error);
}
Beispiel #17
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.
 */
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;
}
Beispiel #18
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;
}
Beispiel #19
0
/*
 * cparent is locked exclusively, with an extra ref, cluster is not locked.
 * Replace element [i] in the cluster.
 */
static
int
hammer2_sync_replace(hammer2_thread_t *thr,
		     hammer2_chain_t *parent, hammer2_chain_t *chain,
		     hammer2_tid_t mtid, int idx,
		     hammer2_chain_t *focus)
{
	int nradix;
	uint8_t otype;

#if HAMMER2_THREAD_DEBUG
	if (hammer2_debug & 1)
	kprintf("replace rec %p slave %d %d.%016jx mod=%016jx\n",
		chain,
		idx,
		focus->bref.type, focus->bref.key, mtid);
#endif
	hammer2_chain_unlock(chain);
	hammer2_chain_lock(chain, HAMMER2_RESOLVE_ALWAYS);
	if (chain->bytes != focus->bytes) {
		/* XXX what if compressed? */
		nradix = hammer2_getradix(chain->bytes);
		hammer2_chain_resize(NULL, parent, chain,
				     mtid, nradix, 0);
	}
	hammer2_chain_modify(chain, mtid, 0);
	otype = chain->bref.type;
	chain->bref.type = focus->bref.type;
	chain->bref.methods = focus->bref.methods;
	chain->bref.keybits = focus->bref.keybits;
	chain->bref.vradix = focus->bref.vradix;
	/* mirror_tid updated by flush */
	KKASSERT(chain->bref.modify_tid == mtid);
	chain->bref.flags = focus->bref.flags;
	/* key already present */
	/* check code will be recalculated */
	chain->error = 0;

	/*
	 * Copy data body.
	 */
	switch(chain->bref.type) {
	case HAMMER2_BREF_TYPE_INODE:
		if ((focus->data->ipdata.meta.op_flags &
		     HAMMER2_OPFLAG_DIRECTDATA) == 0) {
			/*
			 * If DIRECTDATA is transitioning to 0 or the old
			 * chain is not an inode we have to initialize
			 * the block table.
			 */
			if (otype != HAMMER2_BREF_TYPE_INODE ||
			    (chain->data->ipdata.meta.op_flags &
			     HAMMER2_OPFLAG_DIRECTDATA)) {
				kprintf("chain inode trans away from dd\n");
				bzero(&chain->data->ipdata.u,
				      sizeof(chain->data->ipdata.u));
			}
			bcopy(focus->data, chain->data,
			      offsetof(hammer2_inode_data_t, u));
			/* XXX setcheck on inode should not be needed */
			hammer2_chain_setcheck(chain, chain->data);
			break;
		}
		/* fall through */
	case HAMMER2_BREF_TYPE_DATA:
		bcopy(focus->data, chain->data, chain->bytes);
		hammer2_chain_setcheck(chain, chain->data);
		break;
	default:
		KKASSERT(0);
		break;
	}

	hammer2_chain_unlock(chain);
	hammer2_chain_lock(chain, HAMMER2_RESOLVE_SHARED |
				  HAMMER2_RESOLVE_MAYBE);

	return 0;
}
Beispiel #20
0
/*
 * Consolidate for hard link creation.  This moves the specified terminal
 * hardlink inode to a directory common to its current directory and tdip
 * if necessary, replacing *ipp with the new inode chain element and
 * modifying the original inode chain element to OBJTYPE_HARDLINK.
 *
 * If the original inode chain element was a prior incarnation of a hidden
 * inode it can simply be deleted instead of converted.
 *
 * (*ipp)'s nlinks field is locked on entry and the new (*ipp)'s nlinks
 * field will be locked on return (with the original's unlocked).
 *
 * The link count is bumped if requested.
 */
int
hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip)
{
	hammer2_mount_t *hmp;
	hammer2_inode_t *oip = *ipp;
	hammer2_inode_t *nip = NULL;
	hammer2_inode_t *fdip;
	hammer2_chain_t *parent;
	int error;

	hmp = tdip->hmp;

	if (hammer2_hardlink_enable < 0)
		return (0);
	if (hammer2_hardlink_enable == 0)
	/*XXX	return (ENOTSUP);
*/

	/*
	 * Find the common parent directory
	 */
	fdip = oip->pip;
	while (fdip->depth > tdip->depth) {
		fdip = fdip->pip;
		KKASSERT(fdip != NULL);
	}
	while (tdip->depth > fdip->depth) {
		tdip = tdip->pip;
		KKASSERT(tdip != NULL);
	}
	while (fdip != tdip) {
		fdip = fdip->pip;
		tdip = tdip->pip;
		KKASSERT(fdip != NULL);
		KKASSERT(tdip != NULL);
	}

	/*
	 * Nothing to do (except bump the link count) if the hardlink has
	 * already been consolidated in the correct place.
	 */
	if (oip->pip == fdip &&
	    (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
		kprintf("hardlink already consolidated correctly\n");
		nip = oip;
		hammer2_inode_lock_ex(nip);
		hammer2_chain_modify(hmp, &nip->chain, 0);
		++nip->ip_data.nlinks;
		hammer2_inode_unlock_ex(nip);
		return (0);
	}

	/*
	 * Create a hidden inode directory entry in the parent, copying
	 * (*oip)'s state.  Then replace oip with OBJTYPE_HARDLINK.
	 *
	 * The duplication function will either flush or move any chains
	 * under oip to the new hardlink target inode, retiring all chains
	 * related to oip before returning.  XXX vp->ip races.
	 */
	error = hammer2_inode_duplicate(fdip, oip, &nip, NULL, 0);
	if (error == 0) {
		/*
		 * Bump nlinks on duplicated hidden inode.
		 */
		kprintf("hardlink consolidation success in parent dir %s\n",
			fdip->ip_data.filename);
		hammer2_inode_lock_nlinks(nip);
		hammer2_inode_unlock_nlinks(oip);
		hammer2_chain_modify(hmp, &nip->chain, 0);
		++nip->ip_data.nlinks;
		hammer2_inode_unlock_ex(nip);

		if (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) {
			/*
			 * Replace the old inode with an OBJTYPE_HARDLINK
			 * pointer.
			 */
			hammer2_inode_lock_ex(oip);
			hammer2_chain_modify(hmp, &oip->chain, 0);
			oip->ip_data.target_type = oip->ip_data.type;
			oip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
			oip->ip_data.uflags = 0;
			oip->ip_data.rmajor = 0;
			oip->ip_data.rminor = 0;
			oip->ip_data.ctime = 0;
			oip->ip_data.mtime = 0;
			oip->ip_data.atime = 0;
			oip->ip_data.btime = 0;
			bzero(&oip->ip_data.uid, sizeof(oip->ip_data.uid));
			bzero(&oip->ip_data.gid, sizeof(oip->ip_data.gid));
			oip->ip_data.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
			oip->ip_data.cap_flags = 0;
			oip->ip_data.mode = 0;
			oip->ip_data.size = 0;
			oip->ip_data.nlinks = 1;
			oip->ip_data.iparent = 0;	/* XXX */
			oip->ip_data.pfs_type = 0;
			oip->ip_data.pfs_inum = 0;
			bzero(&oip->ip_data.pfs_clid,
			      sizeof(oip->ip_data.pfs_clid));
			bzero(&oip->ip_data.pfs_fsid,
			      sizeof(oip->ip_data.pfs_fsid));
			oip->ip_data.data_quota = 0;
			oip->ip_data.data_count = 0;
			oip->ip_data.inode_quota = 0;
			oip->ip_data.inode_count = 0;
			oip->ip_data.attr_tid = 0;
			oip->ip_data.dirent_tid = 0;
			bzero(&oip->ip_data.u, sizeof(oip->ip_data.u));
			/* XXX transaction ids */

			hammer2_inode_unlock_ex(oip);
		} else {
			/*
			 * The old inode was a hardlink target, which we
			 * have now moved.  We must delete it so the new
			 * hardlink target at a higher directory level
			 * becomes the only hardlink target for this inode.
			 */
			kprintf("DELETE INVISIBLE\n");
			parent = oip->chain.parent;
			hammer2_chain_lock(hmp, parent,
					   HAMMER2_RESOLVE_ALWAYS);
			hammer2_chain_lock(hmp, &oip->chain,
					   HAMMER2_RESOLVE_ALWAYS);
			hammer2_chain_delete(hmp, parent, &oip->chain, 0);
			hammer2_chain_unlock(hmp, &oip->chain);
			hammer2_chain_unlock(hmp, parent);
		}
		*ipp = nip;
	} else {
		KKASSERT(nip == NULL);
	}

	return (error);
}
Beispiel #21
0
/*
 * 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;
}
Beispiel #22
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);
}
Beispiel #23
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;
}
Beispiel #24
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);
}