| /* |
| * Quota code necessary even when VFS quota support is not compiled |
| * into the kernel. The interesting stuff is over in dquot.c, here |
| * we have symbols for initial quotactl(2) handling, the sysctl(2) |
| * variables, etc - things needed even when quota support disabled. |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <asm/current.h> |
| #include <asm/uaccess.h> |
| #include <linux/kernel.h> |
| #include <linux/smp_lock.h> |
| #include <linux/quotaops.h> |
| #include <linux/quotacompat.h> |
| |
| struct dqstats dqstats; |
| |
| /* Check validity of quotactl */ |
| static int check_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id) |
| { |
| if (type >= MAXQUOTAS) |
| return -EINVAL; |
| if (!sb && cmd != Q_SYNC) |
| return -ENODEV; |
| /* Is operation supported? */ |
| if (sb && !sb->s_qcop) |
| return -ENOSYS; |
| |
| switch (cmd) { |
| case Q_GETFMT: |
| break; |
| case Q_QUOTAON: |
| if (!sb->s_qcop->quota_on) |
| return -ENOSYS; |
| break; |
| case Q_QUOTAOFF: |
| if (!sb->s_qcop->quota_off) |
| return -ENOSYS; |
| break; |
| case Q_SETINFO: |
| if (!sb->s_qcop->set_info) |
| return -ENOSYS; |
| break; |
| case Q_GETINFO: |
| if (!sb->s_qcop->get_info) |
| return -ENOSYS; |
| break; |
| case Q_SETQUOTA: |
| if (!sb->s_qcop->set_dqblk) |
| return -ENOSYS; |
| break; |
| case Q_GETQUOTA: |
| if (!sb->s_qcop->get_dqblk) |
| return -ENOSYS; |
| break; |
| case Q_SYNC: |
| if (sb && !sb->s_qcop->quota_sync) |
| return -ENOSYS; |
| break; |
| case Q_XQUOTAON: |
| case Q_XQUOTAOFF: |
| case Q_XQUOTARM: |
| if (!sb->s_qcop->set_xstate) |
| return -ENOSYS; |
| break; |
| case Q_XGETQSTAT: |
| if (!sb->s_qcop->get_xstate) |
| return -ENOSYS; |
| break; |
| case Q_XSETQLIM: |
| if (!sb->s_qcop->set_xquota) |
| return -ENOSYS; |
| break; |
| case Q_XGETQUOTA: |
| if (!sb->s_qcop->get_xquota) |
| return -ENOSYS; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* Is quota turned on for commands which need it? */ |
| switch (cmd) { |
| case Q_GETFMT: |
| case Q_GETINFO: |
| case Q_QUOTAOFF: |
| case Q_SETINFO: |
| case Q_SETQUOTA: |
| case Q_GETQUOTA: |
| if (!sb_has_quota_enabled(sb, type)) |
| return -ESRCH; |
| } |
| /* Check privileges */ |
| if (cmd == Q_GETQUOTA || cmd == Q_XGETQUOTA) { |
| if (((type == USRQUOTA && current->euid != id) || |
| (type == GRPQUOTA && !in_egroup_p(id))) && |
| !capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| } |
| else if (cmd != Q_GETFMT && cmd != Q_SYNC && cmd != Q_GETINFO && cmd != Q_XGETQSTAT) |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| return 0; |
| } |
| |
| /* Resolve device pathname to superblock */ |
| static struct super_block *resolve_dev(const char *path) |
| { |
| int ret; |
| mode_t mode; |
| struct nameidata nd; |
| kdev_t dev; |
| struct super_block *sb; |
| |
| ret = user_path_walk(path, &nd); |
| if (ret) |
| goto out; |
| |
| dev = nd.dentry->d_inode->i_rdev; |
| mode = nd.dentry->d_inode->i_mode; |
| path_release(&nd); |
| |
| ret = -ENOTBLK; |
| if (!S_ISBLK(mode)) |
| goto out; |
| ret = -ENODEV; |
| sb = get_super(dev); |
| if (!sb) |
| goto out; |
| return sb; |
| out: |
| return ERR_PTR(ret); |
| } |
| |
| /* Copy parameters and call proper function */ |
| static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id, caddr_t addr) |
| { |
| int ret; |
| |
| switch (cmd) { |
| case Q_QUOTAON: { |
| char *pathname; |
| |
| if (IS_ERR(pathname = getname(addr))) |
| return PTR_ERR(pathname); |
| ret = sb->s_qcop->quota_on(sb, type, id, pathname); |
| putname(pathname); |
| return ret; |
| } |
| case Q_QUOTAOFF: |
| return sb->s_qcop->quota_off(sb, type); |
| |
| case Q_GETFMT: { |
| __u32 fmt; |
| |
| fmt = sb_dqopt(sb)->info[type].dqi_format->qf_fmt_id; |
| if (copy_to_user(addr, &fmt, sizeof(fmt))) |
| return -EFAULT; |
| return 0; |
| } |
| case Q_GETINFO: { |
| struct if_dqinfo info; |
| |
| if ((ret = sb->s_qcop->get_info(sb, type, &info))) |
| return ret; |
| if (copy_to_user(addr, &info, sizeof(info))) |
| return -EFAULT; |
| return 0; |
| } |
| case Q_SETINFO: { |
| struct if_dqinfo info; |
| |
| if (copy_from_user(&info, addr, sizeof(info))) |
| return -EFAULT; |
| return sb->s_qcop->set_info(sb, type, &info); |
| } |
| case Q_GETQUOTA: { |
| struct if_dqblk idq; |
| |
| if ((ret = sb->s_qcop->get_dqblk(sb, type, id, &idq))) |
| return ret; |
| if (copy_to_user(addr, &idq, sizeof(idq))) |
| return -EFAULT; |
| return 0; |
| } |
| case Q_SETQUOTA: { |
| struct if_dqblk idq; |
| |
| if (copy_from_user(&idq, addr, sizeof(idq))) |
| return -EFAULT; |
| return sb->s_qcop->set_dqblk(sb, type, id, &idq); |
| } |
| case Q_SYNC: |
| if (sb) |
| return sb->s_qcop->quota_sync(sb, type); |
| sync_dquots_dev(NODEV, type); |
| return 0; |
| case Q_XQUOTAON: |
| case Q_XQUOTAOFF: |
| case Q_XQUOTARM: { |
| __u32 flags; |
| |
| if (copy_from_user(&flags, addr, sizeof(flags))) |
| return -EFAULT; |
| return sb->s_qcop->set_xstate(sb, flags, cmd); |
| } |
| case Q_XGETQSTAT: { |
| struct fs_quota_stat fqs; |
| |
| if ((ret = sb->s_qcop->get_xstate(sb, &fqs))) |
| return ret; |
| if (copy_to_user(addr, &fqs, sizeof(fqs))) |
| return -EFAULT; |
| return 0; |
| } |
| case Q_XSETQLIM: { |
| struct fs_disk_quota fdq; |
| |
| if (copy_from_user(&fdq, addr, sizeof(fdq))) |
| return -EFAULT; |
| return sb->s_qcop->set_xquota(sb, type, id, &fdq); |
| } |
| case Q_XGETQUOTA: { |
| struct fs_disk_quota fdq; |
| |
| if ((ret = sb->s_qcop->get_xquota(sb, type, id, &fdq))) |
| return ret; |
| if (copy_to_user(addr, &fdq, sizeof(fdq))) |
| return -EFAULT; |
| return 0; |
| } |
| /* We never reach here unless validity check is broken */ |
| default: |
| BUG(); |
| } |
| return 0; |
| } |
| |
| static int check_compat_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id) |
| { |
| if (type >= MAXQUOTAS) |
| return -EINVAL; |
| /* Is operation supported? */ |
| /* sb==NULL for GETSTATS calls */ |
| if (sb && !sb->s_qcop) |
| return -ENOSYS; |
| |
| switch (cmd) { |
| case Q_COMP_QUOTAON: |
| if (!sb->s_qcop->quota_on) |
| return -ENOSYS; |
| break; |
| case Q_COMP_QUOTAOFF: |
| if (!sb->s_qcop->quota_off) |
| return -ENOSYS; |
| break; |
| case Q_COMP_SYNC: |
| if (sb && !sb->s_qcop->quota_sync) |
| return -ENOSYS; |
| break; |
| case Q_V1_SETQLIM: |
| case Q_V1_SETUSE: |
| case Q_V1_SETQUOTA: |
| if (!sb->s_qcop->set_dqblk) |
| return -ENOSYS; |
| break; |
| case Q_V1_GETQUOTA: |
| if (!sb->s_qcop->get_dqblk) |
| return -ENOSYS; |
| break; |
| case Q_V1_RSQUASH: |
| if (!sb->s_qcop->set_info) |
| return -ENOSYS; |
| break; |
| case Q_V1_GETSTATS: |
| return 0; /* GETSTATS need no other checks */ |
| default: |
| return -EINVAL; |
| } |
| |
| /* Is quota turned on for commands which need it? */ |
| switch (cmd) { |
| case Q_V2_SETFLAGS: |
| case Q_V2_SETGRACE: |
| case Q_V2_SETINFO: |
| case Q_V2_GETINFO: |
| case Q_COMP_QUOTAOFF: |
| case Q_V1_RSQUASH: |
| case Q_V1_SETQUOTA: |
| case Q_V1_SETQLIM: |
| case Q_V1_SETUSE: |
| case Q_V2_SETQUOTA: |
| /* Q_V2_SETQLIM: collision with Q_V1_SETQLIM */ |
| case Q_V2_SETUSE: |
| case Q_V1_GETQUOTA: |
| case Q_V2_GETQUOTA: |
| if (!sb_has_quota_enabled(sb, type)) |
| return -ESRCH; |
| } |
| if (cmd != Q_COMP_QUOTAON && |
| cmd != Q_COMP_QUOTAOFF && |
| cmd != Q_COMP_SYNC && |
| sb_dqopt(sb)->info[type].dqi_format->qf_fmt_id != QFMT_VFS_OLD) |
| return -ESRCH; |
| |
| /* Check privileges */ |
| if (cmd == Q_V1_GETQUOTA || cmd == Q_V2_GETQUOTA) { |
| if (((type == USRQUOTA && current->euid != id) || |
| (type == GRPQUOTA && !in_egroup_p(id))) && |
| !capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| } |
| else if (cmd != Q_V1_GETSTATS && cmd != Q_V2_GETSTATS && cmd != Q_V2_GETINFO && cmd != Q_COMP_SYNC) |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| return 0; |
| } |
| |
| static int v1_set_rsquash(struct super_block *sb, int type, int flag) |
| { |
| struct if_dqinfo info; |
| |
| info.dqi_valid = IIF_FLAGS; |
| info.dqi_flags = flag ? V1_DQF_RSQUASH : 0; |
| return sb->s_qcop->set_info(sb, type, &info); |
| } |
| |
| static int v1_get_dqblk(struct super_block *sb, int type, qid_t id, struct v1c_mem_dqblk *mdq) |
| { |
| struct if_dqblk idq; |
| int ret; |
| |
| if ((ret = sb->s_qcop->get_dqblk(sb, type, id, &idq)) < 0) |
| return ret; |
| mdq->dqb_ihardlimit = idq.dqb_ihardlimit; |
| mdq->dqb_isoftlimit = idq.dqb_isoftlimit; |
| mdq->dqb_curinodes = idq.dqb_curinodes; |
| mdq->dqb_bhardlimit = idq.dqb_bhardlimit; |
| mdq->dqb_bsoftlimit = idq.dqb_bsoftlimit; |
| mdq->dqb_curblocks = toqb(idq.dqb_curspace); |
| mdq->dqb_itime = idq.dqb_itime; |
| mdq->dqb_btime = idq.dqb_btime; |
| if (id == 0) { /* Times for id 0 are in fact grace times */ |
| struct if_dqinfo info; |
| |
| if ((ret = sb->s_qcop->get_info(sb, type, &info)) < 0) |
| return ret; |
| mdq->dqb_btime = info.dqi_bgrace; |
| mdq->dqb_itime = info.dqi_igrace; |
| } |
| return 0; |
| } |
| |
| static int v1_set_dqblk(struct super_block *sb, int type, int cmd, qid_t id, struct v1c_mem_dqblk *mdq) |
| { |
| struct if_dqblk idq; |
| int ret; |
| |
| idq.dqb_valid = 0; |
| if (cmd == Q_V1_SETQUOTA || cmd == Q_V1_SETQLIM) { |
| idq.dqb_ihardlimit = mdq->dqb_ihardlimit; |
| idq.dqb_isoftlimit = mdq->dqb_isoftlimit; |
| idq.dqb_bhardlimit = mdq->dqb_bhardlimit; |
| idq.dqb_bsoftlimit = mdq->dqb_bsoftlimit; |
| idq.dqb_valid |= QIF_LIMITS; |
| } |
| if (cmd == Q_V1_SETQUOTA || cmd == Q_V1_SETUSE) { |
| idq.dqb_curinodes = mdq->dqb_curinodes; |
| idq.dqb_curspace = ((qsize_t)mdq->dqb_curblocks) << QUOTABLOCK_BITS; |
| idq.dqb_valid |= QIF_USAGE; |
| } |
| ret = sb->s_qcop->set_dqblk(sb, type, id, &idq); |
| if (!ret && id == 0 && cmd == Q_V1_SETQUOTA) { /* Times for id 0 are in fact grace times */ |
| struct if_dqinfo info; |
| |
| info.dqi_bgrace = mdq->dqb_btime; |
| info.dqi_igrace = mdq->dqb_itime; |
| info.dqi_valid = IIF_BGRACE | IIF_IGRACE; |
| ret = sb->s_qcop->set_info(sb, type, &info); |
| } |
| return ret; |
| } |
| |
| static void v1_get_stats(struct v1c_dqstats *dst) |
| { |
| memcpy(dst, &dqstats, sizeof(dqstats)); |
| } |
| |
| /* Handle requests to old interface */ |
| static int do_compat_quotactl(struct super_block *sb, int type, int cmd, qid_t id, caddr_t addr) |
| { |
| int ret; |
| |
| switch (cmd) { |
| case Q_COMP_QUOTAON: { |
| char *pathname; |
| |
| if (IS_ERR(pathname = getname(addr))) |
| return PTR_ERR(pathname); |
| ret = sb->s_qcop->quota_on(sb, type, QFMT_VFS_OLD, pathname); |
| putname(pathname); |
| return ret; |
| } |
| case Q_COMP_QUOTAOFF: |
| return sb->s_qcop->quota_off(sb, type); |
| case Q_COMP_SYNC: |
| if (sb) |
| return sb->s_qcop->quota_sync(sb, type); |
| sync_dquots_dev(NODEV, type); |
| return 0; |
| case Q_V1_RSQUASH: { |
| int flag; |
| |
| if (copy_from_user(&flag, addr, sizeof(flag))) |
| return -EFAULT; |
| return v1_set_rsquash(sb, type, flag); |
| } |
| case Q_V1_GETQUOTA: { |
| struct v1c_mem_dqblk mdq; |
| |
| if ((ret = v1_get_dqblk(sb, type, id, &mdq))) |
| return ret; |
| if (copy_to_user(addr, &mdq, sizeof(mdq))) |
| return -EFAULT; |
| return 0; |
| } |
| case Q_V1_SETQLIM: |
| case Q_V1_SETUSE: |
| case Q_V1_SETQUOTA: { |
| struct v1c_mem_dqblk mdq; |
| |
| if (copy_from_user(&mdq, addr, sizeof(mdq))) |
| return -EFAULT; |
| return v1_set_dqblk(sb, type, cmd, id, &mdq); |
| } |
| case Q_V1_GETSTATS: { |
| struct v1c_dqstats dst; |
| |
| v1_get_stats(&dst); |
| if (copy_to_user(addr, &dst, sizeof(dst))) |
| return -EFAULT; |
| return 0; |
| } |
| } |
| BUG(); |
| return 0; |
| } |
| |
| /* Macros for short-circuiting the compatibility tests */ |
| #define NEW_COMMAND(c) ((c) & (0x80 << 16)) |
| #define XQM_COMMAND(c) (((c) & ('X' << 8)) == ('X' << 8)) |
| |
| /* |
| * This is the system call interface. This communicates with |
| * the user-level programs. Currently this only supports diskquota |
| * calls. Maybe we need to add the process quotas etc. in the future, |
| * but we probably should use rlimits for that. |
| */ |
| asmlinkage long sys_quotactl(unsigned int cmd, const char *special, qid_t id, caddr_t addr) |
| { |
| uint cmds, type; |
| struct super_block *sb = NULL; |
| int ret = -EINVAL; |
| |
| lock_kernel(); |
| cmds = cmd >> SUBCMDSHIFT; |
| type = cmd & SUBCMDMASK; |
| |
| if (cmds != Q_V1_GETSTATS && cmds != Q_V2_GETSTATS && IS_ERR(sb = resolve_dev(special))) { |
| ret = PTR_ERR(sb); |
| sb = NULL; |
| goto out; |
| } |
| if (!NEW_COMMAND(cmds) && !XQM_COMMAND(cmds)) { |
| if ((ret = check_compat_quotactl_valid(sb, type, cmds, id)) < 0) |
| goto out; |
| ret = do_compat_quotactl(sb, type, cmds, id, addr); |
| goto out; |
| } |
| if ((ret = check_quotactl_valid(sb, type, cmds, id)) < 0) |
| goto out; |
| ret = do_quotactl(sb, type, cmds, id, addr); |
| out: |
| if (sb) |
| drop_super(sb); |
| unlock_kernel(); |
| return ret; |
| } |