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