make_ext4fs: write out super blocks at last for block devices

Change-Id: I93ee37a649aabff14464030efd88f79e1ae324f0
Signed-off-by: Eric Miao <emiao@nvidia.com>
diff --git a/ext4_utils/ext4_sb.h b/ext4_utils/ext4_sb.h
index 832fa33..159580d 100644
--- a/ext4_utils/ext4_sb.h
+++ b/ext4_utils/ext4_sb.h
@@ -25,6 +25,8 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
+
 struct fs_info {
 	int64_t len;	/* If set to 0, ask the block device for the size,
 			 * if less than 0, reserve that much space at the
@@ -41,6 +43,7 @@
 	uint32_t bg_desc_reserve_blocks;
 	const char *label;
 	uint8_t no_journal;
+	bool block_device;	/* target fd is a block device? */
 };
 
 int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info);
diff --git a/ext4_utils/ext4_utils.c b/ext4_utils/ext4_utils.c
index 3b22b81..0639293 100644
--- a/ext4_utils/ext4_utils.c
+++ b/ext4_utils/ext4_utils.c
@@ -168,10 +168,31 @@
 		critical_error("failed to write all of superblock");
 }
 
+static void block_device_write_sb(int fd)
+{
+	unsigned long long offset;
+	u32 i;
+
+	/* write out the backup superblocks */
+	for (i = 1; i < aux_info.groups; i++) {
+		if (ext4_bg_has_super_block(i)) {
+			offset = info.block_size * (aux_info.first_data_block
+				+ i * info.blocks_per_group);
+			write_sb(fd, offset, aux_info.backup_sb[i]);
+		}
+	}
+
+	/* write out the primary superblock */
+	write_sb(fd, 1024, aux_info.sb);
+}
+
 /* Write the filesystem image to a file */
 void write_ext4_image(int fd, int gz, int sparse, int crc)
 {
 	sparse_file_write(ext4_sparse_file, fd, gz, sparse, crc);
+
+	if (info.block_device)
+		block_device_write_sb(fd);
 }
 
 /* Compute the rest of the parameters of the filesystem from the basic info */
@@ -203,7 +224,27 @@
 		aux_info.len_blocks -= last_group_size;
 	}
 
-	aux_info.sb = calloc(info.block_size, 1);
+	/* A zero-filled superblock to be written firstly to the block
+	 * device to mark the file-system as invalid
+	 */
+	aux_info.sb_zero = calloc(1, info.block_size);
+	if (!aux_info.sb_zero)
+		critical_error_errno("calloc");
+
+	/* The write_data* functions expect only block aligned calls.
+	 * This is not an issue, except when we write out the super
+	 * block on a system with a block size > 1K.  So, we need to
+	 * deal with that here.
+	 */
+	aux_info.sb_block = calloc(1, info.block_size);
+	if (!aux_info.sb_block)
+		critical_error_errno("calloc");
+
+	if (info.block_size > 1024)
+		aux_info.sb = (struct ext4_super_block *)((char *)aux_info.sb_block + 1024);
+	else
+		aux_info.sb = aux_info.sb_block;
+
 	/* Alloc an array to hold the pointers to the backup superblocks */
 	aux_info.backup_sb = calloc(aux_info.groups, sizeof(char *));
 
@@ -224,7 +265,8 @@
 		if (aux_info.backup_sb[i])
 			free(aux_info.backup_sb[i]);
 	}
-	free(aux_info.sb);
+	free(aux_info.sb_block);
+	free(aux_info.sb_zero);
 	free(aux_info.bg_desc);
 }
 
@@ -324,8 +366,8 @@
 				memcpy(aux_info.backup_sb[i], sb, info.block_size);
 				/* Update the block group nr of this backup superblock */
 				aux_info.backup_sb[i]->s_block_group_nr = i;
