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