Coverage Report

Created: 2024-09-08 06:27

/src/e2fsprogs/lib/ext2fs/fileio.c
Line
Count
Source (jump to first uncovered line)
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
0
#define BMAP_BUFFER (file->buf + fs->blocksize)
42
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
0
{
47
0
  ext2_file_t   file;
48
0
  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
0
  if ((flags & (EXT2_FILE_WRITE | EXT2_FILE_CREATE)) &&
55
0
      !(fs->flags & EXT2_FLAG_RW))
56
0
    return EXT2_ET_RO_FILSYS;
57
58
0
  retval = ext2fs_get_mem(sizeof(struct ext2_file), &file);
59
0
  if (retval)
60
0
    return retval;
61
62
0
  memset(file, 0, sizeof(struct ext2_file));
63
0
  file->magic = EXT2_ET_MAGIC_EXT2_FILE;
64
0
  file->fs = fs;
65
0
  file->ino = ino;
66
0
  file->flags = flags & EXT2_FILE_MASK;
67
68
0
  if (inode) {
69
0
    memcpy(&file->inode, inode, sizeof(struct ext2_inode));
70
0
  } else {
71
0
    retval = ext2fs_read_inode(fs, ino, &file->inode);
72
0
    if (retval)
73
0
      goto fail;
74
0
  }
75
76
0
  retval = ext2fs_get_array(3, fs->blocksize, &file->buf);
77
0
  if (retval)
78
0
    goto fail;
79
80
0
  *ret = file;
81
0
  return 0;
82
83
0
fail:
84
0
  if (file->buf)
85
0
    ext2fs_free_mem(&file->buf);
86
0
  ext2fs_free_mem(&file);
87
0
  return retval;
88
0
}
89
90
errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino,
91
         int flags, ext2_file_t *ret)
92
0
{
93
0
  return ext2fs_file_open2(fs, ino, NULL, flags, ret);
94
0
}
95
96
/*
97
 * This function returns the filesystem handle of a file from the structure
98
 */
99
ext2_filsys ext2fs_file_get_fs(ext2_file_t file)
100
0
{
101
0
  if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
102
0
    return 0;
103
0
  return file->fs;
104
0
}
105
106
/*
107
 * This function returns the pointer to the inode of a file from the structure
108
 */
109
struct ext2_inode *ext2fs_file_get_inode(ext2_file_t file)
110
0
{
111
0
  if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
112
0
    return NULL;
113
0
  return &file->inode;
114
0
}
115
116
/* This function returns the inode number from the structure */
117
ext2_ino_t ext2fs_file_get_inode_num(ext2_file_t file)
118
0
{
119
0
  if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
120
0
    return 0;
121
0
  return file->ino;
122
0
}
123
124
/*
125
 * This function flushes the dirty block buffer out to disk if
126
 * necessary.
127
 */
128
errcode_t ext2fs_file_flush(ext2_file_t file)
129
0
{
130
0
  errcode_t retval;
131
0
  ext2_filsys fs;
132
0
  int   ret_flags;
133
0
  blk64_t   dontcare;
134
135
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
136
0
  fs = file->fs;
137
138
0
  if (!(file->flags & EXT2_FILE_BUF_VALID) ||
139
0
      !(file->flags & EXT2_FILE_BUF_DIRTY))
140
0
    return 0;
141
142
  /* Is this an uninit block? */
143
0
  if (file->physblock && file->inode.i_flags & EXT4_EXTENTS_FL) {
144
0
    retval = ext2fs_bmap2(fs, file->ino, &file->inode, BMAP_BUFFER,
145
0
              0, file->blockno, &ret_flags, &dontcare);
146
0
    if (retval)
147
0
      return retval;
148
0
    if (ret_flags & BMAP_RET_UNINIT) {
149
0
      retval = ext2fs_bmap2(fs, file->ino, &file->inode,
150
0
                BMAP_BUFFER, BMAP_SET,
151
0
                file->blockno, 0,
152
0
                &file->physblock);
153
0
      if (retval)
154
0
        return retval;
155
0
    }
156
0
  }
157
158
  /*
159
   * OK, the physical block hasn't been allocated yet.
160
   * Allocate it.
161
   */
162
0
  if (!file->physblock) {
163
0
    retval = ext2fs_bmap2(fs, file->ino, &file->inode,
164
0
             BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0,
165
0
             file->blockno, 0, &file->physblock);
166
0
    if (retval)
167
0
      return retval;
168
0
  }
169
170
0
  retval = io_channel_write_blk64(fs->io, file->physblock, 1, file->buf);
171
0
  if (retval)
172
0
    return retval;
173
174
0
  file->flags &= ~EXT2_FILE_BUF_DIRTY;
175
176
0
  return retval;
177
0
}
178
179
/*
180
 * This function synchronizes the file's block buffer and the current
181
 * file position, possibly invalidating block buffer if necessary
182
 */
