Coverage Report

Created: 2026-09-03 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-test/test-istream.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "memarea.h"
5
#include "istream-private.h"
6
#include "test-common.h"
7
8
struct test_istream {
9
  struct istream_private istream;
10
  const void *orig_buffer;
11
  unsigned int skip_diff;
12
  size_t max_pos;
13
  bool allow_eof;
14
  bool set_input_pending;
15
};
16
17
static void test_buffer_free(unsigned char *buf)
18
0
{
19
0
  i_free(buf);
20
0
}
21
22
static ssize_t test_read(struct istream_private *stream)
23
0
{
24
0
  struct test_istream *tstream = (struct test_istream *)stream;
25
26
0
  if (tstream->set_input_pending)
27
0
    i_stream_set_input_pending(&stream->istream, TRUE);
28
0
  unsigned int new_skip_diff;
29
0
  size_t cur_max;
30
0
  ssize_t ret;
31
32
0
  i_assert(stream->skip <= stream->pos);
33
34
0
  if (stream->pos - stream->skip >= tstream->istream.max_buffer_size) {
35
0
    i_assert(stream->skip != stream->pos);
36
0
    return -2;
37
0
  }
38
39
0
  if (tstream->max_pos < stream->pos) {
40
    /* we seeked past the end of file. */
41
0
    ret = 0;
42
0
  } else {
43
    /* copy data to a buffer in somewhat random place. this could
44
       help catch bugs. */
45
0
    new_skip_diff = i_rand_limit(128);
46
0
    stream->skip = (stream->skip - tstream->skip_diff) +
47
0
      new_skip_diff;
48
0
    stream->pos = (stream->pos - tstream->skip_diff) +
49
0
      new_skip_diff;
50
0
    tstream->max_pos = (tstream->max_pos - tstream->skip_diff) +
51
0
      new_skip_diff;
52
0
    tstream->skip_diff = new_skip_diff;
53
54
0
    cur_max = tstream->max_pos;
55
0
    if (stream->max_buffer_size < SIZE_MAX - stream->skip &&
56
0
        cur_max > stream->skip + stream->max_buffer_size)
57
0
      cur_max = stream->skip + stream->max_buffer_size;
58
59
    /* Reallocate the memory area if needed. Use exactly correct
60
       buffer size so valgrind can catch read overflows. If a
61
       correctly sized memarea already exists, use it only if
62
       its refcount is 1. Otherwise with refcount>1 we could be
63
       moving data within an existing memarea, which breaks
64
       snapshots. */
65
0
    if (cur_max > 0 && (stream->buffer_size != cur_max ||
66
0
            stream->memarea == NULL ||
67
0
            memarea_get_refcount(stream->memarea) > 1)) {
68
0
      void *old_w_buffer = stream->w_buffer;
69
0
      stream->w_buffer = i_malloc(cur_max);
70
0
      if (stream->buffer_size != 0) {
71
0
        memcpy(stream->w_buffer, old_w_buffer,
72
0
               I_MIN(stream->buffer_size, cur_max));
73
0
      }
74
0
      stream->buffer = stream->w_buffer;
75
0
      stream->buffer_size = cur_max;
76
77
0
      if (stream->memarea != NULL)
78
0
        memarea_unref(&stream->memarea);
79
0
      stream->memarea = memarea_init(stream->w_buffer,
80
0
                   stream->buffer_size,
81
0
                   test_buffer_free,
82
0
                   stream->w_buffer);
83
0
    }
84
0
    ssize_t size = cur_max - new_skip_diff;
85
0
    if (size > 0)
86
0
      memcpy(stream->w_buffer + new_skip_diff,
87
0
        tstream->orig_buffer, (size_t)size);
88
89
0
    ret = cur_max - stream->pos;
90
0
    stream->pos = cur_max;
91
0
  }
92
93
0
  if (ret > 0)
94
0
    return ret;
95
0
  else if (!tstream->allow_eof ||
96
0
     stream->pos - tstream->skip_diff < (uoff_t)stream->statbuf.st_size)
97
0
    return 0;
98
0
  else {
99
0
    stream->istream.eof = TRUE;
100
0
    return -1;
101
0
  }
102
0
}
103
104
static void test_seek(struct istream_private *stream, uoff_t v_offset,
105
          bool mark ATTR_UNUSED)
106
0
{
107
0
  struct test_istream *tstream = (struct test_istream *)stream;
108
109
0
  stream->istream.v_offset = v_offset;
110
0
  stream->skip = v_offset + tstream->skip_diff;
111
0
  stream->pos = stream->skip;
112
0
}
113
114
struct istream *test_istream_create_data(const void *data, size_t size)
115
0
{
116
0
  struct test_istream *tstream;
117
118
0
  tstream = i_new(struct test_istream, 1);
119
0
  tstream->orig_buffer = data;
120
121
0
  tstream->istream.read = test_read;
122
0
  tstream->istream.seek = test_seek;
123
124
0
  tstream->istream.istream.blocking = FALSE;
125
0
  tstream->istream.istream.seekable = TRUE;
126
0
  i_stream_create(&tstream->istream, NULL, -1,
127
0
      ISTREAM_HIDDEN_INPUTS_NONE, 0);
128
0
  tstream->istream.statbuf.st_size = tstream->max_pos = size;
129
0
  tstream->allow_eof = TRUE;
130
0
  tstream->istream.max_buffer_size = SIZE_MAX;
131
0
  return &tstream->istream.istream;
132
0
}
133
134
struct istream *test_istream_create(const char *data)
135
0
{
136
0
  return test_istream_create_data(data, strlen(data));
137
0
}
138
139
static struct test_istream *test_istream_find(struct istream *input)
140
0
{
141
0
  struct istream *in;
142
143
0
  i_assert(input != NULL);
144
145
0
  for (in = input; in != NULL; in = in->real_stream->parent) {
146
0
    if (in->real_stream->read == test_read)
147
0
      return (struct test_istream *)in->real_stream;
148
0
  }
149
0
  i_panic("%s isn't test-istream", i_stream_get_name(input));
150
0
}
151
152
void test_istream_set_allow_eof(struct istream *input, bool allow)
153
0
{
154
0
  struct test_istream *tstream = test_istream_find(input);
155
156
0
  tstream->allow_eof = allow;
157
0
}
158
159
void test_istream_set_max_buffer_size(struct istream *input, size_t size)
160
0
{
161
0
  struct test_istream *tstream = test_istream_find(input);
162
163
0
  tstream->istream.max_buffer_size = size;
164
0
}
165
166
void test_istream_set_size(struct istream *input, uoff_t size)
167
0
{
168
0
  struct test_istream *tstream = test_istream_find(input);
169
170
0
  if (size > (uoff_t)tstream->istream.statbuf.st_size)
171
0
    size = (uoff_t)tstream->istream.statbuf.st_size;
172
0
  tstream->max_pos = size + tstream->skip_diff;
173
0
}
174
175
void test_istream_set_input_pending(struct istream *input, bool set)
176
0
{
177
0
  struct test_istream *tstream = test_istream_find(input);
178
179
0
  tstream->set_input_pending = set;
180
0
}