]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Add functions to reliably autoclose fd for i/ostream-file
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 27 Jun 2025 21:21:17 +0000 (00:21 +0300)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Sat, 28 Jun 2025 16:19:49 +0000 (16:19 +0000)
These can be used to create iostreams where fd is autoclosed after
both istream and ostream are closed in either order.

src/lib/iostream.c
src/lib/iostream.h
src/lib/istream-file-private.h
src/lib/istream-file.c
src/lib/istream.h
src/lib/ostream-file-private.h
src/lib/ostream-file.c
src/lib/ostream.h

index f2ba00088d2465c0b235a81c786e375115c1b57f..2b45c8b90263ec5bca63095f40a9e82ad01513ca 100644 (file)
@@ -6,6 +6,45 @@
 #include "ostream.h"
 #include "iostream-private.h"
 
+struct iostream_fd *iostream_fd_init(int fd)
+{
+       struct iostream_fd *ref = i_new(struct iostream_fd, 1);
+       ref->refcount = 1;
+       ref->fd = fd;
+       return ref;
+}
+
+void iostream_fd_ref(struct iostream_fd *ref)
+{
+       i_assert(ref->refcount > 0);
+       ref->refcount++;
+}
+
+bool iostream_fd_unref(struct iostream_fd **_ref)
+{
+       struct iostream_fd *ref = *_ref;
+
+       i_assert(ref != NULL);
+       i_assert(ref->refcount > 0);
+
+       if (--ref->refcount > 0)
+               return TRUE;
+       i_free(ref);
+       return FALSE;
+}
+
+void io_stream_create_fd_autoclose(int *fd, size_t max_in_buffer_size,
+                                  size_t max_out_buffer_size,
+                                  struct istream **input_r,
+                                  struct ostream **output_r)
+{
+       struct iostream_fd *fd_ref = iostream_fd_init(*fd);
+       *input_r = i_stream_create_fd_ref_autoclose(fd_ref, max_in_buffer_size);
+       *output_r = o_stream_create_fd_ref_autoclose(fd_ref, max_out_buffer_size);
+       iostream_fd_unref(&fd_ref);
+       *fd = -1;
+}
+
 static void
 io_stream_default_close(struct iostream_private *stream ATTR_UNUSED,
                        bool close_parent ATTR_UNUSED)
index 87bc37486b1a24d460394ea827c9c800b0963f59..72b5da86a98d82fe3511930ad40354864d92fa79 100644 (file)
@@ -1,6 +1,24 @@
 #ifndef IOSTREAM_H
 #define IOSTREAM_H
 
+struct iostream_fd {
+       int refcount;
+       int fd;
+};
+
+/* Used to allow autoclosing fds with istream-file and ostream-file without
+   requiring them to be closed in any specific order. */
+struct iostream_fd *iostream_fd_init(int fd);
+void iostream_fd_ref(struct iostream_fd *ref);
+bool iostream_fd_unref(struct iostream_fd **ref);
+
+/* Create i/ostreams for the given fd. The fd is set to -1 immediately to avoid
+   accidentally closing it twice. */
+void io_stream_create_fd_autoclose(int *fd, size_t max_in_buffer_size,
+                                  size_t max_out_buffer_size,
+                                  struct istream **input_r,
+                                  struct ostream **output_r);
+
 /* Returns human-readable reason for why iostream was disconnected.
    The output is either "Connection closed" for clean disconnections or
    "Connection closed: <error>" for unclean disconnections. */
