|
@@ -41,6 +41,7 @@
|
|
|
#include <linux/debugfs.h>
|
|
|
#include <linux/mount.h>
|
|
|
#include <linux/seq_file.h>
|
|
|
+#include <linux/quotaops.h>
|
|
|
|
|
|
#define MLOG_MASK_PREFIX ML_SUPER
|
|
|
#include <cluster/masklog.h>
|
|
@@ -127,6 +128,9 @@ static int ocfs2_get_sector(struct super_block *sb,
|
|
|
static void ocfs2_write_super(struct super_block *sb);
|
|
|
static struct inode *ocfs2_alloc_inode(struct super_block *sb);
|
|
|
static void ocfs2_destroy_inode(struct inode *inode);
|
|
|
+static int ocfs2_susp_quotas(struct ocfs2_super *osb, int unsuspend);
|
|
|
+static int ocfs2_enable_quotas(struct ocfs2_super *osb);
|
|
|
+static void ocfs2_disable_quotas(struct ocfs2_super *osb);
|
|
|
|
|
|
static const struct super_operations ocfs2_sops = {
|
|
|
.statfs = ocfs2_statfs,
|
|
@@ -165,6 +169,8 @@ enum {
|
|
|
Opt_inode64,
|
|
|
Opt_acl,
|
|
|
Opt_noacl,
|
|
|
+ Opt_usrquota,
|
|
|
+ Opt_grpquota,
|
|
|
Opt_err,
|
|
|
};
|
|
|
|
|
@@ -189,6 +195,8 @@ static const match_table_t tokens = {
|
|
|
{Opt_inode64, "inode64"},
|
|
|
{Opt_acl, "acl"},
|
|
|
{Opt_noacl, "noacl"},
|
|
|
+ {Opt_usrquota, "usrquota"},
|
|
|
+ {Opt_grpquota, "grpquota"},
|
|
|
{Opt_err, NULL}
|
|
|
};
|
|
|
|
|
@@ -452,6 +460,12 @@ static int ocfs2_remount(struct super_block *sb, int *flags, char *data)
|
|
|
|
|
|
/* We're going to/from readonly mode. */
|
|
|
if ((*flags & MS_RDONLY) != (sb->s_flags & MS_RDONLY)) {
|
|
|
+ /* Disable quota accounting before remounting RO */
|
|
|
+ if (*flags & MS_RDONLY) {
|
|
|
+ ret = ocfs2_susp_quotas(osb, 0);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
/* Lock here so the check of HARD_RO and the potential
|
|
|
* setting of SOFT_RO is atomic. */
|
|
|
spin_lock(&osb->osb_lock);
|
|
@@ -487,6 +501,21 @@ static int ocfs2_remount(struct super_block *sb, int *flags, char *data)
|
|
|
}
|
|
|
unlock_osb:
|
|
|
spin_unlock(&osb->osb_lock);
|
|
|
+ /* Enable quota accounting after remounting RW */
|
|
|
+ if (!ret && !(*flags & MS_RDONLY)) {
|
|
|
+ if (sb_any_quota_suspended(sb))
|
|
|
+ ret = ocfs2_susp_quotas(osb, 1);
|
|
|
+ else
|
|
|
+ ret = ocfs2_enable_quotas(osb);
|
|
|
+ if (ret < 0) {
|
|
|
+ /* Return back changes... */
|
|
|
+ spin_lock(&osb->osb_lock);
|
|
|
+ sb->s_flags |= MS_RDONLY;
|
|
|
+ osb->osb_flags |= OCFS2_OSB_SOFT_RO;
|
|
|
+ spin_unlock(&osb->osb_lock);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (!ret) {
|
|
@@ -647,6 +676,131 @@ static int ocfs2_verify_userspace_stack(struct ocfs2_super *osb,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int ocfs2_susp_quotas(struct ocfs2_super *osb, int unsuspend)
|
|
|
+{
|
|
|
+ int type;
|
|
|
+ struct super_block *sb = osb->sb;
|
|
|
+ unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_GRPQUOTA};
|
|
|
+ int status = 0;
|
|
|
+
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++) {
|
|
|
+ if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type]))
|
|
|
+ continue;
|
|
|
+ if (unsuspend)
|
|
|
+ status = vfs_quota_enable(
|
|
|
+ sb_dqopt(sb)->files[type],
|
|
|
+ type, QFMT_OCFS2,
|
|
|
+ DQUOT_SUSPENDED);
|
|
|
+ else
|
|
|
+ status = vfs_quota_disable(sb, type,
|
|
|
+ DQUOT_SUSPENDED);
|
|
|
+ if (status < 0)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (status < 0)
|
|
|
+ mlog(ML_ERROR, "Failed to suspend/unsuspend quotas on "
|
|
|
+ "remount (error = %d).\n", status);
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static int ocfs2_enable_quotas(struct ocfs2_super *osb)
|
|
|
+{
|
|
|
+ struct inode *inode[MAXQUOTAS] = { NULL, NULL };
|
|
|
+ struct super_block *sb = osb->sb;
|
|
|
+ unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_GRPQUOTA};
|
|
|
+ unsigned int ino[MAXQUOTAS] = { LOCAL_USER_QUOTA_SYSTEM_INODE,
|
|
|
+ LOCAL_GROUP_QUOTA_SYSTEM_INODE };
|
|
|
+ int status;
|
|
|
+ int type;
|
|
|
+
|
|
|
+ sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NEGATIVE_USAGE;
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++) {
|
|
|
+ if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type]))
|
|
|
+ continue;
|
|
|
+ inode[type] = ocfs2_get_system_file_inode(osb, ino[type],
|
|
|
+ osb->slot_num);
|
|
|
+ if (!inode[type]) {
|
|
|
+ status = -ENOENT;
|
|
|
+ goto out_quota_off;
|
|
|
+ }
|
|
|
+ status = vfs_quota_enable(inode[type], type, QFMT_OCFS2,
|
|
|
+ DQUOT_USAGE_ENABLED);
|
|
|
+ if (status < 0)
|
|
|
+ goto out_quota_off;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++)
|
|
|
+ iput(inode[type]);
|
|
|
+ return 0;
|
|
|
+out_quota_off:
|
|
|
+ ocfs2_disable_quotas(osb);
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++)
|
|
|
+ iput(inode[type]);
|
|
|
+ mlog_errno(status);
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static void ocfs2_disable_quotas(struct ocfs2_super *osb)
|
|
|
+{
|
|
|
+ int type;
|
|
|
+ struct inode *inode;
|
|
|
+ struct super_block *sb = osb->sb;
|
|
|
+
|
|
|
+ /* We mostly ignore errors in this function because there's not much
|
|
|
+ * we can do when we see them */
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++) {
|
|
|
+ if (!sb_has_quota_loaded(sb, type))
|
|
|
+ continue;
|
|
|
+ inode = igrab(sb->s_dquot.files[type]);
|
|
|
+ /* Turn off quotas. This will remove all dquot structures from
|
|
|
+ * memory and so they will be automatically synced to global
|
|
|
+ * quota files */
|
|
|
+ vfs_quota_disable(sb, type, DQUOT_USAGE_ENABLED |
|
|
|
+ DQUOT_LIMITS_ENABLED);
|
|
|
+ if (!inode)
|
|
|
+ continue;
|
|
|
+ iput(inode);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Handle quota on quotactl */
|
|
|
+static int ocfs2_quota_on(struct super_block *sb, int type, int format_id,
|
|
|
+ char *path, int remount)
|
|
|
+{
|
|
|
+ unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_GRPQUOTA};
|
|
|
+
|
|
|
+ if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type]))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (remount)
|
|
|
+ return 0; /* Just ignore it has been handled in
|
|
|
+ * ocfs2_remount() */
|
|
|
+ return vfs_quota_enable(sb_dqopt(sb)->files[type], type,
|
|
|
+ format_id, DQUOT_LIMITS_ENABLED);
|
|
|
+}
|
|
|
+
|
|
|
+/* Handle quota off quotactl */
|
|
|
+static int ocfs2_quota_off(struct super_block *sb, int type, int remount)
|
|
|
+{
|
|
|
+ if (remount)
|
|
|
+ return 0; /* Ignore now and handle later in
|
|
|
+ * ocfs2_remount() */
|
|
|
+ return vfs_quota_disable(sb, type, DQUOT_LIMITS_ENABLED);
|
|
|
+}
|
|
|
+
|
|
|
+static struct quotactl_ops ocfs2_quotactl_ops = {
|
|
|
+ .quota_on = ocfs2_quota_on,
|
|
|
+ .quota_off = ocfs2_quota_off,
|
|
|
+ .quota_sync = vfs_quota_sync,
|
|
|
+ .get_info = vfs_get_dqinfo,
|
|
|
+ .set_info = vfs_set_dqinfo,
|
|
|
+ .get_dqblk = vfs_get_dqblk,
|
|
|
+ .set_dqblk = vfs_set_dqblk,
|
|
|
+};
|
|
|
+
|
|
|
static int ocfs2_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
{
|
|
|
struct dentry *root;
|
|
@@ -689,6 +843,22 @@ static int ocfs2_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
osb->osb_commit_interval = parsed_options.commit_interval;
|
|
|
osb->local_alloc_default_bits = ocfs2_megabytes_to_clusters(sb, parsed_options.localalloc_opt);
|
|
|
osb->local_alloc_bits = osb->local_alloc_default_bits;
|
|
|
+ if (osb->s_mount_opt & OCFS2_MOUNT_USRQUOTA &&
|
|
|
+ !OCFS2_HAS_RO_COMPAT_FEATURE(sb,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) {
|
|
|
+ status = -EINVAL;
|
|
|
+ mlog(ML_ERROR, "User quotas were requested, but this "
|
|
|
+ "filesystem does not have the feature enabled.\n");
|
|
|
+ goto read_super_error;
|
|
|
+ }
|
|
|
+ if (osb->s_mount_opt & OCFS2_MOUNT_GRPQUOTA &&
|
|
|
+ !OCFS2_HAS_RO_COMPAT_FEATURE(sb,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) {
|
|
|
+ status = -EINVAL;
|
|
|
+ mlog(ML_ERROR, "Group quotas were requested, but this "
|
|
|
+ "filesystem does not have the feature enabled.\n");
|
|
|
+ goto read_super_error;
|
|
|
+ }
|
|
|
|
|
|
status = ocfs2_verify_userspace_stack(osb, &parsed_options);
|
|
|
if (status)
|
|
@@ -793,6 +963,28 @@ static int ocfs2_fill_super(struct super_block *sb, void *data, int silent)
|
|
|
atomic_set(&osb->vol_state, VOLUME_MOUNTED);
|
|
|
wake_up(&osb->osb_mount_event);
|
|
|
|
|
|
+ /* Now we can initialize quotas because we can afford to wait
|
|
|
+ * for cluster locks recovery now. That also means that truncation
|
|
|
+ * log recovery can happen but that waits for proper quota setup */
|
|
|
+ if (!(sb->s_flags & MS_RDONLY)) {
|
|
|
+ status = ocfs2_enable_quotas(osb);
|
|
|
+ if (status < 0) {
|
|
|
+ /* We have to err-out specially here because
|
|
|
+ * s_root is already set */
|
|
|
+ mlog_errno(status);
|
|
|
+ atomic_set(&osb->vol_state, VOLUME_DISABLED);
|
|
|
+ wake_up(&osb->osb_mount_event);
|
|
|
+ mlog_exit(status);
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ocfs2_complete_quota_recovery(osb);
|
|
|
+
|
|
|
+ /* Now we wake up again for processes waiting for quotas */
|
|
|
+ atomic_set(&osb->vol_state, VOLUME_MOUNTED_QUOTAS);
|
|
|
+ wake_up(&osb->osb_mount_event);
|
|
|
+
|
|
|
mlog_exit(status);
|
|
|
return status;
|
|
|
|
|
@@ -980,6 +1172,28 @@ static int ocfs2_parse_options(struct super_block *sb,
|
|
|
case Opt_inode64:
|
|
|
mopt->mount_opt |= OCFS2_MOUNT_INODE64;
|
|
|
break;
|
|
|
+ case Opt_usrquota:
|
|
|
+ /* We check only on remount, otherwise features
|
|
|
+ * aren't yet initialized. */
|
|
|
+ if (is_remount && !OCFS2_HAS_RO_COMPAT_FEATURE(sb,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) {
|
|
|
+ mlog(ML_ERROR, "User quota requested but "
|
|
|
+ "filesystem feature is not set\n");
|
|
|
+ status = 0;
|
|
|
+ goto bail;
|
|
|
+ }
|
|
|
+ mopt->mount_opt |= OCFS2_MOUNT_USRQUOTA;
|
|
|
+ break;
|
|
|
+ case Opt_grpquota:
|
|
|
+ if (is_remount && !OCFS2_HAS_RO_COMPAT_FEATURE(sb,
|
|
|
+ OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) {
|
|
|
+ mlog(ML_ERROR, "Group quota requested but "
|
|
|
+ "filesystem feature is not set\n");
|
|
|
+ status = 0;
|
|
|
+ goto bail;
|
|
|
+ }
|
|
|
+ mopt->mount_opt |= OCFS2_MOUNT_GRPQUOTA;
|
|
|
+ break;
|
|
|
#ifdef CONFIG_OCFS2_FS_POSIX_ACL
|
|
|
case Opt_acl:
|
|
|
mopt->mount_opt |= OCFS2_MOUNT_POSIX_ACL;
|
|
@@ -1056,6 +1270,10 @@ static int ocfs2_show_options(struct seq_file *s, struct vfsmount *mnt)
|
|
|
if (osb->osb_cluster_stack[0])
|
|
|
seq_printf(s, ",cluster_stack=%.*s", OCFS2_STACK_LABEL_LEN,
|
|
|
osb->osb_cluster_stack);
|
|
|
+ if (opts & OCFS2_MOUNT_USRQUOTA)
|
|
|
+ seq_printf(s, ",usrquota");
|
|
|
+ if (opts & OCFS2_MOUNT_GRPQUOTA)
|
|
|
+ seq_printf(s, ",grpquota");
|
|
|
|
|
|
if (opts & OCFS2_MOUNT_NOUSERXATTR)
|
|
|
seq_printf(s, ",nouser_xattr");
|
|
@@ -1394,6 +1612,8 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err)
|
|
|
osb = OCFS2_SB(sb);
|
|
|
BUG_ON(!osb);
|
|
|
|
|
|
+ ocfs2_disable_quotas(osb);
|
|
|
+
|
|
|
ocfs2_shutdown_local_alloc(osb);
|
|
|
|
|
|
ocfs2_truncate_log_shutdown(osb);
|
|
@@ -1504,6 +1724,8 @@ static int ocfs2_initialize_super(struct super_block *sb,
|
|
|
sb->s_fs_info = osb;
|
|
|
sb->s_op = &ocfs2_sops;
|
|
|
sb->s_export_op = &ocfs2_export_ops;
|
|
|
+ sb->s_qcop = &ocfs2_quotactl_ops;
|
|
|
+ sb->dq_op = &ocfs2_quota_operations;
|
|
|
sb->s_xattr = ocfs2_xattr_handlers;
|
|
|
sb->s_time_gran = 1;
|
|
|
sb->s_flags |= MS_NOATIME;
|