[RFC PATCH 3/5] erofs-utils: lib: implement composefs manifest subset
adigitX
adityakammati.workspace at gmail.com
Sun Mar 22 02:28:30 AEDT 2026
---
lib/manifest.c | 715 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 712 insertions(+), 3 deletions(-)
diff --git a/lib/manifest.c b/lib/manifest.c
index d7bbd5d..ed37f94 100644
--- a/lib/manifest.c
+++ b/lib/manifest.c
@@ -1,16 +1,725 @@
// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+#define _GNU_SOURCE
#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "erofs/diskbuf.h"
+#include "erofs/hashmap.h"
+#include "erofs/inode.h"
#include "erofs/manifest.h"
#include "erofs/print.h"
+#include "liberofs_rebuild.h"
+
+#define EROFS_MANIFEST_COMPOSEFS_NR_FIELDS 11
+
+struct erofs_manifest_path {
+ struct hashmap_entry ent;
+ char path[];
+};
+
+struct erofs_manifest_ctx {
+ struct erofs_importer *im;
+ struct erofs_inode *root;
+ struct hashmap explicit_paths;
+};
+
+static int erofs_manifest_path_cmp(const void *a, const void *b,
+ const void *keydata)
+{
+ const struct erofs_manifest_path *ea = a;
+ const struct erofs_manifest_path *eb = b;
+ const char *path = keydata ? keydata : eb->path;
+
+ return strcmp(ea->path, path);
+}
+
+static void erofs_manifest_ctx_init(struct erofs_manifest_ctx *ctx,
+ struct erofs_importer *im)
+{
+ *ctx = (struct erofs_manifest_ctx) {
+ .im = im,
+ .root = im->root,
+ };
+ hashmap_init(&ctx->explicit_paths, erofs_manifest_path_cmp, 0);
+}
+
+static void erofs_manifest_ctx_exit(struct erofs_manifest_ctx *ctx)
+{
+ struct hashmap_iter iter;
+ struct erofs_manifest_path *path;
+
+ while ((path = hashmap_iter_first(&ctx->explicit_paths, &iter)) != NULL) {
+ hashmap_remove(&ctx->explicit_paths, &path->ent);
+ free(path);
+ }
+ (void)hashmap_free(&ctx->explicit_paths);
+}
+
+static bool erofs_manifest_has_explicit_path(struct erofs_manifest_ctx *ctx,
+ const char *path)
+{
+ return hashmap_get_from_hash(&ctx->explicit_paths, strhash(path), path);
+}
+
+static int erofs_manifest_add_explicit_path(struct erofs_manifest_ctx *ctx,
+ const char *path)
+{
+ struct erofs_manifest_path *entry;
+
+ entry = malloc(sizeof(*entry) + strlen(path) + 1);
+ if (!entry)
+ return -ENOMEM;
+
+ hashmap_entry_init(&entry->ent, strhash(path));
+ strcpy(entry->path, path);
+ hashmap_add(&ctx->explicit_paths, &entry->ent);
+ return 0;
+}
+
+static int erofs_manifest_open_source(const char *source, FILE **fp)
+{
+ if (!strcmp(source, "-")) {
+ *fp = stdin;
+ return 0;
+ }
+
+ *fp = fopen(source, "r");
+ if (!*fp)
+ return -errno;
+ return 0;
+}
+
+static void erofs_manifest_close_source(const char *source, FILE *fp)
+{
+ if (fp && strcmp(source, "-"))
+ fclose(fp);
+}
+
+static void erofs_manifest_trim_newline(char *line)
+{
+ size_t len = strlen(line);
+
+ while (len && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+}
+
+static int erofs_manifest_parse_uint(const char *value, int base, u64 *out)
+{
+ char *end;
+ unsigned long long n;
+
+ if (!value || !*value)
+ return -EINVAL;
+
+ errno = 0;
+ n = strtoull(value, &end, base);
+ if (errno || *end != '\0')
+ return -EINVAL;
+ *out = n;
+ return 0;
+}
+
+static int erofs_manifest_parse_mtime(const char *value, u64 *sec, u32 *nsec)
+{
+ char *end;
+ unsigned long long seconds, nanoseconds;
+
+ if (!value || !*value || !strcmp(value, "-"))
+ return -EINVAL;
+
+ errno = 0;
+ seconds = strtoull(value, &end, 10);
+ if (errno || *end != '.')
+ return -EINVAL;
+
+ errno = 0;
+ nanoseconds = strtoull(end + 1, &end, 10);
+ if (errno || *end != '\0' || nanoseconds >= 1000000000ULL)
+ return -EINVAL;
+
+ *sec = seconds;
+ *nsec = nanoseconds;
+ return 0;
+}
+
+static int erofs_manifest_hex2nibble(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -EINVAL;
+}
+
+static int erofs_manifest_unescape(const char *value, bool optional, char **out)
+{
+ size_t len, i, j;
+ char *decoded;
+
+ if (optional && !strcmp(value, "-")) {
+ *out = NULL;
+ return 0;
+ }
+
+ len = strlen(value);
+ decoded = malloc(len + 1);
+ if (!decoded)
+ return -ENOMEM;
+
+ for (i = 0, j = 0; i < len; ++i) {
+ int c = value[i];
+
+ if (c != '\\') {
+ decoded[j++] = c;
+ continue;
+ }
+
+ if (++i >= len) {
+ free(decoded);
+ return -EINVAL;
+ }
+
+ switch (value[i]) {
+ case '\\':
+ decoded[j++] = '\\';
+ break;
+ case 'n':
+ decoded[j++] = '\n';
+ break;
+ case 'r':
+ decoded[j++] = '\r';
+ break;
+ case 't':
+ decoded[j++] = '\t';
+ break;
+ case 'x': {
+ int hi, lo;
+
+ if (i + 2 >= len) {
+ free(decoded);
+ return -EINVAL;
+ }
+ hi = erofs_manifest_hex2nibble(value[i + 1]);
+ lo = erofs_manifest_hex2nibble(value[i + 2]);
+ if (hi < 0 || lo < 0) {
+ free(decoded);
+ return -EINVAL;
+ }
+ c = (hi << 4) | lo;
+ if (!c) {
+ free(decoded);
+ return -EINVAL;
+ }
+ decoded[j++] = c;
+ i += 2;
+ break;
+ }
+ default:
+ free(decoded);
+ return -EINVAL;
+ }
+ }
+ decoded[j] = '\0';
+ *out = decoded;
+ return 0;
+}
+
+static int erofs_manifest_next_field(char **cursor, char **field)
+{
+ char *start, *end;
+
+ start = *cursor;
+ if (!start || !*start)
+ return -EINVAL;
+
+ end = strchr(start, ' ');
+ if (end) {
+ *end = '\0';
+ *cursor = end + 1;
+ if (!**cursor)
+ *cursor = NULL;
+ } else {
+ *cursor = NULL;
+ }
+
+ if (!*start)
+ return -EINVAL;
+ *field = start;
+ return 0;
+}
+
+static int erofs_manifest_apply_inode(struct erofs_manifest_ctx *ctx,
+ struct erofs_inode *inode,
+ const char *path,
+ umode_t mode, u32 uid, u32 gid,
+ u64 mtime, u32 mtime_nsec,
+ dev_t rdev, erofs_off_t size)
+{
+ struct stat st = { 0 };
+ int err;
+
+ st.st_mode = mode;
+ st.st_uid = uid;
+ st.st_gid = gid;
+ st.st_mtime = mtime;
+ ST_MTIM_NSEC_SET(&st, mtime_nsec);
+ st.st_rdev = rdev;
+ st.st_size = size;
+
+ err = __erofs_fill_inode(ctx->im, inode, &st, path);
+ if (err)
+ return err;
+
+ inode->i_mode = mode;
+ inode->i_size = S_ISDIR(mode) || S_ISCHR(mode) || S_ISBLK(mode) ||
+ S_ISFIFO(mode) ? 0 : size;
+ inode->u.i_rdev = (S_ISCHR(mode) || S_ISBLK(mode)) ?
+ erofs_new_encode_dev(rdev) : 0;
+ if (!inode->i_srcpath) {
+ inode->i_srcpath = strdup(path);
+ if (!inode->i_srcpath)
+ return -ENOMEM;
+ }
+ if (!S_ISDIR(mode))
+ inode->i_nlink = 1;
+ return 0;
+}
+
+static int erofs_manifest_stage_file(struct erofs_inode *inode,
+ const char *payload)
+{
+ char buf[32768];
+ struct stat st;
+ int srcfd = -1, dstfd, err;
+ u64 off, left;
+
+ srcfd = open(payload, O_RDONLY);
+ if (srcfd < 0)
+ return -errno;
+ if (fstat(srcfd, &st)) {
+ err = -errno;
+ goto out;
+ }
+ if (!S_ISREG(st.st_mode) || st.st_size != inode->i_size) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!inode->i_diskbuf) {
+ inode->i_diskbuf = calloc(1, sizeof(*inode->i_diskbuf));
+ if (!inode->i_diskbuf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ } else {
+ erofs_diskbuf_close(inode->i_diskbuf);
+ }
+
+ dstfd = erofs_diskbuf_reserve(inode->i_diskbuf, 0, &off);
+ if (dstfd < 0) {
+ err = dstfd;
+ goto out;
+ }
+
+ left = inode->i_size;
+ while (left) {
+ ssize_t nread;
+
+ nread = read(srcfd, buf, min_t(u64, sizeof(buf), left));
+ if (nread < 0) {
+ err = -errno;
+ goto out;
+ }
+ if (!nread) {
+ err = -EIO;
+ goto out;
+ }
+ if (pwrite(dstfd, buf, nread, off) != nread) {
+ err = -errno ? -errno : -EIO;
+ goto out;
+ }
+ left -= nread;
+ off += nread;
+ }
+
+ erofs_diskbuf_commit(inode->i_diskbuf, inode->i_size);
+ inode->datasource = EROFS_INODE_DATA_SOURCE_DISKBUF;
+ err = 0;
+out:
+ if (srcfd >= 0)
+ close(srcfd);
+ return err;
+}
+
+static int erofs_manifest_lookup_dentry(struct erofs_manifest_ctx *ctx,
+ const char *path,
+ struct erofs_dentry **out)
+{
+ struct erofs_dentry *d;
+ char *scratch;
+ bool ignored;
+
+ scratch = strdup(path);
+ if (!scratch)
+ return -ENOMEM;
+
+ d = erofs_rebuild_get_dentry(ctx->root, scratch, false,
+ &ignored, &ignored, false);
+ free(scratch);
+ if (IS_ERR(d))
+ return PTR_ERR(d);
+ *out = d;
+ return 0;
+}
+
+static int erofs_manifest_prepare_dentry(struct erofs_manifest_ctx *ctx,
+ const char *path, umode_t mode,
+ struct erofs_dentry **out_dentry,
+ struct erofs_inode **out_inode,
+ bool *out_root)
+{
+ struct erofs_dentry *d;
+ struct erofs_inode *inode;
+ char *scratch;
+ bool ignored;
+ int err;
+
+ *out_root = false;
+ if (erofs_manifest_has_explicit_path(ctx, path))
+ return -EEXIST;
+
+ if (!strcmp(path, "/")) {
+ if (!S_ISDIR(mode))
+ return -EINVAL;
+ *out_inode = ctx->root;
+ *out_dentry = NULL;
+ *out_root = true;
+ return 0;
+ }
+
+ scratch = strdup(path);
+ if (!scratch)
+ return -ENOMEM;
+
+ d = erofs_rebuild_get_dentry(ctx->root, scratch, false,
+ &ignored, &ignored, false);
+ free(scratch);
+ if (IS_ERR(d))
+ return PTR_ERR(d);
+ if (!d)
+ return -EINVAL;
+
+ if (d->type == EROFS_FT_UNKNOWN) {
+ inode = erofs_new_inode(ctx->root->sbi);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ inode->i_parent = d->inode;
+ d->inode = inode;
+ d->type = erofs_mode_to_ftype(mode);
+ } else {
+ inode = d->inode;
+ if (!S_ISDIR(inode->i_mode) || !S_ISDIR(mode))
+ return -EEXIST;
+ }
+
+ err = erofs_manifest_add_explicit_path(ctx, path);
+ if (err)
+ return err;
+
+ *out_inode = inode;
+ *out_dentry = d;
+ return 0;
+}
+
+static int erofs_manifest_apply_composefs_line(struct erofs_manifest_ctx *ctx,
+ char *line, unsigned int lineno)
+{
+ char *cursor = line, *field[EROFS_MANIFEST_COMPOSEFS_NR_FIELDS];
+ char *path = NULL, *payload = NULL, *content = NULL, *digest = NULL;
+ struct erofs_dentry *dentry = NULL, *target = NULL;
+ struct erofs_inode *inode, *target_inode;
+ umode_t mode;
+ u64 size, mode_raw, uid, gid, rdev_raw;
+ u64 mtime;
+ u32 mtime_nsec;
+ bool hardlink, is_root;
+ int err, i;
+
+ for (i = 0; i < EROFS_MANIFEST_COMPOSEFS_NR_FIELDS; ++i) {
+ err = erofs_manifest_next_field(&cursor, &field[i]);
+ if (err) {
+ erofs_err("manifest:%u: expected %u composefs fields",
+ lineno, EROFS_MANIFEST_COMPOSEFS_NR_FIELDS);
+ return err;
+ }
+ }
+ if (cursor && *cursor) {
+ erofs_err("manifest:%u: xattrs are not supported yet", lineno);
+ return -EOPNOTSUPP;
+ }
+
+ err = erofs_manifest_unescape(field[0], false, &path);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs path", lineno);
+ goto out;
+ }
+ if (path[0] != '/') {
+ err = -EINVAL;
+ erofs_err("manifest:%u: composefs path must be absolute: %s",
+ lineno, path);
+ goto out;
+ }
+
+ err = erofs_manifest_parse_uint(field[1], 10, &size);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs size", lineno);
+ goto out;
+ }
+
+ hardlink = field[2][0] == '@';
+ err = erofs_manifest_parse_uint(field[2] + hardlink, 8, &mode_raw);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs mode", lineno);
+ goto out;
+ }
+ mode = mode_raw;
+
+ err = erofs_manifest_parse_uint(field[4], 10, &uid);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs uid", lineno);
+ goto out;
+ }
+
+ err = erofs_manifest_parse_uint(field[5], 10, &gid);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs gid", lineno);
+ goto out;
+ }
+
+ if (!strcmp(field[6], "-")) {
+ rdev_raw = 0;
+ } else {
+ err = erofs_manifest_parse_uint(field[6], 10, &rdev_raw);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs rdev", lineno);
+ goto out;
+ }
+ }
+
+ err = erofs_manifest_parse_mtime(field[7], &mtime, &mtime_nsec);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs mtime", lineno);
+ goto out;
+ }
+
+ err = erofs_manifest_unescape(field[8], true, &payload);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs payload", lineno);
+ goto out;
+ }
+ err = erofs_manifest_unescape(field[9], true, &content);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs content", lineno);
+ goto out;
+ }
+ err = erofs_manifest_unescape(field[10], true, &digest);
+ if (err) {
+ erofs_err("manifest:%u: invalid composefs digest", lineno);
+ goto out;
+ }
+
+ if (hardlink) {
+ if (content || digest) {
+ err = -EOPNOTSUPP;
+ erofs_err("manifest:%u: hardlinks only support PATH and PAYLOAD in v1",
+ lineno);
+ goto out;
+ }
+ if (!payload || payload[0] != '/') {
+ err = -EINVAL;
+ erofs_err("manifest:%u: hardlink target must be absolute",
+ lineno);
+ goto out;
+ }
+ err = erofs_manifest_prepare_dentry(ctx, path, S_IFREG | 0644,
+ &dentry, &inode, &is_root);
+ if (err)
+ goto out;
+ if (is_root) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = erofs_manifest_lookup_dentry(ctx, payload, &target);
+ if (err)
+ goto out;
+ if (!target || target->type == EROFS_FT_UNKNOWN) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ target_inode = target->inode;
+ if (S_ISDIR(target_inode->i_mode)) {
+ err = -EISDIR;
+ goto out;
+ }
+
+ erofs_iput(inode);
+ dentry->inode = erofs_igrab(target_inode);
+ dentry->type = erofs_mode_to_ftype(target_inode->i_mode);
+ ++target_inode->i_nlink;
+ err = 0;
+ goto out;
+ }
+
+ err = erofs_manifest_prepare_dentry(ctx, path, mode, &dentry, &inode,
+ &is_root);
+ if (err)
+ goto out;
+
+ if (content) {
+ err = -EOPNOTSUPP;
+ erofs_err("manifest:%u: inline composefs content is not supported yet",
+ lineno);
+ goto out;
+ }
+ if (digest) {
+ err = -EOPNOTSUPP;
+ erofs_err("manifest:%u: composefs digests are not supported yet",
+ lineno);
+ goto out;
+ }
+
+ switch (mode & S_IFMT) {
+ case S_IFDIR:
+ if (payload) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = erofs_manifest_apply_inode(ctx, inode, path, mode,
+ uid, gid, mtime, mtime_nsec,
+ 0, 0);
+ break;
+ case S_IFREG:
+ err = erofs_manifest_apply_inode(ctx, inode, path, mode,
+ uid, gid, mtime, mtime_nsec,
+ 0, size);
+ if (err)
+ break;
+ if (payload) {
+ err = erofs_manifest_stage_file(inode, payload);
+ } else if (size) {
+ err = -EINVAL;
+ }
+ break;
+ case S_IFLNK:
+ if (!payload || size != strlen(payload)) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = erofs_manifest_apply_inode(ctx, inode, path, mode,
+ uid, gid, mtime, mtime_nsec,
+ 0, strlen(payload));
+ if (err)
+ break;
+ inode->i_link = strdup(payload);
+ if (!inode->i_link)
+ err = -ENOMEM;
+ break;
+ case S_IFCHR:
+ case S_IFBLK:
+ if (payload) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = erofs_manifest_apply_inode(ctx, inode, path, mode,
+ uid, gid, mtime, mtime_nsec,
+ (dev_t)rdev_raw, 0);
+ break;
+ case S_IFIFO:
+ if (payload) {
+ err = -EINVAL;
+ goto out;
+ }
+ err = erofs_manifest_apply_inode(ctx, inode, path, mode,
+ uid, gid, mtime, mtime_nsec,
+ 0, 0);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+out:
+ free(path);
+ free(payload);
+ free(content);
+ free(digest);
+ return err;
+}
+
+static int erofs_manifest_load_composefs(struct erofs_manifest_ctx *ctx,
+ const char *source)
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t cap = 0;
+ unsigned int lineno = 0;
+ int err;
+
+ err = erofs_manifest_open_source(source, &fp);
+ if (err)
+ return err;
+
+ while (getline(&line, &cap, fp) >= 0) {
+ ++lineno;
+ erofs_manifest_trim_newline(line);
+ if (!line[0])
+ continue;
+
+ err = erofs_manifest_apply_composefs_line(ctx, line, lineno);
+ if (err)
+ goto out;
+ }
+
+ err = ferror(fp) ? -EIO : 0;
+out:
+ free(line);
+ erofs_manifest_close_source(source, fp);
+ return err;
+}
int erofs_manifest_load(struct erofs_importer *im,
enum erofs_manifest_format format,
const char *source)
{
+ struct erofs_manifest_ctx ctx;
+ int err;
+
if (!im || !im->root || !source)
return -EINVAL;
- (void)format;
- erofs_err("manifest input support is not implemented yet");
- return -EOPNOTSUPP;
+ erofs_manifest_ctx_init(&ctx, im);
+ switch (format) {
+ case EROFS_MANIFEST_FORMAT_AUTO:
+ case EROFS_MANIFEST_FORMAT_COMPOSEFS:
+ err = erofs_manifest_load_composefs(&ctx, source);
+ break;
+ case EROFS_MANIFEST_FORMAT_PROTO:
+ erofs_err("proto manifest input support is not implemented yet");
+ err = -EOPNOTSUPP;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ erofs_manifest_ctx_exit(&ctx);
+ return err;
}
--
2.51.0
More information about the Linux-erofs
mailing list