[RFC PATCH 4/5] erofs-utils: lib: implement proto manifest subset

adigitX adityakammati.workspace at gmail.com
Sun Mar 22 02:28:31 AEDT 2026


---
 lib/manifest.c | 388 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 362 insertions(+), 26 deletions(-)

diff --git a/lib/manifest.c b/lib/manifest.c
index ed37f94..a376a98 100644
--- a/lib/manifest.c
+++ b/lib/manifest.c
@@ -8,6 +8,10 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <config.h>
+#if defined(HAVE_SYS_SYSMACROS_H)
+#include <sys/sysmacros.h>
+#endif
 #include "erofs/diskbuf.h"
 #include "erofs/hashmap.h"
 #include "erofs/inode.h"
@@ -16,6 +20,7 @@
 #include "liberofs_rebuild.h"
 
 #define EROFS_MANIFEST_COMPOSEFS_NR_FIELDS	11
+#define EROFS_MANIFEST_PROTO_HEADER		"%erofs.proto.v1"
 
 struct erofs_manifest_path {
 	struct hashmap_entry ent;
@@ -236,7 +241,12 @@ static int erofs_manifest_next_field(char **cursor, char **field)
 	char *start, *end;
 
 	start = *cursor;
-	if (!start || !*start)
+	if (!start)
+		return -EINVAL;
+
+	while (*start == ' ')
+		++start;
+	if (!*start)
 		return -EINVAL;
 
 	end = strchr(start, ' ');
@@ -255,6 +265,18 @@ static int erofs_manifest_next_field(char **cursor, char **field)
 	return 0;
 }
 
