|
@@ -1905,48 +1905,30 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * __d_path - return the path of a dentry
|
|
|
+ * Prepend path string to a buffer
|
|
|
+ *
|
|
|
* @path: the dentry/vfsmount to report
|
|
|
* @root: root vfsmnt/dentry (may be modified by this function)
|
|
|
- * @buffer: buffer to return value in
|
|
|
- * @buflen: buffer length
|
|
|
+ * @buffer: pointer to the end of the buffer
|
|
|
+ * @buflen: pointer to buffer length
|
|
|
*
|
|
|
- * 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 a pointer into the buffer or an error code if the
|
|
|
- * path was too long.
|
|
|
- *
|
|
|
- * "buflen" should be positive. Caller holds the dcache_lock.
|
|
|
+ * Caller holds the dcache_lock.
|
|
|
*
|
|
|
* If path is not reachable from the supplied root, then the value of
|
|
|
* root is changed (without modifying refcounts).
|
|
|
*/
|
|
|
-char *__d_path(const struct path *path, struct path *root,
|
|
|
- char *buffer, int buflen)
|
|
|
+static int prepend_path(const struct path *path, struct path *root,
|
|
|
+ char **buffer, int *buflen)
|
|
|
{
|
|
|
struct dentry *dentry = path->dentry;
|
|
|
struct vfsmount *vfsmnt = path->mnt;
|
|
|
- char *end = buffer + buflen;
|
|
|
- char *retval;
|
|
|
+ bool slash = false;
|
|
|
+ int error = 0;
|
|
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
- prepend(&end, &buflen, "\0", 1);
|
|
|
- if (d_unlinked(dentry) &&
|
|
|
- (prepend(&end, &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) {
|
|
@@ -1958,28 +1940,88 @@ char *__d_path(const struct path *path, struct path *root,
|
|
|
}
|
|
|
parent = dentry->d_parent;
|
|
|
prefetch(parent);
|
|
|
- if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) ||
|
|
|
- (prepend(&end, &buflen, "/", 1) != 0))
|
|
|
- goto Elong;
|
|
|
- retval = end;
|
|
|
+ error = prepend_name(buffer, buflen, &dentry->d_name);
|
|
|
+ if (!error)
|
|
|
+ error = prepend(buffer, buflen, "/", 1);
|
|
|
+ if (error)
|
|
|
+ break;
|
|
|
+
|
|
|
+ slash = true;
|
|
|
dentry = parent;
|
|
|
}
|
|
|
|
|
|
out:
|
|
|
+ if (!error && !slash)
|
|
|
+ error = prepend(buffer, buflen, "/", 1);
|
|
|
+
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
- return retval;
|
|
|
+ return error;
|
|
|
|
|
|
global_root:
|
|
|
- retval += 1; /* hit the slash */
|
|
|
- if (prepend_name(&retval, &buflen, &dentry->d_name) != 0)
|
|
|
- goto Elong;
|
|
|
+ /*
|
|
|
+ * Filesystems needing to implement special "root names"
|
|
|
+ * should do so with ->d_dname()
|
|
|
+ */
|
|
|
+ if (IS_ROOT(dentry) &&
|
|
|
+ (dentry->d_name.len != 1 || dentry->d_name.name[0] != '/')) {
|
|
|
+ WARN(1, "Root dentry has weird name <%.*s>\n",
|
|
|
+ (int) dentry->d_name.len, dentry->d_name.name);
|
|
|
+ }
|
|
|
root->mnt = vfsmnt;
|
|
|
root->dentry = dentry;
|
|
|
goto out;
|
|
|
+}
|
|
|
|
|
|
-Elong:
|
|
|
- retval = ERR_PTR(-ENAMETOOLONG);
|
|
|
- goto out;
|
|
|
+/**
|
|
|
+ * __d_path - return the path of a dentry
|
|
|
+ * @path: the dentry/vfsmount to report
|
|
|
+ * @root: root vfsmnt/dentry (may be modified by this function)
|
|
|
+ * @buffer: buffer to return value in
|
|
|
+ * @buflen: buffer length
|
|
|
+ *
|
|
|
+ * Convert a dentry into an ASCII path name.
|
|
|
+ *
|
|
|
+ * Returns a pointer into the buffer or an error code if the
|
|
|
+ * path was too long.
|
|
|
+ *
|
|
|
+ * "buflen" should be positive. Caller holds the dcache_lock.
|
|
|
+ *
|
|
|
+ * If path is not reachable from the supplied root, then the value of
|
|
|
+ * root is changed (without modifying refcounts).
|
|
|
+ */
|
|
|
+char *__d_path(const struct path *path, struct path *root,
|
|
|
+ char *buf, int buflen)
|
|
|
+{
|
|
|
+ char *res = buf + buflen;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ prepend(&res, &buflen, "\0", 1);
|
|
|
+ error = prepend_path(path, root, &res, &buflen);
|
|
|
+ if (error)
|
|
|
+ return ERR_PTR(error);
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * same as __d_path but appends "(deleted)" for unlinked files.
|
|
|
+ */
|
|
|
+static int path_with_deleted(const struct path *path, struct path *root,
|
|
|
+ char **buf, int *buflen)
|
|
|
+{
|
|
|
+ prepend(buf, buflen, "\0", 1);
|
|
|
+ if (d_unlinked(path->dentry)) {
|
|
|
+ int error = prepend(buf, buflen, " (deleted)", 10);
|
|
|
+ if (error)
|
|
|
+ return error;
|
|
|
+ }
|
|
|
+
|
|
|
+ return prepend_path(path, root, buf, buflen);
|
|
|
+}
|
|
|
+
|
|
|
+static int prepend_unreachable(char **buffer, int *buflen)
|
|
|
+{
|
|
|
+ return prepend(buffer, buflen, "(unreachable)", 13);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -2000,9 +2042,10 @@ Elong:
|
|
|
*/
|
|
|
char *d_path(const struct path *path, char *buf, int buflen)
|
|
|
{
|
|
|
- char *res;
|
|
|
+ char *res = buf + buflen;
|
|
|
struct path root;
|
|
|
struct path tmp;
|
|
|
+ int error;
|
|
|
|
|
|
/*
|
|
|
* We have various synthetic filesystems that never get mounted. On
|
|
@@ -2014,19 +2057,51 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
|
|
if (path->dentry->d_op && path->dentry->d_op->d_dname)
|
|
|
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
|
|
|
|
|
|
- read_lock(¤t->fs->lock);
|
|
|
- root = current->fs->root;
|
|
|
- path_get(&root);
|
|
|
- read_unlock(¤t->fs->lock);
|
|
|
+ get_fs_root(current->fs, &root);
|
|
|
spin_lock(&dcache_lock);
|
|
|
tmp = root;
|
|
|
- res = __d_path(path, &tmp, buf, buflen);
|
|
|
+ error = path_with_deleted(path, &tmp, &res, &buflen);
|
|
|
+ if (error)
|
|
|
+ res = ERR_PTR(error);
|
|
|
spin_unlock(&dcache_lock);
|
|
|
path_put(&root);
|
|
|
return res;
|
|
|
}
|
|
|
EXPORT_SYMBOL(d_path);
|
|
|
|
|
|
+/**
|
|
|
+ * d_path_with_unreachable - return the path of a dentry
|
|
|
+ * @path: path to report
|
|
|
+ * @buf: buffer to return value in
|
|
|
+ * @buflen: buffer length
|
|
|
+ *
|
|
|
+ * The difference from d_path() is that this prepends "(unreachable)"
|
|
|
+ * to paths which are unreachable from the current process' root.
|
|
|
+ */
|
|
|
+char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
|
|
|
+{
|
|
|
+ char *res = buf + buflen;
|
|
|
+ struct path root;
|
|
|
+ struct path tmp;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ if (path->dentry->d_op && path->dentry->d_op->d_dname)
|
|
|
+ return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
|
|
|
+
|
|
|
+ get_fs_root(current->fs, &root);
|
|
|
+ spin_lock(&dcache_lock);
|
|
|
+ tmp = root;
|
|
|
+ error = path_with_deleted(path, &tmp, &res, &buflen);
|
|
|
+ if (!error && !path_equal(&tmp, &root))
|
|
|
+ error = prepend_unreachable(&res, &buflen);
|
|
|
+ spin_unlock(&dcache_lock);
|
|
|
+ path_put(&root);
|
|
|
+ if (error)
|
|
|
+ res = ERR_PTR(error);
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Helper function for dentry_operations.d_dname() members
|
|
|
*/
|
|
@@ -2129,27 +2204,30 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
|
|
|
if (!page)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- read_lock(¤t->fs->lock);
|
|
|
- pwd = current->fs->pwd;
|
|
|
- path_get(&pwd);
|
|
|
- root = current->fs->root;
|
|
|
- path_get(&root);
|
|
|
- read_unlock(¤t->fs->lock);
|
|
|
+ get_fs_root_and_pwd(current->fs, &root, &pwd);
|
|
|
|
|
|
error = -ENOENT;
|
|
|
spin_lock(&dcache_lock);
|
|
|
if (!d_unlinked(pwd.dentry)) {
|
|
|
unsigned long len;
|
|
|
struct path tmp = root;
|
|
|
- char * cwd;
|
|
|
+ char *cwd = page + PAGE_SIZE;
|
|
|
+ int buflen = PAGE_SIZE;
|
|
|
|
|
|
- cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
|
|
|
+ prepend(&cwd, &buflen, "\0", 1);
|
|
|
+ error = prepend_path(&pwd, &tmp, &cwd, &buflen);
|
|
|
spin_unlock(&dcache_lock);
|
|
|
|
|
|
- error = PTR_ERR(cwd);
|
|
|
- if (IS_ERR(cwd))
|
|
|
+ if (error)
|
|
|
goto out;
|
|
|
|
|
|
+ /* Unreachable from current root */
|
|
|
+ if (!path_equal(&tmp, &root)) {
|
|
|
+ error = prepend_unreachable(&cwd, &buflen);
|
|
|
+ if (error)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
error = -ERANGE;
|
|
|
len = PAGE_SIZE + page - cwd;
|
|
|
if (len <= size) {
|