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 "input.h" 36 #include "path.h" 37 #include "utils.h" 38 39 40 41 /* Copy buffer size. */ 42 43 const int BUFSIZE = 4096; 44 45 /* Maximum number of arguments in scripts. */ 46 47 const int MAX_ARGS = 32; 48 49 50 51 /* Alternative metadata set by options. */ 52 53 struct metadata 54 { 55 uid_t uid; 56 gid_t gid; 57 mode_t mask; 58 int have_uid, have_gid; 59 }; 60 61 struct metadata md; 62 63 /* Parse program options. */ 64 65 static int parse_options(int argc, char *argv[]) 66 { 67 int opt; 68 69 md.have_uid = 0; 70 md.have_gid = 0; 71 md.mask = 0000; 72 73 while ((opt = getopt(argc, argv, "g:m:u:")) != -1) 74 { 75 switch (opt) 76 { 77 case 'g': 78 md.gid = atoi(optarg); 79 md.have_gid = 1; 80 break; 81 82 case 'm': 83 md.mask = strtol(optarg, NULL, 0); 84 break; 85 86 case 'u': 87 md.uid = atoi(optarg); 88 md.have_uid = 1; 89 break; 90 91 default: 92 fprintf(stderr, "Option not recognised: %s\n", argv[optind]); 93 return -1; 94 } 95 } 96 97 return 0; 98 } 99 100 101 102 /* Copy a file into the filesystem image. */ 103 104 int copy_file_in(const char *filename, ext2_filsys fs, ext2_ino_t ino_file, int flags) 105 { 106 int retval = 0; 107 ext2_file_t file; 108 109 /* Copying details. */ 110 111 FILE *fp; 112 char buf[BUFSIZE]; 113 size_t got; 114 unsigned int written; 115 116 /* Open a file in the target directory. */ 117 118 if (ext2fs_file_open(fs, ino_file, flags, &file)) 119 return 1; 120 121 /* Open the file in the source directory. */ 122 123 fp = fopen(filename, "r"); 124 125 /* Copy the file content. */ 126 127 if (fp != NULL) 128 { 129 while (got = fread(buf, sizeof(char), BUFSIZE, fp)) 130 { 131 while (got) 132 { 133 if (ext2fs_file_write(file, buf, got, &written)) 134 { 135 retval = 1; 136 goto close_files; 137 } 138 got -= written; 139 } 140 } 141 } 142 143 close_files: 144 fclose(fp); 145 ext2fs_file_flush(file); 146 ext2fs_file_close(file); 147 148 return retval; 149 } 150 151 /* Copy a file out of the filesystem image. */ 152 153 int copy_file_out(const char *target, const char *filename, int target_is_file, 154 ext2_filsys fs, ext2_ino_t ino_file) 155 { 156 int retval = 0; 157 ext2_file_t file; 158 159 /* Copying details. */ 160 161 FILE *fp; 162 char buf[BUFSIZE]; 163 unsigned int got; 164 size_t written; 165 166 /* Open the file in the source directory. */ 167 168 if (ext2fs_file_open(fs, ino_file, 0, &file)) 169 return 1; 170 171 /* Open a file in the target directory. */ 172 173 if (target_is_file) 174 fp = fopen(target, "w"); 175 else 176 fp = open_file_in_dir(target, path_basename(filename), "w"); 177 178 /* Copy the file content. */ 179 180 if (fp != NULL) 181 { 182 do 183 { 184 if (ext2fs_file_read(file, buf, BUFSIZE, &got)) 185 { 186 retval = 1; 187 goto close_files; 188 } 189 190 while (got) 191 { 192 written = fwrite(buf, sizeof(char), got, fp); 193 got -= written; 194 } 195 196 } while (got); 197 } 198 199 close_files: 200 fclose(fp); 201 ext2fs_file_close(file); 202 203 return retval; 204 } 205 206 207 208 /* Copy source files from the external environment into the filesystem image. */ 209 210 int copy_in(ext2_filsys fs, int argc, char *argv[]) 211 { 212 errcode_t retval; 213 214 /* Target filename details. */ 215 216 const char *target = argv[argc - 1]; 217 const char *target_remaining = argv[argc - 1]; 218 const char *basename; 219 int target_is_file; 220 221 /* Target file and directory details. */ 222 223 int 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 653 654 655 /* Help message. */ 656 657 char help_text[] = "\ 658 Usage: %s [ <options> ] <image file> <operation> <filename>...\n\ 659 \n\ 660 File ownership options:\n\ 661 \n\ 662 -g GID Set group identifier for new files\n\ 663 -u UID Set user identifier for new files\n\ 664 \n\ 665 File permission options:\n\ 666 \n\ 667 -m MASK Set mode/permissions mask for new directories\n\ 668 \n\ 669 Transfer operations:\n\ 670 \n\ 671 copy-in Copy files into a directory within the image\n\ 672 copy-out Copy files from the image into a directory\n\ 673 \n\ 674 Image operations:\n\ 675 \n\ 676 ls List files and directories within the image\n\ 677 mkdir Make directories within the image\n\ 678 rm Remove non-directory objects from the image\n\ 679 rmdir Remove directories from the image\n\ 680 \n\ 681 Script operations:\n\ 682 \n\ 683 script Read operations from a script file\n\ 684 "; 685 686 /* Operations exposed by the program. */ 687 688 struct operation 689 { 690 const char *name; 691 int (*fn)(ext2_filsys, int, char *[]); 692 }; 693 694 static struct operation operations[] = { 695 {"copy-in", copy_in}, 696 {"copy-out", copy_out}, 697 {"ls", list}, 698 {"mkdir", make_dirs}, 699 {"rm", remove_non_dirs}, 700 {"rmdir", remove_dirs}, 701 {"script", run_script}, 702 {NULL, NULL}, 703 }; 704 705 /* Invocation of operations. */ 706 707 enum op_results run_operation(ext2_filsys fs, const char *operation, int argc, char *argv[]) 708 { 709 struct operation *op; 710 int exitcode; 711 712 for (op = &operations[0]; op->name != NULL; op++) 713 { 714 if (!strcmp(operation, op->name)) 715 { 716 exitcode = op->fn(fs, argc, argv); 717 if (exitcode) 718 return OP_FAILED; 719 break; 720 } 721 } 722 723 if (op->name == NULL) 724 return OP_UNKNOWN; 725 726 return OP_SUCCESS; 727 } 728 729 /* Main program. */ 730 731 int main(int argc, char *argv[]) 732 { 733 int flags = EXT2_FLAG_RW; // | EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS; 734 ext2_filsys fs = NULL; 735 errcode_t retval; 736 int exitcode = 0; 737 738 /* Program argument details. */ 739 740 char **args; 741 char *fsname, *filename; 742 int num_args; 743 enum op_results op_result; 744 745 /* Parse program options and initialise the argument details. */ 746 747 if (parse_options(argc, argv)) 748 return 1; 749 750 args = &argv[optind]; 751 num_args = argc - optind; 752 753 if (num_args < 3) 754 { 755 fprintf(stderr, help_text, argv[0]); 756 return 1; 757 } 758 759 /* Open the filesystem image using the POSIX file access mechanism. */ 760 761 fsname = args[0]; 762 763 retval = ext2fs_open(fsname, flags, 0, 0, unix_io_manager, &fs); 764 if (retval) 765 { 766 fprintf(stderr, "Could not open filesystem: %s\n", fsname); 767 return 1; 768 } 769 770 //fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 771 772 retval = ext2fs_read_bitmaps(fs); 773 if (retval) 774 { 775 fprintf(stderr, "Could not read bitmaps: %s\n", fsname); 776 return 1; 777 } 778 779 /* Perform the requested operation. */ 780 781 op_result = run_operation(fs, args[1], num_args - 2, &args[2]); 782 exitcode = handle_op_result(args[1], op_result); 783 784 /* Close the filesystem image. */ 785 786 retval = ext2fs_flush(fs); 787 if (retval) 788 { 789 fprintf(stderr, "Error flushing filesystem: %s\n", fsname); 790 exitcode = 1; 791 } 792 793 retval = ext2fs_close(fs); 794 if (retval) 795 { 796 fprintf(stderr, "Error closing filesystem: %s\n", fsname); 797 exitcode = 1; 798 } 799 800 ext2fs_free(fs); 801 return exitcode; 802 } 803 804 /* vim: tabstop=4 expandtab shiftwidth=4 805 */