Commit 9f6df573 authored by Aditya Kali's avatar Aditya Kali Committed by Tejun Heo
Browse files

kernfs: Add API to generate relative kernfs path



The new function kernfs_path_from_node() generates and returns kernfs
path of a given kernfs_node relative to a given parent kernfs_node.

Signed-off-by: default avatarAditya Kali <adityakali@google.com>
Signed-off-by: default avatarSerge E. Hallyn <serge.hallyn@canonical.com>
Acked-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
parent 223ffb29
Loading
Loading
Loading
Loading
+160 −31
Original line number Original line Diff line number Diff line
@@ -44,28 +44,122 @@ static int kernfs_name_locked(struct kernfs_node *kn, char *buf, size_t buflen)
	return strlcpy(buf, kn->parent ? kn->name : "/", buflen);
	return strlcpy(buf, kn->parent ? kn->name : "/", buflen);
}
}


static char * __must_check kernfs_path_locked(struct kernfs_node *kn, char *buf,
/* kernfs_node_depth - compute depth from @from to @to */
					      size_t buflen)
static size_t kernfs_depth(struct kernfs_node *from, struct kernfs_node *to)
{
{
	char *p = buf + buflen;
	size_t depth = 0;
	int len;


	*--p = '\0';
	while (to->parent && to != from) {
		depth++;
		to = to->parent;
	}
	return depth;
}


	do {
static struct kernfs_node *kernfs_common_ancestor(struct kernfs_node *a,
		len = strlen(kn->name);
						  struct kernfs_node *b)
		if (p - buf < len + 1) {
{
	size_t da, db;
	struct kernfs_root *ra = kernfs_root(a), *rb = kernfs_root(b);

	if (ra != rb)
		return NULL;

	da = kernfs_depth(ra->kn, a);
	db = kernfs_depth(rb->kn, b);

	while (da > db) {
		a = a->parent;
		da--;
	}
	while (db > da) {
		b = b->parent;
		db--;
	}

	/* worst case b and a will be the same at root */
	while (b != a) {
		b = b->parent;
		a = a->parent;
	}

	return a;
}

/**
 * kernfs_path_from_node_locked - find a pseudo-absolute path to @kn_to,
 * where kn_from is treated as root of the path.
 * @kn_from: kernfs node which should be treated as root for the path
 * @kn_to: kernfs node to which path is needed
 * @buf: buffer to copy the path into
 * @buflen: size of @buf
 *
 * We need to handle couple of scenarios here:
 * [1] when @kn_from is an ancestor of @kn_to at some level
 * kn_from: /n1/n2/n3
 * kn_to:   /n1/n2/n3/n4/n5
 * result:  /n4/n5
 *
 * [2] when @kn_from is on a different hierarchy and we need to find common
 * ancestor between @kn_from and @kn_to.
 * kn_from: /n1/n2/n3/n4
 * kn_to:   /n1/n2/n5
 * result:  /../../n5
 * OR
 * kn_from: /n1/n2/n3/n4/n5   [depth=5]
 * kn_to:   /n1/n2/n3         [depth=3]
 * result:  /../..
 *
 * return value: length of the string.  If greater than buflen,
 * then contents of buf are undefined.  On error, -1 is returned.
 */
static int kernfs_path_from_node_locked(struct kernfs_node *kn_to,
					struct kernfs_node *kn_from,
					char *buf, size_t buflen)
{
	struct kernfs_node *kn, *common;
	const char parent_str[] = "/..";
	size_t depth_from, depth_to, len = 0, nlen = 0;
	char *p;
	int i;

	if (!kn_from)
		kn_from = kernfs_root(kn_to)->kn;

	if (kn_from == kn_to)
		return strlcpy(buf, "/", buflen);

	common = kernfs_common_ancestor(kn_from, kn_to);
	if (WARN_ON(!common))
		return -1;

	depth_to = kernfs_depth(common, kn_to);
	depth_from = kernfs_depth(common, kn_from);

	if (buf)
		buf[0] = '\0';
		buf[0] = '\0';
			p = NULL;

			break;
	for (i = 0; i < depth_from; i++)
		len += strlcpy(buf + len, parent_str,
			       len < buflen ? buflen - len : 0);

	/* Calculate how many bytes we need for the rest */
	for (kn = kn_to; kn != common; kn = kn->parent)
		nlen += strlen(kn->name) + 1;

	if (len + nlen >= buflen)
		return len + nlen;

	p = buf + len + nlen;
	*p = '\0';
	for (kn = kn_to; kn != common; kn = kn->parent) {
		nlen = strlen(kn->name);
		p -= nlen;
		memcpy(p, kn->name, nlen);
		*(--p) = '/';
	}
	}
		p -= len;
		memcpy(p, kn->name, len);
		*--p = '/';
		kn = kn->parent;
	} while (kn && kn->parent);


	return p;
	return len + nlen;
}
}