-				sparse_file_add_data(ext4_sparse_file, aux_info.backup_sb[i],
-						info.block_size, group_start_block);
+				ext4_queue_sb(group_start_block, info.block_device ?
+						aux_info.sb_zero : aux_info.backup_sb[i]);
 			}
 			sparse_file_add_data(ext4_sparse_file, aux_info.bg_desc,
 				aux_info.bg_desc_blocks * info.block_size,
@@ -341,22 +383,23 @@
 		aux_info.bg_desc[i].bg_free_inodes_count = sb->s_inodes_per_group;
 		aux_info.bg_desc[i].bg_used_dirs_count = 0;
 	}
+
+	/* Queue the primary superblock to be written out - if it's a block device,
+	 * queue a zero-filled block first, the correct version of superblock will
+	 * be written to the block device after all other blocks are written.
+	 *
+	 * The file-system on the block device will not be valid until the correct
+	 * version of superblocks are written, this is to avoid the likelihood of a
+	 * partially created file-system.
+	 */
+	ext4_queue_sb(aux_info.first_data_block, info.block_device ?
+				aux_info.sb_zero : aux_info.sb_block);
 }
 
-void ext4_queue_sb(void)
+
+void ext4_queue_sb(u64 start_block, struct ext4_super_block *sb)
 {
-	/* The write_data* functions expect only block aligned calls.
-	 * This is not an issue, except when we write out the super
-	 * block on a system with a block size > 1K.  So, we need to
-	 * deal with that here.
-	 */
-	if (info.block_size > 1024) {
-		u8 *buf = calloc(info.block_size, 1);
-		memcpy(buf + 1024, (u8*)aux_info.sb, 1024);
-		sparse_file_add_data(ext4_sparse_file, buf, info.block_size, 0);
-	} else {
-		sparse_file_add_data(ext4_sparse_file, aux_info.sb, 1024, 1);
-	}
+	sparse_file_add_data(ext4_sparse_file, sb, info.block_size, start_block);
 }
 
 void ext4_parse_sb_info(struct ext4_super_block *sb)
diff --git a/ext4_utils/ext4_utils.h b/ext4_utils/ext4_utils.h
index ea95446..8cc8c2c 100644
--- a/ext4_utils/ext4_utils.h
+++ b/ext4_utils/ext4_utils.h
@@ -99,6 +99,8 @@
 
 struct fs_aux_info {
 	struct ext4_super_block *sb;
+	struct ext4_super_block *sb_block;
+	struct ext4_super_block *sb_zero;
 	struct ext4_super_block **backup_sb;
 	struct ext2_group_desc *bg_desc;
 	struct block_group_info *bgs;
@@ -142,7 +144,7 @@
 void ext4_create_resize_inode(void);
 void ext4_create_journal_inode(void);
 void ext4_update_free(void);
-void ext4_queue_sb(void);
+void ext4_queue_sb(u64 start_block, struct ext4_super_block *sb);
 u64 get_block_device_size(int fd);
 int is_block_device_fd(int fd);
 u64 get_file_size(int fd);
diff --git a/ext4_utils/make_ext4fs.c b/ext4_utils/make_ext4fs.c
index 5c9e208..5bb1872 100644
--- a/ext4_utils/make_ext4fs.c
+++ b/ext4_utils/make_ext4fs.c
@@ -503,6 +503,13 @@
 	if (setjmp(setjmp_env))
 		return EXIT_FAILURE; /* Handle a call to longjmp() */
 
+	info.block_device = is_block_device_fd(fd);
+
+	if (info.block_device && (sparse || gzip || crc)) {
+		fprintf(stderr, "No sparse/gzip/crc allowed for block device\n");
+		return EXIT_FAILURE;
+	}
+
 	if (_mountpoint == NULL) {
 		mountpoint = strdup("");
 	} else {
@@ -629,8 +636,6 @@
 
 	ext4_update_free();
 
-	ext4_queue_sb();
-
 	if (block_list_file) {
 		size_t dirlen = directory ? strlen(directory) : 0;
 		struct block_allocation* p = get_saved_allocation_chain();