Merge "e2fsck: Add an extended option for unsharing blocks."
diff --git a/e2fsck/e2fsck.8.in b/e2fsck/e2fsck.8.in
index 915273d..18d0f4b 100644
--- a/e2fsck/e2fsck.8.in
+++ b/e2fsck/e2fsck.8.in
@@ -240,6 +240,15 @@
 Only fix damaged metadata; do not optimize htree directories or compress
 extent trees.  This option is incompatible with the -D and -E bmap2extent
 options.
+.TP
+.BI unshare_blocks
+If the filesystem has shared blocks, with the shared blocks read-only feature
+enabled, then this will unshare all shared blocks and unset the read-only
+feature bit. If there is not enough free space then the operation will fail.
+If the filesystem does not have the read-only feature bit, but has shared
+blocks anyway, then this option will have no effect. Note when using this
+option, if there is no free space to clone blocks, there is no prompt to
+delete files and instead the operation will fail.
 .RE
 .TP
 .B \-f
diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h
index f356810..52833e6 100644
--- a/e2fsck/e2fsck.h
+++ b/e2fsck/e2fsck.h
@@ -153,22 +153,23 @@
 /*
  * E2fsck options
  */
-#define E2F_OPT_READONLY	0x0001
-#define E2F_OPT_PREEN		0x0002
-#define E2F_OPT_YES		0x0004
-#define E2F_OPT_NO		0x0008
-#define E2F_OPT_TIME		0x0010
-#define E2F_OPT_TIME2		0x0020
-#define E2F_OPT_CHECKBLOCKS	0x0040
-#define E2F_OPT_DEBUG		0x0080
-#define E2F_OPT_FORCE		0x0100
-#define E2F_OPT_WRITECHECK	0x0200
-#define E2F_OPT_COMPRESS_DIRS	0x0400
-#define E2F_OPT_FRAGCHECK	0x0800
-#define E2F_OPT_JOURNAL_ONLY	0x1000 /* only replay the journal */
-#define E2F_OPT_DISCARD		0x2000
-#define E2F_OPT_CONVERT_BMAP	0x4000 /* convert blockmap to extent */
-#define E2F_OPT_FIXES_ONLY	0x8000 /* skip all optimizations */
+#define E2F_OPT_READONLY	0x00001
+#define E2F_OPT_PREEN		0x00002
+#define E2F_OPT_YES		0x00004
+#define E2F_OPT_NO		0x00008
+#define E2F_OPT_TIME		0x00010
+#define E2F_OPT_TIME2		0x00020
+#define E2F_OPT_CHECKBLOCKS	0x00040
+#define E2F_OPT_DEBUG		0x00080
+#define E2F_OPT_FORCE		0x00100
+#define E2F_OPT_WRITECHECK	0x00200
+#define E2F_OPT_COMPRESS_DIRS	0x00400
+#define E2F_OPT_FRAGCHECK	0x00800
+#define E2F_OPT_JOURNAL_ONLY	0x01000 /* only replay the journal */
+#define E2F_OPT_DISCARD		0x02000
+#define E2F_OPT_CONVERT_BMAP	0x04000 /* convert blockmap to extent */
+#define E2F_OPT_FIXES_ONLY	0x08000 /* skip all optimizations */
+#define E2F_OPT_UNSHARE_BLOCKS  0x10000
 
 /*
  * E2fsck flags
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index c5fdbf7..1d34f9a 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -2131,8 +2131,10 @@
 	clear_problem_context(&pctx);
 
 	if (ext2fs_fast_test_block_bitmap2(ctx->block_found_map, block)) {
-		if (ext2fs_has_feature_shared_blocks(ctx->fs->super))
+		if (ext2fs_has_feature_shared_blocks(ctx->fs->super) &&
+		    !(ctx->options & E2F_OPT_UNSHARE_BLOCKS)) {
 			return;
+		}
 		if (!ctx->block_dup_map) {
 			pctx.errcode = e2fsck_allocate_block_bitmap(ctx->fs,
 					_("multiply claimed block map"),
diff --git a/e2fsck/pass1b.c b/e2fsck/pass1b.c
index b40f026..eb46415 100644
--- a/e2fsck/pass1b.c
+++ b/e2fsck/pass1b.c
@@ -245,6 +245,24 @@
 	pass1d(ctx, block_buf);
 	print_resource_track(ctx, "Pass 1d", &rtrack, ctx->fs->io);
 
+	if (ext2fs_has_feature_shared_blocks(ctx->fs->super) &&
+	    (ctx->options & E2F_OPT_UNSHARE_BLOCKS)) {
+		/*
+		 * If we successfully managed to unshare all blocks, unset the
+		 * shared block feature.
+		 */
+		blk64_t next;
+		int result = ext2fs_find_first_set_block_bitmap2(
+			ctx->block_dup_map,
+			ctx->fs->super->s_first_data_block,
+			ext2fs_blocks_count(ctx->fs->super) - 1,
+			&next);
+		if (result == ENOENT) {
+			ext2fs_clear_feature_shared_blocks(ctx->fs->super);
+			ext2fs_mark_super_dirty(ctx->fs);
+		}
+	}
+
 	/*
 	 * Time to free all of the accumulated data structures that we
 	 * don't need anymore.
@@ -582,14 +600,21 @@
 			fix_problem(ctx, PR_1D_DUP_BLOCKS_DEALT, &pctx);
 			continue;
 		}
-		if (fix_problem(ctx, PR_1D_CLONE_QUESTION, &pctx)) {
+		if ((ctx->options & E2F_OPT_UNSHARE_BLOCKS) ||
+                    fix_problem(ctx, PR_1D_CLONE_QUESTION, &pctx)) {
 			pctx.errcode = clone_file(ctx, ino, p, block_buf);
 			if (pctx.errcode)
 				fix_problem(ctx, PR_1D_CLONE_ERROR, &pctx);
 			else
 				continue;
 		}
-		if (fix_problem(ctx, PR_1D_DELETE_QUESTION, &pctx))
+		/*
+		 * Note: When unsharing blocks, we don't prompt to delete
+		 * files. If the clone operation fails than the unshare
+		 * operation should fail too.
+		 */
+		if (!(ctx->options & E2F_OPT_UNSHARE_BLOCKS) &&
+                    fix_problem(ctx, PR_1D_DELETE_QUESTION, &pctx))
 			delete_file(ctx, ino, p, block_buf);
 		else
 			ext2fs_unmark_valid(fs);
