|
@@ -22,6 +22,7 @@
|
|
|
#include <linux/random.h>
|
|
|
#include <linux/exportfs.h>
|
|
|
#include <linux/blkdev.h>
|
|
|
+#include <linux/quotaops.h>
|
|
|
#include <linux/f2fs_fs.h>
|
|
|
#include <linux/sysfs.h>
|
|
|
|
|
@@ -106,6 +107,8 @@ enum {
|
|
|
Opt_fault_injection,
|
|
|
Opt_lazytime,
|
|
|
Opt_nolazytime,
|
|
|
+ Opt_usrquota,
|
|
|
+ Opt_grpquota,
|
|
|
Opt_err,
|
|
|
};
|
|
|
|
|
@@ -141,6 +144,8 @@ static match_table_t f2fs_tokens = {
|
|
|
{Opt_fault_injection, "fault_injection=%u"},
|
|
|
{Opt_lazytime, "lazytime"},
|
|
|
{Opt_nolazytime, "nolazytime"},
|
|
|
+ {Opt_usrquota, "usrquota"},
|
|
|
+ {Opt_grpquota, "grpquota"},
|
|
|
{Opt_err, NULL},
|
|
|
};
|
|
|
|
|
@@ -380,6 +385,20 @@ static int parse_options(struct super_block *sb, char *options)
|
|
|
case Opt_nolazytime:
|
|
|
sb->s_flags &= ~MS_LAZYTIME;
|
|
|
break;
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+ case Opt_usrquota:
|
|
|
+ set_opt(sbi, USRQUOTA);
|
|
|
+ break;
|
|
|
+ case Opt_grpquota:
|
|
|
+ set_opt(sbi, GRPQUOTA);
|
|
|
+ break;
|
|
|
+#else
|
|
|
+ case Opt_usrquota:
|
|
|
+ case Opt_grpquota:
|
|
|
+ f2fs_msg(sb, KERN_INFO,
|
|
|
+ "quota operations not supported");
|
|
|
+ break;
|
|
|
+#endif
|
|
|
default:
|
|
|
f2fs_msg(sb, KERN_ERR,
|
|
|
"Unrecognized mount option \"%s\" or missing value",
|
|
@@ -421,6 +440,10 @@ static struct inode *f2fs_alloc_inode(struct super_block *sb)
|
|
|
init_rwsem(&fi->dio_rwsem[WRITE]);
|
|
|
init_rwsem(&fi->i_mmap_sem);
|
|
|
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+ memset(&fi->i_dquot, 0, sizeof(fi->i_dquot));
|
|
|
+ fi->i_reserved_quota = 0;
|
|
|
+#endif
|
|
|
/* Will be used by directory only */
|
|
|
fi->i_dir_level = F2FS_SB(sb)->dir_level;
|
|
|
return &fi->vfs_inode;
|
|
@@ -561,11 +584,14 @@ static void destroy_device_list(struct f2fs_sb_info *sbi)
|
|
|
kfree(sbi->devs);
|
|
|
}
|
|
|
|
|
|
+static void f2fs_quota_off_umount(struct super_block *sb);
|
|
|
static void f2fs_put_super(struct super_block *sb)
|
|
|
{
|
|
|
struct f2fs_sb_info *sbi = F2FS_SB(sb);
|
|
|
int i;
|
|
|
|
|
|
+ f2fs_quota_off_umount(sb);
|
|
|
+
|
|
|
/* prevent remaining shrinker jobs */
|
|
|
mutex_lock(&sbi->umount_mutex);
|
|
|
|
|
@@ -782,6 +808,12 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root)
|
|
|
seq_printf(seq, ",fault_injection=%u",
|
|
|
sbi->fault_info.inject_rate);
|
|
|
#endif
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+ if (test_opt(sbi, USRQUOTA))
|
|
|
+ seq_puts(seq, ",usrquota");
|
|
|
+ if (test_opt(sbi, GRPQUOTA))
|
|
|
+ seq_puts(seq, ",grpquota");
|
|
|
+#endif
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -822,6 +854,7 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)
|
|
|
{
|
|
|
struct f2fs_sb_info *sbi = F2FS_SB(sb);
|
|
|
struct f2fs_mount_info org_mount_opt;
|
|
|
+ unsigned long old_sb_flags;
|
|
|
int err, active_logs;
|
|
|
bool need_restart_gc = false;
|
|
|
bool need_stop_gc = false;
|
|
@@ -835,6 +868,7 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)
|
|
|
* need to restore them.
|
|
|
*/
|
|
|
org_mount_opt = sbi->mount_opt;
|
|
|
+ old_sb_flags = sb->s_flags;
|
|
|
active_logs = sbi->active_logs;
|
|
|
|
|
|
/* recover superblocks we couldn't write due to previous RO mount */
|
|
@@ -860,6 +894,16 @@ static int f2fs_remount(struct super_block *sb, int *flags, char *data)
|
|
|
if (f2fs_readonly(sb) && (*flags & MS_RDONLY))
|
|
|
goto skip;
|
|
|
|
|
|
+ if (!f2fs_readonly(sb) && (*flags & MS_RDONLY)) {
|
|
|
+ err = dquot_suspend(sb, -1);
|
|
|
+ if (err < 0)
|
|
|
+ goto restore_opts;
|
|
|
+ } else {
|
|
|
+ /* dquot_resume needs RW */
|
|
|
+ sb->s_flags &= ~MS_RDONLY;
|
|
|
+ dquot_resume(sb, -1);
|
|
|
+ }
|
|
|
+
|
|
|
/* disallow enable/disable extent_cache dynamically */
|
|
|
if (no_extent_cache == !!test_opt(sbi, EXTENT_CACHE)) {
|
|
|
err = -EINVAL;
|
|
@@ -924,12 +968,237 @@ restore_gc:
|
|
|
restore_opts:
|
|
|
sbi->mount_opt = org_mount_opt;
|
|
|
sbi->active_logs = active_logs;
|
|
|
+ sb->s_flags = old_sb_flags;
|
|
|
#ifdef CONFIG_F2FS_FAULT_INJECTION
|
|
|
sbi->fault_info = ffi;
|
|
|
#endif
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+/* Read data from quotafile */
|
|
|
+static ssize_t f2fs_quota_read(struct super_block *sb, int type, char *data,
|
|
|
+ size_t len, loff_t off)
|
|
|
+{
|
|
|
+ struct inode *inode = sb_dqopt(sb)->files[type];
|
|
|
+ struct address_space *mapping = inode->i_mapping;
|
|
|
+ block_t blkidx = F2FS_BYTES_TO_BLK(off);
|
|
|
+ int offset = off & (sb->s_blocksize - 1);
|
|
|
+ int tocopy;
|
|
|
+ size_t toread;
|
|
|
+ loff_t i_size = i_size_read(inode);
|
|
|
+ struct page *page;
|
|
|
+ char *kaddr;
|
|
|
+
|
|
|
+ if (off > i_size)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (off + len > i_size)
|
|
|
+ len = i_size - off;
|
|
|
+ toread = len;
|
|
|
+ while (toread > 0) {
|
|
|
+ tocopy = min_t(unsigned long, sb->s_blocksize - offset, toread);
|
|
|
+repeat:
|
|
|
+ page = read_mapping_page(mapping, blkidx, NULL);
|
|
|
+ if (IS_ERR(page))
|
|
|
+ return PTR_ERR(page);
|
|
|
+
|
|
|
+ lock_page(page);
|
|
|
+
|
|
|
+ if (unlikely(page->mapping != mapping)) {
|
|
|
+ f2fs_put_page(page, 1);
|
|
|
+ goto repeat;
|
|
|
+ }
|
|
|
+ if (unlikely(!PageUptodate(page))) {
|
|
|
+ f2fs_put_page(page, 1);
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ kaddr = kmap_atomic(page);
|
|
|
+ memcpy(data, kaddr + offset, tocopy);
|
|
|
+ kunmap_atomic(kaddr);
|
|
|
+ f2fs_put_page(page, 1);
|
|
|
+
|
|
|
+ offset = 0;
|
|
|
+ toread -= tocopy;
|
|
|
+ data += tocopy;
|
|
|
+ blkidx++;
|
|
|
+ }
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+/* Write to quotafile */
|
|
|
+static ssize_t f2fs_quota_write(struct super_block *sb, int type,
|
|
|
+ const char *data, size_t len, loff_t off)
|
|
|
+{
|
|
|
+ struct inode *inode = sb_dqopt(sb)->files[type];
|
|
|
+ struct address_space *mapping = inode->i_mapping;
|
|
|
+ const struct address_space_operations *a_ops = mapping->a_ops;
|
|
|
+ int offset = off & (sb->s_blocksize - 1);
|
|
|
+ size_t towrite = len;
|
|
|
+ struct page *page;
|
|
|
+ char *kaddr;
|
|
|
+ int err = 0;
|
|
|
+ int tocopy;
|
|
|
+
|
|
|
+ while (towrite > 0) {
|
|
|
+ tocopy = min_t(unsigned long, sb->s_blocksize - offset,
|
|
|
+ towrite);
|
|
|
+
|
|
|
+ err = a_ops->write_begin(NULL, mapping, off, tocopy, 0,
|
|
|
+ &page, NULL);
|
|
|
+ if (unlikely(err))
|
|
|
+ break;
|
|
|
+
|
|
|
+ kaddr = kmap_atomic(page);
|
|
|
+ memcpy(kaddr + offset, data, tocopy);
|
|
|
+ kunmap_atomic(kaddr);
|
|
|
+ flush_dcache_page(page);
|
|
|
+
|
|
|
+ a_ops->write_end(NULL, mapping, off, tocopy, tocopy,
|
|
|
+ page, NULL);
|
|
|
+ offset = 0;
|
|
|
+ towrite -= tocopy;
|
|
|
+ off += tocopy;
|
|
|
+ data += tocopy;
|
|
|
+ cond_resched();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (len == towrite)
|
|
|
+ return err;
|
|
|
+ inode->i_version++;
|
|
|
+ inode->i_mtime = inode->i_ctime = current_time(inode);
|
|
|
+ f2fs_mark_inode_dirty_sync(inode, false);
|
|
|
+ return len - towrite;
|
|
|
+}
|
|
|
+
|
|
|
+static struct dquot **f2fs_get_dquots(struct inode *inode)
|
|
|
+{
|
|
|
+ return F2FS_I(inode)->i_dquot;
|
|
|
+}
|
|
|
+
|
|
|
+static qsize_t *f2fs_get_reserved_space(struct inode *inode)
|
|
|
+{
|
|
|
+ return &F2FS_I(inode)->i_reserved_quota;
|
|
|
+}
|
|
|
+
|
|
|
+static int f2fs_quota_sync(struct super_block *sb, int type)
|
|
|
+{
|
|
|
+ struct quota_info *dqopt = sb_dqopt(sb);
|
|
|
+ int cnt;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = dquot_writeback_dquots(sb, type);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now when everything is written we can discard the pagecache so
|
|
|
+ * that userspace sees the changes.
|
|
|
+ */
|
|
|
+ for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
|
|
|
+ if (type != -1 && cnt != type)
|
|
|
+ continue;
|
|
|
+ if (!sb_has_quota_active(sb, cnt))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ret = filemap_write_and_wait(dqopt->files[cnt]->i_mapping);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ inode_lock(dqopt->files[cnt]);
|
|
|
+ truncate_inode_pages(&dqopt->files[cnt]->i_data, 0);
|
|
|
+ inode_unlock(dqopt->files[cnt]);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int f2fs_quota_on(struct super_block *sb, int type, int format_id,
|
|
|
+ const struct path *path)
|
|
|
+{
|
|
|
+ struct inode *inode;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ err = f2fs_quota_sync(sb, -1);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ err = dquot_quota_on(sb, type, format_id, path);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ inode = d_inode(path->dentry);
|
|
|
+
|
|
|
+ inode_lock(inode);
|
|
|
+ F2FS_I(inode)->i_flags |= FS_NOATIME_FL | FS_IMMUTABLE_FL;
|
|
|
+ inode_set_flags(inode, S_NOATIME | S_IMMUTABLE,
|
|
|
+ S_NOATIME | S_IMMUTABLE);
|
|
|
+ inode_unlock(inode);
|
|
|
+ f2fs_mark_inode_dirty_sync(inode, false);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int f2fs_quota_off(struct super_block *sb, int type)
|
|
|
+{
|
|
|
+ struct inode *inode = sb_dqopt(sb)->files[type];
|
|
|
+ int err;
|
|
|
+
|
|
|
+ if (!inode || !igrab(inode))
|
|
|
+ return dquot_quota_off(sb, type);
|
|
|
+
|
|
|
+ f2fs_quota_sync(sb, -1);
|
|
|
+
|
|
|
+ err = dquot_quota_off(sb, type);
|
|
|
+ if (err)
|
|
|
+ goto out_put;
|
|
|
+
|
|
|
+ inode_lock(inode);
|
|
|
+ F2FS_I(inode)->i_flags &= ~(FS_NOATIME_FL | FS_IMMUTABLE_FL);
|
|
|
+ inode_set_flags(inode, 0, S_NOATIME | S_IMMUTABLE);
|
|
|
+ inode_unlock(inode);
|
|
|
+ f2fs_mark_inode_dirty_sync(inode, false);
|
|
|
+out_put:
|
|
|
+ iput(inode);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static void f2fs_quota_off_umount(struct super_block *sb)
|
|
|
+{
|
|
|
+ int type;
|
|
|
+
|
|
|
+ for (type = 0; type < MAXQUOTAS; type++)
|
|
|
+ f2fs_quota_off(sb, type);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct dquot_operations f2fs_quota_operations = {
|
|
|
+ .get_reserved_space = f2fs_get_reserved_space,
|
|
|
+ .write_dquot = dquot_commit,
|
|
|
+ .acquire_dquot = dquot_acquire,
|
|
|
+ .release_dquot = dquot_release,
|
|
|
+ .mark_dirty = dquot_mark_dquot_dirty,
|
|
|
+ .write_info = dquot_commit_info,
|
|
|
+ .alloc_dquot = dquot_alloc,
|
|
|
+ .destroy_dquot = dquot_destroy,
|
|
|
+ .get_next_id = dquot_get_next_id,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct quotactl_ops f2fs_quotactl_ops = {
|
|
|
+ .quota_on = f2fs_quota_on,
|
|
|
+ .quota_off = f2fs_quota_off,
|
|
|
+ .quota_sync = f2fs_quota_sync,
|
|
|
+ .get_state = dquot_get_state,
|
|
|
+ .set_info = dquot_set_dqinfo,
|
|
|
+ .get_dqblk = dquot_get_dqblk,
|
|
|
+ .set_dqblk = dquot_set_dqblk,
|
|
|
+ .get_nextdqblk = dquot_get_next_dqblk,
|
|
|
+};
|
|
|
+#else
|
|
|
+static inline void f2fs_quota_off_umount(struct super_block *sb)
|
|
|
+{
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
static struct super_operations f2fs_sops = {
|
|
|
.alloc_inode = f2fs_alloc_inode,
|
|
|
.drop_inode = f2fs_drop_inode,
|
|
@@ -937,6 +1206,11 @@ static struct super_operations f2fs_sops = {
|
|
|
.write_inode = f2fs_write_inode,
|
|
|
.dirty_inode = f2fs_dirty_inode,
|
|
|
.show_options = f2fs_show_options,
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+ .quota_read = f2fs_quota_read,
|
|
|
+ .quota_write = f2fs_quota_write,
|
|
|
+ .get_dquots = f2fs_get_dquots,
|
|
|
+#endif
|
|
|
.evict_inode = f2fs_evict_inode,
|
|
|
.put_super = f2fs_put_super,
|
|
|
.sync_fs = f2fs_sync_fs,
|
|
@@ -1679,6 +1953,12 @@ try_onemore:
|
|
|
sb->s_max_links = F2FS_LINK_MAX;
|
|
|
get_random_bytes(&sbi->s_next_generation, sizeof(u32));
|
|
|
|
|
|
+#ifdef CONFIG_QUOTA
|
|
|
+ sb->dq_op = &f2fs_quota_operations;
|
|
|
+ sb->s_qcop = &f2fs_quotactl_ops;
|
|
|
+ sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
|
|
|
+#endif
|
|
|
+
|
|
|
sb->s_op = &f2fs_sops;
|
|
|
sb->s_cop = &f2fs_cryptops;
|
|
|
sb->s_xattr = f2fs_xattr_handlers;
|