/**
/**
@@ -114,6 +208,34 @@ size_t kernfs_path_len(struct kernfs_node *kn)
	return len;
	return len;
}
}


/**
 * kernfs_path_from_node - build path of node @to relative to @from.
 * @from: parent kernfs_node relative to which we need to build the path
 * @to: kernfs_node of interest
 * @buf: buffer to copy @to's path into
 * @buflen: size of @buf
 *
 * Builds @to's path relative to @from in @buf. @from and @to must
 * be on the same kernfs-root. If @from is not parent of @to, then a relative
 * path (which includes '..'s) as needed to reach from @from to @to is
 * returned.
 *
 * If @buf isn't long enough, the return value will be greater than @buflen
 * and @buf contents are undefined.
 */
int kernfs_path_from_node(struct kernfs_node *to, struct kernfs_node *from,
			  char *buf, size_t buflen)
{
	unsigned long flags;
	int ret;

	spin_lock_irqsave(&kernfs_rename_lock, flags);
	ret = kernfs_path_from_node_locked(to, from, buf, buflen);
	spin_unlock_irqrestore(&kernfs_rename_lock, flags);
	return ret;
}
EXPORT_SYMBOL_GPL(kernfs_path_from_node);

/**
/**
 * kernfs_path - build full path of a given node
 * kernfs_path - build full path of a given node
 * @kn: kernfs_node of interest
 * @kn: kernfs_node of interest
@@ -127,13 +249,12 @@ size_t kernfs_path_len(struct kernfs_node *kn)
 */
 */
char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen)
char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen)
{
{
	unsigned long flags;
	int ret;
	char *p;


	spin_lock_irqsave(&kernfs_rename_lock, flags);
	ret = kernfs_path_from_node(kn, NULL, buf, buflen);
	p = kernfs_path_locked(kn, buf, buflen);
	if (ret < 0 || ret >= buflen)
	spin_unlock_irqrestore(&kernfs_rename_lock, flags);
		return NULL;
	return p;
	return buf;
}
}
EXPORT_SYMBOL_GPL(kernfs_path);
EXPORT_SYMBOL_GPL(kernfs_path);


@@ -164,17 +285,25 @@ void pr_cont_kernfs_name(struct kernfs_node *kn)
void pr_cont_kernfs_path(struct kernfs_node *kn)
void pr_cont_kernfs_path(struct kernfs_node *kn)
{
{
	unsigned long flags;
	unsigned long flags;
	char *p;
	int sz;


	spin_lock_irqsave(&kernfs_rename_lock, flags);
	spin_lock_irqsave(&kernfs_rename_lock, flags);


	p = kernfs_path_locked(kn, kernfs_pr_cont_buf,
	sz = kernfs_path_from_node_locked(kn, NULL, kernfs_pr_cont_buf,
					  sizeof(kernfs_pr_cont_buf));
					  sizeof(kernfs_pr_cont_buf));
	if (p)
	if (sz < 0) {
		pr_cont("%s", p);
		pr_cont("(error)");
	else
		goto out;
		pr_cont("<name too long>");
	}

	if (sz >= sizeof(kernfs_pr_cont_buf)) {
		pr_cont("(name too long)");
		goto out;
	}

	pr_cont("%s", kernfs_pr_cont_buf);


out:
	spin_unlock_irqrestore(&kernfs_rename_lock, flags);
	spin_unlock_irqrestore(&kernfs_rename_lock, flags);
}
}


+5 −4
Original line number Original line Diff line number Diff line
@@ -267,8 +267,9 @@ static inline bool kernfs_ns_enabled(struct kernfs_node *kn)


int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen);
int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen);
size_t kernfs_path_len(struct kernfs_node *kn);
size_t kernfs_path_len(struct kernfs_node *kn);
char * __must_check kernfs_path(struct kernfs_node *kn, char *buf,
int kernfs_path_from_node(struct kernfs_node *root_kn, struct kernfs_node *kn,
				size_t buflen);
			  char *buf, size_t buflen);
char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen);
void pr_cont_kernfs_name(struct kernfs_node *kn);
void pr_cont_kernfs_name(struct kernfs_node *kn);
void pr_cont_kernfs_path(struct kernfs_node *kn);
void pr_cont_kernfs_path(struct kernfs_node *kn);
struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn);
struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn);
@@ -338,7 +339,7 @@ static inline int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen)
static inline size_t kernfs_path_len(struct kernfs_node *kn)
static inline size_t kernfs_path_len(struct kernfs_node *kn)
{ return 0; }
{ return 0; }


static inline char * __must_check kernfs_path(struct kernfs_node *kn, char *buf,
static inline char *kernfs_path(struct kernfs_node *kn, char *buf,
				size_t buflen)
				size_t buflen)
{ return NULL; }
{ return NULL; }