1 From: Andreas Gruenbacher <agruen@suse.de>
2 Subject: Fix __d_path() for lazy unmounts and make it unambiguous
4 First, when __d_path() hits a lazily unmounted mount point, it tries to prepend
5 the name of the lazily unmounted dentry to the path name. It gets this wrong,
6 and also overwrites the slash that separates the name from the following
7 pathname component. This patch fixes that; if a process was in directory
8 /foo/bar and /foo got lazily unmounted, the old result was ``foobar'' (note the
9 missing slash), while the new result with this patch is ``foo/bar''.
11 Second, it isn't always possible to tell from the __d_path() result whether the
12 specified root and rootmnt (i.e., the chroot) was reached. We need an
13 unambiguous result for AppArmor at least though, so we make sure that paths
14 will only start with a slash if the path leads all the way up to the root.
16 We also add a @fail_deleted argument, which allows to get rid of some of the
19 This patch leaves getcwd() and d_path() as they were before for everything
20 except for bind-mounted directories; for them, it reports ``/foo/bar'' instead
21 of ``foobar'' in the example described above.
23 Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
24 Signed-off-by: John Johansen <jjohansen@suse.de>
25 Acked-by: Alan Cox <alan@lxorguk.ukuu.org.uk>
27 [ Moved dcache_lock outside vfsmount_lock to fix lock order (bnc#490902) ]
28 Signed-off-by: Nick Piggin <npiggin@suse.de>
31 fs/dcache.c | 126 +++++++++++++++++++++++++++----------------------
33 include/linux/dcache.h | 5 +
34 3 files changed, 75 insertions(+), 60 deletions(-)
36 Index: linux-2.6.27/fs/dcache.c
37 ===================================================================
38 --- linux-2.6.27.orig/fs/dcache.c
39 +++ linux-2.6.27/fs/dcache.c
40 @@ -1898,44 +1898,46 @@ static int prepend_name(char **buffer, i
41 * @root: root vfsmnt/dentry (may be modified by this function)
42 * @buffer: buffer to return value in
43 * @buflen: buffer length
44 + * @flags: flags controling behavior of d_path
46 - * Convert a dentry into an ASCII path name. If the entry has been deleted
47 - * the string " (deleted)" is appended. Note that this is ambiguous.
49 - * Returns the buffer or an error code if the path was too long.
51 - * "buflen" should be positive. Caller holds the dcache_lock.
52 + * Convert a dentry into an ASCII path name. If the entry has been deleted,
53 + * then if @flags has D_PATH_FAIL_DELETED set, ERR_PTR(-ENOENT) is returned.
54 + * Otherwise, the string " (deleted)" is appended. Note that this is ambiguous.
56 * If path is not reachable from the supplied root, then the value of
57 - * root is changed (without modifying refcounts).
58 + * root is changed (without modifying refcounts). The path returned in this
59 + * case will be relative (i.e., it will not start with a slash).
61 + * Returns the buffer or an error code if the path was too long.
63 char *__d_path(const struct path *path, struct path *root,
64 - char *buffer, int buflen)
65 + char *buffer, int buflen, int flags)
67 struct dentry *dentry = path->dentry;
68 struct vfsmount *vfsmnt = path->mnt;
69 - char *end = buffer + buflen;
71 + const unsigned char *name;
75 + prepend(&buffer, &buflen, "\0", 1);
77 + spin_lock(&dcache_lock);
78 spin_lock(&vfsmount_lock);
79 - prepend(&end, &buflen, "\0", 1);
80 - if (!IS_ROOT(dentry) && d_unhashed(dentry) &&
81 - (prepend(&end, &buflen, " (deleted)", 10) != 0))
82 + if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
83 + if (flags & D_PATH_FAIL_DELETED) {
84 + buffer = ERR_PTR(-ENOENT);
87 + if (prepend(&buffer, &buflen, " (deleted)", 10) != 0)
98 + while (dentry != root->dentry || vfsmnt != root->mnt) {
99 struct dentry * parent;
101 - if (dentry == root->dentry && vfsmnt == root->mnt)
103 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
105 if (vfsmnt->mnt_parent == vfsmnt) {
108 @@ -1945,27 +1947,51 @@ char *__d_path(const struct path *path,
110 parent = dentry->d_parent;
112 - if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
113 - (prepend(&end, &buflen, "/", 1) != 0))
114 + if ((prepend_name(&buffer, &buflen, &dentry->d_name) != 0) ||
115 + (prepend(&buffer, &buflen, "/", 1) != 0))
120 + /* Get '/' right. */
121 + if (*buffer != '/' && prepend(&buffer, &buflen, "/", 1))
125 spin_unlock(&vfsmount_lock);
127 + spin_unlock(&dcache_lock);
131 - retval += 1; /* hit the slash */
132 - if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
134 + * We went past the (vfsmount, dentry) we were looking for and have
135 + * either hit a root dentry, a lazily unmounted dentry, an
136 + * unconnected dentry, or the file is on a pseudo filesystem.
138 + namelen = dentry->d_name.len;
139 + name = dentry->d_name.name;
142 + * If this is a root dentry, then overwrite the slash. This
143 + * will also DTRT with pseudo filesystems which have root
144 + * dentries named "foo:".
146 + if (IS_ROOT(dentry) && *buffer == '/') {
150 + if ((flags & D_PATH_DISCONNECT) && *name == '/') {
151 + /* Make sure we won't return a pathname starting with '/' */
155 + if (prepend(&buffer, &buflen, name, namelen))
158 root->dentry = dentry;
162 - retval = ERR_PTR(-ENAMETOOLONG);
163 + buffer = ERR_PTR(-ENAMETOOLONG);
167 @@ -2002,10 +2028,8 @@ char *d_path(const struct path *path, ch
168 root = current->fs->root;
170 read_unlock(¤t->fs->lock);
171 - spin_lock(&dcache_lock);
173 - res = __d_path(path, &tmp, buf, buflen);
174 - spin_unlock(&dcache_lock);
175 + res = __d_path(path, &tmp, buf, buflen, 0);
179 @@ -2088,9 +2112,9 @@ Elong:
181 SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
184 - struct path pwd, root;
185 - char *page = (char *) __get_free_page(GFP_USER);
187 + struct path pwd, root, tmp;
188 + char *page = (char *) __get_free_page(GFP_USER), *cwd;
192 @@ -2102,30 +2126,20 @@ SYSCALL_DEFINE2(getcwd, char __user *, b
194 read_unlock(¤t->fs->lock);
197 - /* Has the current directory has been unlinked? */
198 - spin_lock(&dcache_lock);
199 - if (IS_ROOT(pwd.dentry) || !d_unhashed(pwd.dentry)) {
201 - struct path tmp = root;
204 - cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
205 - spin_unlock(&dcache_lock);
208 + cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE, D_PATH_FAIL_DELETED);
210 error = PTR_ERR(cwd);
217 - len = PAGE_SIZE + page - cwd;
220 - if (copy_to_user(buf, cwd, len))
224 - spin_unlock(&dcache_lock);
226 + len = PAGE_SIZE + page - cwd;
229 + if (copy_to_user(buf, cwd, len))
235 Index: linux-2.6.27/fs/seq_file.c
236 ===================================================================
237 --- linux-2.6.27.orig/fs/seq_file.c
238 +++ linux-2.6.27/fs/seq_file.c
239 @@ -441,9 +441,7 @@ int seq_path_root(struct seq_file *m, st
240 char *s = m->buf + m->count;
243 - spin_lock(&dcache_lock);
244 - p = __d_path(path, root, s, m->size - m->count);
245 - spin_unlock(&dcache_lock);
246 + p = __d_path(path, root, s, m->size - m->count, 0);
249 s = mangle_path(s, p, esc);
250 Index: linux-2.6.27/include/linux/dcache.h
251 ===================================================================
252 --- linux-2.6.27.orig/include/linux/dcache.h
253 +++ linux-2.6.27/include/linux/dcache.h
254 @@ -299,9 +299,12 @@ extern int d_validate(struct dentry *, s
256 * helper function for dentry_operations.d_dname() members
258 +#define D_PATH_FAIL_DELETED 1
259 +#define D_PATH_DISCONNECT 2
260 extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...);
262 -extern char *__d_path(const struct path *path, struct path *root, char *, int);
263 +extern char *__d_path(const struct path *path, struct path *root, char *, int,
265 extern char *d_path(const struct path *, char *, int);
266 extern char *dentry_path(struct dentry *, char *, int);