Coverage Report

Created: 2025-10-12 07:19

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) 2002-2018 Dovecot authors, see the included 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.4k
{
18
18.4k
  struct istream_private *_stream =
19
18.4k
    container_of(stream, struct istream_private, iostream);
20
18.4k
  struct file_istream *fstream =
21
18.4k
    container_of(_stream, struct file_istream, istream);
22
23
18.4k
  bool refs_left = fstream->fd_ref != NULL &&
24
0
    iostream_fd_unref(&fstream->fd_ref);
25
18.4k
  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.4k
  _stream->fd = -1;
35
18.4k
}
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
23.6k
{
53
23.6k
  struct file_istream *fstream =
54
23.6k
    container_of(stream, struct file_istream, istream);
55
23.6k
  uoff_t offset;
56
23.6k
  size_t size;
57
23.6k
  ssize_t ret;
58
59
23.6k
  if (!i_stream_try_alloc(stream, 1, &size))
60
0
    return -2;
61
62
23.6k
  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
23.6k
  offset = stream->istream.v_offset + (stream->pos - stream->skip);
69
70
23.6k
  if (fstream->file) {
71
0
    ret = pread(stream->fd, stream->w_buffer + stream->pos,
72
0
          size, offset);
73
23.6k
  } 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
23.6k
  } else {
78
23.6k
    ret = read(stream->fd, stream->w_buffer + stream->pos,
79
23.6k
         size);
80
23.6k
  }
81
82
23.6k
  if (ret == 0) {
83
    /* EOF */
84
5.82k
    stream->istream.eof = TRUE;
85
5.82k
    fstream->seen_eof = TRUE;
86
5.82k
    return -1;
87
5.82k
  }
88
89
17.8k
  if (unlikely(ret < 0)) {
90
430
    if ((errno == EINTR || errno == EAGAIN) &&
91
430
        !stream->istream.blocking) {
92
430
      ret = 0;
93
430
    } 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
430
  }
111
112
17.8k
  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
17.8k
  stream->pos += ret;
128
17.8k
  i_assert(ret != 0 || !fstream->file);
129
17.8k
  i_assert(ret != -1);
130
17.8k
  return ret;
131
17.8k
}
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, 0);
245
6.16k
  i_stream_set_name(input, is_file ? "(file)" : "(fd)");
246
6.16k
  return input;
247
6.16k
}
248
249
struct istream *i_stream_create_fd(int fd, size_t max_buffer_size)
250
6.16k
{
251
6.16k
  struct file_istream *fstream;
252
253
6.16k
  i_assert(fd != -1);
254
255
6.16k
  fstream = i_new(struct file_istream, 1);
256
6.16k
  return i_stream_create_file_common(fstream, fd, NULL,
257
6.16k
             max_buffer_size, FALSE);
258
6.16k
}
259
260
struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
261
0
{
262
0
  struct istream *input;
263
0
  struct file_istream *fstream;
264
265
0
  i_assert(*fd != -1);
266
267
0
  fstream = i_new(struct file_istream, 1);
268
0
  input = i_stream_create_file_common(fstream, *fd, NULL,
269
0
             max_buffer_size, TRUE);
270
0
  *fd = -1;
271
0
  return input;
272
0
}
273
274
struct istream *i_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
275
             size_t max_buffer_size)
276
0
{
277
0
  struct file_istream *fstream;
278
279
0
  fstream = i_new(struct file_istream, 1);
280
0
  fstream->fd_ref = ref;
281
0
  iostream_fd_ref(ref);
282
0
  return i_stream_create_file_common(fstream, ref->fd, NULL,
283
0
             max_buffer_size, TRUE);
284
0
}
285
286
struct istream *i_stream_create_file(const char *path, size_t max_buffer_size)
287
0
{
288
0
  struct file_istream *fstream;
289
0
  struct istream *input;
290
291
0
  fstream = i_new(struct file_istream, 1);
292
0
  input = i_stream_create_file_common(fstream, -1, path,
293
0
              max_buffer_size, TRUE);
294
0
  i_stream_set_name(input, path);
295
0
  return input;
296
0
}