• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ext2fs_file_open2(ext2_filsys fs,ext2_ino_t ino,struct ext2_inode * inode,int flags,ext2_file_t * ret)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 
ext2fs_file_open(ext2_filsys fs,ext2_ino_t ino,int flags,ext2_file_t * ret)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  */
ext2fs_file_get_fs(ext2_file_t file)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  */
ext2fs_file_get_inode(ext2_file_t file)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 */
ext2fs_file_get_inode_num(ext2_file_t file)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  */
ext2fs_file_flush(ext2_file_t file)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  */
sync_buffer_position(ext2_file_t file)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
load_buffer(ext2_file_t file,int dontfill)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 
ext2fs_file_close(ext2_file_t file)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
ext2fs_file_read_inline_data(ext2_file_t file,void * buf,unsigned int wanted,unsigned int * got)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 
ext2fs_file_read(ext2_file_t file,void * buf,unsigned int wanted,unsigned int * got)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
ext2fs_file_write_inline_data(ext2_file_t file,const void * buf,unsigned int nbytes,unsigned int * written)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 
ext2fs_file_write(ext2_file_t file,const void * buf,unsigned int nbytes,unsigned int * written)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 
ext2fs_file_llseek(ext2_file_t file,__u64 offset,int whence,__u64 * ret_pos)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 
ext2fs_file_lseek(ext2_file_t file,ext2_off_t offset,int whence,ext2_off_t * ret_pos)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  */
ext2fs_file_get_lsize(ext2_file_t file,__u64 * ret_size)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  */
ext2fs_file_get_size(ext2_file_t file)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. */
ext2fs_file_zero_past_offset(ext2_file_t file,ext2_off64_t offset)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  */
ext2fs_file_set_size2(ext2_file_t file,ext2_off64_t size)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 
ext2fs_file_set_size(ext2_file_t file,ext2_off_t size)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 }
661