183
static errcode_t sync_buffer_position(ext2_file_t file)
184
0
{
185
0
  blk64_t b;
186
0
  errcode_t retval;
187
188
0
  b = file->pos / file->fs->blocksize;
189
0
  if (b != file->blockno) {
190
0
    retval = ext2fs_file_flush(file);
191
0
    if (retval)
192
0
      return retval;
193
0
    file->flags &= ~EXT2_FILE_BUF_VALID;
194
0
  }
195
0
  file->blockno = b;
196
0
  return 0;
197
0
}
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
208
static errcode_t load_buffer(ext2_file_t file, int dontfill)
209
0
{
210
0
  ext2_filsys fs = file->fs;
211
0
  errcode_t retval;
212
0
  int   ret_flags;
213
214
0
  if (!(file->flags & EXT2_FILE_BUF_VALID)) {
215
0
    retval = ext2fs_bmap2(fs, file->ino, &file->inode,
216
0
             BMAP_BUFFER, 0, file->blockno, &ret_flags,
217
0
             &file->physblock);
218
0
    if (retval)
219
0
      return retval;
220
0
    if (!dontfill) {
221
0
      if (file->physblock &&
222
0
          !(ret_flags & BMAP_RET_UNINIT)) {
223
0
        retval = io_channel_read_blk64(fs->io,
224
0
                     file->physblock,
225
0
                     1, file->buf);
226
0
        if (retval)
227
0
          return retval;
228
0
      } else
229
0
        memset(file->buf, 0, fs->blocksize);
230
0
    }
231
0
    file->flags |= EXT2_FILE_BUF_VALID;
232
0
  }
233
0
  return 0;
234
0
}
235
236
237
errcode_t ext2fs_file_close(ext2_file_t file)
238
0
{
239
0
  errcode_t retval;
240
241
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
242
243
0
  retval = ext2fs_file_flush(file);
244
245
0
  if (file->buf)
246
0
    ext2fs_free_mem(&file->buf);
247
0
  ext2fs_free_mem(&file);
248
249
0
  return retval;
250
0
}
251
252
253
static errcode_t
254
ext2fs_file_read_inline_data(ext2_file_t file, void *buf,
255
           unsigned int wanted, unsigned int *got)
256
0
{
257
0
  ext2_filsys fs;
258
0
  errcode_t retval;
259
0
  unsigned int count = 0;
260
0
  size_t size;
261
262
0
  fs = file->fs;
263
0
  retval = ext2fs_inline_data_get(fs, file->ino, &file->inode,
264
0
          file->buf, &size);
265
0
  if (retval)
266
0
    return retval;
267
268
0
  if (file->pos >= size)
269
0
    goto out;
270
271
0
  count = size - file->pos;
272
0
  if (count > wanted)
273
0
    count = wanted;
274
0
  memcpy(buf, file->buf + file->pos, count);
275
0
  file->pos += count;
276
0
  buf = (char *) buf + count;
277
278
0
out:
279
0
  if (got)
280
0
    *got = count;
281
0
  return retval;
282
0
}
283
284
285
errcode_t ext2fs_file_read(ext2_file_t file, void *buf,
286
         unsigned int wanted, unsigned int *got)
