Merge tag 'drm-intel-next-fixes-2015-04-25' of git://anongit.freedesktop.org/drm...
[firefly-linux-kernel-4.4.55.git] / fs / quota / quota.c
index d14a799c77850e6a1dc8ca762244f325721785a7..86ded7375c216d55fe26eb1dfd6ff366ce1fa29d 100644 (file)
@@ -118,13 +118,30 @@ static int quota_getfmt(struct super_block *sb, int type, void __user *addr)
 
 static int quota_getinfo(struct super_block *sb, int type, void __user *addr)
 {
-       struct if_dqinfo info;
+       struct qc_state state;
+       struct qc_type_state *tstate;
+       struct if_dqinfo uinfo;
        int ret;
 
-       if (!sb->s_qcop->get_info)
+       /* This checks whether qc_state has enough entries... */
+       BUILD_BUG_ON(MAXQUOTAS > XQM_MAXQUOTAS);
+       if (!sb->s_qcop->get_state)
                return -ENOSYS;
-       ret = sb->s_qcop->get_info(sb, type, &info);
-       if (!ret && copy_to_user(addr, &info, sizeof(info)))
+       ret = sb->s_qcop->get_state(sb, &state);
+       if (ret)
+               return ret;
+       tstate = state.s_state + type;
+       if (!(tstate->flags & QCI_ACCT_ENABLED))
+               return -ESRCH;
+       memset(&uinfo, 0, sizeof(uinfo));
+       uinfo.dqi_bgrace = tstate->spc_timelimit;
+       uinfo.dqi_igrace = tstate->ino_timelimit;
+       if (tstate->flags & QCI_SYSFILE)
+               uinfo.dqi_flags |= DQF_SYS_FILE;
+       if (tstate->flags & QCI_ROOT_SQUASH)
+               uinfo.dqi_flags |= DQF_ROOT_SQUASH;
+       uinfo.dqi_valid = IIF_ALL;
+       if (!ret && copy_to_user(addr, &uinfo, sizeof(uinfo)))
                return -EFAULT;
        return ret;
 }
@@ -132,12 +149,31 @@ static int quota_getinfo(struct super_block *sb, int type, void __user *addr)
 static int quota_setinfo(struct super_block *sb, int type, void __user *addr)
 {
        struct if_dqinfo info;
+       struct qc_info qinfo;
 
        if (copy_from_user(&info, addr, sizeof(info)))
                return -EFAULT;
        if (!sb->s_qcop->set_info)
                return -ENOSYS;
-       return sb->s_qcop->set_info(sb, type, &info);
+       if (info.dqi_valid & ~(IIF_FLAGS | IIF_BGRACE | IIF_IGRACE))
+               return -EINVAL;
+       memset(&qinfo, 0, sizeof(qinfo));
+       if (info.dqi_valid & IIF_FLAGS) {
+               if (info.dqi_flags & ~DQF_SETINFO_MASK)
+                       return -EINVAL;
+               if (info.dqi_flags & DQF_ROOT_SQUASH)
+                       qinfo.i_flags |= QCI_ROOT_SQUASH;
+               qinfo.i_fieldmask |= QC_FLAGS;
+       }
+       if (info.dqi_valid & IIF_BGRACE) {
+               qinfo.i_spc_timelimit = info.dqi_bgrace;
+               qinfo.i_fieldmask |= QC_SPC_TIMER;
+       }
+       if (info.dqi_valid & IIF_IGRACE) {
+               qinfo.i_ino_timelimit = info.dqi_igrace;
+               qinfo.i_fieldmask |= QC_INO_TIMER;
+       }
+       return sb->s_qcop->set_info(sb, type, &qinfo);
 }
 
 static inline qsize_t qbtos(qsize_t blocks)
@@ -252,25 +288,149 @@ static int quota_disable(struct super_block *sb, void __user *addr)
        return sb->s_qcop->quota_disable(sb, flags);
 }
 