@@ -818,6 +843,13 @@
 			cs->errcode = retval;
 			return BLOCK_ABORT;
 		}
+		if (ext2fs_has_feature_shared_blocks(fs->super)) {
+			/*
+			 * Update the block stats so we don't get a prompt to fix block
+			 * counts in the final pass.
+			 */
+			ext2fs_block_alloc_stats2(fs, new_block, +1);
+		}
 cluster_alloc_ok:
 		cs->alloc_block = new_block;
 
diff --git a/e2fsck/unix.c b/e2fsck/unix.c
index 6029cc3..ee27481 100644
--- a/e2fsck/unix.c
+++ b/e2fsck/unix.c
@@ -721,6 +721,9 @@
 		} else if (strcmp(token, "fixes_only") == 0) {
 			ctx->options |= E2F_OPT_FIXES_ONLY;
 			continue;
+		} else if (strcmp(token, "unshare_blocks") == 0) {
+			ctx->options |= E2F_OPT_UNSHARE_BLOCKS;
+			continue;
 		} else {
 			fprintf(stderr, _("Unknown extended option: %s\n"),
 				token);
@@ -741,6 +744,7 @@
 		fputs(("\tnodiscard\n"), stderr);
 		fputs(("\treadahead_kb=<buffer size>\n"), stderr);
 		fputs(("\tbmap2extent\n"), stderr);
+		fputs(("\tunshare_blocks\n"), stderr);
 		fputc('\n', stderr);
 		exit(1);
 	}
