apparmor/kernel-patches/for-mainline/unambiguous-__d_path.diff
2008-07-02 20:24:33 +00:00

257 lines
7.9 KiB
Diff

From: Andreas Gruenbacher <agruen@suse.de>
Subject: Fix __d_path() for lazy unmounts and make it unambiguous
First, when __d_path() hits a lazily unmounted mount point, it tries to prepend
the name of the lazily unmounted dentry to the path name. It gets this wrong,
and also overwrites the slash that separates the name from the following
pathname component. This patch fixes that; if a process was in directory
/foo/bar and /foo got lazily unmounted, the old result was ``foobar'' (note the
missing slash), while the new result with this patch is ``foo/bar''.
Second, it isn't always possible to tell from the __d_path() result whether the
specified root and rootmnt (i.e., the chroot) was reached. We need an
unambiguous result for AppArmor at least though, so we make sure that paths
will only start with a slash if the path leads all the way up to the root.
We also add a @fail_deleted argument, which allows to get rid of some of the
mess in sys_getcwd().
This patch leaves getcwd() and d_path() as they were before for everything
except for bind-mounted directories; for them, it reports ``/foo/bar'' instead
of ``foobar'' in the example described above.
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
Signed-off-by: John Johansen <jjohansen@suse.de>
Acked-by: Alan Cox <alan@lxorguk.ukuu.org.uk>
---
fs/dcache.c | 126 ++++++++++++++++++++++++++-----------------------
fs/seq_file.c | 4 -
include/linux/dcache.h | 5 +
3 files changed, 74 insertions(+), 61 deletions(-)
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1768,44 +1768,46 @@ static int prepend_name(char **buffer, i
* @root: root vfsmnt/dentry (may be modified by this function)
* @buffer: buffer to return value in
* @buflen: buffer length
+ * @flags: flags controling behavior of d_path
*
- * Convert a dentry into an ASCII path name. If the entry has been deleted
- * the string " (deleted)" is appended. Note that this is ambiguous.
- *
- * Returns the buffer or an error code if the path was too long.
- *
- * "buflen" should be positive. Caller holds the dcache_lock.
+ * Convert a dentry into an ASCII path name. If the entry has been deleted,
+ * then if @flags has D_PATH_FAIL_DELETED set, ERR_PTR(-ENOENT) is returned.
+ * Otherwise, the string " (deleted)" is appended. Note that this is ambiguous.
*
* If path is not reachable from the supplied root, then the value of
- * root is changed (without modifying refcounts).
+ * root is changed (without modifying refcounts). The path returned in this
+ * case will be relative (i.e., it will not start with a slash).
+ *
+ * Returns the buffer or an error code if the path was too long.
*/
char *__d_path(const struct path *path, struct path *root,
- char *buffer, int buflen)
+ char *buffer, int buflen, int flags)
{
struct dentry *dentry = path->dentry;
struct vfsmount *vfsmnt = path->mnt;
- char *end = buffer + buflen;
- char *retval;
+ const unsigned char *name;
+ int namelen;
+
+ buffer += buflen;
+ prepend(&buffer, &buflen, "\0", 1);
spin_lock(&vfsmount_lock);
- prepend(&end, &buflen, "\0", 1);
- if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
- (prepend(&end, &buflen, " (deleted)", 10) != 0))
+ spin_lock(&dcache_lock);
+ if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
+ if (flags & D_PATH_FAIL_DELETED) {
+ buffer = ERR_PTR(-ENOENT);
+ goto out;
+ }
+ if (prepend(&buffer, &buflen, " (deleted)", 10) != 0)
goto Elong;
-
+ }
if (buflen < 1)
goto Elong;
- /* Get '/' right */
- retval = end-1;
- *retval = '/';
- for (;;) {
+ while (dentry != root->dentry || vfsmnt != root->mnt) {
struct dentry * parent;
- if (dentry == root->dentry && vfsmnt == root->mnt)
- break;
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
- /* Global root? */
if (vfsmnt->mnt_parent == vfsmnt) {
goto global_root;
}
@@ -1815,27 +1817,51 @@ char *__d_path(const struct path *path,
}
parent = dentry->d_parent;
prefetch(parent);
- if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
- (prepend(&end, &buflen, "/", 1) != 0))
+ if ((prepend_name(&buffer, &buflen, &dentry->d_name) != 0) ||
+ (prepend(&buffer, &buflen, "/", 1) != 0))
goto Elong;
- retval = end;
dentry = parent;
}
+ /* Get '/' right. */
+ if (*buffer != '/' && prepend(&buffer, &buflen, "/", 1))
+ goto Elong;
out:
+ spin_unlock(&dcache_lock);
spin_unlock(&vfsmount_lock);
- return retval;
+ return buffer;
global_root:
- retval += 1; /* hit the slash */
- if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
+ /*
+ * We went past the (vfsmount, dentry) we were looking for and have
+ * either hit a root dentry, a lazily unmounted dentry, an
+ * unconnected dentry, or the file is on a pseudo filesystem.
+ */
+ namelen = dentry->d_name.len;
+ name = dentry->d_name.name;
+
+ /*
+ * If this is a root dentry, then overwrite the slash. This
+ * will also DTRT with pseudo filesystems which have root
+ * dentries named "foo:".
+ */
+ if (IS_ROOT(dentry) && *buffer == '/') {
+ buffer++;
+ buflen++;
+ }
+ if ((flags & D_PATH_DISCONNECT) && *name == '/') {
+ /* Make sure we won't return a pathname starting with '/' */
+ name++;
+ namelen--;
+ }
+ if (prepend(&buffer, &buflen, name, namelen))
goto Elong;
root->mnt = vfsmnt;
root->dentry = dentry;
goto out;
Elong:
- retval = ERR_PTR(-ENAMETOOLONG);
+ buffer = ERR_PTR(-ENAMETOOLONG);
goto out;
}
@@ -1872,10 +1898,8 @@ char *d_path(const struct path *path, ch
root = current->fs->root;
path_get(&root);
read_unlock(&current->fs->lock);
- spin_lock(&dcache_lock);
tmp = root;
- res = __d_path(path, &tmp, buf, buflen);
- spin_unlock(&dcache_lock);
+ res = __d_path(path, &tmp, buf, buflen, 0);
path_put(&root);
return res;
}
@@ -1958,9 +1982,9 @@ Elong:
*/
asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
{
- int error;
+ int error, len;
struct path pwd, root;
- char *page = (char *) __get_free_page(GFP_USER);
+ char *page = (char *) __get_free_page(GFP_USER), *cwd;
if (!page)
return -ENOMEM;
@@ -1972,30 +1996,18 @@ asmlinkage long sys_getcwd(char __user *
path_get(&root);
read_unlock(&current->fs->lock);
- error = -ENOENT;
- /* Has the current directory has been unlinked? */
- spin_lock(&dcache_lock);
- if (IS_ROOT(pwd.dentry) || !d_unhashed(pwd.dentry)) {
- unsigned long len;
- struct path tmp = root;
- char * cwd;
-
- cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
- spin_unlock(&dcache_lock);
-
- error = PTR_ERR(cwd);
- if (IS_ERR(cwd))
- goto out;
-
- error = -ERANGE;
- len = PAGE_SIZE + page - cwd;
- if (len <= size) {
- error = len;
- if (copy_to_user(buf, cwd, len))
- error = -EFAULT;
- }
- } else
- spin_unlock(&dcache_lock);
+ cwd = __d_path(&pwd, &root, page, PAGE_SIZE, 0);
+ error = PTR_ERR(cwd);
+ if (IS_ERR(cwd))
+ goto out;
+
+ error = -ERANGE;
+ len = PAGE_SIZE + page - cwd;
+ if (len <= size) {
+ error = len;
+ if (copy_to_user(buf, cwd, len))
+ error = -EFAULT;
+ }
out:
path_put(&pwd);
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -405,9 +405,7 @@ int seq_path_root(struct seq_file *m, st
char *s = m->buf + m->count;
char *p;
- spin_lock(&dcache_lock);
- p = __d_path(path, root, s, m->size - m->count);
- spin_unlock(&dcache_lock);
+ p = __d_path(path, root, s, m->size - m->count, 0);
err = PTR_ERR(p);
if (!IS_ERR(p)) {
s = mangle_path(s, p, esc);
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -297,9 +297,12 @@ extern int d_validate(struct dentry *, s
/*
* helper function for dentry_operations.d_dname() members
*/
+#define D_PATH_FAIL_DELETED 1
+#define D_PATH_DISCONNECT 2
extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...);
-extern char *__d_path(const struct path *path, struct path *root, char *, int);
+extern char *__d_path(const struct path *path, struct path *root, char *, int,
+ int);
extern char *d_path(const struct path *, char *, int);
extern char *dentry_path(struct dentry *, char *, int);