+static int quota_state_to_flags(struct qc_state *state)
+{
+       int flags = 0;
+
+       if (state->s_state[USRQUOTA].flags & QCI_ACCT_ENABLED)
+               flags |= FS_QUOTA_UDQ_ACCT;
+       if (state->s_state[USRQUOTA].flags & QCI_LIMITS_ENFORCED)
+               flags |= FS_QUOTA_UDQ_ENFD;
+       if (state->s_state[GRPQUOTA].flags & QCI_ACCT_ENABLED)
+               flags |= FS_QUOTA_GDQ_ACCT;
+       if (state->s_state[GRPQUOTA].flags & QCI_LIMITS_ENFORCED)
+               flags |= FS_QUOTA_GDQ_ENFD;
+       if (state->s_state[PRJQUOTA].flags & QCI_ACCT_ENABLED)
+               flags |= FS_QUOTA_PDQ_ACCT;
+       if (state->s_state[PRJQUOTA].flags & QCI_LIMITS_ENFORCED)
+               flags |= FS_QUOTA_PDQ_ENFD;
+       return flags;
+}
+
+static int quota_getstate(struct super_block *sb, struct fs_quota_stat *fqs)
+{
+       int type;
+       struct qc_state state;
+       int ret;
+
+       ret = sb->s_qcop->get_state(sb, &state);
+       if (ret < 0)
+               return ret;
+
+       memset(fqs, 0, sizeof(*fqs));
+       fqs->qs_version = FS_QSTAT_VERSION;
+       fqs->qs_flags = quota_state_to_flags(&state);
+       /* No quota enabled? */
+       if (!fqs->qs_flags)
+               return -ENOSYS;
+       fqs->qs_incoredqs = state.s_incoredqs;
+       /*
+        * GETXSTATE quotactl has space for just one set of time limits so
+        * report them for the first enabled quota type
+        */
+       for (type = 0; type < XQM_MAXQUOTAS; type++)
+               if (state.s_state[type].flags & QCI_ACCT_ENABLED)
+                       break;
+       BUG_ON(type == XQM_MAXQUOTAS);
+       fqs->qs_btimelimit = state.s_state[type].spc_timelimit;
+       fqs->qs_itimelimit = state.s_state[type].ino_timelimit;
+       fqs->qs_rtbtimelimit = state.s_state[type].rt_spc_timelimit;
+       fqs->qs_bwarnlimit = state.s_state[type].spc_warnlimit;
+       fqs->qs_iwarnlimit = state.s_state[type].ino_warnlimit;
+       if (state.s_state[USRQUOTA].flags & QCI_ACCT_ENABLED) {
+               fqs->qs_uquota.qfs_ino = state.s_state[USRQUOTA].ino;
+               fqs->qs_uquota.qfs_nblks = state.s_state[USRQUOTA].blocks;
+               fqs->qs_uquota.qfs_nextents = state.s_state[USRQUOTA].nextents;
+       }
+       if (state.s_state[GRPQUOTA].flags & QCI_ACCT_ENABLED) {
+               fqs->qs_gquota.qfs_ino = state.s_state[GRPQUOTA].ino;
+               fqs->qs_gquota.qfs_nblks = state.s_state[GRPQUOTA].blocks;
+               fqs->qs_gquota.qfs_nextents = state.s_state[GRPQUOTA].nextents;
+       }
+       if (state.s_state[PRJQUOTA].flags & QCI_ACCT_ENABLED) {
+               /*
+                * Q_XGETQSTAT doesn't have room for both group and project
+                * quotas.  So, allow the project quota values to be copied out
+                * only if there is no group quota information available.
+                */
+               if (!(state.s_state[GRPQUOTA].flags & QCI_ACCT_ENABLED)) {
+                       fqs->qs_gquota.qfs_ino = state.s_state[PRJQUOTA].ino;
+                       fqs->qs_gquota.qfs_nblks =
+                                       state.s_state[PRJQUOTA].blocks;
+                       fqs->qs_gquota.qfs_nextents =
+                                       state.s_state[PRJQUOTA].nextents;
+               }
+       }
+       return 0;
+}
+
 static int quota_getxstate(struct super_block *sb, void __user *addr)
 {
        struct fs_quota_stat fqs;
        int ret;
 
-       if (!sb->s_qcop->get_xstate)
+       if (!sb->s_qcop->get_state)
                return -ENOSYS;
-       ret = sb->s_qcop->get_xstate(sb, &fqs);
+       ret = quota_getstate(sb, &fqs);
        if (!ret && copy_to_user(addr, &fqs, sizeof(fqs)))
                return -EFAULT;
        return ret;
 }
 
