1 /* 2 * Access a filesystem. 3 * 4 * Copyright (C) 2019, 2022 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <sys/types.h> 28 #include <sys/stat.h> 29 #include <sys/sysmacros.h> /* major, minor */ 30 31 #include <ext2fs/ext2fs.h> 32 33 #include "file.h" 34 #include "format.h" 35 #include "image.h" 36 #include "input.h" 37 #include "path.h" 38 #include "utils.h" 39 40 41 42 /* Copy buffer size. */ 43 44 const int BUFSIZE = 4096; 45 46 /* Maximum number of arguments in scripts. */ 47 48 const int MAX_ARGS = 32; 49 50 51 52 /* Alternative metadata set by options. */ 53 54 struct metadata 55 { 56 uid_t uid; 57 gid_t gid; 58 mode_t mask; 59 int have_uid, have_gid; 60 }; 61 62 struct metadata md; 63 64 /* Parse program options. */ 65 66 static int parse_options(int argc, char *argv[]) 67 { 68 int opt; 69 70 md.have_uid = 0; 71 md.have_gid = 0; 72 md.mask = 0000; 73 74 while ((opt = getopt(argc, argv, "g:m:u:")) != -1) 75 { 76 switch (opt) 77 { 78 case 'g': 79 md.gid = atoi(optarg); 80 md.have_gid = 1; 81 break; 82 83 case 'm': 84 md.mask = strtol(optarg, NULL, 0); 85 break; 86 87 case 'u': 88 md.uid = atoi(optarg); 89 md.have_uid = 1; 90 break; 91 92 default: 93 fprintf(stderr, "Option not recognised: %s\n", argv[optind]); 94 return -1; 95 } 96 } 97 98 return 0; 99 } 100 101 102 103 /* Copy a file into the filesystem image. */ 104 105 int copy_file_in(const char *filename, ext2_filsys fs, ext2_ino_t ino_file, int flags) 106 { 107 int retval = 0; 108 ext2_file_t file; 109 110 /* Copying details. */ 111 112 FILE *fp; 113 char buf[BUFSIZE]; 114 size_t got; 115 unsigned int written; 116 117 /* Open a file in the target directory. */ 118 119 if (ext2fs_file_open(fs, ino_file, flags, &file)) 120 return 1; 121 122 /* Open the file in the source directory. */ 123 124 fp = fopen(filename, "r"); 125 126 /* Copy the file content. */ 127 128 if (fp != NULL) 129 { 130 while (got = fread(buf, sizeof(char), BUFSIZE, fp)) 131 { 132 while (got) 133 { 134 if (ext2fs_file_write(file, buf, got, &written)) 135 { 136 retval = 1; 137 goto close_files; 138 } 139 got -= written; 140 } 141 } 142 } 143 144 close_files: 145 fclose(fp); 146 ext2fs_file_flush(file); 147 ext2fs_file_close(file); 148 149 return retval; 150 } 151 152 /* Copy a file out of the filesystem image. */ 153 154 int copy_file_out(const char *target, const char *filename, int target_is_file, 155 ext2_filsys fs, ext2_ino_t ino_file) 156 { 157 int retval = 0; 158 ext2_file_t file; 159 160 /* Copying details. */ 161 162 FILE *fp; 163 char buf[BUFSIZE]; 164 unsigned int got; 165 size_t written; 166 167 /* Open the file in the source directory. */ 168 169 if (ext2fs_file_open(fs, ino_file, 0, &file)) 170 return 1; 171 172 /* Open a file in the target directory. */ 173 174 if (target_is_file) 175 fp = fopen(target, "w"); 176 else 177 fp = open_file_in_dir(target, path_basename(filename), "w"); 178 179 /* Copy the file content. */ 180 181 if (fp != NULL) 182 { 183 do 184 { 185 if (ext2fs_file_read(file, buf, BUFSIZE, &got)) 186 { 187 retval = 1; 188 goto close_files; 189 } 190 191 while (got) 192 { 193 written = fwrite(buf, sizeof(char), got, fp); 194 got -= written; 195 } 196 197 } while (got); 198 } 199 200 close_files: 201 fclose(fp); 202 ext2fs_file_close(file); 203 204 return retval; 205 } 206 207 208 209 /* Make a new object using the given function. */ 210 211 static int _make_object(ext2_filsys fs, const char *filename, 212 ext2_ino_t ino_parent, const char *basename, 213 ext2_ino_t *ino_target, 214 errcode_t (*fn)(ext2_filsys, ext2_ino_t, const char *, 215 __u16, __u16, __u16, ext2_ino_t *)) 216 { 217 errcode_t retval; 218 struct stat st; 219 220 /* Obtain the metadata. */ 221 222 if (lstat(filename, &st)) 223 { 224 fprintf(stderr, "Failed to read object metadata: %s\n", filename); 225 return 1; 226 } 227 228 retval = fn(fs, ino_parent, basename, st.st_mode & ~md.mask, 229 md.have_uid ? md.uid : st.st_uid, 230 md.have_gid ? md.gid : st.st_gid, 231 ino_target); 232 233 if (retval) 234 { 235 fprintf(stderr, "Failed to create object: %s\n", filename); 236 return 1; 237 } 238 239 return 0; 240 } 241 242 /* Copy a source file from the external environment into the filesystem. */ 243 244 static int _copy_in(ext2_filsys fs, const char *filename, ext2_ino_t ino_target, 245 const char *basename) 246 { 247 errcode_t retval; 248 int flags; 249 250 /* By default, treat the target as a new object. */ 251 252 ext2_ino_t ino_parent = ino_target; 253 int target_is_new = 1; 254 255 /* Source file details. */ 256 257 struct stat st; 258 259 /* Without a basename, the target exists and is either a directory, into 260 which the source file shall be copied, or it is a file that shall be 261 overwritten. */ 262 263 if (basename == NULL) 264 { 265 basename = path_basename(filename); 266 target_is_new = image_isdir_by_inode(fs, ino_target); 267 } 268 269 /* Directories are created with the same metadata. */ 270 271 if (isdir(filename)) 272 { 273 if (!target_is_new) 274 { 275 fprintf(stderr, "Directory cannot be copied as it already exists: %s\n", filename); 276 return 1; 277 } 278 279 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 280 image_make_dir)) 281 return 1; 282 } 283 284 /* Files are copied. */ 285 286 else if (isfile(filename)) 287 { 288 flags = EXT2_FILE_WRITE; 289 290 /* Obtain the inode for the target file. */ 291 292 if (target_is_new) 293 { 294 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 295 image_create_file)) 296 return 1; 297 298 flags |= EXT2_FILE_CREATE; 299 } 300 301 /* NOTE: Overwrite/update metadata where appropriate. */ 302 303 if (copy_file_in(filename, fs, ino_target, flags)) 304 { 305 fprintf(stderr, "Failed to copy file: %s\n", filename); 306 return 1; 307 } 308 } 309 } 310 311 /* Copy source files from the external environment into the filesystem image. */ 312 313 int copy_in(ext2_filsys fs, int argc, char *argv[]) 314 { 315 errcode_t retval; 316 int i; 317 318 /* Target filename details. */ 319 320 const char *target = argv[argc - 1]; 321 const char *target_remaining = target; 322 const char *basename; 323 324 /* Target file and directory details. */ 325 326 int target_is_file; 327 ext2_ino_t ino_target; 328 329 /* Locate the target and test whether it is a file or a directory. */ 330 331 if (image_resolve_by_path(fs, &target_remaining, &ino_target)) 332 { 333 /* Only a non-existent file in an existing directory is permitted. */ 334 335 if (!image_isdir_by_inode(fs, ino_target) || !path_is_leafname(target_remaining)) 336 { 337 fprintf(stderr, "Target not found: %s\n", target); 338 return 1; 339 } 340 341 /* Any absent target leaves the basename remaining. 342 The resolved target is the parent directory. */ 343 344 target_is_file = 0; 345 basename = target_remaining; 346 } 347 else 348 { 349 /* Any present target can be a file or directory. 350 The resolved target is the actual target. */ 351 352 target_is_file = image_isfile_by_inode(fs, ino_target); 353 basename = NULL; 354 } 355 356 /* Only permit a target file when one source file is given. */ 357 358 if (target_is_file) 359 { 360 if (argc > 2) 361 { 362 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 363 return 1; 364 } 365 } 366 else if (!image_isdir_by_inode(fs, ino_target)) 367 { 368 fprintf(stderr, "Target is not a directory: %s\n", target); 369 return 1; 370 } 371 372 /* Copy each source object to the target directory. */ 373 374 for (i = 0; i < argc - 1; i++) 375 if (_copy_in(fs, argv[i], ino_target, basename)) 376 return 1; 377 378 return 0; 379 } 380 381 /* Copy source files out of the filesystem image into the external environment. */ 382 383 int copy_out(ext2_filsys fs, int argc, char *argv[]) 384 { 385 /* Target filename details. */ 386 387 char *target = argv[argc - 1]; 388 int target_is_file; 389 390 /* Target file and directory details. */ 391 392 ext2_file_t file; 393 ext2_ino_t ino_file; 394 395 /* Source file details. */ 396 397 const char *path; 398 int i; 399 400 /* Locate the target and test whether it is a directory. */ 401 402 if (!isdir(target)) 403 { 404 /* Only a new or existing file in an existing directory is permitted. */ 405 406 if (isfile(target) || isdir_dirname(target)) 407 target_is_file = 1; 408 else 409 { 410 fprintf(stderr, "Target is not a directory: %s\n", target); 411 return 1; 412 } 413 414 /* Only permit a target file when one source file is given. */ 415 416 if (argc > 2) 417 { 418 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 419 return 1; 420 } 421 } 422 else 423 target_is_file = 0; 424 425 /* For each source filename, test whether it references a file. */ 426 427 for (i = 0; i < argc - 1; i++) 428 { 429 if (!image_isfile_by_path(fs, argv[i])) 430 { 431 fprintf(stderr, "Source is not a file: %s\n", argv[i]); 432 return 1; 433 } 434 } 435 436 /* Copy each source file to the target directory. */ 437 438 for (i = 0; i < argc - 1; i++) 439 { 440 path = argv[i]; 441 442 if (image_find_by_path(fs, path, &ino_file)) 443 { 444 fprintf(stderr, "Failed to find file: %s\n", argv[i]); 445 return 1; 446 } 447 448 if (copy_file_out(target, argv[i], target_is_file, fs, ino_file)) 449 { 450 fprintf(stderr, "Failed to read from file: %s\n", argv[i]); 451 return 1; 452 } 453 454 /* NOTE: Overwrite/update metadata where appropriate. */ 455 } 456 457 return 0; 458 } 459 460 /* List objects in the filesystem image. */ 461 462 int list(ext2_filsys fs, int argc, char *argv[]) 463 { 464 int i; 465 char *path; 466 467 for (i = 0; i < argc; i++) 468 { 469 path = argv[i]; 470 471 /* Emit each object. */ 472 473 puts(path); 474 475 /* List individual files or directories. */ 476 477 if (utils_list_dir(fs, path)) 478 { 479 fprintf(stderr, "Failed to list object: %s\n", path); 480 return 1; 481 } 482 } 483 484 return 0; 485 } 486 487 /* Make directories in the filesystem image. */ 488 489 int make_dirs(ext2_filsys fs, int argc, char *argv[]) 490 { 491 int i; 492 const char *path; 493 ext2_ino_t ino; 494 495 /* Make each directory component in the given pathname. */ 496 497 for (i = 0; i < argc; i++) 498 { 499 path = argv[i]; 500 501 /* Search for the remaining components. */ 502 503 if ((!*path) || !image_resolve_by_path(fs, &path, &ino)) 504 { 505 fprintf(stderr, "Path exists: %s\n", argv[i]); 506 return 1; 507 } 508 509 /* From the first unrecognised component, make the remaining 510 directories. */ 511 512 if (image_make_dirs(fs, &path, ino, 513 0777 & ~md.mask, 514 md.have_uid ? md.uid : 0, 515 md.have_gid ? md.gid : 0)) 516 { 517 fprintf(stderr, "Failed to make directory: %s\n", argv[i]); 518 return 1; 519 } 520 } 521 522 return 0; 523 } 524 525 /* Remove objects from the filesystem image. */ 526 527 int _remove(ext2_filsys fs, int argc, char *argv[], int dir_only) 528 { 529 int i; 530 const char *path; 531 ext2_ino_t ino; 532 533 /* Remove each directory with the given pathname. */ 534 535 for (i = 0; i < argc; i++) 536 { 537 path = argv[i]; 538 539 /* Detect missing objects. */ 540 541 if ((!*path) || image_find_by_path(fs, path, &ino)) 542 { 543 fprintf(stderr, "Not found: %s\n", path); 544 return 1; 545 } 546 547 /* Insist on a directory if specified. */ 548 549 if (dir_only) 550 { 551 if (!image_isdir_by_inode(fs, ino)) 552 { 553 fprintf(stderr, "Not a directory: %s\n", path); 554 return 1; 555 } 556 557 /* Test for an empty directory. */ 558 559 if (image_dir_empty_by_inode(fs, ino)) 560 { 561 fprintf(stderr, "Directory not empty: %s\n", path); 562 return 1; 563 } 564 } 565 566 /* Otherwise, insist on a non-directory. */ 567 568 else if (image_isdir_by_inode(fs, ino)) 569 { 570 fprintf(stderr, "Cannot remove a directory: %s\n", path); 571 return 1; 572 } 573 574 /* Unlink the object. */ 575 576 if (image_unlink_by_path(fs, path)) 577 { 578 fprintf(stderr, "Could not unlink object: %s\n", path); 579 return 1; 580 } 581 582 /* Remove the object. */ 583 584 if (image_remove_by_inode(fs, ino)) 585 { 586 fprintf(stderr, "Could not remove object: %s\n", path); 587 return 1; 588 } 589 } 590 591 return 0; 592 } 593 594 /* Remove directories from the filesystem image. */ 595 596 int remove_dirs(ext2_filsys fs, int argc, char *argv[]) 597 { 598 return _remove(fs, argc, argv, 1); 599 } 600 601 /* Remove non-directories from the filesystem image. */ 602 603 int remove_non_dirs(ext2_filsys fs, int argc, char *argv[]) 604 { 605 return _remove(fs, argc, argv, 0); 606 } 607 608 /* Read operations from a script file. */ 609 610 enum op_results 611 { 612 OP_SUCCESS = 0, 613 OP_FAILED = 1, 614 OP_UNKNOWN = 2, 615 }; 616 617 int handle_op_result(const char *operation, enum op_results op_result) 618 { 619 if (op_result == OP_UNKNOWN) 620 { 621 fprintf(stderr, "Operation not recognised: %s\n", operation); 622 return 1; 623 } 624 else if (op_result == OP_FAILED) 625 { 626 fprintf(stderr, "Operation failed: %s\n", operation); 627 return 1; 628 } 629 else 630 return 0; 631 } 632 633 enum op_results run_operation(ext2_filsys fs, const char *operation, int argc, char *argv[]); 634 635 int run_script(ext2_filsys fs, int argc, char *argv[]) 636 { 637 FILE *fp; 638 char buffer[BUFSIZE]; 639 struct read_line_state state; 640 enum op_results op_result; 641 int num_args; 642 char *args[MAX_ARGS]; 643 int i; 644 645 for (i = 0; i < argc; i++) 646 { 647 fp = fopen(argv[i], "r"); 648 649 state.buffer = buffer; 650 state.start = buffer; 651 state.end = buffer; 652 state.buffer_size = BUFSIZE; 653 state.remaining = BUFSIZE - 1; 654 655 while (read_line(fp, &state) != NULL) 656 { 657 parse_line(state.start, state.eolp, &num_args, args, MAX_ARGS); 658 659 if (num_args > 1) 660 { 661 op_result = run_operation(fs, args[0], num_args - 1, &args[1]); 662 663 if (handle_op_result(args[0], op_result)) 664 return 1; 665 } 666 667 state.start = state.eolp + 1; 668 } 669 670 fclose(fp); 671 } 672 673 return 0; 674 } 675 676 /* Show statistics for files and directories. */ 677 678 int stat_objects(ext2_filsys fs, int argc, char *argv[]) 679 { 680 int i; 681 const char *path; 682 ext2_ino_t ino; 683 struct stat st; 684 685 for (i = 0; i < argc; i++) 686 { 687 path = argv[i]; 688 689 /* Detect missing objects. */ 690 691 if (image_find_by_path(fs, path, &ino)) 692 { 693 fprintf(stderr, "Not found: %s\n", path); 694 return 1; 695 } 696 697 /* Even though the statistics could be read directly out of ext2 data 698 structures, it is convenient to use the standard stat structure. */ 699 700 if (image_stat_inode(fs, ino, &st)) 701 { 702 fprintf(stderr, "Cannot stat object: %s\n", path); 703 return 1; 704 } 705 706 /* Terse stat output: 707 %n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o %C */ 708 709 printf("%s %ld %ld %x %d %d ", 710 path, st.st_size, st.st_blocks, st.st_mode, st.st_uid, st.st_gid); 711 712 printf("%d %d %d %x %x ", 713 st.st_dev, st.st_ino, st.st_nlink, 714 major(st.st_rdev), minor(st.st_rdev)); 715 716 printf("%d %d %d ", 717 st.st_atim, st.st_mtim, st.st_ctim); 718 719 /* NOTE: Arbitrary values: 720 %W (creation time) given as 0 721 %o (I/O transfer size hint) given as 0 722 %C (SELinux security context) given as empty string */ 723 724 printf("%d %d %s\n", 725 0, 0, ""); 726 } 727 728 return 0; 729 } 730 731 732 733 /* Help message. */ 734 735 char help_text[] = "\ 736 Usage: %s [ <options> ] <image file> <operation> <filename>...\n\ 737 \n\ 738 File ownership options:\n\ 739 \n\ 740 -g GID Set group identifier for new files\n\ 741 -u UID Set user identifier for new files\n\ 742 \n\ 743 File permission options:\n\ 744 \n\ 745 -m MASK Set mode/permissions mask for new directories\n\ 746 \n\ 747 Transfer operations:\n\ 748 \n\ 749 copy-in Copy files into a directory within the image\n\ 750 copy-out Copy files from the image into a directory\n\ 751 \n\ 752 Image operations:\n\ 753 \n\ 754 ls List files and directories within the image\n\ 755 mkdir Make directories within the image\n\ 756 rm Remove non-directory objects from the image\n\ 757 rmdir Remove directories from the image\n\ 758 stat Show statistics for files and directories\n\ 759 \n\ 760 Script operations:\n\ 761 \n\ 762 script Read operations from a script file\n\ 763 "; 764 765 /* Operations exposed by the program. */ 766 767 struct operation 768 { 769 const char *name; 770 int (*fn)(ext2_filsys, int, char *[]); 771 }; 772 773 static struct operation operations[] = { 774 {"copy-in", copy_in}, 775 {"copy-out", copy_out}, 776 {"ls", list}, 777 {"mkdir", make_dirs}, 778 {"rm", remove_non_dirs}, 779 {"rmdir", remove_dirs}, 780 {"script", run_script}, 781 {"stat", stat_objects}, 782 {NULL, NULL}, 783 }; 784 785 /* Invocation of operations. */ 786 787 enum op_results run_operation(ext2_filsys fs, const char *operation, int argc, char *argv[]) 788 { 789 struct operation *op; 790 int exitcode; 791 792 for (op = &operations[0]; op->name != NULL; op++) 793 { 794 if (!strcmp(operation, op->name)) 795 { 796 exitcode = op->fn(fs, argc, argv); 797 if (exitcode) 798 return OP_FAILED; 799 break; 800 } 801 } 802 803 if (op->name == NULL) 804 return OP_UNKNOWN; 805 806 return OP_SUCCESS; 807 } 808 809 /* Main program. */ 810 811 int main(int argc, char *argv[]) 812 { 813 int flags = EXT2_FLAG_RW; // | EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS; 814 ext2_filsys fs = NULL; 815 errcode_t retval; 816 int exitcode = 0; 817 818 /* Program argument details. */ 819 820 char **args; 821 char *fsname, *filename; 822 int num_args; 823 enum op_results op_result; 824 825 /* Parse program options and initialise the argument details. */ 826 827 if (parse_options(argc, argv)) 828 return 1; 829 830 args = &argv[optind]; 831 num_args = argc - optind; 832 833 if (num_args < 3) 834 { 835 fprintf(stderr, help_text, argv[0]); 836 return 1; 837 } 838 839 /* Open the filesystem image using the POSIX file access mechanism. */ 840 841 fsname = args[0]; 842 843 retval = ext2fs_open(fsname, flags, 0, 0, unix_io_manager, &fs); 844 if (retval) 845 { 846 fprintf(stderr, "Could not open filesystem: %s\n", fsname); 847 return 1; 848 } 849 850 //fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 851 852 retval = ext2fs_read_bitmaps(fs); 853 if (retval) 854 { 855 fprintf(stderr, "Could not read bitmaps: %s\n", fsname); 856 return 1; 857 } 858 859 /* Perform the requested operation. */ 860 861 op_result = run_operation(fs, args[1], num_args - 2, &args[2]); 862 exitcode = handle_op_result(args[1], op_result); 863 864 /* Close the filesystem image. */ 865 866 retval = ext2fs_flush(fs); 867 if (retval) 868 { 869 fprintf(stderr, "Error flushing filesystem: %s\n", fsname); 870 exitcode = 1; 871 } 872 873 retval = ext2fs_close(fs); 874 if (retval) 875 { 876 fprintf(stderr, "Error closing filesystem: %s\n", fsname); 877 exitcode = 1; 878 } 879 880 ext2fs_free(fs); 881 return exitcode; 882 } 883 884 /* vim: tabstop=4 expandtab shiftwidth=4 885 */