1 /* 2 * fileio.c --- Simple file I/O routines 3 * 4 * Copyright (C) 1997 Theodore Ts'o. 5 * 6 * %Begin-Header% 7 * This file may be redistributed under the terms of the GNU Library 8 * General Public License, version 2. 9 * %End-Header% 10 */ 11 12 #include "config.h" 13 #include <stdio.h> 14 #include <string.h> 15 #if HAVE_UNISTD_H 16 #include <unistd.h> 17 #endif 18 19 #include "ext2_fs.h" 20 #include "ext2fs.h" 21 #include "ext2fsP.h" 22 23 struct ext2_file { 24 errcode_t magic; 25 ext2_filsys fs; 26 ext2_ino_t ino; 27 struct ext2_inode inode; 28 int flags; 29 __u64 pos; 30 blk64_t blockno; 31 blk64_t physblock; 32 char *buf; 33 }; 34 35 struct block_entry { 36 blk64_t physblock; 37 unsigned char sha[EXT2FS_SHA512_LENGTH]; 38 }; 39 typedef struct block_entry *block_entry_t; 40 41 #define BMAP_BUFFER (file->buf + fs->blocksize) 42 43 errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino, 44 struct ext2_inode *inode, 45 int flags, ext2_file_t *ret) 46 { 47 ext2_file_t file; 48 errcode_t retval; 49 50 /* 51 * Don't let caller create or open a file for writing if the 52 * filesystem is read-only. 53 */ 54 if ((flags & (EXT2_FILE_WRITE | EXT2_FILE_CREATE)) && 55 !(fs->flags & EXT2_FLAG_RW)) 56 return EXT2_ET_RO_FILSYS; 57 58 retval = ext2fs_get_mem(sizeof(struct ext2_file), &file); 59 if (retval) 60 return retval; 61 62 memset(file, 0, sizeof(struct ext2_file)); 63 file->magic = EXT2_ET_MAGIC_EXT2_FILE; 64 file->fs = fs; 65 file->ino = ino; 66 file->flags = flags & EXT2_FILE_MASK; 67 68 if (inode) { 69 memcpy(&file->inode, inode, sizeof(struct ext2_inode)); 70 } else { 71 retval = ext2fs_read_inode(fs, ino, &file->inode); 72 if (retval) 73 goto fail; 74 } 75 76 retval = ext2fs_get_array(3, fs->blocksize, &file->buf); 77 if (retval) 78 goto fail; 79 80 *ret = file; 81 return 0; 82 83 fail: 84 if (file->buf) 85 ext2fs_free_mem(&file->buf); 86 ext2fs_free_mem(&file); 87 return retval; 88 } 89 90 errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino, 91 int flags, ext2_file_t *ret) 92 { 93 return ext2fs_file_open2(fs, ino, NULL, flags, ret); 94 } 95 96 /* 97 * This function returns the filesystem handle of a file from the structure 98 */ 99 ext2_filsys ext2fs_file_get_fs(ext2_file_t file) 100 { 101 if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) 102 return 0; 103 return file->fs; 104 } 105 106 /* 107 * This function returns the pointer to the inode of a file from the structure 108 */ 109 struct ext2_inode *ext2fs_file_get_inode(ext2_file_t file) 110 { 111 if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) 112 return NULL; 113 return &file->inode; 114 } 115 116 /* This function returns the inode number from the structure */ 117 ext2_ino_t ext2fs_file_get_inode_num(ext2_file_t file) 118 { 119 if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) 120 return 0; 121 return file->ino; 122 } 123 124 /* 125 * This function flushes the dirty block buffer out to disk if 126 * necessary. 127 */ 128 errcode_t ext2fs_file_flush(ext2_file_t file) 129 { 130 errcode_t retval; 131 ext2_filsys fs; 132 int ret_flags; 133 blk64_t dontcare; 134 135 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 136 fs = file->fs; 137 138 if (!(file->flags & EXT2_FILE_BUF_VALID) || 139 !(file->flags & EXT2_FILE_BUF_DIRTY)) 140 return 0; 141 142 /* Is this an uninit block? */ 143 if (file->physblock && file->inode.i_flags & EXT4_EXTENTS_FL) { 144 retval = ext2fs_bmap2(fs, file->ino, &file->inode, BMAP_BUFFER, 145 0, file->blockno, &ret_flags, &dontcare); 146 if (retval) 147 return retval; 148 if (ret_flags & BMAP_RET_UNINIT) { 149 retval = ext2fs_bmap2(fs, file->ino, &file->inode, 150 BMAP_BUFFER, BMAP_SET, 151 file->blockno, 0, 152 &file->physblock); 153 if (retval) 154 return retval; 155 } 156 } 157 158 /* 159 * OK, the physical block hasn't been allocated yet. 160 * Allocate it. 161 */ 162 if (!file->physblock) { 163 retval = ext2fs_bmap2(fs, file->ino, &file->inode, 164 BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0, 165 file->blockno, 0, &file->physblock); 166 if (retval) 167 return retval; 168 } 169 170 retval = io_channel_write_blk64(fs->io, file->physblock, 1, file->buf); 171 if (retval) 172 return retval; 173 174 file->flags &= ~EXT2_FILE_BUF_DIRTY; 175 176 return retval; 177 } 178 179 /* 180 * This function synchronizes the file's block buffer and the current 181 * file position, possibly invalidating block buffer if necessary 182 */ 183 static errcode_t sync_buffer_position(ext2_file_t file) 184 { 185 blk64_t b; 186 errcode_t retval; 187 188 b = file->pos / file->fs->blocksize; 189 if (b != file->blockno) { 190 retval = ext2fs_file_flush(file); 191 if (retval) 192 return retval; 193 file->flags &= ~EXT2_FILE_BUF_VALID; 194 } 195 file->blockno = b; 196 return 0; 197 } 198 199 /* 200 * This function loads the file's block buffer with valid data from 201 * the disk as necessary. 202 * 203 * If dontfill is true, then skip initializing the buffer since we're 204 * going to be replacing its entire contents anyway. If set, then the 205 * function basically only sets file->physblock and EXT2_FILE_BUF_VALID 206 */ 207 #define DONTFILL 1 208 static errcode_t load_buffer(ext2_file_t file, int dontfill) 209 { 210 ext2_filsys fs = file->fs; 211 errcode_t retval; 212 int ret_flags; 213 214 if (!(file->flags & EXT2_FILE_BUF_VALID)) { 215 retval = ext2fs_bmap2(fs, file->ino, &file->inode, 216 BMAP_BUFFER, 0, file->blockno, &ret_flags, 217 &file->physblock); 218 if (retval) 219 return retval; 220 if (!dontfill) { 221 if (file->physblock && 222 !(ret_flags & BMAP_RET_UNINIT)) { 223 retval = io_channel_read_blk64(fs->io, 224 file->physblock, 225 1, file->buf); 226 if (retval) 227 return retval; 228 } else 229 memset(file->buf, 0, fs->blocksize); 230 } 231 file->flags |= EXT2_FILE_BUF_VALID; 232 } 233 return 0; 234 } 235 236 237 errcode_t ext2fs_file_close(ext2_file_t file) 238 { 239 errcode_t retval; 240 241 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 242 243 retval = ext2fs_file_flush(file); 244 245 if (file->buf) 246 ext2fs_free_mem(&file->buf); 247 ext2fs_free_mem(&file); 248 249 return retval; 250 } 251 252 253 static errcode_t 254 ext2fs_file_read_inline_data(ext2_file_t file, void *buf, 255 unsigned int wanted, unsigned int *got) 256 { 257 ext2_filsys fs; 258 errcode_t retval; 259 unsigned int count = 0; 260 size_t size; 261 262 fs = file->fs; 263 retval = ext2fs_inline_data_get(fs, file->ino, &file->inode, 264 file->buf, &size); 265 if (retval) 266 return retval; 267 268 if (file->pos >= size) 269 goto out; 270 271 count = size - file->pos; 272 if (count > wanted) 273 count = wanted; 274 memcpy(buf, file->buf + file->pos, count); 275 file->pos += count; 276 buf = (char *) buf + count; 277 278 out: 279 if (got) 280 *got = count; 281 return retval; 282 } 283 284 285 errcode_t ext2fs_file_read(ext2_file_t file, void *buf, 286 unsigned int wanted, unsigned int *got) 287 { 288 ext2_filsys fs; 289 errcode_t retval = 0; 290 unsigned int start, c, count = 0; 291 __u64 left; 292 char *ptr = (char *) buf; 293 294 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 295 fs = file->fs; 296 297 /* If an inode has inline data, things get complicated. */ 298 if (file->inode.i_flags & EXT4_INLINE_DATA_FL) 299 return ext2fs_file_read_inline_data(file, buf, wanted, got); 300 301 while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) { 302 retval = sync_buffer_position(file); 303 if (retval) 304 goto fail; 305 retval = load_buffer(file, 0); 306 if (retval) 307 goto fail; 308 309 start = file->pos % fs->blocksize; 310 c = fs->blocksize - start; 311 if (c > wanted) 312 c = wanted; 313 left = EXT2_I_SIZE(&file->inode) - file->pos ; 314 if (c > left) 315 c = left; 316 317 memcpy(ptr, file->buf+start, c); 318 file->pos += c; 319 ptr += c; 320 count += c; 321 wanted -= c; 322 } 323 324 fail: 325 if (got) 326 *got = count; 327 return retval; 328 } 329 330 331 static errcode_t 332 ext2fs_file_write_inline_data(ext2_file_t file, const void *buf, 333 unsigned int nbytes, unsigned int *written) 334 { 335 ext2_filsys fs; 336 errcode_t retval; 337 unsigned int count = 0; 338 size_t size; 339 340 fs = file->fs; 341 retval = ext2fs_inline_data_get(fs, file->ino, &file->inode, 342 file->buf, &size); 343 if (retval) 344 return retval; 345 346 if (file->pos < size) { 347 count = nbytes - file->pos; 348 memcpy(file->buf + file->pos, buf, count); 349 350 retval = ext2fs_inline_data_set(fs, file->ino, &file->inode, 351 file->buf, count); 352 if (retval == EXT2_ET_INLINE_DATA_NO_SPACE) 353 goto expand; 354 if (retval) 355 return retval; 356 357 file->pos += count; 358 359 /* Update inode size */ 360 if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) { 361 errcode_t rc; 362 363 rc = ext2fs_file_set_size2(file, file->pos); 364 if (retval == 0) 365 retval = rc; 366 } 367 368 if (written) 369 *written = count; 370 return 0; 371 } 372 373 expand: 374 retval = ext2fs_inline_data_expand(fs, file->ino); 375 if (retval) 376 return retval; 377 /* 378 * reload inode and return no space error 379 * 380 * XXX: file->inode could be copied from the outside 381 * in ext2fs_file_open2(). We have no way to modify 382 * the outside inode. 383 */ 384 retval = ext2fs_read_inode(fs, file->ino, &file->inode); 385 if (retval) 386 return retval; 387 return EXT2_ET_INLINE_DATA_NO_SPACE; 388 } 389 390 391 errcode_t ext2fs_file_write(ext2_file_t file, const void *buf, 392 unsigned int nbytes, unsigned int *written) 393 { 394 ext2_filsys fs; 395 errcode_t retval = 0; 396 unsigned int start, c, count = 0; 397 const char *ptr = (const char *) buf; 398 block_entry_t new_block = NULL, old_block = NULL; 399 int bmap_flags = 0; 400 401 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 402 fs = file->fs; 403 404 if (!(file->flags & EXT2_FILE_WRITE)) 405 return EXT2_ET_FILE_RO; 406 407 /* If an inode has inline data, things get complicated. */ 408 if (file->inode.i_flags & EXT4_INLINE_DATA_FL) { 409 retval = ext2fs_file_write_inline_data(file, buf, nbytes, 410 written); 411 if (retval != EXT2_ET_INLINE_DATA_NO_SPACE) 412 return retval; 413 /* fall through to read data from the block */ 414 retval = 0; 415 } 416 417 while (nbytes > 0) { 418 retval = sync_buffer_position(file); 419 if (retval) 420 goto fail; 421 422 start = file->pos % fs->blocksize; 423 c = fs->blocksize - start; 424 if (c > nbytes) 425 c = nbytes; 426 427 /* 428 * We only need to do a read-modify-update cycle if 429 * we're doing a partial write. 430 */ 431 retval = load_buffer(file, (c == fs->blocksize)); 432 if (retval) 433 goto fail; 434 435 file->flags |= EXT2_FILE_BUF_DIRTY; 436 memcpy(file->buf+start, ptr, c); 437 438 /* 439 * OK, the physical block hasn't been allocated yet. 440 * Allocate it. 441 */ 442 if (!file->physblock) { 443 bmap_flags = (file->ino ? BMAP_ALLOC : 0); 444 if (fs->flags & EXT2_FLAG_SHARE_DUP) { 445 new_block = calloc(1, sizeof(*new_block)); 446 if (!new_block) { 447 retval = EXT2_ET_NO_MEMORY; 448 goto fail; 449 } 450 ext2fs_sha512((const unsigned char*)file->buf, 451 fs->blocksize, new_block->sha); 452 old_block = ext2fs_hashmap_lookup( 453 fs->block_sha_map, 454 new_block->sha, 455 sizeof(new_block->sha)); 456 } 457 458 if (old_block) { 459 file->physblock = old_block->physblock; 460 bmap_flags |= BMAP_SET; 461 free(new_block); 462 new_block = NULL; 463 } 464 465 retval = ext2fs_bmap2(fs, file->ino, &file->inode, 466 BMAP_BUFFER, 467 bmap_flags, 468 file->blockno, 0, 469 &file->physblock); 470 if (retval) { 471 free(new_block); 472 new_block = NULL; 473 goto fail; 474 } 475 476 if (new_block) { 477 new_block->physblock = file->physblock; 478 ext2fs_hashmap_add(fs->block_sha_map, new_block, 479 new_block->sha, sizeof(new_block->sha)); 480 } 481 482 if (bmap_flags & BMAP_SET) { 483 ext2fs_iblk_add_blocks(fs, &file->inode, 1); 484 ext2fs_write_inode(fs, file->ino, &file->inode); 485 } 486 } 487 488 file->pos += c; 489 ptr += c; 490 count += c; 491 nbytes -= c; 492 } 493 494 fail: 495 /* Update inode size */ 496 if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) { 497 errcode_t rc; 498 499 rc = ext2fs_file_set_size2(file, file->pos); 500 if (retval == 0) 501 retval = rc; 502 } 503 504 if (written) 505 *written = count; 506 return retval; 507 } 508 509 errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, 510 int whence, __u64 *ret_pos) 511 { 512 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 513 514 if (whence == EXT2_SEEK_SET) 515 file->pos = offset; 516 else if (whence == EXT2_SEEK_CUR) 517 file->pos += offset; 518 else if (whence == EXT2_SEEK_END) 519 file->pos = EXT2_I_SIZE(&file->inode) + offset; 520 else 521 return EXT2_ET_INVALID_ARGUMENT; 522 523 if (ret_pos) 524 *ret_pos = file->pos; 525 526 return 0; 527 } 528 529 errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, 530 int whence, ext2_off_t *ret_pos) 531 { 532 __u64 loffset, ret_loffset = 0; 533 errcode_t retval; 534 535 loffset = offset; 536 retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset); 537 if (ret_pos) 538 *ret_pos = (ext2_off_t) ret_loffset; 539 return retval; 540 } 541 542 543 /* 544 * This function returns the size of the file, according to the inode 545 */ 546 errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size) 547 { 548 if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) 549 return EXT2_ET_MAGIC_EXT2_FILE; 550 *ret_size = EXT2_I_SIZE(&file->inode); 551 return 0; 552 } 553 554 /* 555 * This function returns the size of the file, according to the inode 556 */ 557 ext2_off_t ext2fs_file_get_size(ext2_file_t file) 558 { 559 __u64 size; 560 561 if (ext2fs_file_get_lsize(file, &size)) 562 return 0; 563 if ((size >> 32) != 0) 564 return 0; 565 return size; 566 } 567 568 /* Zero the parts of the last block that are past EOF. */ 569 static errcode_t ext2fs_file_zero_past_offset(ext2_file_t file, 570 ext2_off64_t offset) 571 { 572 ext2_filsys fs = file->fs; 573 char *b = NULL; 574 ext2_off64_t off = offset % fs->blocksize; 575 blk64_t blk; 576 int ret_flags; 577 errcode_t retval; 578 579 if (off == 0) 580 return 0; 581 582 retval = sync_buffer_position(file); 583 if (retval) 584 return retval; 585 586 /* Is there an initialized block at the end? */ 587 retval = ext2fs_bmap2(fs, file->ino, NULL, NULL, 0, 588 offset / fs->blocksize, &ret_flags, &blk); 589 if (retval) 590 return retval; 591 if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT)) 592 return 0; 593 594 /* Zero to the end of the block */ 595 retval = ext2fs_get_mem(fs->blocksize, &b); 596 if (retval) 597 return retval; 598 599 /* Read/zero/write block */ 600 retval = io_channel_read_blk64(fs->io, blk, 1, b); 601 if (retval) 602 goto out; 603 604 memset(b + off, 0, fs->blocksize - off); 605 606 retval = io_channel_write_blk64(fs->io, blk, 1, b); 607 if (retval) 608 goto out; 609 610 out: 611 ext2fs_free_mem(&b); 612 return retval; 613 } 614 615 /* 616 * This function sets the size of the file, truncating it if necessary 617 * 618 */ 619 errcode_t ext2fs_file_set_size2(ext2_file_t file, ext2_off64_t size) 620 { 621 ext2_off64_t old_size; 622 errcode_t retval; 623 blk64_t old_truncate, truncate_block; 624 625 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 626 627 if (size && ext2fs_file_block_offset_too_big(file->fs, &file->inode, 628 (size - 1) / file->fs->blocksize)) 629 return EXT2_ET_FILE_TOO_BIG; 630 truncate_block = ((size + file->fs->blocksize - 1) >> 631 EXT2_BLOCK_SIZE_BITS(file->fs->super)); 632 old_size = EXT2_I_SIZE(&file->inode); 633 old_truncate = ((old_size + file->fs->blocksize - 1) >> 634 EXT2_BLOCK_SIZE_BITS(file->fs->super)); 635 636 retval = ext2fs_inode_size_set(file->fs, &file->inode, size); 637 if (retval) 638 return retval; 639 640 if (file->ino) { 641 retval = ext2fs_write_inode(file->fs, file->ino, &file->inode); 642 if (retval) 643 return retval; 644 } 645 646 retval = ext2fs_file_zero_past_offset(file, size); 647 if (retval) 648 return retval; 649 650 if (truncate_block >= old_truncate) 651 return 0; 652 653 return ext2fs_punch(file->fs, file->ino, &file->inode, 0, 654 truncate_block, ~0ULL); 655 } 656 657 errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size) 658 { 659 return ext2fs_file_set_size2(file, size); 660 }