Coverage Report

Created: 2026-10-28 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/istream-file.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
/* @UNSAFE: whole file */
4
5
#include "lib.h"
6
#include "ioloop.h"
7
#include "istream-file-private.h"
8
#include "net.h"
9
10
#include <time.h>
11
#include <unistd.h>
12
#include <fcntl.h>
13
#include <sys/stat.h>
14
15
void i_stream_file_close(struct iostream_private *stream,
16
       bool close_parent ATTR_UNUSED)
17
18.5k
{
18
18.5k
  struct istream_private *_stream =
19
18.5k
    container_of(stream, struct istream_private, iostream);
20
18.5k
  struct file_istream *fstream =
21
18.5k
    container_of(_stream, struct file_istream, istream);
22
23
18.5k
  bool refs_left = fstream->fd_ref != NULL &&
24
0
    iostream_fd_unref(&fstream->fd_ref);
25
18.5k
  if (fstream->autoclose_fd && _stream->fd != -1 && !refs_left) {
26
    /* Ignore ECONNRESET because we don't really care about it here,
27
       as we are closing the socket down in any case. There might be
28
       unsent data but nothing we can do about that. */
29
0
    if (unlikely(close(_stream->fd) < 0 && errno != ECONNRESET)) {
30
0
      i_error("file_istream.close(%s) failed: %m",
31
0
        i_stream_get_name(&_stream->istream));
32
0
    }
33
0
  }
34
18.5k
  _stream->fd = -1;
35
18.5k
}
36
37
static int i_stream_file_open(struct istream_private *stream)
38
0
{
39
0
  const char *path = i_stream_get_name(&stream->istream);
40
41
0
  stream->fd = open(path, O_RDONLY);
42
0
  if (stream->fd == -1) {
43
0
    io_stream_set_error(&stream->iostream,
44
0
            "open(%s) failed: %m", path);
45
0
    stream->istream.stream_errno = errno;
46
0
    return -1;
47
0
  }
48
0
  return 0;
49
0
}
50
51
ssize_t i_stream_file_read(struct istream_private *stream)
52
26.5k
{
53
26.5k
  struct file_istream *fstream =
54
26.5k
    container_of(stream, struct file_istream, istream);
55
26.5k
  uoff_t offset;
56
26.5k
  size_t size;
57
26.5k
  ssize_t ret;
58
59
26.5k
  if (!i_stream_try_alloc(stream, 1, &size))
60
0
    return -2;
61
62
26.5k
  if (stream->fd == -1) {
63
0
    if (i_stream_file_open(stream) < 0)
64
0
      return -1;
65
0
    i_assert(stream->fd != -1);
66
0
  }
67
68
26.5k
  offset = stream->istream.v_offset + (stream->pos - stream->skip);
69
70
26.5k
  if (fstream->file) {
71
0
    ret = pread(stream->fd, stream->w_buffer + stream->pos,
72
0
          size, offset);
73
26.5k
  } else if (fstream->seen_eof) {
74
    /* don't try to read() again. EOF from keyboard (^D)
75
       requires this to work right. */
76
0
    ret = 0;
77
26.5k
  } else {
78
26.5k
    ret = read(stream->fd, stream->w_buffer + stream->pos,
79
26.5k
         size);
80
26.5k
  }
81
82
26.5k
  if (ret == 0) {
83
    /* EOF */
84
5.81k
    stream->istream.eof = TRUE;
85
5.81k
    fstream->seen_eof = TRUE;
86
5.81k
    return -1;
87
5.81k
  }
88
89
20.7k
  if (unlikely(ret < 0)) {
90
555
    if ((errno == EINTR || errno == EAGAIN) &&
91
555
        !stream->istream.blocking) {
92
555
      ret = 0;
93
555
    } else {
94
0
      i_assert(errno != 0);
95
      /* if we get EBADF for a valid fd, it means something's
96
         really wrong and we'd better just crash. */
97
0
      i_assert(errno != EBADF);
98
0
      if (fstream->file) {
99
0
        io_stream_set_error(&stream->iostream,
100
0
          "pread(size=%zu offset=%"PRIuUOFF_T") failed: %m",
101
0
          size, offset);
102
0
      } else {
103
0
        io_stream_set_error(&stream->iostream,
104
0
          "read(size=%zu) failed: %m",
105
0
          size);
106
0
      }
107
0
      stream->istream.stream_errno = errno;
108
0
      return -1;
109
0
    }
110
555
  }
111
112
20.7k
  if (ret > 0 && fstream->skip_left > 0) {
113
0
    i_assert(!fstream->file);
114
0
    i_assert(stream->skip == stream->pos);
115
116
0
    if (fstream->skip_left >= (size_t)ret) {
117
0
      fstream->skip_left -= ret;
118
0
      ret = 0;
119
0
    } else {
120
0
      ret -= fstream->skip_left;
121
0
      stream->pos += fstream->skip_left;
122
0
      stream->skip += fstream->skip_left;
123
0
      fstream->skip_left = 0;
124
0
    }
125
0
  }
126
127
20.7k
  stream->pos += ret;
128
20.7k
  i_assert(ret != 0 || !fstream->file);
129
20.7k
  i_assert(ret != -1);
130
20.7k
  return ret;
131
20.7k
}
132
133
static void i_stream_file_seek(struct istream_private *stream, uoff_t v_offset,
134
             bool mark ATTR_UNUSED)
