blob: 08c1b86025cb08a64f9905a1d8d8f61534d29e7b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <config.h>
#if defined(HAVE_SYS_SYSMACROS_H)
#include <sys/sysmacros.h>
#endif
#include "erofs/print.h"
#include "erofs/inode.h"
#include "erofs/rebuild.h"
#include "erofs/dir.h"
#include "erofs/xattr.h"
#include "erofs/blobchunk.h"
#include "erofs/internal.h"
#include "liberofs_uuid.h"
#ifdef HAVE_LINUX_AUFS_TYPE_H
#include <linux/aufs_type.h>
#else
#define AUFS_WH_PFX ".wh."
#define AUFS_DIROPQ_NAME AUFS_WH_PFX ".opq"
#define AUFS_WH_DIROPQ AUFS_WH_PFX AUFS_DIROPQ_NAME
#endif
static struct erofs_dentry *erofs_rebuild_mkdir(struct erofs_inode *dir,
const char *s)
{
struct erofs_inode *inode;
struct erofs_dentry *d;
inode = erofs_new_inode(dir->sbi);
if (IS_ERR(inode))
return ERR_CAST(inode);
if (asprintf(&inode->i_srcpath, "%s/%s",
dir->i_srcpath ? : "", s) < 0) {
erofs_iput(inode);
return ERR_PTR(-ENOMEM);
}
inode->i_mode = S_IFDIR | 0755;
inode->i_parent = dir;
inode->i_uid = getuid();
inode->i_gid = getgid();
inode->i_mtime = inode->sbi->build_time;
inode->i_mtime_nsec = inode->sbi->build_time_nsec;
erofs_init_empty_dir(inode);
d = erofs_d_alloc(dir, s);
if (IS_ERR(d)) {
erofs_iput(inode);
} else {
d->type = EROFS_FT_DIR;
d->inode = inode;
}
return d;
}
struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd,
char *path, bool aufs, bool *whout, bool *opq, bool to_head)
{
struct erofs_dentry *d = NULL;
unsigned int len = strlen(path);
char *s = path;
*whout = false;
*opq = false;
while (s < path + len) {
char *slash = memchr(s, '/', path + len - s);
if (slash) {
if (s == slash) {
while (*++s == '/'); /* skip '//...' */
continue;
}
*slash = '\0';
}
if (!memcmp(s, ".", 2)) {
/* null */
} else if (!memcmp(s, "..", 3)) {
pwd = pwd->i_parent;
} else {
struct erofs_inode *inode = NULL;
if (aufs && !slash) {
if (!memcmp(s, AUFS_WH_DIROPQ, sizeof(AUFS_WH_DIROPQ))) {
*opq = true;
break;
}
if (!memcmp(s, AUFS_WH_PFX, sizeof(AUFS_WH_PFX) - 1)) {
s += sizeof(AUFS_WH_PFX) - 1;
*whout = true;
}
}
list_for_each_entry(d, &pwd->i_subdirs, d_child) {
if (!strcmp(d->name, s)) {
if (d->type != EROFS_FT_DIR && slash)
return ERR_PTR(-EIO);
inode = d->inode;
break;
}
}
if (inode) {
if (to_head) {
list_del(&d->d_child);
list_add(&d->d_child, &pwd->i_subdirs);
}
pwd = inode;
} else if (!slash) {
d = erofs_d_alloc(pwd, s);
if (IS_ERR(d))
return d;
d->type = EROFS_FT_UNKNOWN;
d->inode = pwd;
} else {
d = erofs_rebuild_mkdir(pwd, s);
if (IS_ERR(d))
return d;
pwd = d->inode;
}
}
if (slash) {
*slash = '/';
s = slash + 1;
} else {
break;
}
}
return d;
}
static int erofs_rebuild_write_blob_index(struct erofs_sb_info *dst_sb,
struct erofs_inode *inode)
{
int ret;
unsigned int count, unit, chunkbits, i;
struct erofs_inode_chunk_index *idx;
erofs_off_t chunksize;
erofs_blk_t blkaddr;
/* TODO: fill data map in other layouts */
if (inode->datalayout == EROFS_INODE_CHUNK_BASED) {
chunkbits = inode->u.chunkbits;
if (chunkbits < dst_sb->blkszbits) {
erofs_err("%s: chunk size %u is smaller than the target block size %u",
inode->i_srcpath, 1U << chunkbits,
1U << dst_sb->blkszbits);
return -EINVAL;
}
} else if (inode->datalayout == EROFS_INODE_FLAT_PLAIN) {
chunkbits = ilog2(inode->i_size - 1) + 1;
if (chunkbits < dst_sb->blkszbits)
chunkbits = dst_sb->blkszbits;
if (chunkbits - dst_sb->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK)
chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + dst_sb->blkszbits;
} else {
erofs_err("%s: unsupported datalayout %d ", inode->i_srcpath,
inode->datalayout);
return -EOPNOTSUPP;
}
chunksize = 1ULL << chunkbits;
count = DIV_ROUND_UP(inode->i_size, chunksize);
unit = sizeof(struct erofs_inode_chunk_index);
inode->extent_isize = count * unit;
idx = malloc(max(sizeof(*idx), sizeof(void *)));
if (!idx)
return -ENOMEM;
inode->chunkindexes = idx;
for (i = 0; i < count; i++) {
struct erofs_blobchunk *chunk;
struct erofs_map_blocks map = {
.index = UINT_MAX,
};
map.m_la = i << chunkbits;
ret = erofs_map_blocks(inode, &map, 0);
if (ret)
goto err;
blkaddr = erofs_blknr(dst_sb, map.m_pa);
chunk = erofs_get_unhashed_chunk(inode->dev, blkaddr, 0);
if (IS_ERR(chunk)) {
ret = PTR_ERR(chunk);
goto err;
}
*(void **)idx++ = chunk;
}
inode->datalayout = EROFS_INODE_CHUNK_BASED;
inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES;
inode->u.chunkformat |= chunkbits - dst_sb->blkszbits;
return 0;
err:
free(inode->chunkindexes);
inode->chunkindexes = NULL;
return ret;
}
static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb,
struct erofs_inode *inode,
enum erofs_rebuild_datamode datamode)
{
int err = 0;
switch (inode->i_mode & S_IFMT) {
case S_IFCHR:
if (erofs_inode_is_whiteout(inode))
inode->i_parent->whiteouts = true;
/* fallthrough */
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
inode->i_size = 0;
erofs_dbg("\tdev: %d %d", major(inode->u.i_rdev),
minor(inode->u.i_rdev));
inode->u.i_rdev = erofs_new_encode_dev(inode->u.i_rdev);
break;
case S_IFDIR:
err = erofs_init_empty_dir(inode);
break;
case S_IFLNK:
inode->i_link = malloc(inode->i_size + 1);
if (!inode->i_link)
return -ENOMEM;
err = erofs_pread(inode, inode->i_link, inode->i_size, 0);
erofs_dbg("\tsymlink: %s -> %s", inode->i_srcpath, inode->i_link);
break;
case S_IFREG:
if (!inode->i_size) {
inode->u.i_blkaddr = NULL_ADDR;
break;
}
if (datamode == EROFS_REBUILD_DATA_BLOB_INDEX)
err = erofs_rebuild_write_blob_index(dst_sb, inode);
else if (datamode == EROFS_REBUILD_DATA_RESVSP)
inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP;
else
err = -EOPNOTSUPP;
break;
default:
return -EINVAL;
}
return err;
}
/*
* @mergedir: parent directory in the merged tree
* @ctx.dir: parent directory when itering erofs_iterate_dir()
* @datamode: indicate how to import inode data
*/
struct erofs_rebuild_dir_context {
struct erofs_dir_context ctx;
struct erofs_inode *mergedir;
enum erofs_rebuild_datamode datamode;
};
static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx)
{
struct erofs_rebuild_dir_context *rctx = (void *)ctx;
struct erofs_inode *mergedir = rctx->mergedir;
struct erofs_inode *dir = ctx->dir;
struct erofs_inode *inode, *candidate;
struct erofs_inode src;
struct erofs_dentry *d;
char *path, *dname;
bool dumb;
int ret;
if (ctx->dot_dotdot)
return 0;
ret = asprintf(&path, "%s/%.*s", rctx->mergedir->i_srcpath,
ctx->de_namelen, ctx->dname);
if (ret < 0)
return ret;
erofs_dbg("parsing %s", path);
dname = path + strlen(mergedir->i_srcpath) + 1;
d = erofs_rebuild_get_dentry(mergedir, dname, false,
&dumb, &dumb, false);
if (IS_ERR(d)) {
ret = PTR_ERR(d);
goto out;
}
ret = 0;
if (d->type != EROFS_FT_UNKNOWN) {
/*
* bail out if the file exists in the upper layers. (Note that
* extended attributes won't be merged too even for dirs.)
*/
if (!S_ISDIR(d->inode->i_mode) || d->inode->opaque)
goto out;
/* merge directory entries */
src = (struct erofs_inode) {
.sbi = dir->sbi,
.nid = ctx->de_nid
};
ret = erofs_read_inode_from_disk(&src);
if (ret || !S_ISDIR(src.i_mode))
goto out;
mergedir = d->inode;
inode = dir = &src;
} else {
u64 nid;
DBG_BUGON(mergedir != d->inode);
inode = erofs_new_inode(dir->sbi);
if (IS_ERR(inode)) {
ret = PTR_ERR(inode);
goto out;
}
/* reuse i_ino[0] to read nid in source fs */
nid = inode->i_ino[0];
inode->sbi = dir->sbi;
inode->nid = ctx->de_nid;
ret = erofs_read_inode_from_disk(inode);
if (ret)
goto out;
/* restore nid in new generated fs */
inode->i_ino[1] = inode->i_ino[0];
inode->i_ino[0] = nid;
inode->dev = inode->sbi->dev;
if (S_ISREG(inode->i_mode) && inode->i_nlink > 1 &&
(candidate = erofs_iget(inode->dev, ctx->de_nid))) {
/* hardlink file */
erofs_iput(inode);
inode = candidate;
if (S_ISDIR(inode->i_mode)) {
erofs_err("hardlink directory not supported");
ret = -EISDIR;
goto out;
}
inode->i_nlink++;
erofs_dbg("\thardlink: %s -> %s", path, inode->i_srcpath);
} else {
ret = erofs_read_xattrs_from_disk(inode);
if (ret) {
erofs_iput(inode);
goto out;
}
inode->i_parent = d->inode;
inode->i_srcpath = path;
path = NULL;
inode->i_ino[1] = inode->nid;
inode->i_nlink = 1;
ret = erofs_rebuild_update_inode(&g_sbi, inode,
rctx->datamode);
if (ret) {
erofs_iput(inode);
goto out;
}
erofs_insert_ihash(inode);
mergedir = dir = inode;
}
d->inode = inode;
d->type = erofs_mode_to_ftype(inode->i_mode);
}
if (S_ISDIR(inode->i_mode)) {
struct erofs_rebuild_dir_context nctx = *rctx;
nctx.mergedir = mergedir;
nctx.ctx.dir = dir;
ret = erofs_iterate_dir(&nctx.ctx, false);
if (ret)
goto out;
}
/* reset sbi, nid after subdirs are all loaded for the final dump */
inode->sbi = &g_sbi;
inode->nid = 0;
out:
free(path);
return ret;
}
int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
enum erofs_rebuild_datamode mode)
{
struct erofs_inode inode = {};
struct erofs_rebuild_dir_context ctx;
char uuid_str[37];
char *fsid = sbi->devname;
int ret;
if (!fsid) {
erofs_uuid_unparse_lower(sbi->uuid, uuid_str);
fsid = uuid_str;
}
ret = erofs_read_superblock(sbi);
if (ret) {
erofs_err("failed to read superblock of %s", fsid);
return ret;
}
inode.nid = sbi->root_nid;
inode.sbi = sbi;
ret = erofs_read_inode_from_disk(&inode);
if (ret) {
erofs_err("failed to read root inode of %s", fsid);
return ret;
}
inode.i_srcpath = strdup("/");
ctx = (struct erofs_rebuild_dir_context) {
.ctx.dir = &inode,
.ctx.cb = erofs_rebuild_dirent_iter,
.mergedir = root,
.datamode = mode,
};
ret = erofs_iterate_dir(&ctx.ctx, false);
free(inode.i_srcpath);
return ret;
}
static int erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context *ctx)
{
struct erofs_rebuild_dir_context *rctx = (void *)ctx;
struct erofs_inode *dir = ctx->dir;
struct erofs_inode *mergedir = rctx->mergedir;
struct erofs_dentry *d;
char *dname;
bool dumb;
int ret;
if (ctx->dot_dotdot)
return 0;
dname = strndup(ctx->dname, ctx->de_namelen);
if (!dname)
return -ENOMEM;
d = erofs_rebuild_get_dentry(mergedir, dname, false,
&dumb, &dumb, false);
if (IS_ERR(d)) {
ret = PTR_ERR(d);
goto out;
}
if (d->type == EROFS_FT_UNKNOWN) {
d->nid = ctx->de_nid;
d->type = ctx->de_ftype;
d->validnid = true;
if (!mergedir->whiteouts && erofs_dentry_is_wht(dir->sbi, d))
mergedir->whiteouts = true;
} else {
struct erofs_inode *inode = d->inode;
/* update sub-directories only for recursively loading */
if (S_ISDIR(inode->i_mode)) {
list_del(&inode->i_hash);
inode->dev = dir->sbi->dev;
inode->i_ino[1] = ctx->de_nid;
erofs_insert_ihash(inode);
}
}
ret = 0;
out:
free(dname);
return ret;
}
int erofs_rebuild_load_basedir(struct erofs_inode *dir)
{
struct erofs_inode fakeinode = {
.sbi = dir->sbi,
.nid = dir->i_ino[1],
};
struct erofs_rebuild_dir_context ctx;
int ret;
ret = erofs_read_inode_from_disk(&fakeinode);
if (ret) {
erofs_err("failed to read inode @ %llu", fakeinode.nid);
return ret;
}
/* Inherit the maximum xattr size for the root directory */
if (__erofs_unlikely(IS_ROOT(dir)))
dir->xattr_isize = fakeinode.xattr_isize;
ctx = (struct erofs_rebuild_dir_context) {
.ctx.dir = &fakeinode,
.ctx.cb = erofs_rebuild_basedir_dirent_iter,
.mergedir = dir,
};
return erofs_iterate_dir(&ctx.ctx, false);
}