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 goto fail; 472 473 if (new_block) { 474 new_block->physblock = file->physblock; 475 ext2fs_hashmap_add(fs->block_sha_map, new_block, 476 new_block->sha, sizeof(new_block->sha)); 477 } 478 } 479 480 file->pos += c; 481 ptr += c; 482 count += c; 483 nbytes -= c; 484 } 485 486 fail: 487 /* Update inode size */ 488 if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) { 489 errcode_t rc; 490 491 rc = ext2fs_file_set_size2(file, file->pos); 492 if (retval == 0) 493 retval = rc; 494 } 495 496 if (written) 497 *written = count; 498 return retval; 499 } 500 501 errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, 502 int whence, __u64 *ret_pos) 503 { 504 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 505 506 if (whence == EXT2_SEEK_SET) 507 file->pos = offset; 508 else if (whence == EXT2_SEEK_CUR) 509 file->pos += offset; 510 else if (whence == EXT2_SEEK_END) 511 file->pos = EXT2_I_SIZE(&file->inode) + offset; 512 else 513 return EXT2_ET_INVALID_ARGUMENT; 514 515 if (ret_pos) 516 *ret_pos = file->pos; 517 518 return 0; 519 } 520 521 errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, 522 int whence, ext2_off_t *ret_pos) 523 { 524 __u64 loffset, ret_loffset = 0; 525 errcode_t retval; 526 527 loffset = offset; 528 retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset); 529 if (ret_pos) 530 *ret_pos = (ext2_off_t) ret_loffset; 531 return retval; 532 } 533 534 535 /* 536 * This function returns the size of the file, according to the inode 537 */ 538 errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size) 539 { 540 if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) 541 return EXT2_ET_MAGIC_EXT2_FILE; 542 *ret_size = EXT2_I_SIZE(&file->inode); 543 return 0; 544 } 545 546 /* 547 * This function returns the size of the file, according to the inode 548 */ 549 ext2_off_t ext2fs_file_get_size(ext2_file_t file) 550 { 551 __u64 size; 552 553 if (ext2fs_file_get_lsize(file, &size)) 554 return 0; 555 if ((size >> 32) != 0) 556 return 0; 557 return size; 558 } 559 560 /* Zero the parts of the last block that are past EOF. */ 561 static errcode_t ext2fs_file_zero_past_offset(ext2_file_t file, 562 ext2_off64_t offset) 563 { 564 ext2_filsys fs = file->fs; 565 char *b = NULL; 566 ext2_off64_t off = offset % fs->blocksize; 567 blk64_t blk; 568 int ret_flags; 569 errcode_t retval; 570 571 if (off == 0) 572 return 0; 573 574 retval = sync_buffer_position(file); 575 if (retval) 576 return retval; 577 578 /* Is there an initialized block at the end? */ 579 retval = ext2fs_bmap2(fs, file->ino, NULL, NULL, 0, 580 offset / fs->blocksize, &ret_flags, &blk); 581 if (retval) 582 return retval; 583 if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT)) 584 return 0; 585 586 /* Zero to the end of the block */ 587 retval = ext2fs_get_mem(fs->blocksize, &b); 588 if (retval) 589 return retval; 590 591 /* Read/zero/write block */ 592 retval = io_channel_read_blk64(fs->io, blk, 1, b); 593 if (retval) 594 goto out; 595 596 memset(b + off, 0, fs->blocksize - off); 597 598 retval = io_channel_write_blk64(fs->io, blk, 1, b); 599 if (retval) 600 goto out; 601 602 out: 603 ext2fs_free_mem(&b); 604 return retval; 605 } 606 607 /* 608 * This function sets the size of the file, truncating it if necessary 609 * 610 */ 611 errcode_t ext2fs_file_set_size2(ext2_file_t file, ext2_off64_t size) 612 { 613 ext2_off64_t old_size; 614 errcode_t retval; 615 blk64_t old_truncate, truncate_block; 616 617 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); 618 619 if (size && ext2fs_file_block_offset_too_big(file->fs, &file->inode, 620 (size - 1) / file->fs->blocksize)) 621 return EXT2_ET_FILE_TOO_BIG; 622 truncate_block = ((size + file->fs->blocksize - 1) >> 623 EXT2_BLOCK_SIZE_BITS(file->fs->super)); 624 old_size = EXT2_I_SIZE(&file->inode); 625 old_truncate = ((old_size + file->fs->blocksize - 1) >> 626 EXT2_BLOCK_SIZE_BITS(file->fs->super)); 627 628 retval = ext2fs_inode_size_set(file->fs, &file->inode, size); 629 if (retval) 630 return retval; 631 632 if (file->ino) { 633 retval = ext2fs_write_inode(file->fs, file->ino, &file->inode); 634 if (retval) 635 return retval; 636 } 637 638 retval = ext2fs_file_zero_past_offset(file, size); 639 if (retval) 640 return retval; 641 642 if (truncate_block >= old_truncate) 643 return 0; 644 645 return ext2fs_punch(file->fs, file->ino, &file->inode, 0, 646 truncate_block, ~0ULL); 647 } 648 649 errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size) 650 { 651 return ext2fs_file_set_size2(file, size); 652 } 653