Coverage Report

Created: 2026-06-08 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/thirdparty/extract/src/buffer.c
Line
Count
Source
1
#include "extract/buffer.h"
2
#include "extract/alloc.h"
3
4
#include "outf.h"
5
6
#include <assert.h>
7
#include <errno.h>
8
#include <stdio.h>
9
#include <stdlib.h>
10
#include <string.h>
11
12
/* TODO: Check whether the whole complexity of the cache is actually justified. */
13
14
struct extract_buffer_t
15
{
16
  /* First member must be extract_buffer_cache_t - required by inline
17
  implementations of extract_buffer_read() and extract_buffer_write(). */
18
  extract_buffer_cache_t   cache;
19
  extract_alloc_t         *alloc;
20
  void                    *handle;
21
  extract_buffer_fn_read  *fn_read;
22
  extract_buffer_fn_write *fn_write;
23
  extract_buffer_fn_cache *fn_cache;
24
  extract_buffer_fn_close *fn_close;
25
  size_t                   pos;    /* Does not include bytes currently read/written to cache. */
26
};
27
28
29
extract_alloc_t *extract_buffer_alloc(extract_buffer_t* buffer)
30
0
{
31
0
  return buffer->alloc;
32
0
}
33
34
35
int extract_buffer_open(extract_alloc_t          *alloc,
36
      void                     *handle,
37
      extract_buffer_fn_read   *fn_read,
38
      extract_buffer_fn_write  *fn_write,
39
      extract_buffer_fn_cache  *fn_cache,
40
      extract_buffer_fn_close  *fn_close,
41
      extract_buffer_t        **o_buffer)
42
0
{
43
0
  extract_buffer_t *buffer;
44
45
0
  if (extract_malloc(alloc, &buffer, sizeof(*buffer)))
46
0
    return -1;
47
48
0
  buffer->alloc = alloc;
49
0
  buffer->handle = handle;
50
0
  buffer->fn_read = fn_read;
51
0
  buffer->fn_write = fn_write;
52
0
  buffer->fn_cache = fn_cache;
53
0
  buffer->fn_close = fn_close;
54
0
  buffer->cache.cache = NULL;
55
0
  buffer->cache.numbytes = 0;
56
0
  buffer->cache.pos = 0;
57
0
  buffer->pos = 0;
58
59
0
  *o_buffer = buffer;
60
61
0
  return 0;
62
0
}
63
64
65
size_t extract_buffer_pos(extract_buffer_t *buffer)
66
0
{
67
0
  size_t ret = buffer->pos;
68
69
0
  if (buffer->cache.cache)
70
0
    ret += buffer->cache.pos;
71
72
0
  return ret;
73
0
}
74
75
76
/* Send contents of cache to fn_write() using a loop to cope with short
77
writes. Returns with *o_actual containing the number of bytes successfully
78
sent, and buffer->cache.{cache,numbytes,pos} all set to zero.
79
80
If we return zero but *actual is less than original buffer->cache.numbytes,
81
then fn_write returned EOF. */
82
static int cache_flush(extract_buffer_t *buffer, size_t *o_actual)
83
0
{
84
0
  int e = -1;
85
0
  size_t p = 0;
86
87
0
  assert(buffer->cache.pos <= buffer->cache.numbytes);
88
89
0
  while (p != buffer->cache.pos)
90
0
  {
91
0
    size_t actual;
92
0
    if (buffer->fn_write(
93
0
      buffer->handle,
94
0
      (char*) buffer->cache.cache + p,
95
0
      buffer->cache.pos - p,
96
0
      &actual
97
0
      )) goto end;
98
0
    buffer->pos += actual;
99
0
    p += actual;
100
0
    if (actual == 0)
101
0
    {
102
      /* EOF while flushing cache. We set <pos> to the
103
       * number of bytes in data..+numbytes that we know
104
       * have been successfully handled by buffer->fn_write().
105
       * This can be negative if we failed to flush
106
       * earlier data. */
107
0
      outf("*** buffer->fn_write() EOF\n");
108
0
      e = 0;
109
0
      goto end;
110
0
    }
111
0
  }
112
0
  outfx("cache flush, buffer->pos=%i p=buffer->cache.pos=%i\n",
113
0
    buffer->pos, p);
114
0
  buffer->cache.cache = NULL;
115
0
  buffer->cache.numbytes = 0;
116
0
  buffer->cache.pos = 0;
117
118
0
  e = 0;
119
0
end:
120
0
  *o_actual = p;
121
122
0
  return e;
123
0
}
124
125
int extract_buffer_close(extract_buffer_t **p_buffer)
126
0
{
127
0
  extract_buffer_t *buffer = *p_buffer;
128
0
  int e = -1;
129
130
0
  if (buffer == NULL)
131
0
    return 0;
132
133
0
  if (buffer->cache.cache && buffer->fn_write)
134
0
  {
135
    /* Flush cache. */
136
0
    size_t cache_bytes = buffer->cache.pos;
137
0
    size_t actual;
138
0
    if (cache_flush(buffer, &actual)) goto end;
139
0
    if (actual != cache_bytes)
140
0
    {
141
0
      e = 1;
142
0
      goto end;
143
0
    }
144
0
  }
145
146
0
  if (buffer->fn_close)
147
0
    buffer->fn_close(buffer->handle);
148
149
0
  e = 0;
150
0
end:
151
0
  extract_free(buffer->alloc, &buffer);
152
0
  *p_buffer = NULL;
153
154
0
  return e;
155
0
}
156
157
static int simple_cache(void *handle, void **o_cache, size_t *o_numbytes)
158
0
{
159
  /* Indicate EOF. */
160
0
  (void) handle;
161
0
  *o_cache = NULL;
162
0
  *o_numbytes = 0;
163
164
0
  return 0;
165
0
}
166
167
int extract_buffer_open_simple(extract_alloc_t           *alloc,
168
        const void               *data,
169
        size_t                    numbytes,
170
        void                     *handle,
171
        extract_buffer_fn_close  *fn_close,
172
        extract_buffer_t        **o_buffer)
