| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * mkfs/main.c |
| * |
| * Copyright (C) 2018-2019 HUAWEI, Inc. |
| * http://www.huawei.com/ |
| * Created by Li Guifu <bluce.liguifu@huawei.com> |
| */ |
| #define _GNU_SOURCE |
| #include <time.h> |
| #include <sys/time.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <libgen.h> |
| #include <sys/stat.h> |
| #include "erofs/config.h" |
| #include "erofs/print.h" |
| #include "erofs/cache.h" |
| #include "erofs/inode.h" |
| #include "erofs/io.h" |
| #include "erofs/compress.h" |
| |
| #define EROFS_SUPER_END (EROFS_SUPER_OFFSET + sizeof(struct erofs_super_block)) |
| |
| static void usage(void) |
| { |
| fprintf(stderr, "usage: [options] FILE DIRECTORY\n\n"); |
| fprintf(stderr, "Generate erofs image from DIRECTORY to FILE, and [options] are:\n"); |
| fprintf(stderr, " -zX[,Y] X=compressor (Y=compression level, optional)\n"); |
| fprintf(stderr, " -d# set output message level to # (maximum 9)\n"); |
| fprintf(stderr, " -EX[,...] X=extended options\n"); |
| fprintf(stderr, " -T# set a fixed UNIX timestamp # to all files\n"); |
| } |
| |
| static int parse_extended_opts(const char *opts) |
| { |
| #define MATCH_EXTENTED_OPT(opt, token, keylen) \ |
| (keylen == sizeof(opt) - 1 && !memcmp(token, opt, sizeof(opt) - 1)) |
| |
| const char *token, *next, *tokenend, *value __maybe_unused; |
| unsigned int keylen, vallen; |
| |
| value = NULL; |
| for (token = opts; *token != '\0'; token = next) { |
| 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 (MATCH_EXTENTED_OPT("legacy-compress", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| /* disable compacted indexes and 0padding */ |
| cfg.c_legacy_compress = true; |
| sbi.feature_incompat &= |
| ~EROFS_FEATURE_INCOMPAT_LZ4_0PADDING; |
| } |
| |
| if (MATCH_EXTENTED_OPT("force-inode-compact", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_inodeversion = FORCE_INODE_COMPACT; |
| } |
| |
| if (MATCH_EXTENTED_OPT("force-inode-extended", token, keylen)) { |
| if (vallen) |
| return -EINVAL; |
| cfg.c_force_inodeversion = FORCE_INODE_EXTENDED; |
| } |
| } |
| return 0; |
| } |
| |
| static int mkfs_parse_options_cfg(int argc, char *argv[]) |
| { |
| char *endptr; |
| int opt, i; |
| |
| while ((opt = getopt(argc, argv, "d:z:E:T:")) != -1) { |
| switch (opt) { |
| case 'z': |
| if (!optarg) { |
| cfg.c_compr_alg_master = "(default)"; |
| break; |
| } |
| /* get specified compression level */ |
| for (i = 0; optarg[i] != '\0'; ++i) { |
| if (optarg[i] == ',') { |
| cfg.c_compr_level_master = |
| atoi(optarg + i + 1); |
| optarg[i] = '\0'; |
| break; |
| } |
| } |
| cfg.c_compr_alg_master = strndup(optarg, 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 'E': |
| opt = parse_extended_opts(optarg); |
| if (opt) |
| return opt; |
| 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; |
| } |
| break; |
| |
| default: /* '?' */ |
| return -EINVAL; |
| } |
| } |
| |
| if (optind >= argc) |
| return -EINVAL; |
| |
| cfg.c_img_path = strdup(argv[optind++]); |
| if (!cfg.c_img_path) |
| return -ENOMEM; |
| |
| if (optind >= argc) { |
| erofs_err("Source directory is missing"); |
| return -EINVAL; |
| } |
| |
| 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 (optind < argc) { |
| erofs_err("Unexpected argument: %s\n", argv[optind]); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| int erofs_mkfs_update_super_block(struct erofs_buffer_head *bh, |
| erofs_nid_t root_nid, |
| erofs_blk_t *blocks) |
| { |
| struct erofs_super_block sb = { |
| .magic = cpu_to_le32(EROFS_SUPER_MAGIC_V1), |
| .blkszbits = LOG_BLOCK_SIZE, |
| .inos = 0, |
| .build_time = cpu_to_le64(sbi.build_time), |
| .build_time_nsec = cpu_to_le32(sbi.build_time_nsec), |
| .blocks = 0, |
| .meta_blkaddr = sbi.meta_blkaddr, |
| .xattr_blkaddr = 0, |
| .feature_incompat = cpu_to_le32(sbi.feature_incompat), |
| }; |
| const unsigned int sb_blksize = |
| round_up(EROFS_SUPER_END, EROFS_BLKSIZ); |
| char *buf; |
| |
| *blocks = erofs_mapbh(NULL, true); |
| sb.blocks = cpu_to_le32(*blocks); |
| sb.root_nid = cpu_to_le16(root_nid); |
| |
| buf = calloc(sb_blksize, 1); |
| if (!buf) { |
| erofs_err("Failed to allocate memory for sb: %s", |
| erofs_strerror(-errno)); |
| return -ENOMEM; |
| } |
| memcpy(buf + EROFS_SUPER_OFFSET, &sb, sizeof(sb)); |
| |
| bh->fsprivate = buf; |
| bh->op = &erofs_buf_write_bhops; |
| return 0; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int err = 0; |
| struct erofs_buffer_head *sb_bh; |
| struct erofs_inode *root_inode; |
| erofs_nid_t root_nid; |
| struct stat64 st; |
| erofs_blk_t nblocks; |
| struct timeval t; |
| |
| erofs_init_configure(); |
| fprintf(stderr, "%s %s\n", basename(argv[0]), cfg.c_version); |
| |
| cfg.c_legacy_compress = false; |
| sbi.feature_incompat = EROFS_FEATURE_INCOMPAT_LZ4_0PADDING; |
| |
| err = mkfs_parse_options_cfg(argc, argv); |
| if (err) { |
| if (err == -EINVAL) |
| usage(); |
| return 1; |
| } |
| |
| err = lstat64(cfg.c_src_path, &st); |
| if (err) |
| return 1; |
| if ((st.st_mode & S_IFMT) != S_IFDIR) { |
| erofs_err("root of the filesystem is not a directory - %s", |
| cfg.c_src_path); |
| usage(); |
| return 1; |
| } |
| |
| if (cfg.c_unix_timestamp != -1) { |
| sbi.build_time = cfg.c_unix_timestamp; |
| sbi.build_time_nsec = 0; |
| } else if (!gettimeofday(&t, NULL)) { |
| sbi.build_time = t.tv_sec; |
| sbi.build_time_nsec = t.tv_usec; |
| } |
| |
| err = dev_open(cfg.c_img_path); |
| if (err) { |
| usage(); |
| return 1; |
| } |
| |
| erofs_show_config(); |
| |
| sb_bh = erofs_buffer_init(); |
| if (IS_ERR(sb_bh)) { |
| err = PTR_ERR(sb_bh); |
| erofs_err("Failed to initialize buffers: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| err = erofs_bh_balloon(sb_bh, EROFS_SUPER_END); |
| if (err < 0) { |
| erofs_err("Failed to balloon erofs_super_block: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| |
| err = z_erofs_compress_init(); |
| if (err) { |
| erofs_err("Failed to initialize compressor: %s", |
| erofs_strerror(err)); |
| goto exit; |
| } |
| |
| erofs_inode_manager_init(); |
| |
| root_inode = erofs_mkfs_build_tree_from_path(NULL, cfg.c_src_path); |
| if (IS_ERR(root_inode)) { |
| err = PTR_ERR(root_inode); |
| goto exit; |
| } |
| |
| root_nid = erofs_lookupnid(root_inode); |
| erofs_iput(root_inode); |
| |
| err = erofs_mkfs_update_super_block(sb_bh, root_nid, &nblocks); |
| if (err) |
| goto exit; |
| |
| /* flush all remaining buffers */ |
| if (!erofs_bflush(NULL)) |
| err = -EIO; |
| else |
| err = dev_resize(nblocks); |
| exit: |
| z_erofs_compress_exit(); |
| dev_close(); |
| erofs_exit_configure(); |
| |
| if (err) { |
| erofs_err("\tCould not format the device : %s\n", |
| erofs_strerror(err)); |
| return 1; |
| } |
| return 0; |
| } |