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 /* Update the reference count for the given inode. */ 328 329 errcode_t image_inode_refcount_update(ext2_filsys fs, ext2_ino_t ino, int change) 330 { 331 struct ext2_inode inode; 332 errcode_t retval = ext2fs_read_inode(fs, ino, &inode); 333 334 if (retval) 335 return retval; 336 337 /* NOTE: FUSE implementation tests for > 1. */ 338 339 if ((change < 0) && (inode.i_links_count + change < 0)) 340 inode.i_links_count = 0; 341 else 342 inode.i_links_count += change; 343 344 /* NOTE: Update modification time. */ 345 346 return ext2fs_write_inode(fs, ino, &inode); 347 } 348 349 /* List a directory in the filesystem image. */ 350 351 errcode_t image_list_dir(ext2_filsys fs, const char *path, 352 int (*proc)(struct ext2_dir_entry *, int, int, char *, 353 void *), 354 void *data) 355 { 356 char *buf; 357 ext2_ino_t ino; 358 errcode_t retval; 359 360 retval = ext2fs_get_mem(fs->blocksize, &buf); 361 if (retval) 362 return retval; 363 364 /* Locate the object and test whether it is a directory. */ 365 366 retval = image_find_path(fs, &path, &ino); 367 if (retval) 368 { 369 ext2fs_free_mem(&buf); 370 return retval; 371 } 372 373 if (!_image_isdir(fs, ino)) 374 return 1; 375 376 /* List the directory contents. */ 377 378 retval = ext2fs_dir_iterate(fs, ino, 0, buf, proc, data); 379 if (retval) 380 { 381 ext2fs_free_mem(&buf); 382 return retval; 383 } 384 385 ext2fs_free_mem(&buf); 386 return 0; 387 } 388 389 /* Make a directory in the given directory in the filesystem image having the 390 given name and 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 in the filesystem image, updating 421 the name reference to refer 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 in the filesystem image. */ 453 454 errcode_t image_make_dirs(ext2_filsys fs, const char **pathname, 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 (**pathname) 461 { 462 retval = image_make_next_dir(fs, ino_dir, pathname, 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(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 (_image_isdir(fs, source)) 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 st->st_dev = 0; /* device identifier */ 639 st->st_ino = ino; 640 st->st_mode = inode.i_mode; 641 st->st_nlink = inode.i_links_count; 642 st->st_uid = inode_uid(inode); 643 st->st_gid = inode_gid(inode); 644 st->st_rdev = 0; /* special file device identifier */ 645 st->st_size = EXT2_I_SIZE(&inode); 646 st->st_blksize = fs->blocksize; 647 st->st_blocks = 0; /* number of 512 byte blocks allocated */ 648 st->st_atim.tv_sec = inode.i_atime; 649 st->st_atim.tv_nsec = 0; /* nanosecond resolution */ 650 st->st_mtim.tv_sec = inode.i_mtime; 651 st->st_mtim.tv_nsec = 0; 652 st->st_ctim.tv_sec = inode.i_ctime; 653 st->st_ctim.tv_nsec = 0; 654 655 return 0; 656 } 657 658 /* Unlink a directory entry by name. */ 659 660 errcode_t image_unlink_by_name(ext2_filsys fs, ext2_ino_t ino_parent, 661 const char *basename) 662 { 663 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 664 } 665 666 /* Unlink a directory entry by full path. */ 667 668 errcode_t image_unlink_by_path(ext2_filsys fs, const char *path) 669 { 670 char _path[strlen(path) + 1]; 671 const char *remaining = _path; 672 char *basename; 673 ext2_ino_t ino_parent; 674 errcode_t retval; 675 676 /* Copy the path and split it. */ 677 678 strcpy(_path, path); 679 basename = path_split(_path); 680 681 /* Determine the parent directory. */ 682 683 if (basename != _path) 684 { 685 retval = image_find_path(fs, &remaining, &ino_parent); 686 687 if (retval) 688 return retval; 689 } 690 else 691 ino_parent = EXT2_ROOT_INO; 692 693 return ext2fs_unlink(fs, ino_parent, basename, 0, 0); 694 } 695 696 /* Unlink a directory entry by inode number. */ 697 698 errcode_t image_unlink_by_inode(ext2_filsys fs, ext2_ino_t ino_parent, 699 ext2_ino_t ino) 700 { 701 return ext2fs_unlink(fs, ino_parent, 0, ino, 0); 702 } 703 704 705 706 /* Test object presence and types in the filesystem image. */ 707 708 int image_exists(ext2_filsys fs, const char *name) 709 { 710 ext2_ino_t ino; 711 712 return !image_find_path(fs, &name, &ino); 713 } 714 715 int _image_isdir(ext2_filsys fs, ext2_ino_t ino) 716 { 717 struct ext2_inode inode; 718 719 if (ext2fs_read_inode(fs, ino, &inode)) 720 return 0; 721 722 return LINUX_S_ISDIR(inode.i_mode); 723 } 724 725 int image_isdir(ext2_filsys fs, const char *name) 726 { 727 ext2_ino_t ino; 728 729 return image_isdir_by_path(fs, name, &ino); 730 } 731 732 int image_isdir_by_path(ext2_filsys fs, const char *name, ext2_ino_t *ino) 733 { 734 if (image_find_path(fs, &name, ino)) 735 return 0; 736 737 return _image_isdir(fs, *ino); 738 } 739 740 int _image_isfile(ext2_filsys fs, ext2_ino_t ino) 741 { 742 struct ext2_inode inode; 743 744 if (ext2fs_read_inode(fs, ino, &inode)) 745 return 0; 746 747 return LINUX_S_ISREG(inode.i_mode); 748 } 749 750 int image_isfile(ext2_filsys fs, const char *name) 751 { 752 ext2_ino_t ino; 753 754 return image_isfile_by_path(fs, name, &ino); 755 } 756 757 int image_isfile_by_path(ext2_filsys fs, const char *name, ext2_ino_t *ino) 758 { 759 if (image_find_path(fs, &name, ino)) 760 return 0; 761 762 return _image_isfile(fs, *ino); 763 } 764 765 /* vim: tabstop=4 expandtab shiftwidth=4 766 */