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