287
0
{
288
0
  ext2_filsys fs;
289
0
  errcode_t retval = 0;
290
0
  unsigned int  start, c, count = 0;
291
0
  __u64   left;
292
0
  char    *ptr = (char *) buf;
293
294
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
295
0
  fs = file->fs;
296
297
  /* If an inode has inline data, things get complicated. */
298
0
  if (file->inode.i_flags & EXT4_INLINE_DATA_FL)
299
0
    return ext2fs_file_read_inline_data(file, buf, wanted, got);
300
301
0
  while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) {
302
0
    retval = sync_buffer_position(file);
303
0
    if (retval)
304
0
      goto fail;
305
0
    retval = load_buffer(file, 0);
306
0
    if (retval)
307
0
      goto fail;
308
309
0
    start = file->pos % fs->blocksize;
310
0
    c = fs->blocksize - start;
311
0
    if (c > wanted)
312
0
      c = wanted;
313
0
    left = EXT2_I_SIZE(&file->inode) - file->pos ;
314
0
    if (c > left)
315
0
      c = left;
316
317
0
    memcpy(ptr, file->buf+start, c);
318
0
    file->pos += c;
319
0
    ptr += c;
320
0
    count += c;
321
0
    wanted -= c;
322
0
  }
323
324
0
fail:
325
0
  if (got)
326
0
    *got = count;
327
0
  return retval;
328
0
}
329
330
331
static errcode_t
332
ext2fs_file_write_inline_data(ext2_file_t file, const void *buf,
333
            unsigned int nbytes, unsigned int *written)
334
0
{
335
0
  ext2_filsys fs;
336
0
  errcode_t retval;
337
0
  unsigned int count = 0;
338
0
  size_t size;
339
340
0
  fs = file->fs;
341
0
  retval = ext2fs_inline_data_get(fs, file->ino, &file->inode,
342
0
          file->buf, &size);
343
0
  if (retval)
344
0
    return retval;
345
346
0
  if (file->pos < size) {
347
0
    count = nbytes - file->pos;
348
0
    memcpy(file->buf + file->pos, buf, count);
349
350
0
    retval = ext2fs_inline_data_set(fs, file->ino, &file->inode,
351
0
            file->buf, count);
352
0
    if (retval == EXT2_ET_INLINE_DATA_NO_SPACE)
353
0
      goto expand;
354
0
    if (retval)
355
0
      return retval;
356
357
0
    file->pos += count;
358
359
    /* Update inode size */
360
0
    if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) {
361
0
      errcode_t rc;
362
363
0
      rc = ext2fs_file_set_size2(file, file->pos);
364
0
      if (retval == 0)
365
0
        retval = rc;
366
0
    }
367
368
0
    if (written)
369
0
      *written = count;
370
0
    return 0;
371
0
  }
372
373
0
expand:
374
0
  retval = ext2fs_inline_data_expand(fs, file->ino);
375
0
  if (retval)
376
0
    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
0
  retval = ext2fs_read_inode(fs, file->ino, &file->inode);
385
0
  if (retval)
386
0
    return retval;
387
0
  return EXT2_ET_INLINE_DATA_NO_SPACE;
388
0
}
389
390
391
errcode_t ext2fs_file_write(ext2_file_t file, const void *buf,
392
          unsigned int nbytes, unsigned int *written)