173
0
{
174
0
  extract_buffer_t *buffer;
175
176
0
  if (extract_malloc(alloc, &buffer, sizeof(*buffer)))
177
0
    return -1;
178
179
  /* We need cast away the const here. data[] will be written-to if caller
180
  uses us as a write buffer. */
181
0
  buffer->alloc = alloc;
182
0
  buffer->cache.cache = (void*) data;
183
0
  buffer->cache.numbytes = numbytes;
184
0
  buffer->cache.pos = 0;
185
0
  buffer->handle = handle;
186
0
  buffer->fn_read = NULL;
187
0
  buffer->fn_write = NULL;
188
0
  buffer->fn_cache = simple_cache;
189
0
  buffer->fn_close = fn_close;
190
0
  *o_buffer = buffer;
191
192
0
  return 0;
193
0
}
194
195
196
/* Implementation of extract_buffer_file*. */
197
198
static int file_read(void *handle, void *data, size_t numbytes, size_t *o_actual)
199
0
{
200
0
  FILE   *file = handle;
201
0
  size_t  n    = fread(data, 1, numbytes, file);
202
203
0
  outfx("file=%p numbytes=%i => n=%zi", file, numbytes, n);
204
0
  assert(o_actual); /* We are called by other extract_buffer fns, not by user code. */
205
206
0
  *o_actual = n;
207
0
  if (n == 0 && ferror(file))
208
0
  {
209
0
    errno = EIO;
210
0
    return -1;
211
0
  }
212
213
0
  return 0;
214
0
}
215
216
static int file_write(void *handle, const void *data, size_t numbytes, size_t *o_actual)
217
0
{
218
0
  FILE   *file = handle;
219
0
  size_t  n    = fwrite(data, 1 /*size*/, numbytes /*nmemb*/, file);
220
221
0
  outfx("file=%p numbytes=%i => n=%zi", file, numbytes, n);
222
0
  assert(o_actual); /* We are called by other extract_buffer fns, not by user code. */
223
224
0
  *o_actual = n;
225
0
  if (n == 0 && ferror(file))
226
0
  {
227
0
    errno = EIO;
228
0
    return -1;
229
0
  }
230
231
0
  return 0;
232
0
}
233
234
static void file_close(void *handle)
235
0
{
236
0
  FILE *file = handle;
237
238
0
  if (file)
239
0
    fclose(file);
240
0
}
241
242
int extract_buffer_open_file(extract_alloc_t *alloc, const char *path, int writable, extract_buffer_t **o_buffer)
243
0
{
244
0
  int   e    = -1;
245
0
  FILE *file = fopen(path, (writable) ? "wb" : "rb");
246
247
0
  if (!file)
248
0
  {
249
0
    outf("failed to open '%s': %s", path, strerror(errno));
250
0
    goto end;
251
0
  }
252
253
0
  if (extract_buffer_open(alloc,
254
0
        file /*handle*/,
255
0
        writable ? NULL : file_read,
256
0
        writable ? file_write : NULL,
257
0
        NULL /*fn_cache*/,
258
0
        file_close,
259
0
        o_buffer)) goto end;
260
261
0
  e = 0;
262
0
end:
263
264
0
  if (e)
265
0
  {
266
0
    if (file)
267
0
      fclose(file);
268
0
    *o_buffer = NULL;
269
0
  }
270
271
0
  return e;
272
0
}
273
274
275
/* Support for read/write. */
276
277
/* Called by extract_buffer_read() if not enough space in buffer->cache. */
278
int extract_buffer_read_internal(extract_buffer_t *buffer,
279
        void              *destination,
280
        size_t             numbytes,
281
        size_t            *o_actual)
