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