393
0
{
394
0
  ext2_filsys fs;
395
0
  errcode_t retval = 0;
396
0
  unsigned int  start, c, count = 0;
397
0
  const char  *ptr = (const char *) buf;
398
0
  block_entry_t new_block = NULL, old_block = NULL;
399
0
  int   bmap_flags = 0;
400
401
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
402
0
  fs = file->fs;
403
404
0
  if (!(file->flags & EXT2_FILE_WRITE))
405
0
    return EXT2_ET_FILE_RO;
406
407
  /* If an inode has inline data, things get complicated. */
408
0
  if (file->inode.i_flags & EXT4_INLINE_DATA_FL) {
409
0
    retval = ext2fs_file_write_inline_data(file, buf, nbytes,
410
0
                   written);
411
0
    if (retval != EXT2_ET_INLINE_DATA_NO_SPACE)
412
0
      return retval;
413
    /* fall through to read data from the block */
414
0
    retval = 0;
415
0
  }
416
417
0
  while (nbytes > 0) {
418
0
    retval = sync_buffer_position(file);
419
0
    if (retval)
420
0
      goto fail;
421
422
0
    start = file->pos % fs->blocksize;
423
0
    c = fs->blocksize - start;
424
0
    if (c > nbytes)
425
0
      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
0
    retval = load_buffer(file, (c == fs->blocksize));
432
0
    if (retval)
433
0
      goto fail;
434
435
0
    file->flags |= EXT2_FILE_BUF_DIRTY;
436
0
    memcpy(file->buf+start, ptr, c);
437
438
    /*
439
     * OK, the physical block hasn't been allocated yet.
440
     * Allocate it.
441
     */
442
0
    if (!file->physblock) {
443
0
      bmap_flags = (file->ino ? BMAP_ALLOC : 0);
444
0
      if (fs->flags & EXT2_FLAG_SHARE_DUP) {
445
0
        new_block = calloc(1, sizeof(*new_block));
446
0
        if (!new_block) {
447
0
          retval = EXT2_ET_NO_MEMORY;
448
0
          goto fail;
449
0
        }
450
0
        ext2fs_sha512((const unsigned char*)file->buf,
451
0
            fs->blocksize, new_block->sha);
452
0
        old_block = ext2fs_hashmap_lookup(
453
0
              fs->block_sha_map,
454
0
              new_block->sha,
455
0
              sizeof(new_block->sha));
456
0
      }
457
458
0
      if (old_block) {
459
0
        file->physblock = old_block->physblock;
460
0
        bmap_flags |= BMAP_SET;
461
0
        free(new_block);
462
0
        new_block = NULL;
463
0
      }
464
465
0
      retval = ext2fs_bmap2(fs, file->ino, &file->inode,
466
0
                BMAP_BUFFER,
467
0
                bmap_flags,
468
0
                file->blockno, 0,
469
0
                &file->physblock);
470
0
      if (retval) {
471
0
        free(new_block);
472
0
        new_block = NULL;
473
0
        goto fail;
474
0
      }
475
476
0
      if (new_block) {
477
0
        new_block->physblock = file->physblock;
478
0
        int ret = ext2fs_hashmap_add(fs->block_sha_map,
479
0
            new_block, new_block->sha,
480
0
            sizeof(new_block->sha));
481
0
        if (ret) {
482
0
          retval = EXT2_ET_NO_MEMORY;
483
0
          free(new_block);
484
0
          goto fail;
485
0
        }
486
0
      }
487
488
0
      if (bmap_flags & BMAP_SET) {
489
0
        ext2fs_iblk_add_blocks(fs, &file->inode, 1);
490
0
        ext2fs_write_inode(fs, file->ino, &file->inode);
491
0
      }
492
0
    }
493
494
0
    file->pos += c;
495
0
    ptr += c;
496
0
    count += c;
497
0
    nbytes -= c;
498
0
  }
499
500
0
fail:
501
  /* Update inode size */
502
0
  if (count != 0 && EXT2_I_SIZE(&file->inode) < file->pos) {
503
0
    errcode_t rc;
504
505
0
    rc = ext2fs_file_set_size2(file, file->pos);
506
0
    if (retval == 0)
507
0
      retval = rc;
508
0
  }
509
510
0
  if (written)
511
0
    *written = count;
512
0
  return retval;
513
0
}
514
515
errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset,
516
          int whence, __u64 *ret_pos)
517
0
{
518
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
519
520
0
  if (whence == EXT2_SEEK_SET)
521
0
    file->pos = offset;
522
0
  else if (whence == EXT2_SEEK_CUR)
523
0
    file->pos += offset;
524
0
  else if (whence == EXT2_SEEK_END)
525
0
    file->pos = EXT2_I_SIZE(&file->inode) + offset;
526
0
  else
527
0
    return EXT2_ET_INVALID_ARGUMENT;
528
529
0
  if (ret_pos)
530
0
    *ret_pos = file->pos;
531
532
0
  return 0;
533
0
}
534
535
errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset,
536
          int whence, ext2_off_t *ret_pos)
537
0
{
538
0
  __u64   loffset, ret_loffset = 0;
539
0
  errcode_t retval;
540
541
0
  loffset = offset;
542
0
  retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset);
543
0
  if (ret_pos)
544
0
    *ret_pos = (ext2_off_t) ret_loffset;
545
0
  return retval;
546
0
}
547
548
549
/*
550
 * This function returns the size of the file, according to the inode
551
 */
