fsck.f2fs: add basic compress related check/fix

* check/fix i_compr_blocks
* check/fix compressed flag in i_flags when compression is not
  supported.

Signed-off-by: Robin Hsu <robinhsu@google.com>
[Jaegeuk Kim: minor coding style]
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 06a38a9..7c8437b 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -533,7 +533,8 @@
 
 int fsck_chk_node_blk(struct f2fs_sb_info *sbi, struct f2fs_inode *inode,
 		u32 nid, enum FILE_TYPE ftype, enum NODE_TYPE ntype,
-		u32 *blk_cnt, struct child_info *child)
+		u32 *blk_cnt, struct f2fs_compr_blk_cnt *cbc,
+		struct child_info *child)
 {
 	struct node_info ni;
 	struct f2fs_node *node_blk = NULL;
@@ -547,7 +548,8 @@
 	if (ntype == TYPE_INODE) {
 		struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 
-		fsck_chk_inode_blk(sbi, nid, ftype, node_blk, blk_cnt, &ni, child);
+		fsck_chk_inode_blk(sbi, nid, ftype, node_blk, blk_cnt, cbc,
+				&ni, child);
 		quota_add_inode_usage(fsck->qctx, nid, &node_blk->i);
 	} else {
 		switch (ntype) {
@@ -555,19 +557,19 @@
 			f2fs_set_main_bitmap(sbi, ni.blk_addr,
 							CURSEG_WARM_NODE);
 			fsck_chk_dnode_blk(sbi, inode, nid, ftype, node_blk,
-					blk_cnt, child, &ni);
+					blk_cnt, cbc, child, &ni);
 			break;
 		case TYPE_INDIRECT_NODE:
 			f2fs_set_main_bitmap(sbi, ni.blk_addr,
 							CURSEG_COLD_NODE);
 			fsck_chk_idnode_blk(sbi, inode, ftype, node_blk,
-					blk_cnt, child);
+					blk_cnt, cbc, child);
 			break;
 		case TYPE_DOUBLE_INDIRECT_NODE:
 			f2fs_set_main_bitmap(sbi, ni.blk_addr,
 							CURSEG_COLD_NODE);
 			fsck_chk_didnode_blk(sbi, inode, ftype, node_blk,
-					blk_cnt, child);
+					blk_cnt, cbc, child);
 			break;
 		default:
 			ASSERT(0);
@@ -667,7 +669,8 @@
 /* start with valid nid and blkaddr */
 void fsck_chk_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 		enum FILE_TYPE ftype, struct f2fs_node *node_blk,