+static int erofs_manifest_stat_regular(const char *path, erofs_off_t *size)
+{
+	struct stat st;
+
+	if (stat(path, &st))
+		return -errno;
+	if (!S_ISREG(st.st_mode))
+		return -EINVAL;
+	*size = st.st_size;
+	return 0;
+}
+
 static int erofs_manifest_apply_inode(struct erofs_manifest_ctx *ctx,
 				      struct erofs_inode *inode,
 				      const char *path,
@@ -399,7 +421,8 @@ static int erofs_manifest_prepare_dentry(struct erofs_manifest_ctx *ctx,
 		if (!S_ISDIR(mode))
 			return -EINVAL;
 		*out_inode = ctx->root;
-		*out_dentry = NULL;
+		if (out_dentry)
+			*out_dentry = NULL;
 		*out_root = true;
 		return 0;
 	}
@@ -435,7 +458,8 @@ static int erofs_manifest_prepare_dentry(struct erofs_manifest_ctx *ctx,
 		return err;
 
 	*out_inode = inode;
-	*out_dentry = d;
+	if (out_dentry)
+		*out_dentry = d;
 	return 0;
 }
 
@@ -666,33 +690,357 @@ out:
 }
 
 static int erofs_manifest_load_composefs(struct erofs_manifest_ctx *ctx,
-					 const char *source)
+					 FILE *fp, char **line, size_t *cap,
+					 unsigned int lineno, bool has_line)
+{
+	int err;
+
+	if (has_line && (*line)[0]) {
+		err = erofs_manifest_apply_composefs_line(ctx, *line, lineno);
+		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)
+			return err;
+	}
+	return ferror(fp) ? -EIO : 0;
+}
+
+static int erofs_manifest_apply_proto_hardlink(struct erofs_manifest_ctx *ctx,
+					       const char *path,
+					       const char *target_path,
+					       umode_t mode)
+{
+	struct erofs_dentry *dentry = NULL, *target = NULL;
+	struct erofs_inode *inode, *target_inode;
+	bool is_root;
+	int err;
+
+	err = erofs_manifest_prepare_dentry(ctx, path, mode, &dentry, &inode,
+					    &is_root);
+	if (err)
+		return err;
+	if (is_root)
+		return -EINVAL;
+
+	err = erofs_manifest_lookup_dentry(ctx, target_path, &target);
+	if (err)
+		return err;
+	if (!target || target->type == EROFS_FT_UNKNOWN)
+		return -ENOENT;
+
+	target_inode = target->inode;
+	if (S_ISDIR(target_inode->i_mode))
+		return -EISDIR;
+
+	erofs_iput(inode);
+	dentry->inode = erofs_igrab(target_inode);
+	dentry->type = erofs_mode_to_ftype(target_inode->i_mode);
+	++target_inode->i_nlink;
+	return 0;
+}
+
+static int erofs_manifest_apply_proto_line(struct erofs_manifest_ctx *ctx,
+					   char *line, unsigned int lineno)
+{
+	char *cursor = line, *field, *path = NULL, *payload = NULL;
+	struct erofs_inode *inode;
+	umode_t mode;
+	erofs_off_t size = 0;
+	u64 mode_raw, uid, gid, major_id, minor_id;
+	u64 mtime = ctx->root->i_mtime;
+	u32 mtime_nsec = ctx->root->i_mtime_nsec;
+	dev_t rdev = 0;
+	char kind;
+	bool is_root;
+	int err;
+
+	err = erofs_manifest_next_field(&cursor, &field);
+	if (err) {
+		erofs_err("manifest:%u: missing proto path", lineno);
+		return err;
+	}
+	err = erofs_manifest_unescape(field, false, &path);
+	if (err) {
+		erofs_err("manifest:%u: invalid proto path", lineno);
+		goto out;
+	}
+	if (path[0] != '/') {
+		err = -EINVAL;
+		erofs_err("manifest:%u: proto path must be absolute: %s",
+			  lineno, path);
+		goto out;
+	}
+
+	err = erofs_manifest_next_field(&cursor, &field);
+	if (err || strlen(field) != 1) {
+		err = -EINVAL;
+		erofs_err("manifest:%u: invalid proto type", lineno);
+		goto out;
+	}
+	kind = field[0];
+
+	err = erofs_manifest_next_field(&cursor, &field);
+	if (err || erofs_manifest_parse_uint(field, 8, &mode_raw)) {
+		err = -EINVAL;
+		erofs_err("manifest:%u: invalid proto mode", lineno);
+		goto out;
+	}
+
+	err = erofs_manifest_next_field(&cursor, &field);
+	if (err || erofs_manifest_parse_uint(field, 10, &uid)) {
+		err = -EINVAL;
+		erofs_err("manifest:%u: invalid proto uid", lineno);
+		goto out;
+	}
+
+	err = erofs_manifest_next_field(&cursor, &field);
+	if (err || erofs_manifest_parse_uint(field, 10, &gid)) {
+		err = -EINVAL;
+		erofs_err("manifest:%u: invalid proto gid", lineno);
+		goto out;
+	}
+
+	switch (kind) {
+	case 'd':
+		mode = S_IFDIR | (mode_raw & 07777);
+		break;
+	case 'f':
+		mode = S_IFREG | (mode_raw & 07777);
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err || erofs_manifest_unescape(field, false, &payload)) {
+			err = -EINVAL;
+			erofs_err("manifest:%u: invalid proto file payload", lineno);
+			goto out;
+		}
+		err = erofs_manifest_stat_regular(payload, &size);
+		if (err)
+			goto out;
+		break;
+	case 'l':
+		mode = S_IFLNK | (mode_raw & 07777);
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err || erofs_manifest_unescape(field, false, &payload)) {
+			err = -EINVAL;
+			erofs_err("manifest:%u: invalid proto symlink target",
+				  lineno);
+			goto out;
+		}
+		size = strlen(payload);
+		break;
+	case 'L':
+		mode = S_IFREG | (mode_raw & 07777);
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err || erofs_manifest_unescape(field, false, &payload)) {
+			err = -EINVAL;
+			erofs_err("manifest:%u: invalid proto hardlink target",
+				  lineno);
+			goto out;
+		}
+		if (payload[0] != '/') {
+			err = -EINVAL;
+			erofs_err("manifest:%u: hardlink target must be absolute",
+				  lineno);
+			goto out;
+		}
+		break;
+	case 'p':
+		mode = S_IFIFO | (mode_raw & 07777);
+		break;
+	case 'c':
+	case 'b':
+		mode = (kind == 'c' ? S_IFCHR : S_IFBLK) | (mode_raw & 07777);
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err || erofs_manifest_parse_uint(field, 10, &major_id)) {
+			err = -EINVAL;
+			erofs_err("manifest:%u: invalid proto device major", lineno);
+			goto out;
+		}
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err || erofs_manifest_parse_uint(field, 10, &minor_id)) {
+			err = -EINVAL;
+			erofs_err("manifest:%u: invalid proto device minor", lineno);
+			goto out;
+		}
+		rdev = makedev(major_id, minor_id);
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		erofs_err("manifest:%u: unsupported proto type %c", lineno, kind);
+		goto out;
+	}
+
+	while (cursor && *cursor) {
+		err = erofs_manifest_next_field(&cursor, &field);
+		if (err)
+			goto out;
+		if (!strncmp(field, "mtime:", 6)) {
+			err = erofs_manifest_parse_mtime(field + 6, &mtime,
+							 &mtime_nsec);
+			if (err) {
+				erofs_err("manifest:%u: invalid proto mtime",
+					  lineno);
+				goto out;
+			}
+			continue;
+		}
+
+		err = -EINVAL;
+		erofs_err("manifest:%u: unsupported proto attribute %s",
+			  lineno, field);
+		goto out;
+	}
+
+	if (kind == 'L') {
+		err = erofs_manifest_apply_proto_hardlink(ctx, path, payload,
+							  mode);
+		goto out;
+	}
+
+	err = erofs_manifest_prepare_dentry(ctx, path, mode, NULL, &inode,
+					    &is_root);
+	if (err)
+		goto out;
+
+	switch (mode & S_IFMT) {
+	case S_IFDIR:
+	case S_IFREG:
+	case S_IFLNK:
+	case S_IFCHR:
+	case S_IFBLK:
+	case S_IFIFO:
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		goto out;
+	}
+
+	err = erofs_manifest_apply_inode(ctx, inode, path, mode, uid, gid,
+					 mtime, mtime_nsec, rdev, size);
+	if (err)
+		goto out;
+
+	if (S_ISREG(mode) && payload) {
+		err = erofs_manifest_stage_file(inode, payload);
+	} else if (S_ISLNK(mode)) {
+		inode->i_link = strdup(payload);
+		if (!inode->i_link)
+			err = -ENOMEM;
+	}
+out:
+	free(path);
+	free(payload);
+	return err;
+}
+
+static int erofs_manifest_load_proto(struct erofs_manifest_ctx *ctx, FILE *fp,
+				     char **line, size_t *cap,
+				     unsigned int lineno, bool has_line)
+{
+	bool seen_header = false;
+	int err;
+
+	if (has_line && (*line)[0]) {
+		if (strcmp(*line, EROFS_MANIFEST_PROTO_HEADER)) {
+			erofs_err("manifest:%u: expected %s", lineno,
+				  EROFS_MANIFEST_PROTO_HEADER);
+			return -EINVAL;
+		}
+		seen_header = true;
+	}
+
+	while (getline(line, cap, fp) >= 0) {
+		++lineno;
+		erofs_manifest_trim_newline(*line);
+		if (!(*line)[0])
+			continue;
+		if (!seen_header) {
+			if (strcmp(*line, EROFS_MANIFEST_PROTO_HEADER)) {
+				erofs_err("manifest:%u: expected %s", lineno,
+					  EROFS_MANIFEST_PROTO_HEADER);
+				return -EINVAL;
+			}
+			seen_header = true;
+			continue;
+		}
+
+		if ((err = erofs_manifest_apply_proto_line(ctx, *line, lineno)))
+			return err;
+	}
+
+	if (!seen_header) {
+		erofs_err("manifest: missing %s header",
+			  EROFS_MANIFEST_PROTO_HEADER);
+		return -EINVAL;
+	}
+
+	return ferror(fp) ? -EIO : 0;
+}
+
+static int erofs_manifest_load_source(struct erofs_manifest_ctx *ctx,
+				      enum erofs_manifest_format format,
+				      const char *source)
 {
 	FILE *fp;
 	char *line = NULL;
 	size_t cap = 0;
 	unsigned int lineno = 0;
+	bool has_line = false;
 	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;
+	if (format == EROFS_MANIFEST_FORMAT_AUTO) {
+		while (getline(&line, &cap, fp) >= 0) {
+			++lineno;
+			erofs_manifest_trim_newline(line);
+			if (!line[0])
+				continue;
+
+			format = !strcmp(line, EROFS_MANIFEST_PROTO_HEADER) ?
+				EROFS_MANIFEST_FORMAT_PROTO :
+				EROFS_MANIFEST_FORMAT_COMPOSEFS;
+			has_line = true;
+			break;
+		}
 
-		err = erofs_manifest_apply_composefs_line(ctx, line, lineno);
-		if (err)
+		if (ferror(fp)) {
+			err = -EIO;
+			goto out;
+		}
+		if (format == EROFS_MANIFEST_FORMAT_AUTO) {
+			err = 0;
 			goto out;
+		}
+	}
+
+	switch (format) {
+	case EROFS_MANIFEST_FORMAT_COMPOSEFS:
+		err = erofs_manifest_load_composefs(ctx, fp, &line, &cap,
+						    lineno, has_line);
+		break;
+	case EROFS_MANIFEST_FORMAT_PROTO:
+		err = erofs_manifest_load_proto(ctx, fp, &line, &cap,
+						lineno, has_line);
+		break;
+	default:
+		err = -EINVAL;
+		break;
 	}
 
-	err = ferror(fp) ? -EIO : 0;
 out:
-	free(line);
 	erofs_manifest_close_source(source, fp);
+	free(line);
 	return err;
 }
 
@@ -707,19 +1055,7 @@ int erofs_manifest_load(struct erofs_importer *im,
 		return -EINVAL;
 
 	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;
-	}
+	err = erofs_manifest_load_source(&ctx, format, source);
 	erofs_manifest_ctx_exit(&ctx);
 	return err;
 }
-- 
2.51.0



More information about the Linux-erofs mailing list