Merge branch 'work.d_path' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull vfs d_path() updates from Al Viro: "d_path.c refactoring" * 'work.d_path' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: getcwd(2): clean up error handling d_path: prepend_path() is unlikely to return non-zero d_path: prepend_path(): lift the inner loop into a new helper d_path: prepend_path(): lift resetting b in case when we'd return 3 out of loop d_path: prepend_path(): get rid of vfsmnt d_path: introduce struct prepend_buffer d_path: make prepend_name() boolean d_path: lift -ENAMETOOLONG handling into callers of prepend_path() d_path: don't bother with return value of prepend() getcwd(2): saner logics around prepend_path() call d_path: get rid of path_with_deleted() d_path: regularize handling of root dentry in __dentry_path() d_path: saner calling conventions for __dentry_path() d_path: "\0" is {0,0}, not {0}
This commit is contained in:
commit
f92a322a63
326
fs/d_path.c
326
fs/d_path.c
@ -8,14 +8,27 @@
|
||||
#include <linux/prefetch.h>
|
||||
#include "mount.h"
|
||||
|
||||
static int prepend(char **buffer, int *buflen, const char *str, int namelen)
|
||||
struct prepend_buffer {
|
||||
char *buf;
|
||||
int len;
|
||||
};
|
||||
#define DECLARE_BUFFER(__name, __buf, __len) \
|
||||
struct prepend_buffer __name = {.buf = __buf + __len, .len = __len}
|
||||
|
||||
static char *extract_string(struct prepend_buffer *p)
|
||||
{
|
||||
*buflen -= namelen;
|
||||
if (*buflen < 0)
|
||||
return -ENAMETOOLONG;
|
||||
*buffer -= namelen;
|
||||
memcpy(*buffer, str, namelen);
|
||||
return 0;
|
||||
if (likely(p->len >= 0))
|
||||
return p->buf;
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
}
|
||||
|
||||
static void prepend(struct prepend_buffer *p, const char *str, int namelen)
|
||||
{
|
||||
p->len -= namelen;
|
||||
if (likely(p->len >= 0)) {
|
||||
p->buf -= namelen;
|
||||
memcpy(p->buf, str, namelen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,22 +48,58 @@ static int prepend(char **buffer, int *buflen, const char *str, int namelen)
|
||||
*
|
||||
* Load acquire is needed to make sure that we see that terminating NUL.
|
||||
*/
|
||||
static int prepend_name(char **buffer, int *buflen, const struct qstr *name)
|
||||
static bool prepend_name(struct prepend_buffer *p, const struct qstr *name)
|
||||
{
|
||||
const char *dname = smp_load_acquire(&name->name); /* ^^^ */
|
||||
u32 dlen = READ_ONCE(name->len);
|
||||
char *p;
|
||||
char *s;
|
||||
|
||||
*buflen -= dlen + 1;
|
||||
if (*buflen < 0)
|
||||
return -ENAMETOOLONG;
|
||||
p = *buffer -= dlen + 1;
|
||||
*p++ = '/';
|
||||
p->len -= dlen + 1;
|
||||
if (unlikely(p->len < 0))
|
||||
return false;
|
||||
s = p->buf -= dlen + 1;
|
||||
*s++ = '/';
|
||||
while (dlen--) {
|
||||
char c = *dname++;
|
||||
if (!c)
|
||||
break;
|
||||
*p++ = c;
|
||||
*s++ = c;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __prepend_path(const struct dentry *dentry, const struct mount *mnt,
|
||||
const struct path *root, struct prepend_buffer *p)
|
||||
{
|
||||
while (dentry != root->dentry || &mnt->mnt != root->mnt) {
|
||||
const struct dentry *parent = READ_ONCE(dentry->d_parent);
|
||||
|
||||
if (dentry == mnt->mnt.mnt_root) {
|
||||
struct mount *m = READ_ONCE(mnt->mnt_parent);
|
||||
struct mnt_namespace *mnt_ns;
|
||||
|
||||
if (likely(mnt != m)) {
|
||||
dentry = READ_ONCE(mnt->mnt_mountpoint);
|
||||
mnt = m;
|
||||
continue;
|
||||
}
|
||||
/* Global root */
|
||||
mnt_ns = READ_ONCE(mnt->mnt_ns);
|
||||
/* open-coded is_mounted() to use local mnt_ns */
|
||||
if (!IS_ERR_OR_NULL(mnt_ns) && !is_anon_ns(mnt_ns))
|
||||
return 1; // absolute root
|
||||
else
|
||||
return 2; // detached or not attached yet
|
||||
}
|
||||
|
||||
if (unlikely(dentry == parent))
|
||||
/* Escaped? */
|
||||
return 3;
|
||||
|
||||
prefetch(parent);
|
||||
if (!prepend_name(p, &dentry->d_name))
|
||||
break;
|
||||
dentry = parent;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -74,15 +123,11 @@ static int prepend_name(char **buffer, int *buflen, const struct qstr *name)
|
||||
*/
|
||||
static int prepend_path(const struct path *path,
|
||||
const struct path *root,
|
||||
char **buffer, int *buflen)
|
||||
struct prepend_buffer *p)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
struct vfsmount *vfsmnt;
|
||||
struct mount *mnt;
|
||||
int error = 0;
|
||||
unsigned seq, m_seq = 0;
|
||||
char *bptr;
|
||||
int blen;
|
||||
struct prepend_buffer b;
|
||||
int error;
|
||||
|
||||
rcu_read_lock();
|
||||
restart_mnt:
|
||||
@ -90,50 +135,9 @@ restart_mnt:
|
||||
seq = 0;
|
||||
rcu_read_lock();
|
||||
restart:
|
||||
bptr = *buffer;
|
||||
blen = *buflen;
|
||||
error = 0;
|
||||
dentry = path->dentry;
|
||||
vfsmnt = path->mnt;
|
||||
mnt = real_mount(vfsmnt);
|
||||
b = *p;
|
||||
read_seqbegin_or_lock(&rename_lock, &seq);
|
||||
while (dentry != root->dentry || vfsmnt != root->mnt) {
|
||||
struct dentry * parent;
|
||||
|
||||
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
|
||||
struct mount *parent = READ_ONCE(mnt->mnt_parent);
|
||||
struct mnt_namespace *mnt_ns;
|
||||
|
||||
/* Escaped? */
|
||||
if (dentry != vfsmnt->mnt_root) {
|
||||
bptr = *buffer;
|
||||
blen = *buflen;
|
||||
error = 3;
|
||||
break;
|
||||
}
|
||||
/* Global root? */
|
||||
if (mnt != parent) {
|
||||
dentry = READ_ONCE(mnt->mnt_mountpoint);
|
||||
mnt = parent;
|
||||
vfsmnt = &mnt->mnt;
|
||||
continue;
|
||||
}
|
||||
mnt_ns = READ_ONCE(mnt->mnt_ns);
|
||||
/* open-coded is_mounted() to use local mnt_ns */
|
||||
if (!IS_ERR_OR_NULL(mnt_ns) && !is_anon_ns(mnt_ns))
|
||||
error = 1; // absolute root
|
||||
else
|
||||
error = 2; // detached or not attached yet
|
||||
break;
|
||||
}
|
||||
parent = dentry->d_parent;
|
||||
prefetch(parent);
|
||||
error = prepend_name(&bptr, &blen, &dentry->d_name);
|
||||
if (error)
|
||||
break;
|
||||
|
||||
dentry = parent;
|
||||
}
|
||||
error = __prepend_path(path->dentry, real_mount(path->mnt), root, &b);
|
||||
if (!(seq & 1))
|
||||
rcu_read_unlock();
|
||||
if (need_seqretry(&rename_lock, seq)) {
|
||||
@ -150,14 +154,13 @@ restart:
|
||||
}
|
||||
done_seqretry(&mount_lock, m_seq);
|
||||
|
||||
if (error >= 0 && bptr == *buffer) {
|
||||
if (--blen < 0)
|
||||
error = -ENAMETOOLONG;
|
||||
else
|
||||
*--bptr = '/';
|
||||
}
|
||||
*buffer = bptr;
|
||||
*buflen = blen;
|
||||
if (unlikely(error == 3))
|
||||
b = *p;
|
||||
|
||||
if (b.len == p->len)
|
||||
prepend(&b, "/", 1);
|
||||
|
||||
*p = b;
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -181,56 +184,24 @@ char *__d_path(const struct path *path,
|
||||
const struct path *root,
|
||||
char *buf, int buflen)
|
||||
{
|
||||
char *res = buf + buflen;
|
||||
int error;
|
||||
DECLARE_BUFFER(b, buf, buflen);
|
||||
|
||||
prepend(&res, &buflen, "\0", 1);
|
||||
error = prepend_path(path, root, &res, &buflen);
|
||||
|
||||
if (error < 0)
|
||||
return ERR_PTR(error);
|
||||
if (error > 0)
|
||||
prepend(&b, "", 1);
|
||||
if (unlikely(prepend_path(path, root, &b) > 0))
|
||||
return NULL;
|
||||
return res;
|
||||
return extract_string(&b);
|
||||
}
|
||||
|
||||
char *d_absolute_path(const struct path *path,
|
||||
char *buf, int buflen)
|
||||
{
|
||||
struct path root = {};
|
||||
char *res = buf + buflen;
|
||||
int error;
|
||||
DECLARE_BUFFER(b, buf, buflen);
|
||||
|
||||
prepend(&res, &buflen, "\0", 1);
|
||||
error = prepend_path(path, &root, &res, &buflen);
|
||||
|
||||
if (error > 1)
|
||||
error = -EINVAL;
|
||||
if (error < 0)
|
||||
return ERR_PTR(error);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* same as __d_path but appends "(deleted)" for unlinked files.
|
||||
*/
|
||||
static int path_with_deleted(const struct path *path,
|
||||
const struct path *root,
|
||||
char **buf, int *buflen)
|
||||
{
|
||||
prepend(buf, buflen, "\0", 1);
|
||||
if (d_unlinked(path->dentry)) {
|
||||
int error = prepend(buf, buflen, " (deleted)", 10);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
return prepend_path(path, root, buf, buflen);
|
||||
}
|
||||
|
||||
static int prepend_unreachable(char **buffer, int *buflen)
|
||||
{
|
||||
return prepend(buffer, buflen, "(unreachable)", 13);
|
||||
prepend(&b, "", 1);
|
||||
if (unlikely(prepend_path(path, &root, &b) > 1))
|
||||
return ERR_PTR(-EINVAL);
|
||||
return extract_string(&b);
|
||||
}
|
||||
|
||||
static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
|
||||
@ -261,9 +232,8 @@ static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
|
||||
*/
|
||||
char *d_path(const struct path *path, char *buf, int buflen)
|
||||
{
|
||||
char *res = buf + buflen;
|
||||
DECLARE_BUFFER(b, buf, buflen);
|
||||
struct path root;
|
||||
int error;
|
||||
|
||||
/*
|
||||
* We have various synthetic filesystems that never get mounted. On
|
||||
@ -282,12 +252,14 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
||||
|
||||
rcu_read_lock();
|
||||
get_fs_root_rcu(current->fs, &root);
|
||||
error = path_with_deleted(path, &root, &res, &buflen);
|
||||
if (unlikely(d_unlinked(path->dentry)))
|
||||
prepend(&b, " (deleted)", 11);
|
||||
else
|
||||
prepend(&b, "", 1);
|
||||
prepend_path(path, &root, &b);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (error < 0)
|
||||
res = ERR_PTR(error);
|
||||
return res;
|
||||
return extract_string(&b);
|
||||
}
|
||||
EXPORT_SYMBOL(d_path);
|
||||
|
||||
@ -314,47 +286,34 @@ char *dynamic_dname(struct dentry *dentry, char *buffer, int buflen,
|
||||
|
||||
char *simple_dname(struct dentry *dentry, char *buffer, int buflen)
|
||||
{
|
||||
char *end = buffer + buflen;
|
||||
DECLARE_BUFFER(b, buffer, buflen);
|
||||
/* these dentries are never renamed, so d_lock is not needed */
|
||||
if (prepend(&end, &buflen, " (deleted)", 11) ||
|
||||
prepend(&end, &buflen, dentry->d_name.name, dentry->d_name.len) ||
|
||||
prepend(&end, &buflen, "/", 1))
|
||||
end = ERR_PTR(-ENAMETOOLONG);
|
||||
return end;
|
||||
prepend(&b, " (deleted)", 11);
|
||||
prepend(&b, dentry->d_name.name, dentry->d_name.len);
|
||||
prepend(&b, "/", 1);
|
||||
return extract_string(&b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write full pathname from the root of the filesystem into the buffer.
|
||||
*/
|
||||
static char *__dentry_path(const struct dentry *d, char *buf, int buflen)
|
||||
static char *__dentry_path(const struct dentry *d, struct prepend_buffer *p)
|
||||
{
|
||||
const struct dentry *dentry;
|
||||
char *end, *retval;
|
||||
int len, seq = 0;
|
||||
int error = 0;
|
||||
|
||||
if (buflen < 2)
|
||||
goto Elong;
|
||||
struct prepend_buffer b;
|
||||
int seq = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
restart:
|
||||
dentry = d;
|
||||
end = buf + buflen;
|
||||
len = buflen;
|
||||
prepend(&end, &len, "\0", 1);
|
||||
/* Get '/' right */
|
||||
retval = end-1;
|
||||
*retval = '/';
|
||||
b = *p;
|
||||
read_seqbegin_or_lock(&rename_lock, &seq);
|
||||
while (!IS_ROOT(dentry)) {
|
||||
const struct dentry *parent = dentry->d_parent;
|
||||
|
||||
prefetch(parent);
|
||||
error = prepend_name(&end, &len, &dentry->d_name);
|
||||
if (error)
|
||||
if (!prepend_name(&b, &dentry->d_name))
|
||||
break;
|
||||
|
||||
retval = end;
|
||||
dentry = parent;
|
||||
}
|
||||
if (!(seq & 1))
|
||||
@ -364,36 +323,29 @@ restart:
|
||||
goto restart;
|
||||
}
|
||||
done_seqretry(&rename_lock, seq);
|
||||
if (error)
|
||||
goto Elong;
|
||||
return retval;
|
||||
Elong:
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
if (b.len == p->len)
|
||||
prepend(&b, "/", 1);
|
||||
return extract_string(&b);
|
||||
}
|
||||
|
||||
char *dentry_path_raw(const struct dentry *dentry, char *buf, int buflen)
|
||||
{
|
||||
return __dentry_path(dentry, buf, buflen);
|
||||
DECLARE_BUFFER(b, buf, buflen);
|
||||
|
||||
prepend(&b, "", 1);
|
||||
return __dentry_path(dentry, &b);
|
||||
}
|
||||
EXPORT_SYMBOL(dentry_path_raw);
|
||||
|
||||
char *dentry_path(const struct dentry *dentry, char *buf, int buflen)
|
||||
{
|
||||
char *p = NULL;
|
||||
char *retval;
|
||||
DECLARE_BUFFER(b, buf, buflen);
|
||||
|
||||
if (d_unlinked(dentry)) {
|
||||
p = buf + buflen;
|
||||
if (prepend(&p, &buflen, "//deleted", 10) != 0)
|
||||
goto Elong;
|
||||
buflen++;
|
||||
}
|
||||
retval = __dentry_path(dentry, buf, buflen);
|
||||
if (!IS_ERR(retval) && p)
|
||||
*p = '/'; /* restore '/' overriden with '\0' */
|
||||
return retval;
|
||||
Elong:
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
if (unlikely(d_unlinked(dentry)))
|
||||
prepend(&b, "//deleted", 10);
|
||||
else
|
||||
prepend(&b, "", 1);
|
||||
return __dentry_path(dentry, &b);
|
||||
}
|
||||
|
||||
static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root,
|
||||
@ -438,38 +390,28 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
|
||||
rcu_read_lock();
|
||||
get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
|
||||
|
||||
error = -ENOENT;
|
||||
if (!d_unlinked(pwd.dentry)) {
|
||||
unsigned long len;
|
||||
char *cwd = page + PATH_MAX;
|
||||
int buflen = PATH_MAX;
|
||||
|
||||
prepend(&cwd, &buflen, "\0", 1);
|
||||
error = prepend_path(&pwd, &root, &cwd, &buflen);
|
||||
if (unlikely(d_unlinked(pwd.dentry))) {
|
||||
rcu_read_unlock();
|
||||
|
||||
if (error < 0)
|
||||
goto out;
|
||||
|
||||
/* Unreachable from current root */
|
||||
if (error > 0) {
|
||||
error = prepend_unreachable(&cwd, &buflen);
|
||||
if (error)
|
||||
goto out;
|
||||
}
|
||||
|
||||
error = -ERANGE;
|
||||
len = PATH_MAX + page - cwd;
|
||||
if (len <= size) {
|
||||
error = len;
|
||||
if (copy_to_user(buf, cwd, len))
|
||||
error = -EFAULT;
|
||||
}
|
||||
error = -ENOENT;
|
||||
} else {
|
||||
rcu_read_unlock();
|
||||
}
|
||||
unsigned len;
|
||||
DECLARE_BUFFER(b, page, PATH_MAX);
|
||||
|
||||
out:
|
||||
prepend(&b, "", 1);
|
||||
if (unlikely(prepend_path(&pwd, &root, &b) > 0))
|
||||
prepend(&b, "(unreachable)", 13);
|
||||
rcu_read_unlock();
|
||||
|
||||
len = PATH_MAX - b.len;
|
||||
if (unlikely(len > PATH_MAX))
|
||||
error = -ENAMETOOLONG;
|
||||
else if (unlikely(len > size))
|
||||
error = -ERANGE;
|
||||
else if (copy_to_user(buf, b.buf, len))
|
||||
error = -EFAULT;
|
||||
else
|
||||
error = len;
|
||||
}
|
||||
__putname(page);
|
||||
return error;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user