linux-f2fs-devel.lists.sourceforge.net archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/4] sload.f2fs: fix bugs in f2fs_write_block()
@ 2017-10-31  3:42 Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 2/4] f2fs-tools: f2fs_read() and f2fs_filesize_update() are added Jaegeuk Kim
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Jaegeuk Kim @ 2017-10-31  3:42 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

From: Hyojun Kim <hyojun@google.com>

Found and fixed following three bugs in f2fs_write_block() function.
 - Write (4096 - offset) bytes for the first block even for small count.
 - For overwriting, found blkaddr is not used for writing.
 - dn.idirty status can be lost by set_new_dnode().
 - missing inode_checksum

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
---
 fsck/segment.c | 36 +++++++++++++++++++++---------------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/fsck/segment.c b/fsck/segment.c
index d568d61..2ea5bf1 100644
--- a/fsck/segment.c
+++ b/fsck/segment.c
@@ -16,6 +16,14 @@
 #include "fsck.h"
 #include "node.h"
 
+static void write_inode(u64 blkaddr, struct f2fs_node *inode)
+{
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
+		inode->i.i_inode_checksum =
+			cpu_to_le32(f2fs_inode_chksum(inode));
+	ASSERT(dev_write_block(inode, blkaddr) >= 0);
+}
+
 void reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
 			struct f2fs_summary *sum, int type)
 {
@@ -71,6 +79,7 @@ static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
 	void *data_blk;
 	struct node_info ni;
 	struct f2fs_node *inode;
+	int idirty = 0;
 	int ret = -1;
 
 	get_node_info(sbi, ino, &ni);
@@ -86,6 +95,8 @@ static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
 
 	off_in_block = offset & ((1 << F2FS_BLKSIZE_BITS) - 1);
 	len_in_block = (1 << F2FS_BLKSIZE_BITS) - off_in_block;
+	if (len_in_block > count)
+		len_in_block = count;
 	len_already = 0;
 
 	/*
@@ -115,17 +126,20 @@ static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
 			blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
 
 			/* A new page from WARM_DATA */
-			if (blkaddr == NULL_ADDR)
+			if (blkaddr == NULL_ADDR) {
 				new_data_block(sbi, data_blk, &dn,
 							CURSEG_WARM_DATA);
+				blkaddr = dn.data_blkaddr;
+				idirty |= dn.idirty;
+			}
 
 			/* Copy data from buffer to file */
-			ret = dev_read_block(data_blk, dn.data_blkaddr);
+			ret = dev_read_block(data_blk, blkaddr);
 			ASSERT(ret >= 0);
 
 			memcpy(data_blk + off_in_block, buffer, len_in_block);
 
-			ret = dev_write_block(data_blk, dn.data_blkaddr);
+			ret = dev_write_block(data_blk, blkaddr);
 			ASSERT(ret >= 0);
 
 			off_in_block = 0;
@@ -148,13 +162,12 @@ static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
 	/* Update the inode info */
 	if (le64_to_cpu(inode->i.i_size) < offset + count) {
 		inode->i.i_size = cpu_to_le64(offset + count);
-		dn.idirty = 1;
+		idirty = 1;
 	}
 
-	if (dn.idirty) {
+	if (idirty) {
 		ASSERT(inode == dn.inode_blk);
-		ret = dev_write_block(inode, ni.blk_addr);
-		ASSERT(ret >= 0);
+		write_inode(ni.blk_addr, inode);
 	}
 
 	if (dn.node_blk && dn.node_blk != dn.inode_blk)
@@ -203,15 +216,8 @@ int f2fs_build_file(struct f2fs_sb_info *sbi, struct dentry *de)
 		n = read(fd, buffer, BLOCK_SZ);
 		ASSERT(n == de->size);
 		memcpy(inline_data_addr(node_blk), buffer, de->size);
-
 		node_blk->i.i_size = cpu_to_le64(de->size);
-
-		if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
-			node_blk->i.i_inode_checksum =
-				cpu_to_le32(f2fs_inode_chksum(node_blk));
-
-		ret = dev_write_block(node_blk, ni.blk_addr);
-		ASSERT(ret >= 0);
+		write_inode(ni.blk_addr, node_blk);
 		free(node_blk);
 	} else {
 		while ((n = read(fd, buffer, BLOCK_SZ)) > 0) {
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/4] f2fs-tools: f2fs_read() and f2fs_filesize_update() are added
  2017-10-31  3:42 [PATCH 1/4] sload.f2fs: fix bugs in f2fs_write_block() Jaegeuk Kim
@ 2017-10-31  3:42 ` Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 3/4] mkfs.f2fs: support quota option in mkfs Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 4/4] fsck.f2fs: support quota Jaegeuk Kim
  2 siblings, 0 replies; 4+ messages in thread
From: Jaegeuk Kim @ 2017-10-31  3:42 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

From: Hyojun Kim <hyojun@google.com>

This patch adds f2fs_read() and f2fs_filesize_update(). It also refactors
f2fs_write_block() and renamed as f2fs_write().

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
---
 fsck/fsck.h    |   6 ++
 fsck/segment.c | 248 ++++++++++++++++++++++++++++++++++++++-------------------
 2 files changed, 171 insertions(+), 83 deletions(-)

diff --git a/fsck/fsck.h b/fsck/fsck.h
index 1e8ed0b..5628906 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -219,6 +219,12 @@ void f2fs_alloc_nid(struct f2fs_sb_info *, nid_t *, int);
 void set_data_blkaddr(struct dnode_of_data *);
 block_t new_node_block(struct f2fs_sb_info *,
 					struct dnode_of_data *, unsigned int);
+
+/* segment.c */
+u64 f2fs_read(struct f2fs_sb_info *, nid_t, void *, u64, pgoff_t);
+u64 f2fs_write(struct f2fs_sb_info *, nid_t, void *, u64, pgoff_t);
+void f2fs_filesize_update(struct f2fs_sb_info *, nid_t, u64);
+
 void get_dnode_of_data(struct f2fs_sb_info *, struct dnode_of_data *,
 					pgoff_t, int);
 void make_dentry_ptr(struct f2fs_dentry_ptr *, struct f2fs_node *, void *, int);
diff --git a/fsck/segment.c b/fsck/segment.c
index 2ea5bf1..e13f147 100644
--- a/fsck/segment.c
+++ b/fsck/segment.c
@@ -68,111 +68,193 @@ void new_data_block(struct f2fs_sb_info *sbi, void *block,
 	set_data_blkaddr(dn);
 }
 
-static void f2fs_write_block(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
+u64 f2fs_read(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
 					u64 count, pgoff_t offset)
 {
-	u64 start = F2FS_BYTES_TO_BLK(offset);
-	u64 len = F2FS_BYTES_TO_BLK(count);
-	u64 end_offset;
-	u64 off_in_block, len_in_block, len_already;
-	struct dnode_of_data dn = {0};
-	void *data_blk;
+	struct dnode_of_data dn;
 	struct node_info ni;
 	struct f2fs_node *inode;
-	int idirty = 0;
-	int ret = -1;
-
+	char *blk_buffer;
+	u64 filesize;
+	u64 off_in_blk;
+	u64 len_in_blk;
+	u64 read_count;
+	u64 remained_blkentries;
+	block_t blkaddr;
+	void *index_node = NULL;
+
+	memset(&dn, 0, sizeof(dn));
+
+	/* Memory allocation for block buffer and inode. */
+	blk_buffer = calloc(BLOCK_SZ, 2);
+	ASSERT(blk_buffer);
+	inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
+
+	/* Read inode */
 	get_node_info(sbi, ino, &ni);
-	inode = calloc(BLOCK_SZ, 1);
-	ASSERT(inode);
-
-	ret = dev_read_block(inode, ni.blk_addr);
-	ASSERT(ret >= 0);
-
-	if (S_ISDIR(le16_to_cpu(inode->i.i_mode)) ||
-			S_ISLNK(le16_to_cpu(inode->i.i_mode)))
-		ASSERT(0);
-
-	off_in_block = offset & ((1 << F2FS_BLKSIZE_BITS) - 1);
-	len_in_block = (1 << F2FS_BLKSIZE_BITS) - off_in_block;
-	if (len_in_block > count)
-		len_in_block = count;
-	len_already = 0;
-
-	/*
-	 * When calculate how many blocks this 'count' stride accross,
-	 * We should take offset in a block in account.
-	 */
-	len = F2FS_BYTES_TO_BLK(count + off_in_block
-			+ ((1 << F2FS_BLKSIZE_BITS) - 1));
-
-	data_blk = calloc(BLOCK_SZ, 1);
-	ASSERT(data_blk);
-
-	set_new_dnode(&dn, inode, NULL, ino);
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
+
+	/* Adjust count with file length. */
+	filesize = le64_to_cpu(inode->i.i_size);
+	if (offset > filesize)
+		count = 0;
+	else if (count + offset > filesize)
+		count = filesize - offset;
+
+	/* Main loop for file blocks */
+	read_count = remained_blkentries = 0;
+	while (count > 0) {
+		if (remained_blkentries == 0) {
+			set_new_dnode(&dn, inode, NULL, ino);
+			get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
+					LOOKUP_NODE);
+			if (index_node)
+				free(index_node);
+			index_node = (dn.node_blk == dn.inode_blk) ?
+							NULL : dn.node_blk;
+			remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
+		}
+		ASSERT(remained_blkentries > 0);
+
+		blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
+		if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+			break;
+
+		off_in_blk = offset % BLOCK_SZ;
+		len_in_blk = BLOCK_SZ - off_in_blk;
+		if (len_in_blk > count)
+			len_in_blk = count;
+
+		/* Read data from single block. */
+		if (len_in_blk < BLOCK_SZ) {
+			ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
+			memcpy(buffer, blk_buffer + off_in_blk, len_in_blk);
+		} else {
+			/* Direct read */
+			ASSERT(dev_read_block(buffer, blkaddr) >= 0);
+		}
 
-	while (len) {
-		if (dn.node_blk != dn.inode_blk)
-			free(dn.node_blk);
+		offset += len_in_blk;
+		count -= len_in_blk;
+		buffer += len_in_blk;
+		read_count += len_in_blk;
 
-		set_new_dnode(&dn, inode, NULL, ino);
-		get_dnode_of_data(sbi, &dn, start, ALLOC_NODE);
+		dn.ofs_in_node++;
+		remained_blkentries--;
+	}
+	if (index_node)
+		free(index_node);
+	free(blk_buffer);
 
-		end_offset = ADDRS_PER_PAGE(dn.node_blk);
+	return read_count;
+}
 
-		while (dn.ofs_in_node < end_offset && len) {
-			block_t blkaddr;
+u64 f2fs_write(struct f2fs_sb_info *sbi, nid_t ino, void *buffer,
+					u64 count, pgoff_t offset)
+{
+	struct dnode_of_data dn;
+	struct node_info ni;
+	struct f2fs_node *inode;
+	char *blk_buffer;
+	u64 off_in_blk;
+	u64 len_in_blk;
+	u64 written_count;
+	u64 remained_blkentries;
+	block_t blkaddr;
+	void* index_node = NULL;
+	int idirty = 0;
 
-			blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
+	/* Memory allocation for block buffer and inode. */
+	blk_buffer = calloc(BLOCK_SZ, 2);
+	ASSERT(blk_buffer);
+	inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
 
-			/* A new page from WARM_DATA */
-			if (blkaddr == NULL_ADDR) {
-				new_data_block(sbi, data_blk, &dn,
-							CURSEG_WARM_DATA);
-				blkaddr = dn.data_blkaddr;
-				idirty |= dn.idirty;
-			}
+	/* Read inode */
+	get_node_info(sbi, ino, &ni);
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
+
+	/* Main loop for file blocks */
+	written_count = remained_blkentries = 0;
+	while (count > 0) {
+		if (remained_blkentries == 0) {
+			set_new_dnode(&dn, inode, NULL, ino);
+			get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
+					ALLOC_NODE);
+			idirty |= dn.idirty;
+			if (index_node)
+				free(index_node);
+			index_node = (dn.node_blk == dn.inode_blk) ?
+							NULL : dn.node_blk;
+			remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
+		}
+		ASSERT(remained_blkentries > 0);
 
-			/* Copy data from buffer to file */
-			ret = dev_read_block(data_blk, blkaddr);
-			ASSERT(ret >= 0);
+		blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
+		if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR) {
+			new_data_block(sbi, blk_buffer, &dn, CURSEG_WARM_DATA);
+			blkaddr = dn.data_blkaddr;
+		}
 
-			memcpy(data_blk + off_in_block, buffer, len_in_block);
+		off_in_blk = offset % BLOCK_SZ;
+		len_in_blk = BLOCK_SZ - off_in_blk;
+		if (len_in_blk > count)
+			len_in_blk = count;
+
+		/* Write data to single block. */
+		if (len_in_blk < BLOCK_SZ) {
+			ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
+			memcpy(blk_buffer + off_in_blk, buffer, len_in_blk);
+			ASSERT(dev_write_block(blk_buffer, blkaddr) >= 0);
+		} else {
+			/* Direct write */
+			ASSERT(dev_write_block(buffer, blkaddr) >= 0);
+		}
 
-			ret = dev_write_block(data_blk, blkaddr);
-			ASSERT(ret >= 0);
+		offset += len_in_blk;
+		count -= len_in_blk;
+		buffer += len_in_blk;
+		written_count += len_in_blk;
 
-			off_in_block = 0;
-			len_already += len_in_block;
-			if ((count - len_already) > (1 << F2FS_BLKSIZE_BITS))
-				len_in_block = 1 << F2FS_BLKSIZE_BITS;
-			else
-				len_in_block = count - len_already;
-			len--;
-			start++;
-			dn.ofs_in_node++;
-		}
-		/* Update the direct node */
-		if (dn.ndirty) {
-			ret = dev_write_block(dn.node_blk, dn.node_blkaddr);
-			ASSERT(ret >= 0);
-		}
+		dn.ofs_in_node++;
+		if ((--remained_blkentries == 0 || count == 0) && (dn.ndirty))
+			ASSERT(dev_write_block(dn.node_blk, dn.node_blkaddr) >= 0);
 	}
-
-	/* Update the inode info */
-	if (le64_to_cpu(inode->i.i_size) < offset + count) {
-		inode->i.i_size = cpu_to_le64(offset + count);
+	if (offset > le64_to_cpu(inode->i.i_size)) {
+		inode->i.i_size = cpu_to_le64(offset);
 		idirty = 1;
 	}
-
 	if (idirty) {
 		ASSERT(inode == dn.inode_blk);
 		write_inode(ni.blk_addr, inode);
 	}
+	if (index_node)
+		free(index_node);
+	free(blk_buffer);
+
+	return written_count;
+}
+
+/* This function updates only inode->i.i_size */
+void f2fs_filesize_update(struct f2fs_sb_info *sbi, nid_t ino, u64 filesize)
+{
+	struct node_info ni;
+	struct f2fs_node *inode;
+
+	inode = calloc(BLOCK_SZ, 1);
+	ASSERT(inode);
+	get_node_info(sbi, ino, &ni);
+
+	ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
+	ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
+	ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
+
+	inode->i.i_size = cpu_to_le64(filesize);
 
-	if (dn.node_blk && dn.node_blk != dn.inode_blk)
-		free(dn.node_blk);
-	free(data_blk);
+	write_inode(ni.blk_addr, inode);
 	free(inode);
 }
 
@@ -221,7 +303,7 @@ int f2fs_build_file(struct f2fs_sb_info *sbi, struct dentry *de)
 		free(node_blk);
 	} else {
 		while ((n = read(fd, buffer, BLOCK_SZ)) > 0) {
-			f2fs_write_block(sbi, de->ino, buffer, n, off);
+			f2fs_write(sbi, de->ino, buffer, n, off);
 			off += n;
 		}
 	}
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/4] mkfs.f2fs: support quota option in mkfs
  2017-10-31  3:42 [PATCH 1/4] sload.f2fs: fix bugs in f2fs_write_block() Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 2/4] f2fs-tools: f2fs_read() and f2fs_filesize_update() are added Jaegeuk Kim
@ 2017-10-31  3:42 ` Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 4/4] fsck.f2fs: support quota Jaegeuk Kim
  2 siblings, 0 replies; 4+ messages in thread
From: Jaegeuk Kim @ 2017-10-31  3:42 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

From: Hyojun Kim <hyojun@google.com>

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>
---
 include/f2fs_fs.h       |  21 +++-
 include/quota.h         |  77 ++++++++++++++
 mkfs/f2fs_format.c      | 278 +++++++++++++++++++++++++++++++++++++++++++++---
 mkfs/f2fs_format_main.c |   2 +
 4 files changed, 363 insertions(+), 15 deletions(-)
 create mode 100644 include/quota.h

diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index 9f56388..d26b7cf 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -458,6 +458,13 @@ enum {
 #define F2FS_NODE_INO(sbi)	(sbi->node_ino_num)
 #define F2FS_META_INO(sbi)	(sbi->meta_ino_num)
 
+#define F2FS_QUOTA_INO		3
+#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 +485,7 @@ enum {
 #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 +536,8 @@ struct f2fs_super_block {
 	__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 +1180,14 @@ static inline __le64 get_cp_crc(struct f2fs_checkpoint *cp)
 	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 @@ struct f2fs_checkpoint *cp;
 #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 @@ static int f2fs_prepare_super_block(void)
 	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 @@ static int f2fs_prepare_super_block(void)
 	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 @@ static int f2fs_write_check_point_pack(void)
 	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 @@ static int f2fs_write_check_point_pack(void)
 		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 @@ static int f2fs_write_check_point_pack(void)
 
 	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 @@ static int f2fs_write_check_point_pack(void)
 	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 @@ static int f2fs_write_check_point_pack(void)
 			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 @@ static int f2fs_write_check_point_pack(void)
 	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 @@ static int f2fs_write_check_point_pack(void)
 	/* 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 @@ static int f2fs_write_check_point_pack(void)
 	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 @@ static int f2fs_write_check_point_pack(void)
 
 	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 f2fs_write_super_block(void)
 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 @@ static int discard_obsolete_dnode(struct f2fs_node *raw_node, u_int64_t offset)
 		}
 		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 @@ static int f2fs_write_root_inode(void)
 			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 @@ static int f2fs_write_root_inode(void)
 
 #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 @@ static int f2fs_update_nat_root(void)
 {
 	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 @@ static int f2fs_update_nat_root(void)
 		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 @@ static int f2fs_add_default_dentry_root(void)
 	/* 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_add_default_dentry_root(void)
 
 static int f2fs_create_root_dir(void)
 {
+	enum quota_type qtype;
 	int err = 0;
 
 	err = f2fs_write_root_inode();
@@ -1074,6 +1314,16 @@ static int f2fs_create_root_dir(void)
 		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 @@ static void parse_feature(const char *features)
 		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();
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 4/4] fsck.f2fs: support quota
  2017-10-31  3:42 [PATCH 1/4] sload.f2fs: fix bugs in f2fs_write_block() Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 2/4] f2fs-tools: f2fs_read() and f2fs_filesize_update() are added Jaegeuk Kim
  2017-10-31  3:42 ` [PATCH 3/4] mkfs.f2fs: support quota option in mkfs Jaegeuk Kim
@ 2017-10-31  3:42 ` Jaegeuk Kim
  2 siblings, 0 replies; 4+ messages in thread
From: Jaegeuk Kim @ 2017-10-31  3:42 UTC (permalink / raw)
  To: linux-f2fs-devel; +Cc: Hyojun Kim, Jaegeuk Kim

From: Hyojun Kim <hyojun@google.com>

This patch let fsck to check and fix quota file contents.

Signed-off-by: Hyojun Kim <hyojun@google.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@google.com>
---
 fsck/Makefile.am    |    3 +-
 fsck/common.h       |   30 +
 fsck/dict.c         | 1501 +++++++++++++++++++++++++++++++++++++++++++++++++++
 fsck/dict.h         |  144 +++++
 fsck/dqblk_v2.h     |   31 ++
 fsck/fsck.c         |  105 +++-
 fsck/fsck.h         |   10 +
 fsck/main.c         |   18 +-
 fsck/mkquota.c      |  403 ++++++++++++++
 fsck/mount.c        |   15 +-
 fsck/quotaio.c      |  221 ++++++++
 fsck/quotaio.h      |  255 +++++++++
 fsck/quotaio_tree.c |  679 +++++++++++++++++++++++
 fsck/quotaio_tree.h |   66 +++
 fsck/quotaio_v2.c   |  284 ++++++++++
 fsck/quotaio_v2.h   |   54 ++
 fsck/segment.c      |   22 +-
 include/f2fs_fs.h   |   19 +
 mkfs/f2fs_format.c  |   10 +-
 19 files changed, 3852 insertions(+), 18 deletions(-)
 create mode 100644 fsck/common.h
 create mode 100644 fsck/dict.c
 create mode 100644 fsck/dict.h
 create mode 100644 fsck/dqblk_v2.h
 create mode 100644 fsck/mkquota.c
 create mode 100644 fsck/quotaio.c
 create mode 100644 fsck/quotaio.h
 create mode 100644 fsck/quotaio_tree.c
 create mode 100644 fsck/quotaio_tree.h
 create mode 100644 fsck/quotaio_v2.c
 create mode 100644 fsck/quotaio_v2.h

diff --git a/fsck/Makefile.am b/fsck/Makefile.am
index 7abcd00..cf0f7d1 100644
--- a/fsck/Makefile.am
+++ b/fsck/Makefile.am
@@ -5,7 +5,8 @@ AM_CFLAGS = -Wall
 sbin_PROGRAMS = fsck.f2fs
 fsck_f2fs_SOURCES = main.c fsck.c dump.c mount.c defrag.c f2fs.h fsck.h $(top_srcdir)/include/f2fs_fs.h	\
 		resize.c										\
-		node.c segment.c dir.c sload.c xattr.c
+		node.c segment.c dir.c sload.c xattr.c \
+		dict.c mkquota.c quotaio.c quotaio_tree.c quotaio_v2.c
 fsck_f2fs_LDADD = ${libselinux_LIBS} ${libuuid_LIBS} $(top_builddir)/lib/libf2fs.la
 
 install-data-hook:
diff --git a/fsck/common.h b/fsck/common.h
new file mode 100644
index 0000000..19a6ecc
--- /dev/null
+++ b/fsck/common.h
@@ -0,0 +1,30 @@
+/**
+ *
+ * Various things common for all utilities
+ *
+ */
+
+#ifndef __QUOTA_COMMON_H__
+#define __QUOTA_COMMON_H__
+
+#undef DEBUG_QUOTA
+
+#ifndef __attribute__
+# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
+#  define __attribute__(x)
+# endif
+#endif
+
+#define log_err(format, arg ...)					\
+	fprintf(stderr, "[ERROR] %s:%d:%s:: " format "\n",		\
+		__FILE__, __LINE__, __func__, ## arg)
+
+#ifdef DEBUG_QUOTA
+# define log_debug(format, arg ...)					\
+	fprintf(stderr, "[DEBUG] %s:%d:%s:: " format "\n",		\
+		__FILE__, __LINE__, __func__, ## arg)
+#else
+# define log_debug(...)
+#endif
+
+#endif /* __QUOTA_COMMON_H__ */
diff --git a/fsck/dict.c b/fsck/dict.c
new file mode 100644
index 0000000..bb7600c
--- /dev/null
+++ b/fsck/dict.c
@@ -0,0 +1,1501 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ */
+
+#define DICT_NODEBUG
+
+#include "config.h"
+#include <stdlib.h>
+#include <stddef.h>
+#ifdef DICT_NODEBUG
+#define dict_assert(x)
+#else
+#include <assert.h>
+#define dict_assert(x) assert(x)
+#endif
+#define DICT_IMPLEMENTATION
+#include "dict.h"
+#include <f2fs_fs.h>
+
+#ifdef KAZLIB_RCSID
+static const char rcsid[] = "$Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $";
+#endif
+
+/*
+ * These macros provide short convenient names for structure members,
+ * which are embellished with dict_ prefixes so that they are
+ * properly confined to the documented namespace. It's legal for a
+ * program which uses dict to define, for instance, a macro called ``parent''.
+ * Such a macro would interfere with the dnode_t struct definition.
+ * In general, highly portable and reusable C modules which expose their
+ * structures need to confine structure member names to well-defined spaces.
+ * The resulting identifiers aren't necessarily convenient to use, nor
+ * readable, in the implementation, however!
+ */
+
+#define left dict_left
+#define right dict_right
+#define parent dict_parent
+#define color dict_color
+#define key dict_key
+#define data dict_data
+
+#define nilnode dict_nilnode
+#define nodecount dict_nodecount
+#define maxcount dict_maxcount
+#define compare dict_compare
+#define allocnode dict_allocnode
+#define freenode dict_freenode
+#define context dict_context
+#define dupes dict_dupes
+
+#define dictptr dict_dictptr
+
+#define dict_root(D) ((D)->nilnode.left)
+#define dict_nil(D) (&(D)->nilnode)
+#define DICT_DEPTH_MAX 64
+
+static dnode_t *dnode_alloc(void *context);
+static void dnode_free(dnode_t *node, void *context);
+
+/*
+ * Perform a ``left rotation'' adjustment on the tree.  The given node P and
+ * its right child C are rearranged so that the P instead becomes the left
+ * child of C.   The left subtree of C is inherited as the new right subtree
+ * for P.  The ordering of the keys within the tree is thus preserved.
+ */
+static void rotate_left(dnode_t *upper)
+{
+	dnode_t *lower, *lowleft, *upparent;
+
+	lower = upper->right;
+	upper->right = lowleft = lower->left;
+	lowleft->parent = upper;
+
+	lower->parent = upparent = upper->parent;
+
+	/* don't need to check for root node here because root->parent is
+	   the sentinel nil node, and root->parent->left points back to root */
+
+	if (upper == upparent->left) {
+		upparent->left = lower;
+	} else {
+		dict_assert(upper == upparent->right);
+		upparent->right = lower;
+	}
+
+	lower->left = upper;
+	upper->parent = lower;
+}
+
+/*
+ * This operation is the ``mirror'' image of rotate_left. It is
+ * the same procedure, but with left and right interchanged.
+ */
+static void rotate_right(dnode_t *upper)
+{
+	dnode_t *lower, *lowright, *upparent;
+
+	lower = upper->left;
+	upper->left = lowright = lower->right;
+	lowright->parent = upper;
+
+	lower->parent = upparent = upper->parent;
+
+	if (upper == upparent->right) {
+		upparent->right = lower;
+	} else {
+		dict_assert(upper == upparent->left);
+		upparent->left = lower;
+	}
+
+	lower->right = upper;
+	upper->parent = lower;
+}
+
+/*
+ * Do a postorder traversal of the tree rooted at the specified
+ * node and free everything under it.  Used by dict_free().
+ */
+static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil)
+{
+	if (node == nil)
+		return;
+	free_nodes(dict, node->left, nil);
+	free_nodes(dict, node->right, nil);
+	dict->freenode(node, dict->context);
+}
+
+/*
+ * This procedure performs a verification that the given subtree is a binary
+ * search tree. It performs an inorder traversal of the tree using the
+ * dict_next() successor function, verifying that the key of each node is
+ * strictly lower than that of its successor, if duplicates are not allowed,
+ * or lower or equal if duplicates are allowed.  This function is used for
+ * debugging purposes.
+ */
+#ifndef DICT_NODEBUG
+static int verify_bintree(dict_t *dict)
+{
+	dnode_t *first, *next;
+
+	first = dict_first(dict);
+
+	if (dict->dupes) {
+		while (first && (next = dict_next(dict, first))) {
+			if (dict->compare(first->key, next->key) > 0)
+				return 0;
+			first = next;
+		}
+	} else {
+		while (first && (next = dict_next(dict, first))) {
+			if (dict->compare(first->key, next->key) >= 0)
+				return 0;
+			first = next;
+		}
+	}
+	return 1;
+}
+
+/*
+ * This function recursively verifies that the given binary subtree satisfies
+ * three of the red black properties. It checks that every red node has only
+ * black children. It makes sure that each node is either red or black. And it
+ * checks that every path has the same count of black nodes from root to leaf.
+ * It returns the blackheight of the given subtree; this allows blackheights to
+ * be computed recursively and compared for left and right siblings for
+ * mismatches. It does not check for every nil node being black, because there
+ * is only one sentinel nil node. The return value of this function is the
+ * black height of the subtree rooted at the node ``root'', or zero if the
+ * subtree is not red-black.
+ */
+static unsigned int verify_redblack(dnode_t *nil, dnode_t *root)
+{
+	unsigned height_left, height_right;
+
+	if (root != nil) {
+		height_left = verify_redblack(nil, root->left);
+		height_right = verify_redblack(nil, root->right);
+		if (height_left == 0 || height_right == 0)
+			return 0;
+		if (height_left != height_right)
+			return 0;
+		if (root->color == dnode_red) {
+			if (root->left->color != dnode_black)
+				return 0;
+			if (root->right->color != dnode_black)
+				return 0;
+			return height_left;
+		}
+		if (root->color != dnode_black)
+			return 0;
+		return height_left + 1;
+	}
+	return 1;
+}
+
+/*
+ * Compute the actual count of nodes by traversing the tree and
+ * return it. This could be compared against the stored count to
+ * detect a mismatch.
+ */
+static dictcount_t verify_node_count(dnode_t *nil, dnode_t *root)
+{
+	if (root == nil)
+		return 0;
+	else
+		return 1 + verify_node_count(nil, root->left)
+			+ verify_node_count(nil, root->right);
+}
+#endif
+
+/*
+ * Verify that the tree contains the given node. This is done by
+ * traversing all of the nodes and comparing their pointers to the
+ * given pointer. Returns 1 if the node is found, otherwise
+ * returns zero. It is intended for debugging purposes.
+ */
+static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node)
+{
+	if (root != nil) {
+		return root == node
+			|| verify_dict_has_node(nil, root->left, node)
+			|| verify_dict_has_node(nil, root->right, node);
+	}
+	return 0;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Dynamically allocate and initialize a dictionary object.
+ */
+dict_t *dict_create(dictcount_t maxcount, dict_comp_t comp)
+{
+	dict_t *new = malloc(sizeof *new);
+
+	if (new) {
+		new->compare = comp;
+		new->allocnode = dnode_alloc;
+		new->freenode = dnode_free;
+		new->context = NULL;
+		new->nodecount = 0;
+		new->maxcount = maxcount;
+		new->nilnode.left = &new->nilnode;
+		new->nilnode.right = &new->nilnode;
+		new->nilnode.parent = &new->nilnode;
+		new->nilnode.color = dnode_black;
+		new->dupes = 0;
+	}
+	return new;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Select a different set of node allocator routines.
+ */
+void dict_set_allocator(dict_t *dict, dnode_alloc_t al,
+		dnode_free_t fr, void *context)
+{
+	dict_assert(dict_count(dict) == 0);
+	dict_assert((al == NULL && fr == NULL) || (al != NULL && fr != NULL));
+
+	dict->allocnode = al ? al : dnode_alloc;
+	dict->freenode = fr ? fr : dnode_free;
+	dict->context = context;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Free a dynamically allocated dictionary object. Removing the nodes
+ * from the tree before deleting it is required.
+ */
+void dict_destroy(dict_t *dict)
+{
+	dict_assert(dict_isempty(dict));
+	free(dict);
+}
+#endif
+
+/*
+ * Free all the nodes in the dictionary by using the dictionary's
+ * installed free routine. The dictionary is emptied.
+ */
+void dict_free_nodes(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+	free_nodes(dict, root, nil);
+	dict->nodecount = 0;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Obsolescent function, equivalent to dict_free_nodes
+ */
+void dict_free(dict_t *dict)
+{
+#ifdef KAZLIB_OBSOLESCENT_DEBUG
+	dict_assert("call to obsolescent function dict_free()" && 0);
+#endif
+	dict_free_nodes(dict);
+}
+#endif
+
+/*
+ * Initialize a user-supplied dictionary object.
+ */
+dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp)
+{
+	dict->compare = comp;
+	dict->allocnode = dnode_alloc;
+	dict->freenode = dnode_free;
+	dict->context = NULL;
+	dict->nodecount = 0;
+	dict->maxcount = maxcount;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict->nilnode.color = dnode_black;
+	dict->dupes = 0;
+	return dict;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Initialize a dictionary in the likeness of another dictionary
+ */
+void dict_init_like(dict_t *dict, const dict_t *template)
+{
+	dict->compare = template->compare;
+	dict->allocnode = template->allocnode;
+	dict->freenode = template->freenode;
+	dict->context = template->context;
+	dict->nodecount = 0;
+	dict->maxcount = template->maxcount;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict->nilnode.color = dnode_black;
+	dict->dupes = template->dupes;
+
+	dict_assert(dict_similar(dict, template));
+}
+
+/*
+ * Remove all nodes from the dictionary (without freeing them in any way).
+ */
+static void dict_clear(dict_t *dict)
+{
+	dict->nodecount = 0;
+	dict->nilnode.left = &dict->nilnode;
+	dict->nilnode.right = &dict->nilnode;
+	dict->nilnode.parent = &dict->nilnode;
+	dict_assert(dict->nilnode.color == dnode_black);
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Verify the integrity of the dictionary structure.  This is provided for
+ * debugging purposes, and should be placed in assert statements.   Just because
+ * this function succeeds doesn't mean that the tree is not corrupt. Certain
+ * corruptions in the tree may simply cause undefined behavior.
+ */
+#ifndef DICT_NODEBUG
+int dict_verify(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+
+	/* check that the sentinel node and root node are black */
+	if (root->color != dnode_black)
+		return 0;
+	if (nil->color != dnode_black)
+		return 0;
+	if (nil->right != nil)
+		return 0;
+	/* nil->left is the root node; check that its parent pointer is nil */
+	if (nil->left->parent != nil)
+		return 0;
+	/* perform a weak test that the tree is a binary search tree */
+	if (!verify_bintree(dict))
+		return 0;
+	/* verify that the tree is a red-black tree */
+	if (!verify_redblack(nil, root))
+		return 0;
+	if (verify_node_count(nil, root) != dict_count(dict))
+		return 0;
+	return 1;
+}
+#endif /* DICT_NODEBUG */
+
+#ifdef FSCK_NOTUSED
+/*
+ * Determine whether two dictionaries are similar: have the same comparison and
+ * allocator functions, and same status as to whether duplicates are allowed.
+ */
+int dict_similar(const dict_t *left, const dict_t *right)
+{
+	if (left->compare != right->compare)
+		return 0;
+
+	if (left->allocnode != right->allocnode)
+		return 0;
+
+	if (left->freenode != right->freenode)
+		return 0;
+
+	if (left->context != right->context)
+		return 0;
+
+	if (left->dupes != right->dupes)
+		return 0;
+
+	return 1;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Locate a node in the dictionary having the given key.
+ * If the node is not found, a null a pointer is returned (rather than
+ * a pointer that dictionary's nil sentinel node), otherwise a pointer to the
+ * located node is returned.
+ */
+dnode_t *dict_lookup(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *saved;
+	int result;
+
+	/* simple binary search adapted for trees that contain duplicate keys */
+
+	while (root != nil) {
+		result = dict->compare(key, root->key);
+		if (result < 0)
+			root = root->left;
+		else if (result > 0)
+			root = root->right;
+		else {
+			if (!dict->dupes) {	/* no duplicates, return match		*/
+				return root;
+			} else {		/* could be dupes, find leftmost one	*/
+				do {
+					saved = root;
+					root = root->left;
+					while (root != nil && dict->compare(key, root->key))
+						root = root->right;
+				} while (root != nil);
+				return saved;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Look for the node corresponding to the lowest key that is equal to or
+ * greater than the given key.  If there is no such node, return null.
+ */
+dnode_t *dict_lower_bound(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *tentative = 0;
+
+	while (root != nil) {
+		int result = dict->compare(key, root->key);
+
+		if (result > 0) {
+			root = root->right;
+		} else if (result < 0) {
+			tentative = root;
+			root = root->left;
+		} else {
+			if (!dict->dupes) {
+				return root;
+			} else {
+				tentative = root;
+				root = root->left;
+			}
+		}
+	}
+
+	return tentative;
+}
+
+/*
+ * Look for the node corresponding to the greatest key that is equal to or
+ * lower than the given key.  If there is no such node, return null.
+ */
+dnode_t *dict_upper_bound(dict_t *dict, const void *key)
+{
+	dnode_t *root = dict_root(dict);
+	dnode_t *nil = dict_nil(dict);
+	dnode_t *tentative = 0;
+
+	while (root != nil) {
+		int result = dict->compare(key, root->key);
+
+		if (result < 0) {
+			root = root->left;
+		} else if (result > 0) {
+			tentative = root;
+			root = root->right;
+		} else {
+			if (!dict->dupes) {
+				return root;
+			} else {
+				tentative = root;
+				root = root->right;
+			}
+		}
+	}
+
+	return tentative;
+}
+#endif
+
+/*
+ * Insert a node into the dictionary. The node should have been
+ * initialized with a data field. All other fields are ignored.
+ * The behavior is undefined if the user attempts to insert into
+ * a dictionary that is already full (for which the dict_isfull()
+ * function returns true).
+ */
+void dict_insert(dict_t *dict, dnode_t *node, const void *key)
+{
+	dnode_t *where = dict_root(dict), *nil = dict_nil(dict);
+	dnode_t *parent = nil, *uncle, *grandpa;
+	int result = -1;
+
+	node->key = key;
+
+	dict_assert(!dict_isfull(dict));
+	dict_assert(!dict_contains(dict, node));
+	dict_assert(!dnode_is_in_a_dict(node));
+
+	/* basic binary tree insert */
+
+	while (where != nil) {
+		parent = where;
+		result = dict->compare(key, where->key);
+		/* trap attempts at duplicate key insertion unless it's explicitly allowed */
+		dict_assert(dict->dupes || result != 0);
+		if (result < 0)
+			where = where->left;
+		else
+			where = where->right;
+	}
+
+	dict_assert(where == nil);
+
+	if (result < 0)
+		parent->left = node;
+	else
+		parent->right = node;
+
+	node->parent = parent;
+	node->left = nil;
+	node->right = nil;
+
+	dict->nodecount++;
+
+	/* red black adjustments */
+
+	node->color = dnode_red;
+
+	while (parent->color == dnode_red) {
+		grandpa = parent->parent;
+		if (parent == grandpa->left) {
+			uncle = grandpa->right;
+			if (uncle->color == dnode_red) {	/* red parent, red uncle */
+				parent->color = dnode_black;
+				uncle->color = dnode_black;
+				grandpa->color = dnode_red;
+				node = grandpa;
+				parent = grandpa->parent;
+			} else {				/* red parent, black uncle */
+				if (node == parent->right) {
+					rotate_left(parent);
+					parent = node;
+					dict_assert(grandpa == parent->parent);
+					/* rotation between parent and child preserves grandpa */
+				}
+				parent->color = dnode_black;
+				grandpa->color = dnode_red;
+				rotate_right(grandpa);
+				break;
+			}
+		} else { 	/* symmetric cases: parent == parent->parent->right */
+			uncle = grandpa->left;
+			if (uncle->color == dnode_red) {
+				parent->color = dnode_black;
+				uncle->color = dnode_black;
+				grandpa->color = dnode_red;
+				node = grandpa;
+				parent = grandpa->parent;
+			} else {
+				if (node == parent->left) {
+					rotate_right(parent);
+					parent = node;
+					dict_assert(grandpa == parent->parent);
+				}
+				parent->color = dnode_black;
+				grandpa->color = dnode_red;
+				rotate_left(grandpa);
+				break;
+			}
+		}
+	}
+
+	dict_root(dict)->color = dnode_black;
+
+	dict_assert(dict_verify(dict));
+}
+
+#ifdef FSCK_NOTUSED
+/*
+ * Delete the given node from the dictionary. If the given node does not belong
+ * to the given dictionary, undefined behavior results.  A pointer to the
+ * deleted node is returned.
+ */
+dnode_t *dict_delete(dict_t *dict, dnode_t *delete)
+{
+	dnode_t *nil = dict_nil(dict), *child, *delparent = delete->parent;
+
+	/* basic deletion */
+
+	dict_assert(!dict_isempty(dict));
+	dict_assert(dict_contains(dict, delete));
+
+	/*
+	 * If the node being deleted has two children, then we replace it with its
+	 * successor (i.e. the leftmost node in the right subtree.) By doing this,
+	 * we avoid the traditional algorithm under which the successor's key and
+	 * value *only* move to the deleted node and the successor is spliced out
+	 * from the tree. We cannot use this approach because the user may hold
+	 * pointers to the successor, or nodes may be inextricably tied to some
+	 * other structures by way of embedding, etc. So we must splice out the
+	 * node we are given, not some other node, and must not move contents from
+	 * one node to another behind the user's back.
+	 */
+
+	if (delete->left != nil && delete->right != nil) {
+		dnode_t *next = dict_next(dict, delete);
+		dnode_t *nextparent = next->parent;
+		dnode_color_t nextcolor = next->color;
+
+		dict_assert(next != nil);
+		dict_assert(next->parent != nil);
+		dict_assert(next->left == nil);
+
+		/*
+		 * First, splice out the successor from the tree completely, by
+		 * moving up its right child into its place.
+		 */
+
+		child = next->right;
+		child->parent = nextparent;
+
+		if (nextparent->left == next) {
+			nextparent->left = child;
+		} else {
+			dict_assert(nextparent->right == next);
+			nextparent->right = child;
+		}
+
+		/*
+		 * Now that the successor has been extricated from the tree, install it
+		 * in place of the node that we want deleted.
+		 */
+
+		next->parent = delparent;
+		next->left = delete->left;
+		next->right = delete->right;
+		next->left->parent = next;
+		next->right->parent = next;
+		next->color = delete->color;
+		delete->color = nextcolor;
+
+		if (delparent->left == delete) {
+			delparent->left = next;
+		} else {
+			dict_assert(delparent->right == delete);
+			delparent->right = next;
+		}
+
+	} else {
+		dict_assert(delete != nil);
+		dict_assert(delete->left == nil || delete->right == nil);
+
+		child = (delete->left != nil) ? delete->left : delete->right;
+
+		child->parent = delparent = delete->parent;
+
+		if (delete == delparent->left) {
+			delparent->left = child;
+		} else {
+			dict_assert(delete == delparent->right);
+			delparent->right = child;
+		}
+	}
+
+	delete->parent = NULL;
+	delete->right = NULL;
+	delete->left = NULL;
+
+	dict->nodecount--;
+
+	dict_assert(verify_bintree(dict));
+
+	/* red-black adjustments */
+
+	if (delete->color == dnode_black) {
+		dnode_t *parent, *sister;
+
+		dict_root(dict)->color = dnode_red;
+
+		while (child->color == dnode_black) {
+			parent = child->parent;
+			if (child == parent->left) {
+				sister = parent->right;
+				dict_assert(sister != nil);
+				if (sister->color == dnode_red) {
+					sister->color = dnode_black;
+					parent->color = dnode_red;
+					rotate_left(parent);
+					sister = parent->right;
+					dict_assert(sister != nil);
+				}
+				if (sister->left->color == dnode_black
+						&& sister->right->color == dnode_black) {
+					sister->color = dnode_red;
+					child = parent;
+				} else {
+					if (sister->right->color == dnode_black) {
+						dict_assert(sister->left->color == dnode_red);
+						sister->left->color = dnode_black;
+						sister->color = dnode_red;
+						rotate_right(sister);
+						sister = parent->right;
+						dict_assert(sister != nil);
+					}
+					sister->color = parent->color;
+					sister->right->color = dnode_black;
+					parent->color = dnode_black;
+					rotate_left(parent);
+					break;
+				}
+			} else {	/* symmetric case: child == child->parent->right */
+				dict_assert(child == parent->right);
+				sister = parent->left;
+				dict_assert(sister != nil);
+				if (sister->color == dnode_red) {
+					sister->color = dnode_black;
+					parent->color = dnode_red;
+					rotate_right(parent);
+					sister = parent->left;
+					dict_assert(sister != nil);
+				}
+				if (sister->right->color == dnode_black
+						&& sister->left->color == dnode_black) {
+					sister->color = dnode_red;
+					child = parent;
+				} else {
+					if (sister->left->color == dnode_black) {
+						dict_assert(sister->right->color == dnode_red);
+						sister->right->color = dnode_black;
+						sister->color = dnode_red;
+						rotate_left(sister);
+						sister = parent->left;
+						dict_assert(sister != nil);
+					}
+					sister->color = parent->color;
+					sister->left->color = dnode_black;
+					parent->color = dnode_black;
+					rotate_right(parent);
+					break;
+				}
+			}
+		}
+
+		child->color = dnode_black;
+		dict_root(dict)->color = dnode_black;
+	}
+
+	dict_assert(dict_verify(dict));
+
+	return delete;
+}
+#endif /* FSCK_NOTUSED */
+
+/*
+ * Allocate a node using the dictionary's allocator routine, give it
+ * the data item.
+ */
+int dict_alloc_insert(dict_t *dict, const void *key, void *data)
+{
+	dnode_t *node = dict->allocnode(dict->context);
+
+	if (node) {
+		dnode_init(node, data);
+		dict_insert(dict, node, key);
+		return 1;
+	}
+	return 0;
+}
+
+#ifdef FSCK_NOTUSED
+void dict_delete_free(dict_t *dict, dnode_t *node)
+{
+	dict_delete(dict, node);
+	dict->freenode(node, dict->context);
+}
+#endif
+
+/*
+ * Return the node with the lowest (leftmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+dnode_t *dict_first(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left;
+
+	if (root != nil)
+		while ((left = root->left) != nil)
+			root = left;
+
+	return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the node with the highest (rightmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+dnode_t *dict_last(dict_t *dict)
+{
+	dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *right;
+
+	if (root != nil)
+		while ((right = root->right) != nil)
+			root = right;
+
+	return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the given node's successor node---the node which has the
+ * next key in the the left to right ordering. If the node has
+ * no successor, a null pointer is returned rather than a pointer to
+ * the nil node.
+ */
+dnode_t *dict_next(dict_t *dict, dnode_t *curr)
+{
+	dnode_t *nil = dict_nil(dict), *parent, *left;
+
+	if (curr->right != nil) {
+		curr = curr->right;
+		while ((left = curr->left) != nil)
+			curr = left;
+		return curr;
+	}
+
+	parent = curr->parent;
+
+	while (parent != nil && curr == parent->right) {
+		curr = parent;
+		parent = curr->parent;
+	}
+
+	return (parent == nil) ? NULL : parent;
+}
+
+/*
+ * Return the given node's predecessor, in the key order.
+ * The nil sentinel node is returned if there is no predecessor.
+ */
+dnode_t *dict_prev(dict_t *dict, dnode_t *curr)
+{
+	dnode_t *nil = dict_nil(dict), *parent, *right;
+
+	if (curr->left != nil) {
+		curr = curr->left;
+		while ((right = curr->right) != nil)
+			curr = right;
+		return curr;
+	}
+
+	parent = curr->parent;
+
+	while (parent != nil && curr == parent->left) {
+		curr = parent;
+		parent = curr->parent;
+	}
+
+	return (parent == nil) ? NULL : parent;
+}
+
+void dict_allow_dupes(dict_t *dict)
+{
+	dict->dupes = 1;
+}
+
+#undef dict_count
+#undef dict_isempty
+#undef dict_isfull
+#undef dnode_get
+#undef dnode_put
+#undef dnode_getkey
+
+dictcount_t dict_count(dict_t *dict)
+{
+	return dict->nodecount;
+}
+
+int dict_isempty(dict_t *dict)
+{
+	return dict->nodecount == 0;
+}
+
+int dict_isfull(dict_t *dict)
+{
+	return dict->nodecount == dict->maxcount;
+}
+
+int dict_contains(dict_t *dict, dnode_t *node)
+{
+	return verify_dict_has_node(dict_nil(dict), dict_root(dict), node);
+}
+
+static dnode_t *dnode_alloc(void *UNUSED(context))
+{
+	return malloc(sizeof *dnode_alloc(NULL));
+}
+
+static void dnode_free(dnode_t *node, void *UNUSED(context))
+{
+	free(node);
+}
+
+dnode_t *dnode_create(void *data)
+{
+	dnode_t *new = malloc(sizeof *new);
+	if (new) {
+		new->data = data;
+		new->parent = NULL;
+		new->left = NULL;
+		new->right = NULL;
+	}
+	return new;
+}
+
+dnode_t *dnode_init(dnode_t *dnode, void *data)
+{
+	dnode->data = data;
+	dnode->parent = NULL;
+	dnode->left = NULL;
+	dnode->right = NULL;
+	return dnode;
+}
+
+void dnode_destroy(dnode_t *dnode)
+{
+	dict_assert(!dnode_is_in_a_dict(dnode));
+	free(dnode);
+}
+
+void *dnode_get(dnode_t *dnode)
+{
+	return dnode->data;
+}
+
+const void *dnode_getkey(dnode_t *dnode)
+{
+	return dnode->key;
+}
+
+#ifdef FSCK_NOTUSED
+void dnode_put(dnode_t *dnode, void *data)
+{
+	dnode->data = data;
+}
+#endif
+
+#ifndef DICT_NODEBUG
+int dnode_is_in_a_dict(dnode_t *dnode)
+{
+	return (dnode->parent && dnode->left && dnode->right);
+}
+#endif
+
+#ifdef FSCK_NOTUSED
+void dict_process(dict_t *dict, void *context, dnode_process_t function)
+{
+	dnode_t *node = dict_first(dict), *next;
+
+	while (node != NULL) {
+		/* check for callback function deleting	*/
+		/* the next node from under us		*/
+		dict_assert(dict_contains(dict, node));
+		next = dict_next(dict, node);
+		function(dict, node, context);
+		node = next;
+	}
+}
+
+static void load_begin_internal(dict_load_t *load, dict_t *dict)
+{
+	load->dictptr = dict;
+	load->nilnode.left = &load->nilnode;
+	load->nilnode.right = &load->nilnode;
+}
+
+void dict_load_begin(dict_load_t *load, dict_t *dict)
+{
+	dict_assert(dict_isempty(dict));
+	load_begin_internal(load, dict);
+}
+
+void dict_load_next(dict_load_t *load, dnode_t *newnode, const void *key)
+{
+	dict_t *dict = load->dictptr;
+	dnode_t *nil = &load->nilnode;
+
+	dict_assert(!dnode_is_in_a_dict(newnode));
+	dict_assert(dict->nodecount < dict->maxcount);
+
+#ifndef DICT_NODEBUG
+	if (dict->nodecount > 0) {
+		if (dict->dupes)
+			dict_assert(dict->compare(nil->left->key, key) <= 0);
+		else
+			dict_assert(dict->compare(nil->left->key, key) < 0);
+	}
+#endif
+
+	newnode->key = key;
+	nil->right->left = newnode;
+	nil->right = newnode;
+	newnode->left = nil;
+	dict->nodecount++;
+}
+
+void dict_load_end(dict_load_t *load)
+{
+	dict_t *dict = load->dictptr;
+	dnode_t *tree[DICT_DEPTH_MAX] = { 0 };
+	dnode_t *curr, *dictnil = dict_nil(dict), *loadnil = &load->nilnode, *next;
+	dnode_t *complete = 0;
+	dictcount_t fullcount = DICTCOUNT_T_MAX, nodecount = dict->nodecount;
+	dictcount_t botrowcount;
+	unsigned baselevel = 0, level = 0, i;
+
+	dict_assert(dnode_red == 0 && dnode_black == 1);
+
+	while (fullcount >= nodecount && fullcount)
+		fullcount >>= 1;
+
+	botrowcount = nodecount - fullcount;
+
+	for (curr = loadnil->left; curr != loadnil; curr = next) {
+		next = curr->left;
+
+		if (complete == NULL && botrowcount-- == 0) {
+			dict_assert(baselevel == 0);
+			dict_assert(level == 0);
+			baselevel = level = 1;
+			complete = tree[0];
+
+			if (complete != 0) {
+				tree[0] = 0;
+				complete->right = dictnil;
+				while (tree[level] != 0) {
+					tree[level]->right = complete;
+					complete->parent = tree[level];
+					complete = tree[level];
+					tree[level++] = 0;
+				}
+			}
+		}
+
+		if (complete == NULL) {
+			curr->left = dictnil;
+			curr->right = dictnil;
+			curr->color = level % 2;
+			complete = curr;
+
+			dict_assert(level == baselevel);
+			while (tree[level] != 0) {
+				tree[level]->right = complete;
+				complete->parent = tree[level];
+				complete = tree[level];
+				tree[level++] = 0;
+			}
+		} else {
+			curr->left = complete;
+			curr->color = (level + 1) % 2;
+			complete->parent = curr;
+			tree[level] = curr;
+			complete = 0;
+			level = baselevel;
+		}
+	}
+
+	if (complete == NULL)
+		complete = dictnil;
+
+	for (i = 0; i < DICT_DEPTH_MAX; i++) {
+		if (tree[i] != 0) {
+			tree[i]->right = complete;
+			complete->parent = tree[i];
+			complete = tree[i];
+		}
+	}
+
+	dictnil->color = dnode_black;
+	dictnil->right = dictnil;
+	complete->parent = dictnil;
+	complete->color = dnode_black;
+	dict_root(dict) = complete;
+
+	dict_assert(dict_verify(dict));
+}
+
+void dict_merge(dict_t *dest, dict_t *source)
+{
+	dict_load_t load;
+	dnode_t *leftnode = dict_first(dest), *rightnode = dict_first(source);
+
+	dict_assert(dict_similar(dest, source));
+
+	if (source == dest)
+		return;
+
+	dest->nodecount = 0;
+	load_begin_internal(&load, dest);
+
+	for (;;) {
+		if (leftnode != NULL && rightnode != NULL) {
+			if (dest->compare(leftnode->key, rightnode->key) < 0)
+				goto copyleft;
+			else
+				goto copyright;
+		} else if (leftnode != NULL) {
+			goto copyleft;
+		} else if (rightnode != NULL) {
+			goto copyright;
+		} else {
+			dict_assert(leftnode == NULL && rightnode == NULL);
+			break;
+		}
+
+copyleft:
+		{
+			dnode_t *next = dict_next(dest, leftnode);
+#ifndef DICT_NODEBUG
+			leftnode->left = NULL;	/* suppress assertion in dict_load_next */
+#endif
+			dict_load_next(&load, leftnode, leftnode->key);
+			leftnode = next;
+			continue;
+		}
+
+copyright:
+		{
+			dnode_t *next = dict_next(source, rightnode);
+#ifndef DICT_NODEBUG
+			rightnode->left = NULL;
+#endif
+			dict_load_next(&load, rightnode, rightnode->key);
+			rightnode = next;
+			continue;
+		}
+	}
+
+	dict_clear(source);
+	dict_load_end(&load);
+}
+#endif /* FSCK_NOTUSED */
+
+#ifdef KAZLIB_TEST_MAIN
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+	char **tokptr;
+	va_list arglist;
+	int tokcount = 0;
+
+	va_start(arglist, string);
+	tokptr = va_arg(arglist, char **);
+	while (tokptr) {
+		while (*string && isspace((unsigned char) *string))
+			string++;
+		if (!*string)
+			break;
+		*tokptr = string;
+		while (*string && !isspace((unsigned char) *string))
+			string++;
+		tokptr = va_arg(arglist, char **);
+		tokcount++;
+		if (!*string)
+			break;
+		*string++ = 0;
+	}
+	va_end(arglist);
+
+	return tokcount;
+}
+
+static int comparef(const void *key1, const void *key2)
+{
+	return strcmp(key1, key2);
+}
+
+static char *dupstring(char *str)
+{
+	int sz = strlen(str) + 1;
+	char *new = malloc(sz);
+	if (new)
+		memcpy(new, str, sz);
+	return new;
+}
+
+static dnode_t *new_node(void *c)
+{
+	static dnode_t few[5];
+	static int count;
+
+	if (count < 5)
+		return few + count++;
+
+	return NULL;
+}
+
+static void del_node(dnode_t *n, void *c)
+{
+}
+
+static int prompt = 0;
+
+static void construct(dict_t *d)
+{
+	input_t in;
+	int done = 0;
+	dict_load_t dl;
+	dnode_t *dn;
+	char *tok1, *tok2, *val;
+	const char *key;
+	char *help =
+		"p                      turn prompt on\n"
+		"q                      finish construction\n"
+		"a <key> <val>          add new entry\n";
+
+	if (!dict_isempty(d))
+		puts("warning: dictionary not empty!");
+
+	dict_load_begin(&dl, d);
+
+	while (!done) {
+		if (prompt)
+			putchar('>');
+		fflush(stdout);
+
+		if (!fgets(in, sizeof(input_t), stdin))
+			break;
+
+		switch (in[0]) {
+			case '?':
+				puts(help);
+				break;
+			case 'p':
+				prompt = 1;
+				break;
+			case 'q':
+				done = 1;
+				break;
+			case 'a':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				}
+				key = dupstring(tok1);
+				val = dupstring(tok2);
+				dn = dnode_create(val);
+
+				if (!key || !val || !dn) {
+					puts("out of memory");
+					free((void *) key);
+					free(val);
+					if (dn)
+						dnode_destroy(dn);
+				}
+
+				dict_load_next(&dl, dn, key);
+				break;
+			default:
+				putchar('?');
+				putchar('\n');
+				break;
+		}
+	}
+
+	dict_load_end(&dl);
+}
+
+int main(void)
+{
+	input_t in;
+	dict_t darray[10];
+	dict_t *d = &darray[0];
+	dnode_t *dn;
+	int i;
+	char *tok1, *tok2, *val;
+	const char *key;
+
+	char *help =
+		"a <key> <val>          add value to dictionary\n"
+		"d <key>                delete value from dictionary\n"
+		"l <key>                lookup value in dictionary\n"
+		"( <key>                lookup lower bound\n"
+		") <key>                lookup upper bound\n"
+		"# <num>                switch to alternate dictionary (0-9)\n"
+		"j <num> <num>          merge two dictionaries\n"
+		"f                      free the whole dictionary\n"
+		"k                      allow duplicate keys\n"
+		"c                      show number of entries\n"
+		"t                      dump whole dictionary in sort order\n"
+		"m                      make dictionary out of sorted items\n"
+		"p                      turn prompt on\n"
+		"s                      switch to non-functioning allocator\n"
+		"q                      quit";
+
+	for (i = 0; i < sizeof darray / sizeof *darray; i++)
+		dict_init(&darray[i], DICTCOUNT_T_MAX, comparef);
+
+	for (;;) {
+		if (prompt)
+			putchar('>');
+		fflush(stdout);
+
+		if (!fgets(in, sizeof(input_t), stdin))
+			break;
+
+		switch(in[0]) {
+			case '?':
+				puts(help);
+				break;
+			case 'a':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				}
+				key = dupstring(tok1);
+				val = dupstring(tok2);
+
+				if (!key || !val) {
+					puts("out of memory");
+					free((void *) key);
+					free(val);
+				}
+
+				if (!dict_alloc_insert(d, key, val)) {
+					puts("dict_alloc_insert failed");
+					free((void *) key);
+					free(val);
+					break;
+				}
+				break;
+			case 'd':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				}
+				dn = dict_lookup(d, tok1);
+				if (!dn) {
+					puts("dict_lookup failed");
+					break;
+				}
+				val = dnode_get(dn);
+				key = dnode_getkey(dn);
+				dict_delete_free(d, dn);
+
+				free(val);
+				free((void *) key);
+				break;
+			case 'f':
+				dict_free(d);
+				break;
+			case 'l':
+			case '(':
+			case ')':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				}
+				dn = 0;
+				switch (in[0]) {
+					case 'l':
+						dn = dict_lookup(d, tok1);
+						break;
+					case '(':
+						dn = dict_lower_bound(d, tok1);
+						break;
+					case ')':
+						dn = dict_upper_bound(d, tok1);
+						break;
+				}
+				if (!dn) {
+					puts("lookup failed");
+					break;
+				}
+				val = dnode_get(dn);
+				puts(val);
+				break;
+			case 'm':
+				construct(d);
+				break;
+			case 'k':
+				dict_allow_dupes(d);
+				break;
+			case 'c':
+				printf("%lu\n", (unsigned long) dict_count(d));
+				break;
+			case 't':
+				for (dn = dict_first(d); dn; dn = dict_next(d, dn)) {
+					printf("%s\t%s\n", (char *) dnode_getkey(dn),
+							(char *) dnode_get(dn));
+				}
+				break;
+			case 'q':
+				exit(0);
+				break;
+			case '\0':
+				break;
+			case 'p':
+				prompt = 1;
+				break;
+			case 's':
+				dict_set_allocator(d, new_node, del_node, NULL);
+				break;
+			case '#':
+				if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+					puts("what?");
+					break;
+				} else {
+					int dictnum = atoi(tok1);
+					if (dictnum < 0 || dictnum > 9) {
+						puts("invalid number");
+						break;
+					}
+					d = &darray[dictnum];
+				}
+				break;
+			case 'j':
+				if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+					puts("what?");
+					break;
+				} else {
+					int dict1 = atoi(tok1), dict2 = atoi(tok2);
+					if (dict1 < 0 || dict1 > 9 || dict2 < 0 || dict2 > 9) {
+						puts("invalid number");
+						break;
+					}
+					dict_merge(&darray[dict1], &darray[dict2]);
+				}
+				break;
+			default:
+				putchar('?');
+				putchar('\n');
+				break;
+		}
+	}
+
+	return 0;
+}
+
+#endif
diff --git a/fsck/dict.h b/fsck/dict.h
new file mode 100644
index 0000000..c59e1a2
--- /dev/null
+++ b/fsck/dict.h
@@ -0,0 +1,144 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.h,v 1.22.2.6 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ */
+
+#ifndef DICT_H
+#define DICT_H
+
+#include <limits.h>
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long dictcount_t;
+#define DICTCOUNT_T_MAX ULONG_MAX
+
+/*
+ * The dictionary is implemented as a red-black tree
+ */
+
+typedef enum { dnode_red, dnode_black } dnode_color_t;
+
+typedef struct dnode_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	struct dnode_t *dict_left;
+	struct dnode_t *dict_right;
+	struct dnode_t *dict_parent;
+	dnode_color_t dict_color;
+	const void *dict_key;
+	void *dict_data;
+#else
+	int dict_dummy;
+#endif
+} dnode_t;
+
+typedef int (*dict_comp_t)(const void *, const void *);
+typedef dnode_t *(*dnode_alloc_t)(void *);
+typedef void (*dnode_free_t)(dnode_t *, void *);
+
+typedef struct dict_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	dnode_t dict_nilnode;
+	dictcount_t dict_nodecount;
+	dictcount_t dict_maxcount;
+	dict_comp_t dict_compare;
+	dnode_alloc_t dict_allocnode;
+	dnode_free_t dict_freenode;
+	void *dict_context;
+	int dict_dupes;
+#else
+	int dict_dummmy;
+#endif
+} dict_t;
+
+typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *);
+
+typedef struct dict_load_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+	dict_t *dict_dictptr;
+	dnode_t dict_nilnode;
+#else
+	int dict_dummmy;
+#endif
+} dict_load_t;
+
+extern dict_t *dict_create(dictcount_t, dict_comp_t);
+extern void dict_set_allocator(dict_t *, dnode_alloc_t, dnode_free_t, void *);
+extern void dict_destroy(dict_t *);
+extern void dict_free_nodes(dict_t *);
+extern void dict_free(dict_t *);
+extern dict_t *dict_init(dict_t *, dictcount_t, dict_comp_t);
+extern void dict_init_like(dict_t *, const dict_t *);
+extern int dict_verify(dict_t *);
+extern int dict_similar(const dict_t *, const dict_t *);
+extern dnode_t *dict_lookup(dict_t *, const void *);
+extern dnode_t *dict_lower_bound(dict_t *, const void *);
+extern dnode_t *dict_upper_bound(dict_t *, const void *);
+extern void dict_insert(dict_t *, dnode_t *, const void *);
+extern dnode_t *dict_delete(dict_t *, dnode_t *);
+extern int dict_alloc_insert(dict_t *, const void *, void *);
+extern void dict_delete_free(dict_t *, dnode_t *);
+extern dnode_t *dict_first(dict_t *);
+extern dnode_t *dict_last(dict_t *);
+extern dnode_t *dict_next(dict_t *, dnode_t *);
+extern dnode_t *dict_prev(dict_t *, dnode_t *);
+extern dictcount_t dict_count(dict_t *);
+extern int dict_isempty(dict_t *);
+extern int dict_isfull(dict_t *);
+extern int dict_contains(dict_t *, dnode_t *);
+extern void dict_allow_dupes(dict_t *);
+extern int dnode_is_in_a_dict(dnode_t *);
+extern dnode_t *dnode_create(void *);
+extern dnode_t *dnode_init(dnode_t *, void *);
+extern void dnode_destroy(dnode_t *);
+extern void *dnode_get(dnode_t *);
+extern const void *dnode_getkey(dnode_t *);
+extern void dnode_put(dnode_t *, void *);
+extern void dict_process(dict_t *, void *, dnode_process_t);
+extern void dict_load_begin(dict_load_t *, dict_t *);
+extern void dict_load_next(dict_load_t *, dnode_t *, const void *);
+extern void dict_load_end(dict_load_t *);
+extern void dict_merge(dict_t *, dict_t *);
+
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#define dict_isfull(D) (SFX_CHECK(D)->dict_nodecount == (D)->dict_maxcount)
+#else
+#define dict_isfull(D) ((D)->dict_nodecount == (D)->dict_maxcount)
+#endif
+#define dict_count(D) ((D)->dict_nodecount)
+#define dict_isempty(D) ((D)->dict_nodecount == 0)
+#define dnode_get(N) ((N)->dict_data)
+#define dnode_getkey(N) ((N)->dict_key)
+#define dnode_put(N, X) ((N)->dict_data = (X))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/fsck/dqblk_v2.h b/fsck/dqblk_v2.h
new file mode 100644
index 0000000..d12512a
--- /dev/null
+++ b/fsck/dqblk_v2.h
@@ -0,0 +1,31 @@
+/*
+ * Header file for disk format of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ */
+
+#ifndef __QUOTA_DQBLK_V2_H__
+#define __QUOTA_DQBLK_V2_H__
+
+#include "quotaio_tree.h"
+
+/* Structure for format specific information */
+struct v2_mem_dqinfo {
+	struct qtree_mem_dqinfo dqi_qtree;
+	unsigned int dqi_flags;		/* Flags set in quotafile */
+	unsigned int dqi_used_entries;	/* Number of entries in file -
+					   updated by scan_dquots */
+	unsigned int dqi_data_blocks;	/* Number of data blocks in file -
+					   updated by scan_dquots */
+};
+
+struct v2_mem_dqblk {
+	long long dqb_off;	/* Offset of dquot in file */
+};
+
+struct quotafile_ops;		/* Will be defined later in quotaio.h */
+
+/* Operations above this format */
+extern struct quotafile_ops quotafile_ops_2;
+
+#endif  /* __QUOTA_DQBLK_V2_H__ */
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 56a47be..35df68c 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -9,12 +9,12 @@
  * published by the Free Software Foundation.
  */
 #include "fsck.h"
+#include "quotaio.h"
 
 char *tree_mark;
 uint32_t tree_mark_size = 256;
 
-static inline int f2fs_set_main_bitmap(struct f2fs_sb_info *sbi, u32 blk,
-								int type)
+int f2fs_set_main_bitmap(struct f2fs_sb_info *sbi, u32 blk, int type)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct seg_entry *se;
@@ -50,6 +50,13 @@ static inline int f2fs_test_sit_bitmap(struct f2fs_sb_info *sbi, u32 blk)
 	return f2fs_test_bit(BLKOFF_FROM_MAIN(sbi, blk), fsck->sit_area_bitmap);
 }
 
+int f2fs_set_sit_bitmap(struct f2fs_sb_info *sbi, u32 blk)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+
+	return f2fs_set_bit(BLKOFF_FROM_MAIN(sbi, blk), fsck->sit_area_bitmap);
+}
+
 static int add_into_hard_link_list(struct f2fs_sb_info *sbi,
 						u32 nid, u32 link_cnt)
 {
@@ -500,7 +507,9 @@ int fsck_chk_node_blk(struct f2fs_sb_info *sbi, struct f2fs_inode *inode,
 		goto err;
 
 	if (ntype == TYPE_INODE) {
+		struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 		fsck_chk_inode_blk(sbi, nid, ftype, node_blk, blk_cnt, &ni);
+		quota_add_inode_usage(fsck->qctx, nid, &node_blk->i);
 	} else {
 		switch (ntype) {
 		case TYPE_DIRECT_NODE:
@@ -622,7 +631,8 @@ void fsck_chk_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 		if (f2fs_test_main_bitmap(sbi, ni->blk_addr) == 0) {
 			f2fs_set_main_bitmap(sbi, ni->blk_addr,
 							CURSEG_WARM_NODE);
-			if (i_links > 1 && ftype != F2FS_FT_ORPHAN) {
+			if (i_links > 1 && ftype != F2FS_FT_ORPHAN &&
+					!is_qf_ino(F2FS_RAW_SUPER(sbi), nid)) {
 				/* First time. Create new hard link node */
 				add_into_hard_link_list(sbi, nid, i_links);
 				fsck->chk.multi_hard_link_files++;
@@ -807,6 +817,11 @@ skip_blkcnt_fix:
 				le32_to_cpu(node_blk->footer.ino),
 				en, (u32)i_blocks);
 
+	if (is_qf_ino(F2FS_RAW_SUPER(sbi), nid))
+		DBG(1, "Quota Inode: 0x%x [%s] i_blocks: %u\n\n",
+				le32_to_cpu(node_blk->footer.ino),
+				en, (u32)i_blocks);
+
 	if (ftype == F2FS_FT_DIR) {
 		DBG(1, "Directory Inode: 0x%x [%s] depth: %d has %d files\n\n",
 				le32_to_cpu(node_blk->footer.ino), en,
@@ -1558,6 +1573,82 @@ int fsck_chk_orphan_node(struct f2fs_sb_info *sbi)
 	return 0;
 }
 
+int fsck_chk_quota_node(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	enum quota_type qtype;
+	int ret = 0;
+	u32 blk_cnt = 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (sb->qf_ino[qtype] == 0)
+			continue;
+		nid_t ino = QUOTA_INO(sb, qtype);
+		struct node_info ni;
+
+		DBG(1, "[%3d] ino [0x%x]\n", qtype, ino);
+		blk_cnt = 1;
+
+		if (c.preen_mode == PREEN_MODE_1 && !c.fix_on) {
+			get_node_info(sbi, ino, &ni);
+			if (!IS_VALID_NID(sbi, ino) ||
+					!IS_VALID_BLK_ADDR(sbi, ni.blk_addr))
+				return -EINVAL;
+		}
+		ret = fsck_chk_node_blk(sbi, NULL, ino,
+				F2FS_FT_REG_FILE, TYPE_INODE, &blk_cnt, NULL);
+		if (ret)
+			ASSERT_MSG("[0x%x] wrong orphan inode", ino);
+	}
+	return ret;
+}
+
+int fsck_chk_quota_files(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	enum quota_type qtype;
+	f2fs_ino_t ino;
+	int ret = 0;
+	int needs_writeout;
+
+	/* Return if quota feature is disabled */
+	if (!fsck->qctx)
+		return 0;
+
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		ino = sb->qf_ino[qtype];
+		if (!ino)
+			continue;
+
+	        DBG(1, "Checking Quota file ([%3d] ino [0x%x])\n", qtype, ino);
+		needs_writeout = 0;
+		ret = quota_compare_and_update(sbi, qtype, &needs_writeout);
+		if (ret == 0 && needs_writeout == 0) {
+			DBG(1, "OK\n");
+			continue;
+		}
+
+		/* Something is wrong */
+		if (c.fix_on) {
+			DBG(0, "Fixing Quota file ([%3d] ino [0x%x])\n",
+							qtype, ino);
+			f2fs_filesize_update(sbi, ino, 0);
+			ret = quota_write_inode(sbi, qtype);
+			if (!ret) {
+				c.bug_on = 1;
+				DBG(1, "OK\n");
+			} else {
+				ASSERT_MSG("Unable to write quota file");
+			}
+		} else {
+			ASSERT_MSG("Quota file is missing or invalid"
+					" quota file content found.");
+		}
+	}
+	return ret;
+}
+
 int fsck_chk_meta(struct f2fs_sb_info *sbi)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
@@ -1618,6 +1709,10 @@ int fsck_chk_meta(struct f2fs_sb_info *sbi)
 	if (fsck_chk_orphan_node(sbi))
 		return -EINVAL;
 
+	/* 5. check quota inode simply */
+	if (fsck_chk_quota_node(sbi))
+		return -EINVAL;
+
 	if (fsck->nat_valid_inode_cnt != le32_to_cpu(cp->valid_inode_count)) {
 		ASSERT_MSG("valid inode does not match: nat_valid_inode_cnt %u,"
 				" valid_inode_count %u",
@@ -2042,6 +2137,10 @@ int fsck_verify(struct f2fs_sb_info *sbi)
 void fsck_free(struct f2fs_sb_info *sbi)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+
+	if (fsck->qctx)
+		quota_release_context(&fsck->qctx);
+
 	if (fsck->main_area_bitmap)
 		free(fsck->main_area_bitmap);
 
diff --git a/fsck/fsck.h b/fsck/fsck.h
index 5628906..7b6ac2b 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -13,6 +13,8 @@
 
 #include "f2fs.h"
 
+struct quota_ctx;
+
 #define FSCK_UNMATCHED_EXTENT		0x00000001
 
 enum {
@@ -85,6 +87,8 @@ struct f2fs_fsck {
 	u32 dentry_depth;
 	struct f2fs_nat_entry *entries;
 	u32 nat_valid_inode_cnt;
+
+	struct quota_ctx *qctx;
 };
 
 #define BLOCK_SZ		4096
@@ -118,6 +122,8 @@ enum seg_type {
 struct selabel_handle;
 
 extern int fsck_chk_orphan_node(struct f2fs_sb_info *);
+extern int fsck_chk_quota_node(struct f2fs_sb_info *);
+extern int fsck_chk_quota_files(struct f2fs_sb_info *);
 extern int fsck_chk_node_blk(struct f2fs_sb_info *, struct f2fs_inode *, u32,
 		enum FILE_TYPE, enum NODE_TYPE, u32 *,
 		struct child_info *);
@@ -154,6 +160,8 @@ extern void nullify_nat_entry(struct f2fs_sb_info *, u32);
 extern void rewrite_sit_area_bitmap(struct f2fs_sb_info *);
 extern void build_nat_area_bitmap(struct f2fs_sb_info *);
 extern void build_sit_area_bitmap(struct f2fs_sb_info *);
+extern int f2fs_set_main_bitmap(struct f2fs_sb_info *, u32, int);
+extern int f2fs_set_sit_bitmap(struct f2fs_sb_info *, u32);
 extern void fsck_init(struct f2fs_sb_info *);
 extern int fsck_verify(struct f2fs_sb_info *);
 extern void fsck_free(struct f2fs_sb_info *);
@@ -210,6 +218,8 @@ int f2fs_resize(struct f2fs_sb_info *);
 /* sload.c */
 int f2fs_sload(struct f2fs_sb_info *, const char *, const char *,
 		const char *, struct selabel_handle *);
+
+/* segment.c */
 void reserve_new_block(struct f2fs_sb_info *, block_t *,
 					struct f2fs_summary *, int);
 void new_data_block(struct f2fs_sb_info *, void *,
diff --git a/fsck/main.c b/fsck/main.c
index c9411eb..eab213d 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -18,6 +18,7 @@
 #include "fsck.h"
 #include <libgen.h>
 #include <ctype.h>
+#include "quotaio.h"
 
 struct f2fs_fsck gfsck;
 
@@ -407,6 +408,7 @@ static void do_fsck(struct f2fs_sb_info *sbi)
 	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
 	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
 	u32 blk_cnt;
+	errcode_t ret;
 
 	fsck_init(sbi);
 
@@ -429,8 +431,7 @@ static void do_fsck(struct f2fs_sb_info *sbi)
 				c.fix_on = 1;
 			break;
 		}
-	} else {
-		/*
+	} else { /*
 		 * we can hit this in 3 situations:
 		 *  1. fsck -f, fix_on has already been set to 1 when
 		 *     parsing options;
@@ -443,12 +444,23 @@ static void do_fsck(struct f2fs_sb_info *sbi)
 		c.fix_on = 1;
 	}
 
-	fsck_chk_orphan_node(sbi);
+	fsck_chk_quota_node(sbi);
 
 	/* Traverse all block recursively from root inode */
 	blk_cnt = 1;
+
+	if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		ret = quota_init_context(sbi);
+		if (ret) {
+			ASSERT_MSG("quota_init_context failure: %d", ret);
+			return;
+		}
+	}
+	fsck_chk_orphan_node(sbi);
 	fsck_chk_node_blk(sbi, NULL, sbi->root_ino_num,
 			F2FS_FT_DIR, TYPE_INODE, &blk_cnt, NULL);
+	fsck_chk_quota_files(sbi);
+
 	fsck_verify(sbi);
 	fsck_free(sbi);
 }
diff --git a/fsck/mkquota.c b/fsck/mkquota.c
new file mode 100644
index 0000000..aadfae7
--- /dev/null
+++ b/fsck/mkquota.c
@@ -0,0 +1,403 @@
+/*
+ * mkquota.c --- create quota files for a filesystem
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#include "quotaio.h"
+#include "quotaio_v2.h"
+#include "quotaio_tree.h"
+#include "common.h"
+#include "dict.h"
+
+
+/* Needed for architectures where sizeof(int) != sizeof(void *) */
+#define UINT_TO_VOIDPTR(val)  ((void *)(intptr_t)(val))
+#define VOIDPTR_TO_UINT(ptr)  ((unsigned int)(intptr_t)(ptr))
+
+#if DEBUG_QUOTA
+static void print_dquot(const char *desc, struct dquot *dq)
+{
+	if (desc)
+		fprintf(stderr, "%s: ", desc);
+	fprintf(stderr, "%u %lld:%lld:%lld %lld:%lld:%lld\n",
+		dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+		(long long) dq->dq_dqb.dqb_bsoftlimit,
+		(long long) dq->dq_dqb.dqb_bhardlimit,
+		(long long) dq->dq_dqb.dqb_curinodes,
+		(long long) dq->dq_dqb.dqb_isoftlimit,
+		(long long) dq->dq_dqb.dqb_ihardlimit);
+}
+#else
+#define print_dquot(...)
+#endif
+
+static void write_dquots(dict_t *dict, struct quota_handle *qh)
+{
+	dnode_t		*n;
+	struct dquot	*dq;
+
+	for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+		dq = dnode_get(n);
+		if (dq) {
+			print_dquot("write", dq);
+			dq->dq_h = qh;
+			update_grace_times(dq);
+			qh->qh_ops->commit_dquot(dq);
+		}
+	}
+}
+
+errcode_t quota_write_inode(struct f2fs_sb_info *sbi, enum quota_type qtype)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	struct quota_handle *h = NULL;
+	int retval = 0;
+	dict_t *dict;
+
+	if ((!qctx) || (!sb->qf_ino[qtype]))
+		return 0;
+
+	retval = quota_get_mem(sizeof(struct quota_handle), &h);
+	if (retval) {
+		log_debug("Unable to allocate quota handle");
+		goto out;
+	}
+
+	dict = qctx->quota_dict[qtype];
+	if (dict) {
+		retval = quota_file_create(sbi, h, qtype);
+		if (retval) {
+			log_debug("Cannot initialize io on quotafile");
+		} else {
+			write_dquots(dict, h);
+			quota_file_close(sbi, h, 1);
+		}
+	}
+out:
+	if (h)
+		quota_free_mem(&h);
+	return retval;
+}
+
+/******************************************************************/
+/* Helper functions for computing quota in memory.                */
+/******************************************************************/
+
+static int dict_uint_cmp(const void *a, const void *b)
+{
+	unsigned int	c, d;
+
+	c = VOIDPTR_TO_UINT(a);
+	d = VOIDPTR_TO_UINT(b);
+
+	if (c == d)
+		return 0;
+	else if (c > d)
+		return 1;
+	else
+		return -1;
+}
+
+static inline qid_t get_qid(struct f2fs_inode *inode, enum quota_type qtype)
+{
+	switch (qtype) {
+	case USRQUOTA:
+		return inode->i_uid;
+	case GRPQUOTA:
+		return inode->i_gid;
+	case PRJQUOTA:
+		return inode->i_projid;
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static void quota_dnode_free(dnode_t *node, void *UNUSED(context))
+{
+	void *ptr = node ? dnode_get(node) : 0;
+
+	quota_free_mem(&ptr);
+	free(node);
+}
+
+/*
+ * Set up the quota tracking data structures.
+ */
+errcode_t quota_init_context(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	errcode_t err;
+	dict_t	*dict;
+	quota_ctx_t ctx;
+	enum quota_type	qtype;
+
+	err = quota_get_mem(sizeof(struct quota_ctx), &ctx);
+	if (err) {
+		log_debug("Failed to allocate quota context");
+		return err;
+	}
+
+	memset(ctx, 0, sizeof(struct quota_ctx));
+	dict_init(&ctx->linked_inode_dict, DICTCOUNT_T_MAX, dict_uint_cmp);
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		ctx->quota_file[qtype] = NULL;
+		if (!sb->qf_ino[qtype])
+			continue;
+		err = quota_get_mem(sizeof(dict_t), &dict);
+		if (err) {
+			log_debug("Failed to allocate dictionary");
+			quota_release_context(&ctx);
+			return err;
+		}
+		ctx->quota_dict[qtype] = dict;
+		dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp);
+		dict_set_allocator(dict, NULL, quota_dnode_free, NULL);
+	}
+	ctx->sbi = sbi;
+	fsck->qctx = ctx;
+	return 0;
+}
+
+void quota_release_context(quota_ctx_t *qctx)
+{
+	dict_t	*dict;
+	enum quota_type	qtype;
+	quota_ctx_t ctx;
+
+	if (!qctx)
+		return;
+
+	ctx = *qctx;
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = ctx->quota_dict[qtype];
+		ctx->quota_dict[qtype] = 0;
+		if (dict) {
+			dict_free_nodes(dict);
+			free(dict);
+		}
+	}
+	dict_free_nodes(&ctx->linked_inode_dict);
+	*qctx = NULL;
+	free(ctx);
+}
+
+static struct dquot *get_dq(dict_t *dict, __u32 key)
+{
+	struct dquot	*dq;
+	dnode_t		*n;
+
+	n = dict_lookup(dict, UINT_TO_VOIDPTR(key));
+	if (n)
+		dq = dnode_get(n);
+	else {
+		if (quota_get_mem(sizeof(struct dquot), &dq)) {
+			log_err("Unable to allocate dquot");
+			return NULL;
+		}
+		memset(dq, 0, sizeof(struct dquot));
+		dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq);
+		dq->dq_id = key;
+	}
+	return dq;
+}
+
+/*
+ * Called to update the blocks used by a particular inode
+ */
+void quota_data_add(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space)
+{
+	struct dquot	*dq;
+	dict_t		*dict;
+	enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			if (dq)
+				dq->dq_dqb.dqb_curspace += space;
+		}
+	}
+}
+
+/*
+ * Called to remove some blocks used by a particular inode
+ */
+void quota_data_sub(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space)
+{
+	struct dquot	*dq;
+	dict_t		*dict;
+	enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			dq->dq_dqb.dqb_curspace -= space;
+		}
+	}
+}
+
+/*
+ * Called to count the files used by an inode's user/group
+ */
+void quota_data_inodes(quota_ctx_t qctx, struct f2fs_inode *inode, int adjust)
+{
+	struct dquot	*dq;
+	dict_t		*dict; enum quota_type	qtype;
+
+	if (!qctx)
+		return;
+
+	for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+		dict = qctx->quota_dict[qtype];
+		if (dict) {
+			dq = get_dq(dict, get_qid(inode, qtype));
+			dq->dq_dqb.dqb_curinodes += adjust;
+		}
+	}
+}
+
+/*
+ * Called from fsck to count quota.
+ */
+void quota_add_inode_usage(quota_ctx_t qctx, f2fs_ino_t ino,
+		struct f2fs_inode* inode)
+{
+	if (qctx) {
+		/* Handle hard linked inodes */
+		if (inode->i_links > 1) {
+			if (dict_lookup(&qctx->linked_inode_dict,
+				UINT_TO_VOIDPTR(ino))) {
+				return;
+			}
+			dict_alloc_insert(&qctx->linked_inode_dict,
+					UINT_TO_VOIDPTR(ino), NULL);
+		}
+
+		qsize_t space = (inode->i_blocks - 1) * BLOCK_SZ;
+		quota_data_add(qctx, inode, space);
+		quota_data_inodes(qctx, inode, +1);
+	}
+}
+
+struct scan_dquots_data {
+	dict_t		*quota_dict;
+	int             update_limits; /* update limits from disk */
+	int		update_usage;
+	int		usage_is_inconsistent;
+};
+
+static int scan_dquots_callback(struct dquot *dquot, void *cb_data)
+{
+	struct scan_dquots_data *scan_data = cb_data;
+	dict_t *quota_dict = scan_data->quota_dict;
+	struct dquot *dq;
+
+	dq = get_dq(quota_dict, dquot->dq_id);
+	dq->dq_id = dquot->dq_id;
+	dq->dq_flags |= DQF_SEEN;
+
+	print_dquot("mem", dq);
+	print_dquot("dsk", dquot);
+	/* Check if there is inconsistency */
+	if (dq->dq_dqb.dqb_curspace != dquot->dq_dqb.dqb_curspace ||
+	    dq->dq_dqb.dqb_curinodes != dquot->dq_dqb.dqb_curinodes) {
+		scan_data->usage_is_inconsistent = 1;
+		log_debug("[QUOTA WARNING] Usage inconsistent for ID %u:"
+			"actual (%lld, %lld) != expected (%lld, %lld)\n",
+				dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+				(long long) dq->dq_dqb.dqb_curinodes,
+				(long long) dquot->dq_dqb.dqb_curspace,
+				(long long) dquot->dq_dqb.dqb_curinodes);
+	}
+
+	if (scan_data->update_limits) {
+		dq->dq_dqb.dqb_ihardlimit = dquot->dq_dqb.dqb_ihardlimit;
+		dq->dq_dqb.dqb_isoftlimit = dquot->dq_dqb.dqb_isoftlimit;
+		dq->dq_dqb.dqb_bhardlimit = dquot->dq_dqb.dqb_bhardlimit;
+		dq->dq_dqb.dqb_bsoftlimit = dquot->dq_dqb.dqb_bsoftlimit;
+	}
+
+	if (scan_data->update_usage) {
+		dq->dq_dqb.dqb_curspace = dquot->dq_dqb.dqb_curspace;
+		dq->dq_dqb.dqb_curinodes = dquot->dq_dqb.dqb_curinodes;
+	}
+
+	return 0;
+}
+
+/*
+ * Compares the measured quota in qctx->quota_dict with that in the quota inode
+ * on disk and updates the limits in qctx->quota_dict. 'usage_inconsistent' is
+ * set to 1 if the supplied and on-disk quota usage values are not identical.
+ */
+errcode_t quota_compare_and_update(struct f2fs_sb_info *sbi,
+		enum quota_type qtype, int *usage_inconsistent)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	struct quota_handle qh;
+	struct scan_dquots_data scan_data;
+	struct dquot *dq;
+	dnode_t *n;
+	dict_t *dict = qctx->quota_dict[qtype];
+	errcode_t err = 0;
+
+	if (!dict)
+		goto out;
+
+	err = quota_file_open(sbi, &qh, qtype, 0);
+	if (err) {
+		log_debug("Open quota file failed");
+		goto out;
+	}
+
+	scan_data.quota_dict = qctx->quota_dict[qtype];
+	scan_data.update_limits = 1;
+	scan_data.update_usage = 0;
+	scan_data.usage_is_inconsistent = 0;
+	err = qh.qh_ops->scan_dquots(&qh, scan_dquots_callback, &scan_data);
+	if (err) {
+		log_debug("Error scanning dquots");
+		goto out;
+	}
+
+	for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+		dq = dnode_get(n);
+		if (!dq)
+			continue;
+		if ((dq->dq_flags & DQF_SEEN) == 0) {
+			log_debug("[QUOTA WARNING] "
+				"Missing quota entry ID %d\n", dq->dq_id);
+			scan_data.usage_is_inconsistent = 1;
+		}
+	}
+	*usage_inconsistent = scan_data.usage_is_inconsistent;
+
+out:
+	return err;
+}
+
diff --git a/fsck/mount.c b/fsck/mount.c
index 29af3b7..d71e107 100644
--- a/fsck/mount.c
+++ b/fsck/mount.c
@@ -297,6 +297,9 @@ void print_sb_state(struct f2fs_super_block *sb)
 	if (f & cpu_to_le32(F2FS_FEATURE_FLEXIBLE_INLINE_XATTR)) {
 		MSG(0, "%s", " flexible inline xattr");
 	}
