Home | History | Annotate | Download | only in ext4_utils
      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, time_t fixed_time)
    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 		if (fixed_time == -1) {
    185 			dentries[i].mtime = stat.st_mtime;
    186 		} else {
    187 			dentries[i].mtime = fixed_time;
    188 		}
    189 		uint64_t capabilities;
    190 		if (fs_config_func != NULL) {
    191 #ifdef ANDROID
    192 			unsigned int mode = 0;
    193 			unsigned int uid = 0;
    194 			unsigned int gid = 0;
    195 			int dir = S_ISDIR(stat.st_mode);
    196 			fs_config_func(dentries[i].path, dir, &uid, &gid, &mode, &capabilities);
    197 			dentries[i].mode = mode;
    198 			dentries[i].uid = uid;
    199 			dentries[i].gid = gid;
    200 			dentries[i].capabilities = capabilities;
    201 #else
    202 			error("can't set android permissions - built without android support");
    203 #endif
    204 		}
    205 #ifndef USE_MINGW
    206 		if (sehnd) {
    207 			if (selabel_lookup(sehnd, &dentries[i].secon, dentries[i].path, stat.st_mode) < 0) {
    208 				error("cannot lookup security context for %s", dentries[i].path);
    209 			}
    210 
    211 			if (dentries[i].secon && verbose)
    212 				printf("Labeling %s as %s\n", dentries[i].path, dentries[i].secon);
    213 		}
    214 #endif
    215 
    216 		if (S_ISREG(stat.st_mode)) {
    217 			dentries[i].file_type = EXT4_FT_REG_FILE;
    218 		} else if (S_ISDIR(stat.st_mode)) {
    219 			dentries[i].file_type = EXT4_FT_DIR;
    220 			dirs++;
    221 		} else if (S_ISCHR(stat.st_mode)) {
    222 			dentries[i].file_type = EXT4_FT_CHRDEV;
    223 		} else if (S_ISBLK(stat.st_mode)) {
    224 			dentries[i].file_type = EXT4_FT_BLKDEV;
    225 		} else if (S_ISFIFO(stat.st_mode)) {
    226 			dentries[i].file_type = EXT4_FT_FIFO;
    227 		} else if (S_ISSOCK(stat.st_mode)) {
    228 			dentries[i].file_type = EXT4_FT_SOCK;
    229 		} else if (S_ISLNK(stat.st_mode)) {
    230 			dentries[i].file_type = EXT4_FT_SYMLINK;
    231 			dentries[i].link = calloc(info.block_size, 1);
    232 			readlink(dentries[i].full_path, dentries[i].link, info.block_size - 1);
    233 		} else {
    234 			error("unknown file type on %s", dentries[i].path);
    235 			i--;
    236 			entries--;
    237 		}
    238 	}
    239 	free(namelist);
    240 
    241 	if (needs_lost_and_found) {
    242 		/* insert a lost+found directory at the beginning of the dentries */
    243 		struct dentry *tmp = calloc(entries + 1, sizeof(struct dentry));
    244 		memset(tmp, 0, sizeof(struct dentry));
    245 		memcpy(tmp + 1, dentries, entries * sizeof(struct dentry));
    246 		dentries = tmp;
    247 
    248 		dentries[0].filename = strdup("lost+found");
    249 		asprintf(&dentries[0].path, "%slost+found", dir_path);
    250 		dentries[0].full_path = NULL;
    251 		dentries[0].size = 0;
    252 		dentries[0].mode = S_IRWXU;
    253 		dentries[0].file_type = EXT4_FT_DIR;
    254 		dentries[0].uid = 0;
    255 		dentries[0].gid = 0;
    256 		if (sehnd) {
    257 			if (selabel_lookup(sehnd, &dentries[0].secon, dentries[0].path, dentries[0].mode) < 0)
    258 				error("cannot lookup security context for %s", dentries[0].path);
    259 		}
    260 		entries++;
    261 		dirs++;
    262 	}
    263 
    264 	inode = make_directory(dir_inode, entries, dentries, dirs);
    265 
    266 	for (i = 0; i < entries; i++) {
    267 		if (dentries[i].file_type == EXT4_FT_REG_FILE) {
    268 			entry_inode = make_file(dentries[i].full_path, dentries[i].size);
    269 		} else if (dentries[i].file_type == EXT4_FT_DIR) {
    270 			char *subdir_full_path = NULL;
    271 			char *subdir_dir_path;
    272 			if (dentries[i].full_path) {
    273 				ret = asprintf(&subdir_full_path, "%s/", dentries[i].full_path);
    274 				if (ret < 0)
    275 					critical_error_errno("asprintf");
    276 			}
    277 			ret = asprintf(&subdir_dir_path, "%s/", dentries[i].path);
    278 			if (ret < 0)
    279 				critical_error_errno("asprintf");
    280 			entry_inode = build_directory_structure(subdir_full_path,
    281 					subdir_dir_path, inode, fs_config_func, sehnd, verbose, fixed_time);
    282 			free(subdir_full_path);
    283 			free(subdir_dir_path);
    284 		} else if (dentries[i].file_type == EXT4_FT_SYMLINK) {
    285 			entry_inode = make_link(dentries[i].link);
    286 		} else {
    287 			error("unknown file type on %s", dentries[i].path);
    288 			entry_inode = 0;
    289 		}
    290 		*dentries[i].inode = entry_inode;
    291 
    292 		ret = inode_set_permissions(entry_inode, dentries[i].mode,
    293 			dentries[i].uid, dentries[i].gid,
    294 			dentries[i].mtime);
    295 		if (ret)
    296 			error("failed to set permissions on %s\n", dentries[i].path);
    297 
    298 		/*
    299 		 * It's important to call inode_set_selinux() before
    300 		 * inode_set_capabilities(). Extended attributes need to
    301 		 * be stored sorted order, and we guarantee this by making
    302 		 * the calls in the proper order.
    303 		 * Please see xattr_assert_sane() in contents.c
    304 		 */
    305 		ret = inode_set_selinux(entry_inode, dentries[i].secon);
    306 		if (ret)
    307 			error("failed to set SELinux context on %s\n", dentries[i].path);
    308 		ret = inode_set_capabilities(entry_inode, dentries[i].capabilities);
    309 		if (ret)
    310 			error("failed to set capability on %s\n", dentries[i].path);
    311 
    312 		free(dentries[i].path);
    313 		free(dentries[i].full_path);
    314 		free(dentries[i].link);
    315 		free((void *)dentries[i].filename);
    316 		free(dentries[i].secon);
    317 	}
    318 
    319 	free(dentries);
    320 	return inode;
    321 }
    322 #endif
    323 
    324 static u32 compute_block_size()
    325 {
    326 	return 4096;
    327 }
    328 
    329 static u32 compute_journal_blocks()
    330 {
    331 	u32 journal_blocks = DIV_ROUND_UP(info.len, info.block_size) / 64;
    332 	if (journal_blocks < 1024)
    333 		journal_blocks = 1024;
    334 	if (journal_blocks > 32768)
    335 		journal_blocks = 32768;
    336 	return journal_blocks;
    337 }
    338 
    339 static u32 compute_blocks_per_group()
    340 {
    341 	return info.block_size * 8;
    342 }
    343 
    344 static u32 compute_inodes()
    345 {
    346 	return DIV_ROUND_UP(info.len, info.block_size) / 4;
    347 }
    348 
    349 static u32 compute_inodes_per_group()
    350 {
    351 	u32 blocks = DIV_ROUND_UP(info.len, info.block_size);
    352 	u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group);
    353 	u32 inodes = DIV_ROUND_UP(info.inodes, block_groups);
    354 	inodes = EXT4_ALIGN(inodes, (info.block_size / info.inode_size));
    355 
    356 	/* After properly rounding up the number of inodes/group,
    357 	 * make sure to update the total inodes field in the info struct.
    358 	 */
    359 	info.inodes = inodes * block_groups;
    360 
    361 	return inodes;
    362 }
    363 
    364 static u32 compute_bg_desc_reserve_blocks()
    365 {
    366 	u32 blocks = DIV_ROUND_UP(info.len, info.block_size);
    367 	u32 block_groups = DIV_ROUND_UP(blocks, info.blocks_per_group);
    368 	u32 bg_desc_blocks = DIV_ROUND_UP(block_groups * sizeof(struct ext2_group_desc),
    369 			info.block_size);
    370 
    371 	u32 bg_desc_reserve_blocks =
    372 			DIV_ROUND_UP(block_groups * 1024 * sizeof(struct ext2_group_desc),
    373 					info.block_size) - bg_desc_blocks;
    374 
    375 	if (bg_desc_reserve_blocks > info.block_size / sizeof(u32))
    376 		bg_desc_reserve_blocks = info.block_size / sizeof(u32);
    377 
    378 	return bg_desc_reserve_blocks;
    379 }
    380 
    381 void reset_ext4fs_info() {
    382 	// Reset all the global data structures used by make_ext4fs so it
    383 	// can be called again.
    384 	memset(&info, 0, sizeof(info));
    385 	memset(&aux_info, 0, sizeof(aux_info));
    386 
    387 	if (ext4_sparse_file) {
    388 		sparse_file_destroy(ext4_sparse_file);
    389 		ext4_sparse_file = NULL;
    390 	}
    391 }
    392 
    393 int make_ext4fs_sparse_fd(int fd, long long len,
    394 				const char *mountpoint, struct selabel_handle *sehnd)
    395 {
    396 	reset_ext4fs_info();
    397 	info.len = len;
    398 
    399 	return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, sehnd, 0, -1, NULL);
    400 }
    401 
    402 int make_ext4fs(const char *filename, long long len,
    403 				const char *mountpoint, struct selabel_handle *sehnd)
    404 {
    405 	int fd;
    406 	int status;
    407 
    408 	reset_ext4fs_info();
    409 	info.len = len;
    410 
    411 	fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
    412 	if (fd < 0) {
    413 		error_errno("open");
    414 		return EXIT_FAILURE;
    415 	}
    416 
    417 	status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, sehnd, 0, -1, NULL);
    418 	close(fd);
    419 
    420 	return status;
    421 }
    422 
    423 /* return a newly-malloc'd string that is a copy of str.  The new string
    424    is guaranteed to have a trailing slash.  If absolute is true, the new string
    425    is also guaranteed to have a leading slash.
    426 */
    427 static char *canonicalize_slashes(const char *str, bool absolute)
    428 {
    429 	char *ret;
    430 	int len = strlen(str);
    431 	int newlen = len;
    432 	char *ptr;
    433 
    434 	if (len == 0) {
    435 		if (absolute)
    436 			return strdup("/");
    437 		else
    438 			return strdup("");
    439 	}
    440 
    441 	if (str[0] != '/' && absolute) {
    442 		newlen++;
    443 	}
    444 	if (str[len - 1] != '/') {
    445 		newlen++;
    446 	}
    447 	ret = malloc(newlen + 1);
    448 	if (!ret) {
    449 		critical_error("malloc");
    450 	}
    451 
    452 	ptr = ret;
    453 	if (str[0] != '/' && absolute) {
    454 		*ptr++ = '/';
    455 	}
    456 
    457 	strcpy(ptr, str);
    458 	ptr += len;
    459 
    460 	if (str[len - 1] != '/') {
    461 		*ptr++ = '/';
    462 	}
    463 
    464 	if (ptr != ret + newlen) {
    465 		critical_error("assertion failed\n");
    466 	}
    467 
    468 	*ptr = '\0';
    469 
    470 	return ret;
    471 }
    472 
    473 static char *canonicalize_abs_slashes(const char *str)
    474 {
    475 	return canonicalize_slashes(str, true);
    476 }
    477 
    478 static char *canonicalize_rel_slashes(const char *str)
    479 {
    480 	return canonicalize_slashes(str, false);
    481 }
    482 
    483 int make_ext4fs_internal(int fd, const char *_directory,
    484 						 const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
    485 						 int sparse, int crc, int wipe,
    486 						 struct selabel_handle *sehnd, int verbose, time_t fixed_time,
    487 						 FILE* block_list_file)
    488 {
    489 	u32 root_inode_num;
    490 	u16 root_mode;
    491 	char *mountpoint;
    492 	char *directory = NULL;
    493 
    494 	if (setjmp(setjmp_env))
    495 		return EXIT_FAILURE; /* Handle a call to longjmp() */
    496 
    497 	if (_mountpoint == NULL) {
    498 		mountpoint = strdup("");
    499 	} else {
    500 		mountpoint = canonicalize_abs_slashes(_mountpoint);
    501 	}
    502 
    503 	if (_directory) {
    504 		directory = canonicalize_rel_slashes(_directory);
    505 	}
    506 
    507 	if (info.len <= 0)
    508 		info.len = get_file_size(fd);
    509 
    510 	if (info.len <= 0) {
    511 		fprintf(stderr, "Need size of filesystem\n");
    512 		return EXIT_FAILURE;
    513 	}
    514 
    515 	if (info.block_size <= 0)
    516 		info.block_size = compute_block_size();
    517 
    518 	/* Round down the filesystem length to be a multiple of the block size */
    519 	info.len &= ~((u64)info.block_size - 1);
    520 
    521 	if (info.journal_blocks == 0)
    522 		info.journal_blocks = compute_journal_blocks();
    523 
    524 	if (info.no_journal == 0)
    525 		info.feat_compat = EXT4_FEATURE_COMPAT_HAS_JOURNAL;
    526 	else
    527 		info.journal_blocks = 0;
    528 
    529 	if (info.blocks_per_group <= 0)
    530 		info.blocks_per_group = compute_blocks_per_group();
    531 
    532 	if (info.inodes <= 0)
    533 		info.inodes = compute_inodes();
    534 
    535 	if (info.inode_size <= 0)
    536 		info.inode_size = 256;
    537 
    538 	if (info.label == NULL)
    539 		info.label = "";
    540 
    541 	info.inodes_per_group = compute_inodes_per_group();
    542 
    543 	info.feat_compat |=
    544 			EXT4_FEATURE_COMPAT_RESIZE_INODE |
    545 			EXT4_FEATURE_COMPAT_EXT_ATTR;
    546 
    547 	info.feat_ro_compat |=
    548 			EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER |
    549 			EXT4_FEATURE_RO_COMPAT_LARGE_FILE |
    550 			EXT4_FEATURE_RO_COMPAT_GDT_CSUM;
    551 
    552 	info.feat_incompat |=
    553 			EXT4_FEATURE_INCOMPAT_EXTENTS |
    554 			EXT4_FEATURE_INCOMPAT_FILETYPE;
    555 
    556 
    557 	info.bg_desc_reserve_blocks = compute_bg_desc_reserve_blocks();
    558 
    559 	printf("Creating filesystem with parameters:\n");
    560 	printf("    Size: %"PRIu64"\n", info.len);
    561 	printf("    Block size: %d\n", info.block_size);
    562 	printf("    Blocks per group: %d\n", info.blocks_per_group);
    563 	printf("    Inodes per group: %d\n", info.inodes_per_group);
    564 	printf("    Inode size: %d\n", info.inode_size);
    565 	printf("    Journal blocks: %d\n", info.journal_blocks);
    566 	printf("    Label: %s\n", info.label);
    567 
    568 	ext4_create_fs_aux_info();
    569 
    570 	printf("    Blocks: %"PRIu64"\n", aux_info.len_blocks);
    571 	printf("    Block groups: %d\n", aux_info.groups);
    572 	printf("    Reserved block group size: %d\n", info.bg_desc_reserve_blocks);
    573 
    574 	ext4_sparse_file = sparse_file_new(info.block_size, info.len);
    575 
    576 	block_allocator_init();
    577 
    578 	ext4_fill_in_sb();
    579 
    580 	if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
    581 		error("failed to reserve first 10 inodes");
    582 
    583 	if (info.feat_compat & EXT4_FEATURE_COMPAT_HAS_JOURNAL)
    584 		ext4_create_journal_inode();
    585 
    586 	if (info.feat_compat & EXT4_FEATURE_COMPAT_RESIZE_INODE)
    587 		ext4_create_resize_inode();
    588 
    589 #ifdef USE_MINGW
    590 	// Windows needs only 'create an empty fs image' functionality
    591 	assert(!directory);
    592 	root_inode_num = build_default_directory_structure(mountpoint, sehnd);
    593 #else
    594 	if (directory)
    595 		root_inode_num = build_directory_structure(directory, mountpoint, 0,
    596 			fs_config_func, sehnd, verbose, fixed_time);
    597 	else
    598 		root_inode_num = build_default_directory_structure(mountpoint, sehnd);
    599 #endif
    600 
    601 	root_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
    602 	inode_set_permissions(root_inode_num, root_mode, 0, 0, 0);
    603 
    604 #ifndef USE_MINGW
    605 	if (sehnd) {
    606 		char *secontext = NULL;
    607 
    608 		if (selabel_lookup(sehnd, &secontext, mountpoint, S_IFDIR) < 0) {
    609 			error("cannot lookup security context for %s", mountpoint);
    610 		}
    611 		if (secontext) {
    612 			if (verbose) {
    613 				printf("Labeling %s as %s\n", mountpoint, secontext);
    614 			}
    615 			inode_set_selinux(root_inode_num, secontext);
    616 		}
    617 		freecon(secontext);
    618 	}
    619 #endif
    620 
    621 	ext4_update_free();
    622 
    623 	ext4_queue_sb();
    624 
    625 	if (block_list_file) {
    626 		size_t dirlen = directory ? strlen(directory) : 0;
    627 		struct block_allocation* p = get_saved_allocation_chain();
    628 		while (p) {
    629 			if (directory && strncmp(p->filename, directory, dirlen) == 0) {
    630 				// substitute mountpoint for the leading directory in the filename, in the output file
    631 				fprintf(block_list_file, "%s%s", mountpoint, p->filename + dirlen);
    632 			} else {
    633 				fprintf(block_list_file, "%s", p->filename);
    634 			}
    635 			print_blocks(block_list_file, p);
    636 			struct block_allocation* pn = p->next;
    637 			free_alloc(p);
    638 			p = pn;
    639 		}
    640 	}
    641 
    642 	printf("Created filesystem with %d/%d inodes and %d/%d blocks\n",
    643 			aux_info.sb->s_inodes_count - aux_info.sb->s_free_inodes_count,
    644 			aux_info.sb->s_inodes_count,
    645 			aux_info.sb->s_blocks_count_lo - aux_info.sb->s_free_blocks_count_lo,
    646 			aux_info.sb->s_blocks_count_lo);
    647 
    648 	if (wipe && WIPE_IS_SUPPORTED) {
    649 		wipe_block_device(fd, info.len);
    650 	}
    651 
    652 	write_ext4_image(fd, gzip, sparse, crc);
    653 
    654 	sparse_file_destroy(ext4_sparse_file);
    655 	ext4_sparse_file = NULL;
    656 
    657 	free(mountpoint);
    658 	free(directory);
    659 
    660 	return 0;
    661 }
    662