[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