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