mkfs.f2fs: support quota option in mkfs

This patch let mkfs to handle quota option and create quota files.

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index 9f56388..53701d3 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -458,6 +458,12 @@
 #define F2FS_NODE_INO(sbi)	(sbi->node_ino_num)
 #define F2FS_META_INO(sbi)	(sbi->meta_ino_num)
 
+#define F2FS_MAX_QUOTAS		3
+#define QUOTA_DATA(i)		(2)
+#define QUOTA_INO(sb,t)	(le32_to_cpu((sb)->qf_ino[t]))
+
+#define FS_IMMUTABLE_FL		0x00000010 /* Immutable file */
+
 /* This flag is used by node and meta inodes, and by recovery */
 #define GFP_F2FS_ZERO	(GFP_NOFS | __GFP_ZERO)
 
@@ -478,6 +484,7 @@
 #define F2FS_FEATURE_PRJQUOTA		0x0010
 #define F2FS_FEATURE_INODE_CHKSUM	0x0020
 #define F2FS_FEATURE_FLEXIBLE_INLINE_XATTR	0x0040
+#define F2FS_FEATURE_QUOTA_INO		0x0080
 
 #define MAX_VOLUME_NAME		512
 
@@ -528,7 +535,8 @@
 	__u8 encryption_level;		/* versioning level for encryption */
 	__u8 encrypt_pw_salt[16];	/* Salt used for string2key algorithm */
 	struct f2fs_device devs[MAX_DEVICES];	/* device list */
-	__u8 reserved[327];		/* valid reserved region */
+	__le32 qf_ino[F2FS_MAX_QUOTAS];	/* quota inode numbers */
+	__u8 reserved[315];		/* valid reserved region */
 } __attribute__((packed));
 
 /*
@@ -1171,4 +1179,14 @@
 	return cpu_to_le64(cp_ver);
 }
 
+static inline int is_qf_ino(struct f2fs_super_block *sb, nid_t ino)
+{
+	int i;
+
+	for (i = 0; i < F2FS_MAX_QUOTAS; i++)
+		if (sb->qf_ino[i] == ino)
+			return 1;
+	return 0;
+}
+
 #endif	/*__F2FS_FS_H */
diff --git a/include/quota.h b/include/quota.h
new file mode 100644
index 0000000..4c9f5f4
--- /dev/null
+++ b/include/quota.h
@@ -0,0 +1,77 @@
+/*
+ *
+ * Header file for disk format of new quotafile format
+ *
+ * Copied essential definitions and structures for mkfs.f2fs from quotaio.h
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Jan Kara <jack@suse.cz>
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ *
+ */
+#ifndef F2FS_QUOTA_H
+#define F2FS_QUOTA_H
+
+enum quota_type {
+	USRQUOTA = 0,
+	GRPQUOTA = 1,
+	PRJQUOTA = 2,
+	MAXQUOTAS = 3,
+};
+
+#if MAXQUOTAS > 32
+#error "cannot have more than 32 quota types to fit in qtype_bits"
+#endif
+
+#define QUOTA_USR_BIT (1 << USRQUOTA)
+#define QUOTA_GRP_BIT (1 << GRPQUOTA)
+#define QUOTA_PRJ_BIT (1 << PRJQUOTA)
+#define QUOTA_ALL_BIT (QUOTA_USR_BIT | QUOTA_GRP_BIT | QUOTA_PRJ_BIT)
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define INITQMAGICS {\
+	0xd9c01f11,	/* USRQUOTA */\
+	0xd9c01927,	/* GRPQUOTA */\
+	0xd9c03f14      /* PRJQUOTA */\
+}
+
+#define V2_DQINFOOFF	sizeof(struct v2_disk_dqheader)	/* Offset of info header in file */
+
+#define MAX_IQ_TIME  604800	/* (7*24*60*60) 1 week */
+#define MAX_DQ_TIME  604800	/* (7*24*60*60) 1 week */
+
+#define QT_TREEOFF	1	/* Offset of tree in file in blocks */
+
+struct v2_disk_dqheader {
+	u_int32_t dqh_magic;	/* Magic number identifying file */
+	u_int32_t dqh_version;	/* File version */
+} __attribute__ ((packed));
+
+/* Header with type and version specific information */
+struct v2_disk_dqinfo {
+	u_int32_t dqi_bgrace;	/* Time before block soft limit becomes hard limit */
+	u_int32_t dqi_igrace;	/* Time before inode soft limit becomes hard limit */
+	u_int32_t dqi_flags;	/* Flags for quotafile (DQF_*) */
+	u_int32_t dqi_blocks;	/* Number of blocks in file */
+	u_int32_t dqi_free_blk;	/* Number of first free block in the list */
+	u_int32_t dqi_free_entry;	/* Number of block with at least one free entry */
+} __attribute__ ((packed));
+
+struct v2r1_disk_dqblk {
+	__le32 dqb_id;  	/* id this quota applies to */
+	__le32 dqb_pad;
+	__le64 dqb_ihardlimit;  /* absolute limit on allocated inodes */
+	__le64 dqb_isoftlimit;  /* preferred inode limit */
+	__le64 dqb_curinodes;   /* current # allocated inodes */
+	__le64 dqb_bhardlimit;  /* absolute limit on disk space
+				 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_bsoftlimit;  /* preferred limit on disk space
+				 * (in QUOTABLOCK_SIZE) */
+	__le64 dqb_curspace;    /* current space occupied (in bytes) */
+	__le64 dqb_btime;       /* time limit for excessive disk use */
+	__le64 dqb_itime;       /* time limit for excessive inode use */
+} __attribute__ ((packed));
+
+#endif
diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
index 8a821d3..c809225 100644
--- a/mkfs/f2fs_format.c
+++ b/mkfs/f2fs_format.c
@@ -19,6 +19,7 @@
 #include <uuid/uuid.h>
 
 #include "f2fs_fs.h"
