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