[PATCH] erofs-utils: lib: follow symlinks on intermediate tar path components
[email protected] Sat, 25 Jul 2026 15:09:20 +0000
| Newsgroups | org.ozlabs.lists.linux-erofs |
|---|---|
| Message-ID | <[email protected]> |
From: Mikhail Malyshev <[email protected]> mkfs.erofs --tar fails to convert layers that store files underneath a symlinked directory, a very common "usr-merge" layout in RHEL/UBI/Fedora based OCI images. For example a base layer contains lib64 -> usr/lib64 (symlink) usr/lib64/ (directory) lib64/libc.so.6 (regular file, path goes THROUGH the symlink) erofs_rebuild_get_dentry() walks the path component by component and, when it hits the "lib64" component, finds an existing dentry that is a symlink rather than a directory. Since there are still components left to resolve it bails out with -ENOTDIR. In practice this surfaces as a bogus ~2 TiB image and "Could not format the device", making the whole layer unconvertible even though nothing is wrong with the tar. A real filesystem (and containerd's walking differ, which applies the same layers via fs.RootPath) resolves such a path by following the symlink to its target directory. Do the same during tar rebuild: when an intermediate path component resolves to a symlink, follow it to the target directory (relative targets from the containing directory, absolute targets from the tree root), chasing chained symlinks up to a bounded depth to avoid loops. Signed-off-by: Mikhail Malyshev <[email protected]> --- lib/rebuild.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/rebuild.c b/lib/rebuild.c index 108a464..febc95c 100644 --- a/lib/rebuild.c +++ b/lib/rebuild.c @@ -82,8 +82,60 @@ struct erofs_dentry *erofs_d_lookup(struct erofs_inode *dir, const char *name) return NULL; } -struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, - char *path, bool aufs, bool *whout, bool *opq, bool to_head) +/* Bound symlink chains during path resolution, like the kernel's MAXSYMLINKS. */ +#define EROFS_REBUILD_SYMLINK_DEPTH 40 + +static struct erofs_dentry *__erofs_rebuild_get_dentry(struct erofs_inode *root, + struct erofs_inode *pwd, char *path, bool aufs, + bool *whout, bool *opq, bool to_head, unsigned int depth); + +/* + * Resolve the target of an intermediate symlink path component to its + * directory inode, following chained symlinks up to a bounded depth. This + * lets tar layers that store files under a symlinked directory be converted + * the same way a real filesystem extracts them, e.g. usr-merge layouts where + * "/lib64 -> usr/lib64" coexists with entries such as "lib64/libc.so.6". + */ +static struct erofs_inode *erofs_rebuild_follow_link(struct erofs_inode *root, + struct erofs_inode *pwd, struct erofs_inode *link, + unsigned int depth) +{ + struct erofs_dentry *d; + struct erofs_inode *base; + bool dumb_wh, dumb_opq; + char *target; + + if (depth >= EROFS_REBUILD_SYMLINK_DEPTH) + return ERR_PTR(-ELOOP); + if (!link->i_link) + return ERR_PTR(-ENOENT); + + target = strdup(link->i_link); + if (!target) + return ERR_PTR(-ENOMEM); + + /* absolute targets are resolved from the tree root */ + base = target[0] == '/' ? root : pwd; + d = __erofs_rebuild_get_dentry(root, base, target, false, + &dumb_wh, &dumb_opq, false, depth + 1); + free(target); + if (IS_ERR(d)) + return ERR_CAST(d); + /* target referred to the root or the base directory itself ("/", ".") */ + if (!d) + return base; + if (d->type == EROFS_FT_DIR) + return d->inode; + /* the target is itself a symlink: keep following the chain */ + if (d->type == EROFS_FT_SYMLINK) + return erofs_rebuild_follow_link(root, d->inode->i_parent, + d->inode, depth + 1); + return ERR_PTR(-ENOTDIR); +} + +static struct erofs_dentry *__erofs_rebuild_get_dentry(struct erofs_inode *root, + struct erofs_inode *pwd, char *path, bool aufs, + bool *whout, bool *opq, bool to_head, unsigned int depth) { struct erofs_dentry *d = NULL; char *s = path; @@ -121,6 +173,15 @@ struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, } d = erofs_d_lookup(pwd, s); + if (d && slash && d->type == EROFS_FT_SYMLINK) { + pwd = erofs_rebuild_follow_link(root, pwd, + d->inode, depth); + if (IS_ERR(pwd)) + return ERR_CAST(pwd); + *slash = '/'; + s = slash + 1; + continue; + } if (d) { if (d->type != EROFS_FT_DIR) { if (slash) @@ -152,6 +213,13 @@ struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, return d; } +struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd, + char *path, bool aufs, bool *whout, bool *opq, bool to_head) +{ + return __erofs_rebuild_get_dentry(pwd, pwd, path, aufs, whout, opq, + to_head, 0); +} + static int erofs_rebuild_write_blob_index(struct erofs_sb_info *dst_sb, struct erofs_inode *inode) { -- 2.43.0