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