Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "make_ext4fs.h"
     18 #include "ext4_utils.h"
     19 #include "allocate.h"
     20 #include "contents.h"
     21 #include "uuid.h"
     22 #include "wipe.h"
     23 
     24 #include <sparse/sparse.h>
     25 
     26 #include <assert.h>
     27 #include <dirent.h>
     28 #include <fcntl.h>
     29 #include <inttypes.h>
     30 #include <libgen.h>
     31 #include <stdio.h>
     32 #include <stdlib.h>
     33 #include <string.h>
     34 #include <unistd.h>
     35 #include <sys/stat.h>
     36 #include <sys/types.h>
     37 
     38 #ifdef USE_MINGW
     39 
     40 #include <winsock2.h>
     41 
     42 /* These match the Linux definitions of these flags.
     43    L_xx is defined to avoid conflicting with the win32 versions.
     44 */
     45 #define L_S_IRUSR 00400
     46 #define L_S_IWUSR 00200
     47 #define L_S_IXUSR 00100
     48 #define S_IRWXU (L_S_IRUSR | L_S_IWUSR | L_S_IXUSR)
     49 #define S_IRGRP 00040
     50 #define S_IWGRP 00020
     51 #define S_IXGRP 00010
     52 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
     53 #define S_IROTH 00004
     54 #define S_IWOTH 00002
     55 #define S_IXOTH 00001
     56 #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
     57 #define S_ISUID 0004000
     58 #define S_ISGID 0002000
     59 #define S_ISVTX 0001000
     60 
     61 #else
     62 
     63 #include <selinux/selinux.h>
     64 #include <selinux/label.h>
     65 #include <selinux/android.h>
     66 
     67 #define O_BINARY 0
     68 
     69 #endif
     70 
     71 /* TODO: Not implemented:
     72    Allocating blocks in the same block group as the file inode
     73    Hash or binary tree directories
     74    Special files: sockets, devices, fifos
     75  */
     76 
     77 static int filter_dot(const struct dirent *d)
     78 {
     79 	return (strcmp(d->d_name, "..") && strcmp(d->d_name, "."));
     80 }
     81 
     82 static u32 build_default_directory_structure(const char *dir_path,
     83 					     struct selabel_handle *sehnd)
     84 {
     85 	u32 inode;
     86 	u32 root_inode;
     87 	struct dentry dentries = {
     88 			.filename = "lost+found",
     89 			.file_type = EXT4_FT_DIR,
     90 			.mode = S_IRWXU,
     91 			.uid = 0,
     92 			.gid = 0,
     93 			.mtime = 0,
     94 	};
     95 	root_inode = make_directory(0, 1, &dentries, 1);
     96 	inode = make_directory(root_inode, 0, NULL, 0);
     97 	*dentries.inode = inode;
     98 	inode_set_permissions(inode, dentries.mode,
     99 		dentries.uid, dentries.gid, dentries.mtime);
    100 
    101 #ifndef USE_MINGW
    102 	if (sehnd) {
    103 		char *path = NULL;
    104 		char *secontext = NULL;
    105 
    106 		asprintf(&path, "%slost+found", dir_path);
    107 		if (selabel_lookup(sehnd, &secontext, path, S_IFDIR) < 0) {
    108 			error("cannot lookup security context for %s", path);
    109 		} else {
    110 			inode_set_selinux(inode, secontext);
    111 			freecon(secontext);
    112 		}
    113 		free(path);
    114 	}
    115 #endif
    116 
    117 	return root_inode;
    118 }
    119 
    120 #ifndef USE_MINGW
    121 /* Read a local directory and create the same tree in the generated filesystem.
    122    Calls itself recursively with each directory in the given directory.
    123    full_path is an absolute or relative path, with a trailing slash, to the
    124    directory on disk that should be copied, or NULL if this is a directory
    125    that does not exist on disk (e.g. lost+found).
    126    dir_path is an absolute path, with trailing slash, to the same directory
    127    if the image were mounted at the specified mount point */
    128 static u32 build_directory_structure(const char *full_path, const char *dir_path,
    129 		u32 dir_inode, fs_config_func_t fs_config_func,
    130 		struct selabel_handle *sehnd, int verbose)
    131 {
    132 	int entries = 0;
    133 	struct dentry *dentries;
    134 	struct dirent **namelist = NULL;
    135 	struct stat stat;
    136 	int ret;
    137 	int i;
    138 	u32 inode;
    139 	u32 entry_inode;
    140 	u32 dirs = 0;
    141 	bool needs_lost_and_found = false;
    142 
    143 	if (full_path) {
    144 		entries = scandir(full_path, &namelist, filter_dot, (void*)alphasort);
    145 		if (entries < 0) {
    146 			error_errno("scandir");
    147 			return EXT4_ALLOCATE_FAILED;
    148 		}
    149 	}
    150 
    151 	if (dir_inode == 0) {
    152 		/* root directory, check if lost+found already exists */
    153 		for (i = 0; i < entries; i++)
    154 			if (strcmp(namelist[i]->d_name, "lost+found") == 0)
    155 				break;
    156 		if (i == entries)
    157 			needs_lost_and_found = true;
    158 	}
    159 
    160 	dentries = calloc(entries, sizeof(struct dentry));
    161 	if (dentries == NULL)
    162 		critical_error_errno("malloc");
    163 
    164 	for (i = 0; i < entries; i++) {
    165 		dentries[i].filename = strdup(namelist[i]->d_name);
    166 		if (dentries[i].filename == NULL)
    167 			critical_error_errno("strdup");
    168 
    169 		asprintf(&dentries[i].path, "%s%s", dir_path, namelist[i]->d_name);
    170 		asprintf(&dentries[i].full_path, "%s%s", full_path, namelist[i]->d_name);
    171 
    172 		free(namelist[i]);
    173 
    174 		ret = lstat(dentries[i].full_path, &stat);
    175 		if (ret < 0) {
    176 			error_errno("lstat");
    177 			i--;
    178 			entries--;
    179 			continue;
    180 		}
    181 
    182 		dentries[i].size = stat.st_size;
    183 		dentries[i].mode = stat.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
    184 		dentries[i].mtime = stat.st_mtime;
    185 		uint64_t capabilities;
    186 		if (fs_config_func != NULL) {
    187 #ifdef ANDROID
    188 			unsigned int mode = 0;
    189 			unsigned int uid = 0;
    190 			unsigned int gid = 0;
    191 			int dir = S_ISDIR(stat.st_mode);
    192 			fs_config_func(dentries[i].path, dir, &uid, &gid, &mode, &capabilities);
    193 			dentries[i].mode = mode;
    194 			dentries[i].uid = uid;
    195 			dentries[i].gid = gid;
    196 			dentries[i].capabilities = capabilities;
    197 #else
    198 			error("can't set android permissions - built without android support");
    199 #endif
    200 		}
    201 #ifndef USE_MINGW
    202 		if (sehnd) {
    203 			if (selabel_lookup(sehnd, &dentries[i].secon, dentries[i].path, stat.st_mode) < 0) {
    204 				error("cannot lookup security context for %s", dentries[i].path);
    205 			}
    206 
    207 			if (dentries[i].secon && verbose)
    208 				printf("Labeling %s as %s\n", dentries[i].path, dentries[i].secon);
    209 		}
    210 #endif
    211 
    212 		if (S_ISREG(stat.st_mode)) {
    213 			dentries[i].file_type = EXT4_FT_REG_FILE;
    214 		} else if (S_ISDIR(stat.st_mode)) {
    215 			dentries[i].file_type = EXT4_FT_DIR;
    216 			dirs++;
    217 		} else if (S_ISCHR(stat.st_mode)) {
    218 			dentries[i].file_type = EXT4_FT_CHRDEV;
    219 		} else if (S_ISBLK(stat.st_mode)) {
    220 			dentries[i].file_type = EXT4_FT_BLKDEV;
    221 		} else if (S_ISFIFO(stat.st_mode)) {
    222 			dentries[i].file_type = EXT4_FT_FIFO;
    223 		} else if (S_ISSOCK(stat.st_mode)) {
    224 			dentries[i].file_type = EXT4_FT_SOCK;
    225 		} else if (S_ISLNK(stat.st_mode)) {
    226 			dentries[i].file_type = EXT4_FT_SYMLINK;
    227 			dentries[i].link = calloc(info.block_size, 1);
    228 			readlink(dentries[i].full_path, dentries[i].link, info.block_size - 1);
    229 		} else {
    230 			error("unknown file type on %s", dentries[i].path);
    231 			i--;
    232 			entries--;
    233 		}
    234 	}
    235 	free(namelist);
    236 
    237 	if (needs_lost_and_found) {
    238 		/* insert a lost+found directory at the beginning of the dentries */
    239 		struct dentry *tmp = calloc(entries + 1, sizeof(struct dentry));
    240 		memset(tmp, 0, sizeof(struct dentry));
    241 		memcpy(tmp + 1, dentries, entries * sizeof(struct dentry));
    242 		dentries = tmp;
    243 
    244 		dentries[0].filename = strdup("lost+found");
    245 		asprintf(&dentries[0].path, "%slost+found", dir_path);
    246 		dentries[0].full_path = NULL;
    247 		dentries[0].size = 0;
    248 		dentries[0].mode = S_IRWXU;
    249 		dentries[0].file_type = EXT4_FT_DIR;
    250 		dentries[0].uid = 0;
    251 		dentries[0].gid = 0;
    252 		if (sehnd) {
    253 			if (selabel_lookup(sehnd, &dentries[0].secon, dentries[0].path, dentries[0].mode) < 0)
    254 				error("cannot lookup security context for %s", dentries[0].path);
    255 		}
    256 		entries++;
    257 		dirs++;
    258 	}
    259 
    260 	inode = make_directory(dir_inode, entries, dentries, dirs);
    261 
    262 	for (i = 0; i < entries; i++) {
    263 		if (dentries[i].file_type == EXT4_FT_REG_FILE) {
    264 			entry_inode = make_file(dentries[i].full_path, dentries[i].size);
    265 		} else if (dentries[i].file_type == EXT4_FT_DIR) {
    266 			char *subdir_full_path = NULL;
    267 			char *subdir_dir_path;
    268 			if (dentries[i].full_path) {
    269 				ret = asprintf(&subdir_full_path, "%s/", dentries[i].full_path);
    270 				if (ret < 0)
    271 					critical_error_errno("asprintf");
    272 			}
    273 			ret = asprintf(&subdir_dir_path, "%s/", dentries[i].path);
    274 			if (ret < 0)
    275 				critical_error_errno("asprintf");
    276 			entry_inode = build_directory_structure(subdir_full_path,
    277 					subdir_dir_path, inode, fs_config_func, sehnd, verbose);
    278 			free(subdir_full_path);
    279 			free(subdir_dir_path);
    280 		} else if (dentries[i].file_type == EXT4_FT_SYMLINK) {
    281 			entry_inode = make_link(dentries[i].link);
    282 		} else {
    283 			error("unknown file type on %s", dentries[i].path);
    284 			entry_inode = 0;
    285 		}
    286 		*dentries[i].inode = entry_inode;
    287 
    288 		ret = inode_set_permissions(entry_inode, dentries[i].mode,
    289 			dentries[i].uid, dentries[i].gid,
    290 			dentries[i].mtime);
    291 		if (ret)
    292 			error("failed to set permissions on %s\n", dentries[i].path);
    293 
    294 		/*
    295 		 * It's important to call inode_set_selinux() before
    296 		 * inode_set_capabilities(). Extended attributes need to
    297 		 * be stored sorted order, and we guarantee this by making
    298 		 * the calls in the proper order.
    299 		 * Please see xattr_assert_sane() in contents.c
    300 		 */
    301 		ret = inode_set_selinux(entry_inode, dentries[i].secon);
    302 		if (ret)
    303 			error("failed to set SELinux context on %s\n", dentries[i].path);
    304 		ret = inode_set_capabilities(entry_inode, dentries[i].capabilities);
    305 		if (ret)
    306 			error("failed to set capability on %s\n", dentries[i].path);
    307 
    308 		free(dentries[i].path);
    309 		free(dentries[i].full_path);
    310 		free(dentries[i].link);
    311 		free((void *)dentries[i].filename);
    312 		free(dentries[i].secon);
    313 	}
    314 
    315 	free(dentries);
    316 	return inode;
    317 }
    318 #endif
    319 
    320 static u32 compute_block_size()
    321 {
    322 	return 4096;
    323 }
    324 
    325 static u32 compute_journal_blocks()
    326 {
    327 	u32 journal_blocks = DIV_ROUND_UP(info.len, info.block_size) / 64;
    328 	if (journal_blocks < 1024)
    329 		journal_blocks = 1024;
    330 	if (journal_blocks > 32768)
    331 		journal_blocks = 32768;
    332 	return journal_blocks;
    333 }
    334 
    335 static u32 compute_blocks_per_group()
    336 {
    337 	return info.block_size * 8;
    338 }
    339 
    340 static u32 compute_inodes()
    341 {
    342 	return DIV_ROUND_UP(info.len, info.block_size) / 4;
    343 }
    344 
    345 static u32 compute_inodes_per_group()
    346 {
    347 	u32 blocks = DIV_ROUND_UP(info.len, info.block_size);
    348 	u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group);
    349 	u32 inodes = DIV_ROUND_UP(info.inodes, block_groups);
    350 	inodes = ALIGN(inodes, (info.block_size / info.inode_size));
    351 
    352 	/* After properly rounding up the number of inodes/group,
    353 	 * make sure to update the total inodes field in the info struct.
    354 	 */
    355 	info.inodes = inodes * block_groups;
    356 
    357 	return inodes;
    358 }
    359 
    360 static u32 compute_bg_desc_reserve_blocks()
    361 {
    362 	u32 blocks = DIV_ROUND_UP(info.len, info.block_size);
    363 	u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group);
    364 	u32 bg_desc_blocks = DIV_ROUND_UP(block_groups * sizeof(struct ext2_group_desc),
    365 			info.block_size);
    366 
    367 	u32 bg_desc_reserve_blocks =
    368 			DIV_ROUND_UP(block_groups * 1024 * sizeof(struct ext2_group_desc),
    369 					info.block_size) - bg_desc_blocks;
    370 
    371 	if (bg_desc_reserve_blocks > info.block_size / sizeof(u32))
    372 		bg_desc_reserve_blocks = info.block_size / sizeof(u32);
    373 
    374 	return bg_desc_reserve_blocks;
    375 }
    376 
    377 void reset_ext4fs_info() {
    378     // Reset all the global data structures used by make_ext4fs so it
    379     // can be called again.
    380     memset(&info, 0, sizeof(info));
    381     memset(&aux_info, 0, sizeof(aux_info));
    382 
    383     if (ext4_sparse_file) {
    384         sparse_file_destroy(ext4_sparse_file);
    385         ext4_sparse_file = NULL;
    386     }
    387 }
    388 
    389 int make_ext4fs_sparse_fd(int fd, long long len,
    390                 const char *mountpoint, struct selabel_handle *sehnd)
    391 {
    392 	reset_ext4fs_info();
    393 	info.len = len;
    394 
    395 	return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, sehnd, 0);
    396 }
    397 
    398 int make_ext4fs(const char *filename, long long len,
    399                 const char *mountpoint, struct selabel_handle *sehnd)
    400 {
    401 	int fd;
    402 	int status;
    403 
    404 	reset_ext4fs_info();
    405 	info.len = len;
    406 
    407 	fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
    408 	if (fd < 0) {
    409 		error_errno("open");
    410 		return EXIT_FAILURE;
    411 	}
    412 
    413 	status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, sehnd, 0);
    414 	close(fd);
    415 
    416 	return status;
    417 }
    418 
    419 /* return a newly-malloc'd string that is a copy of str.  The new string
    420    is guaranteed to have a trailing slash.  If absolute is true, the new string
    421    is also guaranteed to have a leading slash.
    422 */
    423 static char *canonicalize_slashes(const char *str, bool absolute)
    424 {
    425 	char *ret;
    426 	int len = strlen(str);
    427 	int newlen = len;
    428 	char *ptr;
    429 
    430 	if (len == 0) {
    431 		if (absolute)
    432 			return strdup("/");
    433 		else
    434 			return strdup("");
    435 	}
    436 
    437 	if (str[0] != '/' && absolute) {
    438 		newlen++;
    439 	}
    440 	if (str[len - 1] != '/') {
    441 		newlen++;
    442 	}
    443 	ret = malloc(newlen + 1);
    444 	if (!ret) {
    445 		critical_error("malloc");
    446 	}
    447 
    448 	ptr = ret;
    449 	if (str[0] != '/' && absolute) {
    450 		*ptr++ = '/';
    451 	}
    452 
    453 	strcpy(ptr, str);
    454 	ptr += len;
    455 
    456 	if (str[len - 1] != '/') {
    457 		*ptr++ = '/';
    458 	}
    459 
    460 	if (ptr != ret + newlen) {
    461 		critical_error("assertion failed\n");
    462 	}
    463 
    464 	*ptr = '\0';
    465 
    466 	return ret;
    467 }
    468 
    469 static char *canonicalize_abs_slashes(const char *str)
    470 {
    471 	return canonicalize_slashes(str, true);
    472 }
    473 
    474 static char *canonicalize_rel_slashes(const char *str)
    475 {
    476 	return canonicalize_slashes(str, false);
    477 }
    478 
    479 int make_ext4fs_internal(int fd, const char *_directory,
    480                          const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
    481                          int sparse, int crc, int wipe,
    482                          struct selabel_handle *sehnd, int verbose)
    483 {
    484 	u32 root_inode_num;
    485 	u16 root_mode;
    486 	char *mountpoint;
    487 	char *directory = NULL;
    488 
    489 	if (setjmp(setjmp_env))
    490 		return EXIT_FAILURE; /* Handle a call to longjmp() */
    491 
    492 	if (_mountpoint == NULL) {
    493 		mountpoint = strdup("");
    494 	} else {
    495 		mountpoint = canonicalize_abs_slashes(_mountpoint);
    496 	}
    497 
    498 	if (_directory) {
    499 		directory = canonicalize_rel_slashes(_directory);
    500 	}
    501 
    502 	if (info.len <= 0)
    503 		info.len = get_file_size(fd);
    504 
    505 	if (info.len <= 0) {
    506 		fprintf(stderr, "Need size of filesystem\n");
    507 		return EXIT_FAILURE;
    508 	}
    509 
    510 	if (info.block_size <= 0)
    511 		info.block_size = compute_block_size();
    512 
    513 	/* Round down the filesystem length to be a multiple of the block size */
    514 	info.len &= ~((u64)info.block_size - 1);
    515 
    516 	if (info.journal_blocks == 0)
    517 		info.journal_blocks = compute_journal_blocks();
    518 
    519 	if (info.no_journal == 0)
    520 		info.feat_compat = EXT4_FEATURE_COMPAT_HAS_JOURNAL;
    521 	else
    522 		info.journal_blocks = 0;
    523 
    524 	if (info.blocks_per_group <= 0)
    525 		info.blocks_per_group = compute_blocks_per_group();
    526 
    527 	if (info.inodes <= 0)
    528 		info.inodes = compute_inodes();
    529 
    530 	if (info.inode_size <= 0)
    531 		info.inode_size = 256;
    532 
    533 	if (info.label == NULL)
    534 		info.label = "";
    535 
    536 	info.inodes_per_group = compute_inodes_per_group();
    537 
    538 	info.feat_compat |=
    539 			EXT4_FEATURE_COMPAT_RESIZE_INODE |
    540 			EXT4_FEATURE_COMPAT_EXT_ATTR;
    541 
    542 	info.feat_ro_compat |=
    543 			EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER |
    544 			EXT4_FEATURE_RO_COMPAT_LARGE_FILE |
    545 			EXT4_FEATURE_RO_COMPAT_GDT_CSUM;
    546 
    547 	info.feat_incompat |=
    548 			EXT4_FEATURE_INCOMPAT_EXTENTS |
    549 			EXT4_FEATURE_INCOMPAT_FILETYPE;
    550 
    551 
    552 	info.bg_desc_reserve_blocks = compute_bg_desc_reserve_blocks();
    553 
    554 	printf("Creating filesystem with parameters:\n");
    555 	printf("    Size: %"PRIu64"\n", info.len);
    556 	printf("    Block size: %d\n", info.block_size);
    557 	printf("    Blocks per group: %d\n", info.blocks_per_group);
    558 	printf("    Inodes per group: %d\n", info.inodes_per_group);
    559 	printf("    Inode size: %d\n", info.inode_size);
    560 	printf("    Journal blocks: %d\n", info.journal_blocks);
    561 	printf("    Label: %s\n", info.label);
    562 
    563 	ext4_create_fs_aux_info();
    564 
    565 	printf("    Blocks: %"PRIu64"\n", aux_info.len_blocks);
    566 	printf("    Block groups: %d\n", aux_info.groups);
    567 	printf("    Reserved block group size: %d\n", info.bg_desc_reserve_blocks);
    568 
    569 	ext4_sparse_file = sparse_file_new(info.block_size, info.len);
    570 
    571 	block_allocator_init();
    572 
    573 	ext4_fill_in_sb();
    574 
    575 	if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
    576 		error("failed to reserve first 10 inodes");
    577 
    578 	if (info.feat_compat & EXT4_FEATURE_COMPAT_HAS_JOURNAL)
    579 		ext4_create_journal_inode();
    580 
    581 	if (info.feat_compat & EXT4_FEATURE_COMPAT_RESIZE_INODE)
    582 		ext4_create_resize_inode();
    583 
    584 #ifdef USE_MINGW
    585 	// Windows needs only 'create an empty fs image' functionality
    586 	assert(!directory);
    587 	root_inode_num = build_default_directory_structure(mountpoint, sehnd);
    588 #else
    589 	if (directory)
    590 		root_inode_num = build_directory_structure(directory, mountpoint, 0,
    591                         fs_config_func, sehnd, verbose);
    592 	else
    593 		root_inode_num = build_default_directory_structure(mountpoint, sehnd);
    594 #endif
    595 
    596 	root_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
    597 	inode_set_permissions(root_inode_num, root_mode, 0, 0, 0);
    598 
    599 #ifndef USE_MINGW
    600 	if (sehnd) {
    601 		char *secontext = NULL;
    602 
    603 		if (selabel_lookup(sehnd, &secontext, mountpoint, S_IFDIR) < 0) {
    604 			error("cannot lookup security context for %s", mountpoint);
    605 		}
    606 		if (secontext) {
    607 			if (verbose) {
    608 				printf("Labeling %s as %s\n", mountpoint, secontext);
    609 			}
    610 			inode_set_selinux(root_inode_num, secontext);
    611 		}
    612 		freecon(secontext);
    613 	}
    614 #endif
    615 
    616 	ext4_update_free();
    617 
    618 	ext4_queue_sb();
    619 
    620 	printf("Created filesystem with %d/%d inodes and %d/%d blocks\n",
    621 			aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
    622 			aux_info.sb->s_inodes_count,
    623 			aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo,
    624 			aux_info.sb->s_blocks_count_lo);
    625 
    626 	if (wipe && WIPE_IS_SUPPORTED) {
    627 		wipe_block_device(fd, info.len);
    628 	}
    629 
    630 	write_ext4_image(fd, gzip, sparse, crc);
    631 
    632 	sparse_file_destroy(ext4_sparse_file);
    633 	ext4_sparse_file = NULL;
    634 
    635 	free(mountpoint);
    636 	free(directory);
    637 
    638 	return 0;
    639 }
    640