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