diff --git a/tests/f_unshare_blocks_no_space/expect.1 b/tests/f_unshare_blocks_no_space/expect.1
new file mode 100644
index 0000000..b2f6ab1
--- /dev/null
+++ b/tests/f_unshare_blocks_no_space/expect.1
@@ -0,0 +1,136 @@
+Pass 1: Checking inodes, blocks, and sizes
+
+Running additional passes to resolve blocks claimed by more than one inode...
+Pass 1B: Rescanning for multiply-claimed blocks
+Multiply-claimed block(s) in inode 24: 10
+Multiply-claimed block(s) in inode 25: 9 9 9--10
+Multiply-claimed block(s) in inode 26: 9 9 9--10
+Multiply-claimed block(s) in inode 27: 9 9 9--10
+Multiply-claimed block(s) in inode 28: 9 9 9--10
+Multiply-claimed block(s) in inode 29: 9 9 9--10
+Multiply-claimed block(s) in inode 30: 9 9 9--10
+Multiply-claimed block(s) in inode 31: 9 9 9--10
+Multiply-claimed block(s) in inode 32: 9 9 9--10
+Pass 1C: Scanning directories for inodes with multiply-claimed blocks
+Pass 1D: Reconciling multiply-claimed blocks
+(There are 9 inodes containing multiply-claimed blocks.)
+
+File /file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018) 
+  has 1 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+File /file18.txt (inode #32, mod time Mon Mar  5 20:30:04 2018) 
+  has 4 multiply-claimed block(s), shared with 8 file(s):
+	/file6.txt (inode #31, mod time Mon Mar  5 20:30:04 2018)
+	/file12.txt (inode #30, mod time Mon Mar  5 20:30:04 2018)
+	/file3.txt (inode #29, mod time Mon Mar  5 20:30:04 2018)
+	/file9.txt (inode #28, mod time Mon Mar  5 20:30:04 2018)
+	/file8.txt (inode #27, mod time Mon Mar  5 20:30:04 2018)
+	/file15.txt (inode #26, mod time Mon Mar  5 20:30:04 2018)
+	/file20.txt (inode #25, mod time Mon Mar  5 20:30:04 2018)
+	/file4.txt (inode #24, mod time Mon Mar  5 20:30:04 2018)
+clone_file: Could not allocate block in ext2 filesystem returned from clone_file_block
+Couldn't clone file: Could not allocate block in ext2 filesystem
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+
+test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
+
+test_filesys: ********** WARNING: Filesystem still has errors **********
+
+test_filesys: 32/32 files (34.4% non-contiguous), 64/64 blocks
+Exit status is 4
diff --git a/tests/f_unshare_blocks_no_space/expect.2 b/tests/f_unshare_blocks_no_space/expect.2
new file mode 100644
index 0000000..8137dc7
--- /dev/null
+++ b/tests/f_unshare_blocks_no_space/expect.2
@@ -0,0 +1,7 @@
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 32/32 files (34.4% non-contiguous), 64/64 blocks
+Exit status is 1
diff --git a/tests/f_unshare_blocks_no_space/image.gz b/tests/f_unshare_blocks_no_space/image.gz
new file mode 100644
index 0000000..8fff6d4
--- /dev/null
+++ b/tests/f_unshare_blocks_no_space/image.gz
Binary files differ
diff --git a/tests/f_unshare_blocks_no_space/name b/tests/f_unshare_blocks_no_space/name
new file mode 100644
index 0000000..ca323a6
--- /dev/null
+++ b/tests/f_unshare_blocks_no_space/name
@@ -0,0 +1 @@
+unshare blocks should fail with no free space
diff --git a/tests/f_unshare_blocks_no_space/script b/tests/f_unshare_blocks_no_space/script
new file mode 100644
index 0000000..bc44354
--- /dev/null
+++ b/tests/f_unshare_blocks_no_space/script
@@ -0,0 +1,2 @@
+FSCK_OPT="-yf -E unshare_blocks"
+. $cmd_dir/run_e2fsck
diff --git a/tests/f_unshare_blocks_ok/expect.1 b/tests/f_unshare_blocks_ok/expect.1
new file mode 100644
index 0000000..e0ea764
--- /dev/null
+++ b/tests/f_unshare_blocks_ok/expect.1
@@ -0,0 +1,26 @@
+Pass 1: Checking inodes, blocks, and sizes
+
+Running additional passes to resolve blocks claimed by more than one inode...
+Pass 1B: Rescanning for multiply-claimed blocks
+Multiply-claimed block(s) in inode 12: 9
+Multiply-claimed block(s) in inode 13: 9
+Pass 1C: Scanning directories for inodes with multiply-claimed blocks
+Pass 1D: Reconciling multiply-claimed blocks
+(There are 2 inodes containing multiply-claimed blocks.)
+
+File /file2.txt (inode #12, mod time Sat Mar  3 02:12:33 2018) 
+  has 1 multiply-claimed block(s), shared with 1 file(s):
+	/file1.txt (inode #13, mod time Sat Mar  3 02:12:15 2018)
+File /file1.txt (inode #13, mod time Sat Mar  3 02:12:15 2018) 
+  has 1 multiply-claimed block(s), shared with 1 file(s):
+	/file2.txt (inode #12, mod time Sat Mar  3 02:12:33 2018)
+Multiply-claimed blocks already reassigned or cloned.
+
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+
+test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
+test_filesys: 13/32 files (0.0% non-contiguous), 13/64 blocks
+Exit status is 0
diff --git a/tests/f_unshare_blocks_ok/expect.2 b/tests/f_unshare_blocks_ok/expect.2
new file mode 100644
index 0000000..b215382
--- /dev/null
+++ b/tests/f_unshare_blocks_ok/expect.2
@@ -0,0 +1,7 @@
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/32 files (0.0% non-contiguous), 13/64 blocks
+Exit status is 0
diff --git a/tests/f_unshare_blocks_ok/image.gz b/tests/f_unshare_blocks_ok/image.gz
new file mode 100644
index 0000000..db747e2
--- /dev/null
+++ b/tests/f_unshare_blocks_ok/image.gz
Binary files differ
diff --git a/tests/f_unshare_blocks_ok/name b/tests/f_unshare_blocks_ok/name
new file mode 100644
index 0000000..e051a62
--- /dev/null
+++ b/tests/f_unshare_blocks_ok/name
@@ -0,0 +1 @@
+unshare blocks successfully
diff --git a/tests/f_unshare_blocks_ok/script b/tests/f_unshare_blocks_ok/script
new file mode 100644
index 0000000..bc44354
--- /dev/null
+++ b/tests/f_unshare_blocks_ok/script
@@ -0,0 +1,2 @@
+FSCK_OPT="-yf -E unshare_blocks"
+. $cmd_dir/run_e2fsck