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