552
errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size)
553
0
{
554
0
  if (file->magic != EXT2_ET_MAGIC_EXT2_FILE)
555
0
    return EXT2_ET_MAGIC_EXT2_FILE;
556
0
  *ret_size = EXT2_I_SIZE(&file->inode);
557
0
  return 0;
558
0
}
559
560
/*
561
 * This function returns the size of the file, according to the inode
562
 */
563
ext2_off_t ext2fs_file_get_size(ext2_file_t file)
564
0
{
565
0
  __u64 size;
566
567
0
  if (ext2fs_file_get_lsize(file, &size))
568
0
    return 0;
569
0
  if ((size >> 32) != 0)
570
0
    return 0;
571
0
  return size;
572
0
}
573
574
/* Zero the parts of the last block that are past EOF. */
575
static errcode_t ext2fs_file_zero_past_offset(ext2_file_t file,
576
                ext2_off64_t offset)
577
0
{
578
0
  ext2_filsys fs = file->fs;
579
0
  char *b = NULL;
580
0
  ext2_off64_t off = offset % fs->blocksize;
581
0
  blk64_t blk;
582
0
  int ret_flags;
583
0
  errcode_t retval;
584
585
0
  if (off == 0)
586
0
    return 0;
587
588
0
  retval = sync_buffer_position(file);
589
0
  if (retval)
590
0
    return retval;
591
592
  /* Is there an initialized block at the end? */
593
0
  retval = ext2fs_bmap2(fs, file->ino, NULL, NULL, 0,
594
0
            offset / fs->blocksize, &ret_flags, &blk);
595
0
  if (retval)
596
0
    return retval;
597
0
  if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT))
598
0
    return 0;
599
600
  /* Zero to the end of the block */
601
0
  retval = ext2fs_get_mem(fs->blocksize, &b);
602
0
  if (retval)
603
0
    return retval;
604
605
  /* Read/zero/write block */
606
0
  retval = io_channel_read_blk64(fs->io, blk, 1, b);
607
0
  if (retval)
608
0
    goto out;
609
610
0
  memset(b + off, 0, fs->blocksize - off);
611
612
0
  retval = io_channel_write_blk64(fs->io, blk, 1, b);
613
0
  if (retval)
614
0
    goto out;
615
616
0
out:
617
0
  ext2fs_free_mem(&b);
618
0
  return retval;
619
0
}
620
621
/*
622
 * This function sets the size of the file, truncating it if necessary
623
 *
624
 */
625
errcode_t ext2fs_file_set_size2(ext2_file_t file, ext2_off64_t size)
626
0
{
627
0
  ext2_off64_t  old_size;
628
0
  errcode_t retval;
629
0
  blk64_t   old_truncate, truncate_block;
630
631
0
  EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE);
632
633
0
  if (size && ext2fs_file_block_offset_too_big(file->fs, &file->inode,
634
0
          (size - 1) / file->fs->blocksize))
635
0
    return EXT2_ET_FILE_TOO_BIG;
636
0
  truncate_block = ((size + file->fs->blocksize - 1) >>
637
0
        EXT2_BLOCK_SIZE_BITS(file->fs->super));
638
0
  old_size = EXT2_I_SIZE(&file->inode);
639
0
  old_truncate = ((old_size + file->fs->blocksize - 1) >>
640
0
          EXT2_BLOCK_SIZE_BITS(file->fs->super));
641
642
0
  retval = ext2fs_inode_size_set(file->fs, &file->inode, size);
643
0
  if (retval)
644
0
    return retval;
645
646
0
  if (file->ino) {
647
0
    retval = ext2fs_write_inode(file->fs, file->ino, &file->inode);
648
0
    if (retval)
649
0
      return retval;
650
0
  }
651
652
0
  retval = ext2fs_file_zero_past_offset(file, size);
653
0
  if (retval)
654
0
    return retval;
655
656
0
  if (truncate_block >= old_truncate)
657
0
    return 0;
658
659
0
  return ext2fs_punch(file->fs, file->ino, &file->inode, 0,
660
0
          truncate_block, ~0ULL);
661
0
}
662
663
errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size)
664
0
{
665
0
  return ext2fs_file_set_size2(file, size);
666
0
}