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