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