index c4701ed2ff902d5887836175c678b23f1b42ae59..840de65fd26d2e5ed933a972767c1a931096c5ac 100644 (file)
@@ -6,6 +6,7 @@
 struct file_istream {
        struct istream_private istream;
 
+       struct iostream_fd *fd_ref;
        uoff_t skip_left;
 
        bool file:1;
index 8c945bd9109f5ce7f01f33aa2b3896ef3cd95595..4683ef04b1aa5e319456d8c61768b70277046544 100644 (file)
@@ -20,7 +20,9 @@ void i_stream_file_close(struct iostream_private *stream,
        struct file_istream *fstream =
                container_of(_stream, struct file_istream, istream);
 
-       if (fstream->autoclose_fd && _stream->fd != -1) {
+       bool refs_left = fstream->fd_ref != NULL &&
+               iostream_fd_unref(&fstream->fd_ref);
+       if (fstream->autoclose_fd && _stream->fd != -1 && !refs_left) {
                /* 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. */
@@ -269,6 +271,18 @@ struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
        return input;
 }
 
+struct istream *i_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
+                                                size_t max_buffer_size)
+{
+       struct file_istream *fstream;
+
+       fstream = i_new(struct file_istream, 1);
+       fstream->fd_ref = ref;
+       iostream_fd_ref(ref);
+       return i_stream_create_file_common(fstream, ref->fd, NULL,
+                                          max_buffer_size, TRUE);
+}
+
 struct istream *i_stream_create_file(const char *path, size_t max_buffer_size)
 {
        struct file_istream *fstream;
index acab9feed5989c144e22a9cb363960b13fa7a9ef..f0648f36a80b0f9f53257e2987f0ded265059bde 100644 (file)
@@ -5,6 +5,7 @@
 #include <sys/stat.h>
 
 struct ioloop;
+struct iostream_fd;
 
 struct istream {
        uoff_t v_offset;
@@ -41,6 +42,10 @@ typedef void istream_callback_t(void *context);
 struct istream *i_stream_create_fd(int fd, size_t max_buffer_size);
 /* The fd is set to -1 immediately to avoid accidentally closing it twice. */
 struct istream *i_stream_create_fd_autoclose(int *fd, size_t max_buffer_size);
+/* Autoclose the fd once ref's refcount drops to 0. This function increases the
+   refcount, so the caller is expected to unref it as well. */
+struct istream *i_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
+                                                size_t max_buffer_size);
 /* Open the given path only when something is actually tried to be read from
    the stream. */
 struct istream *i_stream_create_file(const char *path, size_t max_buffer_size);
index dc3d4dbb7539600535527e0c7b0ebefc31214c24..122a974e40468bf2b286ea3525cf96a47e45792d 100644 (file)
@@ -11,6 +11,7 @@ struct file_ostream {
                 unsigned int iov_count, const char **error_r);
 
        int fd;
+       struct iostream_fd *fd_ref;
        struct io *io;
        uoff_t buffer_offset;
        uoff_t real_offset;
index b350e6df60e329d59f582590ab5ea3b148826bf4..f6b5a1eefa1fabec33bde809fd5d848e82165650 100644 (file)
        ((size) < SSIZE_T_MAX ? (size_t)(size) : SSIZE_T_MAX)
 
 static void stream_send_io(struct file_ostream *fstream);
-static struct ostream * o_stream_create_fd_common(int fd,
-               size_t max_buffer_size, bool autoclose_fd);
 
 static void stream_closed(struct file_ostream *fstream)
 {
        io_remove(&fstream->io);
 
-       if (fstream->autoclose_fd && fstream->fd != -1) {
+       bool refs_left = fstream->fd_ref != NULL &&
+               iostream_fd_unref(&fstream->fd_ref);
+       if (fstream->autoclose_fd && fstream->fd != -1 && !refs_left) {
                /* 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. */
@@ -1080,15 +1080,19 @@ static void fstream_init_file(struct file_ostream *fstream)
        }
 }
 
-static
-struct ostream * o_stream_create_fd_common(int fd, size_t max_buffer_size,
-               bool autoclose_fd)
+static struct ostream *
+o_stream_create_fd_common(int fd, struct iostream_fd *ref,
+                         size_t max_buffer_size, bool autoclose_fd)
 {
        struct file_ostream *fstream;
        struct ostream *ostream;
        off_t offset;
 
        fstream = i_new(struct file_ostream, 1);
+       if (ref != NULL) {
+               fstream->fd_ref = ref;
+               iostream_fd_ref(ref);
+       }
        ostream = o_stream_create_file_common
                (fstream, fd, max_buffer_size, autoclose_fd);
 
@@ -1121,18 +1125,24 @@ struct ostream * o_stream_create_fd_common(int fd, size_t max_buffer_size,
 struct ostream *
 o_stream_create_fd(int fd, size_t max_buffer_size)
 {
-       return o_stream_create_fd_common(fd, max_buffer_size, FALSE);
+       return o_stream_create_fd_common(fd, NULL, max_buffer_size, FALSE);
 }
 
 struct ostream *
 o_stream_create_fd_autoclose(int *fd, size_t max_buffer_size)
 {
-       struct ostream *ostream = o_stream_create_fd_common(*fd,
+       struct ostream *ostream = o_stream_create_fd_common(*fd, NULL,
                        max_buffer_size, TRUE);
        *fd = -1;
        return ostream;
 }
 
+struct ostream *o_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
+                                                size_t max_buffer_size)
+{
+       return o_stream_create_fd_common(ref->fd, ref, max_buffer_size, TRUE);
+}
+
 struct ostream *
 o_stream_create_fd_file(int fd, uoff_t offset, bool autoclose_fd)
 {
index abce2c186eb8514f1a2b06a5fb42761a47e1f6c9..f36e98b446edadb22297adae5f09f9eba13098a7 100644 (file)
@@ -3,6 +3,8 @@
 
 #include "ioloop.h"
 
+struct iostream_fd;
+
 enum ostream_send_istream_result {
        /* All of the istream was successfully sent to ostream. */
        OSTREAM_SEND_ISTREAM_RESULT_FINISHED,
@@ -59,6 +61,10 @@ typedef void ostream_callback_t(void *context);
 struct ostream *o_stream_create_fd(int fd, size_t max_buffer_size);
 /* The fd is set to -1 immediately to avoid accidentally closing it twice. */
 struct ostream *o_stream_create_fd_autoclose(int *fd, size_t max_buffer_size);
+/* Autoclose the fd once ref's refcount drops to 0. This function increases the
+   refcount, so the caller is expected to unref it as well. */
+struct ostream *o_stream_create_fd_ref_autoclose(struct iostream_fd *ref,
+                                                size_t max_buffer_size);
 /* Create an output stream from a regular file which begins at given offset.
    If offset==UOFF_T_MAX, the current offset isn't known. */
 struct ostream *