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