1 /* 2 * Filesystem access functions. 3 * 4 * Copyright (C) 2019, 2021, 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 <string.h> 23 24 #include <ext2fs/ext2fs.h> 25 26 #include "image.h" 27 #include "path.h" 28 29 30 31 /* Link an inode within the given target directory having the given basename. */ 32 33 static errcode_t _image_link(ext2_filsys fs, ext2_ino_t ino_target, 34 const char *basename, ext2_ino_t ino_file, 35 int flags) 36 { 37 errcode_t retval; 38 int retry; 39 40 /* Connect the inode to its parent. */ 41 42 for (retry = 0; retry <= 1; retry++) 43 { 44 retval = ext2fs_link(fs, ino_target, basename, ino_file, flags); 45 46 if (!retval) 47 break; 48 else if (retry) 49 return retval; 50 51 /* Expand the directory if necessary. */ 52 53 if (retval == EXT2_ET_DIR_NO_SPACE) 54 retval = ext2fs_expand_dir(fs, ino_target); 55 56 if (retval) 57 return retval; 58 } 59 60 return 0; 61 } 62 63 /* Get entry details for a directory to be removed. */ 64 65 static int _image_get_parent_entry(struct ext2_dir_entry *dir_entry, 66 int offset, int blocksize, char *buf, 67 void *priv_data) 68 { 69 ext2_ino_t *ino = (ext2_ino_t *) priv_data; 70 71 (void) offset; (void) blocksize; (void) buf; 72 73 if (!strcmp(dir_entry->name, "..")) 74 { 75 *ino = dir_entry->inode; 76 return DIRENT_ABORT; 77 } 78 79 return 0; 80 } 81 82 /* Test for objects in a directory. */ 83 84 static int _image_test_directory(struct ext2_dir_entry *dir_entry, 85 int offset, int blocksize, char *buf, 86 void *priv_data) 87 { 88 int *nonempty = (int *) priv_data; 89 90 (void) offset; (void) blocksize; (void) buf; 91 92 if (strcmp(dir_entry->name, ".") && strcmp(dir_entry->name, "..")) 93 { 94 *nonempty = 1; 95 return DIRENT_ABORT; 96 } 97 98 return 0; 99 } 100 101 /* Update the parent entry of a renamed directory. */ 102 103 static int _image_update_parent_entry(struct ext2_dir_entry *dir_entry, 104 int offset, int blocksize, char *buf, 105 void *priv_data) 106 { 107 ext2_ino_t *ino = (ext2_ino_t *) priv_data; 108 109 (void) offset; (void) blocksize; (void) buf; 110 111 if (!strcmp(dir_entry->name, "..")) 112 { 113 dir_entry->inode = *ino; 114 return DIRENT_CHANGED | DIRENT_ABORT; 115 } 116 else 117 return 0; 118 } 119 120 121 122 /* Adapter function. */ 123 124 errcode_t image_access_by_path(ext2_filsys fs, const char *path, 125 errcode_t (*op)(ext2_filsys, ext2_ino_t), 126 ext2_ino_t *ino) 127 { 128 errcode_t retval = image_find_by_path(fs, path, ino); 129 130 if (retval) 131 return retval; 132 133 return op(fs, *ino); 134 } 135 136 137 138 /* Create an inode for a file. */ 139 140 errcode_t image_create_file(ext2_filsys fs, ext2_ino_t ino_target, 141 const char *basename, __u16 mode, 142 __u16 uid, __u16 gid, ext2_ino_t *ino_file) 143 { 144 struct ext2_inode inode_file; 145 errcode_t retval; 146 147 /* Without an inode, create a new one. */ 148 149 retval = ext2fs_new_inode(fs, ino_target, LINUX_S_IFREG | mode, 0, ino_file); 150 if (retval) 151 return retval; 152 153 _image_link(fs, ino_target, basename, *ino_file, EXT2_FT_REG_FILE); 154 155 /* Make sure that subsequent files employ different inodes. */ 156 157 ext2fs_inode_alloc_stats2(fs, *ino_file, 1, 0); 158 159 /* Populate the inode details. */ 160 161 image_set_metadata(&inode_file, 1, LINUX_S_IFREG | mode, uid, gid); 162 inode_file.i_links_count++; 163 164 return ext2fs_write_new_inode(fs, *ino_file, &inode_file); 165 } 166 167 /* Test for an empty directory. */ 168 169 errcode_t image_dir_empty_by_inode(ext2_filsys fs, ext2_ino_t ino) 170 { 171 /* Initialise the directory listing processing state. */ 172 173 int nonempty = 0; 174 errcode_t retval = ext2fs_dir_iterate(fs, ino, 0, NULL, 175 _image_test_directory, &nonempty); 176 177 if (retval) 178 return retval; 179 180 /* NOTE: Need a proper error here. */ 181 182 return nonempty; 183 } 184 185 /* Test for an empty directory using its path. */ 186 187 errcode_t image_dir_empty_by_path(ext2_filsys fs, const char *path, 188 ext2_ino_t *ino) 189 { 190 return image_access_by_path(fs, path, image_dir_empty_by_inode, ino); 191 } 192 193 /* Find any parent reference. */ 194 195 errcode_t image_dir_get_parent(ext2_filsys fs, ext2_ino_t ino, ext2_ino_t *ino_parent) 196 { 197 /* Initialise the directory listing processing state. */ 198 199 *ino_parent = 0; 200 201 /* A directory needs to be inspected for the .. entry in order to obtain the 202 parent directory. */ 203 204 return ext2fs_dir_iterate(fs, ino, 0, NULL, _image_get_parent_entry, 205 ino_parent); 206 } 207 208 /* Return the appropriate ext2 file type value for the given mode value. */ 209 210 int image_file_type(int mode) 211 { 212 switch (mode & LINUX_S_IFMT) 213 { 214 case LINUX_S_IFSOCK: return EXT2_FT_SOCK; 215 case LINUX_S_IFLNK: return EXT2_FT_SYMLINK; 216 case LINUX_S_IFREG: return EXT2_FT_REG_FILE; 217 case LINUX_S_IFBLK: return EXT2_FT_BLKDEV; 218 case LINUX_S_IFDIR: return EXT2_FT_DIR; 219 case LINUX_S_IFCHR: return EXT2_FT_CHRDEV; 220 case LINUX_S_IFIFO: return EXT2_FT_FIFO; 221 222 /* NOTE: Perhaps signal an error. */ 223 224 default: return EXT2_FT_REG_FILE; 225 } 226 } 227 228 /* Find an object with the given path. */ 229 230 errcode_t image_find_by_path(ext2_filsys fs, const char *path, ext2_ino_t *ino) 231 { 232 return image_resolve_by_path(fs, &path, ino); 233 } 234 235 /* Find an object with the given path. */ 236 237 errcode_t image_resolve_by_path(ext2_filsys fs, const char **path, 238 ext2_ino_t *ino) 239 { 240 char *buf; 241 ext2_ino_t ino_dir; 242 errcode_t retval; 243 244 /* Initialise the inode in case of early failure. */ 245 246 *ino = 0; 247 248 retval = ext2fs_get_mem(fs->blocksize, &buf); 249 250 if (retval) 251 return retval; 252 253 /* Skip any leading root marker. */ 254 255 if (**path == '/') 256 (*path)++; 257 258 if (!**path) 259 *ino = EXT2_ROOT_INO; 260 261 /* Start at the root. */ 262 263 ino_dir = EXT2_ROOT_INO; 264 265 /* With any remaining path, find the next component. */ 266 267 while (**path) 268 { 269 retval = image_resolve_next(fs, ino_dir, path, buf, ino); 270 if (retval) 271 { 272 *ino = ino_dir; 273 break; 274 } 275 276 /* Move into the found object for searching the next component. */ 277 278 ino_dir = *ino; 279 } 280 281 ext2fs_free_mem(&buf); 282 283 return retval; 284 } 285 286 /* Find an object in the given directory with the given name, updating the name 287 reference to refer to the next component. */ 288 289 errcode_t image_resolve_next(ext2_filsys fs, ext2_ino_t ino_dir, 290 const char **basename, char *buf, ext2_ino_t *ino) 291 { 292 const char *end = path_component_end(*basename); 293 errcode_t retval; 294 295 /* Find the basename in the directory. */ 296 297 retval = ext2fs_lookup(fs, ino_dir, *basename, end - *basename, buf, ino); 298 299 /* Update the current component. */ 300 301 if (!retval) 302 *basename = path_component_next(end); 303 304 return retval; 305 } 306 307 /* Find an object in the given directory with the given name. */ 308 309 errcode_t image_find_file(ext2_filsys fs, const char *dirname, 310 const char *basename, ext2_ino_t *ino) 311 { 312 char path[strlen(dirname) + strlen(basename) + 2]; 313 314 strcpy(path, dirname); 315 strcat(path, "/"); 316 strcat(path, basename); 317 318 return image_find_by_path(fs, path, ino); 319 } 320 321 /* Find an object in the given directory with the given inode. */ 322 323 errcode_t image_find_file_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 324 const char *basename, ext2_ino_t *ino) 325 { 326 char *buf; 327 errcode_t retval = ext2fs_get_mem(fs->blocksize, &buf); 328 329 if (retval) 330 return retval; 331 332 retval = ext2fs_lookup(fs, ino_parent, basename, strlen(basename), buf, ino); 333 334 ext2fs_free_mem(&buf); 335 336 return retval; 337 } 338 339 /* Obtain the inode for the object with the given path. */ 340 341 errcode_t image_inode(ext2_filsys fs, const char *path, 342 struct ext2_inode *inode) 343 { 344 ext2_ino_t ino; 345 errcode_t retval; 346 347 retval = image_find_by_path(fs, path, &ino); 348 349 if (retval) 350 return retval; 351 352 return ext2fs_read_inode(fs, ino, inode); 353 } 354 355 /* Update the reference count for the given inode. */ 356 357 errcode_t image_inode_refcount_update(ext2_filsys fs, ext2_ino_t ino, int change) 358 { 359 struct ext2_inode inode; 360 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 361 362 if (retval) 363 return retval; 364 365 /* NOTE: FUSE implementation tests for > 1. */ 366 367 if ((change < 0) && (inode.i_links_count + change < 0)) 368 inode.i_links_count = 0; 369 else 370 inode.i_links_count += change; 371 372 /* NOTE: Update modification time. */ 373 374 return ext2fs_write_inode(fs, ino, &inode); 375 } 376 377 /* List a directory. */ 378 379 errcode_t image_list_dir(ext2_filsys fs, const char *path, 380 int (*proc)(struct ext2_dir_entry *, int, int, char *, 381 void *), 382 void *data) 383 { 384 char *buf; 385 ext2_ino_t ino; 386 errcode_t retval; 387 388 retval = ext2fs_get_mem(fs->blocksize, &buf); 389 if (retval) 390 return retval; 391 392 /* Locate the object and test whether it is a directory. */ 393 394 retval = image_find_by_path(fs, path, &ino); 395 396 if (retval) 397 { 398 ext2fs_free_mem(&buf); 399 return retval; 400 } 401 402 if (!image_isdir_by_inode(fs, ino)) 403 return 1; 404 405 /* List the directory contents. */ 406 407 retval = ext2fs_dir_iterate(fs, ino, 0, buf, proc, data); 408 409 if (retval) 410 { 411 ext2fs_free_mem(&buf); 412 return retval; 413 } 414 415 ext2fs_free_mem(&buf); 416 return 0; 417 } 418 419 /* Make a directory in the given directory having the given name and 420 metadata. */ 421 422 errcode_t image_make_dir(ext2_filsys fs, ext2_ino_t ino_dir, 423 const char *basename, __u16 mode, 424 __u16 uid, __u16 gid, ext2_ino_t *ino) 425 { 426 struct ext2_inode inode_dir; 427 errcode_t retval = 0; 428 429 /* Create an inode in the directory. */ 430 431 retval = ext2fs_new_inode(fs, ino_dir, LINUX_S_IFDIR | mode, 0, ino); 432 if (retval) 433 return retval; 434 435 /* Make the directory and update the metadata (due to ext2fs_mkdir 436 limitation). */ 437 438 retval = ext2fs_mkdir(fs, ino_dir, *ino, basename); 439 if (retval) 440 return retval; 441 442 retval = ext2fs_read_inode(fs, *ino, &inode_dir); 443 if (retval) 444 return retval; 445 446 image_set_metadata(&inode_dir, 0, LINUX_S_IFDIR | mode, uid, gid); 447 return ext2fs_write_inode(fs, *ino, &inode_dir); 448 } 449 450 /* Make a directory in the given directory, updating the name reference to refer 451 to the next component. */ 452 453 errcode_t image_make_next_dir(ext2_filsys fs, ext2_ino_t ino_dir, 454 const char **basename, __u16 mode, __u16 uid, 455 __u16 gid, ext2_ino_t *ino) 456 { 457 char *end = (char *) path_component_end(*basename); 458 char endchar = *end; 459 errcode_t retval = 0; 460 461 /* Delimit the basename and make a directory using the inode. */ 462 463 if (endchar) 464 *end = '\0'; 465 466 /* Do not create directories for empty components. */ 467 468 if (**basename) 469 retval = image_make_dir(fs, ino_dir, *basename, mode, uid, gid, ino); 470 471 /* Restore the path separator and update the current component. */ 472 473 if (endchar) 474 *end = '/'; 475 476 if (!retval) 477 *basename = path_component_next(end); 478 479 return retval; 480 } 481 482 /* Make directories descending to the given path. */ 483 484 errcode_t image_make_dirs(ext2_filsys fs, const char **path, 485 ext2_ino_t ino_dir, __u16 mode, __u16 uid, __u16 gid) 486 { 487 ext2_ino_t ino; 488 errcode_t retval; 489 490 while (**path) 491 { 492 retval = image_make_next_dir(fs, ino_dir, path, mode, uid, gid, &ino); 493 if (retval) 494 return retval; 495 496 /* Move into the created object for handling the next component. */ 497 498 ino_dir = ino; 499 } 500 501 return 0; 502 } 503 504 /* Remove an inode. */ 505 506 errcode_t image_remove_by_inode(ext2_filsys fs, ext2_ino_t ino) 507 { 508 struct ext2_inode_large inode; 509 ext2_ino_t ino_parent = 0; 510 errcode_t retval; 511 int isdir = image_isdir_by_inode(fs, ino); 512 513 if (isdir) 514 { 515 retval = image_dir_get_parent(fs, ino, &ino_parent); 516 if (retval) 517 return retval; 518 } 519 520 retval = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, 521 sizeof(inode)); 522 523 /* Handle invalid inodes, ignore unreferenced inodes. */ 524 525 if (retval) 526 return retval; 527 528 if (!inode.i_links_count) 529 return 0; 530 531 /* Decrement the reference count. With no more references to the inode, 532 remove its resources. Directories appear to need a double decrement. */ 533 534 inode.i_links_count--; 535 536 if (isdir && inode.i_links_count) 537 inode.i_links_count--; 538 539 if (!inode.i_links_count) 540 { 541 /* NOTE: Update deletion time. */ 542 543 retval = ext2fs_free_ext_attr(fs, ino, &inode); 544 545 if (!retval) 546 { 547 /* Deallocate blocks, if appropriate. ~0ULL as the end represents 548 truncation. */ 549 550 if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *) &inode)) 551 { 552 retval = ext2fs_punch(fs, ino, (struct ext2_inode *) &inode, 553 NULL, 0, ~0ULL); 554 555 /* Update allocation statistics. */ 556 557 if (!retval) 558 ext2fs_inode_alloc_stats2(fs, ino, -1, 559 LINUX_S_ISDIR(inode.i_mode)); 560 } 561 } 562 } 563 564 retval = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *) &inode, 565 sizeof(inode)); 566 567 /* Decrement the parent reference count for directories. */ 568 569 if (!retval && !inode.i_links_count && ino_parent) 570 retval = image_inode_refcount_update(fs, ino_parent, -1); 571 572 return retval; 573 } 574 575 /* Remove a directory entry using its full path. */ 576 577 errcode_t image_remove_by_path(ext2_filsys fs, const char *path, 578 ext2_ino_t *ino) 579 { 580 return image_access_by_path(fs, path, image_remove_by_inode, ino); 581 } 582 583 /* Rename a file. */ 584 585 errcode_t image_rename(ext2_filsys fs, ext2_ino_t source, 586 ext2_ino_t source_parent, const char *source_basename, 587 ext2_ino_t target_parent, const char *target_basename) 588 { 589 errcode_t retval; 590 struct ext2_inode source_inode; 591 592 /* NOTE: Should check for space. */ 593 594 /* Obtain the source object. */ 595 596 retval = ext2fs_read_inode(fs, source, &source_inode); 597 598 if (retval) 599 return retval; 600 601 /* Link from the target parent. */ 602 603 retval = _image_link(fs, target_parent, target_basename, source, 604 image_file_type(source_inode.i_mode)); 605 606 if (retval) 607 return retval; 608 609 if (image_isdir_by_inode(fs, source)) 610 { 611 /* Update the link count for the target. */ 612 613 retval = image_inode_refcount_update(fs, target_parent, 1); 614 615 if (retval) 616 return retval; 617 618 /* A directory needs its .. entry updating to refer to its new 619 parent. */ 620 621 retval = ext2fs_dir_iterate(fs, source, 0, NULL, 622 _image_update_parent_entry, &target_parent); 623 624 /* Update the link count for the source. */ 625 626 retval = image_inode_refcount_update(fs, source_parent, -1); 627 628 if (retval) 629 return retval; 630 } 631 632 /* Unlink from the source parent, doing so by name because the file is now 633 already linked from the target parent, and when the parents are the same, 634 unlinking by inode could just cause the file to disappear from the 635 catalogue. */ 636 637 retval = image_unlink_by_name(fs, source_parent, source_basename); 638 639 if (retval) 640 return retval; 641 642 return ext2fs_flush2(fs, 0); 643 } 644 645 /* Set the mode, user and group metadata for a file. */ 646 647 void image_set_metadata(struct ext2_inode *inode, int clean, __u16 mode, 648 __u16 uid, __u16 gid) 649 { 650 if (clean) 651 memset(inode, 0, sizeof(*inode)); 652 653 inode->i_mode = mode; 654 inode->i_uid = uid; 655 inode->i_gid = gid; 656 } 657 658 /* Copy file metadata into a stat structure. */ 659 660 errcode_t image_stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *st) 661 { 662 struct ext2_inode inode; 663 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 664 665 if (retval) 666 return retval; 667 668 /* NOTE: Could access large inode members and other information. */ 669 670 st->st_dev = 0; /* device identifier */ 671 st->st_ino = ino; 672 st->st_mode = inode.i_mode; 673 st->st_nlink = inode.i_links_count; 674 st->st_uid = inode_uid(inode); 675 st->st_gid = inode_gid(inode); 676 st->st_rdev = 0; /* special file device identifier */ 677 st->st_size = EXT2_I_SIZE(&inode); 678 st->st_blksize = fs->blocksize; 679 st->st_blocks = inode.i_blocks; /* number of 512 byte blocks allocated */ 680 st->st_atim.tv_sec = inode.i_atime; 681 st->st_atim.tv_nsec = 0; /* nanosecond resolution */ 682 st->st_mtim.tv_sec = inode.i_mtime; 683 st->st_mtim.tv_nsec = 0; 684 st->st_ctim.tv_sec = inode.i_ctime; 685 st->st_ctim.tv_nsec = 0; 686 687 return 0; 688 } 689 690 /* Unlink a directory entry by name. */ 691 692 errcode_t image_unlink_by_name(ext2_filsys fs, ext2_ino_t ino_parent, 693 const char *basename) 694 { 695 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 696 } 697 698 /* Unlink a directory entry by full path. */ 699 700 errcode_t image_unlink_by_path(ext2_filsys fs, const char *path) 701 { 702 char _path[strlen(path) + 1]; 703 char *basename; 704 ext2_ino_t ino_parent; 705 errcode_t retval; 706 707 /* Copy the path and split it. */ 708 709 strcpy(_path, path); 710 basename = path_split(_path); 711 712 /* Determine the parent directory. */ 713 714 if (basename != _path) 715 { 716 retval = image_find_by_path(fs, _path, &ino_parent); 717 718 if (retval) 719 return retval; 720 } 721 else 722 ino_parent = EXT2_ROOT_INO; 723 724 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 725 } 726 727 /* Unlink a directory entry by inode number. */ 728 729 errcode_t image_unlink_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 730 ext2_ino_t ino) 731 { 732 return ext2fs_unlink(fs, ino_parent, 0, ino, 0); 733 } 734 735 736 737 /* Test object presence and types. */ 738 739 int image_exists(ext2_filsys fs, const char *path) 740 { 741 ext2_ino_t ino; 742 743 return !image_find_by_path(fs, path, &ino); 744 } 745 746 int image_isdir_by_inode(ext2_filsys fs, ext2_ino_t ino) 747 { 748 struct ext2_inode inode; 749 750 if (ext2fs_read_inode(fs, ino, &inode)) 751 return 0; 752 753 return LINUX_S_ISDIR(inode.i_mode); 754 } 755 756 int image_isdir_by_path(ext2_filsys fs, const char *path) 757 { 758 ext2_ino_t ino; 759 760 if (image_find_by_path(fs, path, &ino)) 761 return 0; 762 763 return image_isdir_by_inode(fs, ino); 764 } 765 766 int image_isfile_by_inode(ext2_filsys fs, ext2_ino_t ino) 767 { 768 struct ext2_inode inode; 769 770 if (ext2fs_read_inode(fs, ino, &inode)) 771 return 0; 772 773 return LINUX_S_ISREG(inode.i_mode); 774 } 775 776 int image_isfile_by_path(ext2_filsys fs, const char *path) 777 { 778 ext2_ino_t ino; 779 780 if (image_find_by_path(fs, path, &ino)) 781 return 0; 782 783 return image_isfile_by_inode(fs, ino); 784 } 785 786 /* vim: tabstop=4 expandtab shiftwidth=4 787 */