+#include "quota.h"
 #include "f2fs_format_utils.h"
 
 extern struct f2fs_configuration c;
@@ -32,6 +33,8 @@
 #define last_zone(cur)		((cur - 1) * c.segs_per_zone)
 #define last_section(cur)	(cur + (c.secs_per_zone - 1) * c.segs_per_sec)
 
+static unsigned int quotatype_bits = 0;
+
 const char *media_ext_lists[] = {
 	"jpg",
 	"gif",
@@ -153,6 +156,8 @@
 	u_int32_t sit_bitmap_size, max_sit_bitmap_size;
 	u_int32_t max_nat_bitmap_size, max_nat_segments;
 	u_int32_t total_zones;
+	u_int32_t next_ino;
+	enum quota_type qtype;
 	int i;
 
 	set_sb(magic, F2FS_SUPER_MAGIC);
@@ -382,6 +387,17 @@
 	set_sb(node_ino, 1);
 	set_sb(meta_ino, 2);
 	set_sb(root_ino, 3);
+	next_ino = 4;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		quotatype_bits = QUOTA_USR_BIT | QUOTA_GRP_BIT;
+		if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+			quotatype_bits |= QUOTA_PRJ_BIT;
+	}
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
+		sb->qf_ino[qtype] =
+			((1 << qtype) & quotatype_bits) ? next_ino++ : 0;
 
 	if (total_zones <= 6) {
 		MSG(1, "\tError: %d zones: Need more zones "
@@ -513,6 +529,9 @@
 	char *cp_payload = NULL;
 	char *sum_compact, *sum_compact_p;
 	struct f2fs_summary *sum_entry;
+	enum quota_type qtype;
+	u_int32_t quota_inum, quota_dnum;
+	int off;
 	int ret = -1;
 
 	cp = calloc(F2FS_BLKSIZE, 1);
@@ -562,9 +581,16 @@
 		set_cp(cur_data_segno[i], 0xffffffff);
 	}
 
-	set_cp(cur_node_blkoff[0], 1);
-	set_cp(cur_data_blkoff[0], 1);
-	set_cp(valid_block_count, 2);
+	quota_inum = quota_dnum = 0;
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
+		if (sb->qf_ino[qtype]) {
+			quota_inum++;
+			quota_dnum += QUOTA_DATA(qtype);
+		}
+
+	set_cp(cur_node_blkoff[0], 1 + quota_inum);
+	set_cp(cur_data_blkoff[0], 1 + quota_dnum);
+	set_cp(valid_block_count, 2 + quota_inum + quota_dnum);
 	set_cp(rsvd_segment_count, c.reserved_segments);
 	set_cp(overprov_segment_count, (get_sb(segment_count_main) -
 			get_cp(rsvd_segment_count)) *
@@ -593,9 +619,9 @@
 
 	set_cp(ckpt_flags, flags);
 	set_cp(cp_pack_start_sum, 1 + get_sb(cp_payload));
-	set_cp(valid_node_count, 1);
-	set_cp(valid_inode_count, 1);
-	set_cp(next_free_nid, get_sb(root_ino) + 1);
+	set_cp(valid_node_count, 1 + quota_inum);
+	set_cp(valid_inode_count, 1 + quota_inum);
+	set_cp(next_free_nid, get_sb(root_ino) + 1 + quota_inum);
 	set_cp(sit_ver_bitmap_bytesize, ((get_sb(segment_count_sit) / 2) <<
 			get_sb(log_blocks_per_seg)) / 8);
 
@@ -653,7 +679,7 @@
 	SET_SUM_TYPE((&sum->footer), SUM_TYPE_DATA);
 
 	journal = &sum->journal;
-	journal->n_nats = cpu_to_le16(1);
+	journal->n_nats = cpu_to_le16(1 + quota_inum);
 	journal->nat_j.entries[0].nid = sb->root_ino;
 	journal->nat_j.entries[0].ne.version = 0;
 	journal->nat_j.entries[0].ne.ino = sb->root_ino;
@@ -661,6 +687,19 @@
 			get_sb(main_blkaddr) +
 			get_cp(cur_node_segno[0]) * c.blks_per_seg);
 
+	for (qtype = 0, i = 1; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		journal->nat_j.entries[i].nid = sb->qf_ino[qtype];
+		journal->nat_j.entries[i].ne.version = 0;
+		journal->nat_j.entries[i].ne.ino = sb->qf_ino[qtype];
+		journal->nat_j.entries[i].ne.block_addr = cpu_to_le32(
+				get_sb(main_blkaddr) +
+				get_cp(cur_node_segno[0]) *
+				c.blks_per_seg + i);
+		i++;
+	}
+
 	memcpy(sum_compact_p, &journal->n_nats, SUM_JOURNAL_SIZE);
 	sum_compact_p += SUM_JOURNAL_SIZE;
 
@@ -669,8 +708,11 @@
 	journal->n_sits = cpu_to_le16(6);
 	journal->sit_j.entries[0].segno = cp->cur_node_segno[0];
 	journal->sit_j.entries[0].se.vblocks =
-				cpu_to_le16((CURSEG_HOT_NODE << 10) | 1);
+				cpu_to_le16((CURSEG_HOT_NODE << 10) |
+						(1 + quota_inum));
 	f2fs_set_bit(0, (char *)journal->sit_j.entries[0].se.valid_map);
+	for (i = 1; i <= quota_inum; i++)
+		f2fs_set_bit(i, (char *)journal->sit_j.entries[0].se.valid_map);
 	journal->sit_j.entries[1].segno = cp->cur_node_segno[1];
 	journal->sit_j.entries[1].se.vblocks =
 				cpu_to_le16((CURSEG_WARM_NODE << 10));
@@ -681,8 +723,12 @@
 	/* data sit for root */
 	journal->sit_j.entries[3].segno = cp->cur_data_segno[0];
 	journal->sit_j.entries[3].se.vblocks =
-				cpu_to_le16((CURSEG_HOT_DATA << 10) | 1);
+				cpu_to_le16((CURSEG_HOT_DATA << 10) |
+						(1 + quota_dnum));
 	f2fs_set_bit(0, (char *)journal->sit_j.entries[3].se.valid_map);
+	for (i = 1; i <= quota_dnum; i++)
+		f2fs_set_bit(i, (char *)journal->sit_j.entries[3].se.valid_map);
+
 	journal->sit_j.entries[4].segno = cp->cur_data_segno[1];
 	journal->sit_j.entries[4].se.vblocks =
 				cpu_to_le16((CURSEG_WARM_DATA << 10));
@@ -697,6 +743,20 @@
 	sum_entry = (struct f2fs_summary *)sum_compact_p;
 	sum_entry->nid = sb->root_ino;
 	sum_entry->ofs_in_node = 0;
+
+	off = 1;
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		int j;
+
+		for (j = 0; j < QUOTA_DATA(qtype); j++) {
+			(sum_entry + off + j)->nid = sb->qf_ino[qtype];
+			(sum_entry + off + j)->ofs_in_node = j;
+		}
+		off += QUOTA_DATA(qtype);
+	}
+
 	/* warm data summary, nothing to do */
 	/* cold data summary, nothing to do */
 