135
0
{
136
0
  struct file_istream *fstream =
137
0
    container_of(stream, struct file_istream, istream);
138
139
0
  if (!stream->istream.seekable) {
140
0
    if (v_offset < stream->istream.v_offset)
141
0
      i_panic("stream doesn't support seeking backwards");
142
0
    fstream->skip_left += v_offset - stream->istream.v_offset;
143
0
  }
144
145
0
  stream->istream.v_offset = v_offset;
146
0
  stream->skip = stream->pos = 0;
147
0
  fstream->seen_eof = FALSE;
148
0
}
149
150
static void i_stream_file_sync(struct istream_private *stream)
151
0
{
152
0
  if (!stream->istream.seekable) {
153
    /* can't do anything or data would be lost */
154
0
    return;
155
0
  }
156
157
0
  stream->skip = stream->pos = 0;
158
0
  stream->istream.eof = FALSE;
159
0
}
160
161
static int
162
i_stream_file_stat(struct istream_private *stream, bool exact ATTR_UNUSED)
163
0
{
164
0
  struct file_istream *fstream =
165
0
    container_of(stream, struct file_istream, istream);
166
0
  const char *name = i_stream_get_name(&stream->istream);
167
168
0
  if (!fstream->file) {
169
    /* return defaults */
170
0
  } else if (stream->fd != -1) {
171
0
    if (fstat(stream->fd, &stream->statbuf) < 0) {
172
0
      stream->istream.stream_errno = errno;
173
0
      io_stream_set_error(&stream->iostream,
174
0
        "file_istream.fstat(%s) failed: %m", name);
175
0
      i_error("%s", i_stream_get_error(&stream->istream));
176
0
      return -1;
177
0
    }
178
0
  } else {
179
0
    if (stat(name, &stream->statbuf) < 0) {
180
0
      stream->istream.stream_errno = errno;
181
0
      io_stream_set_error(&stream->iostream,
182
0
        "file_istream.stat(%s) failed: %m", name);
183
0
      i_error("%s", i_stream_get_error(&stream->istream));
184
0
      return -1;
185
0
    }
186
0
  }
187
0
  return 0;
188
0
}
189
190
struct istream *
191
i_stream_create_file_common(struct file_istream *fstream,
192
          int fd, const char *path,
193
          size_t max_buffer_size, bool autoclose_fd)
