istream-file.c revision 03915cfec4eb1b5a65e5b6b676c8f4151bc80351
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen/* @UNSAFE: whole file */
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "lib.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "ioloop.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "istream-file-private.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "net.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
6d2b3ce2c6ef62334985ece4f0ab8b154e0e9560Timo Sirainen#include <time.h>
c6335901c67a4c9365319190a111a2168f3b06f5Timo Sirainen#include <unistd.h>
c6335901c67a4c9365319190a111a2168f3b06f5Timo Sirainen#include <fcntl.h>
c6335901c67a4c9365319190a111a2168f3b06f5Timo Sirainen#include <sys/stat.h>
01230de017cd273de41143d88e9c18df1243ae8aTimo Sirainen
01230de017cd273de41143d88e9c18df1243ae8aTimo Sirainenvoid i_stream_file_close(struct iostream_private *stream,
047c00cd3f7f403672f81569413669238df8c15aTimo Sirainen bool close_parent ATTR_UNUSED)
047c00cd3f7f403672f81569413669238df8c15aTimo Sirainen{
047c00cd3f7f403672f81569413669238df8c15aTimo Sirainen struct file_istream *fstream = (struct file_istream *)stream;
047c00cd3f7f403672f81569413669238df8c15aTimo Sirainen struct istream_private *_stream = (struct istream_private *)stream;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (fstream->autoclose_fd && _stream->fd != -1) {
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (close(_stream->fd) < 0) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_error("file_istream.close(%s) failed: %m",
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_stream_get_name(&_stream->istream));
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen }
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen }
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen _stream->fd = -1;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen}
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainenstatic int i_stream_file_open(struct istream_private *stream)
7487ff578435377bbeefffdbfb78ca09ed1292dfTimo Sirainen{
89237470342ea6d4bbdf4cff9764037cfb3f6f45Timo Sirainen const char *path = i_stream_get_name(&stream->istream);
89237470342ea6d4bbdf4cff9764037cfb3f6f45Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen stream->fd = open(path, O_RDONLY);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (stream->fd == -1) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen io_stream_set_error(&stream->iostream,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen "open(%s) failed: %m", path);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen stream->istream.stream_errno = errno;
4d4d6d4745682790c20d759ba93dbea46b812c5dTimo Sirainen return -1;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen return 0;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen}
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainenssize_t i_stream_file_read(struct istream_private *stream)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen{
a3fe8c0c54d87822f4b4f8f0d10caac611861b2bTimo Sirainen struct file_istream *fstream = (struct file_istream *) stream;
578ef2538ccf42e2a48234c24a8b709397101d88Timo Sirainen uoff_t offset;
578ef2538ccf42e2a48234c24a8b709397101d88Timo Sirainen size_t size;
578ef2538ccf42e2a48234c24a8b709397101d88Timo Sirainen ssize_t ret;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen if (!i_stream_try_alloc(stream, 1, &size))
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen return -2;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (stream->fd == -1) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (i_stream_file_open(stream) < 0)
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen return -1;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_assert(stream->fd != -1);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen offset = stream->istream.v_offset + (stream->pos - stream->skip);
e0740628f6ca05f4bc79a9d8a90b650f4d38d4d0Timo Sirainen
e0740628f6ca05f4bc79a9d8a90b650f4d38d4d0Timo Sirainen if (fstream->file) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ret = pread(stream->fd, stream->w_buffer + stream->pos,
3f603ef00e35fca21605afa0ad8d76e94fee2b96Timo Sirainen size, offset);
3f603ef00e35fca21605afa0ad8d76e94fee2b96Timo Sirainen } else if (fstream->seen_eof) {
3f603ef00e35fca21605afa0ad8d76e94fee2b96Timo Sirainen /* don't try to read() again. EOF from keyboard (^D)
3f603ef00e35fca21605afa0ad8d76e94fee2b96Timo Sirainen requires this to work right. */
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen ret = 0;
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen } else {
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen ret = read(stream->fd, stream->w_buffer + stream->pos,
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen size);
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen }
6fdfa4d4cf14d1d7764d7faa8258f112e39c8dbeTimo Sirainen
6fdfa4d4cf14d1d7764d7faa8258f112e39c8dbeTimo Sirainen if (ret == 0) {
6fdfa4d4cf14d1d7764d7faa8258f112e39c8dbeTimo Sirainen /* EOF */
6fdfa4d4cf14d1d7764d7faa8258f112e39c8dbeTimo Sirainen stream->istream.eof = TRUE;
6fdfa4d4cf14d1d7764d7faa8258f112e39c8dbeTimo Sirainen fstream->seen_eof = TRUE;
3f603ef00e35fca21605afa0ad8d76e94fee2b96Timo Sirainen return -1;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen }
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (unlikely(ret < 0)) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if ((errno == EINTR || errno == EAGAIN) &&
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen !stream->istream.blocking) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ret = 0;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen } else {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen i_assert(errno != 0);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen /* if we get EBADF for a valid fd, it means something's
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen really wrong and we'd better just crash. */
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen i_assert(errno != EBADF);
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (fstream->file) {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen io_stream_set_error(&stream->iostream,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen "pread(size=%"PRIuSIZE_T
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen " offset=%"PRIuUOFF_T") failed: %m",
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen size, offset);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen } else {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen io_stream_set_error(&stream->iostream,
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen "read(size=%"PRIuSIZE_T") failed: %m",
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen size);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
b2d562f9c7fd13f9a16e9b3bcee904630b80b1feTimo Sirainen stream->istream.stream_errno = errno;
b2d562f9c7fd13f9a16e9b3bcee904630b80b1feTimo Sirainen return -1;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen if (ret > 0 && fstream->skip_left > 0) {
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_assert(!fstream->file);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen i_assert(stream->skip == stream->pos);
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen if (fstream->skip_left >= (size_t)ret) {
6c2ce1d5bf17b21e804a079eb0f973b7ab83e0d8Timo Sirainen fstream->skip_left -= ret;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ret = 0;
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen } else {
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen ret -= fstream->skip_left;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen stream->pos += fstream->skip_left;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen stream->skip += fstream->skip_left;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen fstream->skip_left = 0;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen }
}
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 = (struct file_istream *) stream;
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 = (struct file_istream *) stream;
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;
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)
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;
}
fstream->istream.istream.readable_fd = TRUE;
input = i_stream_create(&fstream->istream, NULL, fd);
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;
}