@@ -714,6 +774,13 @@
 
 	sum->entries[0].nid = sb->root_ino;
 	sum->entries[0].ofs_in_node = 0;
+	for (qtype = i = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		sum->entries[1 + i].nid = sb->qf_ino[qtype];
+		sum->entries[1 + i].ofs_in_node = 0;
+		i++;
+	}
 
 	cp_seg_blk++;
 	DBG(1, "\tWriting Segment summary for HOT_NODE, at offset 0x%08"PRIx64"\n",
@@ -855,12 +922,19 @@
 static int discard_obsolete_dnode(struct f2fs_node *raw_node, u_int64_t offset)
 {
 	u_int64_t next_blkaddr = 0;
-	u_int64_t root_inode_pos = get_sb(main_blkaddr);
 	u64 end_blkaddr = (get_sb(segment_count_main) <<
 			get_sb(log_blocks_per_seg)) + get_sb(main_blkaddr);
+	u_int64_t start_inode_pos = get_sb(main_blkaddr);
+	u_int64_t last_inode_pos;
+	enum quota_type qtype;
+	u_int32_t quota_inum = 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
+		if (sb->qf_ino[qtype]) quota_inum++;
 
 	/* only root inode was written before truncating dnodes */
-	root_inode_pos += c.cur_seg[CURSEG_HOT_NODE] * c.blks_per_seg;
+	last_inode_pos = start_inode_pos +
+		c.cur_seg[CURSEG_HOT_NODE] * c.blks_per_seg + quota_inum;
 
 	if (c.zoned_mode)
 		return 0;
@@ -883,7 +957,7 @@
 		}
 		offset = next_blkaddr;
 		/* should avoid recursive chain due to stale data */
-		if (offset == root_inode_pos)
+		if (offset >= start_inode_pos || offset <= last_inode_pos)
 			break;
 	} while (1);
 
