| |
| #include <linux/syscalls.h> |
| #include <linux/export.h> |
| #include <linux/uaccess.h> |
| #include <linux/fs_struct.h> |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/prefetch.h> |
| #include "mount.h" |
| |
| static int prepend(char **buffer, int *buflen, const char *str, int namelen) |
| { |
| <------>*buflen -= namelen; |
| <------>if (*buflen < 0) |
| <------><------>return -ENAMETOOLONG; |
| <------>*buffer -= namelen; |
| <------>memcpy(*buffer, str, namelen); |
| <------>return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int prepend_name(char **buffer, int *buflen, const struct qstr *name) |
| { |
| <------>const char *dname = smp_load_acquire(&name->name); |
| <------>u32 dlen = READ_ONCE(name->len); |
| <------>char *p; |
| |
| <------>*buflen -= dlen + 1; |
| <------>if (*buflen < 0) |
| <------><------>return -ENAMETOOLONG; |
| <------>p = *buffer -= dlen + 1; |
| <------>*p++ = '/'; |
| <------>while (dlen--) { |
| <------><------>char c = *dname++; |
| <------><------>if (!c) |
| <------><------><------>break; |
| <------><------>*p++ = c; |
| <------>} |
| <------>return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int prepend_path(const struct path *path, |
| <------><------><------>const struct path *root, |
| <------><------><------>char **buffer, int *buflen) |
| { |
| <------>struct dentry *dentry; |
| <------>struct vfsmount *vfsmnt; |
| <------>struct mount *mnt; |
| <------>int error = 0; |
| <------>unsigned seq, m_seq = 0; |
| <------>char *bptr; |
| <------>int blen; |
| |
| <------>rcu_read_lock(); |
| restart_mnt: |
| <------>read_seqbegin_or_lock(&mount_lock, &m_seq); |
| <------>seq = 0; |
| <------>rcu_read_lock(); |
| restart: |
| <------>bptr = *buffer; |
| <------>blen = *buflen; |
| <------>error = 0; |
| <------>dentry = path->dentry; |
| <------>vfsmnt = path->mnt; |
| <------>mnt = real_mount(vfsmnt); |
| <------>read_seqbegin_or_lock(&rename_lock, &seq); |
| <------>while (dentry != root->dentry || vfsmnt != root->mnt) { |
| <------><------>struct dentry * parent; |
| |
| <------><------>if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { |
| <------><------><------>struct mount *parent = READ_ONCE(mnt->mnt_parent); |
| <------><------><------>struct mnt_namespace *mnt_ns; |
| |
| <------><------><------> |
| <------><------><------>if (dentry != vfsmnt->mnt_root) { |
| <------><------><------><------>bptr = *buffer; |
| <------><------><------><------>blen = *buflen; |
| <------><------><------><------>error = 3; |
| <------><------><------><------>break; |
| <------><------><------>} |
| <------><------><------> |
| <------><------><------>if (mnt != parent) { |
| <------><------><------><------>dentry = READ_ONCE(mnt->mnt_mountpoint); |
| <------><------><------><------>mnt = parent; |
| <------><------><------><------>vfsmnt = &mnt->mnt; |
| <------><------><------><------>continue; |
| <------><------><------>} |
| <------><------><------>mnt_ns = READ_ONCE(mnt->mnt_ns); |
| <------><------><------> |
| <------><------><------>if (!IS_ERR_OR_NULL(mnt_ns) && !is_anon_ns(mnt_ns)) |
| <------><------><------><------>error = 1; |
| <------><------><------>else |
| <------><------><------><------>error = 2; |
| <------><------><------>break; |
| <------><------>} |
| <------><------>parent = dentry->d_parent; |
| <------><------>prefetch(parent); |
| <------><------>error = prepend_name(&bptr, &blen, &dentry->d_name); |
| <------><------>if (error) |
| <------><------><------>break; |
| |
| <------><------>dentry = parent; |
| <------>} |
| <------>if (!(seq & 1)) |
| <------><------>rcu_read_unlock(); |
| <------>if (need_seqretry(&rename_lock, seq)) { |
| <------><------>seq = 1; |
| <------><------>goto restart; |
| <------>} |
| <------>done_seqretry(&rename_lock, seq); |
| |
| <------>if (!(m_seq & 1)) |
| <------><------>rcu_read_unlock(); |
| <------>if (need_seqretry(&mount_lock, m_seq)) { |
| <------><------>m_seq = 1; |
| <------><------>goto restart_mnt; |
| <------>} |
| <------>done_seqretry(&mount_lock, m_seq); |
| |
| <------>if (error >= 0 && bptr == *buffer) { |
| <------><------>if (--blen < 0) |
| <------><------><------>error = -ENAMETOOLONG; |
| <------><------>else |
| <------><------><------>*--bptr = '/'; |
| <------>} |
| <------>*buffer = bptr; |
| <------>*buflen = blen; |
| <------>return error; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| char *__d_path(const struct path *path, |
| <------> const 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 < 0) |
| <------><------>return ERR_PTR(error); |
| <------>if (error > 0) |
| <------><------>return NULL; |
| <------>return res; |
| } |
| |
| char *d_absolute_path(const struct path *path, |
| <------> char *buf, int buflen) |
| { |
| <------>struct path root = {}; |
| <------>char *res = buf + buflen; |
| <------>int error; |
| |
| <------>prepend(&res, &buflen, "\0", 1); |
| <------>error = prepend_path(path, &root, &res, &buflen); |
| |
| <------>if (error > 1) |
| <------><------>error = -EINVAL; |
| <------>if (error < 0) |
| <------><------>return ERR_PTR(error); |
| <------>return res; |
| } |
| |
| |
| |
| |
| static int path_with_deleted(const struct path *path, |
| <------><------><------> const 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); |
| } |
| |
| static void get_fs_root_rcu(struct fs_struct *fs, struct path *root) |
| { |
| <------>unsigned seq; |
| |
| <------>do { |
| <------><------>seq = read_seqcount_begin(&fs->seq); |
| <------><------>*root = fs->root; |
| <------>} while (read_seqcount_retry(&fs->seq, seq)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| char *d_path(const struct path *path, char *buf, int buflen) |
| { |
| <------>char *res = buf + buflen; |
| <------>struct path root; |
| <------>int error; |
| |
| <------> |
| <------> * We have various synthetic filesystems that never get mounted. On |
| <------> * these filesystems dentries are never used for lookup purposes, and |
| <------> * thus don't need to be hashed. They also don't need a name until a |
| <------> * user wants to identify the object in /proc/pid/fd/. The little hack |
| <------> * below allows us to generate a name for these objects on demand: |
| <------> * |
| <------> * Some pseudo inodes are mountable. When they are mounted |
| <------> * path->dentry == path->mnt->mnt_root. In that case don't call d_dname |
| <------> * and instead have d_path return the mounted path. |
| <------> */ |
| <------>if (path->dentry->d_op && path->dentry->d_op->d_dname && |
| <------> (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root)) |
| <------><------>return path->dentry->d_op->d_dname(path->dentry, buf, buflen); |
| |
| <------>rcu_read_lock(); |
| <------>get_fs_root_rcu(current->fs, &root); |
| <------>error = path_with_deleted(path, &root, &res, &buflen); |
| <------>rcu_read_unlock(); |
| |
| <------>if (error < 0) |
| <------><------>res = ERR_PTR(error); |
| <------>return res; |
| } |
| EXPORT_SYMBOL(d_path); |
| |
| |
| |
| |
| char *dynamic_dname(struct dentry *dentry, char *buffer, int buflen, |
| <------><------><------>const char *fmt, ...) |
| { |
| <------>va_list args; |
| <------>char temp[64]; |
| <------>int sz; |
| |
| <------>va_start(args, fmt); |
| <------>sz = vsnprintf(temp, sizeof(temp), fmt, args) + 1; |
| <------>va_end(args); |
| |
| <------>if (sz > sizeof(temp) || sz > buflen) |
| <------><------>return ERR_PTR(-ENAMETOOLONG); |
| |
| <------>buffer += buflen - sz; |
| <------>return memcpy(buffer, temp, sz); |
| } |
| |
| char *simple_dname(struct dentry *dentry, char *buffer, int buflen) |
| { |
| <------>char *end = buffer + buflen; |
| <------> |
| <------>if (prepend(&end, &buflen, " (deleted)", 11) || |
| <------> prepend(&end, &buflen, dentry->d_name.name, dentry->d_name.len) || |
| <------> prepend(&end, &buflen, "/", 1)) |
| <------><------>end = ERR_PTR(-ENAMETOOLONG); |
| <------>return end; |
| } |
| |
| |
| |
| |
| static char *__dentry_path(struct dentry *d, char *buf, int buflen) |
| { |
| <------>struct dentry *dentry; |
| <------>char *end, *retval; |
| <------>int len, seq = 0; |
| <------>int error = 0; |
| |
| <------>if (buflen < 2) |
| <------><------>goto Elong; |
| |
| <------>rcu_read_lock(); |
| restart: |
| <------>dentry = d; |
| <------>end = buf + buflen; |
| <------>len = buflen; |
| <------>prepend(&end, &len, "\0", 1); |
| <------> |
| <------>retval = end-1; |
| <------>*retval = '/'; |
| <------>read_seqbegin_or_lock(&rename_lock, &seq); |
| <------>while (!IS_ROOT(dentry)) { |
| <------><------>struct dentry *parent = dentry->d_parent; |
| |
| <------><------>prefetch(parent); |
| <------><------>error = prepend_name(&end, &len, &dentry->d_name); |
| <------><------>if (error) |
| <------><------><------>break; |
| |
| <------><------>retval = end; |
| <------><------>dentry = parent; |
| <------>} |
| <------>if (!(seq & 1)) |
| <------><------>rcu_read_unlock(); |
| <------>if (need_seqretry(&rename_lock, seq)) { |
| <------><------>seq = 1; |
| <------><------>goto restart; |
| <------>} |
| <------>done_seqretry(&rename_lock, seq); |
| <------>if (error) |
| <------><------>goto Elong; |
| <------>return retval; |
| Elong: |
| <------>return ERR_PTR(-ENAMETOOLONG); |
| } |
| |
| char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen) |
| { |
| <------>return __dentry_path(dentry, buf, buflen); |
| } |
| EXPORT_SYMBOL(dentry_path_raw); |
| |
| char *dentry_path(struct dentry *dentry, char *buf, int buflen) |
| { |
| <------>char *p = NULL; |
| <------>char *retval; |
| |
| <------>if (d_unlinked(dentry)) { |
| <------><------>p = buf + buflen; |
| <------><------>if (prepend(&p, &buflen, "//deleted", 10) != 0) |
| <------><------><------>goto Elong; |
| <------><------>buflen++; |
| <------>} |
| <------>retval = __dentry_path(dentry, buf, buflen); |
| <------>if (!IS_ERR(retval) && p) |
| <------><------>*p = '/'; |
| <------>return retval; |
| Elong: |
| <------>return ERR_PTR(-ENAMETOOLONG); |
| } |
| |
| static void get_fs_root_and_pwd_rcu(struct fs_struct *fs, struct path *root, |
| <------><------><------><------> struct path *pwd) |
| { |
| <------>unsigned seq; |
| |
| <------>do { |
| <------><------>seq = read_seqcount_begin(&fs->seq); |
| <------><------>*root = fs->root; |
| <------><------>*pwd = fs->pwd; |
| <------>} while (read_seqcount_retry(&fs->seq, seq)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size) |
| { |
| <------>int error; |
| <------>struct path pwd, root; |
| <------>char *page = __getname(); |
| |
| <------>if (!page) |
| <------><------>return -ENOMEM; |
| |
| <------>rcu_read_lock(); |
| <------>get_fs_root_and_pwd_rcu(current->fs, &root, &pwd); |
| |
| <------>error = -ENOENT; |
| <------>if (!d_unlinked(pwd.dentry)) { |
| <------><------>unsigned long len; |
| <------><------>char *cwd = page + PATH_MAX; |
| <------><------>int buflen = PATH_MAX; |
| |
| <------><------>prepend(&cwd, &buflen, "\0", 1); |
| <------><------>error = prepend_path(&pwd, &root, &cwd, &buflen); |
| <------><------>rcu_read_unlock(); |
| |
| <------><------>if (error < 0) |
| <------><------><------>goto out; |
| |
| <------><------> |
| <------><------>if (error > 0) { |
| <------><------><------>error = prepend_unreachable(&cwd, &buflen); |
| <------><------><------>if (error) |
| <------><------><------><------>goto out; |
| <------><------>} |
| |
| <------><------>error = -ERANGE; |
| <------><------>len = PATH_MAX + page - cwd; |
| <------><------>if (len <= size) { |
| <------><------><------>error = len; |
| <------><------><------>if (copy_to_user(buf, cwd, len)) |
| <------><------><------><------>error = -EFAULT; |
| <------><------>} |
| <------>} else { |
| <------><------>rcu_read_unlock(); |
| <------>} |
| |
| out: |
| <------>__putname(page); |
| <------>return error; |
| } |
| |