282
0
{
283
0
  int    e   = -1;
284
0
  size_t pos = 0;    /* Number of bytes read so far. */
285
286
  /* In each iteration we either read from cache, or use buffer->fn_read()
287
  directly or repopulate the cache. */
288
0
  while (pos != numbytes)
289
0
  {
290
0
    size_t n = buffer->cache.numbytes - buffer->cache.pos;
291
0
    if (n)
292
0
    {
293
      /* There is data in cache. */
294
0
      if (n > numbytes - pos) n = numbytes - pos;
295
0
      memcpy((char *)destination + pos, (char *)buffer->cache.cache + buffer->cache.pos, n);
296
0
      pos += n;
297
0
      buffer->cache.pos += n;
298
0
    }
299
    /* No data in the cache - do we use fn_read or fn_cache ? */
300
0
    else if (buffer->fn_read &&
301
0
        (buffer->fn_cache == NULL ||
302
0
          (buffer->cache.numbytes && numbytes - pos > buffer->cache.numbytes / 2)))
303
0
    {
304
      /* Either there is no cache, or this read is large
305
       * compared to previously-returned cache size, so
306
       * let's ignore buffer->fn_cache and use
307
       * buffer->fn_read() directly instead. */
308
      /* Carry on looping in case of short read. */
309
0
      size_t actual;
310
0
      outfx("using buffer->fn_read() directly for numbytes-pos=%i\n", numbytes-pos);
311
0
      if (buffer->fn_read(buffer->handle, (char*) destination + pos, numbytes - pos, &actual))
312
0
        goto end;
313
0
      if (actual == 0)
314
0
        break; /* EOF. */
315
0
      pos += actual;
316
0
      buffer->pos += actual;
317
0
    }
318
0
    else
319
0
    {
320
      /* Repopulate cache. */
321
0
      outfx("using buffer->fn_cache() for buffer->cache.numbytes=%i\n", buffer->cache.numbytes);
322
0
      if (buffer->fn_cache(buffer->handle, &buffer->cache.cache, &buffer->cache.numbytes))
323
0
        goto end;
324
0
      buffer->pos += buffer->cache.pos;
325
0
      buffer->cache.pos = 0;
326
0
      if (buffer->cache.numbytes == 0)
327
0
        break; /* EOF. */
328
0
    }
329
0
  }
330
331
0
  e = 0;
332
0
end:
333
334
0
  if (o_actual)
335
0
    *o_actual = pos;
336
0
  if (e == 0 && pos != numbytes)
337
0
    return +1; /* EOF. */
338
339
0
  return e;
340
0
}
341
342
343
int extract_buffer_write_internal(extract_buffer_t *buffer,
344
                                  const void       *source,
345
                                  size_t            numbytes,
346
                                  size_t           *o_actual)
