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 directory in the external environment. */ 210 211 static int _make_directory(const char *target, const char *basename, 212 ext2_filsys fs, ext2_ino_t ino) 213 { 214 errcode_t retval; 215 struct ext2_inode inode; 216 char target_path[strlen(target) + strlen(basename) + 2]; 217 218 sprintf(target_path, "%s/%s", target, basename); 219 220 retval = ext2fs_read_inode(fs, ino, &inode); 221 222 if (retval) 223 return retval; 224 225 return mkdir(target_path, inode.i_mode); 226 } 227 228 /* Make a new object using the given function. */ 229 230 static int _make_object(ext2_filsys fs, const char *filename, 231 ext2_ino_t ino_parent, const char *basename, 232 ext2_ino_t *ino_target, 233 errcode_t (*fn)(ext2_filsys, ext2_ino_t, const char *, 234 __u16, __u16, __u16, ext2_ino_t *)) 235 { 236 errcode_t retval; 237 struct stat st; 238 239 /* Obtain the metadata. */ 240 241 if (lstat(filename, &st)) 242 { 243 fprintf(stderr, "Failed to read object metadata: %s\n", filename); 244 return 1; 245 } 246 247 retval = fn(fs, ino_parent, basename, st.st_mode & ~md.mask, 248 md.have_uid ? md.uid : st.st_uid, 249 md.have_gid ? md.gid : st.st_gid, 250 ino_target); 251 252 if (retval) 253 { 254 fprintf(stderr, "Failed to create object: %s\n", filename); 255 return 1; 256 } 257 258 return 0; 259 } 260 261 /* Copy a source file from the external environment into the filesystem. */ 262 263 static int _copy_in(ext2_filsys fs, const char *filename, ext2_ino_t ino_target, 264 const char *basename) 265 { 266 errcode_t retval; 267 int flags; 268 269 /* By default, treat the target as a new object. */ 270 271 ext2_ino_t ino_parent = ino_target; 272 int target_is_new = 1; 273 274 /* Source file details. */ 275 276 struct stat st; 277 278 /* Without a basename, the target exists and is either a directory, into 279 which the source file shall be copied, or it is a file that shall be 280 overwritten. */ 281 282 if (basename == NULL) 283 { 284 basename = path_basename(filename); 285 target_is_new = image_isdir_by_inode(fs, ino_target); 286 } 287 288 /* Directories are created with the same metadata. */ 289 290 if (isdir(filename)) 291 { 292 if (!target_is_new) 293 { 294 fprintf(stderr, "Directory cannot be copied as it already exists: %s\n", filename); 295 return 1; 296 } 297 298 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 299 image_make_dir)) 300 return 1; 301 } 302 303 /* Files are copied. */ 304 305 else if (isfile(filename)) 306 { 307 flags = EXT2_FILE_WRITE; 308 309 /* Obtain the inode for the target file. */ 310 311 if (target_is_new) 312 { 313 if (_make_object(fs, filename, ino_parent, basename, &ino_target, 314 image_create_file)) 315 return 1; 316 317 flags |= EXT2_FILE_CREATE; 318 } 319 320 /* NOTE: Overwrite/update metadata where appropriate. */ 321 322 if (copy_file_in(filename, fs, ino_target, flags)) 323 { 324 fprintf(stderr, "Failed to copy file: %s\n", filename); 325 return 1; 326 } 327 } 328 } 329 330 /* Copy source files from the external environment into the filesystem image. */ 331 332 int copy_in(ext2_filsys fs, int argc, char *argv[]) 333 { 334 errcode_t retval; 335 int i; 336 337 /* Target filename details. */ 338 339 const char *target = argv[argc - 1]; 340 const char *target_remaining = target; 341 const char *basename; 342 343 /* Target file and directory details. */ 344 345 int target_is_file; 346 ext2_ino_t ino_target; 347 348 /* Locate the target and test whether it is a file or a directory. */ 349 350 if (image_resolve_by_path(fs, &target_remaining, &ino_target)) 351 { 352 /* Only a non-existent file in an existing directory is permitted. */ 353 354 if (!image_isdir_by_inode(fs, ino_target) || !path_is_leafname(target_remaining)) 355 { 356 fprintf(stderr, "Target not found: %s\n", target); 357 return 1; 358 } 359 360 /* Any absent target leaves the basename remaining. 361 The resolved target is the parent directory. */ 362 363 target_is_file = 0; 364 basename = target_remaining; 365 } 366 else 367 { 368 /* Any present target can be a file or directory. 369 The resolved target is the actual target. */ 370 371 target_is_file = image_isfile_by_inode(fs, ino_target); 372 basename = NULL; 373 } 374 375 /* Only permit a target file when one source file is given. */ 376 377 if (target_is_file) 378 { 379 if (argc > 2) 380 { 381 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 382 return 1; 383 } 384 } 385 else if (!image_isdir_by_inode(fs, ino_target)) 386 { 387 fprintf(stderr, "Target is not a directory: %s\n", target); 388 return 1; 389 } 390 391 /* Copy each source object to the target directory. */ 392 393 for (i = 0; i < argc - 1; i++) 394 if (_copy_in(fs, argv[i], ino_target, basename)) 395 return 1; 396 397 return 0; 398 } 399 400 /* Copy a file from the filesystem into the external environment. */ 401 402 static int _copy_out(ext2_filsys fs, const char *source, const char *target, 403 int target_is_file) 404 { 405 ext2_ino_t ino_source; 406 407 if (image_find_by_path(fs, source, &ino_source)) 408 { 409 fprintf(stderr, "Failed to find file: %s\n", source); 410 return 1; 411 } 412 413 /* Test whether the filename references a file. */ 414 415 if (image_isdir_by_path(fs, source)) 416 { 417 if (_make_directory(target, path_basename(source), fs, ino_source)) 418 { 419 fprintf(stderr, "Failed to make directory: %s/%s\n", target, 420 path_basename(source)); 421 return 1; 422 } 423 } 424 else if (image_isfile_by_path(fs, source)) 425 { 426 if (copy_file_out(target, source, target_is_file, fs, ino_source)) 427 { 428 fprintf(stderr, "Failed to read from file: %s\n", source); 429 return 1; 430 } 431 } 432 else 433 { 434 fprintf(stderr, "Object type not supported: %s\n", source); 435 return 1; 436 } 437 438 /* NOTE: Overwrite/update metadata where appropriate. */ 439 440 return 0; 441 } 442 443 /* Copy source files out of the filesystem image into the external environment. */ 444 445 int copy_out(ext2_filsys fs, int argc, char *argv[]) 446 { 447 int i; 448 449 /* Target filename details. */ 450 451 char *target = argv[argc - 1]; 452 int target_is_file; 453 454 /* Locate the target and test whether it is a directory. */ 455 456 if (!isdir(target)) 457 { 458 /* Only a new or existing file in an existing directory is permitted. */ 459 460 if (isfile(target) || isdir_dirname(target)) 461 target_is_file = 1; 462 else 463 { 464 fprintf(stderr, "Target is not a directory: %s\n", target); 465 return 1; 466 } 467 468 /* Only permit a target file when one source file is given. */ 469 470 if (argc > 2) 471 { 472 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 473 return 1; 474 } 475 } 476 else 477 target_is_file = 0; 478 479 /* Copy each source file to the target directory. */ 480 481 for (i = 0; i < argc - 1; i++) 482 if (_copy_out(fs, argv[i], target, target_is_file)) 483 return 1; 484 485 return 0; 486 } 487 488 /* List objects in the filesystem image. */ 489 490 int list(ext2_filsys fs, int argc, char *argv[]) 491 { 492 int i; 493 char *path; 494 495 for (i = 0; i < argc; i++) 496 { 497 path = argv[i]; 498 499 /* Emit each object. */ 500 501 puts(path); 502 503 /* List individual files or directories. */ 504 505 if (utils_list_dir(fs, path)) 506 { 507 fprintf(stderr, "Failed to list object: %s\n", path); 508 return 1; 509 } 510 } 511 512 return 0; 513 } 514 515 /* Make directories in the filesystem image. */ 516 517 int make_dirs(ext2_filsys fs, int argc, char *argv[]) 518 { 519 int i; 520 const char *path; 521 ext2_ino_t ino; 522 523 /* Make each directory component in the given pathname. */ 524 525 for (i = 0; i < argc; i++) 526 { 527 path = argv[i]; 528 529 /* Search for the remaining components. */ 530 531 if ((!*path) || !image_resolve_by_path(fs, &path, &ino)) 532 { 533 fprintf(stderr, "Path exists: %s\n", argv[i]); 534 return 1; 535 } 536 537 /* From the first unrecognised component, make the remaining 538 directories. */ 539 540 if (image_make_dirs(fs, &path, ino, 541 0777 & ~md.mask, 542 md.have_uid ? md.uid : 0, 543 md.have_gid ? md.gid : 0)) 544 { 545 fprintf(stderr, "Failed to make directory: %s\n", argv[i]); 546 return 1; 547 } 548 } 549 550 return 0; 551 } 552 553 /* Remove objects from the filesystem image. */ 554 555 int _remove(ext2_filsys fs, int argc, char *argv[], int dir_only) 556 { 557 int i; 558 const char *path; 559 ext2_ino_t ino; 560 561 /* Remove each directory with the given pathname. */ 562 563 for (i = 0; i < argc; i++) 564 { 565 path = argv[i]; 566 567 /* Detect missing objects. */ 568 569 if ((!*path) || image_find_by_path(fs, path, &ino)) 570 { 571 fprintf(stderr, "Not found: %s\n", path); 572 return 1; 573 } 574 575 /* Insist on a directory if specified. */ 576 577 if (dir_only) 578 { 579 if (!image_isdir_by_inode(fs, ino)) 580 { 581 fprintf(stderr, "Not a directory: %s\n", path); 582 return 1; 583 } 584 585 /* Test for an empty directory. */ 586 587 if (image_dir_empty_by_inode(fs, ino)) 588 { 589 fprintf(stderr, "Directory not empty: %s\n", path); 590 return 1; 591 } 592 } 593 594 /* Otherwise, insist on a non-directory. */ 595 596 else if (image_isdir_by_inode(fs, ino)) 597 { 598 fprintf(stderr, "Cannot remove a directory: %s\n", path); 599 return 1; 600 } 601 602 /* Unlink the object. */ 603 604 if (image_unlink_by_path(fs, path)) 605 { 606 fprintf(stderr, "Could not unlink object: %s\n", path); 607 return 1; 608 } 609 610 /* Remove the object. */ 611 612 if (image_remove_by_inode(fs, ino)) 613 { 614 fprintf(stderr, "Could not remove object: %s\n", path); 615 return 1; 616 } 617 } 618 619 return 0; 620 } 621 622 /* Remove directories from the filesystem image. */ 623 624 int remove_dirs(ext2_filsys fs, int argc, char *argv[]) 625 { 626 return _remove(fs, argc, argv, 1); 627 } 628 629 /* Remove non-directories from the filesystem image. */ 630 631 int remove_non_dirs(ext2_filsys fs, int argc, char *argv[]) 632 { 633 return _remove(fs, argc, argv, 0); 634 } 635 636 /* Read operations from a script file. */ 637 638 enum op_results 639 { 640 OP_SUCCESS = 0, 641 OP_FAILED = 1, 642 OP_UNKNOWN = 2, 643 }; 644 645 int handle_op_result(const char *operation, enum op_results op_result) 646 { 647 if (op_result == OP_UNKNOWN) 648 { 649 fprintf(stderr, "Operation not recognised: %s\n", operation); 650 return 1; 651 } 652 else if (op_result == OP_FAILED) 653 { 654 fprintf(stderr, "Operation failed: %s\n", operation); 655 return 1; 656 } 657 else 658 return 0; 659 } 660 661 enum op_results run_operation(ext2_filsys fs, const char *operation, int argc, char *argv[]); 662 663 int run_script(ext2_filsys fs, int argc, char *argv[]) 664 { 665 FILE *fp; 666 char buffer[BUFSIZE]; 667 struct read_line_state state; 668 enum op_results op_result; 669 int num_args; 670 char *args[MAX_ARGS]; 671 int i; 672 673 for (i = 0; i < argc; i++) 674 { 675 fp = fopen(argv[i], "r"); 676 677 state.buffer = buffer; 678 state.start = buffer; 679 state.end = buffer; 680 state.buffer_size = BUFSIZE; 681 state.remaining = BUFSIZE - 1; 682 683 while (read_line(fp, &state) != NULL) 684 { 685 parse_line(state.start, state.eolp, &num_args, args, MAX_ARGS); 686 687 if (num_args > 1) 688 { 689 op_result = run_operation(fs, args[0], num_args - 1, &args[1]); 690 691 if (handle_op_result(args[0], op_result)) 692 return 1; 693 } 694 695 state.start = state.eolp + 1; 696 } 697 698 fclose(fp); 699 } 700 701 return 0; 702 } 703 704 /* Show statistics for files and directories. */ 705 706 int stat_objects(ext2_filsys fs, int argc, char *argv[]) 707 { 708 int i; 709 const char *path; 710 ext2_ino_t ino; 711 struct stat st; 712 713 for (i = 0; i < argc; i++) 714 { 715 path = argv[i]; 716 717 /* Detect missing objects. */ 718 719 if (image_find_by_path(fs, path, &ino)) 720 { 721 fprintf(stderr, "Not found: %s\n", path); 722 return 1; 723 } 724 725 /* Even though the statistics could be read directly out of ext2 data 726 structures, it is convenient to use the standard stat structure. */ 727 728 if (image_stat_inode(fs, ino, &st)) 729 { 730 fprintf(stderr, "Cannot stat object: %s\n", path); 731 return 1; 732 } 733 734 /* Terse stat output: 735 %n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o %C */ 736 737 printf("%s %ld %ld %x %d %d ", 738 path, st.st_size, st.st_blocks, st.st_mode, st.st_uid, st.st_gid); 739 740 printf("%d %d %d %x %x ", 741 st.st_dev, st.st_ino, st.st_nlink, 742 major(st.st_rdev), minor(st.st_rdev)); 743 744 printf("%d %d %d ", 745 st.st_atim, st.st_mtim, st.st_ctim); 746 747 /* NOTE: Arbitrary values: 748 %W (creation time) given as 0 749 %o (I/O transfer size hint) given as 0 750 %C (SELinux security context) given as empty string */ 751 752 printf("%d %d %s\n", 753 0, 0, ""); 754 } 755 756 return 0; 757 } 758 759 760 761 /* Help message. */ 762 763 char help_text[] = "\ 764 Usage: %s [ <options> ] <image file> <operation> <filename>...\n\ 765 \n\ 766 File ownership options:\n\ 767 \n\ 768 -g GID Set group identifier for new files\n\ 769 -u UID Set user identifier for new files\n\ 770 \n\ 771 File permission options:\n\ 772 \n\ 773 -m MASK Set mode/permissions mask for new directories\n\ 774 \n\ 775 Transfer operations:\n\ 776 \n\ 777 copy-in Copy files into a directory within the image\n\ 778 copy-out Copy files from the image into a directory\n\ 779 \n\ 780 Image operations:\n\ 781 \n\ 782 ls List files and directories within the image\n\ 783 mkdir Make directories within the image\n\ 784 rm Remove non-directory objects from the image\n\ 785 rmdir Remove directories from the image\n\ 786 stat Show statistics for files and directories\n\ 787 \n\ 788 Script operations:\n\ 789 \n\ 790 script Read operations from a script file\n\ 791 "; 792 793 /* Operations exposed by the program. */ 794 795 struct operation 796 { 797 const char *name; 798 int (*fn)(ext2_filsys, int, char *[]); 799 }; 800 801 static struct operation operations[] = { 802 {"copy-in", copy_in}, 803 {"copy-out", copy_out}, 804 {"ls", list}, 805 {"mkdir", make_dirs}, 806 {"rm", remove_non_dirs}, 807 {"rmdir", remove_dirs}, 808 {"script", run_script}, 809 {"stat", stat_objects}, 810 {NULL, NULL}, 811 }; 812 813 /* Invocation of operations. */ 814 815 enum op_results run_operation(ext2_filsys fs, const char *operation, int argc, char *argv[]) 816 { 817 struct operation *op; 818 int exitcode; 819 820 for (op = &operations[0]; op->name != NULL; op++) 821 { 822 if (!strcmp(operation, op->name)) 823 { 824 exitcode = op->fn(fs, argc, argv); 825 if (exitcode) 826 return OP_FAILED; 827 break; 828 } 829 } 830 831 if (op->name == NULL) 832 return OP_UNKNOWN; 833 834 return OP_SUCCESS; 835 } 836 837 /* Main program. */ 838 839 int main(int argc, char *argv[]) 840 { 841 int flags = EXT2_FLAG_RW; // | EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS; 842 ext2_filsys fs = NULL; 843 errcode_t retval; 844 int exitcode = 0; 845 846 /* Program argument details. */ 847 848 char **args; 849 char *fsname, *filename; 850 int num_args; 851 enum op_results op_result; 852 853 /* Parse program options and initialise the argument details. */ 854 855 if (parse_options(argc, argv)) 856 return 1; 857 858 args = &argv[optind]; 859 num_args = argc - optind; 860 861 if (num_args < 3) 862 { 863 fprintf(stderr, help_text, argv[0]); 864 return 1; 865 } 866 867 /* Open the filesystem image using the POSIX file access mechanism. */ 868 869 fsname = args[0]; 870 871 retval = ext2fs_open(fsname, flags, 0, 0, unix_io_manager, &fs); 872 if (retval) 873 { 874 fprintf(stderr, "Could not open filesystem: %s\n", fsname); 875 return 1; 876 } 877 878 //fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 879 880 retval = ext2fs_read_bitmaps(fs); 881 if (retval) 882 { 883 fprintf(stderr, "Could not read bitmaps: %s\n", fsname); 884 return 1; 885 } 886 887 /* Perform the requested operation. */ 888 889 op_result = run_operation(fs, args[1], num_args - 2, &args[2]); 890 exitcode = handle_op_result(args[1], op_result); 891 892 /* Close the filesystem image. */ 893 894 retval = ext2fs_flush(fs); 895 if (retval) 896 { 897 fprintf(stderr, "Error flushing filesystem: %s\n", fsname); 898 exitcode = 1; 899 } 900 901 retval = ext2fs_close(fs); 902 if (retval) 903 { 904 fprintf(stderr, "Error closing filesystem: %s\n", fsname); 905 exitcode = 1; 906 } 907 908 ext2fs_free(fs); 909 return exitcode; 910 } 911 912 /* vim: tabstop=4 expandtab shiftwidth=4 913 */