[AppArmor 29/47] 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 <[EMAIL PROTECTED]> Signed-off-by: John Johansen <[EMAIL PROTECTED]> Acked-by: Alan Cox <[EMAIL PROTECTED]> --- fs/dcache.c | 166 ++-- 1 file changed, 96 insertions(+), 70 deletions(-) --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1748,51 +1748,49 @@ shouldnt_be_hashed: } /** - * d_path - return the path of a dentry - * @dentry: dentry to report - * @vfsmnt: vfsmnt to which the dentry belongs - * @root: root dentry - * @rootmnt: vfsmnt to which the root dentry belongs + * __d_path - return the path of a dentry + * @path: path to report + * @root: root path * @buffer: buffer to return value in * @buflen: buffer length + * @fail_deleted: what to return for deleted files * - * Convert a dentry into an ASCII path name. If the entry has been deleted + * If @path is not connected to @root, the path returned will be relative + * (i.e., it will not start with a slash). * 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. + * Returns the buffer or an error code. */ -static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt, - struct path *root, char *buffer, int buflen) +static char *__d_path(struct path *path, struct path *root, + char *buffer, int buflen, int fail_deleted) { - char * end = buffer+buflen; - char * retval; - int namelen; + int namelen, is_slash; + struct dentry *dentry = path->dentry; + struct vfsmount *vfsmnt = path->mnt; + + if (buflen < 2) + return ERR_PTR(-ENAMETOOLONG); + buffer += --buflen; + *buffer = '\0'; - *--end = '\0'; - buflen--; + spin_lock(_lock); if (!IS_ROOT(dentry) && d_unhashed(dentry)) { - buflen -= 10; - end -= 10; - if (buflen < 0) + if (fail_deleted) { + buffer = ERR_PTR(-ENOENT); + goto out; + } + if (buflen < 10) goto Elong; - memcpy(end, " (deleted)", 10); + buflen -= 10; + buffer -= 10; + memcpy(buffer, " (deleted)", 10); } - - 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? */ spin_lock(_lock); if (vfsmnt->mnt_parent == vfsmnt) { spin_unlock(_lock); @@ -1806,28 +1804,67 @@ static char *__d_path(struct dentry *den parent = dentry->d_parent; prefetch(parent); namelen = dentry->d_name.len; - buflen -= namelen + 1; - if (buflen < 0) + if (buflen < namelen + 1) goto Elong; - end -= namelen; - memcpy(end, dentry->d_name.name, namelen); - *--end = '/'; - retval = end; + buflen -= namelen + 1; + buffer -= namelen; + memcpy(buffer, dentry->d_name.name, namelen); + *--buffer = '/'; dentry = parent; } + /* Get '/' right. */ + if (*buffer != '/') + *--buffer = '/'; - return retval; +out: + spin_unlock(_lock); + return buffer; global_root: + /* +*
[AppArmor 29/47] 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 [EMAIL PROTECTED] Signed-off-by: John Johansen [EMAIL PROTECTED] Acked-by: Alan Cox [EMAIL PROTECTED] --- fs/dcache.c | 166 ++-- 1 file changed, 96 insertions(+), 70 deletions(-) --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1748,51 +1748,49 @@ shouldnt_be_hashed: } /** - * d_path - return the path of a dentry - * @dentry: dentry to report - * @vfsmnt: vfsmnt to which the dentry belongs - * @root: root dentry - * @rootmnt: vfsmnt to which the root dentry belongs + * __d_path - return the path of a dentry + * @path: path to report + * @root: root path * @buffer: buffer to return value in * @buflen: buffer length + * @fail_deleted: what to return for deleted files * - * Convert a dentry into an ASCII path name. If the entry has been deleted + * If @path is not connected to @root, the path returned will be relative + * (i.e., it will not start with a slash). * 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. + * Returns the buffer or an error code. */ -static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt, - struct path *root, char *buffer, int buflen) +static char *__d_path(struct path *path, struct path *root, + char *buffer, int buflen, int fail_deleted) { - char * end = buffer+buflen; - char * retval; - int namelen; + int namelen, is_slash; + struct dentry *dentry = path-dentry; + struct vfsmount *vfsmnt = path-mnt; + + if (buflen 2) + return ERR_PTR(-ENAMETOOLONG); + buffer += --buflen; + *buffer = '\0'; - *--end = '\0'; - buflen--; + spin_lock(dcache_lock); if (!IS_ROOT(dentry) d_unhashed(dentry)) { - buflen -= 10; - end -= 10; - if (buflen 0) + if (fail_deleted) { + buffer = ERR_PTR(-ENOENT); + goto out; + } + if (buflen 10) goto Elong; - memcpy(end, (deleted), 10); + buflen -= 10; + buffer -= 10; + memcpy(buffer, (deleted), 10); } - - 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? */ spin_lock(vfsmount_lock); if (vfsmnt-mnt_parent == vfsmnt) { spin_unlock(vfsmount_lock); @@ -1806,28 +1804,67 @@ static char *__d_path(struct dentry *den parent = dentry-d_parent; prefetch(parent); namelen = dentry-d_name.len; - buflen -= namelen + 1; - if (buflen 0) + if (buflen namelen + 1) goto Elong; - end -= namelen; - memcpy(end, dentry-d_name.name, namelen); - *--end = '/'; - retval = end; + buflen -= namelen + 1; + buffer -= namelen; + memcpy(buffer, dentry-d_name.name, namelen); + *--buffer = '/'; dentry = parent; } + /* Get '/' right. */ + if (*buffer != '/') + *--buffer = '/'; - return retval; +out: + spin_unlock(dcache_lock); + return buffer; global_root: + /* +* We went