fileio.c revision 269da3b8068ecb0bdf8077b0f7b27f164203cb35
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
23struct 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
37errcode_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
77fail:
78	if (file->buf)
79		ext2fs_free_mem(&file->buf);
80	ext2fs_free_mem(&file);
81	return retval;
82}
83
84errcode_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 */
93ext2_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 */
103struct 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 */
111ext2_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 */
122errcode_t ext2fs_file_flush(ext2_file_t file)
123{
124	errcode_t	retval;
125	ext2_filsys fs;
126
127	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
128	fs = file->fs;
129
130	if (!(file->flags & EXT2_FILE_BUF_VALID) ||
131	    !(file->flags & EXT2_FILE_BUF_DIRTY))
132		return 0;
133
134	/*
135	 * OK, the physical block hasn't been allocated yet.
136	 * Allocate it.
137	 */
138	if (!file->physblock) {
139		retval = ext2fs_bmap2(fs, file->ino, &file->inode,
140				     BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0,
141				     file->blockno, 0, &file->physblock);
142		if (retval)
143			return retval;
144	}
145
146	retval = io_channel_write_blk64(fs->io, file->physblock, 1, file->buf);
147	if (retval)
148		return retval;
149
150	file->flags &= ~EXT2_FILE_BUF_DIRTY;
151
152	return retval;
153}
154
155/*
156 * This function synchronizes the file's block buffer and the current
157 * file position, possibly invalidating block buffer if necessary
158 */
159static errcode_t sync_buffer_position(ext2_file_t file)
160{
161	blk64_t	b;
162	errcode_t	retval;
163
164	b = file->pos / file->fs->blocksize;
165	if (b != file->blockno) {
166		retval = ext2fs_file_flush(file);
167		if (retval)
168			return retval;
169		file->flags &= ~EXT2_FILE_BUF_VALID;
170	}
171	file->blockno = b;
172	return 0;
173}
174
175/*
176 * This function loads the file's block buffer with valid data from
177 * the disk as necessary.
178 *
179 * If dontfill is true, then skip initializing the buffer since we're
180 * going to be replacing its entire contents anyway.  If set, then the
181 * function basically only sets file->physblock and EXT2_FILE_BUF_VALID
182 */
183#define DONTFILL 1
184static errcode_t load_buffer(ext2_file_t file, int dontfill)
185{
186	ext2_filsys	fs = file->fs;
187	errcode_t	retval;
188
189	if (!(file->flags & EXT2_FILE_BUF_VALID)) {
190		retval = ext2fs_bmap2(fs, file->ino, &file->inode,
191				     BMAP_BUFFER, 0, file->blockno, 0,
192				     &file->physblock);
193		if (retval)
194			return retval;
195		if (!dontfill) {
196			if (file->physblock) {
197				retval = io_channel_read_blk64(fs->io,
198							       file->physblock,
199							       1, file->buf);
200				if (retval)
201					return retval;
202			} else
203				memset(file->buf, 0, fs->blocksize);
204		}
205		file->flags |= EXT2_FILE_BUF_VALID;
206	}
207	return 0;
208}
209
210
211errcode_t ext2fs_file_close(ext2_file_t file)
212{
213	errcode_t	retval;
214
215	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
216
217	retval = ext2fs_file_flush(file);
218
219	if (file->buf)
220		ext2fs_free_mem(&file->buf);
221	ext2fs_free_mem(&file);
222
223	return retval;
224}
225
226
227errcode_t ext2fs_file_read(ext2_file_t file, void *buf,
228			   unsigned int wanted, unsigned int *got)
229{
230	ext2_filsys	fs;
231	errcode_t	retval = 0;
232	unsigned int	start, c, count = 0;
233	__u64		left;
234	char		*ptr = (char *) buf;
235
236	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
237	fs = file->fs;
238
239	while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) {
240		retval = sync_buffer_position(file);
241		if (retval)
242			goto fail;
243		retval = load_buffer(file, 0);
244		if (retval)
245			goto fail;
246
247		start = file->pos % fs->blocksize;
248		c = fs->blocksize - start;
249		if (c > wanted)
250			c = wanted;
251		left = EXT2_I_SIZE(&file->inode) - file->pos ;
252		if (c > left)
253			c = left;
254
255		memcpy(ptr, file->buf+start, c);
256		file->pos += c;
257		ptr += c;
258		count += c;
259		wanted -= c;
260	}
261
262fail:
263	if (got)
264		*got = count;
265	return retval;
266}
267
268
269errcode_t ext2fs_file_write(ext2_file_t file, const void *buf,
270			    unsigned int nbytes, unsigned int *written)
271{
272	ext2_filsys	fs;
273	errcode_t	retval = 0;
274	unsigned int	start, c, count = 0;
275	const char	*ptr = (const char *) buf;
276
277	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
278	fs = file->fs;
279
280	if (!(file->flags & EXT2_FILE_WRITE))
281		return EXT2_ET_FILE_RO;
282
283	while (nbytes > 0) {
284		retval = sync_buffer_position(file);
285		if (retval)
286			goto fail;
287
288		start = file->pos % fs->blocksize;
289		c = fs->blocksize - start;
290		if (c > nbytes)
291			c = nbytes;
292
293		/*
294		 * We only need to do a read-modify-update cycle if
295		 * we're doing a partial write.
296		 */
297		retval = load_buffer(file, (c == fs->blocksize));
298		if (retval)
299			goto fail;
300
301		/*
302		 * OK, the physical block hasn't been allocated yet.
303		 * Allocate it.
304		 */
305		if (!file->physblock) {
306			retval = ext2fs_bmap2(fs, file->ino, &file->inode,
307					      BMAP_BUFFER,
308					      file->ino ? BMAP_ALLOC : 0,
309					      file->blockno, 0,
310					      &file->physblock);
311			if (retval)
312				goto fail;
313		}
314
315		file->flags |= EXT2_FILE_BUF_DIRTY;
316		memcpy(file->buf+start, ptr, c);
317		file->pos += c;
318		ptr += c;
319		count += c;
320		nbytes -= c;
321	}
322
323fail:
324	/* Update inode size */
325	if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) {
326		errcode_t	rc;
327
328		rc = ext2fs_file_set_size2(file, file->pos);
329		if (retval == 0)
330			retval = rc;
331	}
332
333	if (written)
334		*written = count;
335	return retval;
336}
337
338errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset,
339			    int whence, __u64 *ret_pos)
340{
341	EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
342
343	if (whence == EXT2_SEEK_SET)
344		file->pos = offset;
345	else if (whence == EXT2_SEEK_CUR)
346		file->pos += offset;
347	else if (whence == EXT2_SEEK_END)
348		file->pos = EXT2_I_SIZE(&file->inode) + offset;
349	else
350		return EXT2_ET_INVALID_ARGUMENT;
351
352	if (ret_pos)
353		*ret_pos = file->pos;
354
355	return 0;
356}
357
358errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset,
359			    int whence, ext2_off_t *ret_pos)
360{
361	__u64		loffset, ret_loffset;
362	errcode_t	retval;
363
364	loffset = offset;
365	retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset);
366	if (ret_pos)
367		*ret_pos = (ext2_off_t) ret_loffset;
368	return retval;
369}
370
371
372/*
373 * This function returns the size of the file, according to the inode
374 */
375errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size)
376{
377	if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
378		return EXT2_ET_MAGIC_EXT2_FILE;
379	*ret_size = EXT2_I_SIZE(&file->inode);
380	return 0;
381}
382
383/*
384 * This function returns the size of the file, according to the inode
385 */
386ext2_off_t ext2fs_file_get_size(ext2_file_t file)
387{
388	__u64	size;
389
390	if (ext2fs_file_get_lsize(file, &size))
391		return 0;
392	if ((size >> 32) != 0)
393		return 0;
394	return size;
395}
396
397/* Zero the parts of the last block that are past EOF. */
398errcode_t ext2fs_file_zero_past_offset(ext2_file_t file, 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
438out:
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 */
447errcode_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
495errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size)
496{
497	return ext2fs_file_set_size2(file, size);
498}
499