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