+static int quota_getstatev(struct super_block *sb, struct fs_quota_statv *fqs)
+{
+       int type;
+       struct qc_state state;
+       int ret;
+
+       ret = sb->s_qcop->get_state(sb, &state);
+       if (ret < 0)
+               return ret;
+
+       memset(fqs, 0, sizeof(*fqs));
+       fqs->qs_version = FS_QSTAT_VERSION;
+       fqs->qs_flags = quota_state_to_flags(&state);
+       /* No quota enabled? */
+       if (!fqs->qs_flags)
+               return -ENOSYS;
+       fqs->qs_incoredqs = state.s_incoredqs;
+       /*
+        * GETXSTATV quotactl has space for just one set of time limits so
+        * report them for the first enabled quota type
+        */
+       for (type = 0; type < XQM_MAXQUOTAS; type++)
+               if (state.s_state[type].flags & QCI_ACCT_ENABLED)
+                       break;
+       BUG_ON(type == XQM_MAXQUOTAS);
+       fqs->qs_btimelimit = state.s_state[type].spc_timelimit;
+       fqs->qs_itimelimit = state.s_state[type].ino_timelimit;
+       fqs->qs_rtbtimelimit = state.s_state[type].rt_spc_timelimit;
+       fqs->qs_bwarnlimit = state.s_state[type].spc_warnlimit;
+       fqs->qs_iwarnlimit = state.s_state[type].ino_warnlimit;
+       if (state.s_state[USRQUOTA].flags & QCI_ACCT_ENABLED) {
+               fqs->qs_uquota.qfs_ino = state.s_state[USRQUOTA].ino;
+               fqs->qs_uquota.qfs_nblks = state.s_state[USRQUOTA].blocks;
+               fqs->qs_uquota.qfs_nextents = state.s_state[USRQUOTA].nextents;
+       }
+       if (state.s_state[GRPQUOTA].flags & QCI_ACCT_ENABLED) {
+               fqs->qs_gquota.qfs_ino = state.s_state[GRPQUOTA].ino;
+               fqs->qs_gquota.qfs_nblks = state.s_state[GRPQUOTA].blocks;
+               fqs->qs_gquota.qfs_nextents = state.s_state[GRPQUOTA].nextents;
+       }
+       if (state.s_state[PRJQUOTA].flags & QCI_ACCT_ENABLED) {
+               fqs->qs_pquota.qfs_ino = state.s_state[PRJQUOTA].ino;
+               fqs->qs_pquota.qfs_nblks = state.s_state[PRJQUOTA].blocks;
+               fqs->qs_pquota.qfs_nextents = state.s_state[PRJQUOTA].nextents;
+       }
+       return 0;
+}
+
 static int quota_getxstatev(struct super_block *sb, void __user *addr)
 {
        struct fs_quota_statv fqs;
        int ret;
 
-       if (!sb->s_qcop->get_xstatev)
+       if (!sb->s_qcop->get_state)
                return -ENOSYS;
 
        memset(&fqs, 0, sizeof(fqs));
@@ -284,7 +444,7 @@ static int quota_getxstatev(struct super_block *sb, void __user *addr)
        default:
                return -EINVAL;
        }
-       ret = sb->s_qcop->get_xstatev(sb, &fqs);
+       ret = quota_getstatev(sb, &fqs);
        if (!ret && copy_to_user(addr, &fqs, sizeof(fqs)))
                return -EFAULT;
        return ret;
@@ -357,6 +517,30 @@ static void copy_from_xfs_dqblk(struct qc_dqblk *dst, struct fs_disk_quota *src)
                dst->d_fieldmask |= QC_RT_SPACE;
 }
 
+static void copy_qcinfo_from_xfs_dqblk(struct qc_info *dst,
+                                      struct fs_disk_quota *src)
+{
+       memset(dst, 0, sizeof(*dst));
+       dst->i_spc_timelimit = src->d_btimer;
+       dst->i_ino_timelimit = src->d_itimer;
+       dst->i_rt_spc_timelimit = src->d_rtbtimer;
+       dst->i_ino_warnlimit = src->d_iwarns;
+       dst->i_spc_warnlimit = src->d_bwarns;
+       dst->i_rt_spc_warnlimit = src->d_rtbwarns;
+       if (src->d_fieldmask & FS_DQ_BWARNS)
+               dst->i_fieldmask |= QC_SPC_WARNS;
+       if (src->d_fieldmask & FS_DQ_IWARNS)
+               dst->i_fieldmask |= QC_INO_WARNS;
+       if (src->d_fieldmask & FS_DQ_RTBWARNS)
+               dst->i_fieldmask |= QC_RT_SPC_WARNS;
+       if (src->d_fieldmask & FS_DQ_BTIMER)
+               dst->i_fieldmask |= QC_SPC_TIMER;
+       if (src->d_fieldmask & FS_DQ_ITIMER)
+               dst->i_fieldmask |= QC_INO_TIMER;
+       if (src->d_fieldmask & FS_DQ_RTBTIMER)
+               dst->i_fieldmask |= QC_RT_SPC_TIMER;
+}
+
 static int quota_setxquota(struct super_block *sb, int type, qid_t id,
                           void __user *addr)
 {
@@ -371,6 +555,21 @@ static int quota_setxquota(struct super_block *sb, int type, qid_t id,
        qid = make_kqid(current_user_ns(), type, id);
        if (!qid_valid(qid))
                return -EINVAL;
+       /* Are we actually setting timer / warning limits for all users? */
+       if (from_kqid(&init_user_ns, qid) == 0 &&
+           fdq.d_fieldmask & (FS_DQ_WARNS_MASK | FS_DQ_TIMER_MASK)) {
+               struct qc_info qinfo;
+               int ret;
+
+               if (!sb->s_qcop->set_info)
+                       return -EINVAL;
+               copy_qcinfo_from_xfs_dqblk(&qinfo, &fdq);
+               ret = sb->s_qcop->set_info(sb, type, &qinfo);
+               if (ret)
+                       return ret;
+               /* These are already done */
+               fdq.d_fieldmask &= ~(FS_DQ_WARNS_MASK | FS_DQ_TIMER_MASK);
+       }
        copy_from_xfs_dqblk(&qdq, &fdq);
        return sb->s_qcop->set_dqblk(sb, qid, &qdq);
 }