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 45 46 /* Alternative metadata set by options. */ 47 48 struct metadata 49 { 50 uid_t uid; 51 gid_t gid; 52 mode_t mask; 53 int have_uid, have_gid; 54 }; 55 56 struct metadata md; 57 58 /* Parse program options. */ 59 60 static int parse_options(int argc, char *argv[]) 61 { 62 int opt; 63 64 md.have_uid = 0; 65 md.have_gid = 0; 66 md.mask = 0000; 67 68 while ((opt = getopt(argc, argv, "g:m:u:")) != -1) 69 { 70 switch (opt) 71 { 72 case 'g': 73 md.gid = atoi(optarg); 74 md.have_gid = 1; 75 break; 76 77 case 'm': 78 md.mask = strtol(optarg, NULL, 0); 79 break; 80 81 case 'u': 82 md.uid = atoi(optarg); 83 md.have_uid = 1; 84 break; 85 86 default: 87 fprintf(stderr, "Option not recognised: %s\n", argv[optind]); 88 return -1; 89 } 90 } 91 92 return 0; 93 } 94 95 96 97 /* Copy a file into the filesystem image. */ 98 99 int copy_file_in(const char *filename, ext2_filsys fs, ext2_ino_t ino_file, int flags) 100 { 101 int retval = 0; 102 ext2_file_t file; 103 104 /* Copying details. */ 105 106 FILE *fp; 107 char buf[BUFSIZE]; 108 size_t got; 109 unsigned int written; 110 111 /* Open a file in the target directory. */ 112 113 if (ext2fs_file_open(fs, ino_file, flags, &file)) 114 return 1; 115 116 /* Open the file in the source directory. */ 117 118 fp = fopen(filename, "r"); 119 120 /* Copy the file content. */ 121 122 if (fp != NULL) 123 { 124 while (got = fread(buf, sizeof(char), BUFSIZE, fp)) 125 { 126 while (got) 127 { 128 if (ext2fs_file_write(file, buf, got, &written)) 129 { 130 retval = 1; 131 goto close_files; 132 } 133 got -= written; 134 } 135 } 136 } 137 138 close_files: 139 fclose(fp); 140 ext2fs_file_flush(file); 141 ext2fs_file_close(file); 142 143 return retval; 144 } 145 146 /* Copy a file out of the filesystem image. */ 147 148 int copy_file_out(const char *target, const char *filename, int target_is_file, 149 ext2_filsys fs, ext2_ino_t ino_file) 150 { 151 int retval = 0; 152 ext2_file_t file; 153 154 /* Copying details. */ 155 156 FILE *fp; 157 char buf[BUFSIZE]; 158 unsigned int got; 159 size_t written; 160 161 /* Open the file in the source directory. */ 162 163 if (ext2fs_file_open(fs, ino_file, 0, &file)) 164 return 1; 165 166 /* Open a file in the target directory. */ 167 168 if (target_is_file) 169 fp = fopen(target, "w"); 170 else 171 fp = open_file_in_dir(target, path_basename(filename), "w"); 172 173 /* Copy the file content. */ 174 175 if (fp != NULL) 176 { 177 do 178 { 179 if (ext2fs_file_read(file, buf, BUFSIZE, &got)) 180 { 181 retval = 1; 182 goto close_files; 183 } 184 185 while (got) 186 { 187 written = fwrite(buf, sizeof(char), got, fp); 188 got -= written; 189 } 190 191 } while (got); 192 } 193 194 close_files: 195 fclose(fp); 196 ext2fs_file_close(file); 197 198 return retval; 199 } 200 201 202 203 /* Copy source files from the external environment into the filesystem image. */ 204 205 int copy_in(ext2_filsys fs, int argc, char *argv[]) 206 { 207 errcode_t retval; 208 209 /* Target filename details. */ 210 211 const char *target = argv[argc - 1]; 212 const char *target_remaining = argv[argc - 1]; 213 const char *basename; 214 int target_is_file; 215 216 /* Target file and directory details. */ 217 218 int target_is_new; 219 ext2_ino_t ino_file, ino_target; 220 int flags; 221 222 /* Source file details. */ 223 224 struct stat st; 225 int i; 226 227 /* Locate the target and test whether it is a file or a directory. */ 228 229 if (image_resolve_by_path(fs, &target_remaining, &ino_target)) 230 { 231 /* Only a non-existent file in an existing directory is permitted. */ 232 233 if (!image_isdir_by_inode(fs, ino_target) || !path_is_leafname(target_remaining)) 234 { 235 fprintf(stderr, "Target not found: %s\n", target); 236 return 1; 237 } 238 239 target_is_file = 1; 240 target_is_new = 1; 241 } 242 else 243 { 244 target_is_file = image_isfile_by_inode(fs, ino_target); 245 target_is_new = 0; 246 } 247 248 /* Only permit a target file when one source file is given. */ 249 250 if (target_is_file) 251 { 252 if (argc > 2) 253 { 254 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 255 return 1; 256 } 257 } 258 else if (!image_isdir_by_inode(fs, ino_target)) 259 { 260 fprintf(stderr, "Target is not a directory: %s\n", target); 261 return 1; 262 } 263 264 /* Copy each source object to the target directory. */ 265 266 for (i = 0; i < argc - 1; i++) 267 { 268 if (target_is_file) 269 basename = target_remaining; 270 else 271 { 272 basename = path_basename(argv[i]); 273 target_is_new = image_find_file(fs, target, basename, &ino_file); 274 } 275 276 /* Directories are created with the same metadata. */ 277 278 if (isdir(argv[i])) 279 { 280 if (!target_is_new) 281 { 282 fprintf(stderr, "Target cannot be created since it already exists: %s\n", target); 283 return 1; 284 } 285 286 /* Obtain the metadata. */ 287 288 if (lstat(argv[i], &st)) 289 { 290 fprintf(stderr, "Failed to read object metadata: %s\n", argv[i]); 291 return 1; 292 } 293 294 retval = image_make_dir(fs, ino_target, basename, 295 st.st_mode & ~md.mask, 296 md.have_uid ? md.uid : st.st_uid, 297 md.have_gid ? md.gid : st.st_gid, 298 &ino_file); 299 300 if (retval) 301 { 302 fprintf(stderr, "Failed to create directory: %s\n", argv[i]); 303 return 1; 304 } 305 } 306 307 /* Files are copied. */ 308 309 else if (isfile(argv[i])) 310 { 311 flags = EXT2_FILE_WRITE; 312 313 /* Obtain the inode for the target file. */ 314 315 if (target_is_new) 316 { 317 /* Populate the inode details. */ 318 319 if (lstat(argv[i], &st)) 320 { 321 fprintf(stderr, "Failed to read object metadata: %s\n", argv[i]); 322 return 1; 323 } 324 325 retval = image_create_file(fs, ino_target, basename, 326 st.st_mode & ~md.mask, 327 md.have_uid ? md.uid : st.st_uid, 328 md.have_gid ? md.gid : st.st_gid, 329 &ino_file); 330 if (retval) 331 { 332 fprintf(stderr, "Failed to create file: %s\n", argv[i]); 333 return 1; 334 } 335 336 flags |= EXT2_FILE_CREATE; 337 } 338 339 /* NOTE: Overwrite/update metadata where appropriate. */ 340 341 if (copy_file_in(argv[i], fs, ino_file, flags)) 342 { 343 fprintf(stderr, "Failed to write to file: %s\n", argv[i]); 344 return 1; 345 } 346 } 347 } 348 349 return 0; 350 } 351 352 /* Copy source files out of the filesystem image into the external environment. */ 353 354 int copy_out(ext2_filsys fs, int argc, char *argv[]) 355 { 356 /* Target filename details. */ 357 358 char *target = argv[argc - 1]; 359 int target_is_file; 360 361 /* Target file and directory details. */ 362 363 ext2_file_t file; 364 ext2_ino_t ino_file; 365 366 /* Source file details. */ 367 368 const char *path; 369 int i; 370 371 /* Locate the target and test whether it is a directory. */ 372 373 if (!isdir(target)) 374 { 375 /* Only a new or existing file in an existing directory is permitted. */ 376 377 if (isfile(target) || isdir_dirname(target)) 378 target_is_file = 1; 379 else 380 { 381 fprintf(stderr, "Target is not a directory: %s\n", target); 382 return 1; 383 } 384 385 /* Only permit a target file when one source file is given. */ 386 387 if (argc > 2) 388 { 389 fprintf(stderr, "Target can only be a file when copying a single file: %s\n", target); 390 return 1; 391 } 392 } 393 else 394 target_is_file = 0; 395 396 /* For each source filename, test whether it references a file. */ 397 398 for (i = 0; i < argc - 1; i++) 399 { 400 if (!image_isfile_by_path(fs, argv[i])) 401 { 402 fprintf(stderr, "Source is not a file: %s\n", argv[i]); 403 return 1; 404 } 405 } 406 407 /* Copy each source file to the target directory. */ 408 409 for (i = 0; i < argc - 1; i++) 410 { 411 path = argv[i]; 412 413 if (image_find_by_path(fs, path, &ino_file)) 414 { 415 fprintf(stderr, "Failed to find file: %s\n", argv[i]); 416 return 1; 417 } 418 419 if (copy_file_out(target, argv[i], target_is_file, fs, ino_file)) 420 { 421 fprintf(stderr, "Failed to read from file: %s\n", argv[i]); 422 return 1; 423 } 424 425 /* NOTE: Overwrite/update metadata where appropriate. */ 426 } 427 428 return 0; 429 } 430 431 /* List objects in the filesystem image. */ 432 433 int list(ext2_filsys fs, int argc, char *argv[]) 434 { 435 int i; 436 char *path; 437 438 for (i = 0; i < argc; i++) 439 { 440 path = argv[i]; 441 442 /* Emit each object. */ 443 444 puts(path); 445 446 /* List individual files or directories. */ 447 448 if (utils_list_dir(fs, path)) 449 { 450 fprintf(stderr, "Failed to list directory: %s\n", path); 451 return 1; 452 } 453 } 454 455 return 0; 456 } 457 458 /* Make directories in the filesystem image. */ 459 460 int make_dirs(ext2_filsys fs, int argc, char *argv[]) 461 { 462 int i; 463 const char *path; 464 ext2_ino_t ino; 465 466 /* Make each directory component in the given pathname. */ 467 468 for (i = 0; i < argc; i++) 469 { 470 path = argv[i]; 471 472 /* Search for the remaining components. */ 473 474 if ((!*path) || !image_resolve_by_path(fs, &path, &ino)) 475 { 476 fprintf(stderr, "Path exists: %s\n", argv[i]); 477 return 1; 478 } 479 480 /* From the first unrecognised component, make the remaining 481 directories. */ 482 483 if (image_make_dirs(fs, &path, ino, 484 0777 & ~md.mask, 485 md.have_uid ? md.uid : 0, 486 md.have_gid ? md.gid : 0)) 487 { 488 fprintf(stderr, "Failed to make directory: %s\n", argv[i]); 489 return 1; 490 } 491 } 492 493 return 0; 494 } 495 496 /* Remove objects from the filesystem image. */ 497 498 int _remove(ext2_filsys fs, int argc, char *argv[], int dir_only) 499 { 500 int i; 501 const char *path; 502 ext2_ino_t ino; 503 504 /* Remove each directory with the given pathname. */ 505 506 for (i = 0; i < argc; i++) 507 { 508 path = argv[i]; 509 510 /* Detect missing objects. */ 511 512 if ((!*path) || image_find_by_path(fs, path, &ino)) 513 { 514 fprintf(stderr, "Not found: %s\n", path); 515 return 1; 516 } 517 518 /* Insist on a directory if specified. */ 519 520 if (dir_only) 521 { 522 if (!image_isdir_by_inode(fs, ino)) 523 { 524 fprintf(stderr, "Not a directory: %s\n", path); 525 return 1; 526 } 527 528 /* Test for an empty directory. */ 529 530 if (image_dir_empty_by_inode(fs, ino)) 531 { 532 fprintf(stderr, "Directory not empty: %s\n", path); 533 return 1; 534 } 535 } 536 537 /* Otherwise, insist on a non-directory. */ 538 539 else if (image_isdir_by_inode(fs, ino)) 540 { 541 fprintf(stderr, "Cannot remove a directory: %s\n", path); 542 return 1; 543 } 544 545 /* Unlink the object. */ 546 547 if (image_unlink_by_path(fs, path)) 548 { 549 fprintf(stderr, "Could not unlink object: %s\n", path); 550 return 1; 551 } 552 553 /* Remove the object. */ 554 555 if (image_remove_by_inode(fs, ino)) 556 { 557 fprintf(stderr, "Could not remove object: %s\n", path); 558 return 1; 559 } 560 } 561 562 return 0; 563 } 564 565 /* Remove directories from the filesystem image. */ 566 567 int remove_dirs(ext2_filsys fs, int argc, char *argv[]) 568 { 569 return _remove(fs, argc, argv, 1); 570 } 571 572 /* Remove non-directories from the filesystem image. */ 573 574 int remove_non_dirs(ext2_filsys fs, int argc, char *argv[]) 575 { 576 return _remove(fs, argc, argv, 0); 577 } 578 579 580 581 /* Help message. */ 582 583 char help_text[] = "\ 584 Usage: %s [ <options> ] <image file> <operation> <filename>...\n\ 585 \n\ 586 File ownership options:\n\ 587 \n\ 588 -g GID Set group identifier for new files\n\ 589 -u UID Set user identifier for new files\n\ 590 \n\ 591 File permission options:\n\ 592 \n\ 593 -m MASK Set mode/permissions mask for new directories\n\ 594 \n\ 595 Operations:\n\ 596 \n\ 597 copy-in Copy files into a directory within the image\n\ 598 copy-out Copy files from the image into a directory\n\ 599 ls List files and directories within the image\n\ 600 mkdir Make directories within the image\n\ 601 rm Remove non-directory objects from the image\n\ 602 rmdir Remove directories from the image\n\ 603 "; 604 605 /* Operations exposed by the program. */ 606 607 struct operation 608 { 609 const char *name; 610 int (*fn)(ext2_filsys, int, char *[]); 611 }; 612 613 static struct operation operations[] = { 614 {"copy-in", copy_in}, 615 {"copy-out", copy_out}, 616 {"ls", list}, 617 {"mkdir", make_dirs}, 618 {"rm", remove_non_dirs}, 619 {"rmdir", remove_dirs}, 620 {NULL, NULL}, 621 }; 622 623 /* Main program. */ 624 625 int main(int argc, char *argv[]) 626 { 627 int flags = EXT2_FLAG_RW; // | EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS; 628 ext2_filsys fs = NULL; 629 errcode_t retval; 630 int exitcode = 0; 631 632 /* Program argument details. */ 633 634 char **args; 635 char *fsname, *operation, *filename; 636 int num_args; 637 struct operation *op; 638 639 /* Parse program options and initialise the argument details. */ 640 641 if (parse_options(argc, argv)) 642 return 1; 643 644 args = &argv[optind]; 645 num_args = argc - optind; 646 647 if (num_args < 3) 648 { 649 fprintf(stderr, help_text, argv[0]); 650 return 1; 651 } 652 653 /* Open the filesystem image using the POSIX file access mechanism. */ 654 655 fsname = args[0]; 656 657 retval = ext2fs_open(fsname, flags, 0, 0, unix_io_manager, &fs); 658 if (retval) 659 { 660 fprintf(stderr, "Could not open filesystem: %s\n", fsname); 661 return 1; 662 } 663 664 //fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 665 666 retval = ext2fs_read_bitmaps(fs); 667 if (retval) 668 { 669 fprintf(stderr, "Could not read bitmaps: %s\n", fsname); 670 return 1; 671 } 672 673 /* Perform the requested operation. */ 674 675 operation = args[1]; 676 args = &args[2]; 677 num_args -= 2; 678 679 for (op = &operations[0]; op->name != NULL; op++) 680 { 681 if (!strcmp(operation, op->name)) 682 { 683 exitcode = op->fn(fs, num_args, args); 684 if (exitcode) 685 fprintf(stderr, "Operation failed: %s\n", operation); 686 break; 687 } 688 } 689 690 if (op->name == NULL) 691 { 692 fprintf(stderr, "Operation not recognised: %s\n", operation); 693 exitcode = 1; 694 } 695 696 /* Close the filesystem image. */ 697 698 retval = ext2fs_flush(fs); 699 if (retval) 700 { 701 fprintf(stderr, "Error flushing filesystem: %s\n", fsname); 702 exitcode = 1; 703 } 704 705 retval = ext2fs_close(fs); 706 if (retval) 707 { 708 fprintf(stderr, "Error closing filesystem: %s\n", fsname); 709 exitcode = 1; 710 } 711 712 ext2fs_free(fs); 713 return exitcode; 714 } 715 716 /* vim: tabstop=4 expandtab shiftwidth=4 717 */