-		u32 *blk_cnt, struct node_info *ni, struct child_info *child_d)
+		u32 *blk_cnt, struct f2fs_compr_blk_cnt *cbc,
+		struct node_info *ni, struct child_info *child_d)
 {
 	struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
 	struct child_info child;
@@ -675,6 +678,11 @@
 	u32 i_links = le32_to_cpu(node_blk->i.i_links);
 	u64 i_size = le64_to_cpu(node_blk->i.i_size);
 	u64 i_blocks = le64_to_cpu(node_blk->i.i_blocks);
+	bool compr_supported = c.feature & cpu_to_le32(F2FS_FEATURE_COMPRESSION);
+	u32 i_flags = le32_to_cpu(node_blk->i.i_flags);
+	bool compressed = i_flags & F2FS_COMPR_FL;
+	bool compr_rel = node_blk->i.i_inline & F2FS_COMPRESS_RELEASED;
+	u64 i_compr_blocks = le64_to_cpu(node_blk->i.i_compr_blocks);
 	nid_t i_xattr_nid = le32_to_cpu(node_blk->i.i_xattr_nid);
 	int ofs;
 	char *en;
@@ -683,7 +691,23 @@
 	unsigned short i_gc_failures;
 	int need_fix = 0;
 	int ret;
+	u32 cluster_size = 1 << node_blk->i.i_log_cluster_size;
 
+	if (!compr_supported && compressed) {
+		/*
+		 * The 'compression' flag in i_flags affects the traverse of
+		 * the node tree.  Thus, it must be fixed unconditionally
+		 * in the memory (node_blk).
+		 */
+		node_blk->i.i_flags &= ~cpu_to_le32(F2FS_COMPR_FL);
+		compressed = false;
+		if (c.fix_on) {
+			need_fix = 1;
+			FIX_MSG("[0x%x] i_flags=0x%x -> 0x%x",
+					nid, i_flags, node_blk->i.i_flags);
+		}
+		i_flags &= ~F2FS_COMPR_FL;
+	}
 	memset(&child, 0, sizeof(child));
 	child.links = 2;
 	child.p_ino = nid;
@@ -899,31 +923,45 @@
 		/* check extent info */
 		check_extent_info(&child, blkaddr, 0);
 
+		if (blkaddr == NULL_ADDR)
+			continue;
 		if (blkaddr == COMPRESS_ADDR) {
-			if (node_blk->i.i_compr_blocks) {
+			if (!compressed || (child.pgofs &
+					(cluster_size - 1)) != 0) {
+				if (c.fix_on) {
+					node_blk->i.i_addr[ofs + idx] =
+							NULL_ADDR;
+					need_fix = 1;
+					FIX_MSG("[0x%x] i_addr[%d] = 0", nid,
+							ofs + idx);
+				}
+				continue;
+			}
+			if (!compr_rel) {
 				fsck->chk.valid_blk_cnt++;
 				*blk_cnt = *blk_cnt + 1;
+				cbc->cheader_pgofs = child.pgofs;
+				cbc->cnt++;
 			}
 			continue;
 		}
-
-		if (blkaddr != 0) {
-			ret = fsck_chk_data_blk(sbi,
-					IS_CASEFOLDED(&node_blk->i),
-					blkaddr,
-					&child, (i_blocks == *blk_cnt),
-					ftype, nid, idx, ni->version,
-					file_is_encrypt(&node_blk->i));
-			if (!ret) {
-				*blk_cnt = *blk_cnt + 1;
-				if (cur_qtype != -1 && blkaddr != NEW_ADDR)
-					qf_last_blkofs[cur_qtype] = child.pgofs;
-			} else if (c.fix_on) {
-				node_blk->i.i_addr[ofs + idx] = 0;
-				need_fix = 1;
-				FIX_MSG("[0x%x] i_addr[%d] = 0",
-							nid, ofs + idx);
-			}
+		if (!compr_rel && blkaddr == NEW_ADDR &&
+				child.pgofs - cbc->cheader_pgofs < cluster_size)
+			cbc->cnt++;
+		ret = fsck_chk_data_blk(sbi,
+				IS_CASEFOLDED(&node_blk->i),
+				blkaddr,
+				&child, (i_blocks == *blk_cnt),
+				ftype, nid, idx, ni->version,
+				file_is_encrypt(&node_blk->i));
+		if (!ret) {
+			*blk_cnt = *blk_cnt + 1;
+			if (cur_qtype != -1 && blkaddr != NEW_ADDR)
+				qf_last_blkofs[cur_qtype] = child.pgofs;
+		} else if (c.fix_on) {
+			node_blk->i.i_addr[ofs + idx] = 0;
+			need_fix = 1;
+			FIX_MSG("[0x%x] i_addr[%d] = 0", nid, ofs + idx);
 		}
 	}
 
@@ -950,7 +988,7 @@
 			goto skip;
 
 		ret = fsck_chk_node_blk(sbi, &node_blk->i, i_nid,
-					ftype, ntype, blk_cnt, &child);
+				ftype, ntype, blk_cnt, cbc, &child);
 		if (!ret) {
 			*blk_cnt = *blk_cnt + 1;
 		} else if (ret == -EINVAL) {
@@ -994,6 +1032,16 @@
 					nid, i_blocks, *blk_cnt);
 		}
 	}
+
+	if (compressed && i_compr_blocks != cbc->cnt) {
+		if (c.fix_on) {
+			node_blk->i.i_compr_blocks = cpu_to_le64(cbc->cnt);
+			need_fix = 1;
+			FIX_MSG("[0x%x] i_compr_blocks=0x%08"PRIx64" -> 0x%x",
+					nid, i_compr_blocks, cbc->cnt);
+		}
+	}
+
 skip_blkcnt_fix:
 	en = malloc(F2FS_PRINT_NAMELEN);
 	ASSERT(en);
