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