162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * fs/kernfs/symlink.c - kernfs symlink implementation
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2001-3 Patrick Mochel
662306a36Sopenharmony_ci * Copyright (c) 2007 SUSE Linux Products GmbH
762306a36Sopenharmony_ci * Copyright (c) 2007, 2013 Tejun Heo <tj@kernel.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/fs.h>
1162306a36Sopenharmony_ci#include <linux/gfp.h>
1262306a36Sopenharmony_ci#include <linux/namei.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "kernfs-internal.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/**
1762306a36Sopenharmony_ci * kernfs_create_link - create a symlink
1862306a36Sopenharmony_ci * @parent: directory to create the symlink in
1962306a36Sopenharmony_ci * @name: name of the symlink
2062306a36Sopenharmony_ci * @target: target node for the symlink to point to
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Return: the created node on success, ERR_PTR() value on error.
2362306a36Sopenharmony_ci * Ownership of the link matches ownership of the target.
2462306a36Sopenharmony_ci */
2562306a36Sopenharmony_cistruct kernfs_node *kernfs_create_link(struct kernfs_node *parent,
2662306a36Sopenharmony_ci				       const char *name,
2762306a36Sopenharmony_ci				       struct kernfs_node *target)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	struct kernfs_node *kn;
3062306a36Sopenharmony_ci	int error;
3162306a36Sopenharmony_ci	kuid_t uid = GLOBAL_ROOT_UID;
3262306a36Sopenharmony_ci	kgid_t gid = GLOBAL_ROOT_GID;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (target->iattr) {
3562306a36Sopenharmony_ci		uid = target->iattr->ia_uid;
3662306a36Sopenharmony_ci		gid = target->iattr->ia_gid;
3762306a36Sopenharmony_ci	}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	kn = kernfs_new_node(parent, name, S_IFLNK|0777, uid, gid, KERNFS_LINK);
4062306a36Sopenharmony_ci	if (!kn)
4162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (kernfs_ns_enabled(parent))
4462306a36Sopenharmony_ci		kn->ns = target->ns;
4562306a36Sopenharmony_ci	kn->symlink.target_kn = target;
4662306a36Sopenharmony_ci	kernfs_get(target);	/* ref owned by symlink */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	error = kernfs_add_one(kn);
4962306a36Sopenharmony_ci	if (!error)
5062306a36Sopenharmony_ci		return kn;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	kernfs_put(kn);
5362306a36Sopenharmony_ci	return ERR_PTR(error);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int kernfs_get_target_path(struct kernfs_node *parent,
5762306a36Sopenharmony_ci				  struct kernfs_node *target, char *path)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct kernfs_node *base, *kn;
6062306a36Sopenharmony_ci	char *s = path;
6162306a36Sopenharmony_ci	int len = 0;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* go up to the root, stop at the base */
6462306a36Sopenharmony_ci	base = parent;
6562306a36Sopenharmony_ci	while (base->parent) {
6662306a36Sopenharmony_ci		kn = target->parent;
6762306a36Sopenharmony_ci		while (kn->parent && base != kn)
6862306a36Sopenharmony_ci			kn = kn->parent;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		if (base == kn)
7162306a36Sopenharmony_ci			break;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		if ((s - path) + 3 >= PATH_MAX)
7462306a36Sopenharmony_ci			return -ENAMETOOLONG;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		strcpy(s, "../");
7762306a36Sopenharmony_ci		s += 3;
7862306a36Sopenharmony_ci		base = base->parent;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* determine end of target string for reverse fillup */
8262306a36Sopenharmony_ci	kn = target;
8362306a36Sopenharmony_ci	while (kn->parent && kn != base) {
8462306a36Sopenharmony_ci		len += strlen(kn->name) + 1;
8562306a36Sopenharmony_ci		kn = kn->parent;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	/* check limits */
8962306a36Sopenharmony_ci	if (len < 2)
9062306a36Sopenharmony_ci		return -EINVAL;
9162306a36Sopenharmony_ci	len--;
9262306a36Sopenharmony_ci	if ((s - path) + len >= PATH_MAX)
9362306a36Sopenharmony_ci		return -ENAMETOOLONG;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* reverse fillup of target string from target to base */
9662306a36Sopenharmony_ci	kn = target;
9762306a36Sopenharmony_ci	while (kn->parent && kn != base) {
9862306a36Sopenharmony_ci		int slen = strlen(kn->name);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		len -= slen;
10162306a36Sopenharmony_ci		memcpy(s + len, kn->name, slen);
10262306a36Sopenharmony_ci		if (len)
10362306a36Sopenharmony_ci			s[--len] = '/';
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		kn = kn->parent;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return 0;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int kernfs_getlink(struct inode *inode, char *path)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	struct kernfs_node *kn = inode->i_private;
11462306a36Sopenharmony_ci	struct kernfs_node *parent = kn->parent;
11562306a36Sopenharmony_ci	struct kernfs_node *target = kn->symlink.target_kn;
11662306a36Sopenharmony_ci	struct kernfs_root *root = kernfs_root(parent);
11762306a36Sopenharmony_ci	int error;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	down_read(&root->kernfs_rwsem);
12062306a36Sopenharmony_ci	error = kernfs_get_target_path(parent, target, path);
12162306a36Sopenharmony_ci	up_read(&root->kernfs_rwsem);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return error;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic const char *kernfs_iop_get_link(struct dentry *dentry,
12762306a36Sopenharmony_ci				       struct inode *inode,
12862306a36Sopenharmony_ci				       struct delayed_call *done)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	char *body;
13162306a36Sopenharmony_ci	int error;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (!dentry)
13462306a36Sopenharmony_ci		return ERR_PTR(-ECHILD);
13562306a36Sopenharmony_ci	body = kzalloc(PAGE_SIZE, GFP_KERNEL);
13662306a36Sopenharmony_ci	if (!body)
13762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
13862306a36Sopenharmony_ci	error = kernfs_getlink(inode, body);
13962306a36Sopenharmony_ci	if (unlikely(error < 0)) {
14062306a36Sopenharmony_ci		kfree(body);
14162306a36Sopenharmony_ci		return ERR_PTR(error);
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci	set_delayed_call(done, kfree_link, body);
14462306a36Sopenharmony_ci	return body;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ciconst struct inode_operations kernfs_symlink_iops = {
14862306a36Sopenharmony_ci	.listxattr	= kernfs_iop_listxattr,
14962306a36Sopenharmony_ci	.get_link	= kernfs_iop_get_link,
15062306a36Sopenharmony_ci	.setattr	= kernfs_iop_setattr,
15162306a36Sopenharmony_ci	.getattr	= kernfs_iop_getattr,
15262306a36Sopenharmony_ci	.permission	= kernfs_iop_permission,
15362306a36Sopenharmony_ci};
154