@@ -963,7 +1037,6 @@
 			c.blks_per_seg, main_area_node_seg_blk_offset);
 	if (dev_write_block(raw_node, main_area_node_seg_blk_offset)) {
 		MSG(1, "\tError: While writing the raw_node to disk!!!\n");
-		free(raw_node);
 		return -1;
 	}
 
@@ -974,10 +1047,161 @@
 
 #ifndef WITH_ANDROID
 	if (discard_obsolete_dnode(raw_node, main_area_node_seg_blk_offset)) {
-		free(raw_node);
 		return -1;
 	}
 #endif
+	return 0;
+}
+
+static int f2fs_write_default_quota(int qtype, unsigned int blkaddr)
+{
+	char *filebuf = calloc(F2FS_BLKSIZE, 2);
+	int file_magics[] = INITQMAGICS;
+	struct v2_disk_dqheader ddqheader;
+	struct v2_disk_dqinfo ddqinfo;
+	struct v2r1_disk_dqblk dqblk;
+
+	if (filebuf == NULL) {
+		MSG(1, "\tError: Calloc Failed for filebuf!!!\n");
+		return -1;
+	}
+
+	/* Write basic quota header */
+	ddqheader.dqh_magic = cpu_to_le32(file_magics[qtype]);
+	/* only support QF_VFSV1 */
+	ddqheader.dqh_version = cpu_to_le32(1);
+
+	memcpy(filebuf, &ddqheader, sizeof(ddqheader));
+
+	/* Fill Initial quota file content */
+	ddqinfo.dqi_bgrace = cpu_to_le32(MAX_DQ_TIME);
+	ddqinfo.dqi_igrace = cpu_to_le32(MAX_IQ_TIME);
+	ddqinfo.dqi_flags = cpu_to_le32(0);
+	ddqinfo.dqi_blocks = cpu_to_le32(QT_TREEOFF + 5);
+	ddqinfo.dqi_free_blk = cpu_to_le32(0);
+	ddqinfo.dqi_free_entry = cpu_to_le32(5);
+
+	memcpy(filebuf + V2_DQINFOOFF, &ddqinfo, sizeof(ddqinfo));
+
+	filebuf[1024] = 2;
+	filebuf[2048] = 3;
+	filebuf[3072] = 4;
+	filebuf[4096] = 5;
+
+	filebuf[5120 + 8] = 1;
+
+	dqblk.dqb_id = cpu_to_le32(0);
+	dqblk.dqb_pad = cpu_to_le32(0);
+	dqblk.dqb_ihardlimit = cpu_to_le64(0);
+	dqblk.dqb_isoftlimit = cpu_to_le64(0);
+	dqblk.dqb_curinodes = cpu_to_le64(1);
+	dqblk.dqb_bhardlimit = cpu_to_le64(0);
+	dqblk.dqb_bsoftlimit = cpu_to_le64(0);
+	dqblk.dqb_curspace = cpu_to_le64(4096);
+	dqblk.dqb_btime = cpu_to_le64(0);
+	dqblk.dqb_itime = cpu_to_le64(0);
+
+	memcpy(filebuf + 5136, &dqblk, sizeof(struct v2r1_disk_dqblk));
+
+	/* Write two blocks */
+	if (dev_write_block(filebuf, blkaddr) ||
+	    dev_write_block(filebuf + F2FS_BLKSIZE, blkaddr + 1)) {
+		MSG(1, "\tError: While writing the quota_blk to disk!!!\n");
+		free(filebuf);
+		return -1;
+	}
+
+	free(filebuf);
+	return 0;
+}
+
+static int f2fs_write_qf_inode(int qtype)
+{
+	struct f2fs_node *raw_node = NULL;
+	u_int64_t data_blk_nor;
+	u_int64_t main_area_node_seg_blk_offset = 0;
+	int i;
+
+	raw_node = calloc(F2FS_BLKSIZE, 1);
+	if (raw_node == NULL) {
+		MSG(1, "\tError: Calloc Failed for raw_node!!!\n");
+		return -1;
+	}
+
+	raw_node->footer.nid = sb->qf_ino[qtype];
+	raw_node->footer.ino = sb->qf_ino[qtype];
+	raw_node->footer.cp_ver = cpu_to_le64(1);
+	raw_node->footer.next_blkaddr = cpu_to_le32(
+			get_sb(main_blkaddr) +
+			c.cur_seg[CURSEG_HOT_NODE] *
+			c.blks_per_seg + 1 + qtype + 1);
+
+	raw_node->i.i_mode = cpu_to_le16(0x8180);
+	raw_node->i.i_links = cpu_to_le32(1);
+	raw_node->i.i_uid = cpu_to_le32(getuid());
+	raw_node->i.i_gid = cpu_to_le32(getgid());
+
+	raw_node->i.i_size = cpu_to_le64(1024 * 6); /* Hard coded */
+	raw_node->i.i_blocks = cpu_to_le64(1 + QUOTA_DATA(qtype));
+
+	raw_node->i.i_atime = cpu_to_le32(time(NULL));
+	raw_node->i.i_atime_nsec = 0;
+	raw_node->i.i_ctime = cpu_to_le32(time(NULL));
+	raw_node->i.i_ctime_nsec = 0;
+	raw_node->i.i_mtime = cpu_to_le32(time(NULL));
+	raw_node->i.i_mtime_nsec = 0;
+	raw_node->i.i_generation = 0;
+	raw_node->i.i_xattr_nid = 0;
+	raw_node->i.i_flags = FS_IMMUTABLE_FL;
+	raw_node->i.i_current_depth = cpu_to_le32(1);
+	raw_node->i.i_dir_level = DEF_DIR_LEVEL;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
+		raw_node->i.i_inline = F2FS_EXTRA_ATTR;
+		raw_node->i.i_extra_isize =
+				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
+	}
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_PRJQUOTA))
+		raw_node->i.i_projid = cpu_to_le32(F2FS_DEF_PROJID);
+
+	data_blk_nor = get_sb(main_blkaddr) +
+		c.cur_seg[CURSEG_HOT_DATA] * c.blks_per_seg + 1;
+
+	for (i = 0; i < qtype; i++)
+		if (sb->qf_ino[i])
+			data_blk_nor += QUOTA_DATA(i);
+
+	/* write two blocks */
+	if (f2fs_write_default_quota(qtype, data_blk_nor)) {
+		free(raw_node);
+		return -1;
+	}
+
+	for (i = 0; i < QUOTA_DATA(qtype); i++)
+		raw_node->i.i_addr[get_extra_isize(raw_node) + i] =
+					cpu_to_le32(data_blk_nor + i);
+	raw_node->i.i_ext.fofs = 0;
+	raw_node->i.i_ext.blk_addr = 0;
+	raw_node->i.i_ext.len = 0;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		raw_node->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(raw_node));
+
+	main_area_node_seg_blk_offset = get_sb(main_blkaddr);
+	main_area_node_seg_blk_offset += c.cur_seg[CURSEG_HOT_NODE] *
+					c.blks_per_seg + qtype + 1;
+
+	DBG(1, "\tWriting quota inode (hot node), %x %x %x at offset 0x%08"PRIu64"\n",
+			get_sb(main_blkaddr),
+			c.cur_seg[CURSEG_HOT_NODE],
+			c.blks_per_seg, main_area_node_seg_blk_offset);
+	if (dev_write_block(raw_node, main_area_node_seg_blk_offset)) {
+		MSG(1, "\tError: While writing the raw_node to disk!!!\n");
+		free(raw_node);
+		return -1;
+	}
 
 	free(raw_node);
 	return 0;