@@ -1132,27 +1180,47 @@
 
 int fsck_chk_dnode_blk(struct f2fs_sb_info *sbi, struct f2fs_inode *inode,
 		u32 nid, enum FILE_TYPE ftype, struct f2fs_node *node_blk,
-		u32 *blk_cnt, struct child_info *child, struct node_info *ni)
+		u32 *blk_cnt, struct f2fs_compr_blk_cnt *cbc,
+		struct child_info *child, struct node_info *ni)
 {
 	int idx, ret;
 	int need_fix = 0;
 	child->p_ino = nid;
 	child->pp_ino = le32_to_cpu(inode->i_pino);
+	u32 i_flags = le32_to_cpu(inode->i_flags);
+	bool compressed = i_flags & F2FS_COMPR_FL;
+	bool compr_rel = inode->i_inline & F2FS_COMPRESS_RELEASED;
+	u32 cluster_size = 1 << inode->i_log_cluster_size;
 
 	for (idx = 0; idx < ADDRS_PER_BLOCK(inode); idx++, child->pgofs++) {
 		block_t blkaddr = le32_to_cpu(node_blk->dn.addr[idx]);
 
 		check_extent_info(child, blkaddr, 0);
 
-		if (blkaddr == 0x0)
+		if (blkaddr == NULL_ADDR)
 			continue;
 		if (blkaddr == COMPRESS_ADDR) {
-			if (inode->i_compr_blocks) {
+			if (!compressed || (child->pgofs &
+					(cluster_size - 1)) != 0) {
+				if (c.fix_on) {
+					node_blk->dn.addr[idx] = NULL_ADDR;
+					need_fix = 1;
+					FIX_MSG("[0x%x] dn.addr[%d] = 0", nid,
+							idx);
+				}
+				continue;
+			}
+			if (!compr_rel) {
 				F2FS_FSCK(sbi)->chk.valid_blk_cnt++;
 				*blk_cnt = *blk_cnt + 1;
+				cbc->cheader_pgofs = child->pgofs;
+				cbc->cnt++;
 			}
 			continue;
 		}
+		if (!compr_rel && blkaddr == NEW_ADDR && child->pgofs -
+				cbc->cheader_pgofs < cluster_size)
+			cbc->cnt++;
 		ret = fsck_chk_data_blk(sbi, IS_CASEFOLDED(inode),
 			blkaddr, child,
 			le64_to_cpu(inode->i_blocks) == *blk_cnt, ftype,
@@ -1163,7 +1231,7 @@
 			if (cur_qtype != -1 && blkaddr != NEW_ADDR)
 				qf_last_blkofs[cur_qtype] = child->pgofs;
 		} else if (c.fix_on) {
-			node_blk->dn.addr[idx] = 0;
+			node_blk->dn.addr[idx] = NULL_ADDR;
 			need_fix = 1;
 			FIX_MSG("[0x%x] dn.addr[%d] = 0", nid, idx);
 		}
@@ -1177,7 +1245,7 @@
 
 int fsck_chk_idnode_blk(struct f2fs_sb_info *sbi, struct f2fs_inode *inode,
 		enum FILE_TYPE ftype, struct f2fs_node *node_blk, u32 *blk_cnt,
-		struct child_info *child)
+		struct f2fs_compr_blk_cnt *cbc, struct child_info *child)
 {
 	int need_fix = 0, ret;
 	int i = 0;
@@ -1189,7 +1257,8 @@
 			goto skip;
 		ret = fsck_chk_node_blk(sbi, inode,
 				le32_to_cpu(node_blk->in.nid[i]),
-				ftype, TYPE_DIRECT_NODE, blk_cnt, child);
+				ftype, TYPE_DIRECT_NODE, blk_cnt,
+				cbc, child);
 		if (!ret)
 			*blk_cnt = *blk_cnt + 1;
 		else if (ret == -EINVAL) {
@@ -1219,7 +1288,7 @@
 
 int fsck_chk_didnode_blk(struct f2fs_sb_info *sbi, struct f2fs_inode *inode,
 		enum FILE_TYPE ftype, struct f2fs_node *node_blk, u32 *blk_cnt,
-		struct child_info *child)
+		struct f2fs_compr_blk_cnt *cbc, struct child_info *child)
 {
 	int i = 0;
 	int need_fix = 0, ret = 0;
@@ -1231,7 +1300,7 @@
 			goto skip;
 		ret = fsck_chk_node_blk(sbi, inode,
 				le32_to_cpu(node_blk->in.nid[i]),
-				ftype, TYPE_INDIRECT_NODE, blk_cnt, child);
+				ftype, TYPE_INDIRECT_NODE, blk_cnt, cbc, child);
 		if (!ret)
 			*blk_cnt = *blk_cnt + 1;
 		else if (ret == -EINVAL) {
@@ -1494,6 +1563,7 @@
 	enum FILE_TYPE ftype;
 	int dentries = 0;
 	u32 blk_cnt;
+	struct f2fs_compr_blk_cnt cbc;
 	u8 *name;
 	char en[F2FS_PRINT_NAMELEN];
 	u16 name_len;
@@ -1633,10 +1703,12 @@
 				dentry, max, i, last_blk, enc_name);
 
 		blk_cnt = 1;
+		cbc.cnt = 0;
+		cbc.cheader_pgofs = CHEADER_PGOFS_NONE;
 		child->i_namelen = name_len;
 		ret = fsck_chk_node_blk(sbi,
 				NULL, le32_to_cpu(dentry[i].ino),
-				ftype, TYPE_INODE, &blk_cnt, child);
+				ftype, TYPE_INODE, &blk_cnt, &cbc, child);
 
 		if (ret && c.fix_on) {
 			int j;
@@ -1800,6 +1872,7 @@
 int fsck_chk_orphan_node(struct f2fs_sb_info *sbi)
 {
 	u32 blk_cnt = 0;
+	struct f2fs_compr_blk_cnt cbc = {0, CHEADER_PGOFS_NONE};
 	block_t start_blk, orphan_blkaddr, i, j;
 	struct f2fs_orphan_block *orphan_blk, *new_blk;
 	struct f2fs_super_block *sb = F2FS_RAW_SUPER(sbi);
@@ -1831,6 +1904,8 @@
 			DBG(1, "[%3d] ino [0x%x]\n", i, ino);
 			struct node_info ni;
 			blk_cnt = 1;
+			cbc.cnt = 0;
+			cbc.cheader_pgofs = CHEADER_PGOFS_NONE;
 
 			if (c.preen_mode == PREEN_MODE_1 && !c.fix_on) {
 				get_node_info(sbi, ino, &ni);
@@ -1846,7 +1921,7 @@
 
 			ret = fsck_chk_node_blk(sbi, NULL, ino,
 					F2FS_FT_ORPHAN, TYPE_INODE, &blk_cnt,
-					NULL);
+					&cbc, NULL);
 			if (!ret)
 				new_blk->ino[new_entry_count++] =
 							orphan_blk->ino[j];
@@ -1876,6 +1951,7 @@
 	enum quota_type qtype;
 	int ret = 0;
 	u32 blk_cnt = 0;
+	struct f2fs_compr_blk_cnt cbc = {0, CHEADER_PGOFS_NONE};
 
 	for (qtype = 0; qtype < F2FS_MAX_QUOTAS; qtype++) {
 		cur_qtype = qtype;
@@ -1886,6 +1962,8 @@
 
 		DBG(1, "qtype [%d] ino [0x%x]\n", qtype, ino);
 		blk_cnt = 1;
+		cbc.cnt = 0;
+		cbc.cheader_pgofs = CHEADER_PGOFS_NONE;
 
 		if (c.preen_mode == PREEN_MODE_1 && !c.fix_on) {
 			get_node_info(sbi, ino, &ni);
@@ -1895,7 +1973,8 @@
 			continue;
 		}
 		ret = fsck_chk_node_blk(sbi, NULL, ino,
-				F2FS_FT_REG_FILE, TYPE_INODE, &blk_cnt, NULL);
+				F2FS_FT_REG_FILE, TYPE_INODE, &blk_cnt,
+				&cbc, NULL);
 		if (ret) {
 			ASSERT_MSG("wrong quota inode, qtype [%d] ino [0x%x]",
 								qtype, ino);
@@ -2806,6 +2885,7 @@
 	struct node_info ni;
 	char *reconnect_bitmap;
 	u32 blk_cnt;
+	struct f2fs_compr_blk_cnt cbc;
 	nid_t nid;
 	int err, cnt = 0, ftype;
 
@@ -2849,8 +2929,10 @@
 
 			DBG(1, "Check inode 0x%x\n", nid);
 			blk_cnt = 1;
+			cbc.cnt = 0;
+			cbc.cheader_pgofs = CHEADER_PGOFS_NONE;
 			fsck_chk_inode_blk(sbi, nid, ftype, node,
-					   &blk_cnt, &ni, NULL);
+					   &blk_cnt, &cbc, &ni, NULL);
 
 			f2fs_set_bit(nid, reconnect_bitmap);
 		}
diff --git a/fsck/fsck.h b/fsck/fsck.h
index 67390f0..11846e1 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -169,16 +169,20 @@
 			struct node_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 *);
+		struct f2fs_compr_blk_cnt *, struct child_info *);
 extern void fsck_chk_inode_blk(struct f2fs_sb_info *, u32, enum FILE_TYPE,
-		struct f2fs_node *, u32 *, struct node_info *, struct child_info *);
+		struct f2fs_node *, u32 *, struct f2fs_compr_blk_cnt *,
+		struct node_info *, struct child_info *);
 extern int fsck_chk_dnode_blk(struct f2fs_sb_info *, struct f2fs_inode *,
 		u32, enum FILE_TYPE, struct f2fs_node *, u32 *,
-		struct child_info *, struct node_info *);
+		struct f2fs_compr_blk_cnt *, struct child_info *,
+		struct node_info *);
 extern int fsck_chk_idnode_blk(struct f2fs_sb_info *, struct f2fs_inode *,
-		enum FILE_TYPE, struct f2fs_node *, u32 *, struct child_info *);
+		enum FILE_TYPE, struct f2fs_node *, u32 *,
+		struct f2fs_compr_blk_cnt *, struct child_info *);
 extern int fsck_chk_didnode_blk(struct f2fs_sb_info *, struct f2fs_inode *,
-		enum FILE_TYPE, struct f2fs_node *, u32 *, struct child_info *);
+		enum FILE_TYPE, struct f2fs_node *, u32 *,
+		struct f2fs_compr_blk_cnt *, struct child_info *);
 extern int fsck_chk_data_blk(struct f2fs_sb_info *, int,
 		u32, struct child_info *, int, enum FILE_TYPE, u32, u16, u8, int);
 extern int fsck_chk_dentry_blk(struct f2fs_sb_info *, int,
diff --git a/fsck/main.c b/fsck/main.c
index 260ff29..f9b05de 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -820,6 +820,7 @@
 	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
 	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
 	u32 blk_cnt;
+	struct f2fs_compr_blk_cnt cbc;
 	errcode_t ret;
 
 	fsck_init(sbi);
@@ -867,6 +868,8 @@
 
 	/* Traverse all block recursively from root inode */
 	blk_cnt = 1;
+	cbc.cnt = 0;
+	cbc.cheader_pgofs = CHEADER_PGOFS_NONE;
 
 	if (c.feature & cpu_to_le32(F2FS_FEATURE_QUOTA_INO)) {
 		ret = quota_init_context(sbi);
@@ -877,7 +880,7 @@
 	}
 	fsck_chk_orphan_node(sbi);
 	fsck_chk_node_blk(sbi, NULL, sbi->root_ino_num,
-			F2FS_FT_DIR, TYPE_INODE, &blk_cnt, NULL);
+			F2FS_FT_DIR, TYPE_INODE, &blk_cnt, &cbc, NULL);
 	fsck_chk_quota_files(sbi);
 
 	ret = fsck_verify(sbi);
diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h
index 6c21e55..ea55ee8 100644
--- a/include/f2fs_fs.h
+++ b/include/f2fs_fs.h
@@ -942,6 +942,21 @@
 #define IS_CASEFOLDED(dir)     ((dir)->i_flags & F2FS_CASEFOLD_FL)
 
 /*
+ * fsck i_compr_blocks counting helper
+ */
+struct f2fs_compr_blk_cnt {
+	/* counting i_compr_blocks, init 0 */
+	u32 cnt;
+
+	/*
+	 * previous seen compression header (COMPR_ADDR) page offsets,
+	 * use CHEADER_PGOFS_NONE for none
+	 */
+	u32 cheader_pgofs;
+};
+#define CHEADER_PGOFS_NONE ((u32)-(1 << MAX_COMPRESS_LOG_SIZE))
+
+/*
  * inode flags
  */
 #define F2FS_COMPR_FL		0x00000004 /* Compress file */