+	if (f & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
+		MSG(0, "%s", " quota ino");
+	}
 	MSG(0, "\n");
 	MSG(0, "Info: superblock encrypt level = %d, salt = ",
 					sb->encryption_level);
@@ -739,7 +742,7 @@ static int f2fs_init_nid_bitmap(struct f2fs_sb_info *sbi)
 	nid_t nid;
 	int i;
 
-	if (!(c.func == SLOAD))
+	if (!(c.func == SLOAD || c.func == FSCK))
 		return 0;
 
 	nm_i->nid_bitmap = (char *)calloc(nid_bitmap_size, 1);
@@ -2159,10 +2162,14 @@ int f2fs_do_mount(struct f2fs_sb_info *sbi)
 	if (c.auto_fix || c.preen_mode) {
 		u32 flag = get_cp(ckpt_flags);
 
-		if (flag & CP_FSCK_FLAG)
+		if (flag & CP_FSCK_FLAG ||
+			(exist_qf_ino(sb) && (!(flag & CP_UMOUNT_FLAG) ||
+						flag & CP_ERROR_FLAG))) {
 			c.fix_on = 1;
-		else if (!c.preen_mode)
+		} else if (!c.preen_mode) {
+			print_cp_state(flag);
 			return 1;
+		}
 	}
 
 	c.bug_on = 0;
