]>
Commit | Line | Data |
---|---|---|
8f69975d BS |
1 | From: Andreas Gruenbacher <agruen@suse.de> |
2 | Subject: Fix __d_path() for lazy unmounts and make it unambiguous | |
3 | ||
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''. | |
10 | ||
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. | |
15 | ||
16 | We also add a @fail_deleted argument, which allows to get rid of some of the | |
17 | mess in sys_getcwd(). | |
18 | ||
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. | |
22 | ||
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> | |
26 | ||
27 | [ Moved dcache_lock outside vfsmount_lock to fix lock order (bnc#490902) ] | |
28 | Signed-off-by: Nick Piggin <npiggin@suse.de> | |
29 | ||
30 | --- | |
31 | fs/dcache.c | 126 +++++++++++++++++++++++++++---------------------- | |
32 | fs/seq_file.c | 4 - | |
33 | include/linux/dcache.h | 5 + | |
34 | 3 files changed, 75 insertions(+), 60 deletions(-) | |
35 | ||
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 | |
45 | * | |
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. | |
48 | - * | |
49 | - * Returns the buffer or an error code if the path was too long. | |
50 | - * | |
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. | |
55 | * | |
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). | |
60 | + * | |
61 | + * Returns the buffer or an error code if the path was too long. | |
62 | */ | |
63 | char *__d_path(const struct path *path, struct path *root, | |
64 | - char *buffer, int buflen) | |
65 | + char *buffer, int buflen, int flags) | |
66 | { | |
67 | struct dentry *dentry = path->dentry; | |
68 | struct vfsmount *vfsmnt = path->mnt; | |
69 | - char *end = buffer + buflen; | |
70 | - char *retval; | |
71 | + const unsigned char *name; | |
72 | + int namelen; | |
73 | + | |
74 | + buffer += buflen; | |
75 | + prepend(&buffer, &buflen, "\0", 1); | |
76 | ||
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); | |
85 | + goto out; | |
86 | + } | |
87 | + if (prepend(&buffer, &buflen, " (deleted)", 10) != 0) | |
88 | goto Elong; | |
89 | - | |
90 | + } | |
91 | if (buflen < 1) | |
92 | goto Elong; | |
93 | - /* Get '/' right */ | |
94 | - retval = end-1; | |
95 | - *retval = '/'; | |
96 | ||
97 | - for (;;) { | |
98 | + while (dentry != root->dentry || vfsmnt != root->mnt) { | |
99 | struct dentry * parent; | |
100 | ||
101 | - if (dentry == root->dentry && vfsmnt == root->mnt) | |
102 | - break; | |
103 | if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { | |
104 | - /* Global root? */ | |
105 | if (vfsmnt->mnt_parent == vfsmnt) { | |
106 | goto global_root; | |
107 | } | |
108 | @@ -1945,27 +1947,51 @@ char *__d_path(const struct path *path, | |
109 | } | |
110 | parent = dentry->d_parent; | |
111 | prefetch(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)) | |
116 | goto Elong; | |
117 | - retval = end; | |
118 | dentry = parent; | |
119 | } | |
120 | + /* Get '/' right. */ | |
121 | + if (*buffer != '/' && prepend(&buffer, &buflen, "/", 1)) | |
122 | + goto Elong; | |
123 | ||
124 | out: | |
125 | spin_unlock(&vfsmount_lock); | |
126 | - return retval; | |
127 | + spin_unlock(&dcache_lock); | |
128 | + return buffer; | |
129 | ||
130 | global_root: | |
131 | - retval += 1; /* hit the slash */ | |
132 | - if (prepend_name(&retval, &buflen, &dentry->d_name) != 0) | |
133 | + /* | |
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. | |
137 | + */ | |
138 | + namelen = dentry->d_name.len; | |
139 | + name = dentry->d_name.name; | |
140 | + | |
141 | + /* | |
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:". | |
145 | + */ | |
146 | + if (IS_ROOT(dentry) && *buffer == '/') { | |
147 | + buffer++; | |
148 | + buflen++; | |
149 | + } | |
150 | + if ((flags & D_PATH_DISCONNECT) && *name == '/') { | |
151 | + /* Make sure we won't return a pathname starting with '/' */ | |
152 | + name++; | |
153 | + namelen--; | |
154 | + } | |
155 | + if (prepend(&buffer, &buflen, name, namelen)) | |
156 | goto Elong; | |
157 | root->mnt = vfsmnt; | |
158 | root->dentry = dentry; | |
159 | goto out; | |
160 | ||
161 | Elong: | |
162 | - retval = ERR_PTR(-ENAMETOOLONG); | |
163 | + buffer = ERR_PTR(-ENAMETOOLONG); | |
164 | goto out; | |
165 | } | |
166 | ||
167 | @@ -2002,10 +2028,8 @@ char *d_path(const struct path *path, ch | |
168 | root = current->fs->root; | |
169 | path_get(&root); | |
170 | read_unlock(¤t->fs->lock); | |
171 | - spin_lock(&dcache_lock); | |
172 | tmp = root; | |
173 | - res = __d_path(path, &tmp, buf, buflen); | |
174 | - spin_unlock(&dcache_lock); | |
175 | + res = __d_path(path, &tmp, buf, buflen, 0); | |
176 | path_put(&root); | |
177 | return res; | |
178 | } | |
179 | @@ -2088,9 +2112,9 @@ Elong: | |
180 | */ | |
181 | SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) | |
182 | { | |
183 | - int error; | |
184 | - struct path pwd, root; | |
185 | - char *page = (char *) __get_free_page(GFP_USER); | |
186 | + int error, len; | |
187 | + struct path pwd, root, tmp; | |
188 | + char *page = (char *) __get_free_page(GFP_USER), *cwd; | |
189 | ||
190 | if (!page) | |
191 | return -ENOMEM; | |
192 | @@ -2102,30 +2126,20 @@ SYSCALL_DEFINE2(getcwd, char __user *, b | |
193 | path_get(&root); | |
194 | read_unlock(¤t->fs->lock); | |
195 | ||
196 | - error = -ENOENT; | |
197 | - /* Has the current directory has been unlinked? */ | |
198 | - spin_lock(&dcache_lock); | |
199 | - if (IS_ROOT(pwd.dentry) || !d_unhashed(pwd.dentry)) { | |
200 | - unsigned long len; | |
201 | - struct path tmp = root; | |
202 | - char * cwd; | |
203 | - | |
204 | - cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE); | |
205 | - spin_unlock(&dcache_lock); | |
206 | - | |
207 | + tmp = root; | |
208 | + cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE, D_PATH_FAIL_DELETED); | |
209 | + if (IS_ERR(cwd)) { | |
210 | error = PTR_ERR(cwd); | |
211 | - if (IS_ERR(cwd)) | |
212 | - goto out; | |
213 | + goto out; | |
214 | + } | |
215 | ||
216 | - error = -ERANGE; | |
217 | - len = PAGE_SIZE + page - cwd; | |
218 | - if (len <= size) { | |
219 | - error = len; | |
220 | - if (copy_to_user(buf, cwd, len)) | |
221 | - error = -EFAULT; | |
222 | - } | |
223 | - } else | |
224 | - spin_unlock(&dcache_lock); | |
225 | + error = -ERANGE; | |
226 | + len = PAGE_SIZE + page - cwd; | |
227 | + if (len <= size) { | |
228 | + error = len; | |
229 | + if (copy_to_user(buf, cwd, len)) | |
230 | + error = -EFAULT; | |
231 | + } | |
232 | ||
233 | out: | |
234 | path_put(&pwd); | |
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; | |
241 | char *p; | |
242 | ||
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); | |
247 | err = PTR_ERR(p); | |
248 | if (!IS_ERR(p)) { | |
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 | |
255 | /* | |
256 | * helper function for dentry_operations.d_dname() members | |
257 | */ | |
258 | +#define D_PATH_FAIL_DELETED 1 | |
259 | +#define D_PATH_DISCONNECT 2 | |
260 | extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...); | |
261 | ||
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, | |
264 | + int); | |
265 | extern char *d_path(const struct path *, char *, int); | |
266 | extern char *dentry_path(struct dentry *, char *, int); | |
267 |