Coverage Report

Created: 2025-07-18 06:24

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