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 <stdio.h>
     13 #include <string.h>
     14 #if HAVE_UNISTD_H
     15 #include <unistd.h>
     16 #endif
     17 
     18 #include "ext2_fs.h"
     19 #include "ext2fs.h"
     20 #include "ext2fsP.h"
     21 
     22 struct ext2_file {
     23 	errcode_t		magic;
     24 	ext2_filsys 		fs;
     25 	ext2_ino_t		ino;
     26 	struct ext2_inode	inode;
     27 	int 			flags;
     28 	__u64			pos;
     29 	blk64_t			blockno;
     30 	blk64_t			physblock;
     31 	char 			*buf;
     32 };
     33 
     34 #define BMAP_BUFFER (file->buf + fs->blocksize)
     35 
     36 errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino,
     37 			    struct ext2_inode *inode,
     38 			    int flags, ext2_file_t *ret)
     39 {
     40 	ext2_file_t 	file;
     41 	errcode_t	retval;
     42 
     43 	/*
     44 	 * Don't let caller create or open a file for writing if the
     45 	 * filesystem is read-only.
     46 	 */
     47 	if ((flags & (EXT2_FILE_WRITE | EXT2_FILE_CREATE)) &&
     48 	    !(fs->flags & EXT2_FLAG_RW))
     49 		return EXT2_ET_RO_FILSYS;
     50 
     51 	retval = ext2fs_get_mem(sizeof(struct ext2_file), &file);
     52 	if (retval)
     53 		return retval;
     54 
     55 	memset(file, 0, sizeof(struct ext2_file));
     56 	file->magic = EXT2_ET_MAGIC_EXT2_FILE;
     57 	file->fs = fs;
     58 	file->ino = ino;
     59 	file->flags = flags & EXT2_FILE_MASK;
     60 
     61 	if (inode) {
     62 		memcpy(&file->inode, inode, sizeof(struct ext2_inode));
     63 	} else {
     64 		retval = ext2fs_read_inode(fs, ino, &file->inode);
     65 		if (retval)
     66 			goto fail;
     67 	}
     68 
     69 	retval = ext2fs_get_array(3, fs->blocksize, &file->buf);
     70 	if (retval)
     71 		goto fail;
     72 
     73 	*ret = file;
     74 	return 0;
     75 
     76 fail:
     77 	if (file->buf)
     78 		ext2fs_free_mem(&file->buf);
     79 	ext2fs_free_mem(&file);
     80 	return retval;
     81 }
     82 
     83 errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino,
     84 			   int flags, ext2_file_t *ret)
     85 {
     86 	return ext2fs_file_open2(fs, ino, NULL, flags, ret);
     87 }
     88 
     89 /*
     90  * This function returns the filesystem handle of a file from the structure
     91  */
     92 ext2_filsys ext2fs_file_get_fs(ext2_file_t file)
     93 {
     94 	if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
     95 		return 0;
     96 	return file->fs;
     97 }
     98 
     99 /*
    100  * This function returns the pointer to the inode of a file from the structure
    101  */
    102 struct ext2_inode *ext2fs_file_get_inode(ext2_file_t file)
    103 {
    104 	if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
    105 		return NULL;
    106 	return &file->inode;
    107 }
    108 
    109 /* This function returns the inode number from the structure */
    110 ext2_ino_t ext2fs_file_get_inode_num(ext2_file_t file)
    111 {
    112 	if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
    113 		return 0;
    114 	return file->ino;
    115 }
    116 
    117 /*
    118  * This function flushes the dirty block buffer out to disk if
    119  * necessary.
    120  */
    121 errcode_t ext2fs_file_flush(ext2_file_t file)
    122 {
    123 	errcode_t	retval;
    124 	ext2_filsys fs;
    125 
    126 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    127 	fs = file->fs;
    128 
    129 	if (!(file->flags & EXT2_FILE_BUF_VALID) ||
    130 	    !(file->flags & EXT2_FILE_BUF_DIRTY))
    131 		return 0;
    132 
    133 	/*
    134 	 * OK, the physical block hasn't been allocated yet.
    135 	 * Allocate it.
    136 	 */
    137 	if (!file->physblock) {
    138 		retval = ext2fs_bmap2(fs, file->ino, &file->inode,
    139 				     BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0,
    140 				     file->blockno, 0, &file->physblock);
    141 		if (retval)
    142 			return retval;
    143 	}
    144 
    145 	retval = io_channel_write_blk64(fs->io, file->physblock, 1, file->buf);
    146 	if (retval)
    147 		return retval;
    148 
    149 	file->flags &= ~EXT2_FILE_BUF_DIRTY;
    150 
    151 	return retval;
    152 }
    153 
    154 /*
    155  * This function synchronizes the file's block buffer and the current
    156  * file position, possibly invalidating block buffer if necessary
    157  */
    158 static errcode_t sync_buffer_position(ext2_file_t file)
    159 {
    160 	blk64_t	b;
    161 	errcode_t	retval;
    162 
    163 	b = file->pos / file->fs->blocksize;
    164 	if (b != file->blockno) {
    165 		retval = ext2fs_file_flush(file);
    166 		if (retval)
    167 			return retval;
    168 		file->flags &= ~EXT2_FILE_BUF_VALID;
    169 	}
    170 	file->blockno = b;
    171 	return 0;
    172 }
    173 
    174 /*
    175  * This function loads the file's block buffer with valid data from
    176  * the disk as necessary.
    177  *
    178  * If dontfill is true, then skip initializing the buffer since we're
    179  * going to be replacing its entire contents anyway.  If set, then the
    180  * function basically only sets file->physblock and EXT2_FILE_BUF_VALID
    181  */
    182 #define DONTFILL 1
    183 static errcode_t load_buffer(ext2_file_t file, int dontfill)
    184 {
    185 	ext2_filsys	fs = file->fs;
    186 	errcode_t	retval;
    187 
    188 	if (!(file->flags & EXT2_FILE_BUF_VALID)) {
    189 		retval = ext2fs_bmap2(fs, file->ino, &file->inode,
    190 				     BMAP_BUFFER, 0, file->blockno, 0,
    191 				     &file->physblock);
    192 		if (retval)
    193 			return retval;
    194 		if (!dontfill) {
    195 			if (file->physblock) {
    196 				retval = io_channel_read_blk64(fs->io,
    197 							       file->physblock,
    198 							       1, file->buf);
    199 				if (retval)
    200 					return retval;
    201 			} else
    202 				memset(file->buf, 0, fs->blocksize);
    203 		}
    204 		file->flags |= EXT2_FILE_BUF_VALID;
    205 	}
    206 	return 0;
    207 }
    208 
    209 
    210 errcode_t ext2fs_file_close(ext2_file_t file)
    211 {
    212 	errcode_t	retval;
    213 
    214 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    215 
    216 	retval = ext2fs_file_flush(file);
    217 
    218 	if (file->buf)
    219 		ext2fs_free_mem(&file->buf);
    220 	ext2fs_free_mem(&file);
    221 
    222 	return retval;
    223 }
    224 
    225 
    226 errcode_t ext2fs_file_read(ext2_file_t file, void *buf,
    227 			   unsigned int wanted, unsigned int *got)
    228 {
    229 	ext2_filsys	fs;
    230 	errcode_t	retval = 0;
    231 	unsigned int	start, c, count = 0;
    232 	__u64		left;
    233 	char		*ptr = (char *) buf;
    234 
    235 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    236 	fs = file->fs;
    237 
    238 	while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) {
    239 		retval = sync_buffer_position(file);
    240 		if (retval)
    241 			goto fail;
    242 		retval = load_buffer(file, 0);
    243 		if (retval)
    244 			goto fail;
    245 
    246 		start = file->pos % fs->blocksize;
    247 		c = fs->blocksize - start;
    248 		if (c > wanted)
    249 			c = wanted;
    250 		left = EXT2_I_SIZE(&file->inode) - file->pos ;
    251 		if (c > left)
    252 			c = left;
    253 
    254 		memcpy(ptr, file->buf+start, c);
    255 		file->pos += c;
    256 		ptr += c;
    257 		count += c;
    258 		wanted -= c;
    259 	}
    260 
    261 fail:
    262 	if (got)
    263 		*got = count;
    264 	return retval;
    265 }
    266 
    267 
    268 errcode_t ext2fs_file_write(ext2_file_t file, const void *buf,
    269 			    unsigned int nbytes, unsigned int *written)
    270 {
    271 	ext2_filsys	fs;
    272 	errcode_t	retval = 0;
    273 	unsigned int	start, c, count = 0;
    274 	const char	*ptr = (const char *) buf;
    275 
    276 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    277 	fs = file->fs;
    278 
    279 	if (!(file->flags & EXT2_FILE_WRITE))
    280 		return EXT2_ET_FILE_RO;
    281 
    282 	while (nbytes > 0) {
    283 		retval = sync_buffer_position(file);
    284 		if (retval)
    285 			goto fail;
    286 
    287 		start = file->pos % fs->blocksize;
    288 		c = fs->blocksize - start;
    289 		if (c > nbytes)
    290 			c = nbytes;
    291 
    292 		/*
    293 		 * We only need to do a read-modify-update cycle if
    294 		 * we're doing a partial write.
    295 		 */
    296 		retval = load_buffer(file, (c == fs->blocksize));
    297 		if (retval)
    298 			goto fail;
    299 
    300 		/*
    301 		 * OK, the physical block hasn't been allocated yet.
    302 		 * Allocate it.
    303 		 */
    304 		if (!file->physblock) {
    305 			retval = ext2fs_bmap2(fs, file->ino, &file->inode,
    306 					      BMAP_BUFFER,
    307 					      file->ino ? BMAP_ALLOC : 0,
    308 					      file->blockno, 0,
    309 					      &file->physblock);
    310 			if (retval)
    311 				goto fail;
    312 		}
    313 
    314 		file->flags |= EXT2_FILE_BUF_DIRTY;
    315 		memcpy(file->buf+start, ptr, c);
    316 		file->pos += c;
    317 		ptr += c;
    318 		count += c;
    319 		nbytes -= c;
    320 	}
    321 
    322 fail:
    323 	/* Update inode size */
    324 	if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) {
    325 		errcode_t	rc;
    326 
    327 		rc = ext2fs_file_set_size2(file, file->pos);
    328 		if (retval == 0)
    329 			retval = rc;
    330 	}
    331 
    332 	if (written)
    333 		*written = count;
    334 	return retval;
    335 }
    336 
    337 errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset,
    338 			    int whence, __u64 *ret_pos)
    339 {
    340 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    341 
    342 	if (whence == EXT2_SEEK_SET)
    343 		file->pos = offset;
    344 	else if (whence == EXT2_SEEK_CUR)
    345 		file->pos += offset;
    346 	else if (whence == EXT2_SEEK_END)
    347 		file->pos = EXT2_I_SIZE(&file->inode) + offset;
    348 	else
    349 		return EXT2_ET_INVALID_ARGUMENT;
    350 
    351 	if (ret_pos)
    352 		*ret_pos = file->pos;
    353 
    354 	return 0;
    355 }
    356 
    357 errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset,
    358 			    int whence, ext2_off_t *ret_pos)
    359 {
    360 	__u64		loffset, ret_loffset;
    361 	errcode_t	retval;
    362 
    363 	loffset = offset;
    364 	retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset);
    365 	if (ret_pos)
    366 		*ret_pos = (ext2_off_t) ret_loffset;
    367 	return retval;
    368 }
    369 
    370 
    371 /*
    372  * This function returns the size of the file, according to the inode
    373  */
    374 errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size)
    375 {
    376 	if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
    377 		return EXT2_ET_MAGIC_EXT2_FILE;
    378 	*ret_size = EXT2_I_SIZE(&file->inode);
    379 	return 0;
    380 }
    381 
    382 /*
    383  * This function returns the size of the file, according to the inode
    384  */
    385 ext2_off_t ext2fs_file_get_size(ext2_file_t file)
    386 {
    387 	__u64	size;
    388 
    389 	if (ext2fs_file_get_lsize(file, &size))
    390 		return 0;
    391 	if ((size >> 32) != 0)
    392 		return 0;
    393 	return size;
    394 }
    395 
    396 /* Zero the parts of the last block that are past EOF. */
    397 static errcode_t ext2fs_file_zero_past_offset(ext2_file_t file,
    398 					      ext2_off64_t offset)
    399 {
    400 	ext2_filsys fs = file->fs;
    401 	char *b = NULL;
    402 	ext2_off64_t off = offset % fs->blocksize;
    403 	blk64_t blk;
    404 	int ret_flags;
    405 	errcode_t retval;
    406 
    407 	if (off == 0)
    408 		return 0;
    409 
    410 	retval = sync_buffer_position(file);
    411 	if (retval)
    412 		return retval;
    413 
    414 	/* Is there an initialized block at the end? */
    415 	retval = ext2fs_bmap2(fs, file->ino, NULL, NULL, 0,
    416 			      offset / fs->blocksize, &ret_flags, &blk);
    417 	if (retval)
    418 		return retval;
    419 	if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT))
    420 		return 0;
    421 
    422 	/* Zero to the end of the block */
    423 	retval = ext2fs_get_mem(fs->blocksize, &b);
    424 	if (retval)
    425 		return retval;
    426 
    427 	/* Read/zero/write block */
    428 	retval = io_channel_read_blk64(fs->io, blk, 1, b);
    429 	if (retval)
    430 		goto out;
    431 
    432 	memset(b + off, 0, fs->blocksize - off);
    433 
    434 	retval = io_channel_write_blk64(fs->io, blk, 1, b);
    435 	if (retval)
    436 		goto out;
    437 
    438 out:
    439 	ext2fs_free_mem(&b);
    440 	return retval;
    441 }
    442 
    443 /*
    444  * This function sets the size of the file, truncating it if necessary
    445  *
    446  */
    447 errcode_t ext2fs_file_set_size2(ext2_file_t file, ext2_off64_t size)
    448 {
    449 	ext2_off64_t	old_size;
    450 	errcode_t	retval;
    451 	blk64_t		old_truncate, truncate_block;
    452 
    453 	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
    454 
    455 	if (size && ext2fs_file_block_offset_too_big(file->fs, &file->inode,
    456 					(size - 1) / file->fs->blocksize))
    457 		return EXT2_ET_FILE_TOO_BIG;
    458 	truncate_block = ((size + file->fs->blocksize - 1) >>
    459 			  EXT2_BLOCK_SIZE_BITS(file->fs->super));
    460 	old_size = EXT2_I_SIZE(&file->inode);
    461 	old_truncate = ((old_size + file->fs->blocksize - 1) >>
    462 		      EXT2_BLOCK_SIZE_BITS(file->fs->super));
    463 
    464 	/* If we're writing a large file, set the large_file flag */
    465 	if (LINUX_S_ISREG(file->inode.i_mode) &&
    466 	    ext2fs_needs_large_file_feature(EXT2_I_SIZE(&file->inode)) &&
    467 	    (!EXT2_HAS_RO_COMPAT_FEATURE(file->fs->super,
    468 					 EXT2_FEATURE_RO_COMPAT_LARGE_FILE) ||
    469 	     file->fs->super->s_rev_level == EXT2_GOOD_OLD_REV)) {
    470 		file->fs->super->s_feature_ro_compat |=
    471 				EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
    472 		ext2fs_update_dynamic_rev(file->fs);
    473 		ext2fs_mark_super_dirty(file->fs);
    474 	}
    475 
    476 	file->inode.i_size = size & 0xffffffff;
    477 	file->inode.i_size_high = (size >> 32);
    478 	if (file->ino) {
    479 		retval = ext2fs_write_inode(file->fs, file->ino, &file->inode);
    480 		if (retval)
    481 			return retval;
    482 	}
    483 
    484 	retval = ext2fs_file_zero_past_offset(file, size);
    485 	if (retval)
    486 		return retval;
    487 
    488 	if (truncate_block >= old_truncate)
    489 		return 0;
    490 
    491 	return ext2fs_punch(file->fs, file->ino, &file->inode, 0,
    492 			    truncate_block, ~0ULL);
    493 }
    494 
    495 errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size)
    496 {
    497 	return ext2fs_file_set_size2(file, size);
    498 }
    499