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 /* Obtain the inode for the object with the given path. */ 322 323 errcode_t image_inode(ext2_filsys fs, const char *path, 324 struct ext2_inode *inode) 325 { 326 ext2_ino_t ino; 327 errcode_t retval; 328 329 retval = image_find_by_path(fs, path, &ino); 330 331 if (retval) 332 return retval; 333 334 return ext2fs_read_inode(fs, ino, inode); 335 } 336 337 /* Update the reference count for the given inode. */ 338 339 errcode_t image_inode_refcount_update(ext2_filsys fs, ext2_ino_t ino, int change) 340 { 341 struct ext2_inode inode; 342 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 343 344 if (retval) 345 return retval; 346 347 /* NOTE: FUSE implementation tests for > 1. */ 348 349 if ((change < 0) && (inode.i_links_count + change < 0)) 350 inode.i_links_count = 0; 351 else 352 inode.i_links_count += change; 353 354 /* NOTE: Update modification time. */ 355 356 return ext2fs_write_inode(fs, ino, &inode); 357 } 358 359 /* List a directory. */ 360 361 errcode_t image_list_dir(ext2_filsys fs, const char *path, 362 int (*proc)(struct ext2_dir_entry *, int, int, char *, 363 void *), 364 void *data) 365 { 366 char *buf; 367 ext2_ino_t ino; 368 errcode_t retval; 369 370 retval = ext2fs_get_mem(fs->blocksize, &buf); 371 if (retval) 372 return retval; 373 374 /* Locate the object and test whether it is a directory. */ 375 376 retval = image_find_by_path(fs, path, &ino); 377 378 if (retval) 379 { 380 ext2fs_free_mem(&buf); 381 return retval; 382 } 383 384 if (!image_isdir_by_inode(fs, ino)) 385 return 1; 386 387 /* List the directory contents. */ 388 389 retval = ext2fs_dir_iterate(fs, ino, 0, buf, proc, data); 390 391 if (retval) 392 { 393 ext2fs_free_mem(&buf); 394 return retval; 395 } 396 397 ext2fs_free_mem(&buf); 398 return 0; 399 } 400 401 /* Make a directory in the given directory having the given name and 402 metadata. */ 403 404 errcode_t image_make_dir(ext2_filsys fs, ext2_ino_t ino_dir, 405 const char *basename, __u16 mode, 406 __u16 uid, __u16 gid, ext2_ino_t *ino) 407 { 408 struct ext2_inode inode_dir; 409 errcode_t retval = 0; 410 411 /* Create an inode in the directory. */ 412 413 retval = ext2fs_new_inode(fs, ino_dir, LINUX_S_IFDIR | mode, 0, ino); 414 if (retval) 415 return retval; 416 417 /* Make the directory and update the metadata (due to ext2fs_mkdir 418 limitation). */ 419 420 retval = ext2fs_mkdir(fs, ino_dir, *ino, basename); 421 if (retval) 422 return retval; 423 424 retval = ext2fs_read_inode(fs, *ino, &inode_dir); 425 if (retval) 426 return retval; 427 428 image_set_metadata(&inode_dir, 0, LINUX_S_IFDIR | mode, uid, gid); 429 return ext2fs_write_inode(fs, *ino, &inode_dir); 430 } 431 432 /* Make a directory in the given directory, updating the name reference to refer 433 to the next component. */ 434 435 errcode_t image_make_next_dir(ext2_filsys fs, ext2_ino_t ino_dir, 436 const char **basename, __u16 mode, __u16 uid, 437 __u16 gid, ext2_ino_t *ino) 438 { 439 char *end = (char *) path_component_end(*basename); 440 char endchar = *end; 441 errcode_t retval = 0; 442 443 /* Delimit the basename and make a directory using the inode. */ 444 445 if (endchar) 446 *end = '\0'; 447 448 /* Do not create directories for empty components. */ 449 450 if (**basename) 451 retval = image_make_dir(fs, ino_dir, *basename, mode, uid, gid, ino); 452 453 /* Restore the path separator and update the current component. */ 454 455 if (endchar) 456 *end = '/'; 457 458 if (!retval) 459 *basename = path_component_next(end); 460 461 return retval; 462 } 463 464 /* Make directories descending to the given path. */ 465 466 errcode_t image_make_dirs(ext2_filsys fs, const char **path, 467 ext2_ino_t ino_dir, __u16 mode, __u16 uid, __u16 gid) 468 { 469 ext2_ino_t ino; 470 errcode_t retval; 471 472 while (**path) 473 { 474 retval = image_make_next_dir(fs, ino_dir, path, mode, uid, gid, &ino); 475 if (retval) 476 return retval; 477 478 /* Move into the created object for handling the next component. */ 479 480 ino_dir = ino; 481 } 482 483 return 0; 484 } 485 486 /* Remove an inode. */ 487 488 errcode_t image_remove_by_inode(ext2_filsys fs, ext2_ino_t ino) 489 { 490 struct ext2_inode_large inode; 491 ext2_ino_t ino_parent = 0; 492 errcode_t retval; 493 int isdir = image_isdir_by_inode(fs, ino); 494 495 if (isdir) 496 { 497 retval = image_dir_get_parent(fs, ino, &ino_parent); 498 if (retval) 499 return retval; 500 } 501 502 retval = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, 503 sizeof(inode)); 504 505 /* Handle invalid inodes, ignore unreferenced inodes. */ 506 507 if (retval) 508 return retval; 509 510 if (!inode.i_links_count) 511 return 0; 512 513 /* Decrement the reference count. With no more references to the inode, 514 remove its resources. Directories appear to need a double decrement. */ 515 516 inode.i_links_count--; 517 518 if (isdir && inode.i_links_count) 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_refcount_update(fs, ino_parent, -1); 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; 573 574 /* NOTE: Should check for space. */ 575 576 /* Obtain the source object. */ 577 578 retval = ext2fs_read_inode(fs, source, &source_inode); 579 580 if (retval) 581 return retval; 582 583 /* Link from the target parent. */ 584 585 retval = _image_link(fs, target_parent, target_basename, source, 586 image_file_type(source_inode.i_mode)); 587 588 if (retval) 589 return retval; 590 591 if (image_isdir_by_inode(fs, source)) 592 { 593 /* Update the link count for the target. */ 594 595 retval = image_inode_refcount_update(fs, target_parent, 1); 596 597 if (retval) 598 return retval; 599 600 /* A directory needs its .. entry updating to refer to its new 601 parent. */ 602 603 retval = ext2fs_dir_iterate(fs, source, 0, NULL, 604 _image_update_parent_entry, &target_parent); 605 606 /* Update the link count for the source. */ 607 608 retval = image_inode_refcount_update(fs, source_parent, -1); 609 610 if (retval) 611 return retval; 612 } 613 614 /* Unlink from the source parent, doing so by name because the file is now 615 already linked from the target parent, and when the parents are the same, 616 unlinking by inode could just cause the file to disappear from the 617 catalogue. */ 618 619 retval = image_unlink_by_name(fs, source_parent, source_basename); 620 621 if (retval) 622 return retval; 623 624 return ext2fs_flush2(fs, 0); 625 } 626 627 /* Set the mode, user and group metadata for a file. */ 628 629 void image_set_metadata(struct ext2_inode *inode, int clean, __u16 mode, 630 __u16 uid, __u16 gid) 631 { 632 if (clean) 633 memset(inode, 0, sizeof(*inode)); 634 635 inode->i_mode = mode; 636 inode->i_uid = uid; 637 inode->i_gid = gid; 638 } 639 640 /* Copy file metadata into a stat structure. */ 641 642 errcode_t image_stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *st) 643 { 644 struct ext2_inode inode; 645 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 646 647 if (retval) 648 return retval; 649 650 /* NOTE: Could access large inode members and other information. */ 651 652 st->st_dev = 0; /* device identifier */ 653 st->st_ino = ino; 654 st->st_mode = inode.i_mode; 655 st->st_nlink = inode.i_links_count; 656 st->st_uid = inode_uid(inode); 657 st->st_gid = inode_gid(inode); 658 st->st_rdev = 0; /* special file device identifier */ 659 st->st_size = EXT2_I_SIZE(&inode); 660 st->st_blksize = fs->blocksize; 661 st->st_blocks = inode.i_blocks; /* number of 512 byte blocks allocated */ 662 st->st_atim.tv_sec = inode.i_atime; 663 st->st_atim.tv_nsec = 0; /* nanosecond resolution */ 664 st->st_mtim.tv_sec = inode.i_mtime; 665 st->st_mtim.tv_nsec = 0; 666 st->st_ctim.tv_sec = inode.i_ctime; 667 st->st_ctim.tv_nsec = 0; 668 669 return 0; 670 } 671 672 /* Unlink a directory entry by name. */ 673 674 errcode_t image_unlink_by_name(ext2_filsys fs, ext2_ino_t ino_parent, 675 const char *basename) 676 { 677 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 678 } 679 680 /* Unlink a directory entry by full path. */ 681 682 errcode_t image_unlink_by_path(ext2_filsys fs, const char *path) 683 { 684 char _path[strlen(path) + 1]; 685 char *basename; 686 ext2_ino_t ino_parent; 687 errcode_t retval; 688 689 /* Copy the path and split it. */ 690 691 strcpy(_path, path); 692 basename = path_split(_path); 693 694 /* Determine the parent directory. */ 695 696 if (basename != _path) 697 { 698 retval = image_find_by_path(fs, _path, &ino_parent); 699 700 if (retval) 701 return retval; 702 } 703 else 704 ino_parent = EXT2_ROOT_INO; 705 706 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 707 } 708 709 /* Unlink a directory entry by inode number. */ 710 711 errcode_t image_unlink_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 712 ext2_ino_t ino) 713 { 714 return ext2fs_unlink(fs, ino_parent, 0, ino, 0); 715 } 716 717 718 719 /* Test object presence and types. */ 720 721 int image_exists(ext2_filsys fs, const char *path) 722 { 723 ext2_ino_t ino; 724 725 return !image_find_by_path(fs, path, &ino); 726 } 727 728 int image_isdir_by_inode(ext2_filsys fs, ext2_ino_t ino) 729 { 730 struct ext2_inode inode; 731 732 if (ext2fs_read_inode(fs, ino, &inode)) 733 return 0; 734 735 return LINUX_S_ISDIR(inode.i_mode); 736 } 737 738 int image_isdir_by_path(ext2_filsys fs, const char *path) 739 { 740 ext2_ino_t ino; 741 742 if (image_find_by_path(fs, path, &ino)) 743 return 0; 744 745 return image_isdir_by_inode(fs, ino); 746 } 747 748 int image_isfile_by_inode(ext2_filsys fs, ext2_ino_t ino) 749 { 750 struct ext2_inode inode; 751 752 if (ext2fs_read_inode(fs, ino, &inode)) 753 return 0; 754 755 return LINUX_S_ISREG(inode.i_mode); 756 } 757 758 int image_isfile_by_path(ext2_filsys fs, const char *path) 759 { 760 ext2_ino_t ino; 761 762 if (image_find_by_path(fs, path, &ino)) 763 return 0; 764 765 return image_isfile_by_inode(fs, ino); 766 } 767 768 /* vim: tabstop=4 expandtab shiftwidth=4 769 */