summaryrefslogtreecommitdiffstats
path: root/src/lib/istream-file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/istream-file.c')
-rw-r--r--src/lib/istream-file.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/lib/istream-file.c b/src/lib/istream-file.c
new file mode 100644
index 0000000..8c945bd
--- /dev/null
+++ b/src/lib/istream-file.c
@@ -0,0 +1,282 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+/* @UNSAFE: whole file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream-file-private.h"
+#include "net.h"
+
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+void i_stream_file_close(struct iostream_private *stream,
+ bool close_parent ATTR_UNUSED)
+{
+ struct istream_private *_stream =
+ container_of(stream, struct istream_private, iostream);
+ struct file_istream *fstream =
+ container_of(_stream, struct file_istream, istream);
+
+ if (fstream->autoclose_fd && _stream->fd != -1) {
+ /* Ignore ECONNRESET because we don't really care about it here,
+ as we are closing the socket down in any case. There might be
+ unsent data but nothing we can do about that. */
+ if (unlikely(close(_stream->fd) < 0 && errno != ECONNRESET)) {
+ i_error("file_istream.close(%s) failed: %m",
+ i_stream_get_name(&_stream->istream));
+ }
+ }
+ _stream->fd = -1;
+}
+
+static int i_stream_file_open(struct istream_private *stream)
+{
+ const char *path = i_stream_get_name(&stream->istream);
+
+ stream->fd = open(path, O_RDONLY);
+ if (stream->fd == -1) {
+ io_stream_set_error(&stream->iostream,
+ "open(%s) failed: %m", path);
+ stream->istream.stream_errno = errno;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t i_stream_file_read(struct istream_private *stream)
+{
+ struct file_istream *fstream =
+ container_of(stream, struct file_istream, istream);
+ uoff_t offset;
+ size_t size;
+ ssize_t ret;
+
+ if (!i_stream_try_alloc(stream, 1, &size))
+ return -2;
+
+ if (stream->fd == -1) {
+ if (i_stream_file_open(stream) < 0)
+ return -1;
+ i_assert(stream->fd != -1);
+ }
+
+ offset = stream->istream.v_offset + (stream->pos - stream->skip);
+
+ if (fstream->file) {
+ ret = pread(stream->fd, stream->w_buffer + stream->pos,
+ size, offset);
+ } else if (fstream->seen_eof) {
+ /* don't try to read() again. EOF from keyboard (^D)
+ requires this to work right. */
+ ret = 0;
+ } else {
+ ret = read(stream->fd, stream->w_buffer + stream->pos,
+ size);
+ }
+
+ if (ret == 0) {
+ /* EOF */
+ stream->istream.eof = TRUE;
+ fstream->seen_eof = TRUE;
+ return -1;
+ }
+
+ if (unlikely(ret < 0)) {
+ if ((errno == EINTR || errno == EAGAIN) &&
+ !stream->istream.blocking) {
+ ret = 0;
+ } else {
+ i_assert(errno != 0);
+ /* if we get EBADF for a valid fd, it means something's
+ really wrong and we'd better just crash. */
+ i_assert(errno != EBADF);
+ if (fstream->file) {
+ io_stream_set_error(&stream->iostream,
+ "pread(size=%zu offset=%"PRIuUOFF_T") failed: %m",
+ size, offset);
+ } else {
+ io_stream_set_error(&stream->iostream,
+ "read(size=%zu) failed: %m",
+ size);
+ }
+ stream->istream.stream_errno = errno;
+ return -1;
+ }
+ }
+
+ if (ret > 0 && fstream->skip_left > 0) {
+ i_assert(!fstream->file);
+ i_assert(stream->skip == stream->pos);
+
+ if (fstream->skip_left >= (size_t)ret) {
+ fstream->skip_left -= ret;
+ ret = 0;
+ } else {
+ ret -= fstream->skip_left;
+ stream->pos += fstream->skip_left;
+ stream->skip += fstream->skip_left;
+ fstream->skip_left = 0;
+ }
+ }
+
+ stream->pos += ret;
+ i_assert(ret != 0 || !fstream->file);
+ i_assert(ret != -1);
+ return ret;
+}
+
+static void i_stream_file_seek(struct istream_private *stream, uoff_t v_offset,
+ bool mark ATTR_UNUSED)
+{
+ struct file_istream *fstream =
+ container_of(stream, struct file_istream, istream);
+
+ if (!stream->istream.seekable) {
+ if (v_offset < stream->istream.v_offset)
+ i_panic("stream doesn't support seeking backwards");
+ fstream->skip_left += v_offset - stream->istream.v_offset;
+ }
+
+ stream->istream.v_offset = v_offset;
+ stream->skip = stream->pos = 0;
+ fstream->seen_eof = FALSE;
+}
+
+static void i_stream_file_sync(struct istream_private *stream)
+{
+ if (!stream->istream.seekable) {
+ /* can't do anything or data would be lost */
+ return;
+ }
+
+ stream->skip = stream->pos = 0;
+ stream->istream.eof = FALSE;
+}
+
+static int
+i_stream_file_stat(struct istream_private *stream, bool exact ATTR_UNUSED)
+{
+ struct file_istream *fstream =
+ container_of(stream, struct file_istream, istream);
+ const char *name = i_stream_get_name(&stream->istream);
+
+ if (!fstream->file) {
+ /* return defaults */
+ } else if (stream->fd != -1) {
+ if (fstat(stream->fd, &stream->statbuf) < 0) {
+ stream->istream.stream_errno = errno;
+ io_stream_set_error(&stream->iostream,
+ "file_istream.fstat(%s) failed: %m", name);
+ i_error("%s", i_stream_get_error(&stream->istream));
+ return -1;
+ }
+ } else {
+ if (stat(name, &stream->statbuf) < 0) {
+ stream->istream.stream_errno = errno;
+ io_stream_set_error(&stream->iostream,
+ "file_istream.stat(%s) failed: %m", name);
+ i_error("%s", i_stream_get_error(&stream->istream));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+struct istream *
+i_stream_create_file_common(struct file_istream *fstream,
+ int fd, const char *path,
+ size_t max_buffer_size, bool autoclose_fd)
+{
+ struct istream *input;
+ struct stat st;
+ bool is_file;
+ int flags;
+
+ fstream->autoclose_fd = autoclose_fd;
+
+ fstream->istream.iostream.close = i_stream_file_close;
+ fstream->istream.max_buffer_size = max_buffer_size;
+ fstream->istream.read = i_stream_file_read;
+ fstream->istream.seek = i_stream_file_seek;
+ fstream->istream.sync = i_stream_file_sync;
+ fstream->istream.stat = i_stream_file_stat;
+
+ /* if it's a file, set the flags properly */
+ if (fd == -1) {
+ /* only the path is known for now - the fd is opened later */
+ is_file = TRUE;
+ } else if (fstat(fd, &st) < 0)
+ is_file = FALSE;
+ else if (S_ISREG(st.st_mode))
+ is_file = TRUE;
+ else if (!S_ISDIR(st.st_mode))
+ is_file = FALSE;
+ else {
+ /* we're trying to open a directory.
+ we're not designed for it. */
+ io_stream_set_error(&fstream->istream.iostream,
+ "%s is a directory, can't read it as file",
+ path != NULL ? path : t_strdup_printf("<fd %d>", fd));
+ fstream->istream.istream.stream_errno = EISDIR;
+ is_file = FALSE;
+ }
+ if (is_file) {
+ fstream->file = TRUE;
+ fstream->istream.istream.blocking = TRUE;
+ fstream->istream.istream.seekable = TRUE;
+ } else if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
+ i_assert(fd > -1);
+ /* shouldn't happen */
+ fstream->istream.istream.stream_errno = errno;
+ io_stream_set_error(&fstream->istream.iostream,
+ "fcntl(%d, F_GETFL) failed: %m", fd);
+ } else if ((flags & O_NONBLOCK) == 0) {
+ /* blocking socket/fifo */
+ fstream->istream.istream.blocking = TRUE;
+ }
+ fstream->istream.istream.readable_fd = TRUE;
+
+ input = i_stream_create(&fstream->istream, NULL, fd, 0);
+ i_stream_set_name(input, is_file ? "(file)" : "(fd)");
+ return input;
+}
+
+struct istream *i_stream_create_fd(int fd, size_t max_buffer_size)
+{
+ struct file_istream *fstream;
+
+ i_assert(fd != -1);
+
+ fstream = i_new(struct file_istream, 1);
+ return i_stream_create_file_common(fstream, fd, NULL,
+ max_buffer_size, FALSE);
+}
+
+struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
+{
+ struct istream *input;
+ struct file_istream *fstream;
+
+ i_assert(*fd != -1);
+
+ fstream = i_new(struct file_istream, 1);
+ input = i_stream_create_file_common(fstream, *fd, NULL,
+ max_buffer_size, TRUE);
+ *fd = -1;
+ return input;
+}
+
+struct istream *i_stream_create_file(const char *path, size_t max_buffer_size)
+{
+ struct file_istream *fstream;
+ struct istream *input;
+
+ fstream = i_new(struct file_istream, 1);
+ input = i_stream_create_file_common(fstream, -1, path,
+ max_buffer_size, TRUE);
+ i_stream_set_name(input, path);
+ return input;
+}