| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018-2019 HUAWEI, Inc. |
| * http://www.huawei.com/ |
| * Created by Li Guifu <bluce.liguifu@huawei.com> |
| */ |
| #define _GNU_SOURCE |
| #include <ctype.h> |
| #include <time.h> |
| #include <sys/time.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <libgen.h> |
| #include <sys/stat.h> |
| #include <getopt.h> |
| #include "erofs/config.h" |
| #include "erofs/print.h" |
| #include "erofs/cache.h" |
| #include "erofs/diskbuf.h" |
| #include "erofs/inode.h" |
| #include "erofs/tar.h" |
| #include "erofs/compress.h" |
| #include "erofs/dedupe.h" |
| #include "erofs/xattr.h" |
| #include "erofs/exclude.h" |
| #include "erofs/block_list.h" |
| #include "erofs/compress_hints.h" |
| #include "erofs/blobchunk.h" |
| #include "erofs/fragments.h" |
| #include "erofs/rebuild.h" |
| #include "../lib/liberofs_private.h" |
| #include "../lib/liberofs_uuid.h" |
| #include "../lib/compressor.h" |
| |
| static struct option long_options[] = { |
| {"version", no_argument, 0, 'V'}, |
| {"help", no_argument, 0, 'h'}, |
| {"exclude-path", required_argument, NULL, 2}, |
| {"exclude-regex", required_argument, NULL, 3}, |
| #ifdef HAVE_LIBSELINUX |
| {"file-contexts", required_argument, NULL, 4}, |
| #endif |
| {"force-uid", required_argument, NULL, 5}, |
| {"force-gid", required_argument, NULL, 6}, |
| {"all-root", no_argument, NULL, 7}, |
| #ifndef NDEBUG |
| {"random-pclusterblks", no_argument, NULL, 8}, |
| {"random-algorithms", no_argument, NULL, 18}, |
| #endif |
| {"max-extent-bytes", required_argument, NULL, 9}, |
| {"compress-hints", required_argument, NULL, 10}, |
| {"chunksize", required_argument, NULL, 11}, |
| {"quiet", no_argument, 0, 12}, |
| {"blobdev", required_argument, NULL, 13}, |
| {"ignore-mtime", no_argument, NULL, 14}, |
| {"preserve-mtime", no_argument, NULL, 15}, |
| {"uid-offset", required_argument, NULL, 16}, |
| {"gid-offset", required_argument, NULL, 17}, |
| {"tar", optional_argument, NULL, 20}, |
| {"aufs", no_argument, NULL, 21}, |
| {"mount-point", required_argument, NULL, 512}, |
| {"xattr-prefix", required_argument, NULL, 19}, |
| #ifdef WITH_ANDROID |
| {"product-out", required_argument, NULL, 513}, |
| {"fs-config-file", required_argument, NULL, 514}, |
| {"block-list-file", required_argument, NULL, 515}, |
| #endif |
| {"ovlfs-strip", optional_argument, NULL, 516}, |
| {"offset", required_argument, NULL, 517}, |
| #ifdef HAVE_ZLIB |
| {"gzip", no_argument, NULL, 518}, |
| {"ungzip", optional_argument, NULL, 518}, |
| #endif |
| #ifdef HAVE_LIBLZMA |
| {"unlzma", optional_argument, NULL, 519}, |
| {"unxz", optional_argument, NULL, 519}, |
| #endif |
| #ifdef EROFS_MT_ENABLED |
| {"workers", required_argument, NULL, 520}, |
| #endif |
| {"zfeature-bits", required_argument, NULL, 521}, |
| {"clean", optional_argument, NULL, 522}, |
| {"incremental", optional_argument, NULL, 523}, |
| {"root-xattr-isize", required_argument, NULL, 524}, |
| {"mkfs-time", no_argument, NULL, 525}, |
| {"all-time", no_argument, NULL, 526}, |
| {"sort", required_argument, NULL, 527}, |
| {0, 0, 0, 0}, |
| }; |
| |
| static void print_available_compressors(FILE *f, const char *delim) |
| { |
| int i = 0; |
| bool comma = false; |
| const struct erofs_algorithm *s; |
| |
| while ((s = z_erofs_list_available_compressors(&i)) != NULL) { |
| if (comma) |
| fputs(delim, f); |
| fputs(s->name, f); |
| comma = true; |
| } |
| fputc('\n', f); |
| } |
| |
| static void usage(int argc, char **argv) |
| { |
| int i = 0; |
| const struct erofs_algorithm *s; |
| |
| // " 1 2 3 4 5 6 7 8 " |
| // "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" |
| printf( |
| "Usage: %s [OPTIONS] FILE SOURCE(s)\n" |
| "Generate EROFS image (FILE) from SOURCE(s).\n" |
| "\n" |
| "General options:\n" |
| " -V, --version print the version number of mkfs.erofs and exit\n" |
| " -h, --help display this help and exit\n" |
| "\n" |
| " -b# set block size to # (# = page size by default)\n" |
| " -d<0-9> set output verbosity; 0=quiet, 9=verbose (default=%i)\n" |
| " -x# set xattr tolerance to # (< 0, disable xattrs; default 2)\n" |
| " -zX[,level=Y] X=compressor (Y=compression level, Z=dictionary size, optional)\n" |
| " [,dictsize=Z] alternative compressors can be separated by colons(:)\n" |
| " [:...] supported compressors and their option ranges are:\n", |
| argv[0], EROFS_WARN); |
| while ((s = z_erofs_list_available_compressors(&i)) != NULL) { |
| const char spaces[] = " "; |
| |
| printf("%s%s\n", spaces, s->name); |
| if (s->c->setlevel) { |
| if (!strcmp(s->name, "lzma")) |
| /* A little kludge to show the range as disjointed |
| * "0-9,100-109" instead of a continuous "0-109", and to |
| * state what those two subranges respectively mean. */ |
| printf("%s [,level=<0-9,100-109>]\t0-9=normal, 100-109=extreme (default=%i)\n", |
| spaces, s->c->default_level); |
| else |
| printf("%s [,level=<0-%i>]\t\t(default=%i)\n", |
| spaces, s->c->best_level, s->c->default_level); |
| } |
| if (s->c->setdictsize) { |
| if (s->c->default_dictsize) |
| printf("%s [,dictsize=<dictsize>]\t(default=%u, max=%u)\n", |
| spaces, s->c->default_dictsize, s->c->max_dictsize); |
| else |
| printf("%s [,dictsize=<dictsize>]\t(default=<auto>, max=%u)\n", |
| spaces, s->c->max_dictsize); |
| } |
| } |
| printf( |
| " -C# specify the size of compress physical cluster in bytes\n" |
| " -EX[,...] X=extended options\n" |
| " -L volume-label set the volume label (maximum 15 bytes)\n" |
| " -T# specify a fixed UNIX timestamp # as build time\n" |
| " --all-time the timestamp is also applied to all files (default)\n" |
| " --mkfs-time the timestamp is applied as build time only\n" |
| " -UX use a given filesystem UUID\n" |
| " --all-root make all files owned by root\n" |
| " --blobdev=X specify an extra device X to store chunked data\n" |
| " --chunksize=# generate chunk-based files with #-byte chunks\n" |
| " --clean=X run full clean build (default) or:\n" |
| " --incremental=X run incremental build\n" |
| " (X = data|rvsp; data=full data, rvsp=space is allocated\n" |
| " and filled with zeroes)\n" |
| " --compress-hints=X specify a file to configure per-file compression strategy\n" |
| " --exclude-path=X avoid including file X (X = exact literal path)\n" |
| " --exclude-regex=X avoid including files that match X (X = regular expression)\n" |
| #ifdef HAVE_LIBSELINUX |
| " --file-contexts=X specify a file contexts file to setup selinux labels\n" |
| #endif |
| " --force-uid=# set all file uids to # (# = UID)\n" |
| " --force-gid=# set all file gids to # (# = GID)\n" |
| " --uid-offset=# add offset # to all file uids (# = id offset)\n" |
| " --gid-offset=# add offset # to all file gids (# = id offset)\n" |
| " --ignore-mtime use build time instead of strict per-file modification time\n" |
| " --max-extent-bytes=# set maximum decompressed extent size # in bytes\n" |
| " --mount-point=X X=prefix of target fs path (default: /)\n" |
| " --preserve-mtime keep per-file modification time strictly\n" |
| " --offset=# skip # bytes at the beginning of IMAGE.\n" |
| " --root-xattr-isize=# ensure the inline xattr size of the root directory is # bytes at least\n" |
| " --aufs replace aufs special files with overlayfs metadata\n" |
| " --sort=<path,none> data sorting order for tarballs as input (default: path)\n" |
| " --tar=X generate a full or index-only image from a tarball(-ish) source\n" |
| " (X = f|i|headerball; f=full mode, i=index mode,\n" |
| " headerball=file data is omited in the source stream)\n" |
| " --ovlfs-strip=<0,1> strip overlayfs metadata in the target image (e.g. whiteouts)\n" |
| " --quiet quiet execution (do not write anything to standard output.)\n" |
| #ifndef NDEBUG |
| " --random-pclusterblks randomize pclusterblks for big pcluster (debugging only)\n" |
| " --random-algorithms randomize per-file algorithms (debugging only)\n" |
| #endif |
| #ifdef HAVE_ZLIB |
| " --ungzip[=X] try to filter the tarball stream through gzip\n" |
| " (and optionally dump the raw stream to X together)\n" |
| #endif |
| #ifdef HAVE_LIBLZMA |
| " --unxz[=X] try to filter the tarball stream through xz/lzma/lzip\n" |
| " (and optionally dump the raw stream to X together)\n" |
| #endif |
| #ifdef EROFS_MT_ENABLED |
| " --workers=# set the number of worker threads to # (default: %u)\n" |
| #endif |
| " --xattr-prefix=X X=extra xattr name prefix\n" |
| " --zfeature-bits=# toggle filesystem compression features according to given bits #\n" |
| #ifdef WITH_ANDROID |
| "\n" |
| "Android-specific options:\n" |
| " --product-out=X X=product_out directory\n" |
| " --fs-config-file=X X=fs_config file\n" |
| " --block-list-file=X X=block_list file\n" |
| #endif |
| #ifdef EROFS_MT_ENABLED |
| , erofs_get_available_processors() /* --workers= */ |
| #endif |
| ); |
| } |
| |
| static void version(void) |
| { |
| printf("mkfs.erofs (erofs-utils) %s\navailable compressors: ", |
| cfg.c_version); |
| print_available_compressors(stdout, ", "); |
| } |
| |
| static unsigned int pclustersize_packed, pclustersize_max; |
| static struct erofs_tarfile erofstar = { |
| .global.xattrs = LIST_HEAD_INIT(erofstar.global.xattrs) |
| }; |
| static bool tar_mode, rebuild_mode, incremental_mode; |
| |
| enum { |
| EROFS_MKFS_DATA_IMPORT_DEFAULT, |
| EROFS_MKFS_DATA_IMPORT_FULLDATA, |
| EROFS_MKFS_DATA_IMPORT_RVSP, |
| EROFS_MKFS_DATA_IMPORT_SPARSE, |
| } dataimport_mode; |
| |
| static unsigned int rebuild_src_count; |
| static LIST_HEAD(rebuild_src_list); |
| static u8 fixeduuid[16]; |
| static bool valid_fixeduuid; |
| |
| static int erofs_mkfs_feat_set_legacy_compress(bool en, const char *val, |
| unsigned int vallen) |
| { |
| if (vallen) |
| return -EINVAL; |
| /* disable compacted indexes and 0padding */ |
| cfg.c_legacy_compress = en; |
| return 0; |
| } |
| |
| static int erofs_mkfs_feat_set_ztailpacking(bool en, const char *val, |
| unsigned int vallen) |
| { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_ztailpacking = en; |
| return 0; |
| } |
| |
| static int erofs_mkfs_feat_set_fragments(bool en, const char *val, |
| unsigned int vallen) |
| { |
| if (!en) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_fragments = false; |
| return 0; |
| } |
| |
| if (vallen) { |
| char *endptr; |
| u64 i = strtoull(val, &endptr, 0); |
| |
| if (endptr - val != vallen) { |
| erofs_err("invalid pcluster size %s for the packed file", val); |
| return -EINVAL; |
| } |
| pclustersize_packed = i; |
| } |
| cfg.c_fragments = true; |
| return 0; |
| } |
| |
| static int erofs_mkfs_feat_set_all_fragments(bool en, const char *val, |
| unsigned int vallen) |
| { |
| cfg.c_all_fragments = en; |
| return erofs_mkfs_feat_set_fragments(en, val, vallen); |
| } |
| |
| static int erofs_mkfs_feat_set_dedupe(bool en, const char *val, |
| unsigned int vallen) |
| { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_dedupe = en; |
| return 0; |
| } |
| |
| static struct { |
| char *feat; |
| int (*set)(bool en, const char *val, unsigned int len); |
| } z_erofs_mkfs_features[] = { |
| {"legacy-compress", erofs_mkfs_feat_set_legacy_compress}, |
| {"ztailpacking", erofs_mkfs_feat_set_ztailpacking}, |
| {"fragments", erofs_mkfs_feat_set_fragments}, |
| {"all-fragments", erofs_mkfs_feat_set_all_fragments}, |
| {"dedupe", erofs_mkfs_feat_set_dedupe}, |
| {NULL, NULL}, |
| }; |
| |
| static int parse_extended_opts(const char *opts) |
| { |
| #define MATCH_EXTENTED_OPT(opt, token, keylen) \ |
| (keylen == strlen(opt) && !memcmp(token, opt, keylen)) |
| |
| const char *token, *next, *tokenend, *value __maybe_unused; |
| unsigned int keylen, vallen; |
| |
| value = NULL; |
| for (token = opts; *token != '\0'; token = next) { |
| bool clear = false; |
| const char *p = strchr(token, ','); |
| |
| next = NULL; |
| if (p) { |
| next = p + 1; |
| } else { |
| p = token + strlen(token); |
| next = p; |
| } |
| |
| tokenend = memchr(token, '=', p - token); |
| if (tokenend) { |
| keylen = tokenend - token; |
| vallen = p - tokenend - 1; |
| if (!vallen) |
| return -EINVAL; |
| |
| value = tokenend + 1; |
| } else { |
| keylen = p - token; |
| vallen = 0; |
| } |
| |
| if (token[0] == '^') { |
| if (keylen < 2) |
| return -EINVAL; |
| ++token; |
| --keylen; |
| clear = true; |
| } |
| |
| if (MATCH_EXTENTED_OPT("force-inode-compact", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_inodeversion = FORCE_INODE_COMPACT; |
| cfg.c_ignore_mtime = true; |
| } else if (MATCH_EXTENTED_OPT("force-inode-extended", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_inodeversion = FORCE_INODE_EXTENDED; |
| } else if (MATCH_EXTENTED_OPT("nosbcrc", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| erofs_sb_clear_sb_chksum(&g_sbi); |
| } else if (MATCH_EXTENTED_OPT("noinline_data", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_inline_data = false; |
| } else if (MATCH_EXTENTED_OPT("inline_data", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_inline_data = !clear; |
| } else if (MATCH_EXTENTED_OPT("force-inode-blockmap", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_chunkformat = FORCE_INODE_BLOCK_MAP; |
| } else if (MATCH_EXTENTED_OPT("force-chunk-indexes", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_chunkformat = FORCE_INODE_CHUNK_INDEXES; |
| } else if (MATCH_EXTENTED_OPT("xattr-name-filter", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_xattr_name_filter = !clear; |
| } else { |
| int i, err; |
| |
| for (i = 0; z_erofs_mkfs_features[i].feat; ++i) { |
| if (!MATCH_EXTENTED_OPT(z_erofs_mkfs_features[i].feat, |
| token, keylen)) |
| continue; |
| err = z_erofs_mkfs_features[i].set(!clear, value, vallen); |
| if (err) |
| return err; |
| break; |
| } |
| |
| if (!z_erofs_mkfs_features[i].feat) { |
| erofs_err("unknown extended option %.*s", |
| (int)(p - token), token); |
| return -EINVAL; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int mkfs_apply_zfeature_bits(uintmax_t bits) |
| { |
| int i; |
| |
| for (i = 0; bits; ++i) { |
| int err; |
| |
| if (!z_erofs_mkfs_features[i].feat) { |
| erofs_err("unsupported zfeature bit %u", i); |
| return -EINVAL; |
| } |
| err = z_erofs_mkfs_features[i].set(bits & 1, NULL, 0); |
| if (err) { |
| erofs_err("failed to apply zfeature %s", |
| z_erofs_mkfs_features[i].feat); |
| return err; |
| } |
| bits >>= 1; |
| } |
| return 0; |
| } |
| |
| static void mkfs_parse_tar_cfg(char *cfg) |
| { |
| char *p; |
| |
| tar_mode = true; |
| if (!cfg) |
| return; |
| p = strchr(cfg, ','); |
| if (p) { |
| *p = '\0'; |
| if ((*++p) != '\0') |
| erofstar.mapfile = strdup(p); |
| } |
| if (!strcmp(cfg, "headerball")) |
| erofstar.headeronly_mode = true; |
| |
| if (erofstar.headeronly_mode || !strcmp(optarg, "i") || |
| !strcmp(optarg, "0")) |
| erofstar.index_mode = true; |
| } |
| |
| static int mkfs_parse_one_compress_alg(char *alg, |
| struct erofs_compr_opts *copts) |
| { |
| char *p, *q, *opt, *endptr; |
| |
| copts->level = -1; |
| copts->dict_size = 0; |
| |
| p = strchr(alg, ','); |
| if (p) { |
| copts->alg = strndup(alg, p - alg); |
| |
| /* support old '-zlzma,9' form */ |
| if (isdigit(*(p + 1))) { |
| copts->level = strtol(p + 1, &endptr, 10); |
| if (*endptr && *endptr != ',') { |
| erofs_err("invalid compression level %s", |
| p + 1); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| } else { |
| copts->alg = strdup(alg); |
| return 0; |
| } |
| |
| opt = p + 1; |
| while (opt) { |
| q = strchr(opt, ','); |
| if (q) |
| *q = '\0'; |
| |
| if ((p = strstr(opt, "level="))) { |
| p += strlen("level="); |
| copts->level = strtol(p, &endptr, 10); |
| if ((endptr == p) || (*endptr && *endptr != ',')) { |
| erofs_err("invalid compression level %s", p); |
| return -EINVAL; |
| } |
| } else if ((p = strstr(opt, "dictsize="))) { |
| p += strlen("dictsize="); |
| copts->dict_size = strtoul(p, &endptr, 10); |
| if (*endptr == 'k' || *endptr == 'K') |
| copts->dict_size <<= 10; |
| else if (*endptr == 'm' || *endptr == 'M') |
| copts->dict_size <<= 20; |
| else if ((endptr == p) || (*endptr && *endptr != ',')) { |
| erofs_err("invalid compression dictsize %s", p); |
| return -EINVAL; |
| } |
| } else { |
| erofs_err("invalid compression option %s", opt); |
| return -EINVAL; |
| } |
| |
| opt = q ? q + 1 : NULL; |
| } |
| |
| return 0; |
| } |
| |
| static int mkfs_parse_compress_algs(char *algs) |
| { |
| unsigned int i; |
| char *s; |
| int ret; |
| |
| for (s = strtok(algs, ":"), i = 0; s; s = strtok(NULL, ":"), ++i) { |
| if (i >= EROFS_MAX_COMPR_CFGS - 1) { |
| erofs_err("too many algorithm types"); |
| return -EINVAL; |
| } |
| |
| ret = mkfs_parse_one_compress_alg(s, &cfg.c_compr_opts[i]); |
| if (ret) |
| return ret; |
| } |
| return 0; |
| } |
| |
| static void erofs_rebuild_cleanup(void) |
| { |
| struct erofs_sb_info *src, *n; |
| |
| list_for_each_entry_safe(src, n, &rebuild_src_list, list) { |
| list_del(&src->list); |
| erofs_put_super(src); |
| erofs_dev_close(src); |
| free(src); |
| } |
| rebuild_src_count = 0; |
| } |
| |
| static int mkfs_parse_options_cfg(int argc, char *argv[]) |
| { |
| char *endptr; |
| int opt, i, err; |
| bool quiet = false; |
| int tarerofs_decoder = 0; |
| bool has_timestamp = false; |
| |
| while ((opt = getopt_long(argc, argv, "C:E:L:T:U:b:d:x:z:Vh", |
| long_options, NULL)) != -1) { |
| switch (opt) { |
| case 'z': |
| i = mkfs_parse_compress_algs(optarg); |
| if (i) |
| return i; |
| break; |
| |
| case 'b': |
| i = atoi(optarg); |
| if (i < 512 || i > EROFS_MAX_BLOCK_SIZE) { |
| erofs_err("invalid block size %s", optarg); |
| return -EINVAL; |
| } |
| g_sbi.blkszbits = ilog2(i); |
| break; |
| |
| case 'd': |
| i = atoi(optarg); |
| if (i < EROFS_MSG_MIN || i > EROFS_MSG_MAX) { |
| erofs_err("invalid debug level %d", i); |
| return -EINVAL; |
| } |
| cfg.c_dbg_lvl = i; |
| break; |
| |
| case 'x': |
| i = strtol(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid xattr tolerance %s", optarg); |
| return -EINVAL; |
| } |
| cfg.c_inline_xattr_tolerance = i; |
| break; |
| |
| case 'E': |
| opt = parse_extended_opts(optarg); |
| if (opt) |
| return opt; |
| break; |
| |
| case 'L': |
| if (optarg == NULL || |
| strlen(optarg) > (sizeof(g_sbi.volume_name) - 1u)) { |
| erofs_err("invalid volume label"); |
| return -EINVAL; |
| } |
| strncpy(g_sbi.volume_name, optarg, |
| sizeof(g_sbi.volume_name)); |
| break; |
| |
| case 'T': |
| cfg.c_unix_timestamp = strtoull(optarg, &endptr, 0); |
| if (cfg.c_unix_timestamp == -1 || *endptr != '\0') { |
| erofs_err("invalid UNIX timestamp %s", optarg); |
| return -EINVAL; |
| } |
| has_timestamp = true; |
| break; |
| case 'U': |
| if (erofs_uuid_parse(optarg, fixeduuid)) { |
| erofs_err("invalid UUID %s", optarg); |
| return -EINVAL; |
| } |
| valid_fixeduuid = true; |
| break; |
| case 2: |
| opt = erofs_parse_exclude_path(optarg, false); |
| if (opt) { |
| erofs_err("failed to parse exclude path: %s", |
| erofs_strerror(opt)); |
| return opt; |
| } |
| break; |
| case 3: |
| opt = erofs_parse_exclude_path(optarg, true); |
| if (opt) { |
| erofs_err("failed to parse exclude regex: %s", |
| erofs_strerror(opt)); |
| return opt; |
| } |
| break; |
| |
| case 4: |
| opt = erofs_selabel_open(optarg); |
| if (opt && opt != -EBUSY) |
| return opt; |
| break; |
| case 5: |
| cfg.c_uid = strtoul(optarg, &endptr, 0); |
| if (cfg.c_uid == -1 || *endptr != '\0') { |
| erofs_err("invalid uid %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 6: |
| cfg.c_gid = strtoul(optarg, &endptr, 0); |
| if (cfg.c_gid == -1 || *endptr != '\0') { |
| erofs_err("invalid gid %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 7: |
| cfg.c_uid = cfg.c_gid = 0; |
| break; |
| #ifndef NDEBUG |
| case 8: |
| cfg.c_random_pclusterblks = true; |
| break; |
| case 18: |
| cfg.c_random_algorithms = true; |
| break; |
| #endif |
| case 9: |
| cfg.c_max_decompressed_extent_bytes = |
| strtoul(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid maximum uncompressed extent size %s", |
| optarg); |
| return -EINVAL; |
| } |
| break; |
| case 10: |
| cfg.c_compress_hints_file = optarg; |
| break; |
| case 512: |
| cfg.mount_point = optarg; |
| /* all trailing '/' should be deleted */ |
| opt = strlen(cfg.mount_point); |
| if (opt && optarg[opt - 1] == '/') |
| optarg[opt - 1] = '\0'; |
| break; |
| #ifdef WITH_ANDROID |
| case 513: |
| cfg.target_out_path = optarg; |
| break; |
| case 514: |
| cfg.fs_config_file = optarg; |
| break; |
| case 515: |
| cfg.block_list_file = optarg; |
| break; |
| #endif |
| case 'C': |
| i = strtoull(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid physical clustersize %s", |
| optarg); |
| return -EINVAL; |
| } |
| pclustersize_max = i; |
| break; |
| case 11: |
| i = strtol(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid chunksize %s", optarg); |
| return -EINVAL; |
| } |
| cfg.c_chunkbits = ilog2(i); |
| if ((1 << cfg.c_chunkbits) != i) { |
| erofs_err("chunksize %s must be a power of two", |
| optarg); |
| return -EINVAL; |
| } |
| erofs_sb_set_chunked_file(&g_sbi); |
| break; |
| case 12: |
| quiet = true; |
| break; |
| case 13: |
| cfg.c_blobdev_path = optarg; |
| break; |
| case 14: |
| cfg.c_ignore_mtime = true; |
| break; |
| case 15: |
| cfg.c_ignore_mtime = false; |
| break; |
| case 16: |
| errno = 0; |
| cfg.c_uid_offset = strtoll(optarg, &endptr, 0); |
| if (errno || *endptr != '\0') { |
| erofs_err("invalid uid offset %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 17: |
| errno = 0; |
| cfg.c_gid_offset = strtoll(optarg, &endptr, 0); |
| if (errno || *endptr != '\0') { |
| erofs_err("invalid gid offset %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 19: |
| errno = 0; |
| opt = erofs_xattr_insert_name_prefix(optarg); |
| if (opt) { |
| erofs_err("failed to parse xattr name prefix: %s", |
| erofs_strerror(opt)); |
| return opt; |
| } |
| cfg.c_extra_ea_name_prefixes = true; |
| break; |
| case 20: |
| mkfs_parse_tar_cfg(optarg); |
| break; |
| case 21: |
| erofstar.aufs = true; |
| break; |
| case 516: |
| if (!optarg || !strcmp(optarg, "1")) |
| cfg.c_ovlfs_strip = true; |
| else |
| cfg.c_ovlfs_strip = false; |
| break; |
| case 517: |
| g_sbi.bdev.offset = strtoull(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid disk offset %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 518: |
| case 519: |
| if (optarg) |
| erofstar.dumpfile = strdup(optarg); |
| tarerofs_decoder = EROFS_IOS_DECODER_GZIP + (opt - 518); |
| break; |
| #ifdef EROFS_MT_ENABLED |
| case 520: { |
| unsigned int processors; |
| |
| cfg.c_mt_workers = strtoul(optarg, &endptr, 0); |
| if (errno || *endptr != '\0') { |
| erofs_err("invalid worker number %s", optarg); |
| return -EINVAL; |
| } |
| |
| processors = erofs_get_available_processors(); |
| if (cfg.c_mt_workers > processors) |
| erofs_warn("%d workers exceed %d processors, potentially impacting performance.", |
| cfg.c_mt_workers, processors); |
| break; |
| } |
| #endif |
| case 521: |
| i = strtol(optarg, &endptr, 0); |
| if (errno || *endptr != '\0') { |
| erofs_err("invalid zfeature bits %s", optarg); |
| return -EINVAL; |
| } |
| err = mkfs_apply_zfeature_bits(i); |
| if (err) |
| return err; |
| break; |
| case 522: |
| case 523: |
| if (!optarg || !strcmp(optarg, "data")) { |
| dataimport_mode = EROFS_MKFS_DATA_IMPORT_FULLDATA; |
| } else if (!strcmp(optarg, "rvsp")) { |
| dataimport_mode = EROFS_MKFS_DATA_IMPORT_RVSP; |
| } else { |
| dataimport_mode = strtol(optarg, &endptr, 0); |
| if (errno || *endptr != '\0') { |
| erofs_err("invalid --%s=%s", |
| opt == 523 ? "incremental" : "clean", optarg); |
| return -EINVAL; |
| } |
| } |
| incremental_mode = (opt == 523); |
| break; |
| case 524: |
| cfg.c_root_xattr_isize = strtoull(optarg, &endptr, 0); |
| if (*endptr != '\0') { |
| erofs_err("invalid the minimum inline xattr size %s", optarg); |
| return -EINVAL; |
| } |
| break; |
| case 525: |
| cfg.c_timeinherit = TIMESTAMP_NONE; |
| break; |
| case 526: |
| cfg.c_timeinherit = TIMESTAMP_FIXED; |
| break; |
| case 527: |
| if (!strcmp(optarg, "none")) |
| erofstar.try_no_reorder = true; |
| break; |
| case 'V': |
| version(); |
| exit(0); |
| case 'h': |
| usage(argc, argv); |
| exit(0); |
| |
| default: /* '?' */ |
| return -EINVAL; |
| } |
| } |
| |
| if (cfg.c_blobdev_path && cfg.c_chunkbits < g_sbi.blkszbits) { |
| erofs_err("--blobdev must be used together with --chunksize"); |
| return -EINVAL; |
| } |
| |
| /* TODO: can be implemented with (deviceslot) mapped_blkaddr */ |
| if (cfg.c_blobdev_path && |
| cfg.c_force_chunkformat == FORCE_INODE_BLOCK_MAP) { |
| erofs_err("--blobdev cannot work with block map currently"); |
| return -EINVAL; |
| } |
| |
| if (optind >= argc) { |
| erofs_err("missing argument: FILE"); |
| return -EINVAL; |
| } |
| |
| cfg.c_img_path = strdup(argv[optind++]); |
| if (!cfg.c_img_path) |
| return -ENOMEM; |
| |
| if (optind >= argc) { |
| if (!tar_mode) { |
| erofs_err("missing argument: SOURCE(s)"); |
| return -EINVAL; |
| } else { |
| int dupfd; |
| |
| dupfd = dup(STDIN_FILENO); |
| if (dupfd < 0) { |
| erofs_err("failed to duplicate STDIN_FILENO: %s", |
| strerror(errno)); |
| return -errno; |
| } |
| err = erofs_iostream_open(&erofstar.ios, dupfd, |
| tarerofs_decoder); |
| if (err) |
| return err; |
| } |
| } else { |
| struct stat st; |
| |
| cfg.c_src_path = realpath(argv[optind++], NULL); |
| if (!cfg.c_src_path) { |
| erofs_err("failed to parse source directory: %s", |
| erofs_strerror(-errno)); |
| return -ENOENT; |
| } |
| |
| if (tar_mode) { |
| int fd = open(cfg.c_src_path, O_RDONLY); |
| |
| if (fd < 0) { |
| erofs_err("failed to open file: %s", cfg.c_src_path); |
| return -errno; |
| } |
| err = erofs_iostream_open(&erofstar.ios, fd, |
| tarerofs_decoder); |
| if (err) |
| return err; |
| |
| if (erofstar.dumpfile) { |
| fd = open(erofstar.dumpfile, |
| O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (fd < 0) { |
| erofs_err("failed to open dumpfile: %s", |
| erofstar.dumpfile); |
| return -errno; |
| } |
| erofstar.ios.dumpfd = fd; |
| } |
| } else { |
| err = lstat(cfg.c_src_path, &st); |
| if (err) |
| return -errno; |
| if (S_ISDIR(st.st_mode)) |
| erofs_set_fs_root(cfg.c_src_path); |
| else |
| rebuild_mode = true; |
| } |
| |
| if (rebuild_mode) { |
| char *srcpath = cfg.c_src_path; |
| struct erofs_sb_info *src; |
| |
| do { |
| src = calloc(1, sizeof(struct erofs_sb_info)); |
| if (!src) { |
| erofs_rebuild_cleanup(); |
| return -ENOMEM; |
| } |
| |
| err = erofs_dev_open(src, srcpath, O_RDONLY); |
| if (err) { |
| free(src); |
| erofs_rebuild_cleanup(); |
| return err; |
| } |
| |
| /* extra device index starts from 1 */ |
| src->dev = ++rebuild_src_count; |
| list_add(&src->list, &rebuild_src_list); |
| } while (optind < argc && (srcpath = argv[optind++])); |
| } else if (optind < argc) { |
| erofs_err("unexpected argument: %s\n", argv[optind]); |
| return -EINVAL; |
| } |
| } |
| if (quiet) { |
| cfg.c_dbg_lvl = EROFS_ERR; |
| cfg.c_showprogress = false; |
| } |
| |
| if (pclustersize_max) { |
| if (pclustersize_max < erofs_blksiz(&g_sbi) || |
| pclustersize_max % erofs_blksiz(&g_sbi)) { |
| erofs_err("invalid physical clustersize %u", |
| pclustersize_max); |
| return -EINVAL; |
| } |
| cfg.c_mkfs_pclustersize_max = pclustersize_max; |
| cfg.c_mkfs_pclustersize_def = cfg.c_mkfs_pclustersize_max; |
| } |
| if (cfg.c_chunkbits && cfg.c_chunkbits < g_sbi.blkszbits) { |
| erofs_err("chunksize %u must be larger than block size", |
| 1u << cfg.c_chunkbits); |
| return -EINVAL; |
| } |
| |
| if (pclustersize_packed) { |
| if (pclustersize_packed < erofs_blksiz(&g_sbi) || |
| pclustersize_packed % erofs_blksiz(&g_sbi)) { |
| erofs_err("invalid pcluster size for the packed file %u", |
| pclustersize_packed); |
| return -EINVAL; |
| } |
| cfg.c_mkfs_pclustersize_packed = pclustersize_packed; |
| } |
| |
| if (has_timestamp && cfg.c_timeinherit == TIMESTAMP_UNSPECIFIED) |
| cfg.c_timeinherit = TIMESTAMP_FIXED; |
| return 0; |
| } |
| |
| static void erofs_mkfs_default_options(void) |
| { |
| cfg.c_showprogress = true; |
| cfg.c_legacy_compress = false; |
| cfg.c_inline_data = true; |
| cfg.c_xattr_name_filter = true; |
| #ifdef EROFS_MT_ENABLED |
| cfg.c_mt_workers = erofs_get_available_processors(); |
| cfg.c_mkfs_segment_size = 16ULL * 1024 * 1024; |
| #endif |
| g_sbi.blkszbits = ilog2(min_t(u32, getpagesize(), EROFS_MAX_BLOCK_SIZE)); |
| cfg.c_mkfs_pclustersize_max = erofs_blksiz(&g_sbi); |
| cfg.c_mkfs_pclustersize_def = cfg.c_mkfs_pclustersize_max; |
| g_sbi.feature_incompat = EROFS_FEATURE_INCOMPAT_ZERO_PADDING; |
| g_sbi.feature_compat = EROFS_FEATURE_COMPAT_SB_CHKSUM | |
| EROFS_FEATURE_COMPAT_MTIME; |
| } |
| |
| /* https://reproducible-builds.org/specs/source-date-epoch/ for more details */ |
| int parse_source_date_epoch(void) |
| { |
| char *source_date_epoch; |
| unsigned long long epoch = -1ULL; |
| char *endptr; |
| |
| source_date_epoch = getenv("SOURCE_DATE_EPOCH"); |
| if (!source_date_epoch) |
| return 0; |
| |
| epoch = strtoull(source_date_epoch, &endptr, 10); |
| if (epoch == -1ULL || *endptr != '\0') { |
| erofs_err("environment variable $SOURCE_DATE_EPOCH %s is invalid", |
| source_date_epoch); |
| return -EINVAL; |
| } |
| |
| if (cfg.c_force_inodeversion != FORCE_INODE_EXTENDED) |
| erofs_info("SOURCE_DATE_EPOCH is set, forcely generate extended inodes instead"); |
| |
| cfg.c_force_inodeversion = FORCE_INODE_EXTENDED; |
| cfg.c_unix_timestamp = epoch; |
| cfg.c_timeinherit = TIMESTAMP_CLAMPING; |
| return 0; |
| } |
| |
| void erofs_show_progs(int argc, char *argv[]) |
| { |
| if (cfg.c_dbg_lvl >= EROFS_WARN) |
| printf("%s %s\n", basename(argv[0]), cfg.c_version); |
| } |
| |
| static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root) |
| { |
| struct erofs_sb_info *src; |
| unsigned int extra_devices = 0; |
| erofs_blk_t nblocks; |
| int ret, idx; |
| enum erofs_rebuild_datamode datamode; |
| |
| switch (dataimport_mode) { |
| case EROFS_MKFS_DATA_IMPORT_DEFAULT: |
| datamode = EROFS_REBUILD_DATA_BLOB_INDEX; |
| break; |
| case EROFS_MKFS_DATA_IMPORT_FULLDATA: |
| datamode = EROFS_REBUILD_DATA_FULL; |
| break; |
| case EROFS_MKFS_DATA_IMPORT_RVSP: |
| datamode = EROFS_REBUILD_DATA_RESVSP; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| list_for_each_entry(src, &rebuild_src_list, list) { |
| ret = erofs_rebuild_load_tree(root, src, datamode); |
| if (ret) { |
| erofs_err("failed to load %s", src->devname); |
| return ret; |
| } |
| if (src->extra_devices > 1) { |
| erofs_err("%s: unsupported number %u of extra devices", |
| src->devname, src->extra_devices); |
| return -EOPNOTSUPP; |
| } |
| extra_devices += src->extra_devices; |
| } |
| |
| if (datamode != EROFS_REBUILD_DATA_BLOB_INDEX) |
| return 0; |
| |
| /* Each blob has either no extra device or only one device for TarFS */ |
| if (extra_devices && extra_devices != rebuild_src_count) { |
| erofs_err("extra_devices(%u) is mismatched with source images(%u)", |
| extra_devices, rebuild_src_count); |
| return -EOPNOTSUPP; |
| } |
| |
| ret = erofs_mkfs_init_devices(&g_sbi, rebuild_src_count); |
| if (ret) |
| return ret; |
| |
| list_for_each_entry(src, &rebuild_src_list, list) { |
| u8 *tag = NULL; |
| |
| if (extra_devices) { |
| nblocks = src->devs[0].blocks; |
| tag = src->devs[0].tag; |
| } else { |
| nblocks = src->primarydevice_blocks; |
| } |
| DBG_BUGON(src->dev < 1); |
| idx = src->dev - 1; |
| g_sbi.devs[idx].blocks = nblocks; |
| if (tag && *tag) |
| memcpy(g_sbi.devs[idx].tag, tag, sizeof(g_sbi.devs[0].tag)); |
| else |
| /* convert UUID of the source image to a hex string */ |
| sprintf((char *)g_sbi.devs[idx].tag, |
| "%04x%04x%04x%04x%04x%04x%04x%04x", |
| (src->uuid[0] << 8) | src->uuid[1], |
| (src->uuid[2] << 8) | src->uuid[3], |
| (src->uuid[4] << 8) | src->uuid[5], |
| (src->uuid[6] << 8) | src->uuid[7], |
| (src->uuid[8] << 8) | src->uuid[9], |
| (src->uuid[10] << 8) | src->uuid[11], |
| (src->uuid[12] << 8) | src->uuid[13], |
| (src->uuid[14] << 8) | src->uuid[15]); |
| } |
| return 0; |
| } |
| |
| static void erofs_mkfs_showsummaries(erofs_blk_t nblocks) |
| { |
| char uuid_str[37] = {}; |
| char *incr = incremental_mode ? "new" : "total"; |
| |
| if (!(cfg.c_dbg_lvl > EROFS_ERR && cfg.c_showprogress)) |
| return; |
| |
| erofs_uuid_unparse_lower(g_sbi.uuid, uuid_str); |
| |
| fprintf(stdout, "------\nFilesystem UUID: %s\n" |
| "Filesystem total blocks: %u (of %u-byte blocks)\n" |
| "Filesystem total inodes: %llu\n" |
| "Filesystem %s metadata blocks: %u\n" |
| "Filesystem %s deduplicated bytes (of source files): %llu\n", |
| uuid_str, nblocks, 1U << g_sbi.blkszbits, g_sbi.inos | 0ULL, |
| incr, erofs_total_metablocks(g_sbi.bmgr), |
| incr, g_sbi.saved_by_deduplication | 0ULL); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int err = 0; |
| struct erofs_buffer_head *sb_bh; |
| struct erofs_inode *root = NULL; |
| erofs_blk_t nblocks = 0; |
| struct timeval t; |
| FILE *packedfile = NULL; |
| FILE *blklst = NULL; |
| u32 crc; |
| |
| erofs_init_configure(); |
| erofs_mkfs_default_options(); |
| |
| err = mkfs_parse_options_cfg(argc, argv); |
| erofs_show_progs(argc, argv); |
| if (err) { |
| if (err == -EINVAL) |
| fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); |
| return 1; |
| } |
| |
| err = parse_source_date_epoch(); |
| if (err) { |
| fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); |
| return 1; |
| } |
| |
| if (cfg.c_unix_timestamp != -1) { |
| g_sbi.build_time = cfg.c_unix_timestamp; |
| g_sbi.build_time_nsec = 0; |
| } else if (!gettimeofday(&t, NULL)) { |
| g_sbi.build_time = t.tv_sec; |
| g_sbi.build_time_nsec = t.tv_usec; |
| } |
| |
| err = erofs_dev_open(&g_sbi, cfg.c_img_path, O_RDWR | |
| (incremental_mode ? 0 : O_TRUNC)); |
| if (err) { |
| fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); |
| return 1; |
| } |
| |
| #ifdef WITH_ANDROID |
| if (cfg.fs_config_file && |
| load_canned_fs_config(cfg.fs_config_file) < 0) { |
| erofs_err("failed to load fs config %s", cfg.fs_config_file); |
| return 1; |
| } |
| |
| if (cfg.block_list_file) { |
| blklst = fopen(cfg.block_list_file, "w"); |
| if (!blklst || erofs_blocklist_open(blklst, false)) { |
| erofs_err("failed to open %s", cfg.block_list_file); |
| return 1; |
| } |
| } |
| #endif |
| erofs_show_config(); |
| if (cfg.c_fragments || cfg.c_extra_ea_name_prefixes) { |
| if (!cfg.c_mkfs_pclustersize_packed) |
| cfg.c_mkfs_pclustersize_packed = cfg.c_mkfs_pclustersize_def; |
| |
| packedfile = erofs_packedfile_init(); |
| if (IS_ERR(packedfile)) { |
| erofs_err("failed to initialize packedfile"); |
| return 1; |
| } |
| } |
| |
| if (cfg.c_fragments) { |
| err = z_erofs_fragments_init(); |
| if (err) { |
| erofs_err("failed to initialize fragments"); |
| return 1; |
| } |
| } |
| |
| #ifndef NDEBUG |
| if (cfg.c_random_pclusterblks) |
| srand(time(NULL)); |
| #endif |
| if (tar_mode) { |
| if (dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP) |
| erofstar.rvsp_mode = true; |
| erofstar.dev = rebuild_src_count + 1; |
| |
| if (erofstar.mapfile) { |
| blklst = fopen(erofstar.mapfile, "w"); |
| if (!blklst || erofs_blocklist_open(blklst, true)) { |
| err = -errno; |
| erofs_err("failed to open %s", erofstar.mapfile); |
| goto exit; |
| } |
| } else if (erofstar.index_mode) { |
| /* |
| * If mapfile is unspecified for tarfs index mode, |
| * 512-byte block size is enforced here. |
| */ |
| g_sbi.blkszbits = 9; |
| } |
| } |
| |
| if (rebuild_mode) { |
| struct erofs_sb_info *src; |
| |
| erofs_warn("EXPERIMENTAL rebuild mode in use. Use at your own risk!"); |
| |
| src = list_first_entry(&rebuild_src_list, struct erofs_sb_info, list); |
| if (!src) |
| goto exit; |
| err = erofs_read_superblock(src); |
| if (err) { |
| erofs_err("failed to read superblock of %s", src->devname); |
| goto exit; |
| } |
| g_sbi.blkszbits = src->blkszbits; |
| } |
| |
| if (!incremental_mode) { |
| g_sbi.bmgr = erofs_buffer_init(&g_sbi, 0); |
| if (!g_sbi.bmgr) { |
| err = -ENOMEM; |
| goto exit; |
| } |
| sb_bh = erofs_reserve_sb(g_sbi.bmgr); |
| if (IS_ERR(sb_bh)) { |
| err = PTR_ERR(sb_bh); |
| goto exit; |
| } |
| } else { |
| union { |
| struct stat st; |
| erofs_blk_t startblk; |
| } u; |
| |
| erofs_warn("EXPERIMENTAL incremental build in use. Use at your own risk!"); |
| err = erofs_read_superblock(&g_sbi); |
| if (err) { |
| erofs_err("failed to read superblock of %s", g_sbi.devname); |
| goto exit; |
| } |
| |
| err = erofs_io_fstat(&g_sbi.bdev, &u.st); |
| if (!err && S_ISREG(u.st.st_mode)) |
| u.startblk = DIV_ROUND_UP(u.st.st_size, erofs_blksiz(&g_sbi)); |
| else |
| u.startblk = g_sbi.primarydevice_blocks; |
| g_sbi.bmgr = erofs_buffer_init(&g_sbi, u.startblk); |
| if (!g_sbi.bmgr) { |
| err = -ENOMEM; |
| goto exit; |
| } |
| sb_bh = NULL; |
| } |
| |
| /* Use the user-defined UUID or generate one for clean builds */ |
| if (valid_fixeduuid) |
| memcpy(g_sbi.uuid, fixeduuid, sizeof(g_sbi.uuid)); |
| else if (!incremental_mode) |
| erofs_uuid_generate(g_sbi.uuid); |
| |
| if (tar_mode && !erofstar.index_mode) { |
| err = erofs_diskbuf_init(1); |
| if (err) { |
| erofs_err("failed to initialize diskbuf: %s", |
| strerror(-err)); |
| goto exit; |
| } |
| } |
| |
| err = erofs_load_compress_hints(&g_sbi); |
| if (err) { |
| erofs_err("failed to load compress hints %s", |
| cfg.c_compress_hints_file); |
| goto exit; |
| } |
| |
| err = z_erofs_compress_init(&g_sbi, sb_bh); |
| if (err) { |
| erofs_err("failed to initialize compressor: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| |
| if (cfg.c_dedupe) { |
| if (!cfg.c_compr_opts[0].alg) { |
| erofs_err("Compression is not enabled. Turn on chunk-based data deduplication instead."); |
| cfg.c_chunkbits = g_sbi.blkszbits; |
| } else { |
| err = z_erofs_dedupe_init(erofs_blksiz(&g_sbi)); |
| if (err) { |
| erofs_err("failed to initialize deduplication: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| } |
| } |
| |
| if (cfg.c_chunkbits) { |
| err = erofs_blob_init(cfg.c_blobdev_path, 1 << cfg.c_chunkbits); |
| if (err) |
| return 1; |
| } |
| |
| if (((erofstar.index_mode && !erofstar.headeronly_mode) && |
| !erofstar.mapfile) || cfg.c_blobdev_path) { |
| err = erofs_mkfs_init_devices(&g_sbi, 1); |
| if (err) { |
| erofs_err("failed to generate device table: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| } |
| |
| erofs_inode_manager_init(); |
| |
| if (tar_mode) { |
| root = erofs_rebuild_make_root(&g_sbi); |
| if (IS_ERR(root)) { |
| err = PTR_ERR(root); |
| goto exit; |
| } |
| |
| while (!(err = tarerofs_parse_tar(root, &erofstar))); |
| |
| if (err < 0) |
| goto exit; |
| |
| err = erofs_rebuild_dump_tree(root, incremental_mode); |
| if (err < 0) |
| goto exit; |
| } else if (rebuild_mode) { |
| root = erofs_rebuild_make_root(&g_sbi); |
| if (IS_ERR(root)) { |
| err = PTR_ERR(root); |
| goto exit; |
| } |
| |
| err = erofs_mkfs_rebuild_load_trees(root); |
| if (err) |
| goto exit; |
| err = erofs_rebuild_dump_tree(root, incremental_mode); |
| if (err) |
| goto exit; |
| } else { |
| err = erofs_build_shared_xattrs_from_path(&g_sbi, cfg.c_src_path); |
| if (err) { |
| erofs_err("failed to build shared xattrs: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| |
| if (cfg.c_extra_ea_name_prefixes) |
| erofs_xattr_write_name_prefixes(&g_sbi, packedfile); |
| |
| root = erofs_mkfs_build_tree_from_path(&g_sbi, cfg.c_src_path); |
| if (IS_ERR(root)) { |
| err = PTR_ERR(root); |
| goto exit; |
| } |
| } |
| |
| if (erofstar.index_mode && g_sbi.extra_devices && !erofstar.mapfile) |
| g_sbi.devs[0].blocks = BLK_ROUND_UP(&g_sbi, erofstar.offset); |
| |
| if (erofs_sb_has_fragments(&g_sbi)) { |
| erofs_update_progressinfo("Handling packed data ..."); |
| err = erofs_flush_packed_inode(&g_sbi); |
| if (err) |
| goto exit; |
| } |
| |
| if (erofstar.index_mode || cfg.c_chunkbits || g_sbi.extra_devices) { |
| err = erofs_mkfs_dump_blobs(&g_sbi); |
| if (err) |
| goto exit; |
| } |
| |
| /* flush all buffers except for the superblock */ |
| err = erofs_bflush(g_sbi.bmgr, NULL); |
| if (err) |
| goto exit; |
| |
| erofs_fixup_root_inode(root); |
| erofs_iput(root); |
| root = NULL; |
| |
| err = erofs_writesb(&g_sbi, sb_bh, &nblocks); |
| if (err) |
| goto exit; |
| |
| /* flush all remaining buffers */ |
| err = erofs_bflush(g_sbi.bmgr, NULL); |
| if (err) |
| goto exit; |
| |
| err = erofs_dev_resize(&g_sbi, nblocks); |
| |
| if (!err && erofs_sb_has_sb_chksum(&g_sbi)) { |
| err = erofs_enable_sb_chksum(&g_sbi, &crc); |
| if (!err) |
| erofs_info("superblock checksum 0x%08x written", crc); |
| } |
| exit: |
| if (root) |
| erofs_iput(root); |
| z_erofs_compress_exit(); |
| z_erofs_dedupe_exit(); |
| blklst = erofs_blocklist_close(); |
| if (blklst) |
| fclose(blklst); |
| erofs_dev_close(&g_sbi); |
| erofs_cleanup_compress_hints(); |
| erofs_cleanup_exclude_rules(); |
| if (cfg.c_chunkbits) |
| erofs_blob_exit(); |
| if (cfg.c_fragments) |
| z_erofs_fragments_exit(); |
| erofs_packedfile_exit(); |
| erofs_xattr_cleanup_name_prefixes(); |
| erofs_rebuild_cleanup(); |
| erofs_diskbuf_exit(); |
| erofs_exit_configure(); |
| if (tar_mode) { |
| erofs_iostream_close(&erofstar.ios); |
| if (erofstar.ios.dumpfd >= 0) |
| close(erofstar.ios.dumpfd); |
| } |
| |
| if (err) { |
| erofs_err("\tCould not format the device : %s\n", |
| erofs_strerror(err)); |
| return 1; |
| } |
| erofs_update_progressinfo("Build completed.\n"); |
| erofs_mkfs_showsummaries(nblocks); |
| erofs_put_super(&g_sbi); |
| return 0; |
| } |