@@ -987,6 +1211,8 @@
 {
 	struct f2fs_nat_block *nat_blk = NULL;
 	u_int64_t nat_seg_blk_offset = 0;
+	enum quota_type qtype;
+	int i;
 
 	nat_blk = calloc(F2FS_BLKSIZE, 1);
 	if(nat_blk == NULL) {
@@ -994,6 +1220,18 @@
 		return -1;
 	}
 
+	/* update quota */
+	for (qtype = i = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		nat_blk->entries[sb->qf_ino[qtype]].block_addr =
+				cpu_to_le32(get_sb(main_blkaddr) +
+				c.cur_seg[CURSEG_HOT_NODE] *
+				c.blks_per_seg + i + 1);
+		nat_blk->entries[sb->qf_ino[qtype]].ino = sb->qf_ino[qtype];
+		i++;
+	}
+
 	/* update root */
 	nat_blk->entries[get_sb(root_ino)].block_addr = cpu_to_le32(
 		get_sb(main_blkaddr) +
@@ -1048,6 +1286,7 @@
 	/* bitmap for . and .. */
 	test_and_set_bit_le(0, dent_blk->dentry_bitmap);
 	test_and_set_bit_le(1, dent_blk->dentry_bitmap);
+
 	data_blk_offset = get_sb(main_blkaddr);
 	data_blk_offset += c.cur_seg[CURSEG_HOT_DATA] *
 				c.blks_per_seg;
@@ -1066,6 +1305,7 @@
 
 static int f2fs_create_root_dir(void)
 {
+	enum quota_type qtype;
 	int err = 0;
 
 	err = f2fs_write_root_inode();
@@ -1074,6 +1314,16 @@
 		goto exit;
 	}
 
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)  {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		err = f2fs_write_qf_inode(qtype);
+		if (err < 0) {
+			MSG(1, "\tError: Failed to write quota inode!!!\n");
+			goto exit;
+		}
+	}
+
 	err = f2fs_update_nat_root();
 	if (err < 0) {
 		MSG(1, "\tError: Failed to update NAT for root!!!\n");
diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c
index ef62777..50735d3 100644
--- a/mkfs/f2fs_format_main.c
+++ b/mkfs/f2fs_format_main.c
@@ -88,6 +88,8 @@
 		c.feature |= cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM);
 	} else if (!strcmp(features, "flexible_inline_xattr")) {
 		c.feature |= cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR);
+	} else if (!strcmp(features, "quota")) {
+		c.feature |= cpu_to_le32(F2FS_FEATURE_QUOTA_INO);
 	} else {
 		MSG(0, "Error: Wrong features\n");
 		mkfs_usage();