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 ext2_ino_t ino_dir; 241 errcode_t retval = 0; 242 243 /* Initialise the inode in case of early failure. */ 244 245 *ino = 0; 246 247 /* Skip any leading root marker. */ 248 249 if (**path == '/') 250 (*path)++; 251 252 if (!**path) 253 *ino = EXT2_ROOT_INO; 254 255 /* Start at the root. */ 256 257 ino_dir = EXT2_ROOT_INO; 258 259 /* With any remaining path, find the next component. */ 260 261 while (**path) 262 { 263 retval = image_resolve_next(fs, ino_dir, path, 0, ino); 264 if (retval) 265 { 266 *ino = ino_dir; 267 break; 268 } 269 270 /* Move into the found object for searching the next component. */ 271 272 ino_dir = *ino; 273 } 274 275 return retval; 276 } 277 278 /* Find an object in the given directory with the given name, updating the name 279 reference to refer to the next component. */ 280 281 errcode_t image_resolve_next(ext2_filsys fs, ext2_ino_t ino_dir, 282 const char **basename, char *buf, ext2_ino_t *ino) 283 { 284 const char *end = path_component_end(*basename); 285 errcode_t retval; 286 287 /* Find the basename in the directory. */ 288 289 retval = ext2fs_lookup(fs, ino_dir, *basename, end - *basename, buf, ino); 290 291 /* Update the current component. */ 292 293 if (!retval) 294 *basename = path_component_next(end); 295 296 return retval; 297 } 298 299 /* Find an object in the given directory with the given name. */ 300 301 errcode_t image_find_file(ext2_filsys fs, const char *dirname, 302 const char *basename, ext2_ino_t *ino) 303 { 304 char path[strlen(dirname) + strlen(basename) + 2]; 305 306 strcpy(path, dirname); 307 strcat(path, "/"); 308 strcat(path, basename); 309 310 return image_find_by_path(fs, path, ino); 311 } 312 313 /* Find an object in the given directory with the given inode. */ 314 315 errcode_t image_find_file_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 316 const char *basename, ext2_ino_t *ino) 317 { 318 return ext2fs_lookup(fs, ino_parent, basename, strlen(basename), 0, 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 ext2_ino_t ino; 367 errcode_t retval; 368 369 /* Locate the object and test whether it is a directory. */ 370 371 retval = image_find_by_path(fs, path, &ino); 372 373 if (retval) 374 return retval; 375 376 if (!image_isdir_by_inode(fs, ino)) 377 return 1; 378 379 /* List the directory contents. */ 380 381 retval = ext2fs_dir_iterate(fs, ino, 0, 0, proc, data); 382 383 if (retval) 384 return retval; 385 386 return 0; 387 } 388 389 /* Make a directory in the given directory having the given name and 390 metadata. */ 391 392 errcode_t image_make_dir(ext2_filsys fs, ext2_ino_t ino_dir, 393 const char *basename, __u16 mode, 394 __u16 uid, __u16 gid, ext2_ino_t *ino) 395 { 396 struct ext2_inode inode_dir; 397 errcode_t retval = 0; 398 399 /* Create an inode in the directory. */ 400 401 retval = ext2fs_new_inode(fs, ino_dir, LINUX_S_IFDIR | mode, 0, ino); 402 if (retval) 403 return retval; 404 405 /* Make the directory and update the metadata (due to ext2fs_mkdir 406 limitation). */ 407 408 retval = ext2fs_mkdir(fs, ino_dir, *ino, basename); 409 if (retval) 410 return retval; 411 412 retval = ext2fs_read_inode(fs, *ino, &inode_dir); 413 if (retval) 414 return retval; 415 416 image_set_metadata(&inode_dir, 0, LINUX_S_IFDIR | mode, uid, gid); 417 return ext2fs_write_inode(fs, *ino, &inode_dir); 418 } 419 420 /* Make a directory in the given directory, updating the name reference to refer 421 to the next component. */ 422 423 errcode_t image_make_next_dir(ext2_filsys fs, ext2_ino_t ino_dir, 424 const char **basename, __u16 mode, __u16 uid, 425 __u16 gid, ext2_ino_t *ino) 426 { 427 char *end = (char *) path_component_end(*basename); 428 char endchar = *end; 429 errcode_t retval = 0; 430 431 /* Delimit the basename and make a directory using the inode. */ 432 433 if (endchar) 434 *end = '\0'; 435 436 /* Do not create directories for empty components. */ 437 438 if (**basename) 439 retval = image_make_dir(fs, ino_dir, *basename, mode, uid, gid, ino); 440 441 /* Restore the path separator and update the current component. */ 442 443 if (endchar) 444 *end = '/'; 445 446 if (!retval) 447 *basename = path_component_next(end); 448 449 return retval; 450 } 451 452 /* Make directories descending to the given path. */ 453 454 errcode_t image_make_dirs(ext2_filsys fs, const char **path, 455 ext2_ino_t ino_dir, __u16 mode, __u16 uid, __u16 gid) 456 { 457 ext2_ino_t ino; 458 errcode_t retval; 459 460 while (**path) 461 { 462 retval = image_make_next_dir(fs, ino_dir, path, mode, uid, gid, &ino); 463 if (retval) 464 return retval; 465 466 /* Move into the created object for handling the next component. */ 467 468 ino_dir = ino; 469 } 470 471 return 0; 472 } 473 474 /* Remove an inode. */ 475 476 errcode_t image_remove_by_inode(ext2_filsys fs, ext2_ino_t ino) 477 { 478 struct ext2_inode_large inode; 479 ext2_ino_t ino_parent = 0; 480 errcode_t retval; 481 int isdir = image_isdir_by_inode(fs, ino); 482 483 if (isdir) 484 { 485 retval = image_dir_get_parent(fs, ino, &ino_parent); 486 if (retval) 487 return retval; 488 } 489 490 retval = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, 491 sizeof(inode)); 492 493 /* Handle invalid inodes, ignore unreferenced inodes. */ 494 495 if (retval) 496 return retval; 497 498 if (!inode.i_links_count) 499 return 0; 500 501 /* Decrement the reference count. With no more references to the inode, 502 remove its resources. Directories appear to need a double decrement. */ 503 504 inode.i_links_count--; 505 506 if (isdir && inode.i_links_count) 507 inode.i_links_count--; 508 509 if (!inode.i_links_count) 510 { 511 /* NOTE: Update deletion time. */ 512 513 retval = ext2fs_free_ext_attr(fs, ino, &inode); 514 515 if (!retval) 516 { 517 /* Deallocate blocks, if appropriate. ~0ULL as the end represents 518 truncation. */ 519 520 if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *) &inode)) 521 { 522 retval = ext2fs_punch(fs, ino, (struct ext2_inode *) &inode, 523 NULL, 0, ~0ULL); 524 525 /* Update allocation statistics. */ 526 527 if (!retval) 528 ext2fs_inode_alloc_stats2(fs, ino, -1, 529 LINUX_S_ISDIR(inode.i_mode)); 530 } 531 } 532 } 533 534 retval = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *) &inode, 535 sizeof(inode)); 536 537 /* Decrement the parent reference count for directories. */ 538 539 if (!retval && !inode.i_links_count && ino_parent) 540 retval = image_inode_refcount_update(fs, ino_parent, -1); 541 542 return retval; 543 } 544 545 /* Remove a directory entry using its full path. */ 546 547 errcode_t image_remove_by_path(ext2_filsys fs, const char *path, 548 ext2_ino_t *ino) 549 { 550 return image_access_by_path(fs, path, image_remove_by_inode, ino); 551 } 552 553 /* Rename a file. */ 554 555 errcode_t image_rename(ext2_filsys fs, ext2_ino_t source, 556 ext2_ino_t source_parent, const char *source_basename, 557 ext2_ino_t target_parent, const char *target_basename) 558 { 559 errcode_t retval; 560 struct ext2_inode source_inode; 561 562 /* NOTE: Should check for space. */ 563 564 /* Obtain the source object. */ 565 566 retval = ext2fs_read_inode(fs, source, &source_inode); 567 568 if (retval) 569 return retval; 570 571 /* Link from the target parent. */ 572 573 retval = _image_link(fs, target_parent, target_basename, source, 574 image_file_type(source_inode.i_mode)); 575 576 if (retval) 577 return retval; 578 579 if (LINUX_S_ISDIR(source_inode.i_mode)) 580 { 581 /* Update the link count for the target. */ 582 583 retval = image_inode_refcount_update(fs, target_parent, 1); 584 585 if (retval) 586 return retval; 587 588 /* A directory needs its .. entry updating to refer to its new 589 parent. */ 590 591 retval = ext2fs_dir_iterate(fs, source, 0, NULL, 592 _image_update_parent_entry, &target_parent); 593 594 /* Update the link count for the source. */ 595 596 retval = image_inode_refcount_update(fs, source_parent, -1); 597 598 if (retval) 599 return retval; 600 } 601 602 /* Unlink from the source parent, doing so by name because the file is now 603 already linked from the target parent, and when the parents are the same, 604 unlinking by inode could just cause the file to disappear from the 605 catalogue. */ 606 607 retval = image_unlink_by_name(fs, source_parent, source_basename); 608 609 if (retval) 610 return retval; 611 612 return ext2fs_flush2(fs, 0); 613 } 614 615 /* Set the mode, user and group metadata for a file. */ 616 617 void image_set_metadata(struct ext2_inode *inode, int clean, __u16 mode, 618 __u16 uid, __u16 gid) 619 { 620 if (clean) 621 memset(inode, 0, sizeof(*inode)); 622 623 inode->i_mode = mode; 624 inode->i_uid = uid; 625 inode->i_gid = gid; 626 } 627 628 /* Copy file metadata into a stat structure. */ 629 630 errcode_t image_stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *st) 631 { 632 struct ext2_inode inode; 633 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 634 635 if (retval) 636 return retval; 637 638 /* NOTE: Could access large inode members and other information. */ 639 640 st->st_dev = 0; /* device identifier */ 641 st->st_ino = ino; 642 st->st_mode = inode.i_mode; 643 st->st_nlink = inode.i_links_count; 644 st->st_uid = inode_uid(inode); 645 st->st_gid = inode_gid(inode); 646 st->st_rdev = 0; /* special file device identifier */ 647 st->st_size = EXT2_I_SIZE(&inode); 648 st->st_blksize = fs->blocksize; 649 st->st_blocks = inode.i_blocks; /* number of 512 byte blocks allocated */ 650 st->st_atim.tv_sec = inode.i_atime; 651 st->st_atim.tv_nsec = 0; /* nanosecond resolution */ 652 st->st_mtim.tv_sec = inode.i_mtime; 653 st->st_mtim.tv_nsec = 0; 654 st->st_ctim.tv_sec = inode.i_ctime; 655 st->st_ctim.tv_nsec = 0; 656 657 return 0; 658 } 659 660 /* Unlink a directory entry by name. */ 661 662 errcode_t image_unlink_by_name(ext2_filsys fs, ext2_ino_t ino_parent, 663 const char *basename) 664 { 665 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 666 } 667 668 /* Unlink a directory entry by full path. */ 669 670 errcode_t image_unlink_by_path(ext2_filsys fs, const char *path) 671 { 672 char _path[strlen(path) + 1]; 673 char *basename; 674 ext2_ino_t ino_parent; 675 errcode_t retval; 676 677 /* Copy the path and split it. */ 678 679 strcpy(_path, path); 680 basename = path_split(_path); 681 682 /* Determine the parent directory. */ 683 684 if (basename != _path) 685 { 686 retval = image_find_by_path(fs, _path, &ino_parent); 687 688 if (retval) 689 return retval; 690 } 691 else 692 ino_parent = EXT2_ROOT_INO; 693 694 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 695 } 696 697 /* Unlink a directory entry by inode number. */ 698 699 errcode_t image_unlink_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 700 ext2_ino_t ino) 701 { 702 return ext2fs_unlink(fs, ino_parent, 0, ino, 0); 703 } 704 705 706 707 /* Test object presence and types. */ 708 709 int image_exists(ext2_filsys fs, const char *path) 710 { 711 ext2_ino_t ino; 712 713 return !image_find_by_path(fs, path, &ino); 714 } 715 716 int image_isdir_by_inode(ext2_filsys fs, ext2_ino_t ino) 717 { 718 struct ext2_inode inode; 719 720 if (ext2fs_read_inode(fs, ino, &inode)) 721 return 0; 722 723 return LINUX_S_ISDIR(inode.i_mode); 724 } 725 726 int image_isdir_by_path(ext2_filsys fs, const char *path) 727 { 728 ext2_ino_t ino; 729 730 if (image_find_by_path(fs, path, &ino)) 731 return 0; 732 733 return image_isdir_by_inode(fs, ino); 734 } 735 736 int image_isfile_by_inode(ext2_filsys fs, ext2_ino_t ino) 737 { 738 struct ext2_inode inode; 739 740 if (ext2fs_read_inode(fs, ino, &inode)) 741 return 0; 742 743 return LINUX_S_ISREG(inode.i_mode); 744 } 745 746 int image_isfile_by_path(ext2_filsys fs, const char *path) 747 { 748 ext2_ino_t ino; 749 750 if (image_find_by_path(fs, path, &ino)) 751 return 0; 752 753 return image_isfile_by_inode(fs, ino); 754 } 755 756 /* vim: tabstop=4 expandtab shiftwidth=4 757 */