Coverage Report

Created: 2026-09-14 06:48

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
0
{
18
0
  struct istream_private *_stream =
19
0
    container_of(stream, struct istream_private, iostream);
20
0
  struct file_istream *fstream =
21
0
    container_of(_stream, struct file_istream, istream);
22
23
0
  bool refs_left = fstream->fd_ref != NULL &&
24
0
    iostream_fd_unref(&fstream->fd_ref);
25
0
  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
0
  _stream->fd = -1;
35
0
}
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
0
{
53
0
  struct file_istream *fstream =
54
0
    container_of(stream, struct file_istream, istream);
55
0
  uoff_t offset;
56
0
  size_t size;
57
0
  ssize_t ret;
58
59
0
  if (!i_stream_try_alloc(stream, 1, &size))
60
0
    return -2;
61
62
0
  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
0
  offset = stream->istream.v_offset + (stream->pos - stream->skip);
69
70
0
  if (fstream->file) {
71
0
    ret = pread(stream->fd, stream->w_buffer + stream->pos,
72
0
          size, offset);
73
0
  } 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
0
  } else {
78
0
    ret = read(stream->fd, stream->w_buffer + stream->pos,
79
0
         size);
80
0
  }
81
82
0
  if (ret == 0) {
83
    /* EOF */
84
0
    stream->istream.eof = TRUE;
85
0
    fstream->seen_eof = TRUE;
86
0
    return -1;
87
0
  }
88
89
0
  if (unlikely(ret < 0)) {
90
0
    if ((errno == EINTR || errno == EAGAIN) &&
91
0
        !stream->istream.blocking) {
92
0
      ret = 0;
93
0
    } 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
0
  }
111
112
0
  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
0
  stream->pos += ret;
128
0
  i_assert(ret != 0 || !fstream->file);
129
0
  i_assert(ret != -1);
130
0
  return ret;
131
0
}
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
0
{
195
0
  struct istream *input;
196
0
  struct stat st;
197
0
  bool is_file;
198
0
  int flags;
199
200
0
  fstream->autoclose_fd = autoclose_fd;
201
202
0
  fstream->istream.iostream.close = i_stream_file_close;
203
0
  fstream->istream.max_buffer_size = max_buffer_size;
204
0
  fstream->istream.read = i_stream_file_read;
205
0
  fstream->istream.seek = i_stream_file_seek;
206
0
  fstream->istream.sync = i_stream_file_sync;
207
0
  fstream->istream.stat = i_stream_file_stat;
208
209
  /* if it's a file, set the flags properly */
210
0
  if (fd == -1) {
211
    /* only the path is known for now - the fd is opened later */
212
0
    is_file = TRUE;
213
0
  } else if (fstat(fd, &st) < 0)
214
0
    is_file = FALSE;
215
0
  else if (S_ISREG(st.st_mode))
216
0
    is_file = TRUE;
217
0
  else if (!S_ISDIR(st.st_mode))
218
0
    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
0
  if (is_file) {
229
0
    fstream->file = TRUE;
230
0
    fstream->istream.istream.blocking = TRUE;
231
0
    fstream->istream.istream.seekable = TRUE;
232
0
  } 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
0
  } else if ((flags & O_NONBLOCK) == 0) {
239
    /* blocking socket/fifo */
240
0
    fstream->istream.istream.blocking = TRUE;
241
0
  }
242
0
  fstream->istream.istream.readable_fd = TRUE;
243
244
0
  input = i_stream_create(&fstream->istream, NULL, fd,
245
0
        ISTREAM_HIDDEN_INPUTS_NONE, 0);
246
0
  i_stream_set_name(input, is_file ? "(file)" : "(fd)");
247
0
  return input;
248
0
}
249
250
struct istream *i_stream_create_fd(int fd, size_t max_buffer_size)
251
0
{
252
0
  struct file_istream *fstream;
253
254
0
  i_assert(fd != -1);
255
256
0
  fstream = i_new(struct file_istream, 1);
257
0
  return i_stream_create_file_common(fstream, fd, NULL,
258
0
             max_buffer_size, FALSE);
259
0
}
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
}