1 /*
2  * Copyright (C) 2018-2022 Intel Corporation.
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 
6 #define _LARGEFILE64_SOURCE
7 #include <stdio.h>
8 #include <fcntl.h>
9 #include <linux/loop.h>
10 #include <sys/stat.h>
11 #include <sys/ioctl.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <utime.h>
17 #include <string.h>
18 #include <blkid/blkid.h>
19 #include <ext2fs/ext2fs.h>
20 #include "fsutils.h"
21 #include "log_sys.h"
22 
23 #define DEV_LOOP_CTL "/dev/loop-control"
24 
25 struct walking_inode_data {
26 	const char *current_out_native_dirpath;
27 	int dumped_count;
28 };
29 
get_par_startaddr_from_img(const char * img,const char * target_parname,unsigned long long * start)30 static int get_par_startaddr_from_img(const char *img,
31 					const char *target_parname,
32 					unsigned long long *start)
33 {
34 	blkid_probe pr;
35 	blkid_partlist ls;
36 	blkid_partition par;
37 	int i;
38 	int nparts;
39 	const char *par_name;
40 	unsigned int sector_size;
41 	unsigned long long par_start;
42 
43 	if (!img || !target_parname || !start)
44 		return -1;
45 
46 	pr = blkid_new_probe_from_filename(img);
47 	if (!pr) {
48 		LOGE("blkid new probe failed\n");
49 		return -1;
50 	}
51 	ls = blkid_probe_get_partitions(pr);
52 	if (!ls) {
53 		LOGE("blkid get partitions failed\n");
54 		goto err;
55 	}
56 	nparts = blkid_partlist_numof_partitions(ls);
57 	if (nparts <= 0) {
58 		LOGE("(%d) partitions in (%s)??\n", nparts, img);
59 		goto err;
60 	}
61 
62 	for (i = 0; i < nparts; i++) {
63 		par = blkid_partlist_get_partition(ls, i);
64 		par_name = blkid_partition_get_name(par);
65 		if (!par_name) {
66 			LOGW("A partition in (%s) don't have name??\n", img);
67 			continue;
68 		}
69 		if (!strcmp(par_name, target_parname))
70 			goto found;
71 	}
72 	LOGE("no partition of (%s) is named %s\n", img, target_parname);
73 err:
74 	blkid_free_probe(pr);
75 	return -1;
76 found:
77 	sector_size = blkid_probe_get_sectorsize(pr);
78 	par_start = (unsigned long long)blkid_partition_get_start(par);
79 	*start = par_start * sector_size;
80 	blkid_free_probe(pr);
81 	return 0;
82 }
83 
loopdev_num_get_free(void)84 int loopdev_num_get_free(void)
85 {
86 	int loopctlfd;
87 	int devnr;
88 
89 	loopctlfd = open(DEV_LOOP_CTL, O_RDONLY);
90 	if (loopctlfd == -1) {
91 		LOGE("failed to open %s, error (%s)\n", DEV_LOOP_CTL,
92 		     strerror(errno));
93 		return -errno;
94 	}
95 
96 	devnr = ioctl(loopctlfd, LOOP_CTL_GET_FREE);
97 	if (devnr == -1) {
98 		LOGE("failed to get free loopdev, error (%s)\n",
99 		     strerror(errno));
100 		close(loopctlfd);
101 		return -errno;
102 	}
103 
104 	close(loopctlfd);
105 	return devnr;
106 }
107 
loopdev_set_status(const char * loopdev,const struct loop_info64 * info)108 static int loopdev_set_status(const char *loopdev,
109 				const struct loop_info64 *info)
110 {
111 	int loopfd;
112 	int res;
113 
114 	if (!loopdev || !info)
115 		return -EINVAL;
116 
117 	loopfd = open(loopdev, O_RDWR);
118 	if (loopfd == -1) {
119 		LOGE("failed to open (%s), error(%s)\n", loopdev,
120 		     strerror(errno));
121 		return -errno;
122 	}
123 
124 	res = ioctl(loopfd, LOOP_SET_STATUS64, info);
125 	if (res == -1) {
126 		LOGE("failed to set info to (%s), error(%s)\n", loopdev,
127 		     strerror(errno));
128 		close(loopfd);
129 		return -errno;
130 	}
131 
132 	close(loopfd);
133 	return 0;
134 }
135 
loopdev_set_img(const char * loopdev,const char * img_path)136 static int loopdev_set_img(const char *loopdev, const char *img_path)
137 {
138 	int loopfd;
139 	int imgfd;
140 	int res;
141 
142 	if (!loopdev || !img_path)
143 		return -EINVAL;
144 
145 	loopfd = open(loopdev, O_WRONLY);
146 	if (loopfd == -1) {
147 		LOGE("failed to open %s, error (%s)\n", loopdev,
148 		     strerror(errno));
149 		return -errno;
150 	}
151 
152 	imgfd = open(img_path, O_RDONLY);
153 	if (imgfd == -1) {
154 		LOGE("failed to open %s, error (%s)\n", img_path,
155 		     strerror(errno));
156 		close(loopfd);
157 		return -errno;
158 	}
159 
160 	res = ioctl(loopfd, LOOP_SET_FD, imgfd);
161 	if (res == -1) {
162 		LOGE("failed to set (%s) to (%s), error (%s)\n", img_path,
163 		     loopdev, strerror(errno));
164 		close(loopfd);
165 		close(imgfd);
166 		return -errno;
167 	}
168 
169 	close(loopfd);
170 	close(imgfd);
171 	return 0;
172 }
173 
loopdev_set_img_par(const char * loopdev,const char * img_path,const char * parname)174 int loopdev_set_img_par(const char *loopdev, const char *img_path,
175 			const char *parname)
176 {
177 	struct loop_info64 info;
178 	unsigned long long par_start;
179 	int res;
180 
181 	if (!loopdev || !img_path || !parname)
182 		return -1;
183 
184 	res = get_par_startaddr_from_img(img_path, parname, &par_start);
185 	if (res == -1) {
186 		LOGE("failed to get data par startaddr\n");
187 		return -1;
188 	}
189 
190 	res = loopdev_set_img(loopdev, img_path);
191 	if (res) {
192 		LOGE("failed to set img (%s) to (%s), error (%s)\n",
193 		       img_path, loopdev, strerror(-res));
194 		return -1;
195 	}
196 
197 	memset(&info, 0, sizeof(info));
198 	info.lo_offset = par_start;
199 
200 	res = loopdev_set_status(loopdev, &info);
201 	if (res < 0) {
202 		LOGE("failed to set loopdev, error (%s)\n", strerror(-res));
203 		return -1;
204 	}
205 	return 0;
206 }
207 
loopdev_check_parname(const char * loopdev,const char * parname)208 int loopdev_check_parname(const char *loopdev, const char *parname)
209 {
210 	struct ext2_super_block super;
211 	int fd;
212 	const int skiprate = 512;
213 	loff_t sk = 0;
214 
215 	if (!loopdev || !parname)
216 		return -ENOENT;
217 
218 	/* quickly find super block */
219 	fd = open(loopdev, O_RDONLY);
220 	if (fd == -1) {
221 		LOGE("failed to open (%s), error(%s)\n", loopdev,
222 		     strerror(errno));
223 		return -errno;
224 	}
225 	for (; lseek64(fd, sk, SEEK_SET) != -1 &&
226 	       read(fd, &super, 512) == 512; sk += skiprate) {
227 		if (super.s_magic != EXT2_SUPER_MAGIC)
228 			continue;
229 
230 		LOGD("find super block at +%ld\n", sk);
231 		/* only look into the primary super block */
232 		if (super.s_volume_name[0]) {
233 			close(fd);
234 			return !strncmp((const char *)super.s_volume_name, parname, EXT2_LABEL_LEN);
235 		}
236 		break;
237 	}
238 
239 	close(fd);
240 	return 0;
241 }
242 
243 /**
244  * Align the file's perms, only print WARNING if errors occurred in this
245  * function.
246  *
247  * Note: Drop user and group.
248  */
align_props(int fd,const char * name,const struct ext2_inode * inode)249 static void align_props(int fd, const char *name,
250 			const struct ext2_inode *inode)
251 {
252 	int res;
253 	struct utimbuf ut;
254 
255 	if (!inode || !name)
256 		return;
257 
258 	if (fd >= 0)
259 		res = fchmod(fd, inode->i_mode);
260 	else
261 		res = chmod(name, inode->i_mode);
262 
263 	if (res == -1)
264 		LOGW("failed to exec (xchmod), error (%s)\n", strerror(errno));
265 
266 	ut.actime = inode->i_atime;
267 	ut.modtime = inode->i_mtime;
268 	res = utime(name, &ut);
269 	if (res == -1)
270 		LOGW("failed to exec (utime), error (%s)\n", strerror(errno));
271 }
272 
e2fs_get_inodenum_by_fpath(ext2_filsys fs,const char * fpath,ext2_ino_t * out_ino)273 static int e2fs_get_inodenum_by_fpath(ext2_filsys fs, const char *fpath,
274 					ext2_ino_t *out_ino)
275 {
276 	ext2_ino_t root;
277 	ext2_ino_t cwd;
278 	errcode_t res;
279 
280 	if (!fs || !fpath || !out_ino)
281 		return -1;
282 
283 	root = EXT2_ROOT_INO;
284 	cwd = EXT2_ROOT_INO;
285 
286 	res = ext2fs_namei(fs, root, cwd, fpath, out_ino);
287 	if (res) {
288 		LOGE("ext2fs failed to get ino, path (%s), error (%s)\n",
289 		       fpath, error_message(res));
290 		return -1;
291 	}
292 
293 	return 0;
294 }
295 
e2fs_read_inode_by_inodenum(ext2_filsys fs,ext2_ino_t ino,struct ext2_inode * inode)296 static int e2fs_read_inode_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
297 					struct ext2_inode *inode)
298 {
299 	errcode_t res;
300 
301 	if (!fs || !ino || !inode)
302 		return -1;
303 
304 	res = ext2fs_read_inode(fs, ino, inode);
305 	if (res) {
306 		LOGE("ext2fs failed to get inode, ino (%d), error (%s)\n",
307 		     ino, error_message(res));
308 		return -1;
309 	}
310 
311 	return 0;
312 }
313 
e2fs_dump_file_by_inodenum(ext2_filsys fs,ext2_ino_t ino,const char * out_fp)314 static int e2fs_dump_file_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
315 					const char *out_fp)
316 {
317 	errcode_t res;
318 	int ret = 0;
319 	int fd;
320 	unsigned int got;
321 	struct ext2_inode inode;
322 	ext2_file_t e2_file;
323 	char *buf;
324 	ssize_t write_b;
325 
326 	if (!fs || !ino || !out_fp)
327 		return -1;
328 
329 	res = e2fs_read_inode_by_inodenum(fs, ino, &inode);
330 	if (res) {
331 		LOGE("ext2fs failed to read inode, error (%s)\n",
332 		     error_message(res));
333 		return -1;
334 	}
335 
336 	fd = open(out_fp, O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE, 0666);
337 	if (fd == -1) {
338 		LOGE("open (%s) failed, error (%s)\n", out_fp, strerror(errno));
339 		return -1;
340 	}
341 
342 	/* open with read only */
343 	res = ext2fs_file_open2(fs, ino, &inode, 0, &e2_file);
344 	if (res) {
345 		LOGE("ext2fs failed to open file, ino (%d), error (%s)\n",
346 		       ino, error_message(res));
347 		close(fd);
348 		return -1;
349 	}
350 
351 	res = ext2fs_get_mem(fs->blocksize, &buf);
352 	if (res) {
353 		LOGE("ext2fs failed to get mem, error (%s)\n",
354 		     error_message(res));
355 		close(fd);
356 		ext2fs_file_close(e2_file);
357 		return -1;
358 	}
359 
360 	while (1) {
361 		res = ext2fs_file_read(e2_file, buf, fs->blocksize, &got);
362 		/* got equals zero in failed case */
363 		if (res) {
364 			LOGE("ext2fs failed to read (%u), error (%s)\n",
365 			     ino, error_message(res));
366 			ret = -1;
367 		}
368 		if (!got)
369 			break;
370 
371 		write_b = write(fd, buf, got);
372 		if ((unsigned int)write_b != got) {
373 			LOGE("failed to write file (%s), error (%s)\n",
374 			     out_fp, strerror(errno));
375 			ret = -1;
376 			break;
377 		}
378 	}
379 	align_props(fd, out_fp, &inode);
380 	if (buf)
381 		ext2fs_free_mem(&buf);
382 	/* ext2fs_file_close only failed in flush process */
383 	ext2fs_file_close(e2_file);
384 	close(fd);
385 
386 	return ret;
387 }
388 
e2fs_dump_file_by_fpath(ext2_filsys fs,const char * in_fp,const char * out_fp)389 int e2fs_dump_file_by_fpath(ext2_filsys fs, const char *in_fp,
390 			const char *out_fp)
391 {
392 	int res;
393 	ext2_ino_t ino;
394 
395 	if (!fs || !in_fp || !out_fp)
396 		return -1;
397 
398 	res = e2fs_get_inodenum_by_fpath(fs, in_fp, &ino);
399 	if (res)
400 		return res;
401 
402 	return e2fs_dump_file_by_inodenum(fs, ino, out_fp);
403 }
404 
e2fs_read_file_by_inodenum(ext2_filsys fs,ext2_ino_t ino,void ** out_data,unsigned long * size)405 static int e2fs_read_file_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
406 					void **out_data, unsigned long *size)
407 {
408 	errcode_t res;
409 	unsigned int got;
410 	struct ext2_inode inode;
411 	ext2_file_t e2_file;
412 	__u64 _size;
413 	char *buf;
414 
415 	if (!fs || !ino || !out_data || !size)
416 		return -1;
417 
418 	res = e2fs_read_inode_by_inodenum(fs, ino, &inode);
419 	if (res) {
420 		LOGE("ext2fs failed to read inode, error (%s)\n",
421 		     error_message(res));
422 		return -1;
423 	}
424 
425 	_size = EXT2_I_SIZE(&inode);
426 	if (!_size) {
427 		LOGW("try to read a empty file\n");
428 		*size = 0;
429 		*out_data = 0;
430 		return 0;
431 	}
432 
433 	/* open with read only */
434 	res = ext2fs_file_open2(fs, ino, &inode, 0, &e2_file);
435 	if (res) {
436 		LOGE("ext2fs failed to open file, ino (%d), error (%s)\n",
437 		       ino, error_message(res));
438 		return -1;
439 	}
440 
441 	res = ext2fs_get_mem(_size + 1, &buf);
442 	if (res) {
443 		LOGE("ext2fs failed to get mem, error (%s)\n",
444 		     error_message(res));
445 		ext2fs_file_close(e2_file);
446 		return -1;
447 	}
448 
449 	res = ext2fs_file_read(e2_file, buf, _size, &got);
450 	/* got equals zero in failed case */
451 	if (res) {
452 		LOGE("ext2fs failed to read (%u), error (%s)\n",
453 		     ino, error_message(res));
454 		goto err;
455 	}
456 
457 	/* ext2fs_file_close only failed in flush process */
458 	ext2fs_file_close(e2_file);
459 
460 	*size = _size;
461 	buf[_size] = 0;
462 	*out_data = buf;
463 
464 	return 0;
465 err:
466 	free(buf);
467 	ext2fs_file_close(e2_file);
468 	return -1;
469 }
470 
e2fs_read_file_by_fpath(ext2_filsys fs,const char * in_fp,void ** out_data,unsigned long * size)471 int e2fs_read_file_by_fpath(ext2_filsys fs, const char *in_fp,
472 			 void **out_data, unsigned long *size)
473 {
474 	int res;
475 	ext2_ino_t ino;
476 
477 	if (!fs || !in_fp || !out_data || !size)
478 		return -1;
479 
480 	res = e2fs_get_inodenum_by_fpath(fs, in_fp, &ino);
481 	if (res)
482 		return res;
483 
484 	return e2fs_read_file_by_inodenum(fs, ino, out_data, size);
485 }
486 
487 static int dump_inode_recursively_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
488 						struct walking_inode_data *data,
489 						const char *fname);
callback_for_subentries(struct ext2_dir_entry * dirent,int offset EXT2FS_ATTR ((unused)),int blocksize EXT2FS_ATTR ((unused)),char * buf EXT2FS_ATTR ((unused)),void * private)490 static int callback_for_subentries(struct ext2_dir_entry *dirent,
491 				int offset EXT2FS_ATTR((unused)),
492 				int blocksize EXT2FS_ATTR((unused)),
493 				char *buf EXT2FS_ATTR((unused)),
494 				void *private)
495 {
496 	char fname[EXT2_NAME_LEN + 1];
497 	struct walking_inode_data *data = private;
498 	int len;
499 
500 	len = dirent->name_len & 0xFF; /* EXT2_NAME_LEN = 255 */
501 	strncpy(fname, dirent->name, len);
502 	fname[len] = 0;
503 
504 	return dump_inode_recursively_by_inodenum(NULL, dirent->inode,
505 						  data, fname);
506 }
507 
dump_inode_recursively_by_inodenum(ext2_filsys fs,ext2_ino_t ino,struct walking_inode_data * data,const char * fname)508 static int dump_inode_recursively_by_inodenum(ext2_filsys fs, ext2_ino_t ino,
509 						struct walking_inode_data *data,
510 						const char *fname)
511 {
512 	int res;
513 	char *out_fpath;
514 	errcode_t err;
515 	static ext2_filsys fs_for_dump;
516 	struct ext2_inode inode;
517 
518 	if (!ino || !data || !data->current_out_native_dirpath || !fname)
519 		goto abort;
520 
521 	/* caller is not callback_for_subentries */
522 	if (fs)
523 		fs_for_dump = fs;
524 
525 	if (!strcmp(fname, ".") || !strcmp(fname, ".."))
526 		return 0;
527 
528 	res = asprintf(&out_fpath, "%s/%s", data->current_out_native_dirpath,
529 		       fname);
530 	if (res == -1) {
531 		LOGE("failed to construct target file name, ");
532 		goto abort;
533 	}
534 
535 	res = e2fs_read_inode_by_inodenum(fs_for_dump, ino, &inode);
536 	if (res) {
537 		LOGE("ext2fs failed to read inode, ");
538 		goto abort_free;
539 	}
540 
541 	if (LINUX_S_ISREG(inode.i_mode)) {
542 		/* do dump for file */
543 		res = e2fs_dump_file_by_inodenum(fs_for_dump, ino, out_fpath);
544 		if (res) {
545 			LOGE("ext2fs failed to dump file, ");
546 			goto abort_free;
547 		}
548 		data->dumped_count++;
549 	} else if (LINUX_S_ISDIR(inode.i_mode)) {
550 		/* mkdir for directory and dump the subentry */
551 		res = mkdir(out_fpath, 0700);
552 		if (res == -1 && errno != EEXIST) {
553 			LOGE("failed to mkdir (%s), error (%s), ", out_fpath,
554 			     strerror(errno));
555 			goto abort_free;
556 		}
557 		data->dumped_count++;
558 
559 		data->current_out_native_dirpath = out_fpath;
560 		err = ext2fs_dir_iterate(fs_for_dump, ino, 0, 0,
561 					 callback_for_subentries,
562 					 (void *)data);
563 		if (err) {
564 			LOGE("ext2fs failed to iterate dir, errno (%s), ",
565 			     error_message(err));
566 			goto abort_free;
567 		}
568 		align_props(-1, out_fpath, &inode);
569 	}
570 	/* else ignore the rest types, such as link, socket, fifo, ... */
571 
572 	free(out_fpath);
573 	return 0;
574 
575 abort_free:
576 	free(out_fpath);
577 abort:
578 	LOGE("dump dir aborted...\n");
579 	return DIRENT_ABORT;
580 }
581 
e2fs_dump_dir_by_dpath(ext2_filsys fs,const char * in_dp,const char * out_dp,int * count)582 int e2fs_dump_dir_by_dpath(ext2_filsys fs, const char *in_dp,
583 			const char *out_dp, int *count)
584 {
585 	ext2_ino_t ino;
586 	struct walking_inode_data dump_needed;
587 	const char *dname;
588 	int res;
589 
590 	if (!fs || !in_dp || !count)
591 		return -1;
592 
593 	*count = 0;
594 	if (!directory_exists(out_dp)) {
595 		LOGE("dir need dump into an existed dir\n");
596 		return -1;
597 	}
598 
599 	res = e2fs_get_inodenum_by_fpath(fs, in_dp, &ino);
600 	if (res == -1)
601 		return -1;
602 
603 	dname = strrchr(in_dp, '/');
604 	if (dname)
605 		dname++;
606 	else
607 		dname = in_dp;
608 
609 	dump_needed.dumped_count = 0;
610 	dump_needed.current_out_native_dirpath = out_dp;
611 	res = dump_inode_recursively_by_inodenum(fs, ino, &dump_needed, dname);
612 	*count = dump_needed.dumped_count;
613 	if (res) {
614 		LOGE("ext2fs failed to dump dir\n");
615 		return -1;
616 	}
617 
618 	return 0;
619 }
620 
e2fs_open(const char * dev,ext2_filsys * outfs)621 int e2fs_open(const char *dev, ext2_filsys *outfs)
622 {
623 	errcode_t res;
624 
625 	if (!dev || !outfs)
626 		return -1;
627 
628 	add_error_table(&et_ext2_error_table);
629 	res = ext2fs_open(dev, EXT2_FLAG_64BITS, 0, 0,
630 			  unix_io_manager, outfs);
631 	if (res) {
632 		LOGE("ext2fs fail to open (%s), error (%s)\n", dev,
633 		     error_message(res));
634 		return -1;
635 	}
636 
637 	return 0;
638 }
639 
e2fs_close(ext2_filsys fs)640 void e2fs_close(ext2_filsys fs)
641 {
642 	if (fs)
643 		ext2fs_close(fs);
644 	remove_error_table(&et_ext2_error_table);
645 }
646