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 486 if (_image_isdir(fs, ino)) 487 { 488 retval = image_dir_get_parent(fs, ino, &ino_parent); 489 if (retval) 490 return retval; 491 } 492 493 retval = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *) &inode, 494 sizeof(inode)); 495 496 /* Handle invalid inodes, ignore unreferenced inodes. */ 497 498 if (retval) 499 return retval; 500 501 if (!inode.i_links_count) 502 return 0; 503 504 /* Decrement the reference count. With no more references to the inode, 505 remove its resources. */ 506 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_decrement(fs, ino_parent); 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, source_parent_inode, target_parent_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 (_image_isdir(fs, source)) 580 { 581 /* Update the link count for the target. */ 582 583 retval = ext2fs_read_inode(fs, target_parent, &target_parent_inode); 584 585 if (retval) 586 return retval; 587 588 target_parent_inode.i_links_count++; 589 590 retval = ext2fs_write_inode(fs, target_parent, &target_parent_inode); 591 592 if (retval) 593 return retval; 594 595 /* A directory needs its .. entry updating to refer to its new 596 parent. */ 597 598 retval = ext2fs_dir_iterate(fs, source, 0, NULL, 599 _image_update_parent_entry, &target_parent); 600 601 /* Update the link count for the source. */ 602 603 retval = ext2fs_read_inode(fs, source_parent, &source_parent_inode); 604 605 if (retval) 606 return retval; 607 608 source_parent_inode.i_links_count--; 609 610 retval = ext2fs_write_inode(fs, source_parent, &source_parent_inode); 611 612 if (retval) 613 return retval; 614 } 615 616 /* Unlink from the source parent, doing so by name because the file is now 617 already linked from the target parent, and when the parents are the same, 618 unlinking by inode could just cause the file to disappear from the 619 catalogue. */ 620 621 retval = image_unlink_by_name(fs, source_parent, source_basename); 622 623 if (retval) 624 return retval; 625 626 return ext2fs_flush2(fs, 0); 627 } 628 629 /* Set the mode, user and group metadata for a file. */ 630 631 void image_set_metadata(struct ext2_inode *inode, int clean, __u16 mode, 632 __u16 uid, __u16 gid) 633 { 634 if (clean) 635 memset(inode, 0, sizeof(*inode)); 636 637 inode->i_mode = mode; 638 inode->i_uid = uid; 639 inode->i_gid = gid; 640 } 641 642 /* Copy file metadata into a stat structure. */ 643 644 errcode_t image_stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *st) 645 { 646 struct ext2_inode inode; 647 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 648 649 if (retval) 650 return retval; 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 = 0; /* 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 const char *remaining = _path; 686 char *basename; 687 ext2_ino_t ino_parent; 688 errcode_t retval; 689 690 /* Copy the path and split it. */ 691 692 strcpy(_path, path); 693 basename = path_split(_path); 694 695 /* Determine the parent directory. */ 696 697 if (basename != _path) 698 { 699 retval = image_find_path(fs, &remaining, &ino_parent); 700 701 if (retval) 702 return retval; 703 } 704 else 705 ino_parent = EXT2_ROOT_INO; 706 707 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 708 } 709 710 /* Unlink a directory entry by inode number. */ 711 712 errcode_t image_unlink_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 713 ext2_ino_t ino) 714 { 715 return ext2fs_unlink(fs, ino_parent, 0, ino, 0); 716 } 717 718 719 720 /* Test object presence and types in the filesystem image. */ 721 722 int image_exists(ext2_filsys fs, const char *name) 723 { 724 ext2_ino_t ino; 725 726 return !image_find_path(fs, &name, &ino); 727 } 728 729 int _image_isdir(ext2_filsys fs, ext2_ino_t ino) 730 { 731 struct ext2_inode inode; 732 733 if (ext2fs_read_inode(fs, ino, &inode)) 734 return 0; 735 736 return LINUX_S_ISDIR(inode.i_mode); 737 } 738 739 int image_isdir(ext2_filsys fs, const char *name) 740 { 741 ext2_ino_t ino; 742 743 if (image_find_path(fs, &name, &ino)) 744 return 0; 745 746 return _image_isdir(fs, ino); 747 } 748 749 int _image_isfile(ext2_filsys fs, ext2_ino_t ino) 750 { 751 struct ext2_inode inode; 752 753 if (ext2fs_read_inode(fs, ino, &inode)) 754 return 0; 755 756 return LINUX_S_ISREG(inode.i_mode); 757 } 758 759 int image_isfile(ext2_filsys fs, const char *name) 760 { 761 ext2_ino_t ino; 762 763 if (image_find_path(fs, &name, &ino)) 764 return 0; 765 766 return _image_isfile(fs, ino); 767 } 768 769 /* vim: tabstop=4 expandtab shiftwidth=4 770 */