From dfe5087675e66f3cce8d98447bf01602f3cdea24 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 21:11:45 -0400 Subject: d_path: "\0" is {0,0}, not {0} Single-element array consisting of one NUL is spelled ""... Signed-off-by: Al Viro --- fs/d_path.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 270d62133996..01df5dfa1f88 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -184,7 +184,7 @@ char *__d_path(const struct path *path, char *res = buf + buflen; int error; - prepend(&res, &buflen, "\0", 1); + prepend(&res, &buflen, "", 1); error = prepend_path(path, root, &res, &buflen); if (error < 0) @@ -201,7 +201,7 @@ char *d_absolute_path(const struct path *path, char *res = buf + buflen; int error; - prepend(&res, &buflen, "\0", 1); + prepend(&res, &buflen, "", 1); error = prepend_path(path, &root, &res, &buflen); if (error > 1) @@ -218,7 +218,7 @@ static int path_with_deleted(const struct path *path, const struct path *root, char **buf, int *buflen) { - prepend(buf, buflen, "\0", 1); + prepend(buf, buflen, "", 1); if (d_unlinked(path->dentry)) { int error = prepend(buf, buflen, " (deleted)", 10); if (error) @@ -341,7 +341,7 @@ restart: dentry = d; end = buf + buflen; len = buflen; - prepend(&end, &len, "\0", 1); + prepend(&end, &len, "", 1); /* Get '/' right */ retval = end-1; *retval = '/'; @@ -444,7 +444,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) char *cwd = page + PATH_MAX; int buflen = PATH_MAX; - prepend(&cwd, &buflen, "\0", 1); + prepend(&cwd, &buflen, "", 1); error = prepend_path(&pwd, &root, &cwd, &buflen); rcu_read_unlock(); -- cgit v1.2.3 From 3a291c974cf7ffb4fad4df6911b912536e511c9d Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 20:16:51 -0400 Subject: d_path: saner calling conventions for __dentry_path() 1) lift NUL-termination into the callers 2) pass pointer to the end of buffer instead of that to beginning. (1) allows to simplify dentry_path() - we don't need to play silly games with restoring the leading / of "//deleted" after __dentry_path() would've overwritten it with NUL. We also do not need to check if (either) prepend() in there fails - if the buffer is not large enough, we'll end with negative buflen after prepend() and __dentry_path() will return the right value (ERR_PTR(-ENAMETOOLONG)) just fine. Signed-off-by: Al Viro --- fs/d_path.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 01df5dfa1f88..1a1cf05e7780 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -326,22 +326,21 @@ char *simple_dname(struct dentry *dentry, char *buffer, int buflen) /* * 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, char *p, int buflen) { const struct dentry *dentry; char *end, *retval; int len, seq = 0; int error = 0; - if (buflen < 2) + if (buflen < 1) goto Elong; rcu_read_lock(); restart: dentry = d; - end = buf + buflen; + end = p; len = buflen; - prepend(&end, &len, "", 1); /* Get '/' right */ retval = end-1; *retval = '/'; @@ -373,27 +372,21 @@ Elong: char *dentry_path_raw(const struct dentry *dentry, char *buf, int buflen) { - return __dentry_path(dentry, buf, buflen); + char *p = buf + buflen; + prepend(&p, &buflen, "", 1); + return __dentry_path(dentry, p, buflen); } EXPORT_SYMBOL(dentry_path_raw); char *dentry_path(const struct dentry *dentry, char *buf, int buflen) { - char *p = NULL; - char *retval; - - 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); + char *p = buf + buflen; + + if (unlikely(d_unlinked(dentry))) + prepend(&p, &buflen, "//deleted", 10); + else + prepend(&p, &buflen, "", 1); + return __dentry_path(dentry, p, buflen); } static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root, -- cgit v1.2.3 From 3acca043261fa6268961d152ea2505e217eabcba Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 21:19:35 -0400 Subject: d_path: regularize handling of root dentry in __dentry_path() All path-forming primitives boil down to sequence of prepend_name() on dentries encountered along the way toward root. Each time we prepend / + dentry name to the buffer. Normally that does exactly what we want, but there's a corner case when we don't call prepend_name() at all (in case of __dentry_path() that happens if we are given root dentry). We obviously want to end up with "/", rather than "", so this corner case needs to be handled. __dentry_path() used to manually put '/' in the end of buffer before doing anything else, to be overwritten by the first call of prepend_name() if one happens and to be left in place if we don't call prepend_name() at all. That required manually checking that we had space in the buffer (prepend_name() and prepend() take care of such checks themselves) and lead to clumsy keeping track of return value. A better approach is to check if the main loop has added anything into the buffer and prepend "/" if it hasn't. A side benefit of using prepend() is that it does the right thing if we'd already run out of buffer, making the overflow-handling logics simpler. Signed-off-by: Al Viro --- fs/d_path.c | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 1a1cf05e7780..b3324ae7cfe2 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -329,31 +329,22 @@ char *simple_dname(struct dentry *dentry, char *buffer, int buflen) static char *__dentry_path(const struct dentry *d, char *p, int buflen) { const struct dentry *dentry; - char *end, *retval; + char *end; int len, seq = 0; - int error = 0; - - if (buflen < 1) - goto Elong; rcu_read_lock(); restart: dentry = d; end = p; len = buflen; - /* Get '/' right */ - retval = end-1; - *retval = '/'; 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 (unlikely(prepend_name(&end, &len, &dentry->d_name) < 0)) break; - retval = end; dentry = parent; } if (!(seq & 1)) @@ -363,11 +354,9 @@ restart: goto restart; } done_seqretry(&rename_lock, seq); - if (error) - goto Elong; - return retval; -Elong: - return ERR_PTR(-ENAMETOOLONG); + if (len == buflen) + prepend(&end, &len, "/", 1); + return len >= 0 ? end : ERR_PTR(-ENAMETOOLONG); } char *dentry_path_raw(const struct dentry *dentry, char *buf, int buflen) -- cgit v1.2.3 From 9024348f539f810e83695df486443f45362e947a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 21:43:01 -0400 Subject: d_path: get rid of path_with_deleted() expand in the sole caller; transform the initial prepends similar to what we'd done in dentry_path() (prepend_path() will fail the right way if we call it with negative buflen, same as __dentry_path() does). Signed-off-by: Al Viro --- fs/d_path.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index b3324ae7cfe2..7f3fac544bbb 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -211,23 +211,6 @@ char *d_absolute_path(const struct path *path, 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, "", 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); @@ -282,7 +265,11 @@ 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(&res, &buflen, " (deleted)", 11); + else + prepend(&res, &buflen, "", 1); + error = prepend_path(path, &root, &res, &buflen); rcu_read_unlock(); if (error < 0) -- cgit v1.2.3 From a0378fb9b33308fb4547f098c6281af8ab4b5fb5 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 21:56:38 -0400 Subject: getcwd(2): saner logics around prepend_path() call The only negative value that might get returned by prepend_path() is -ENAMETOOLONG, and that happens only on overflow. The same goes for prepend_unreachable(). Overflow is detectable by observing negative buflen, so we can simplify the control flow around the prepend_path() call. Expand prepend_unreachable(), while we are at it - that's the only caller. Signed-off-by: Al Viro --- fs/d_path.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 7f3fac544bbb..311d43287572 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -211,11 +211,6 @@ char *d_absolute_path(const struct path *path, return res; } -static int prepend_unreachable(char **buffer, int *buflen) -{ - return prepend(buffer, buflen, "(unreachable)", 13); -} - static void get_fs_root_rcu(struct fs_struct *fs, struct path *root) { unsigned seq; @@ -414,17 +409,13 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) int buflen = PATH_MAX; prepend(&cwd, &buflen, "", 1); - error = prepend_path(&pwd, &root, &cwd, &buflen); + if (prepend_path(&pwd, &root, &cwd, &buflen) > 0) + prepend(&cwd, &buflen, "(unreachable)", 13); rcu_read_unlock(); - if (error < 0) + if (buflen < 0) { + error = -ENAMETOOLONG; goto out; - - /* Unreachable from current root */ - if (error > 0) { - error = prepend_unreachable(&cwd, &buflen); - if (error) - goto out; } error = -ERANGE; -- cgit v1.2.3 From d8548232ea2858d1d130f3ac835185159d367caa Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 22:05:23 -0400 Subject: d_path: don't bother with return value of prepend() Only simple_dname() checks it, and there we can simply do those calls and check for overflow (by looking of negative buflen) in the end. Signed-off-by: Al Viro --- fs/d_path.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 311d43287572..72b8087aaf9c 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -8,14 +8,13 @@ #include #include "mount.h" -static int prepend(char **buffer, int *buflen, const char *str, int namelen) +static void prepend(char **buffer, int *buflen, const char *str, int namelen) { *buflen -= namelen; - if (*buflen < 0) - return -ENAMETOOLONG; - *buffer -= namelen; - memcpy(*buffer, str, namelen); - return 0; + if (likely(*buflen >= 0)) { + *buffer -= namelen; + memcpy(*buffer, str, namelen); + } } /** @@ -298,11 +297,10 @@ char *simple_dname(struct dentry *dentry, char *buffer, int buflen) { char *end = 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(&end, &buflen, " (deleted)", 11); + prepend(&end, &buflen, dentry->d_name.name, dentry->d_name.len); + prepend(&end, &buflen, "/", 1); + return buflen >= 0 ? end : ERR_PTR(-ENAMETOOLONG); } /* -- cgit v1.2.3 From 01a4428ee7068875995ee27e9ba5503874f23e3b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 22:29:03 -0400 Subject: d_path: lift -ENAMETOOLONG handling into callers of prepend_path() The only negative value ever returned by prepend_path() is -ENAMETOOLONG and callers can recognize that situation (overflow) by looking at the sign of buflen. Lift that into the callers; we already have the same logics (buf if buflen is non-negative, ERR_PTR(-ENAMETOOLONG) otherwise) in several places and that'll become a new primitive several commits down the road. Make prepend_path() return 0 instead of -ENAMETOOLONG. That makes for saner calling conventions (0/1/2/3/-ENAMETOOLONG is obnoxious) and callers actually get simpler, especially once the aforementioned primitive gets added. In prepend_path() itself we switch prepending the / (in case of empty path) to use of prepend() - no need to open-code that, compiler will do the right thing. It's exactly the same logics as in __dentry_path(). Signed-off-by: Al Viro --- fs/d_path.c | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 72b8087aaf9c..327cc3744554 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -127,8 +127,7 @@ restart: } parent = dentry->d_parent; prefetch(parent); - error = prepend_name(&bptr, &blen, &dentry->d_name); - if (error) + if (unlikely(prepend_name(&bptr, &blen, &dentry->d_name) < 0)) break; dentry = parent; @@ -149,12 +148,9 @@ restart: } done_seqretry(&mount_lock, m_seq); - if (error >= 0 && bptr == *buffer) { - if (--blen < 0) - error = -ENAMETOOLONG; - else - *--bptr = '/'; - } + if (blen == *buflen) + prepend(&bptr, &blen, "/", 1); + *buffer = bptr; *buflen = blen; return error; @@ -181,16 +177,11 @@ char *__d_path(const struct path *path, char *buf, int buflen) { char *res = buf + buflen; - int error; prepend(&res, &buflen, "", 1); - error = prepend_path(path, root, &res, &buflen); - - if (error < 0) - return ERR_PTR(error); - if (error > 0) + if (prepend_path(path, root, &res, &buflen) > 0) return NULL; - return res; + return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); } char *d_absolute_path(const struct path *path, @@ -198,16 +189,11 @@ char *d_absolute_path(const struct path *path, { struct path root = {}; char *res = buf + buflen; - int error; prepend(&res, &buflen, "", 1); - error = prepend_path(path, &root, &res, &buflen); - - if (error > 1) - error = -EINVAL; - if (error < 0) - return ERR_PTR(error); - return res; + if (prepend_path(path, &root, &res, &buflen) > 1) + return ERR_PTR(-EINVAL); + return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); } static void get_fs_root_rcu(struct fs_struct *fs, struct path *root) @@ -240,7 +226,6 @@ char *d_path(const struct path *path, char *buf, int buflen) { char *res = buf + buflen; struct path root; - int error; /* * We have various synthetic filesystems that never get mounted. On @@ -263,12 +248,10 @@ char *d_path(const struct path *path, char *buf, int buflen) prepend(&res, &buflen, " (deleted)", 11); else prepend(&res, &buflen, "", 1); - error = prepend_path(path, &root, &res, &buflen); + prepend_path(path, &root, &res, &buflen); rcu_read_unlock(); - if (error < 0) - res = ERR_PTR(error); - return res; + return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); } EXPORT_SYMBOL(d_path); -- cgit v1.2.3 From 95b55c42f65af3fb9e2dceaf4d8dde522c0472d3 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 17 May 2021 22:41:11 -0400 Subject: d_path: make prepend_name() boolean It returns only 0 or -ENAMETOOLONG and both callers only check if the result is negative. Might as well return true on success and false on failure... Signed-off-by: Al Viro --- fs/d_path.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 327cc3744554..83db83446afd 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -34,15 +34,15 @@ static void 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(char **buffer, int *buflen, const struct qstr *name) { const char *dname = smp_load_acquire(&name->name); /* ^^^ */ u32 dlen = READ_ONCE(name->len); char *p; *buflen -= dlen + 1; - if (*buflen < 0) - return -ENAMETOOLONG; + if (unlikely(*buflen < 0)) + return false; p = *buffer -= dlen + 1; *p++ = '/'; while (dlen--) { @@ -51,7 +51,7 @@ static int prepend_name(char **buffer, int *buflen, const struct qstr *name) break; *p++ = c; } - return 0; + return true; } /** @@ -127,7 +127,7 @@ restart: } parent = dentry->d_parent; prefetch(parent); - if (unlikely(prepend_name(&bptr, &blen, &dentry->d_name) < 0)) + if (!prepend_name(&bptr, &blen, &dentry->d_name)) break; dentry = parent; @@ -305,7 +305,7 @@ restart: const struct dentry *parent = dentry->d_parent; prefetch(parent); - if (unlikely(prepend_name(&end, &len, &dentry->d_name) < 0)) + if (!prepend_name(&end, &len, &dentry->d_name)) break; dentry = parent; -- cgit v1.2.3 From ad08ae586586ea9e2c0228a3d5a083500ea54202 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 12 May 2021 14:51:03 -0400 Subject: d_path: introduce struct prepend_buffer We've a lot of places where we have pairs of form (pointer to end of buffer, amount of space left in front of that). These sit in pairs of variables located next to each other and usually passed by reference. Turn those into instances of new type (struct prepend_buffer) and pass reference to the pair instead of pairs of references to its fields. Declared and initialized by DECLARE_BUFFER(name, buf, buflen). extract_string(prepend_buffer) returns the buffer contents if no overflow has happened, ERR_PTR(ENAMETOOLONG) otherwise. All places where we used to have that boilerplate converted to use of that helper. Signed-off-by: Al Viro --- fs/d_path.c | 142 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 83db83446afd..06e93dd031bf 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -8,12 +8,26 @@ #include #include "mount.h" -static void 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 (likely(*buflen >= 0)) { - *buffer -= namelen; - memcpy(*buffer, str, namelen); + 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); } } @@ -34,22 +48,22 @@ static void prepend(char **buffer, int *buflen, const char *str, int namelen) * * Load acquire is needed to make sure that we see that terminating NUL. */ -static bool 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 (unlikely(*buflen < 0)) + p->len -= dlen + 1; + if (unlikely(p->len < 0)) return false; - p = *buffer -= dlen + 1; - *p++ = '/'; + s = p->buf -= dlen + 1; + *s++ = '/'; while (dlen--) { char c = *dname++; if (!c) break; - *p++ = c; + *s++ = c; } return true; } @@ -73,15 +87,14 @@ static bool 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; rcu_read_lock(); restart_mnt: @@ -89,8 +102,7 @@ restart_mnt: seq = 0; rcu_read_lock(); restart: - bptr = *buffer; - blen = *buflen; + b = *p; error = 0; dentry = path->dentry; vfsmnt = path->mnt; @@ -105,8 +117,7 @@ restart: /* Escaped? */ if (dentry != vfsmnt->mnt_root) { - bptr = *buffer; - blen = *buflen; + b = *p; error = 3; break; } @@ -127,7 +138,7 @@ restart: } parent = dentry->d_parent; prefetch(parent); - if (!prepend_name(&bptr, &blen, &dentry->d_name)) + if (!prepend_name(&b, &dentry->d_name)) break; dentry = parent; @@ -148,11 +159,10 @@ restart: } done_seqretry(&mount_lock, m_seq); - if (blen == *buflen) - prepend(&bptr, &blen, "/", 1); + if (b.len == p->len) + prepend(&b, "/", 1); - *buffer = bptr; - *buflen = blen; + *p = b; return error; } @@ -176,24 +186,24 @@ char *__d_path(const struct path *path, const struct path *root, char *buf, int buflen) { - char *res = buf + buflen; + DECLARE_BUFFER(b, buf, buflen); - prepend(&res, &buflen, "", 1); - if (prepend_path(path, root, &res, &buflen) > 0) + prepend(&b, "", 1); + if (prepend_path(path, root, &b) > 0) return NULL; - return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); + return extract_string(&b); } char *d_absolute_path(const struct path *path, char *buf, int buflen) { struct path root = {}; - char *res = buf + buflen; + DECLARE_BUFFER(b, buf, buflen); - prepend(&res, &buflen, "", 1); - if (prepend_path(path, &root, &res, &buflen) > 1) + prepend(&b, "", 1); + if (prepend_path(path, &root, &b) > 1) return ERR_PTR(-EINVAL); - return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); + return extract_string(&b); } static void get_fs_root_rcu(struct fs_struct *fs, struct path *root) @@ -224,7 +234,7 @@ 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; /* @@ -245,13 +255,13 @@ char *d_path(const struct path *path, char *buf, int buflen) rcu_read_lock(); get_fs_root_rcu(current->fs, &root); if (unlikely(d_unlinked(path->dentry))) - prepend(&res, &buflen, " (deleted)", 11); + prepend(&b, " (deleted)", 11); else - prepend(&res, &buflen, "", 1); - prepend_path(path, &root, &res, &buflen); + prepend(&b, "", 1); + prepend_path(path, &root, &b); rcu_read_unlock(); - return buflen >= 0 ? res : ERR_PTR(-ENAMETOOLONG); + return extract_string(&b); } EXPORT_SYMBOL(d_path); @@ -278,36 +288,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 */ - prepend(&end, &buflen, " (deleted)", 11); - prepend(&end, &buflen, dentry->d_name.name, dentry->d_name.len); - prepend(&end, &buflen, "/", 1); - return buflen >= 0 ? end : ERR_PTR(-ENAMETOOLONG); + 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 *p, int buflen) +static char *__dentry_path(const struct dentry *d, struct prepend_buffer *p) { const struct dentry *dentry; - char *end; - int len, seq = 0; + struct prepend_buffer b; + int seq = 0; rcu_read_lock(); restart: dentry = d; - end = p; - len = buflen; + b = *p; read_seqbegin_or_lock(&rename_lock, &seq); while (!IS_ROOT(dentry)) { const struct dentry *parent = dentry->d_parent; prefetch(parent); - if (!prepend_name(&end, &len, &dentry->d_name)) + if (!prepend_name(&b, &dentry->d_name)) break; - dentry = parent; } if (!(seq & 1)) @@ -317,28 +325,29 @@ restart: goto restart; } done_seqretry(&rename_lock, seq); - if (len == buflen) - prepend(&end, &len, "/", 1); - return len >= 0 ? end : 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) { - char *p = buf + buflen; - prepend(&p, &buflen, "", 1); - return __dentry_path(dentry, p, 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 = buf + buflen; + DECLARE_BUFFER(b, buf, buflen); if (unlikely(d_unlinked(dentry))) - prepend(&p, &buflen, "//deleted", 10); + prepend(&b, "//deleted", 10); else - prepend(&p, &buflen, "", 1); - return __dentry_path(dentry, p, buflen); + prepend(&b, "", 1); + return __dentry_path(dentry, &b); } static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root, @@ -386,24 +395,23 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) error = -ENOENT; if (!d_unlinked(pwd.dentry)) { unsigned long len; - char *cwd = page + PATH_MAX; - int buflen = PATH_MAX; + DECLARE_BUFFER(b, page, PATH_MAX); - prepend(&cwd, &buflen, "", 1); - if (prepend_path(&pwd, &root, &cwd, &buflen) > 0) - prepend(&cwd, &buflen, "(unreachable)", 13); + prepend(&b, "", 1); + if (prepend_path(&pwd, &root, &b) > 0) + prepend(&b, "(unreachable)", 13); rcu_read_unlock(); - if (buflen < 0) { + if (b.len < 0) { error = -ENAMETOOLONG; goto out; } error = -ERANGE; - len = PATH_MAX + page - cwd; + len = PATH_MAX - b.len; if (len <= size) { error = len; - if (copy_to_user(buf, cwd, len)) + if (copy_to_user(buf, b.buf, len)) error = -EFAULT; } } else { -- cgit v1.2.3 From 7c0d552fd5aa3527ed01290832ebbe072fa5de52 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 12 May 2021 16:24:12 -0400 Subject: d_path: prepend_path(): get rid of vfsmnt it's kept equal to &mnt->mnt all along. Signed-off-by: Al Viro --- fs/d_path.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 06e93dd031bf..3836f5d0b023 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -90,7 +90,6 @@ static int prepend_path(const struct path *path, struct prepend_buffer *p) { struct dentry *dentry; - struct vfsmount *vfsmnt; struct mount *mnt; int error = 0; unsigned seq, m_seq = 0; @@ -105,18 +104,17 @@ restart: b = *p; error = 0; dentry = path->dentry; - vfsmnt = path->mnt; - mnt = real_mount(vfsmnt); + mnt = real_mount(path->mnt); read_seqbegin_or_lock(&rename_lock, &seq); - while (dentry != root->dentry || vfsmnt != root->mnt) { + while (dentry != root->dentry || &mnt->mnt != root->mnt) { struct dentry * parent; - if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { + if (dentry == mnt->mnt.mnt_root || IS_ROOT(dentry)) { struct mount *parent = READ_ONCE(mnt->mnt_parent); struct mnt_namespace *mnt_ns; /* Escaped? */ - if (dentry != vfsmnt->mnt_root) { + if (dentry != mnt->mnt.mnt_root) { b = *p; error = 3; break; @@ -125,7 +123,6 @@ restart: if (mnt != parent) { dentry = READ_ONCE(mnt->mnt_mountpoint); mnt = parent; - vfsmnt = &mnt->mnt; continue; } mnt_ns = READ_ONCE(mnt->mnt_ns); -- cgit v1.2.3 From 2dac0ad17598d1e54a264ed9286fa7bdbd75a97b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 12 May 2021 16:21:43 -0400 Subject: d_path: prepend_path(): lift resetting b in case when we'd return 3 out of loop preparation to extracting the loop into helper (next commit) Signed-off-by: Al Viro --- fs/d_path.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/d_path.c b/fs/d_path.c index 3836f5d0b023..9a0356cc98d3 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -115,7 +115,6 @@ restart: /* Escaped? */ if (dentry != mnt->mnt.mnt_root) { - b = *p; error = 3; break; } @@ -156,6 +155,9 @@ restart: } done_seqretry(&mount_lock, m_seq); + if (unlikely(error == 3)) + b = *p; + if (b.len == p->len) prepend(&b, "/", 1); -- cgit v1.2.3 From 008673ff74a513ac2c282db8825b6118151ee660 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 12 May 2021 16:38:11 -0400 Subject: d_path: prepend_path(): lift the inner loop into a new helper ... and leave the rename_lock/mount_lock handling in prepend_path() itself Signed-off-by: Al Viro --- fs/d_path.c | 77 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 9a0356cc98d3..ba629879a4bf 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -68,6 +68,42 @@ static bool prepend_name(struct prepend_buffer *p, const struct qstr *name) 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; +} + /** * prepend_path - Prepend path string to a buffer * @path: the dentry/vfsmount to report @@ -89,11 +125,9 @@ static int prepend_path(const struct path *path, const struct path *root, struct prepend_buffer *p) { - struct dentry *dentry; - struct mount *mnt; - int error = 0; unsigned seq, m_seq = 0; struct prepend_buffer b; + int error; rcu_read_lock(); restart_mnt: @@ -102,43 +136,8 @@ restart_mnt: rcu_read_lock(); restart: b = *p; - error = 0; - dentry = path->dentry; - mnt = real_mount(path->mnt); read_seqbegin_or_lock(&rename_lock, &seq); - while (dentry != root->dentry || &mnt->mnt != root->mnt) { - struct dentry * parent; - - if (dentry == mnt->mnt.mnt_root || IS_ROOT(dentry)) { - struct mount *parent = READ_ONCE(mnt->mnt_parent); - struct mnt_namespace *mnt_ns; - - /* Escaped? */ - if (dentry != mnt->mnt.mnt_root) { - error = 3; - break; - } - /* Global root? */ - if (mnt != parent) { - dentry = READ_ONCE(mnt->mnt_mountpoint); - mnt = parent; - 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); - if (!prepend_name(&b, &dentry->d_name)) - 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)) { -- cgit v1.2.3 From cf4febc1adc8e5cd11ca3386e1e3ea356e0792f0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 16 May 2021 20:19:06 -0400 Subject: d_path: prepend_path() is unlikely to return non-zero Signed-off-by: Al Viro --- fs/d_path.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index ba629879a4bf..8a9cd44f6689 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -187,7 +187,7 @@ char *__d_path(const struct path *path, DECLARE_BUFFER(b, buf, buflen); prepend(&b, "", 1); - if (prepend_path(path, root, &b) > 0) + if (unlikely(prepend_path(path, root, &b) > 0)) return NULL; return extract_string(&b); } @@ -199,7 +199,7 @@ char *d_absolute_path(const struct path *path, DECLARE_BUFFER(b, buf, buflen); prepend(&b, "", 1); - if (prepend_path(path, &root, &b) > 1) + if (unlikely(prepend_path(path, &root, &b) > 1)) return ERR_PTR(-EINVAL); return extract_string(&b); } @@ -396,7 +396,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) DECLARE_BUFFER(b, page, PATH_MAX); prepend(&b, "", 1); - if (prepend_path(&pwd, &root, &b) > 0) + if (unlikely(prepend_path(&pwd, &root, &b) > 0)) prepend(&b, "(unreachable)", 13); rcu_read_unlock(); -- cgit v1.2.3 From e4b275531887fef7f7d8a7284bfc32f0fbbd4208 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 16 May 2021 20:24:00 -0400 Subject: getcwd(2): clean up error handling Signed-off-by: Al Viro --- fs/d_path.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/fs/d_path.c b/fs/d_path.c index 8a9cd44f6689..23a53f7b5c71 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -390,9 +390,11 @@ 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; + if (unlikely(d_unlinked(pwd.dentry))) { + rcu_read_unlock(); + error = -ENOENT; + } else { + unsigned len; DECLARE_BUFFER(b, page, PATH_MAX); prepend(&b, "", 1); @@ -400,23 +402,16 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) prepend(&b, "(unreachable)", 13); rcu_read_unlock(); - if (b.len < 0) { - error = -ENAMETOOLONG; - goto out; - } - - error = -ERANGE; len = PATH_MAX - b.len; - if (len <= size) { + 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; - if (copy_to_user(buf, b.buf, len)) - error = -EFAULT; - } - } else { - rcu_read_unlock(); } - -out: __putname(page); return error; } -- cgit v1.2.3