Add --hot-file-list to let mkfs prioritize image-relative hot paths for local directory builds. The option loads newline-separated hot paths, resolves symlink aliases in local rootfs builds, prioritizes ancestor directory metadata, and keeps hot regular files out of fragment packing and cross-file dedupe so their physical layout remains stable.
Add layout coverage for hot files, hot directories, symlink aliases, hardlinks, and large root-directory data. Signed-off-by: Mengdie Yan <[email protected]> --- include/erofs/hotfile.h | 25 ++ include/erofs/internal.h | 9 + lib/Makefile.am | 2 + lib/compress.c | 20 +- lib/hotfile.c | 641 +++++++++++++++++++++++++++++++++++++++ lib/inode.c | 313 ++++++++++++++++++- man/mkfs.erofs.1 | 13 + mkfs/main.c | 32 ++ tests/hotfile-layout.sh | 321 ++++++++++++++++++++ 9 files changed, 1359 insertions(+), 17 deletions(-) create mode 100644 include/erofs/hotfile.h create mode 100644 lib/hotfile.c create mode 100755 tests/hotfile-layout.sh diff --git a/include/erofs/hotfile.h b/include/erofs/hotfile.h new file mode 100644 index 0000000..5f43e41 --- /dev/null +++ b/include/erofs/hotfile.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ +#ifndef __EROFS_HOTFILE_H +#define __EROFS_HOTFILE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <limits.h> +#include "defs.h" + +#define EROFS_HOT_RANK_NONE UINT_MAX + +int erofs_hotfile_load(const char *path); +bool erofs_hotfile_enabled(void); +unsigned int erofs_get_hot_file_rank(const char *path); +unsigned int erofs_get_hot_dir_rank(const char *path); +void erofs_hotfile_exit(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/erofs/internal.h b/include/erofs/internal.h index 450e264..d658c36 100644 --- a/include/erofs/internal.h +++ b/include/erofs/internal.h @@ -265,6 +265,10 @@ struct erofs_inode { bool compressed_idata; bool lazy_tailblock; bool opaque; + bool hotfile; + bool hotdir; + bool hotdir_deferred; + u32 hot_rank; /* OVL: non-merge dir that may contain whiteout entries */ bool whiteouts; bool dot_omitted; @@ -574,6 +578,11 @@ static inline bool erofs_is_packed_inode(struct erofs_inode *inode) return inode->i_srcpath == EROFS_PACKED_INODE; } +static inline bool erofs_inode_is_hotfile(struct erofs_inode *inode) +{ + return inode->hotfile; +} + int erofs_packedfile_init(struct erofs_sb_info *sbi, bool fragments_mkfs); void erofs_packedfile_exit(struct erofs_sb_info *sbi); diff --git a/lib/Makefile.am b/lib/Makefile.am index 27bf710..07282e2 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,6 +12,7 @@ noinst_HEADERS = $(top_srcdir)/include/erofs_fs.h \ $(top_srcdir)/include/erofs/exclude.h \ $(top_srcdir)/include/erofs/flex-array.h \ $(top_srcdir)/include/erofs/hashmap.h \ + $(top_srcdir)/include/erofs/hotfile.h \ $(top_srcdir)/include/erofs/inode.h \ $(top_srcdir)/include/erofs/internal.h \ $(top_srcdir)/include/erofs/io.h \ @@ -45,6 +46,7 @@ liberofs_la_SOURCES = config.c io.c cache.c super.c inode.c xattr.c exclude.c \ compress_hints.c hashmap.c sha256.c blobchunk.c dir.c \ fragments.c dedupe.c uuid_unparse.c uuid.c tar.c \ block_list.c rebuild.c diskbuf.c bitops.c dedupe_ext.c \ + hotfile.c \ vmdk.c metabox.c global.c importer.c base64.c liberofs_la_CFLAGS = -Wall ${libuuid_CFLAGS} -I$(top_srcdir)/include diff --git a/lib/compress.c b/lib/compress.c index 62d2672..9d55cbc 100644 --- a/lib/compress.c +++ b/lib/compress.c @@ -301,7 +301,7 @@ static int z_erofs_compress_dedupe(struct z_erofs_compress_sctx *ctx) * No need dedupe for packed inode since it is composed of * fragments which have already been deduplicated. */ - if (erofs_is_packed_inode(inode)) + if (erofs_is_packed_inode(inode) || erofs_inode_is_hotfile(inode)) goto out; do { @@ -573,7 +573,8 @@ static int __z_erofs_compress_one(struct z_erofs_compress_sctx *ctx, bool is_packed_inode = erofs_is_packed_inode(inode); bool tsg = (ctx->seg_idx + 1 >= ictx->seg_num), final = !ctx->remaining; bool may_packing = (params->fragments && tsg && final && !is_packed_inode && - !erofs_is_metabox_inode(inode)); + !erofs_is_metabox_inode(inode) && + !erofs_inode_is_hotfile(inode)); bool data_unaligned = ictx->data_unaligned; bool may_inline = (params->ztailpacking && !data_unaligned && tsg && final && !may_packing); @@ -742,7 +743,8 @@ frag_packing: e->pstart = ctx->pstart; if (ctx->pstart != EROFS_NULL_ADDR) ctx->pstart += e->plen; - if (!may_inline && !may_packing && !is_packed_inode) + if (!may_inline && !may_packing && !is_packed_inode && + !erofs_inode_is_hotfile(inode)) (void)z_erofs_dedupe_insert(e, ctx->queue + ctx->head); ctx->head += e->length; return 0; @@ -1256,6 +1258,7 @@ int z_erofs_compress_segment(struct z_erofs_compress_sctx *ctx, struct erofs_inode *inode = ictx->inode; bool frag = params->fragments && !erofs_is_packed_inode(inode) && !erofs_is_metabox_inode(inode) && + !erofs_inode_is_hotfile(inode) && ctx->seg_idx >= ictx->seg_num - 1; struct erofs_vfile *vf = ictx->vf; int ret; @@ -1551,7 +1554,8 @@ int z_erofs_merge_segment(struct z_erofs_compress_ictx *ictx, struct z_erofs_extent_item *ei, *n; const struct erofs_importer_params *params = ictx->im->params; struct erofs_sb_info *sbi = ictx->inode->sbi; - bool dedupe_ext = params->fragments; + bool dedupe_ext = params->fragments && + !erofs_inode_is_hotfile(ictx->inode); erofs_off_t off = 0; int ret = 0, ret2; erofs_off_t dpo; @@ -1802,7 +1806,8 @@ void *erofs_prepare_compressed_file(struct erofs_importer *im, struct erofs_sb_info *sbi = inode->sbi; struct z_erofs_compress_ictx *ictx; bool frag = params->fragments && !erofs_is_packed_inode(inode) && - !erofs_is_metabox_inode(inode); + !erofs_is_metabox_inode(inode) && + !erofs_inode_is_hotfile(inode); bool all_fragments = params->all_fragments && frag; /* initialize per-file compression setting */ @@ -1888,7 +1893,7 @@ void *erofs_prepare_compressed_file(struct erofs_importer *im, ictx->data_unaligned = false; } if (params->fragments && params->dedupe != EROFS_DEDUPE_FORCE_ON && - !ictx->data_unaligned) + !ictx->data_unaligned && !erofs_inode_is_hotfile(inode)) inode->z_advise |= Z_EROFS_ADVISE_INTERLACED_PCLUSTER; init_list_head(&ictx->extents); @@ -1911,7 +1916,8 @@ int erofs_begin_compressed_file(struct z_erofs_compress_ictx *ictx) const struct erofs_importer_params *params = ictx->im->params; struct erofs_inode *inode = ictx->inode; bool frag = params->fragments && !erofs_is_packed_inode(inode) && - !erofs_is_metabox_inode(inode); + !erofs_is_metabox_inode(inode) && + !erofs_inode_is_hotfile(inode); bool all_fragments = params->all_fragments && frag; int ret; diff --git a/lib/hotfile.c b/lib/hotfile.c new file mode 100644 index 0000000..9e0878a --- /dev/null +++ b/lib/hotfile.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 +#define _GNU_SOURCE + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "erofs/config.h" +#include "erofs/hotfile.h" +#include "erofs/print.h" + +struct erofs_hotfile_manifest { + struct erofs_hotfile_entry { + char *path; + unsigned int rank; + } *file_paths; + unsigned int nr_files; + unsigned int cap_files; + struct erofs_hotfile_entry *dir_paths; + unsigned int nr_dirs; + unsigned int cap_dirs; +}; + +static struct erofs_hotfile_manifest hotfile_manifest; + +static bool erofs_hotfile_is_local_rootfs(char **root) +{ + struct stat st; + + if (!cfg.c_src_path) + return false; + if (stat(cfg.c_src_path, &st)) + return false; + if (!S_ISDIR(st.st_mode)) + return false; + if (root) + *root = cfg.c_src_path; + return true; +} + +static int erofs_hotfile_cmp(const void *a, const void *b) +{ + const struct erofs_hotfile_entry *lhs = a; + const struct erofs_hotfile_entry *rhs = b; + + return strcmp(lhs->path, rhs->path); +} + +static int erofs_hotfile_path_cmp(const void *a, const void *b) +{ + const char *lhs = a; + const struct erofs_hotfile_entry *rhs = b; + + return strcmp(lhs, rhs->path); +} + +static char *erofs_hotfile_normalize(const char *path) +{ + const unsigned char *src = (const unsigned char *)path; + const unsigned char *end; + char *out; + size_t len = 0; + bool slash = false; + + while (*src && isspace(*src)) + ++src; + end = src + strlen((const char *)src); + while (end > src && isspace(end[-1])) + --end; + + while (src < end && *src == '/') + ++src; + + out = malloc((end - src) + 1); + if (!out) + return NULL; + + while (src < end) { + if (*src == '/') { + if (!slash) + out[len++] = '/'; + slash = true; + } else { + out[len++] = *src; + slash = false; + } + ++src; + } + + while (len > 0 && out[len - 1] == '/') + --len; + out[len] = '\0'; + return out; +} + +static bool erofs_hotfile_line_is_dir(const char *path) +{ + const unsigned char *src = (const unsigned char *)path; + const unsigned char *end; + + while (*src && isspace(*src)) + ++src; + if (*src == '#') + return false; + + end = src + strlen((const char *)src); + while (end > src && isspace(end[-1])) + --end; + + return end > src && end[-1] == '/'; +} + +static int erofs_hotfile_add_file(char *path, unsigned int rank); +static int erofs_hotfile_add_dir(char *path, unsigned int rank); +static int erofs_hotfile_collect_parent_dirs(const char *path, + unsigned int rank); + +static char *erofs_hotfile_clean_path(const char *path) +{ + char **stack; + char *normalized, *token, *saveptr; + char *out; + size_t len, depth = 0, i, pos = 0; + + normalized = erofs_hotfile_normalize(path); + if (!normalized) + return NULL; + + len = strlen(normalized); + stack = calloc(len + 1, sizeof(*stack)); + if (!stack) { + free(normalized); + return NULL; + } + + for (token = strtok_r(normalized, "/", &saveptr); token; + token = strtok_r(NULL, "/", &saveptr)) { + if (!strcmp(token, ".")) + continue; + if (!strcmp(token, "..")) { + if (!depth) { + free(stack); + free(normalized); + errno = EXDEV; + return NULL; + } + --depth; + continue; + } + stack[depth++] = token; + } + + out = malloc(len + 1); + if (!out) { + free(stack); + free(normalized); + return NULL; + } + + for (i = 0; i < depth; ++i) { + size_t n = strlen(stack[i]); + + if (i) + out[pos++] = '/'; + memcpy(out + pos, stack[i], n); + pos += n; + } + out[pos] = '\0'; + free(stack); + free(normalized); + return out; +} + +static char *erofs_hotfile_join_symlink_target(const char *linkpath, + const char *target, + const char *suffix) +{ + char *joined, *parent = NULL, *slash; + int ret; + + if (target[0] == '/') { + char *cleaned; + + ret = asprintf(&joined, "%s%s%s", target, + suffix && suffix[0] ? "/" : "", + suffix && suffix[0] ? suffix : ""); + if (ret < 0) + return NULL; + cleaned = erofs_hotfile_clean_path(joined); + free(joined); + return cleaned; + } + + parent = strdup(linkpath); + if (!parent) + return NULL; + slash = strrchr(parent, '/'); + if (slash) + *slash = '\0'; + else + parent[0] = '\0'; + + ret = asprintf(&joined, "%s%s%s%s%s", + parent, + parent[0] ? "/" : "", + target, + suffix && suffix[0] ? "/" : "", + suffix && suffix[0] ? suffix : ""); + free(parent); + if (ret < 0) + return NULL; + parent = erofs_hotfile_clean_path(joined); + free(joined); + return parent; +} + +static int erofs_hotfile_add_file_path(const char *path, unsigned int rank) +{ + char *dup; + int err; + + dup = strdup(path); + if (!dup) + return -ENOMEM; + err = erofs_hotfile_add_file(dup, rank); + if (err) { + free(dup); + return err; + } + return erofs_hotfile_collect_parent_dirs(path, rank); +} + +static int erofs_hotfile_add_dir_path(const char *path, unsigned int rank) +{ + char *dup; + int err; + + dup = strdup(path); + if (!dup) + return -ENOMEM; + err = erofs_hotfile_add_dir(dup, rank); + if (err) { + free(dup); + return err; + } + return erofs_hotfile_collect_parent_dirs(path, rank); +} + +static int erofs_hotfile_add_plain_path(const char *path, bool dir_entry, + unsigned int rank) +{ + if (!path[0]) + return 0; + return dir_entry ? erofs_hotfile_add_dir_path(path, rank) : + erofs_hotfile_add_file_path(path, rank); +} + +static int erofs_hotfile_add_resolved_path(const char *path, bool dir_entry, + unsigned int rank) +{ + char *root, *current; + int depth, err; + + if (!path[0]) + return 0; + + if (!erofs_hotfile_is_local_rootfs(&root)) + return erofs_hotfile_add_plain_path(path, dir_entry, rank); + + current = erofs_hotfile_clean_path(path); + if (!current) + return erofs_hotfile_add_plain_path(path, dir_entry, rank); + + for (depth = 0; depth < 32; ++depth) { + char *walk, *saveptr, *part, *prefix = NULL; + bool redirected = false; + size_t prefix_len = 0; + + walk = strdup(current); + if (!walk) { + free(current); + return -ENOMEM; + } + + for (part = strtok_r(walk, "/", &saveptr); part; + part = strtok_r(NULL, "/", &saveptr)) { + char *fullpath, *next, *target; + const char *suffix = saveptr; + struct stat st; + + if (prefix_len) { + if (asprintf(&next, "%s/%s", prefix, part) < 0) { + free(prefix); + free(walk); + free(current); + return -ENOMEM; + } + free(prefix); + prefix = next; + } else { + prefix = strdup(part); + if (!prefix) { + free(walk); + free(current); + return -ENOMEM; + } + } + prefix_len = strlen(prefix); + + if (asprintf(&fullpath, "%s/%s", root, prefix) < 0) { + free(prefix); + free(walk); + free(current); + return -ENOMEM; + } + if (lstat(fullpath, &st)) { + free(fullpath); + free(prefix); + free(walk); + err = erofs_hotfile_add_plain_path(current, + dir_entry, + rank); + free(current); + return err; + } + if (!S_ISLNK(st.st_mode)) { + free(fullpath); + continue; + } + + err = erofs_hotfile_add_file_path(prefix, rank); + if (err) { + free(fullpath); + free(prefix); + free(walk); + free(current); + return err; + } + + target = malloc(st.st_size + 1); + if (!target) { + free(fullpath); + free(prefix); + free(walk); + free(current); + return -ENOMEM; + } + err = readlink(fullpath, target, st.st_size); + free(fullpath); + if (err < 0) { + err = -errno; + free(target); + free(prefix); + free(walk); + free(current); + return err; + } + target[err] = '\0'; + + next = erofs_hotfile_join_symlink_target(prefix, target, + suffix); + free(target); + free(prefix); + free(walk); + free(current); + if (!next) + return -ENOMEM; + current = next; + redirected = true; + break; + } + + if (!redirected) { + free(prefix); + free(walk); + err = erofs_hotfile_add_plain_path(current, dir_entry, + rank); + free(current); + return err; + } + } + free(current); + return -ELOOP; +} + +static int erofs_hotfile_add(struct erofs_hotfile_entry **paths, + unsigned int *nr, unsigned int *cap, + char *path, unsigned int rank) +{ + struct erofs_hotfile_entry *npaths; + + if (*nr >= *cap) { + unsigned int ncap = *cap ? *cap * 2 : 64; + + npaths = realloc(*paths, ncap * sizeof(*npaths)); + if (!npaths) + return -ENOMEM; + *paths = npaths; + *cap = ncap; + } + (*paths)[*nr].path = path; + (*paths)[*nr].rank = rank; + ++(*nr); + return 0; +} + +static int erofs_hotfile_add_file(char *path, unsigned int rank) +{ + return erofs_hotfile_add(&hotfile_manifest.file_paths, + &hotfile_manifest.nr_files, + &hotfile_manifest.cap_files, + path, rank); +} + +static int erofs_hotfile_add_dir(char *path, unsigned int rank) +{ + return erofs_hotfile_add(&hotfile_manifest.dir_paths, + &hotfile_manifest.nr_dirs, + &hotfile_manifest.cap_dirs, + path, rank); +} + +static int erofs_hotfile_collect_parent_dirs(const char *path, + unsigned int rank) +{ + char *dir = strdup(path); + int err = 0; + + if (!dir) + return -ENOMEM; + + while (1) { + char *slash = strrchr(dir, '/'); + + if (!slash) + break; + *slash = '\0'; + if (!dir[0]) + break; + + slash = strdup(dir); + if (!slash) { + err = -ENOMEM; + break; + } + err = erofs_hotfile_add_dir(slash, rank); + if (err) + break; + } + free(dir); + return err; +} + +static unsigned int erofs_hotfile_sort_dedupe(struct erofs_hotfile_entry *paths, + unsigned int nr) +{ + unsigned int i, out = 0; + + if (!nr) + return 0; + + qsort(paths, nr, sizeof(paths[0]), erofs_hotfile_cmp); + for (i = 0; i < nr; ++i) { + if (out && !strcmp(paths[out - 1].path, paths[i].path)) { + if (paths[i].rank < paths[out - 1].rank) { + free(paths[out - 1].path); + paths[out - 1] = paths[i]; + } else { + free(paths[i].path); + } + continue; + } + paths[out++] = paths[i]; + } + return out; +} + +int erofs_hotfile_load(const char *path) +{ + FILE *fp; + char *line = NULL; + size_t linesz = 0; + ssize_t nread; + unsigned int rank = 0; + int err = 0; + + fp = fopen(path, "r"); + if (!fp) + return -errno; + + while ((nread = getline(&line, &linesz, fp)) >= 0) { + char *normalized; + bool dir_entry; + + if (!nread) + continue; + dir_entry = erofs_hotfile_line_is_dir(line); + normalized = erofs_hotfile_normalize(line); + if (!normalized) { + err = -ENOMEM; + break; + } + if ((!normalized[0] && !dir_entry) || normalized[0] == '#') { + free(normalized); + continue; + } + err = erofs_hotfile_add_resolved_path(normalized, dir_entry, + rank); + free(normalized); + if (err) + break; + ++rank; + } + free(line); + fclose(fp); + if (err) + goto err_out; + + if (!hotfile_manifest.nr_files && !hotfile_manifest.nr_dirs) + return 0; + + hotfile_manifest.nr_files = erofs_hotfile_sort_dedupe( + hotfile_manifest.file_paths, hotfile_manifest.nr_files); + hotfile_manifest.nr_dirs = erofs_hotfile_sort_dedupe( + hotfile_manifest.dir_paths, hotfile_manifest.nr_dirs); + erofs_info("loaded %u hot files and %u hot ancestor dirs from %s", + hotfile_manifest.nr_files, hotfile_manifest.nr_dirs, path); + return 0; + +err_out: + erofs_hotfile_exit(); + return err; +} + +bool erofs_hotfile_enabled(void) +{ + return hotfile_manifest.nr_files || hotfile_manifest.nr_dirs; +} + +unsigned int erofs_get_hot_file_rank(const char *path) +{ + char *normalized; + struct erofs_hotfile_entry *found; + const char *fspath; + unsigned int rank = EROFS_HOT_RANK_NONE; + + if (!erofs_hotfile_enabled()) + return EROFS_HOT_RANK_NONE; + + fspath = erofs_fspath(path); + normalized = erofs_hotfile_normalize(fspath); + if (!normalized) + return EROFS_HOT_RANK_NONE; + + found = bsearch(normalized, hotfile_manifest.file_paths, + hotfile_manifest.nr_files, + sizeof(hotfile_manifest.file_paths[0]), + erofs_hotfile_path_cmp); + if (found) + rank = found->rank; + + if (rank != 0 && hotfile_manifest.nr_files) { + char *root = NULL; + char *fullpath = NULL; + struct stat st; + + if (erofs_hotfile_is_local_rootfs(&root) && + asprintf(&fullpath, "%s/%s", root, normalized) >= 0 && + lstat(fullpath, &st) == 0 && S_ISLNK(st.st_mode)) { + char *resolved = realpath(fullpath, NULL); + + if (resolved && !strncmp(resolved, root, strlen(root)) && + resolved[strlen(root)] == '/') { + char *resolved_norm = + erofs_hotfile_normalize(resolved + strlen(root)); + + if (resolved_norm) { + struct erofs_hotfile_entry *resolved_found; + + resolved_found = bsearch(resolved_norm, + hotfile_manifest.file_paths, + hotfile_manifest.nr_files, + sizeof(hotfile_manifest.file_paths[0]), + erofs_hotfile_path_cmp); + if (resolved_found && resolved_found->rank < rank) + rank = resolved_found->rank; + free(resolved_norm); + } + } + free(resolved); + } + free(fullpath); + } + free(normalized); + return rank; +} + +unsigned int erofs_get_hot_dir_rank(const char *path) +{ + char *normalized; + struct erofs_hotfile_entry *found; + const char *fspath; + + if (!erofs_hotfile_enabled()) + return EROFS_HOT_RANK_NONE; + + fspath = erofs_fspath(path); + normalized = erofs_hotfile_normalize(fspath); + if (!normalized) + return EROFS_HOT_RANK_NONE; + + found = bsearch(normalized, hotfile_manifest.dir_paths, + hotfile_manifest.nr_dirs, + sizeof(hotfile_manifest.dir_paths[0]), + erofs_hotfile_path_cmp); + free(normalized); + return found ? found->rank : EROFS_HOT_RANK_NONE; +} + +void erofs_hotfile_exit(void) +{ + unsigned int i; + + for (i = 0; i < hotfile_manifest.nr_files; ++i) + free(hotfile_manifest.file_paths[i].path); + for (i = 0; i < hotfile_manifest.nr_dirs; ++i) + free(hotfile_manifest.dir_paths[i].path); + free(hotfile_manifest.file_paths); + free(hotfile_manifest.dir_paths); + hotfile_manifest.file_paths = NULL; + hotfile_manifest.nr_files = 0; + hotfile_manifest.cap_files = 0; + hotfile_manifest.dir_paths = NULL; + hotfile_manifest.nr_dirs = 0; + hotfile_manifest.cap_dirs = 0; +} diff --git a/lib/inode.c b/lib/inode.c index 95fd93b..cabe085 100644 --- a/lib/inode.c +++ b/lib/inode.c @@ -24,6 +24,7 @@ #include "erofs/block_list.h" #include "erofs/compress_hints.h" #include "erofs/blobchunk.h" +#include "erofs/hotfile.h" #include "erofs/importer.h" #include "liberofs_cache.h" #include "liberofs_compress.h" @@ -661,10 +662,20 @@ static int erofs_write_unencoded_data(struct erofs_inode *inode, inode->idata_size = inode->i_size % erofs_blksiz(sbi); remaining = inode->i_size - inode->idata_size; - ret = erofs_allocate_inode_bh_data(inode, remaining >> sbi->blkszbits, - in_metazone); - if (ret) - return ret; + /* + * Hot directories may have their main data block pre-allocated + * during hot-queue processing so that its physical offset is + * placed inside the hot region. Reuse it rather than re-balloc, + * which would leak the pre-allocated buffer and defeat the + * layout intent. + */ + if (!inode->bh_data) { + ret = erofs_allocate_inode_bh_data(inode, + remaining >> sbi->blkszbits, + in_metazone); + if (ret) + return ret; + } bh = inode->bh_data; if (bh) { @@ -1008,7 +1019,8 @@ static int erofs_prepare_inode_buffer(struct erofs_importer *im, goto noinline; if (!is_inode_layout_compression(inode)) { - if (params->no_datainline && S_ISREG(inode->i_mode)) { + if (S_ISREG(inode->i_mode) && + (params->no_datainline || erofs_inode_is_hotfile(inode))) { inode->datalayout = EROFS_INODE_FLAT_PLAIN; goto noinline; } @@ -1351,6 +1363,15 @@ static int erofs_fill_inode(struct erofs_importer *im, struct erofs_inode *inode if (!inode->i_srcpath) return -ENOMEM; } + inode->hot_rank = EROFS_HOT_RANK_NONE; + if (!erofs_is_special_identifier(path)) { + inode->hot_rank = erofs_get_hot_file_rank(path); + inode->hotfile = inode->hot_rank != EROFS_HOT_RANK_NONE; + if (!inode->hotfile && S_ISDIR(st->st_mode)) { + inode->hot_rank = erofs_get_hot_dir_rank(path); + inode->hotdir = inode->hot_rank != EROFS_HOT_RANK_NONE; + } + } if (erofs_should_use_inode_extended(im, inode, path)) { if (params->force_inodeversion == EROFS_FORCE_INODE_COMPACT) { @@ -1410,11 +1431,28 @@ static struct erofs_inode *erofs_iget_from_local(struct erofs_importer *im, * lookup in hash table first, if it already exists we have a * hard-link, just return it. Also don't lookup for directories * since hard-link directory isn't allowed. + * + * When a hard-linked inode has already been created via another + * name, only that first name's hotfile rank was recorded. If the + * current name is listed in the hotlist with a lower rank (= hotter + * priority), adopt it so the shared inode gets the hottest rank + * across all of its hard-links. Without this, an inode shared by + * cold + hot paths (e.g. /usr/share/zoneinfo/Asia/Chongqing shared + * with .../Shanghai, only Shanghai is hot) ends up with no hot rank + * and gets placed in the cold region, blowing up the hot-zone end + * to near the full image size. */ if (!S_ISDIR(st.st_mode) && !params->hard_dereference) { inode = erofs_iget(st.st_dev, st.st_ino); - if (inode) + if (inode) { + u32 rank = erofs_get_hot_file_rank(path); + + if (rank != EROFS_HOT_RANK_NONE && rank < inode->hot_rank) { + inode->hot_rank = rank; + inode->hotfile = true; + } return inode; + } } /* cannot find in the inode cache */ @@ -1612,6 +1650,42 @@ static int erofs_mkfs_create_directory(const struct erofs_mkfs_btctx *ctx, if (ret) return ret; inode->bh->op = &erofs_skip_write_bhops; + + /* + * For hot directories, also pre-allocate the main dentry data + * block right now (while we are still in the hot-queue stage), + * so that its physical offset falls inside the hot region. + * Otherwise the main dentry block would be ballocated lazily by + * erofs_write_unencoded_data() during the later cold-dir dump + * pass, landing well past the hot prefix and breaking path + * lookups such as /usr/bin/sh when the image is head-truncated + * to the hot prefix. + * + * A directory is treated as hot here when it has any hot rank, + * either because it was listed as a hot dir (trailing slash) or + * because a caller accidentally listed it as a hot file (no + * trailing slash). In both cases the user signalled that the + * lookup path passes through this directory, so its dentry + * block must be reachable from the hot prefix. + * + * Only uncompressed FLAT_{INLINE,PLAIN} dirs have a separate + * main data extent; compressed dirs carry all bytes via the + * compression path and do not go through erofs_balloc(DIRA). + */ + if ((inode->hotdir || inode->hotfile) && + inode->hot_rank != EROFS_HOT_RANK_NONE && + (inode->datalayout == EROFS_INODE_FLAT_INLINE || + inode->datalayout == EROFS_INODE_FLAT_PLAIN)) { + u64 remaining = inode->i_size - inode->idata_size; + + if (remaining) { + ret = erofs_allocate_inode_bh_data(inode, + remaining >> inode->sbi->blkszbits, + ctx->im->params->dirdata_in_metazone); + if (ret) + return ret; + } + } return 0; } @@ -2129,18 +2203,176 @@ static void erofs_mark_parent_inode(struct erofs_inode *inode, inode->i_parent = (void *)((unsigned long)dir | 1); } +struct erofs_mkfs_hot_item { + struct erofs_mkfs_hot_item *next; + struct erofs_inode *parent; + struct erofs_inode *inode; + unsigned int priority; + unsigned int rank; + unsigned int order; +}; + +static unsigned int erofs_mkfs_inode_rank(struct erofs_inode *inode) +{ + /* + * A directory that the user listed without a trailing slash + * ends up with hotfile=true/hotdir=false but still carries a + * valid hot_rank. Treat it as a hot directory here so its + * metadata (including the pre-allocated dentry data block) + * participates in the hot-queue layout. Otherwise the dentry + * block is emitted during the later cold-dir pass and falls + * outside the hot prefix. + */ + if ((!S_ISDIR(inode->i_mode) && erofs_inode_is_hotfile(inode)) || + (S_ISDIR(inode->i_mode) && + (inode->hotdir || inode->hotfile))) + return inode->hot_rank; + return EROFS_HOT_RANK_NONE; +} + +static unsigned int erofs_mkfs_hot_item_priority(struct erofs_inode *inode) +{ + /* Path lookup needs hot directory and symlink metadata before file data. */ + if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) + return 0; + return 1; +} + +static int erofs_mkfs_enqueue_hot_item(struct erofs_mkfs_hot_item **queue, + struct erofs_inode *parent, + struct erofs_inode *inode, + unsigned int *order) +{ + struct erofs_mkfs_hot_item *item, **pos = queue; + + item = malloc(sizeof(*item)); + if (!item) + return -ENOMEM; + + item->parent = parent; + item->inode = inode; + item->priority = erofs_mkfs_hot_item_priority(inode); + item->rank = erofs_mkfs_inode_rank(inode); + item->order = (*order)++; + item->next = NULL; + + while (*pos) { + if (item->priority < (*pos)->priority) + break; + if (item->priority > (*pos)->priority) { + pos = &(*pos)->next; + continue; + } + if (item->rank < (*pos)->rank) + break; + if (item->rank == (*pos)->rank && + item->order < (*pos)->order) + break; + pos = &(*pos)->next; + } + item->next = *pos; + *pos = item; + return 0; +} + +static int erofs_mkfs_enqueue_hot_children(struct erofs_inode *dir, + struct erofs_mkfs_hot_item **queue, + unsigned int *order) +{ + struct erofs_dentry *d; + + list_for_each_entry(d, &dir->i_subdirs, d_child) { + struct erofs_inode *inode = d->inode; + int err; + + if (is_dot_dotdot(d->name) || + (d->flags & EROFS_DENTRY_FLAG_VALIDNID)) + continue; + if (erofs_mkfs_inode_rank(inode) == EROFS_HOT_RANK_NONE) + continue; + err = erofs_mkfs_enqueue_hot_item(queue, dir, inode, order); + if (err) + return err; + } + return 0; +} + +static int erofs_mkfs_dump_tree_default(const struct erofs_mkfs_btctx *ctx, + struct erofs_inode *dumpdir, + struct list_head *pending_dirs, + bool grouped_dirdata) +{ + int err = 0, err2; + + do { + struct erofs_inode *dir = dumpdir; + /* used for adding sub-directories in reverse order due to FIFO */ + struct erofs_inode *head, **last = &head; + struct erofs_dentry *d; + + dumpdir = dir->next_dirwrite; + list_for_each_entry(d, &dir->i_subdirs, d_child) { + struct erofs_inode *inode = d->inode; + + if (is_dot_dotdot(d->name) || + (d->flags & EROFS_DENTRY_FLAG_VALIDNID)) + continue; + + if (!erofs_inode_visited(inode)) { + DBG_BUGON(ctx->rebuild && (inode->i_nlink == 1 || + S_ISDIR(inode->i_mode)) && + erofs_parent_inode(inode) != dir); + erofs_mark_parent_inode(inode, dir); + + err = erofs_mkfs_handle_inode(ctx, inode); + if (err) + break; + if (S_ISDIR(inode->i_mode)) { + inode->next_dirwrite = NULL; + *last = inode; + last = &inode->next_dirwrite; + (void)erofs_igrab(inode); + } + } else if (!ctx->rebuild) { + ++inode->i_nlink; + } + } + *last = dumpdir; /* fixup the last (or the only) one */ + dumpdir = head; + err2 = grouped_dirdata ? + erofs_mkfs_push_pending_job(pending_dirs, + EROFS_MKFS_JOB_DIR_BH, &dir, sizeof(dir)) : + erofs_mkfs_go(ctx, EROFS_MKFS_JOB_DIR_BH, + &dir, sizeof(dir)); + if (err || err2) { + if (!err) + err = err2; + break; + } + } while (dumpdir); + err2 = erofs_mkfs_flush_pending_jobs(ctx, pending_dirs); + return err ? err : err2; +} + static int erofs_mkfs_dump_tree(const struct erofs_mkfs_btctx *ctx) { struct erofs_importer *im = ctx->im; struct erofs_inode *root = im->root; struct erofs_sb_info *sbi = root->sbi; struct erofs_inode *dumpdir = erofs_igrab(root); + struct erofs_inode *deferdir = NULL, **deferlast = &deferdir; + struct erofs_mkfs_hot_item *hot_queue = NULL; bool grouped_dirdata = im->params->grouped_dirdata; LIST_HEAD(pending_dirs); + unsigned int hot_order = 0; int err, err2; erofs_mark_parent_inode(root, root); /* rootdir mark */ root->next_dirwrite = NULL; + if (erofs_hotfile_enabled()) { + root->hotdir = true; + root->hot_rank = 0; + } /* update dev/i_ino[1] to keep track of the base image */ if (ctx->incremental) { root->dev = root->sbi->dev; @@ -2168,13 +2400,46 @@ static int erofs_mkfs_dump_tree(const struct erofs_mkfs_btctx *ctx) sbi->root_nid = root->nid; } + if (!erofs_hotfile_enabled()) + return erofs_mkfs_dump_tree_default(ctx, dumpdir, &pending_dirs, + grouped_dirdata); + + err = erofs_mkfs_enqueue_hot_children(root, &hot_queue, &hot_order); + if (err) + goto out_hot; + + while (hot_queue) { + struct erofs_mkfs_hot_item *item = hot_queue; + struct erofs_inode *inode = item->inode; + struct erofs_inode *parent = item->parent; + + hot_queue = item->next; + free(item); + + if (!erofs_inode_visited(inode)) { + erofs_mark_parent_inode(inode, parent); + err = erofs_mkfs_handle_inode(ctx, inode); + if (err) + goto out_hot; + if (S_ISDIR(inode->i_mode)) { + err = erofs_mkfs_enqueue_hot_children(inode, + &hot_queue, + &hot_order); + if (err) + goto out_hot; + inode->hotdir_deferred = true; + } + } + } + do { struct erofs_inode *dir = dumpdir; - /* used for adding sub-directories in reverse order due to FIFO */ struct erofs_inode *head, **last = &head; struct erofs_dentry *d; + err2 = 0; dumpdir = dir->next_dirwrite; + dir->next_dirwrite = NULL; list_for_each_entry(d, &dir->i_subdirs, d_child) { struct erofs_inode *inode = d->inode; @@ -2192,16 +2457,37 @@ static int erofs_mkfs_dump_tree(const struct erofs_mkfs_btctx *ctx) if (err) break; if (S_ISDIR(inode->i_mode)) { + inode->next_dirwrite = NULL; *last = inode; last = &inode->next_dirwrite; (void)erofs_igrab(inode); } - } else if (!ctx->rebuild) { - ++inode->i_nlink; + } else { + if (S_ISDIR(inode->i_mode) && + inode->hotdir_deferred) { + inode->hotdir_deferred = false; + inode->next_dirwrite = NULL; + /* + * A hot directory means its own metadata is hot; + * it must not recursively promote all cold + * children beneath it. + */ + *deferlast = inode; + deferlast = &inode->next_dirwrite; + (void)erofs_igrab(inode); + } + if (!ctx->rebuild && + erofs_parent_inode(inode) != dir) + ++inode->i_nlink; } } - *last = dumpdir; /* fixup the last (or the only) one */ + *last = dumpdir; dumpdir = head; + if (!dumpdir && deferdir) { + dumpdir = deferdir; + deferdir = NULL; + deferlast = &deferdir; + } err2 = grouped_dirdata ? erofs_mkfs_push_pending_job(&pending_dirs, EROFS_MKFS_JOB_DIR_BH, &dir, sizeof(dir)) : @@ -2214,6 +2500,13 @@ static int erofs_mkfs_dump_tree(const struct erofs_mkfs_btctx *ctx) } } while (dumpdir); err2 = erofs_mkfs_flush_pending_jobs(ctx, &pending_dirs); +out_hot: + while (hot_queue) { + struct erofs_mkfs_hot_item *item = hot_queue; + + hot_queue = item->next; + free(item); + } return err ? err : err2; } diff --git a/man/mkfs.erofs.1 b/man/mkfs.erofs.1 index 65ec807..393a293 100644 --- a/man/mkfs.erofs.1 +++ b/man/mkfs.erofs.1 @@ -310,6 +310,19 @@ random gzip access. Source file must be a gzip-compressed tarball. .BI "\-\-hard-dereference" Dereference hardlinks and add links as separate inodes. .TP +.BI "\-\-hot-file-list=" path +Read a newline-delimited list of image-relative hot file paths for local +directory sources. +Hot regular files are scheduled before cold files while keeping compression +enabled. Parent directories of listed files are also prioritized so hot +subtrees are traversed earlier than cold sibling subtrees. Entries ending in +\fI/\fR are treated as hot directories: the directory inode and directory data +are prioritized, but children are not recursively marked hot. Hot files do not +participate in fragment packing or cross-file dedupe. Symlink aliases in the +source tree are resolved before matching, so merged-usr compatibility paths such +as \fI/lib/...\fR can match their real payloads under \fI/usr/lib/...\fR while +preserving the user-provided order. +.TP .B "\-\-ignore-mtime" Ignore the file modification time whenever it would cause \fBmkfs.erofs\fR to use extended inodes over compact inodes. When not using a fixed timestamp, this diff --git a/mkfs/main.c b/mkfs/main.c index 31fea7c..c154247 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -23,6 +23,7 @@ #include "erofs/xattr.h" #include "erofs/exclude.h" #include "erofs/block_list.h" +#include "erofs/hotfile.h" #include "erofs/compress_hints.h" #include "erofs/blobchunk.h" #include "../lib/compressor.h" @@ -103,9 +104,12 @@ static struct option long_options[] = { {"MZ", optional_argument, NULL, 537}, {"xattr-prefix", required_argument, NULL, 538}, {"xattr-inode-digest", required_argument, NULL, 539}, + {"hot-file-list", required_argument, NULL, 540}, {0, 0, 0, 0}, }; +static char *hotfile_list_path; + static void print_available_compressors(FILE *f, const char *delim) { int i = 0; @@ -208,6 +212,11 @@ static void usage(int argc, char **argv) " --uid-offset=# add offset # to all file uids (# = id offset)\n" " --gid-offset=# add offset # to all file gids (# = id offset)\n" " --hard-dereference dereference hardlinks, add links as separate inodes\n" + " --hot-file-list=X specify newline-separated hot file paths for\n" + " local directory sources; local rootfs builds\n" + " resolve symlink aliases\n" + " (e.g. /lib/... -> /usr/lib/...) and prioritize\n" + " ancestor directories as well\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" @@ -1487,6 +1496,12 @@ static int mkfs_parse_options_cfg(struct erofs_importer_params *params, return err; } break; + case 540: + free(hotfile_list_path); + hotfile_list_path = strdup(optarg); + if (!hotfile_list_path) + return -ENOMEM; + break; case 'V': version(); exit(0); @@ -1542,6 +1557,11 @@ static int mkfs_parse_options_cfg(struct erofs_importer_params *params, return err; } + if (hotfile_list_path && source_mode != EROFS_MKFS_SOURCE_LOCALDIR) { + erofs_err("--hot-file-list is only supported for local directory sources"); + return -EOPNOTSUPP; + } + if (quiet) { cfg.c_dbg_lvl = EROFS_ERR; cfg.c_showprogress = false; @@ -1778,6 +1798,15 @@ int main(int argc, char **argv) goto exit; } + if (hotfile_list_path) { + err = erofs_hotfile_load(hotfile_list_path); + if (err) { + erofs_err("failed to load hot-file list %s: %s", + hotfile_list_path, erofs_strerror(err)); + goto exit; + } + } + err = parse_source_date_epoch(); if (err) { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); @@ -2065,6 +2094,9 @@ exit: fclose(blklst); erofs_cleanup_compress_hints(); erofs_cleanup_exclude_rules(); + erofs_hotfile_exit(); + free(hotfile_list_path); + hotfile_list_path = NULL; if (cfg.c_chunkbits || source_mode == EROFS_MKFS_SOURCE_REBUILD) erofs_blob_exit(); erofs_xattr_cleanup_name_prefixes(); diff --git a/tests/hotfile-layout.sh b/tests/hotfile-layout.sh new file mode 100755 index 0000000..6af3f41 --- /dev/null +++ b/tests/hotfile-layout.sh @@ -0,0 +1,321 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0+ + +set -eu + +MKFS=${MKFS:-./mkfs/mkfs.erofs} +DUMP=${DUMP:-./dump/dump.erofs} + +tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/erofs-hotfile-layout.XXXXXX") +cleanup() { + rm -rf "$tmpdir" +} +trap cleanup EXIT + +extent_start() { + "$DUMP" -e --path="$1" "$2" | + awk ' + /^[[:space:]]+[0-9]+:/ { + line = $0 + sub(/^.*\|[[:space:]]*[0-9]+[[:space:]]*:[[:space:]]*/, "", line) + sub(/\.\..*$/, "", line) + gsub(/[[:space:]]/, "", line) + print line + exit + } + ' +} + +inode_links() { + "$DUMP" --path="$1" "$2" | + awk ' + /Links:/ { + for (i = 1; i <= NF; ++i) { + if ($i == "Links:") { + print $(i + 1) + exit + } + } + } + ' +} + +assert_hot_file_nonrecursive_hotdir() { + img="$1" + + hot=$(extent_start /a/hot "$img") + hotdir_cold=$(extent_start /a/cold "$img") + outside_cold=$(extent_start /c/cold "$img") + + if [ "$hot" -ge "$hotdir_cold" ]; then + echo "hot file was not placed before cold sibling: hot=$hot cold=$hotdir_cold" >&2 + exit 1 + fi + + if [ "$outside_cold" -ge "$hotdir_cold" ]; then + echo "hot directory promoted cold child recursively: outside=$outside_cold hotdir_cold=$hotdir_cold" >&2 + exit 1 + fi +} + +assert_explicit_hotdir_nonrecursive() { + img="$1" + + hotdir_cold=$(extent_start /a/cold "$img") + outside_cold=$(extent_start /c/cold "$img") + + if [ "$outside_cold" -ge "$hotdir_cold" ]; then + echo "explicit hot directory promoted cold child recursively: outside=$outside_cold hotdir_cold=$hotdir_cold" >&2 + exit 1 + fi +} + +assert_symlink_chain_metadata_hot() { + img="$1" + log="$2" + + parent_link=$(extent_start /lib64 "$img") + parent_cold=$(extent_start /aaa-cold "$img") + symlink=$(extent_start /usr/lib64/ld-linux-x86-64.so.2 "$img") + cold=$(extent_start /usr/lib64/aaa-cold "$img") + real=$(extent_start /usr/lib/x86_64-linux-gnu/ld-real "$img") + + if ! grep -q 'loaded 3 hot files' "$log"; then + echo "hot symlink chain did not preserve parent link, symlink, and target as hot files" >&2 + cat "$log" >&2 + exit 1 + fi + + if [ "$parent_link" -ge "$parent_cold" ]; then + echo "hot parent symlink metadata was not placed before cold sibling: parent_link=$parent_link parent_cold=$parent_cold" >&2 + exit 1 + fi + + if [ "$symlink" -ge "$cold" ]; then + echo "hot symlink metadata was not placed before cold sibling: symlink=$symlink cold=$cold" >&2 + exit 1 + fi + + if [ "$real" -ge "$cold" ]; then + echo "hot symlink target was not placed before cold sibling: real=$real cold=$cold" >&2 + exit 1 + fi +} + +assert_hot_metadata_precedes_regular_data() { + img="$1" + + hotfile=$(extent_start /a/hot "$img") + hotdir=$(extent_start /z "$img") + + if [ "$hotdir" -ge "$hotfile" ]; then + echo "hot metadata was not placed before regular hot file data: hotdir=$hotdir hotfile=$hotfile" >&2 + exit 1 + fi +} + +assert_late_hotdirs_precede_regular_hotfile() { + img="$1" + + sysdir=$(extent_start /sys "$img") + devdir=$(extent_start /dev "$img") + liblink=$(extent_start /lib "$img") + lib64link=$(extent_start /lib64 "$img") + node=$(extent_start /usr/local/bin/node "$img") + + if [ "$liblink" -ge "$node" ]; then + echo "late hot /lib symlink metadata was placed after node data: lib=$liblink node=$node" >&2 + exit 1 + fi + + if [ "$lib64link" -ge "$node" ]; then + echo "late hot /lib64 symlink metadata was placed after node data: lib64=$lib64link node=$node" >&2 + exit 1 + fi + + if [ "$sysdir" -ge "$node" ]; then + echo "late hot /sys metadata was placed after node data: sys=$sysdir node=$node" >&2 + exit 1 + fi + + if [ "$devdir" -ge "$node" ]; then + echo "late hot /dev metadata was placed after node data: dev=$devdir node=$node" >&2 + exit 1 + fi +} + +assert_late_symlink_alias_inherits_target_rank() { + img="$1" + + alias=$(extent_start /usr/lib/x86_64-linux-gnu/libfoo.so.1 "$img") + node=$(extent_start /usr/local/bin/node "$img") + + if [ "$alias" -ge "$node" ]; then + echo "late hot symlink alias was placed after node data: alias=$alias node=$node" >&2 + exit 1 + fi +} + +assert_hot_hardlinks_keep_real_link_count() { + img="$1" + + links=$(inode_links /a/hot "$img") + + if [ "$links" -ne 2 ]; then + echo "hot hardlink aliases changed link count: links=$links" >&2 + exit 1 + fi +} + +assert_root_dirdata_precedes_hot_file() { + img="$1" + + rootdir=$(extent_start / "$img") + hotfile=$(extent_start /zz/hot "$img") + + if [ "$rootdir" -ge "$hotfile" ]; then + echo "root directory data was placed after hot file data: root=$rootdir hotfile=$hotfile" >&2 + exit 1 + fi +} + +root="$tmpdir/root" +mkdir -p "$root/a" "$root/c" +printf hot > "$root/a/hot" +dd if=/dev/zero bs=4096 count=16 of="$root/a/cold" status=none +dd if=/dev/zero bs=4096 count=16 of="$root/c/cold" status=none + +printf '/a/hot\n' > "$tmpdir/hotlist.file" +"$MKFS" -zlz4hc,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.file" \ + "$tmpdir/img.file.erofs" "$root" >/dev/null +assert_hot_file_nonrecursive_hotdir "$tmpdir/img.file.erofs" + +printf '/a/\n' > "$tmpdir/hotlist.dir" +"$MKFS" -zlz4hc,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.dir" \ + "$tmpdir/img.dir.erofs" "$root" >/dev/null +assert_explicit_hotdir_nonrecursive "$tmpdir/img.dir.erofs" + +mkdir -p "$root/usr/lib/x86_64-linux-gnu" "$root/usr/lib64" +printf real > "$root/usr/lib/x86_64-linux-gnu/ld-real" +dd if=/dev/zero bs=4096 count=16 of="$root/aaa-cold" status=none +dd if=/dev/zero bs=4096 count=16 of="$root/usr/lib64/aaa-cold" status=none +ln -s usr/lib64 "$root/lib64" +ln -s /lib/x86_64-linux-gnu/ld-real "$root/usr/lib64/ld-linux-x86-64.so.2" + +printf '/lib64/ld-linux-x86-64.so.2\n' > "$tmpdir/hotlist.symlink" +"$MKFS" -d9 -zlz4hc,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.symlink" \ + "$tmpdir/img.symlink.erofs" "$root" >"$tmpdir/mkfs.symlink.log" 2>&1 +assert_symlink_chain_metadata_hot "$tmpdir/img.symlink.erofs" "$tmpdir/mkfs.symlink.log" + +root_meta="$tmpdir/root-meta-first" +mkdir -p "$root_meta/a" "$root_meta/z" +dd if=/dev/urandom bs=4096 count=256 of="$root_meta/a/hot" status=none +printf '/a/hot\n/z/\n' > "$tmpdir/hotlist.meta-first" +"$MKFS" -d9 -zzstd,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.meta-first" \ + "$tmpdir/img.meta-first.erofs" "$root_meta" >"$tmpdir/mkfs.meta-first.log" 2>&1 +assert_hot_metadata_precedes_regular_data "$tmpdir/img.meta-first.erofs" + +root_usrmerge="$tmpdir/root-usrmerge" +mkdir -p \ + "$root_usrmerge/dev" \ + "$root_usrmerge/etc/ssl" \ + "$root_usrmerge/proc" \ + "$root_usrmerge/sys" \ + "$root_usrmerge/usr/bin" \ + "$root_usrmerge/usr/lib/x86_64-linux-gnu" \ + "$root_usrmerge/usr/lib64" \ + "$root_usrmerge/usr/local/bin" \ + "$root_usrmerge/usr/local/sbin" +ln -s usr/bin "$root_usrmerge/bin" +ln -s usr/lib "$root_usrmerge/lib" +ln -s usr/lib64 "$root_usrmerge/lib64" +ln -s dash "$root_usrmerge/usr/bin/sh" +ln -s /lib/x86_64-linux-gnu/ld-real "$root_usrmerge/usr/lib64/ld-linux-x86-64.so.2" +for file in \ + etc/passwd \ + etc/ld.so.cache \ + etc/ssl/openssl.cnf \ + usr/bin/dash \ + usr/lib/x86_64-linux-gnu/ld-real \ + usr/lib/x86_64-linux-gnu/libc.so.6 \ + usr/lib/x86_64-linux-gnu/libdl.so.2 \ + usr/lib/x86_64-linux-gnu/libstdc++.so.6 \ + usr/lib/x86_64-linux-gnu/libm.so.6 \ + usr/lib/x86_64-linux-gnu/libgcc_s.so.1 \ + usr/lib/x86_64-linux-gnu/libpthread.so.0 \ + usr/local/bin/docker-entrypoint.sh \ + usr/local/bin/node \ + usr/local/sbin/docker-entrypoint.sh; do + printf x > "$root_usrmerge/$file" +done +cat > "$tmpdir/hotlist.usrmerge" <<'EOF' +/etc/passwd +/proc/ +/usr/local/sbin/docker-entrypoint.sh +/usr/local/bin/docker-entrypoint.sh +/bin/sh +/lib64/ld-linux-x86-64.so.2 +/etc/ld.so.preload +/etc/ld.so.cache +/lib/x86_64-linux-gnu/libc.so.6 +/usr/local/sbin/node +/usr/local/bin/node +/lib/x86_64-linux-gnu/libdl.so.2 +/lib/x86_64-linux-gnu/libstdc++.so.6 +/lib/x86_64-linux-gnu/libm.so.6 +/lib/x86_64-linux-gnu/libgcc_s.so.1 +/lib/x86_64-linux-gnu/libpthread.so.0 +/etc/ssl/openssl.cnf +/sys/ +/dev/ +EOF +"$MKFS" -d9 -zzstd,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.usrmerge" \ + "$tmpdir/img.usrmerge.erofs" "$root_usrmerge" >"$tmpdir/mkfs.usrmerge.log" 2>&1 +assert_late_hotdirs_precede_regular_hotfile "$tmpdir/img.usrmerge.erofs" + +root_alias="$tmpdir/root-alias-rank" +mkdir -p "$root_alias/usr/lib/x86_64-linux-gnu" "$root_alias/usr/local/bin" +dd if=/dev/zero bs=4096 count=64 of="$root_alias/usr/local/bin/node" status=none +dd if=/dev/zero bs=4096 count=64 of="$root_alias/usr/lib/x86_64-linux-gnu/libfoo.so.1.2.3" status=none +ln -s libfoo.so.1.2.3 "$root_alias/usr/lib/x86_64-linux-gnu/libfoo.so.1" +cat > "$tmpdir/hotlist.alias-rank" <<'EOF' +/usr/lib/x86_64-linux-gnu/libfoo.so.1.2.3 +/usr/local/bin/node +/usr/lib/x86_64-linux-gnu/libfoo.so.1 +EOF +"$MKFS" -d9 -zzstd,level=9 --workers=1 \ + --hot-file-list="$tmpdir/hotlist.alias-rank" \ + "$tmpdir/img.alias-rank.erofs" "$root_alias" >"$tmpdir/mkfs.alias-rank.log" 2>&1 +assert_late_symlink_alias_inherits_target_rank "$tmpdir/img.alias-rank.erofs" + +root_hardlink="$tmpdir/root-hardlink" +mkdir -p "$root_hardlink/a" "$root_hardlink/b" +printf data > "$root_hardlink/a/hot" +ln "$root_hardlink/a/hot" "$root_hardlink/b/alias" +cat > "$tmpdir/hotlist.hardlink" <<'EOF' +/a/hot +/b/alias +EOF +"$MKFS" -d9 --hot-file-list="$tmpdir/hotlist.hardlink" \ + "$tmpdir/img.hardlink.erofs" "$root_hardlink" >"$tmpdir/mkfs.hardlink.log" 2>&1 +assert_hot_hardlinks_keep_real_link_count "$tmpdir/img.hardlink.erofs" + +root_wide="$tmpdir/root-wide" +mkdir -p "$root_wide/zz" +printf hot > "$root_wide/zz/hot" +i=0 +while [ "$i" -lt 420 ]; do + dir=$(printf 'cold%04d' "$i") + mkdir -p "$root_wide/$dir" + printf x > "$root_wide/$dir/file" + i=$((i + 1)) +done +printf '/zz/hot\n' > "$tmpdir/hotlist.wide-root" +"$MKFS" -d9 --hot-file-list="$tmpdir/hotlist.wide-root" \ + "$tmpdir/img.wide-root.erofs" "$root_wide" >"$tmpdir/mkfs.wide-root.log" 2>&1 +assert_root_dirdata_precedes_hot_file "$tmpdir/img.wide-root.erofs" base-commit: 8a579d4d692689eee0af40df91d91d4e632d4c0e -- 2.43.7
