Home | History | Annotate | Download | only in ext2fs
      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