aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Gruenbacher <agruen@suse.de>2007-02-12 03:51:47 -0500
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-02-12 12:48:27 -0500
commiteb3dfb0cb1f4a44e2d0553f89514ce9f2a9fcaf1 (patch)
tree9b35fa3ac8cb3d6fc4eb74d83d15f0f929f54631
parent5c3bd438ccb94f5d5bf5d8711330e038dc8dd21b (diff)
[PATCH] Fix d_path for lazy unmounts
Here is a bugfix to d_path. 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 is demonstrated by the attached test case, which prints "getcwd returned d_path-bugsubdir" with the bug. The correct result would be "getcwd returned d_path-bug/subdir". It could be argued that the name of the root dentry should not be part of the result of d_path in the first place. On the other hand, what the unconnected namespace was once reachable as may provide some useful hints to users, and so that seems okay. 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: lazy unmounts of bind mounts will produce a path that does start with a non-slash so we can tell from that, but other lazy unmounts will produce a path that starts with a slash, just like "ordinary" paths. The attached patch cleans up __d_path() to fix the bug with overlapping pathname components. It also adds a @fail_deleted argument, which allows to get rid of some of the mess in sys_getcwd(). Grabbing the dcache_lock can then also be moved into __d_path(). The patch also makes sure that paths will only start with a slash for paths which are connected to the root and rootmnt. The @fail_deleted argument could be added to d_path() as well: this would allow callers to recognize deleted files, without having to resort to the ambiguous check for the " (deleted)" string at the end of the pathnames. This is not currently done, but it might be worthwhile. Signed-off-by: Andreas Gruenbacher <agruen@suse.de> Cc: Neil Brown <neilb@suse.de> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Christoph Hellwig <hch@lst.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--fs/dcache.c150
1 files changed, 80 insertions, 70 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index d68631f18df1..b5f613932912 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1739,45 +1739,41 @@ shouldnt_be_hashed:
1739 * @rootmnt: vfsmnt to which the root dentry belongs 1739 * @rootmnt: vfsmnt to which the root dentry belongs
1740 * @buffer: buffer to return value in 1740 * @buffer: buffer to return value in
1741 * @buflen: buffer length 1741 * @buflen: buffer length
1742 * @fail_deleted: what to return for deleted files
1742 * 1743 *
1743 * Convert a dentry into an ASCII path name. If the entry has been deleted 1744 * Convert a dentry into an ASCII path name. If the entry has been deleted,
1744 * the string " (deleted)" is appended. Note that this is ambiguous. 1745 * then if @fail_deleted is true, ERR_PTR(-ENOENT) is returned. Otherwise,
1746 * the the string " (deleted)" is appended. Note that this is ambiguous.
1745 * 1747 *
1746 * Returns the buffer or an error code if the path was too long. 1748 * Returns the buffer or an error code.
1747 *
1748 * "buflen" should be positive. Caller holds the dcache_lock.
1749 */ 1749 */
1750static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt, 1750static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
1751 struct dentry *root, struct vfsmount *rootmnt, 1751 struct dentry *root, struct vfsmount *rootmnt,
1752 char *buffer, int buflen) 1752 char *buffer, int buflen, int fail_deleted)
1753{ 1753{
1754 char * end = buffer+buflen; 1754 int namelen, is_slash;
1755 char * retval; 1755
1756 int namelen; 1756 if (buflen < 2)
1757 return ERR_PTR(-ENAMETOOLONG);
1758 buffer += --buflen;
1759 *buffer = '\0';
1757 1760
1758 *--end = '\0'; 1761 spin_lock(&dcache_lock);
1759 buflen--;
1760 if (!IS_ROOT(dentry) && d_unhashed(dentry)) { 1762 if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
1761 buflen -= 10; 1763 if (fail_deleted) {
1762 end -= 10; 1764 buffer = ERR_PTR(-ENOENT);
1763 if (buflen < 0) 1765 goto out;
1766 }
1767 if (buflen < 10)
1764 goto Elong; 1768 goto Elong;
1765 memcpy(end, " (deleted)", 10); 1769 buflen -= 10;
1770 buffer -= 10;
1771 memcpy(buffer, " (deleted)", 10);
1766 } 1772 }
1767 1773 while (dentry != root || vfsmnt != rootmnt) {
1768 if (buflen < 1)
1769 goto Elong;
1770 /* Get '/' right */
1771 retval = end-1;
1772 *retval = '/';
1773
1774 for (;;) {
1775 struct dentry * parent; 1774 struct dentry * parent;
1776 1775
1777 if (dentry == root && vfsmnt == rootmnt)
1778 break;
1779 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { 1776 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
1780 /* Global root? */
1781 spin_lock(&vfsmount_lock); 1777 spin_lock(&vfsmount_lock);
1782 if (vfsmnt->mnt_parent == vfsmnt) { 1778 if (vfsmnt->mnt_parent == vfsmnt) {
1783 spin_unlock(&vfsmount_lock); 1779 spin_unlock(&vfsmount_lock);
@@ -1791,33 +1787,60 @@ static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
1791 parent = dentry->d_parent; 1787 parent = dentry->d_parent;
1792 prefetch(parent); 1788 prefetch(parent);
1793 namelen = dentry->d_name.len; 1789 namelen = dentry->d_name.len;
1794 buflen -= namelen + 1; 1790 if (buflen <= namelen)
1795 if (buflen < 0)
1796 goto Elong; 1791 goto Elong;
1797 end -= namelen; 1792 buflen -= namelen + 1;
1798 memcpy(end, dentry->d_name.name, namelen); 1793 buffer -= namelen;
1799 *--end = '/'; 1794 memcpy(buffer, dentry->d_name.name, namelen);
1800 retval = end; 1795 *--buffer = '/';
1801 dentry = parent; 1796 dentry = parent;
1802 } 1797 }
1798 /* Get '/' right */
1799 if (*buffer != '/')
1800 *--buffer = '/';
1803 1801
1804 return retval; 1802out:
1803 spin_unlock(&dcache_lock);
1804 return buffer;
1805 1805
1806global_root: 1806global_root:
1807 /*
1808 * We went past the (vfsmount, dentry) we were looking for and have
1809 * either hit a root dentry, a lazily unmounted dentry, an
1810 * unconnected dentry, or the file is on a pseudo filesystem.
1811 */
1807 namelen = dentry->d_name.len; 1812 namelen = dentry->d_name.len;
1808 buflen -= namelen; 1813 is_slash = (namelen == 1 && *dentry->d_name.name == '/');
1809 if (buflen < 0) 1814 if (is_slash || (dentry->d_sb->s_flags & MS_NOUSER)) {
1815 /*
1816 * Make sure we won't return a pathname starting with '/'.
1817 *
1818 * Historically, we also glue together the root dentry and
1819 * remaining name for pseudo filesystems like pipefs, which
1820 * have the MS_NOUSER flag set. This results in pathnames
1821 * like "pipe:[439336]".
1822 */
1823 if (*buffer == '/') {
1824 buffer++;
1825 buflen++;
1826 }
1827 if (is_slash)
1828 goto out;
1829 }
1830 if (buflen < namelen)
1810 goto Elong; 1831 goto Elong;
1811 retval -= namelen-1; /* hit the slash */ 1832 buffer -= namelen;
1812 memcpy(retval, dentry->d_name.name, namelen); 1833 memcpy(buffer, dentry->d_name.name, namelen);
1813 return retval; 1834 goto out;
1835
1814Elong: 1836Elong:
1815 return ERR_PTR(-ENAMETOOLONG); 1837 buffer = ERR_PTR(-ENAMETOOLONG);
1838 goto out;
1816} 1839}
1817 1840
1818/* write full pathname into buffer and return start of pathname */ 1841/* write full pathname into buffer and return start of pathname */
1819char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt, 1842char *d_path(struct dentry *dentry, struct vfsmount *vfsmnt, char *buf,
1820 char *buf, int buflen) 1843 int buflen)
1821{ 1844{
1822 char *res; 1845 char *res;
1823 struct vfsmount *rootmnt; 1846 struct vfsmount *rootmnt;
@@ -1827,9 +1850,7 @@ char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
1827 rootmnt = mntget(current->fs->rootmnt); 1850 rootmnt = mntget(current->fs->rootmnt);
1828 root = dget(current->fs->root); 1851 root = dget(current->fs->root);
1829 read_unlock(&current->fs->lock); 1852 read_unlock(&current->fs->lock);
1830 spin_lock(&dcache_lock); 1853 res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen, 0);
1831 res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen);
1832 spin_unlock(&dcache_lock);
1833 dput(root); 1854 dput(root);
1834 mntput(rootmnt); 1855 mntput(rootmnt);
1835 return res; 1856 return res;
@@ -1855,10 +1876,10 @@ char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
1855 */ 1876 */
1856asmlinkage long sys_getcwd(char __user *buf, unsigned long size) 1877asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
1857{ 1878{
1858 int error; 1879 int error, len;
1859 struct vfsmount *pwdmnt, *rootmnt; 1880 struct vfsmount *pwdmnt, *rootmnt;
1860 struct dentry *pwd, *root; 1881 struct dentry *pwd, *root;
1861 char *page = (char *) __get_free_page(GFP_USER); 1882 char *page = (char *) __get_free_page(GFP_USER), *cwd;
1862 1883
1863 if (!page) 1884 if (!page)
1864 return -ENOMEM; 1885 return -ENOMEM;
@@ -1870,29 +1891,18 @@ asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
1870 root = dget(current->fs->root); 1891 root = dget(current->fs->root);
1871 read_unlock(&current->fs->lock); 1892 read_unlock(&current->fs->lock);
1872 1893
1873 error = -ENOENT; 1894 cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE, 1);
1874 /* Has the current directory has been unlinked? */ 1895 error = PTR_ERR(cwd);
1875 spin_lock(&dcache_lock); 1896 if (IS_ERR(cwd))
1876 if (pwd->d_parent == pwd || !d_unhashed(pwd)) { 1897 goto out;
1877 unsigned long len;
1878 char * cwd;
1879
1880 cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE);
1881 spin_unlock(&dcache_lock);
1882
1883 error = PTR_ERR(cwd);
1884 if (IS_ERR(cwd))
1885 goto out;
1886 1898
1887 error = -ERANGE; 1899 error = -ERANGE;
1888 len = PAGE_SIZE + page - cwd; 1900 len = PAGE_SIZE + page - cwd;
1889 if (len <= size) { 1901 if (len <= size) {
1890 error = len; 1902 error = len;
1891 if (copy_to_user(buf, cwd, len)) 1903 if (copy_to_user(buf, cwd, len))
1892 error = -EFAULT; 1904 error = -EFAULT;
1893 } 1905 }
1894 } else
1895 spin_unlock(&dcache_lock);
1896 1906
1897out: 1907out:
1898 dput(pwd); 1908 dput(pwd);