xref: /aosp_15_r20/external/erofs-utils/lib/rebuild.c (revision 33b1fccf6a0fada2c2875d400ed01119b7676ee5)
1 // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
2 #define _GNU_SOURCE
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/stat.h>
7 #include <config.h>
8 #if defined(HAVE_SYS_SYSMACROS_H)
9 #include <sys/sysmacros.h>
10 #endif
11 #include "erofs/print.h"
12 #include "erofs/inode.h"
13 #include "erofs/rebuild.h"
14 #include "erofs/dir.h"
15 #include "erofs/xattr.h"
16 #include "erofs/blobchunk.h"
17 #include "erofs/internal.h"
18 #include "liberofs_uuid.h"
19 
20 #ifdef HAVE_LINUX_AUFS_TYPE_H
21 #include <linux/aufs_type.h>
22 #else
23 #define AUFS_WH_PFX		".wh."
24 #define AUFS_DIROPQ_NAME	AUFS_WH_PFX ".opq"
25 #define AUFS_WH_DIROPQ		AUFS_WH_PFX AUFS_DIROPQ_NAME
26 #endif
27 
erofs_rebuild_mkdir(struct erofs_inode * dir,const char * s)28 static struct erofs_dentry *erofs_rebuild_mkdir(struct erofs_inode *dir,
29 						const char *s)
30 {
31 	struct erofs_inode *inode;
32 	struct erofs_dentry *d;
33 
34 	inode = erofs_new_inode(dir->sbi);
35 	if (IS_ERR(inode))
36 		return ERR_CAST(inode);
37 
38 	if (asprintf(&inode->i_srcpath, "%s/%s",
39 		     dir->i_srcpath ? : "", s) < 0) {
40 		erofs_iput(inode);
41 		return ERR_PTR(-ENOMEM);
42 	}
43 	inode->i_mode = S_IFDIR | 0755;
44 	inode->i_parent = dir;
45 	inode->i_uid = getuid();
46 	inode->i_gid = getgid();
47 	inode->i_mtime = inode->sbi->build_time;
48 	inode->i_mtime_nsec = inode->sbi->build_time_nsec;
49 	erofs_init_empty_dir(inode);
50 
51 	d = erofs_d_alloc(dir, s);
52 	if (IS_ERR(d)) {
53 		erofs_iput(inode);
54 	} else {
55 		d->type = EROFS_FT_DIR;
56 		d->inode = inode;
57 	}
58 	return d;
59 }
60 
erofs_rebuild_get_dentry(struct erofs_inode * pwd,char * path,bool aufs,bool * whout,bool * opq,bool to_head)61 struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd,
62 		char *path, bool aufs, bool *whout, bool *opq, bool to_head)
63 {
64 	struct erofs_dentry *d = NULL;
65 	unsigned int len = strlen(path);
66 	char *s = path;
67 
68 	*whout = false;
69 	*opq = false;
70 
71 	while (s < path + len) {
72 		char *slash = memchr(s, '/', path + len - s);
73 
74 		if (slash) {
75 			if (s == slash) {
76 				while (*++s == '/');	/* skip '//...' */
77 				continue;
78 			}
79 			*slash = '\0';
80 		}
81 
82 		if (!memcmp(s, ".", 2)) {
83 			/* null */
84 		} else if (!memcmp(s, "..", 3)) {
85 			pwd = pwd->i_parent;
86 		} else {
87 			struct erofs_inode *inode = NULL;
88 
89 			if (aufs && !slash) {
90 				if (!memcmp(s, AUFS_WH_DIROPQ, sizeof(AUFS_WH_DIROPQ))) {
91 					*opq = true;
92 					break;
93 				}
94 				if (!memcmp(s, AUFS_WH_PFX, sizeof(AUFS_WH_PFX) - 1)) {
95 					s += sizeof(AUFS_WH_PFX) - 1;
96 					*whout = true;
97 				}
98 			}
99 
100 			list_for_each_entry(d, &pwd->i_subdirs, d_child) {
101 				if (!strcmp(d->name, s)) {
102 					if (d->type != EROFS_FT_DIR && slash)
103 						return ERR_PTR(-EIO);
104 					inode = d->inode;
105 					break;
106 				}
107 			}
108 
109 			if (inode) {
110 				if (to_head) {
111 					list_del(&d->d_child);
112 					list_add(&d->d_child, &pwd->i_subdirs);
113 				}
114 				pwd = inode;
115 			} else if (!slash) {
116 				d = erofs_d_alloc(pwd, s);
117 				if (IS_ERR(d))
118 					return d;
119 				d->type = EROFS_FT_UNKNOWN;
120 				d->inode = pwd;
121 			} else {
122 				d = erofs_rebuild_mkdir(pwd, s);
123 				if (IS_ERR(d))
124 					return d;
125 				pwd = d->inode;
126 			}
127 		}
128 		if (slash) {
129 			*slash = '/';
130 			s = slash + 1;
131 		} else {
132 			break;
133 		}
134 	}
135 	return d;
136 }
137 
erofs_rebuild_write_blob_index(struct erofs_sb_info * dst_sb,struct erofs_inode * inode)138 static int erofs_rebuild_write_blob_index(struct erofs_sb_info *dst_sb,
139 					  struct erofs_inode *inode)
140 {
141 	int ret;
142 	unsigned int count, unit, chunkbits, i;
143 	struct erofs_inode_chunk_index *idx;
144 	erofs_off_t chunksize;
145 	erofs_blk_t blkaddr;
146 
147 	/* TODO: fill data map in other layouts */
148 	if (inode->datalayout == EROFS_INODE_CHUNK_BASED) {
149 		chunkbits = inode->u.chunkbits;
150 		if (chunkbits < dst_sb->blkszbits) {
151 			erofs_err("%s: chunk size %u is smaller than the target block size %u",
152 				  inode->i_srcpath, 1U << chunkbits,
153 				  1U << dst_sb->blkszbits);
154 			return -EINVAL;
155 		}
156 	} else if (inode->datalayout == EROFS_INODE_FLAT_PLAIN) {
157 		chunkbits = ilog2(inode->i_size - 1) + 1;
158 		if (chunkbits < dst_sb->blkszbits)
159 			chunkbits = dst_sb->blkszbits;
160 		if (chunkbits - dst_sb->blkszbits > EROFS_CHUNK_FORMAT_BLKBITS_MASK)
161 			chunkbits = EROFS_CHUNK_FORMAT_BLKBITS_MASK + dst_sb->blkszbits;
162 	} else {
163 		erofs_err("%s: unsupported datalayout %d ", inode->i_srcpath,
164 			  inode->datalayout);
165 		return -EOPNOTSUPP;
166 	}
167 
168 	chunksize = 1ULL << chunkbits;
169 	count = DIV_ROUND_UP(inode->i_size, chunksize);
170 
171 	unit = sizeof(struct erofs_inode_chunk_index);
172 	inode->extent_isize = count * unit;
173 	idx = malloc(max(sizeof(*idx), sizeof(void *)));
174 	if (!idx)
175 		return -ENOMEM;
176 	inode->chunkindexes = idx;
177 
178 	for (i = 0; i < count; i++) {
179 		struct erofs_blobchunk *chunk;
180 		struct erofs_map_blocks map = {
181 			.index = UINT_MAX,
182 		};
183 
184 		map.m_la = i << chunkbits;
185 		ret = erofs_map_blocks(inode, &map, 0);
186 		if (ret)
187 			goto err;
188 
189 		blkaddr = erofs_blknr(dst_sb, map.m_pa);
190 		chunk = erofs_get_unhashed_chunk(inode->dev, blkaddr, 0);
191 		if (IS_ERR(chunk)) {
192 			ret = PTR_ERR(chunk);
193 			goto err;
194 		}
195 		*(void **)idx++ = chunk;
196 
197 	}
198 	inode->datalayout = EROFS_INODE_CHUNK_BASED;
199 	inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES;
200 	inode->u.chunkformat |= chunkbits - dst_sb->blkszbits;
201 	return 0;
202 err:
203 	free(inode->chunkindexes);
204 	inode->chunkindexes = NULL;
205 	return ret;
206 }
207 
erofs_rebuild_update_inode(struct erofs_sb_info * dst_sb,struct erofs_inode * inode,enum erofs_rebuild_datamode datamode)208 static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb,
209 				      struct erofs_inode *inode,
210 				      enum erofs_rebuild_datamode datamode)
211 {
212 	int err = 0;
213 
214 	switch (inode->i_mode & S_IFMT) {
215 	case S_IFCHR:
216 		if (erofs_inode_is_whiteout(inode))
217 			inode->i_parent->whiteouts = true;
218 		/* fallthrough */
219 	case S_IFBLK:
220 	case S_IFIFO:
221 	case S_IFSOCK:
222 		inode->i_size = 0;
223 		erofs_dbg("\tdev: %d %d", major(inode->u.i_rdev),
224 			  minor(inode->u.i_rdev));
225 		inode->u.i_rdev = erofs_new_encode_dev(inode->u.i_rdev);
226 		break;
227 	case S_IFDIR:
228 		err = erofs_init_empty_dir(inode);
229 		break;
230 	case S_IFLNK:
231 		inode->i_link = malloc(inode->i_size + 1);
232 		if (!inode->i_link)
233 			return -ENOMEM;
234 		err = erofs_pread(inode, inode->i_link, inode->i_size, 0);
235 		erofs_dbg("\tsymlink: %s -> %s", inode->i_srcpath, inode->i_link);
236 		break;
237 	case S_IFREG:
238 		if (!inode->i_size) {
239 			inode->u.i_blkaddr = NULL_ADDR;
240 			break;
241 		}
242 		if (datamode == EROFS_REBUILD_DATA_BLOB_INDEX)
243 			err = erofs_rebuild_write_blob_index(dst_sb, inode);
244 		else if (datamode == EROFS_REBUILD_DATA_RESVSP)
245 			inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP;
246 		else
247 			err = -EOPNOTSUPP;
248 		break;
249 	default:
250 		return -EINVAL;
251 	}
252 	return err;
253 }
254 
255 /*
256  * @mergedir: parent directory in the merged tree
257  * @ctx.dir:  parent directory when itering erofs_iterate_dir()
258  * @datamode: indicate how to import inode data
259  */
260 struct erofs_rebuild_dir_context {
261 	struct erofs_dir_context ctx;
262 	struct erofs_inode *mergedir;
263 	enum erofs_rebuild_datamode datamode;
264 };
265 
erofs_rebuild_dirent_iter(struct erofs_dir_context * ctx)266 static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx)
267 {
268 	struct erofs_rebuild_dir_context *rctx = (void *)ctx;
269 	struct erofs_inode *mergedir = rctx->mergedir;
270 	struct erofs_inode *dir = ctx->dir;
271 	struct erofs_inode *inode, *candidate;
272 	struct erofs_inode src;
273 	struct erofs_dentry *d;
274 	char *path, *dname;
275 	bool dumb;
276 	int ret;
277 
278 	if (ctx->dot_dotdot)
279 		return 0;
280 
281 	ret = asprintf(&path, "%s/%.*s", rctx->mergedir->i_srcpath,
282 		       ctx->de_namelen, ctx->dname);
283 	if (ret < 0)
284 		return ret;
285 
286 	erofs_dbg("parsing %s", path);
287 	dname = path + strlen(mergedir->i_srcpath) + 1;
288 
289 	d = erofs_rebuild_get_dentry(mergedir, dname, false,
290 				     &dumb, &dumb, false);
291 	if (IS_ERR(d)) {
292 		ret = PTR_ERR(d);
293 		goto out;
294 	}
295 
296 	ret = 0;
297 	if (d->type != EROFS_FT_UNKNOWN) {
298 		/*
299 		 * bail out if the file exists in the upper layers.  (Note that
300 		 * extended attributes won't be merged too even for dirs.)
301 		 */
302 		if (!S_ISDIR(d->inode->i_mode) || d->inode->opaque)
303 			goto out;
304 
305 		/* merge directory entries */
306 		src = (struct erofs_inode) {
307 			.sbi = dir->sbi,
308 			.nid = ctx->de_nid
309 		};
310 		ret = erofs_read_inode_from_disk(&src);
311 		if (ret || !S_ISDIR(src.i_mode))
312 			goto out;
313 		mergedir = d->inode;
314 		inode = dir = &src;
315 	} else {
316 		u64 nid;
317 
318 		DBG_BUGON(mergedir != d->inode);
319 		inode = erofs_new_inode(dir->sbi);
320 		if (IS_ERR(inode)) {
321 			ret = PTR_ERR(inode);
322 			goto out;
323 		}
324 
325 		/* reuse i_ino[0] to read nid in source fs */
326 		nid = inode->i_ino[0];
327 		inode->sbi = dir->sbi;
328 		inode->nid = ctx->de_nid;
329 		ret = erofs_read_inode_from_disk(inode);
330 		if (ret)
331 			goto out;
332 
333 		/* restore nid in new generated fs */
334 		inode->i_ino[1] = inode->i_ino[0];
335 		inode->i_ino[0] = nid;
336 		inode->dev = inode->sbi->dev;
337 
338 		if (S_ISREG(inode->i_mode) && inode->i_nlink > 1 &&
339 		    (candidate = erofs_iget(inode->dev, ctx->de_nid))) {
340 			/* hardlink file */
341 			erofs_iput(inode);
342 			inode = candidate;
343 			if (S_ISDIR(inode->i_mode)) {
344 				erofs_err("hardlink directory not supported");
345 				ret = -EISDIR;
346 				goto out;
347 			}
348 			inode->i_nlink++;
349 			erofs_dbg("\thardlink: %s -> %s", path, inode->i_srcpath);
350 		} else {
351 			ret = erofs_read_xattrs_from_disk(inode);
352 			if (ret) {
353 				erofs_iput(inode);
354 				goto out;
355 			}
356 
357 			inode->i_parent = d->inode;
358 			inode->i_srcpath = path;
359 			path = NULL;
360 			inode->i_ino[1] = inode->nid;
361 			inode->i_nlink = 1;
362 
363 			ret = erofs_rebuild_update_inode(&g_sbi, inode,
364 							 rctx->datamode);
365 			if (ret) {
366 				erofs_iput(inode);
367 				goto out;
368 			}
369 
370 			erofs_insert_ihash(inode);
371 			mergedir = dir = inode;
372 		}
373 
374 		d->inode = inode;
375 		d->type = erofs_mode_to_ftype(inode->i_mode);
376 	}
377 
378 	if (S_ISDIR(inode->i_mode)) {
379 		struct erofs_rebuild_dir_context nctx = *rctx;
380 
381 		nctx.mergedir = mergedir;
382 		nctx.ctx.dir = dir;
383 		ret = erofs_iterate_dir(&nctx.ctx, false);
384 		if (ret)
385 			goto out;
386 	}
387 
388 	/* reset sbi, nid after subdirs are all loaded for the final dump */
389 	inode->sbi = &g_sbi;
390 	inode->nid = 0;
391 out:
392 	free(path);
393 	return ret;
394 }
395 
erofs_rebuild_load_tree(struct erofs_inode * root,struct erofs_sb_info * sbi,enum erofs_rebuild_datamode mode)396 int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
397 			    enum erofs_rebuild_datamode mode)
398 {
399 	struct erofs_inode inode = {};
400 	struct erofs_rebuild_dir_context ctx;
401 	char uuid_str[37];
402 	char *fsid = sbi->devname;
403 	int ret;
404 
405 	if (!fsid) {
406 		erofs_uuid_unparse_lower(sbi->uuid, uuid_str);
407 		fsid = uuid_str;
408 	}
409 	ret = erofs_read_superblock(sbi);
410 	if (ret) {
411 		erofs_err("failed to read superblock of %s", fsid);
412 		return ret;
413 	}
414 
415 	inode.nid = sbi->root_nid;
416 	inode.sbi = sbi;
417 	ret = erofs_read_inode_from_disk(&inode);
418 	if (ret) {
419 		erofs_err("failed to read root inode of %s", fsid);
420 		return ret;
421 	}
422 	inode.i_srcpath = strdup("/");
423 
424 	ctx = (struct erofs_rebuild_dir_context) {
425 		.ctx.dir = &inode,
426 		.ctx.cb = erofs_rebuild_dirent_iter,
427 		.mergedir = root,
428 		.datamode = mode,
429 	};
430 	ret = erofs_iterate_dir(&ctx.ctx, false);
431 	free(inode.i_srcpath);
432 	return ret;
433 }
434 
erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context * ctx)435 static int erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context *ctx)
436 {
437 	struct erofs_rebuild_dir_context *rctx = (void *)ctx;
438 	struct erofs_inode *dir = ctx->dir;
439 	struct erofs_inode *mergedir = rctx->mergedir;
440 	struct erofs_dentry *d;
441 	char *dname;
442 	bool dumb;
443 	int ret;
444 
445 	if (ctx->dot_dotdot)
446 		return 0;
447 
448 	dname = strndup(ctx->dname, ctx->de_namelen);
449 	if (!dname)
450 		return -ENOMEM;
451 	d = erofs_rebuild_get_dentry(mergedir, dname, false,
452 				     &dumb, &dumb, false);
453 	if (IS_ERR(d)) {
454 		ret = PTR_ERR(d);
455 		goto out;
456 	}
457 
458 	if (d->type == EROFS_FT_UNKNOWN) {
459 		d->nid = ctx->de_nid;
460 		d->type = ctx->de_ftype;
461 		d->validnid = true;
462 		if (!mergedir->whiteouts && erofs_dentry_is_wht(dir->sbi, d))
463 			mergedir->whiteouts = true;
464 	} else {
465 		struct erofs_inode *inode = d->inode;
466 
467 		/* update sub-directories only for recursively loading */
468 		if (S_ISDIR(inode->i_mode)) {
469 			list_del(&inode->i_hash);
470 			inode->dev = dir->sbi->dev;
471 			inode->i_ino[1] = ctx->de_nid;
472 			erofs_insert_ihash(inode);
473 		}
474 	}
475 	ret = 0;
476 out:
477 	free(dname);
478 	return ret;
479 }
480 
erofs_rebuild_load_basedir(struct erofs_inode * dir)481 int erofs_rebuild_load_basedir(struct erofs_inode *dir)
482 {
483 	struct erofs_inode fakeinode = {
484 		.sbi = dir->sbi,
485 		.nid = dir->i_ino[1],
486 	};
487 	struct erofs_rebuild_dir_context ctx;
488 	int ret;
489 
490 	ret = erofs_read_inode_from_disk(&fakeinode);
491 	if (ret) {
492 		erofs_err("failed to read inode @ %llu", fakeinode.nid);
493 		return ret;
494 	}
495 
496 	/* Inherit the maximum xattr size for the root directory */
497 	if (__erofs_unlikely(IS_ROOT(dir)))
498 		dir->xattr_isize = fakeinode.xattr_isize;
499 
500 	ctx = (struct erofs_rebuild_dir_context) {
501 		.ctx.dir = &fakeinode,
502 		.ctx.cb = erofs_rebuild_basedir_dirent_iter,
503 		.mergedir = dir,
504 	};
505 	return erofs_iterate_dir(&ctx.ctx, false);
506 }
507