@@ -2224,7 +2231,7 @@ void f2fs_do_umount(struct f2fs_sb_info *sbi)
 	unsigned int i;
 
 	/* free nm_info */
-	if (c.func == SLOAD)
+	if (c.func == SLOAD || c.func == FSCK)
 		free(nm_i->nid_bitmap);
 	free(nm_i->nat_bitmap);
 	free(sbi->nm_info);
diff --git a/fsck/quotaio.c b/fsck/quotaio.c
new file mode 100644
index 0000000..afadf56
--- /dev/null
+++ b/fsck/quotaio.c
@@ -0,0 +1,221 @@
+/** quotaio.c
+ *
+ * Generic IO operations on quotafiles
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Aditya Kali <adityakali@google.com> - Ported to e2fsprogs
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <assert.h>
+
+#include "common.h"
+#include "quotaio.h"
+
+static const char * const extensions[MAXQUOTAS] = {
+	[USRQUOTA] = "user",
+	[GRPQUOTA] = "group",
+	[PRJQUOTA] = "project",
+};
+
+/* Header in all newer quotafiles */
+struct disk_dqheader {
+	__le32 dqh_magic;
+	__le32 dqh_version;
+} __attribute__ ((packed));
+
+/**
+ * Convert type of quota to written representation
+ */
+const char *quota_type2name(enum quota_type qtype)
+{
+	if (qtype >= MAXQUOTAS)
+		return "unknown";
+	return extensions[qtype];
+}
+
+/*
+ * Set grace time if needed
+ */
+void update_grace_times(struct dquot *q)
+{
+	time_t now;
+
+	time(&now);
+	if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) >
+			q->dq_dqb.dqb_bsoftlimit) {
+		if (!q->dq_dqb.dqb_btime)
+			q->dq_dqb.dqb_btime =
+				now + q->dq_h->qh_info.dqi_bgrace;
+	} else {
+		q->dq_dqb.dqb_btime = 0;
+	}
+
+	if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes >
+			q->dq_dqb.dqb_isoftlimit) {
+		if (!q->dq_dqb.dqb_itime)
+				q->dq_dqb.dqb_itime =
+					now + q->dq_h->qh_info.dqi_igrace;
+	} else {
+		q->dq_dqb.dqb_itime = 0;
+	}
+}
+
+/* Functions to read/write quota file. */
+static unsigned int quota_write_nomount(struct quota_file *qf,
+					long offset,
+					void *buf, unsigned int size)
+{
+	unsigned int written;
+
+	written = f2fs_write(qf->sbi, qf->ino, buf, size, offset);
+	if (qf->filesize < offset + written)
+		qf->filesize = offset + written;
+
+	return written;
+}
+
+static unsigned int quota_read_nomount(struct quota_file *qf, long offset,
+		void *buf, unsigned int size)
+{
+	return f2fs_read(qf->sbi, qf->ino, buf, size, offset);
+}
+
+/*
+ * Detect quota format and initialize quota IO
+ */
+errcode_t quota_file_open(struct f2fs_sb_info *sbi, struct quota_handle *h,
+			  enum quota_type qtype, int flags)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+	f2fs_ino_t qf_ino;
+	errcode_t err = 0;
+	int allocated_handle = 0;
+
+	if (qtype >= MAXQUOTAS)
+		return EINVAL;
+
+	qf_ino = sb->qf_ino[qtype];
+
+	if (!h) {
+		if (qctx->quota_file[qtype]) {
+			h = qctx->quota_file[qtype];
+			(void) quota_file_close(sbi, h, 0);
+		}
+		err = quota_get_mem(sizeof(struct quota_handle), &h);
+		if (err) {
+			log_err("Unable to allocate quota handle");
+			return err;
+		}
+		allocated_handle = 1;
+	}
+
+	h->qh_qf.sbi = sbi;
+	h->qh_qf.ino = qf_ino;
+	h->write = quota_write_nomount;
+	h->read = quota_read_nomount;
+	h->qh_file_flags = flags;
+	h->qh_io_flags = 0;
+	h->qh_type = qtype;
+	h->qh_fmt = QFMT_VFS_V1;
+	memset(&h->qh_info, 0, sizeof(h->qh_info));
+	h->qh_ops = &quotafile_ops_2;
+
+	if (h->qh_ops->check_file &&
+	    (h->qh_ops->check_file(h, qtype) == 0)) {
+		log_err("qh_ops->check_file failed");
+		err = EIO;
+		goto errout;
+	}
+
+	if (h->qh_ops->init_io && (h->qh_ops->init_io(h) < 0)) {
+		log_err("qh_ops->init_io failed");
+		err = EIO;
+		goto errout;
+	}
+	if (allocated_handle)
+		qctx->quota_file[qtype] = h;
+errout:
+	return err;
+}
+
+/*
+ * Create new quotafile of specified format on given filesystem
+ */
+errcode_t quota_file_create(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		enum quota_type qtype)
+{
+	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
+	f2fs_ino_t qf_inum = sb->qf_ino[qtype];
+	errcode_t err = 0;
+
+	h->qh_qf.sbi = sbi;
+	h->qh_qf.ino = qf_inum;
+	h->write = quota_write_nomount;
+	h->read = quota_read_nomount;
+
+	log_debug("Creating quota ino=%u, type=%d", qf_inum, qtype);
+	h->qh_io_flags = 0;
+	h->qh_type = qtype;
+	h->qh_fmt = QFMT_VFS_V1;
+	memset(&h->qh_info, 0, sizeof(h->qh_info));
+	h->qh_ops = &quotafile_ops_2;
+
+	if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) {
+		log_err("qh_ops->new_io failed");
+		err = EIO;
+	}
+
+	return err;
+}
+
+/*
+ * Close quotafile and release handle
+ */
+errcode_t quota_file_close(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		int update_filesize)
+{
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
+	quota_ctx_t qctx = fsck->qctx;
+
+        if (h->qh_io_flags & IOFL_INFODIRTY) {
+		if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0)
+			return EIO;
+		h->qh_io_flags &= ~IOFL_INFODIRTY;
+	}
+	if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0)
+		return EIO;
+	if (update_filesize) {
+		f2fs_filesize_update(sbi, h->qh_qf.ino, h->qh_qf.filesize);
+	}
+	if (qctx->quota_file[h->qh_type] == h)
+		quota_free_mem(&qctx->quota_file[h->qh_type]);
+	return 0;
+}
+
+/*
+ * Create empty quota structure
+ */
+struct dquot *get_empty_dquot(void)
+{
+	struct dquot *dquot;
+
+	if (quota_get_memzero(sizeof(struct dquot), &dquot)) {
+		log_err("Failed to allocate dquot");
+		return NULL;
+	}
+
+	dquot->dq_id = -1;
+	return dquot;
+}
diff --git a/fsck/quotaio.h b/fsck/quotaio.h
new file mode 100644
index 0000000..e796eb1
--- /dev/null
+++ b/fsck/quotaio.h
@@ -0,0 +1,255 @@
+/** quotaio.h
+ *
+ * Interface to the quota library.
+ *
+ * The quota library provides interface for creating and updating the quota
+ * files and the ext4 superblock fields. It supports the new VFS_V1 quota
+ * format. The quota library also provides support for keeping track of quotas
+ * in memory.
+ *
+ * Aditya Kali <adityakali@google.com>
+ * Header of IO operations for quota utilities
+ *
+ * Jan Kara <jack@suse.cz>
+ *
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#ifndef GUARD_QUOTAIO_H
+#define GUARD_QUOTAIO_H
+
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#include "dict.h"
+#include "f2fs_fs.h"
+#include "f2fs.h"
+#include "node.h"
+#include "fsck.h"
+
+#include "dqblk_v2.h"
+
+typedef int64_t qsize_t;	/* Type in which we store size limitations */
+typedef int32_t f2fs_ino_t;
+typedef int errcode_t;
+
+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)
+
+typedef struct quota_ctx *quota_ctx_t;
+
+struct quota_ctx {
+	struct f2fs_sb_info *sbi;
+	struct dict_t *quota_dict[MAXQUOTAS];
+	struct quota_handle *quota_file[MAXQUOTAS];
+	struct dict_t linked_inode_dict;
+};
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define INITQMAGICS {\
+	0xd9c01f11,	/* USRQUOTA */\
+	0xd9c01927,	/* GRPQUOTA */\
+	0xd9c03f14	/* PRJQUOTA */\
+}
+
+/* Size of blocks in which are counted size limits in generic utility parts */
+#define QUOTABLOCK_BITS 10
+#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
+#define toqb(x) (((x) + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS)
+
+/* Quota format type IDs */
+#define	QFMT_VFS_OLD 1
+#define	QFMT_VFS_V0 2
+#define	QFMT_VFS_V1 4
+
+/*
+ * The following constants define the default amount of time given a user
+ * before the soft limits are treated as hard limits (usually resulting
+ * in an allocation failure). The timer is started when the user crosses
+ * their soft limit, it is reset when they go below their soft limit.
+ */
+#define MAX_IQ_TIME  604800	/* (7*24*60*60) 1 week */
+#define MAX_DQ_TIME  604800	/* (7*24*60*60) 1 week */
+
+#define IOFL_INFODIRTY	0x01	/* Did info change? */
+
+struct quotafile_ops;
+
+/* Generic information about quotafile */
+struct util_dqinfo {
+	time_t dqi_bgrace;	/* Block grace time for given quotafile */
+	time_t dqi_igrace;	/* Inode grace time for given quotafile */
+	union {
+		struct v2_mem_dqinfo v2_mdqi;
+	} u;			/* Format specific info about quotafile */
+};
+
+struct quota_file {
+	struct f2fs_sb_info *sbi;
+	f2fs_ino_t ino;
+	int64_t filesize;
+};
+
+/* Structure for one opened quota file */
+struct quota_handle {
+	enum quota_type qh_type;	/* Type of quotafile */
+	int qh_fmt;		/* Quotafile format */
+	int qh_file_flags;
+	int qh_io_flags;	/* IO flags for file */
+	struct quota_file qh_qf;
+	unsigned int (*read)(struct quota_file *qf, long offset,
+			 void *buf, unsigned int size);
+	unsigned int (*write)(struct quota_file *qf, long offset,
+			  void *buf, unsigned int size);
+	struct quotafile_ops *qh_ops;	/* Operations on quotafile */
+	struct util_dqinfo qh_info;	/* Generic quotafile info */
+};
+
+/* Utility quota block */
+struct util_dqblk {
+	qsize_t dqb_ihardlimit;
+	qsize_t dqb_isoftlimit;
+	qsize_t dqb_curinodes;
+	qsize_t dqb_bhardlimit;
+	qsize_t dqb_bsoftlimit;
+	qsize_t dqb_curspace;
+	time_t dqb_btime;
+	time_t dqb_itime;
+	union {
+		struct v2_mem_dqblk v2_mdqb;
+	} u;			/* Format specific dquot information */
+};
+
+/* Structure for one loaded quota */
+struct dquot {
+	struct dquot *dq_next;	/* Pointer to next dquot in the list */
+	qid_t dq_id;		/* ID dquot belongs to */
+	int dq_flags;		/* Some flags for utils */
+	struct quota_handle *dq_h;	/* Handle of quotafile for this dquot */
+	struct util_dqblk dq_dqb;	/* Parsed data of dquot */
+};
+
+#define DQF_SEEN	0x0001
+
+/* Structure of quotafile operations */
+struct quotafile_ops {
+	/* Check whether quotafile is in our format */
+	int (*check_file) (struct quota_handle *h, int type);
+	/* Open quotafile */
+	int (*init_io) (struct quota_handle *h);
+	/* Create new quotafile */
+	int (*new_io) (struct quota_handle *h);
+	/* Write all changes and close quotafile */
+	int (*end_io) (struct quota_handle *h);
+	/* Write info about quotafile */
+	int (*write_info) (struct quota_handle *h);
+	/* Read dquot into memory */
+	struct dquot *(*read_dquot) (struct quota_handle *h, qid_t id);
+	/* Write given dquot to disk */
+	int (*commit_dquot) (struct dquot *dquot);
+	/* Scan quotafile and call callback on every structure */
+	int (*scan_dquots) (struct quota_handle *h,
+			    int (*process_dquot) (struct dquot *dquot,
+						  void *data),
+			    void *data);
+	/* Function to print format specific file information */
+	int (*report) (struct quota_handle *h, int verbose);
+};
+
+#ifdef __CHECKER__
+# ifndef __bitwise
+#  define __bitwise             __attribute__((bitwise))
+# endif
+#define __force                 __attribute__((force))
+#else
+# ifndef __bitwise
+#  define __bitwise
+# endif
+#define __force
+#endif
+
+#define be32_to_cpu(n) ntohl(n)
+
+/* Open existing quotafile of given type (and verify its format) on given
+ * filesystem. */
+errcode_t quota_file_open(struct f2fs_sb_info *sbi, struct quota_handle *h,
+			  enum quota_type qtype, int flags);
+
+/* Create new quotafile of specified format on given filesystem */
+errcode_t quota_file_create(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		enum quota_type qtype);
+
+/* Close quotafile */
+errcode_t quota_file_close(struct f2fs_sb_info *sbi, struct quota_handle *h,
+		int update_filesize);
+
+/* Get empty quota structure */
+struct dquot *get_empty_dquot(void);
+const char *quota_type2name(enum quota_type qtype);
+void update_grace_times(struct dquot *q);
+
+/* In mkquota.c */
+errcode_t quota_init_context(struct f2fs_sb_info *sbi);
+void quota_data_inodes(quota_ctx_t qctx, struct f2fs_inode *inode, int adjust);
+void quota_data_add(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space);
+void quota_data_sub(quota_ctx_t qctx, struct f2fs_inode *inode, qsize_t space);
+errcode_t quota_write_inode(struct f2fs_sb_info *sbi, enum quota_type qtype);
+void quota_add_inode_usage(quota_ctx_t qctx, f2fs_ino_t ino,
+		struct f2fs_inode* inode);
+void quota_release_context(quota_ctx_t *qctx);
+errcode_t quota_compare_and_update(struct f2fs_sb_info *sbi,
+		enum quota_type qtype, int *usage_inconsistent);
+
+static inline errcode_t quota_get_mem(unsigned long size, void *ptr)
+{
+        void *pp;
+
+        pp = malloc(size);
+        if (!pp)
+                return -1;
+        memcpy(ptr, &pp, sizeof (pp));
+        return 0;
+}
+
+static inline errcode_t quota_get_memzero(unsigned long size, void *ptr)
+{
+        void *pp;
+
+        pp = malloc(size);
+        if (!pp)
+                return -1;
+        memset(pp, 0, size);
+        memcpy(ptr, &pp, sizeof(pp));
+        return 0;
+}
+
+static inline errcode_t quota_free_mem(void *ptr)
+{
+        void *p;
+
+        memcpy(&p, ptr, sizeof(p));
+        free(p);
+        p = 0;
+        memcpy(ptr, &p, sizeof(p));
+        return 0;
+}
+
+#endif /* GUARD_QUOTAIO_H */
diff --git a/fsck/quotaio_tree.c b/fsck/quotaio_tree.c
new file mode 100644
index 0000000..5aef228
--- /dev/null
+++ b/fsck/quotaio_tree.c
@@ -0,0 +1,679 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "quotaio_tree.h"
+#include "quotaio.h"
+
+typedef char *dqbuf_t;
+
+#define freedqbuf(buf)		quota_free_mem(&buf)
+
+static inline dqbuf_t getdqbuf(void)
+{
+	dqbuf_t buf;
+	if (quota_get_memzero(QT_BLKSIZE, &buf)) {
+		log_err("Failed to allocate dqbuf");
+		return NULL;
+	}
+
+	return buf;
+}
+
+/* Is given dquot empty? */
+int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk)
+{
+	unsigned int i;
+
+	for (i = 0; i < info->dqi_entry_size; i++)
+		if (disk[i])
+			return 0;
+	return 1;
+}
+
+int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info)
+{
+	return (QT_BLKSIZE - sizeof(struct qt_disk_dqdbheader)) /
+		info->dqi_entry_size;
+}
+
+static int get_index(qid_t id, int depth)
+{
+	return (id >> ((QT_TREEDEPTH - depth - 1) * 8)) & 0xff;
+}
+
+static inline void mark_quotafile_info_dirty(struct quota_handle *h)
+{
+	h->qh_io_flags |= IOFL_INFODIRTY;
+}
+
+/* Read given block */
+static void read_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf)
+{
+	int err;
+
+	err = h->read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
+			QT_BLKSIZE);
+	if (err < 0)
+		log_err("Cannot read block %u: %s", blk, strerror(errno));
+	else if (err != QT_BLKSIZE)
+		memset(buf + err, 0, QT_BLKSIZE - err);
+}
+
+/* Write block */
+static int write_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf)
+{
+	int err;
+
+	err = h->write(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
+			QT_BLKSIZE);
+	if (err < 0 && errno != ENOSPC)
+		log_err("Cannot write block (%u): %s", blk, strerror(errno));
+	if (err != QT_BLKSIZE)
+		return -ENOSPC;
+	return 0;
+}
+
+/* Get free block in file (either from free list or create new one) */
+static int get_free_dqblk(struct quota_handle *h)
+{
+	dqbuf_t buf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	int blk;
+
+	if (!buf)
+		return -ENOMEM;
+
+	if (info->dqi_free_blk) {
+		blk = info->dqi_free_blk;
+		read_blk(h, blk, buf);
+		info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free);
+	} else {
+		memset(buf, 0, QT_BLKSIZE);
+		/* Assure block allocation... */
+		if (write_blk(h, info->dqi_blocks, buf) < 0) {
+			freedqbuf(buf);
+			log_err("Cannot allocate new quota block "
+				"(out of disk space).");
+			return -ENOSPC;
+		}
+		blk = info->dqi_blocks++;
+	}
+	mark_quotafile_info_dirty(h);
+	freedqbuf(buf);
+	return blk;
+}
+
+/* Put given block to free list */
+static void put_free_dqblk(struct quota_handle *h, dqbuf_t buf,
+			   unsigned int blk)
+{
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	dh->dqdh_next_free = cpu_to_le32(info->dqi_free_blk);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	dh->dqdh_entries = cpu_to_le16(0);
+	info->dqi_free_blk = blk;
+	mark_quotafile_info_dirty(h);
+	write_blk(h, blk, buf);
+}
+
+/* Remove given block from the list of blocks with free entries */
+static void remove_free_dqentry(struct quota_handle *h, dqbuf_t buf,
+				unsigned int blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	unsigned int nextblk = le32_to_cpu(dh->dqdh_next_free), prevblk =
+		le32_to_cpu(dh->dqdh_prev_free);
+
+	if (!tmpbuf)
+		return;
+
+	if (nextblk) {
+		read_blk(h, nextblk, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
+				dh->dqdh_prev_free;
+		write_blk(h, nextblk, tmpbuf);
+	}
+	if (prevblk) {
+		read_blk(h, prevblk, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free =
+				dh->dqdh_next_free;
+		write_blk(h, prevblk, tmpbuf);
+	} else {
+		h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = nextblk;
+		mark_quotafile_info_dirty(h);
+	}
+	freedqbuf(tmpbuf);
+	dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0);
+	write_blk(h, blk, buf);	/* No matter whether write succeeds
+				 * block is out of list */
+}
+
+/* Insert given block to the beginning of list with free entries */
+static void insert_free_dqentry(struct quota_handle *h, dqbuf_t buf,
+				unsigned int blk)
+{
+	dqbuf_t tmpbuf = getdqbuf();
+	struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	if (!tmpbuf)
+		return;
+
+	dh->dqdh_next_free = cpu_to_le32(info->dqi_free_entry);
+	dh->dqdh_prev_free = cpu_to_le32(0);
+	write_blk(h, blk, buf);
+	if (info->dqi_free_entry) {
+		read_blk(h, info->dqi_free_entry, tmpbuf);
+		((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
+				cpu_to_le32(blk);
+		write_blk(h, info->dqi_free_entry, tmpbuf);
+	}
+	freedqbuf(tmpbuf);
+	info->dqi_free_entry = blk;
+	mark_quotafile_info_dirty(h);
+}
+
+/* Find space for dquot */
+static unsigned int find_free_dqentry(struct quota_handle *h,
+				      struct dquot *dquot, int *err)
+{
+	int blk, i;
+	struct qt_disk_dqdbheader *dh;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	char *ddquot;
+	dqbuf_t buf;
+
+	*err = 0;
+	buf = getdqbuf();
+	if (!buf) {
+		*err = -ENOMEM;
+		return 0;
+	}
+
+	dh = (struct qt_disk_dqdbheader *)buf;
+	if (info->dqi_free_entry) {
+		blk = info->dqi_free_entry;
+		read_blk(h, blk, buf);
+	} else {
+		blk = get_free_dqblk(h);
+		if (blk < 0) {
+			freedqbuf(buf);
+			*err = blk;
+			return 0;
+		}
+		memset(buf, 0, QT_BLKSIZE);
+		info->dqi_free_entry = blk;
+		mark_quotafile_info_dirty(h);
+	}
+
+	/* Block will be full? */
+	if (le16_to_cpu(dh->dqdh_entries) + 1 >=
+	    qtree_dqstr_in_blk(info))
+		remove_free_dqentry(h, buf, blk);
+
+	dh->dqdh_entries =
+		cpu_to_le16(le16_to_cpu(dh->dqdh_entries) + 1);
+	/* Find free structure in block */
+	ddquot = buf + sizeof(struct qt_disk_dqdbheader);
+	for (i = 0;
+	     i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot);
+	     i++)
+		ddquot += info->dqi_entry_size;
+
+	if (i == qtree_dqstr_in_blk(info))
+		log_err("find_free_dqentry(): Data block full unexpectedly.");
+
+	write_blk(h, blk, buf);
+	dquot->dq_dqb.u.v2_mdqb.dqb_off =
+		(blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) +
+		i * info->dqi_entry_size;
+	freedqbuf(buf);
+	return blk;
+}
+
+/* Insert reference to structure into the trie */
+static int do_insert_tree(struct quota_handle *h, struct dquot *dquot,
+			  unsigned int * treeblk, int depth)
+{
+	dqbuf_t buf;
+	int newson = 0, newact = 0;
+	__le32 *ref;
+	unsigned int newblk;
+	int ret = 0;
+
+	log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth);
+	buf = getdqbuf();
+	if (!buf)
+		return -ENOMEM;
+
+	if (!*treeblk) {
+		ret = get_free_dqblk(h);
+		if (ret < 0)
+			goto out_buf;
+		*treeblk = ret;
+		memset(buf, 0, QT_BLKSIZE);
+		newact = 1;
+	} else {
+		read_blk(h, *treeblk, buf);
+	}
+
+	ref = (__le32 *) buf;
+	newblk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (!newblk)
+		newson = 1;
+	if (depth == QT_TREEDEPTH - 1) {
+		if (newblk)
+			log_err("Inserting already present quota entry "
+				"(block %u).",
+				ref[get_index(dquot->dq_id, depth)]);
+		newblk = find_free_dqentry(h, dquot, &ret);
+	} else {
+		ret = do_insert_tree(h, dquot, &newblk, depth + 1);
+	}
+
+	if (newson && ret >= 0) {
+		ref[get_index(dquot->dq_id, depth)] =
+			cpu_to_le32(newblk);
+		write_blk(h, *treeblk, buf);
+	} else if (newact && ret < 0) {
+		put_free_dqblk(h, buf, *treeblk);
+	}
+
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Wrapper for inserting quota structure into tree */
+static void dq_insert_tree(struct quota_handle *h, struct dquot *dquot)
+{
+	unsigned int tmp = QT_TREEOFF;
+
+	if (do_insert_tree(h, dquot, &tmp, 0) < 0)
+		log_err("Cannot write quota (id %u): %s",
+			(unsigned int) dquot->dq_id, strerror(errno));
+}
+
+/* Write dquot to file */
+void qtree_write_dquot(struct dquot *dquot)
+{
+	errcode_t retval;
+	unsigned int ret;
+	char *ddquot;
+	struct quota_handle *h = dquot->dq_h;
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+
+
+	log_debug("writing ddquot 1: off=%llu, info->dqi_entry_size=%u",
+			dquot->dq_dqb.u.v2_mdqb.dqb_off,
+			info->dqi_entry_size);
+	retval = quota_get_mem(info->dqi_entry_size, &ddquot);
+	if (retval) {
+		errno = ENOMEM;
+		log_err("Quota write failed (id %u): %s",
+			(unsigned int)dquot->dq_id, strerror(errno));
+		return;
+	}
+	memset(ddquot, 0, info->dqi_entry_size);
+
+	if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) {
+		dq_insert_tree(dquot->dq_h, dquot);
+	}
+	info->dqi_ops->mem2disk_dqblk(ddquot, dquot);
+	log_debug("writing ddquot 2: off=%llu, info->dqi_entry_size=%u",
+			dquot->dq_dqb.u.v2_mdqb.dqb_off,
+			info->dqi_entry_size);
+	ret = h->write(&h->qh_qf, dquot->dq_dqb.u.v2_mdqb.dqb_off, ddquot,
+			info->dqi_entry_size);
+
+	if (ret != info->dqi_entry_size) {
+		if (ret > 0)
+			errno = ENOSPC;
+		log_err("Quota write failed (id %u): %s",
+			(unsigned int)dquot->dq_id, strerror(errno));
+	}
+	quota_free_mem(&ddquot);
+}
+
+/* Free dquot entry in data block */
+static void free_dqentry(struct quota_handle *h, struct dquot *dquot,
+			 unsigned int blk)
+{
+	struct qt_disk_dqdbheader *dh;
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+
+	if (!buf)
+		return;
+
+	if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk)
+		log_err("Quota structure has offset to other block (%u) "
+			"than it should (%u).", blk,
+			  (unsigned int) (dquot->dq_dqb.u.v2_mdqb.dqb_off >>
+				  QT_BLKSIZE_BITS));
+
+	read_blk(h, blk, buf);
+	dh = (struct qt_disk_dqdbheader *)buf;
+	dh->dqdh_entries =
+		cpu_to_le16(le16_to_cpu(dh->dqdh_entries) - 1);
+
+	if (!le16_to_cpu(dh->dqdh_entries)) {	/* Block got free? */
+		remove_free_dqentry(h, buf, blk);
+		put_free_dqblk(h, buf, blk);
+	} else {
+		memset(buf + (dquot->dq_dqb.u.v2_mdqb.dqb_off &
+			      ((1 << QT_BLKSIZE_BITS) - 1)),
+		       0, info->dqi_entry_size);
+
+		/* First free entry? */
+		if (le16_to_cpu(dh->dqdh_entries) ==
+				qtree_dqstr_in_blk(info) - 1)
+			/* This will also write data block */
+			insert_free_dqentry(h, buf, blk);
+		else
+			write_blk(h, blk, buf);
+	}
+	dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
+	freedqbuf(buf);
+}
+
+/* Remove reference to dquot from tree */
+static void remove_tree(struct quota_handle *h, struct dquot *dquot,
+			unsigned int * blk, int depth)
+{
+	dqbuf_t buf = getdqbuf();
+	unsigned int newblk;
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return;
+
+	read_blk(h, *blk, buf);
+	newblk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (depth == QT_TREEDEPTH - 1) {
+		free_dqentry(h, dquot, newblk);
+		newblk = 0;
+	} else {
+		remove_tree(h, dquot, &newblk, depth + 1);
+	}
+
+	if (!newblk) {
+		int i;
+
+		ref[get_index(dquot->dq_id, depth)] = cpu_to_le32(0);
+
+		/* Block got empty? */
+		for (i = 0; i < QT_BLKSIZE && !buf[i]; i++);
+
+		/* Don't put the root block into the free block list */
+		if (i == QT_BLKSIZE && *blk != QT_TREEOFF) {
+			put_free_dqblk(h, buf, *blk);
+			*blk = 0;
+		} else {
+			write_blk(h, *blk, buf);
+		}
+	}
+	freedqbuf(buf);
+}
+
+/* Delete dquot from tree */
+void qtree_delete_dquot(struct dquot *dquot)
+{
+	unsigned int tmp = QT_TREEOFF;
+
+	if (!dquot->dq_dqb.u.v2_mdqb.dqb_off)	/* Even not allocated? */
+		return;
+	remove_tree(dquot->dq_h, dquot, &tmp, 0);
+}
+
+/* Find entry in block */
+static long find_block_dqentry(struct quota_handle *h,
+				      struct dquot *dquot, unsigned int blk)
+{
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+	int i;
+	char *ddquot = buf + sizeof(struct qt_disk_dqdbheader);
+
+	if (!buf)
+		return -ENOMEM;
+
+	read_blk(h, blk, buf);
+	for (i = 0;
+	     i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot);
+	     i++)
+		ddquot += info->dqi_entry_size;
+
+	if (i == qtree_dqstr_in_blk(info))
+		log_err("Quota for id %u referenced but not present.",
+			dquot->dq_id);
+	freedqbuf(buf);
+	return (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) +
+		i * info->dqi_entry_size;
+}
+
+/* Find entry for given id in the tree */
+static long find_tree_dqentry(struct quota_handle *h,
+				     struct dquot *dquot,
+				     unsigned int blk, int depth)
+{
+	dqbuf_t buf = getdqbuf();
+	long ret = 0;
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return -ENOMEM;
+
+	read_blk(h, blk, buf);
+	ret = 0;
+	blk = le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+	if (!blk)	/* No reference? */
+		goto out_buf;
+	if (depth < QT_TREEDEPTH - 1)
+		ret = find_tree_dqentry(h, dquot, blk, depth + 1);
+	else
+		ret = find_block_dqentry(h, dquot, blk);
+out_buf:
+	freedqbuf(buf);
+	return ret;
+}
+
+/* Find entry for given id in the tree - wrapper function */
+static inline long find_dqentry(struct quota_handle *h,
+				       struct dquot *dquot)
+{
+	return find_tree_dqentry(h, dquot, QT_TREEOFF, 0);
+}
+
+/*
+ *  Read dquot from disk.
+ */
+struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id)
+{
+	struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+	long offset;
+	unsigned int ret;
+	char *ddquot;
+	struct dquot *dquot = get_empty_dquot();
+
+	if (!dquot)
+		return NULL;
+	if (quota_get_mem(info->dqi_entry_size, &ddquot)) {
+		quota_free_mem(&dquot);
+		return NULL;
+	}
+
+	dquot->dq_id = id;
+	dquot->dq_h = h;
+	dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
+	memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk));
+
+	offset = find_dqentry(h, dquot);
+	if (offset > 0) {
+		dquot->dq_dqb.u.v2_mdqb.dqb_off = offset;
+		ret = h->read(&h->qh_qf, offset, ddquot,
+			info->dqi_entry_size);
+		if (ret != info->dqi_entry_size) {
+			if (ret > 0)
+				errno = EIO;
+			log_err("Cannot read quota structure for id %u: %s",
+				dquot->dq_id, strerror(errno));
+		}
+		info->dqi_ops->disk2mem_dqblk(dquot, ddquot);
+	}
+	quota_free_mem(&ddquot);
+	return dquot;
+}
+
+/*
+ * Scan all dquots in file and call callback on each
+ */
+#define set_bit(bmp, ind) ((bmp)[(ind) >> 3] |= (1 << ((ind) & 7)))
+#define get_bit(bmp, ind) ((bmp)[(ind) >> 3] & (1 << ((ind) & 7)))
+
+static int report_block(struct dquot *dquot, unsigned int blk, char *bitmap,
+			int (*process_dquot) (struct dquot *, void *),
+			void *data)
+{
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+	dqbuf_t buf = getdqbuf();
+	struct qt_disk_dqdbheader *dh;
+	char *ddata;
+	int entries, i;
+
+	if (!buf)
+		return 0;
+
+	set_bit(bitmap, blk);
+	read_blk(dquot->dq_h, blk, buf);
+	dh = (struct qt_disk_dqdbheader *)buf;
+	ddata = buf + sizeof(struct qt_disk_dqdbheader);
+	entries = le16_to_cpu(dh->dqdh_entries);
+	for (i = 0; i < qtree_dqstr_in_blk(info);
+			i++, ddata += info->dqi_entry_size)
+		if (!qtree_entry_unused(info, ddata)) {
+			dquot->dq_dqb.u.v2_mdqb.dqb_off =
+				(blk << QT_BLKSIZE_BITS) +
+				sizeof(struct qt_disk_dqdbheader) +
+				i * info->dqi_entry_size;
+			info->dqi_ops->disk2mem_dqblk(dquot, ddata);
+			if (process_dquot(dquot, data) < 0)
+				break;
+		}
+	freedqbuf(buf);
+	return entries;
+}
+
+static int check_reference(struct quota_handle *h, unsigned int blk)
+{
+	if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) {
+		log_err("Illegal reference (%u >= %u) in %s quota file. "
+			"Quota file is probably corrupted.\n"
+			"Please run fsck (8) to fix it.",
+			blk,
+			h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks,
+			quota_type2name(h->qh_type));
+		return -1;
+	}
+	return 0;
+}
+
+/* Return 0 for successful run */
+static int report_tree(struct dquot *dquot, unsigned int blk, int depth,
+		       char *bitmap, int *entries,
+		       int (*process_dquot) (struct dquot *, void *),
+		       void *data)
+{
+	int i;
+	dqbuf_t buf = getdqbuf();
+	__le32 *ref = (__le32 *) buf;
+
+	if (!buf)
+		return -1;
+
+	read_blk(dquot->dq_h, blk, buf);
+	for (i = 0; i < QT_BLKSIZE >> 2; i++) {
+		blk = le32_to_cpu(ref[i]);
+		if (blk == 0)
+			continue;
+
+		if (check_reference(dquot->dq_h, blk))
+			break;
+
+		if (depth == QT_TREEDEPTH - 1) {
+			if (!get_bit(bitmap, blk))
+				*entries += report_block(dquot, blk, bitmap,
+							process_dquot, data);
+		} else {
+			if (report_tree(dquot, blk, depth + 1, bitmap, entries,
+						process_dquot, data))
+				break;
+		}
+	}
+	freedqbuf(buf);
+	return (i < QT_BLKSIZE >> 2) ? -1 : 0;
+}
+
+static unsigned int find_set_bits(char *bmp, int blocks)
+{
+	unsigned int	used = 0;
+	int		i;
+
+	for (i = 0; i < blocks; i++)
+		if (get_bit(bmp, i))
+			used++;
+	return used;
+}
+
+int qtree_scan_dquots(struct quota_handle *h,
+		      int (*process_dquot) (struct dquot *, void *),
+		      void *data)
+{
+	struct v2_mem_dqinfo *v2info = &h->qh_info.u.v2_mdqi;
+	struct qtree_mem_dqinfo *info = &v2info->dqi_qtree;
+	struct dquot *dquot = get_empty_dquot();
+	char *bitmap = NULL;
+	int ret = -1;
+	int entries = 0;
+
+	if (!dquot)
+		return -1;
+
+	dquot->dq_h = h;
+	if (quota_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap))
+		goto out;
+	if (report_tree(dquot, QT_TREEOFF, 0, bitmap, &entries, process_dquot,
+				data))
+		goto out;
+
+	v2info->dqi_used_entries = entries;
+	v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks);
+	ret = 0;
+
+out:
+	if (bitmap)
+		quota_free_mem(&bitmap);
+	if (dquot)
+		quota_free_mem(&dquot);
+
+	return ret;
+}
diff --git a/fsck/quotaio_tree.h b/fsck/quotaio_tree.h
new file mode 100644
index 0000000..4ca2d7f
--- /dev/null
+++ b/fsck/quotaio_tree.h
@@ -0,0 +1,66 @@
+/*
+ * Definitions of structures for vfsv0 quota format
+ */
+
+#ifndef _LINUX_QUOTA_TREE_H
+#define _LINUX_QUOTA_TREE_H
+
+#include <inttypes.h>
+#include <linux/types.h>
+#include <sys/types.h>
+
+typedef __u32 qid_t;        /* Type in which we store ids in memory */
+
+#define QT_TREEOFF	1	/* Offset of tree in file in blocks */
+#define QT_TREEDEPTH	4	/* Depth of quota tree */
+#define QT_BLKSIZE_BITS	10
+#define QT_BLKSIZE (1 << QT_BLKSIZE_BITS)	/* Size of block with quota
+						 * structures */
+
+/*
+ *  Structure of header of block with quota structures. It is padded to 16 bytes
+ *  so there will be space for exactly 21 quota-entries in a block
+ */
+struct qt_disk_dqdbheader {
+	__le32 dqdh_next_free;	/* Number of next block with free
+					 * entry */
+	__le32 dqdh_prev_free; /* Number of previous block with free
+				   * entry */
+	__le16 dqdh_entries; /* Number of valid entries in block */
+	__le16 dqdh_pad1;
+	__le32 dqdh_pad2;
+} __attribute__ ((packed));
+
+struct dquot;
+struct quota_handle;
+
+/* Operations */
+struct qtree_fmt_operations {
+	/* Convert given entry from in memory format to disk one */
+	void (*mem2disk_dqblk)(void *disk, struct dquot *dquot);
+	/* Convert given entry from disk format to in memory one */
+	void (*disk2mem_dqblk)(struct dquot *dquot, void *disk);
+	/* Is this structure for given id? */
+	int (*is_id)(void *disk, struct dquot *dquot);
+};
+
+/* Inmemory copy of version specific information */
+struct qtree_mem_dqinfo {
+	unsigned int dqi_blocks;	/* # of blocks in quota file */
+	unsigned int dqi_free_blk;	/* First block in list of free blocks */
+	unsigned int dqi_free_entry;	/* First block with free entry */
+	unsigned int dqi_entry_size;	/* Size of quota entry in quota file */
+	struct qtree_fmt_operations *dqi_ops;	/* Operations for entry
+						 * manipulation */
+};
+
+void qtree_write_dquot(struct dquot *dquot);
+struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id);
+void qtree_delete_dquot(struct dquot *dquot);
+int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk);
+int qtree_scan_dquots(struct quota_handle *h,
+		int (*process_dquot) (struct dquot *, void *), void *data);
+
+int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info);
+
+#endif /* _LINUX_QUOTAIO_TREE_H */
diff --git a/fsck/quotaio_v2.c b/fsck/quotaio_v2.c
new file mode 100644
index 0000000..478cd17
--- /dev/null
+++ b/fsck/quotaio_v2.c
@@ -0,0 +1,284 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ * Hyojun Kim <hyojun@google.com> - Ported to f2fs-tools
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include "quotaio_v2.h"
+#include "dqblk_v2.h"
+#include "quotaio_tree.h"
+
+static int v2_check_file(struct quota_handle *h, int type);
+static int v2_init_io(struct quota_handle *h);
+static int v2_new_io(struct quota_handle *h);
+static int v2_write_info(struct quota_handle *h);
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id);
+static int v2_commit_dquot(struct dquot *dquot);
+static int v2_scan_dquots(struct quota_handle *h,
+			  int (*process_dquot) (struct dquot *dquot,
+						void *data),
+			  void *data);
+static int v2_report(struct quota_handle *h, int verbose);
+
+struct quotafile_ops quotafile_ops_2 = {
+	.check_file	= v2_check_file,
+	.init_io 	= v2_init_io,
+	.new_io 	= v2_new_io,
+	.write_info	= v2_write_info,
+	.read_dquot	= v2_read_dquot,
+	.commit_dquot	= v2_commit_dquot,
+	.scan_dquots	= v2_scan_dquots,
+	.report		= v2_report,
+};
+
+/*
+ * Copy dquot from disk to memory
+ */
+static void v2r1_disk2memdqblk(struct dquot *dquot, void *dp)
+{
+	struct util_dqblk *m = &dquot->dq_dqb;
+	struct v2r1_disk_dqblk *d = dp, empty;
+
+	dquot->dq_id = le32_to_cpu(d->dqb_id);
+	m->dqb_ihardlimit = le64_to_cpu(d->dqb_ihardlimit);
+	m->dqb_isoftlimit = le64_to_cpu(d->dqb_isoftlimit);
+	m->dqb_bhardlimit = le64_to_cpu(d->dqb_bhardlimit);
+	m->dqb_bsoftlimit = le64_to_cpu(d->dqb_bsoftlimit);
+	m->dqb_curinodes = le64_to_cpu(d->dqb_curinodes);
+	m->dqb_curspace = le64_to_cpu(d->dqb_curspace);
+	m->dqb_itime = le64_to_cpu(d->dqb_itime);
+	m->dqb_btime = le64_to_cpu(d->dqb_btime);
+
+	memset(&empty, 0, sizeof(struct v2r1_disk_dqblk));
+	empty.dqb_itime = cpu_to_le64(1);
+	if (!memcmp(&empty, dp, sizeof(struct v2r1_disk_dqblk)))
+		m->dqb_itime = 0;
+}
+
+/*
+ * Copy dquot from memory to disk
+ */
+static void v2r1_mem2diskdqblk(void *dp, struct dquot *dquot)
+{
+	struct util_dqblk *m = &dquot->dq_dqb;
+	struct v2r1_disk_dqblk *d = dp;
+
+	d->dqb_ihardlimit = cpu_to_le64(m->dqb_ihardlimit);
+	d->dqb_isoftlimit = cpu_to_le64(m->dqb_isoftlimit);
+	d->dqb_bhardlimit = cpu_to_le64(m->dqb_bhardlimit);
+	d->dqb_bsoftlimit = cpu_to_le64(m->dqb_bsoftlimit);
+	d->dqb_curinodes = cpu_to_le64(m->dqb_curinodes);
+	d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
+	d->dqb_itime = cpu_to_le64(m->dqb_itime);
+	d->dqb_btime = cpu_to_le64(m->dqb_btime);
+	d->dqb_id = cpu_to_le32(dquot->dq_id);
+	if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp))
+		d->dqb_itime = cpu_to_le64(1);
+}
+
+static int v2r1_is_id(void *dp, struct dquot *dquot)
+{
+	struct v2r1_disk_dqblk *d = dp;
+	struct qtree_mem_dqinfo *info =
+			&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree;
+
+	if (qtree_entry_unused(info, dp))
+		return 0;
+	return le32_to_cpu(d->dqb_id) == dquot->dq_id;
+}
+
+static struct qtree_fmt_operations v2r1_fmt_ops = {
+	.mem2disk_dqblk = v2r1_mem2diskdqblk,
+	.disk2mem_dqblk = v2r1_disk2memdqblk,
+	.is_id = v2r1_is_id,
+};
+
+/*
+ * Copy dqinfo from disk to memory
+ */
+static inline void v2_disk2memdqinfo(struct util_dqinfo *m,
+				     struct v2_disk_dqinfo *d)
+{
+	m->dqi_bgrace = le32_to_cpu(d->dqi_bgrace);
+	m->dqi_igrace = le32_to_cpu(d->dqi_igrace);
+	m->u.v2_mdqi.dqi_flags = le32_to_cpu(d->dqi_flags) & V2_DQF_MASK;
+	m->u.v2_mdqi.dqi_qtree.dqi_blocks = le32_to_cpu(d->dqi_blocks);
+	m->u.v2_mdqi.dqi_qtree.dqi_free_blk =
+		le32_to_cpu(d->dqi_free_blk);
+	m->u.v2_mdqi.dqi_qtree.dqi_free_entry =
+				le32_to_cpu(d->dqi_free_entry);
+}
+
+/*
+ * Copy dqinfo from memory to disk
+ */
+static inline void v2_mem2diskdqinfo(struct v2_disk_dqinfo *d,
+				     struct util_dqinfo *m)
+{
+	d->dqi_bgrace = cpu_to_le32(m->dqi_bgrace);
+	d->dqi_igrace = cpu_to_le32(m->dqi_igrace);
+	d->dqi_flags = cpu_to_le32(m->u.v2_mdqi.dqi_flags & V2_DQF_MASK);
+	d->dqi_blocks = cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_blocks);
+	d->dqi_free_blk =
+		cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_blk);
+	d->dqi_free_entry =
+		cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_entry);
+}
+
+static int v2_read_header(struct quota_handle *h, struct v2_disk_dqheader *dqh)
+{
+	if (h->read(&h->qh_qf, 0, dqh, sizeof(struct v2_disk_dqheader)) !=
+			sizeof(struct v2_disk_dqheader))
+		return 0;
+
+	return 1;
+}
+
+/*
+ * Check whether given quota file is in our format
+ */
+static int v2_check_file(struct quota_handle *h, int type)
+{
+	struct v2_disk_dqheader dqh;
+	int file_magics[] = INITQMAGICS;
+	int be_magic;
+
+	if (!v2_read_header(h, &dqh))
+		return 0;
+
+	be_magic = be32_to_cpu((__force __be32)dqh.dqh_magic);
+	if (be_magic == file_magics[type]) {
+		log_err("Your quota file is stored in wrong endianity");
+		return 0;
+	}
+	if (V2_VERSION != le32_to_cpu(dqh.dqh_version))
+		return 0;
+	return 1;
+}
+
+/*
+ * Open quotafile
+ */
+static int v2_init_io(struct quota_handle *h)
+{
+	struct v2_disk_dqinfo ddqinfo;
+
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size =
+		sizeof(struct v2r1_disk_dqblk);
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
+
+	/* Read information about quotafile */
+	if (h->read(&h->qh_qf, V2_DQINFOOFF, &ddqinfo,
+			 sizeof(ddqinfo)) != sizeof(ddqinfo))
+		return -1;
+	v2_disk2memdqinfo(&h->qh_info, &ddqinfo);
+	return 0;
+}
+
+/*
+ * Initialize new quotafile
+ */
+static int v2_new_io(struct quota_handle *h)
+{
+	int file_magics[] = INITQMAGICS;
+	struct v2_disk_dqheader ddqheader;
+	struct v2_disk_dqinfo ddqinfo;
+
+	if (h->qh_fmt != QFMT_VFS_V1)
+		return -1;
+
+	/* Write basic quota header */
+	ddqheader.dqh_magic = cpu_to_le32(file_magics[h->qh_type]);
+	ddqheader.dqh_version = cpu_to_le32(V2_VERSION);
+	if (h->write(&h->qh_qf, 0, &ddqheader, sizeof(ddqheader)) !=
+			sizeof(ddqheader))
+		return -1;
+
+	/* Write information about quotafile */
+	h->qh_info.dqi_bgrace = MAX_DQ_TIME;
+	h->qh_info.dqi_igrace = MAX_IQ_TIME;
+	h->qh_info.u.v2_mdqi.dqi_flags = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks = QT_TREEOFF + 1;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = 0;
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size =
+				sizeof(struct v2r1_disk_dqblk);
+	h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops;
+	v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+	if (h->write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo,
+			  sizeof(ddqinfo)) !=
+	    sizeof(ddqinfo))
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Write information (grace times to file)
+ */
+static int v2_write_info(struct quota_handle *h)
+{
+	struct v2_disk_dqinfo ddqinfo;
+
+	v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+	if (h->write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo, sizeof(ddqinfo)) !=
+			sizeof(ddqinfo))
+		return -1;
+
+	return 0;
+}
+
+/*
+ * Read dquot from disk
+ */
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id)
+{
+	return qtree_read_dquot(h, id);
+}
+
+/*
+ * Commit changes of dquot to disk - it might also mean deleting it when quota
+ * became fake one and user has no blocks.
+ * User can process use 'errno' to detect errstr.
+ */
+static int v2_commit_dquot(struct dquot *dquot)
+{
+	struct util_dqblk *b = &dquot->dq_dqb;
+
+	if (!b->dqb_curspace && !b->dqb_curinodes && !b->dqb_bsoftlimit &&
+	    !b->dqb_isoftlimit && !b->dqb_bhardlimit && !b->dqb_ihardlimit)
+	{
+		qtree_delete_dquot(dquot);
+	} else {
+		qtree_write_dquot(dquot);
+	}
+	return 0;
+}
+
+static int v2_scan_dquots(struct quota_handle *h,
+			  int (*process_dquot) (struct dquot *, void *),
+			  void *data)
+{
+	return qtree_scan_dquots(h, process_dquot, data);
+}
+
+/* Report information about quotafile.
+ * TODO: Not used right now, but we should be able to use this when we add
+ * support to debugfs to read quota files.
+ */
+static int v2_report(struct quota_handle *UNUSED(h), int UNUSED(verbose))
+{
+	log_err("Not Implemented.");
+	return -1;
+}
diff --git a/fsck/quotaio_v2.h b/fsck/quotaio_v2.h
new file mode 100644
index 0000000..de2db27
--- /dev/null
+++ b/fsck/quotaio_v2.h
@@ -0,0 +1,54 @@
+/*
+ *
+ *	Header file for disk format of new quotafile format
+ *
+ */
+
+#ifndef GUARD_QUOTAIO_V2_H
+#define GUARD_QUOTAIO_V2_H
+
+#include <sys/types.h>
+#include "quotaio.h"
+
+/* Offset of info header in file */
+#define V2_DQINFOOFF		sizeof(struct v2_disk_dqheader)
+/* Supported version of quota-tree format */
+#define V2_VERSION 1
+
+struct v2_disk_dqheader {
+	__le32 dqh_magic;	/* Magic number identifying file */
+	__le32 dqh_version;	/* File version */
+} __attribute__ ((packed));
+
+/* Flags for version specific files */
+#define V2_DQF_MASK  0x0000	/* Mask for all valid ondisk flags */
+
+/* Header with type and version specific information */
+struct v2_disk_dqinfo {
+	__le32 dqi_bgrace;	/* Time before block soft limit becomes
+				 * hard limit */
+	__le32 dqi_igrace;	/* Time before inode soft limit becomes
+				 * hard limit */
+	__le32 dqi_flags;	/* Flags for quotafile (DQF_*) */
+	__le32 dqi_blocks;	/* Number of blocks in file */
+	__le32 dqi_free_blk;	/* Number of first free block in the list */
+	__le32 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/fsck/segment.c b/fsck/segment.c
index e13f147..efbd667 100644
--- a/fsck/segment.c
+++ b/fsck/segment.c
@@ -27,9 +27,10 @@ static void write_inode(u64 blkaddr, struct f2fs_node *inode)
 void reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
 			struct f2fs_summary *sum, int type)
 {
+	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct seg_entry *se;
-	u64 blkaddr;
-	u64 offset;
+	u64 blkaddr, offset;
+	u64 old_blkaddr = *to;
 
 	blkaddr = SM_I(sbi)->main_blkaddr;
 
@@ -43,7 +44,16 @@ void reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
 	se->type = type;
 	se->valid_blocks++;
 	f2fs_set_bit(offset, (char *)se->cur_valid_map);
-	sbi->total_valid_block_count++;
+	if (c.func == FSCK) {
+		f2fs_set_main_bitmap(sbi, blkaddr, type);
+		f2fs_set_sit_bitmap(sbi, blkaddr);
+	}
+
+	if (old_blkaddr == NULL_ADDR) {
+		sbi->total_valid_block_count++;
+		if (c.func == FSCK)
+			fsck->chk.valid_blk_cnt++;
+	}
 	se->dirty = 1;
 
 	/* read/write SSA */
@@ -56,6 +66,7 @@ void new_data_block(struct f2fs_sb_info *sbi, void *block,
 {
 	struct f2fs_summary sum;
 	struct node_info ni;
+	int blkaddr = datablock_addr(dn->node_blk, dn->ofs_in_node);
 
 	ASSERT(dn->node_blk);
 	memset(block, 0, BLOCK_SZ);
@@ -64,7 +75,10 @@ void new_data_block(struct f2fs_sb_info *sbi, void *block,
 	set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
 	reserve_new_block(sbi, &dn->data_blkaddr, &sum, type);
 
-	inc_inode_blocks(dn);
+	if (blkaddr == NULL_ADDR)
+		inc_inode_blocks(dn);
+	else if (blkaddr == NEW_ADDR)
+		dn->idirty = 1;
 	set_data_blkaddr(dn);
 }
 
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index d26b7cf..c2b2454 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -24,6 +24,15 @@
 #include <linux/blkzoned.h>
 #endif
 
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) x
+#else
+# define UNUSED(x) x
+#endif
+
 typedef u_int64_t	u64;
 typedef u_int32_t	u32;
 typedef u_int16_t	u16;
@@ -1180,6 +1189,16 @@ static inline __le64 get_cp_crc(struct f2fs_checkpoint *cp)
 	return cpu_to_le64(cp_ver);
 }
 
+static inline int exist_qf_ino(struct f2fs_super_block *sb)
+{
+	int i;
+
+	for (i = 0; i < F2FS_MAX_QUOTAS; i++)
+		if (sb->qf_ino[i])
+			return 1;
+	return 0;
+}
+
 static inline int is_qf_ino(struct f2fs_super_block *sb, nid_t ino)
 {
 	int i;
diff --git a/mkfs/f2fs_format.c b/mkfs/f2fs_format.c
index c809225..2103f9d 100644
--- a/mkfs/f2fs_format.c
+++ b/mkfs/f2fs_format.c
@@ -395,9 +395,13 @@ static int f2fs_prepare_super_block(void)
 			quotatype_bits |= QUOTA_PRJ_BIT;
 	}
 
-	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++)
-		sb->qf_ino[qtype] =
-			((1 << qtype) & quotatype_bits) ? next_ino++ : 0;
+	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
+		if (!((1 << qtype) & quotatype_bits))
+			continue;
+		sb->qf_ino[qtype] = cpu_to_le32(next_ino++);
+		MSG(0, "Info: add quota type = %u => %u\n",
+					qtype, next_ino - 1);
+	}
 
 	if (total_zones <= 6) {
 		MSG(1, "\tError: %d zones: Need more zones "
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2017-10-31  3:42 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-10-31  3:42 [PATCH 1/4] sload.f2fs: fix bugs in f2fs_write_block() Jaegeuk Kim
2017-10-31  3:42 ` [PATCH 2/4] f2fs-tools: f2fs_read() and f2fs_filesize_update() are added Jaegeuk Kim
2017-10-31  3:42 ` [PATCH 3/4] mkfs.f2fs: support quota option in mkfs Jaegeuk Kim
2017-10-31  3:42 ` [PATCH 4/4] fsck.f2fs: support quota Jaegeuk Kim

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).