blob: 5a39c321aa788e080b2dfacc92e54c9aba309f29 [file] [log] [blame]
lh9ed821d2023-04-07 01:36:19 -07001/*
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. */
398static errcode_t ext2fs_file_zero_past_offset(ext2_file_t file,
399 ext2_off64_t offset)
400{
401 ext2_filsys fs = file->fs;
402 char *b = NULL;
403 ext2_off64_t off = offset % fs->blocksize;
404 blk64_t blk;
405 int ret_flags;
406 errcode_t retval;
407
408 if (off == 0)
409 return 0;
410
411 retval = sync_buffer_position(file);
412 if (retval)
413 return retval;
414
415 /* Is there an initialized block at the end? */
416 retval = ext2fs_bmap2(fs, file->ino, NULL, NULL, 0,
417 offset / fs->blocksize, &ret_flags, &blk);
418 if (retval)
419 return retval;
420 if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT))
421 return 0;
422
423 /* Zero to the end of the block */
424 retval = ext2fs_get_mem(fs->blocksize, &b);
425 if (retval)
426 return retval;
427
428 /* Read/zero/write block */
429 retval = io_channel_read_blk64(fs->io, blk, 1, b);
430 if (retval)
431 goto out;
432
433 memset(b + off, 0, fs->blocksize - off);
434
435 retval = io_channel_write_blk64(fs->io, blk, 1, b);
436 if (retval)
437 goto out;
438
439out:
440 ext2fs_free_mem(&b);
441 return retval;
442}
443
444/*
445 * This function sets the size of the file, truncating it if necessary
446 *
447 */
448errcode_t ext2fs_file_set_size2(ext2_file_t file, ext2_off64_t size)
449{
450 ext2_off64_t old_size;
451 errcode_t retval;
452 blk64_t old_truncate, truncate_block;
453
454 EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
455
456 if (size && ext2fs_file_block_offset_too_big(file->fs, &file->inode,
457 (size - 1) / file->fs->blocksize))
458 return EXT2_ET_FILE_TOO_BIG;
459 truncate_block = ((size + file->fs->blocksize - 1) >>
460 EXT2_BLOCK_SIZE_BITS(file->fs->super));
461 old_size = EXT2_I_SIZE(&file->inode);
462 old_truncate = ((old_size + file->fs->blocksize - 1) >>
463 EXT2_BLOCK_SIZE_BITS(file->fs->super));
464
465 /* If we're writing a large file, set the large_file flag */
466 if (LINUX_S_ISREG(file->inode.i_mode) &&
467 ext2fs_needs_large_file_feature(EXT2_I_SIZE(&file->inode)) &&
468 (!EXT2_HAS_RO_COMPAT_FEATURE(file->fs->super,
469 EXT2_FEATURE_RO_COMPAT_LARGE_FILE) ||
470 file->fs->super->s_rev_level == EXT2_GOOD_OLD_REV)) {
471 file->fs->super->s_feature_ro_compat |=
472 EXT2_FEATURE_RO_COMPAT_LARGE_FILE;
473 ext2fs_update_dynamic_rev(file->fs);
474 ext2fs_mark_super_dirty(file->fs);
475 }
476
477 file->inode.i_size = size & 0xffffffff;
478 file->inode.i_size_high = (size >> 32);
479 if (file->ino) {
480 retval = ext2fs_write_inode(file->fs, file->ino, &file->inode);
481 if (retval)
482 return retval;
483 }
484
485 retval = ext2fs_file_zero_past_offset(file, size);
486 if (retval)
487 return retval;
488
489 if (truncate_block >= old_truncate)
490 return 0;
491
492 return ext2fs_punch(file->fs, file->ino, &file->inode, 0,
493 truncate_block, ~0ULL);
494}
495
496errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size)
497{
498 return ext2fs_file_set_size2(file, size);
499}