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_find_path(fs, &target_remaining, &ino_target)) 230 { 231 /* Only a non-existent file in an existing directory is permitted. */ 232 233 if (!_image_isdir(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(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(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(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_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_find_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 directories from the filesystem image. */ 497 498 int remove_dirs(ext2_filsys fs, int argc, char *argv[]) 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_exists(fs, path)) 513 { 514 fprintf(stderr, "Not found: %s\n", path); 515 return 1; 516 } 517 518 /* Insist on a directory. */ 519 520 if (!image_isdir(fs, path)) 521 { 522 fprintf(stderr, "Not a directory: %s\n", path); 523 return 1; 524 } 525 526 /* Test for an empty directory. */ 527 528 if (image_dir_empty_by_path(fs, path, &ino)) 529 { 530 fprintf(stderr, "Directory not empty: %s\n", path); 531 return 1; 532 } 533 534 /* Unlink the directory. */ 535 536 if (image_unlink_by_path(fs, path)) 537 { 538 fprintf(stderr, "Could not unlink directory: %s\n", path); 539 return 1; 540 } 541 542 /* Remove the directory. */ 543 544 if (image_remove_by_inode(fs, ino)) 545 { 546 fprintf(stderr, "Could not remove directory: %s\n", path); 547 return 1; 548 } 549 } 550 551 return 0; 552 } 553 554 555 556 /* Help message. */ 557 558 char help_text[] = "\ 559 Usage: %s [ <options> ] <image file> <operation> <filename>...\n\ 560 \n\ 561 File ownership options:\n\ 562 \n\ 563 -g GID Set group identifier for new files\n\ 564 -u UID Set user identifier for new files\n\ 565 \n\ 566 File permission options:\n\ 567 \n\ 568 -m MASK Set mode/permissions mask for new directories\n\ 569 \n\ 570 Operations:\n\ 571 \n\ 572 copy-in Copy files into a directory within the image\n\ 573 copy-out Copy files from the image into a directory\n\ 574 ls List files and directories within the image\n\ 575 mkdir Make directories within the image\n\ 576 rmdir Remove directories from the image\n\ 577 "; 578 579 /* Operations exposed by the program. */ 580 581 struct operation 582 { 583 const char *name; 584 int (*fn)(ext2_filsys, int, char *[]); 585 }; 586 587 static struct operation operations[] = { 588 {"copy-in", copy_in}, 589 {"copy-out", copy_out}, 590 {"ls", list}, 591 {"mkdir", make_dirs}, 592 {"rmdir", remove_dirs}, 593 {NULL, NULL}, 594 }; 595 596 /* Main program. */ 597 598 int main(int argc, char *argv[]) 599 { 600 int flags = EXT2_FLAG_RW; // | EXT2_FLAG_SOFTSUPP_FEATURES | EXT2_FLAG_64BITS; 601 ext2_filsys fs = NULL; 602 errcode_t retval; 603 int exitcode = 0; 604 605 /* Program argument details. */ 606 607 char **args; 608 char *fsname, *operation, *filename; 609 int num_args; 610 struct operation *op; 611 612 /* Parse program options and initialise the argument details. */ 613 614 if (parse_options(argc, argv)) 615 return 1; 616 617 args = &argv[optind]; 618 num_args = argc - optind; 619 620 if (num_args < 3) 621 { 622 fprintf(stderr, help_text, argv[0]); 623 return 1; 624 } 625 626 /* Open the filesystem image using the POSIX file access mechanism. */ 627 628 fsname = args[0]; 629 630 retval = ext2fs_open(fsname, flags, 0, 0, unix_io_manager, &fs); 631 if (retval) 632 { 633 fprintf(stderr, "Could not open filesystem: %s\n", fsname); 634 return 1; 635 } 636 637 //fs->default_bitmap_type = EXT2FS_BMAP64_RBTREE; 638 639 retval = ext2fs_read_bitmaps(fs); 640 if (retval) 641 { 642 fprintf(stderr, "Could not read bitmaps: %s\n", fsname); 643 return 1; 644 } 645 646 /* Perform the requested operation. */ 647 648 operation = args[1]; 649 args = &args[2]; 650 num_args -= 2; 651 652 for (op = &operations[0]; op->name != NULL; op++) 653 { 654 if (!strcmp(operation, op->name)) 655 { 656 exitcode = op->fn(fs, num_args, args); 657 if (exitcode) 658 fprintf(stderr, "Operation failed: %s\n", operation); 659 break; 660 } 661 } 662 663 if (op->name == NULL) 664 { 665 fprintf(stderr, "Operation not recognised: %s\n", operation); 666 exitcode = 1; 667 } 668 669 /* Close the filesystem image. */ 670 671 retval = ext2fs_flush(fs); 672 if (retval) 673 { 674 fprintf(stderr, "Error flushing filesystem: %s\n", fsname); 675 exitcode = 1; 676 } 677 678 retval = ext2fs_close(fs); 679 if (retval) 680 { 681 fprintf(stderr, "Error closing filesystem: %s\n", fsname); 682 exitcode = 1; 683 } 684 685 ext2fs_free(fs); 686 return exitcode; 687 } 688 689 /* vim: tabstop=4 expandtab shiftwidth=4 690 */