Add syscall quotactl_path, a variant of quotactl which allows to specify the mountpath instead of a path of to a block device. The quotactl syscall expects a path to the mounted block device to specify the filesystem to work on. This limits usage to filesystems which actually have a block device. quotactl_path replaces the path to the block device with a path where the filesystem is mounted at. The global Q_SYNC command to sync all filesystems is not supported for this new syscall, otherwise quotactl_path behaves like quotactl. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- fs/quota/quota.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/fs/quota/quota.c b/fs/quota/quota.c index 6d16b2be5ac4..9ac09e128686 100644 --- a/fs/quota/quota.c +++ b/fs/quota/quota.c @@ -17,6 +17,7 @@ #include <linux/capability.h> #include <linux/quotaops.h> #include <linux/types.h> +#include <linux/mount.h> #include <linux/writeback.h> #include <linux/nospec.h> #include "compat.h" @@ -968,3 +969,79 @@ SYSCALL_DEFINE4(quotactl, unsigned int, cmd, const char __user *, special, path_put(pathp); return ret; } + +SYSCALL_DEFINE4(quotactl_path, unsigned int, cmd, const char __user *, + mountpoint, qid_t, id, void __user *, addr) +{ + uint cmds, type; + struct super_block *sb = NULL; + struct path path, *pathp = NULL; + struct path mountpath; + bool excl = false, thawed = false; + int ret; + + cmds = cmd >> SUBCMDSHIFT; + type = cmd & SUBCMDMASK; + + if (type >= MAXQUOTAS) + return -EINVAL; + + if (!mountpoint) + return -ENODEV; + + /* + * Path for quotaon has to be resolved before grabbing superblock + * because that gets s_umount sem which is also possibly needed by path + * resolution (think about autofs) and thus deadlocks could arise. + */ + if (cmds == Q_QUOTAON) { + ret = user_path_at(AT_FDCWD, addr, + LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT, &path); + if (ret) + pathp = ERR_PTR(ret); + else + pathp = &path; + } + + ret = user_path_at(AT_FDCWD, mountpoint, + LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT, &mountpath); + if (ret) + goto out; + + if (quotactl_cmd_onoff(cmds)) { + excl = true; + thawed = true; + } else if (quotactl_cmd_write(cmds)) { + thawed = true; + } + + if (thawed) { + ret = mnt_want_write(mountpath.mnt); + if (ret) + goto out1; + } + + sb = mountpath.dentry->d_inode->i_sb; + + if (excl) + down_write(&sb->s_umount); + else + down_read(&sb->s_umount); + + ret = do_quotactl(sb, type, cmds, id, addr, pathp); + + if (excl) + up_write(&sb->s_umount); + else + up_read(&sb->s_umount); + + if (thawed) + mnt_drop_write(mountpath.mnt); +out1: + path_put(&mountpath); + +out: + if (pathp && !IS_ERR(pathp)) + path_put(pathp); + return ret; +} -- 2.20.1