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