194
6.16k
{
195
6.16k
  struct istream *input;
196
6.16k
  struct stat st;
197
6.16k
  bool is_file;
198
6.16k
  int flags;
199
200
6.16k
  fstream->autoclose_fd = autoclose_fd;
201
202
6.16k
  fstream->istream.iostream.close = i_stream_file_close;
203
6.16k
  fstream->istream.max_buffer_size = max_buffer_size;
204
6.16k
  fstream->istream.read = i_stream_file_read;
205
6.16k
  fstream->istream.seek = i_stream_file_seek;
206
6.16k
  fstream->istream.sync = i_stream_file_sync;
207
6.16k
  fstream->istream.stat = i_stream_file_stat;
208
209
  /* if it's a file, set the flags properly */
210
6.16k
  if (fd == -1) {
211
    /* only the path is known for now - the fd is opened later */
212
0
    is_file = TRUE;
213
6.16k
  } else if (fstat(fd, &st) < 0)
214
0
    is_file = FALSE;
215
6.16k
  else if (S_ISREG(st.st_mode))
216
0
    is_file = TRUE;
217
6.16k
  else if (!S_ISDIR(st.st_mode))
218
6.16k
    is_file = FALSE;
219
0
  else {
220
    /* we're trying to open a directory.
221
       we're not designed for it. */
222
0
    io_stream_set_error(&fstream->istream.iostream,
223
0
      "%s is a directory, can't read it as file",
224
0
      path != NULL ? path : t_strdup_printf("<fd %d>", fd));
225
0
    fstream->istream.istream.stream_errno = EISDIR;
226
0
    is_file = FALSE;
227
0
  }
228
6.16k
  if (is_file) {
229
0
    fstream->file = TRUE;
230
0
    fstream->istream.istream.blocking = TRUE;
231
0
    fstream->istream.istream.seekable = TRUE;
232
6.16k
  } else if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
233
0
    i_assert(fd > -1);
234
    /* shouldn't happen */
235
0
    fstream->istream.istream.stream_errno = errno;
236
0
    io_stream_set_error(&fstream->istream.iostream,
237
0
      "fcntl(%d, F_GETFL) failed: %m", fd);
238
6.16k
  } else if ((flags & O_NONBLOCK) == 0) {
239
    /* blocking socket/fifo */
240
0
    fstream->istream.istream.blocking = TRUE;
241
0
  }
242
6.16k
  fstream->istream.istream.readable_fd = TRUE;
243
244
6.16k
  input = i_stream_create(&fstream->istream, NULL, fd,
245
6.16k
        ISTREAM_HIDDEN_INPUTS_NONE, 0);
246
6.16k
  i_stream_set_name(input, is_file ? "(file)" : "(fd)");
247
6.16k
  return input;
248
6.16k
}
249
250
struct istream *i_stream_create_fd(int fd, size_t max_buffer_size)
251
6.16k
{
252
6.16k
  struct file_istream *fstream;
253
254
6.16k
  i_assert(fd != -1);
255
256
6.16k
  fstream = i_new(struct file_istream, 1);
257
6.16k
  return i_stream_create_file_common(fstream, fd, NULL,
258
6.16k
             max_buffer_size, FALSE);
259
6.16k
}
260
261
struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
262
0
{
263
0
  struct istream *input;
264
0
  struct file_istream *fstream;
265
266
0
  i_assert(*fd != -1);
267
268
0
  fstream = i_new(struct file_istream, 1);
269
0
  input = i_stream_create_file_common(fstream, *fd, NULL,
270
0
             max_buffer_size, TRUE);
271
0
  *fd = -1;
272
0
  return input;
273
0
}
274
275
struct istream *i_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
276
             size_t max_buffer_size)
277
0
{
278
0
  struct file_istream *fstream;
279
280
0
  fstream = i_new(struct file_istream, 1);
281
0
  fstream->fd_ref = ref;
282
0
  iostream_fd_ref(ref);
283
0
  return i_stream_create_file_common(fstream, ref->fd, NULL,
284
0
             max_buffer_size, TRUE);
285
0
}
286
287
struct istream *i_stream_create_file(const char *path, size_t max_buffer_size)
288
0
{
289
0
  struct file_istream *fstream;
290
0
  struct istream *input;
291
292
0
  fstream = i_new(struct file_istream, 1);
293
0
  input = i_stream_create_file_common(fstream, -1, path,
294
0
              max_buffer_size, TRUE);
295
0
  i_stream_set_name(input, path);
296
0
  return input;
297
0
}