/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 | } |