347
0
{
348
0
  int    e   = -1;
349
0
  size_t pos = 0;    /* Number of bytes written so far. */
350
351
0
  if (buffer->fn_write == NULL)
352
0
  {
353
0
    errno = EINVAL;
354
0
    return -1;
355
0
  }
356
357
  /* In each iteration we either write to cache, or use buffer->fn_write()
358
  directly or flush the cache. */
359
0
  while (pos != numbytes)
360
0
  {
361
0
    size_t n = buffer->cache.numbytes - buffer->cache.pos;
362
0
    outfx("numbytes=%i pos=%i. buffer->cache.numbytes=%i buffer->cache.pos=%i\n",
363
0
      numbytes, pos, buffer->cache.numbytes, buffer->cache.pos);
364
0
    if (n)
365
0
    {
366
      /* There is space in cache for writing. */
367
0
      if (n > numbytes - pos)
368
0
        n = numbytes - pos;
369
0
      outfx("writing to cache: numbytes=%i n=%i\n", numbytes, n);
370
0
      memcpy((char*) buffer->cache.cache + buffer->cache.pos, (char*) source + pos, n);
371
0
      pos += n;
372
0
      buffer->cache.pos += n;
373
0
    }
374
0
    else
375
0
    {
376
      /* No space left in cache. */
377
0
      outfx("cache empty. pos=%i. buffer->cache.numbytes=%i buffer->cache.pos=%i\n",
378
0
        pos, buffer->cache.numbytes, buffer->cache.pos);
379
0
      {
380
        /* Flush the cache. */
381
0
        size_t actual;
382
0
        size_t b = buffer->cache.numbytes;
383
0
        ptrdiff_t delta;
384
0
        int ee = cache_flush(buffer, &actual);
385
0
        assert(actual <= b);
386
0
        delta = actual - b;
387
0
        pos += delta;
388
0
        buffer->pos += delta;
389
0
        if (delta)
390
0
        {
391
          /* We have only partially flushed the cache. This
392
           * is not recoverable. <pos> will be the number of
393
           * bytes in source..+numbytes that have been
394
           * successfully flushed, and could be negative
395
           * if we failed to flush earlier data. */
396
0
          outf("failed to flush. actual=%li delta=%li\n", (long) actual, (long) delta);
397
0
          e = 0;
398
0
          goto end;
399
0
        }
400
0
        if (ee) goto end;
401
0
      }
402
403
0
      if (buffer->fn_cache == NULL ||
404
0
        (buffer->cache.numbytes && numbytes - pos > buffer->cache.numbytes / 2))
405
0
      {
406
        /* Either there is no cache, or this write is large
407
         * compared to previously-returned cache size, so let's
408
         * ignore the cache and call buffer->fn_write()
409
         * directly instead. Carry on looping in case of short
410
         * write. */
411
0
        size_t actual;
412
0
        if (buffer->fn_write(buffer->handle, (char*) source + pos, numbytes - pos, &actual))
413
0
          goto end;
414
0
        if (actual == 0)
415
0
          break; /* EOF. */
416
0
        outfx("direct write numbytes-pos=%i actual=%i buffer->pos=%i => %i\n",
417
0
          numbytes-pos, actual, buffer->pos, buffer->pos + actual);
418
0
        pos += actual;
419
0
        buffer->pos += actual;
420
0
      }
421
0
      else
422
0
      {
423
        /* Repopulate cache. */
424
0
        outfx("repopulating cache buffer->pos=%i", buffer->pos);
425
0
        if (buffer->fn_cache(buffer->handle, &buffer->cache.cache, &buffer->cache.numbytes))
426
0
          goto end;
427
0
        buffer->cache.pos = 0;
428
0
        if (buffer->cache.numbytes == 0)
429
0
          break;    /* EOF. */
430
0
      }
431
0
    }
432
0
  }
433
434
0
  e = 0;
435
0
end:
436
437
0
  if (o_actual)
438
0
    *o_actual = pos;
439
0
  if (e == 0 && pos != numbytes)
440
0
    e = +1; /* EOF. */
441
442
0
  return e;
443
0
}
444
445
446
static int expanding_memory_buffer_write(void *handle, const void *source, size_t numbytes, size_t *o_actual)
447
0
{
448
  /* We realloc our memory region as required. For efficiency, we also use
449
   * any currently-unused region of our memory buffer as an extract_buffer
450
   * cache. So we can be called either to 'flush the cache' (in which case we
451
   * don't actually copy any data) or to accept data from somewhere else (in
452
   * which case we need to increase the size of our memory region. */
453
0
  extract_buffer_expanding_t *ebe = handle;
454
0
  if ((char *)source >= ebe->data && (char *)source < ebe->data + ebe->alloc_size)
455
0
  {
456
    /* Source is inside our memory region so we are being called by
457
     * extract_buffer_write_internal() to re-populate the cache. We don't
458
     * actually have to copy anything. */
459
0
    assert((size_t) ((char *)source - ebe->data) == ebe->data_size);
460
0
    assert((size_t) ((char *)source - ebe->data + numbytes) <= ebe->alloc_size);
461
0
    ebe->data_size += numbytes;
462
0
  }
463
0
  else
464
0
  {
465
    /* Data is external, so copy into our buffer. We will have already been
466
    called to flush the cache. */
467
0
    if (extract_realloc2(ebe->buffer->alloc, &ebe->data, ebe->alloc_size, ebe->data_size + numbytes))
468
0
      return -1;
469
0
    ebe->alloc_size = ebe->data_size + numbytes;
470
0
    memcpy(ebe->data + ebe->data_size, source, numbytes);
471
0
    ebe->data_size += numbytes;
472
0
  }
473
0
  *o_actual = numbytes;
474
475
0
  return 0;
476
0
}
477
478
static int expanding_memory_buffer_cache(void *handle, void **o_cache, size_t *o_numbytes)
479
0
{
480
0
  extract_buffer_expanding_t *ebe   = handle;
481
0
  size_t                      delta = 4096;
482
483
0
  if (extract_realloc2(ebe->buffer->alloc, &ebe->data, ebe->alloc_size, ebe->data_size + delta))
484
0
    return -1;
485
486
0
  ebe->alloc_size = ebe->data_size + delta;
487
0
  *o_cache = ebe->data + ebe->data_size;
488
0
  *o_numbytes = delta;
489
490
0
  return 0;
491
0
}
492
493
int extract_buffer_expanding_create(extract_alloc_t *alloc, extract_buffer_expanding_t *ebe)
494
0
{
495
0
  ebe->data = NULL;
496
0
  ebe->data_size = 0;
497
0
  ebe->alloc_size = 0;
498
0
  if (extract_buffer_open(alloc,
499
0
        ebe,
500
0
        NULL /*fn_read*/,
501
0
        expanding_memory_buffer_write,
502
0
        expanding_memory_buffer_cache,
503
0
        NULL /*fn_close*/,
504
0
        &ebe->buffer))
505
0
    return -1;
506
507
0
  return 0;
508
0
}