X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/3369a853fbc0fe454ac65fef7adf7e51845ff6a2..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/transports/queuefile.c diff --git a/src/src/transports/queuefile.c b/src/src/transports/queuefile.c index 3f711c145..3a2bae22f 100644 --- a/src/src/transports/queuefile.c +++ b/src/src/transports/queuefile.c @@ -4,12 +4,21 @@ /* Copyright (c) Andrew Colin Kissa 2016 */ /* Copyright (c) University of Cambridge 2016 */ +/* Copyright (c) The Exim Maintainers 1995 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-only */ + #include "../exim.h" + +#ifdef EXPERIMENTAL_QUEUEFILE /* whole file */ #include "queuefile.h" +#ifndef EXIM_HAVE_OPENAT +# error queuefile transport reqires openat() support +#endif + /* Options specific to the appendfile transport. They must be in alphabetic order (note that "_" comes before the lower case letters). Some of them are stored in the publicly visible instance block - these are flagged with the @@ -17,15 +26,28 @@ opt_public flag. */ optionlist queuefile_transport_options[] = { { "directory", opt_stringptr, - (void *)offsetof(queuefile_transport_options_block, dirname) }, + OPT_OFF(queuefile_transport_options_block, dirname) }, }; + /* Size of the options list. An extern variable has to be used so that its address can appear in the tables drtables.c. */ int queuefile_transport_options_count = sizeof(queuefile_transport_options) / sizeof(optionlist); + +#ifdef MACRO_PREDEF + +/* Dummy values */ +queuefile_transport_options_block queuefile_transport_option_defaults = {0}; +void queuefile_transport_init(transport_instance *tblock) {} +BOOL queuefile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;} + +#else /*!MACRO_PREDEF*/ + + + /* Default private options block for the appendfile transport. */ queuefile_transport_options_block queuefile_transport_option_defaults = { @@ -39,9 +61,9 @@ queuefile_transport_options_block queuefile_transport_option_defaults = { void queuefile_transport_init(transport_instance *tblock) { queuefile_transport_options_block *ob = - (queuefile_transport_options_block *)(tblock->options_block); + (queuefile_transport_options_block *) tblock->options_block; -if (ob->dirname == NULL) +if (!ob->dirname) log_write(0, LOG_PANIC_DIE | LOG_CONFIG, "directory must be set for the %s transport", tblock->name); } @@ -49,29 +71,28 @@ if (ob->dirname == NULL) /* This function will copy from a file to another Arguments: - to_fd FILE to write to (the destination queue file) - from_fd FILE to read from (the spool queue file) + dst fd to write to (the destination queue file) + src fd to read from (the spool queue file) -Returns: TRUE if all went well, FALSE otherwise +Returns: TRUE if all went well, FALSE otherwise with errno set */ -static BOOL copy_spool_file (FILE *to_fd, FILE *from_fd) +static BOOL +copy_spool_file(int dst, int src) { int i, j; uschar buffer[16384]; -rewind(from_fd); +if (lseek(src, 0, SEEK_SET) != 0) + return FALSE; do - { - j = fread(buffer, 1, sizeof(buffer), from_fd); - if (j > 0) - { - i = fwrite(buffer, j, 1, to_fd); - if (i != 1) - return FALSE; - } - } + if ((j = read(src, buffer, sizeof(buffer))) > 0) + for (uschar * s = buffer; (i = write(dst, s, j)) != j; s += i, j -= i) + if (i < 0) + return FALSE; + else if (j < 0) + return FALSE; while (j > 0); return TRUE; } @@ -80,120 +101,70 @@ return TRUE; and data files to the destination directory Arguments: - tname uschar the transport name + tb the transport block addr address_item being processed + dstpath destination directory name sdfd int Source directory fd ddfd int Destination directory fd - suffix uschar file suffix - dirname uschar Destination directory link_file BOOL use linkat instead of data copy - is_data_file BOOL the file is a data file not a header file - dst_file FILE to write to - src_file FILE to read from + srcfd fd for data file, or -1 for header file Returns: TRUE if all went well, FALSE otherwise */ -static BOOL copy_spool_files(uschar *tname, address_item *addr, - int sdfd, int ddfd, uschar *suffix, uschar *dirname, BOOL link_file, - BOOL is_data_file, FILE *dst_file, FILE *src_file) +static BOOL +copy_spool_files(transport_instance * tb, address_item * addr, + const uschar * dstpath, int sdfd, int ddfd, BOOL link_file, int srcfd) { -int dstfd, srcfd; -/* -uschar message_subdir[2]; -message_subdir[1] = '\0'; -message_subdir[0] = split_spool_directory? message_id[5] : 0; -*/ -uschar *filename = string_sprintf("%s-%s", message_id, suffix); -/* -uschar *srcpath = string_sprintf("%s/%s/%s/%s-%s", spool_directory, - US"input", message_subdir, message_id, suffix); -*/ -uschar *srcpath = spool_fname(US"input", message_subdir, message_id, suffix); -uschar *dstpath = string_sprintf("%s/%s-%s", dirname, message_id, suffix); +BOOL is_hdr_file = srcfd < 0; +const uschar * suffix = srcfd < 0 ? US"H" : US"D"; +int dstfd; +const uschar * filename = string_sprintf("%s-%s", message_id, suffix); +const uschar * srcpath = spool_fname(US"input", message_subdir, message_id, suffix); +const uschar * s, * op; + +dstpath = string_sprintf("%s/%s-%s", dstpath, message_id, suffix); if (link_file) { - /* use linkat */ - DEBUG(D_transport) - debug_printf("%s transport, linking %s => %s\n", tname, - srcpath, dstpath); - if (linkat(sdfd, CCS filename, ddfd, CCS filename, 0) < 0) - return FALSE; - return TRUE; - } -else - { - /* use data copy */ - DEBUG(D_transport) - debug_printf("%s transport, copying %s => %s\n", tname, - srcpath, dstpath); - if ((dstfd = - openat(ddfd, CCS filename, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) < 0) - { - addr->transport_return = DEFER; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport opening file: %s " - "failed with error: %s", tname, dstpath, strerror(errno)); - return FALSE; - } + DEBUG(D_transport) debug_printf("%s transport, linking %s => %s\n", + tb->name, srcpath, dstpath); - fchmod(dstfd, SPOOL_MODE); + if (linkat(sdfd, CCS filename, ddfd, CCS filename, 0) >= 0) + return TRUE; - if ((dst_file = fdopen(dstfd, "wb")) < 0) - { - addr->transport_return = DEFER; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport opening file fd: %s " - "failed with error: %s", tname, dstpath, strerror(errno)); - (void)close(dstfd); - return FALSE; - } + op = US"linking"; + s = dstpath; + } +else /* use data copy */ + { + DEBUG(D_transport) debug_printf("%s transport, copying %s => %s\n", + tb->name, srcpath, dstpath); + + if ( (s = dstpath, + (dstfd = exim_openat4(ddfd, CCS filename, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE)) + < 0 + ) + || is_hdr_file + && (s = srcpath, (srcfd = exim_openat(sdfd, CCS filename, O_RDONLY)) < 0) + ) + op = US"opening"; - if (is_data_file) - srcfd = deliver_datafile; else - { - if ((srcfd = openat(sdfd, CCS filename, O_RDONLY)) < 0) - { - addr->transport_return = DEFER; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport opening file: %s " - "failed with error: %s", tname, srcpath, strerror(errno)); - return FALSE; - } - } - - if ((src_file = fdopen(srcfd, "rb")) < 0) - { - addr->transport_return = DEFER; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport opening file fd: " - "%s failed with error: %s", tname, srcpath, strerror(errno)); - if (!is_data_file) (void)close(srcfd); - return FALSE; - } - - if (!copy_spool_file(dst_file, src_file)) - { - addr->transport_return = DEFER; - addr->message = string_sprintf("%s transport creating file: " - "%s failed with error: %s", tname, dstpath, strerror(errno)); - return FALSE; - } - - if (!is_data_file) - { - (void)fclose(src_file); - src_file = NULL; - } - - (void)fclose(dst_file); - dst_file = NULL; - - } /* end data copy */ + if (s = dstpath, fchmod(dstfd, SPOOL_MODE) != 0) + op = US"setting perms on"; + else + if (!copy_spool_file(dstfd, srcfd)) + op = US"creating"; + else + return TRUE; + } -return TRUE; +addr->basic_errno = errno; +addr->message = string_sprintf("%s transport %s file: %s failed with error: %s", + tb->name, op, s, strerror(errno)); +addr->transport_return = DEFER; +return FALSE; } /************************************************* @@ -203,87 +174,72 @@ return TRUE; /* This transport always returns FALSE, indicating that the status in the first address is the status for all addresses in a batch. */ -BOOL queuefile_transport_entry(transport_instance *tblock, - address_item *addr) +BOOL +queuefile_transport_entry(transport_instance * tblock, address_item * addr) { -BOOL link_file; -BOOL is_data_file; -uschar *sourcedir; -struct stat dstatbuf; -struct stat sstatbuf; -FILE *dst_file = NULL; -FILE *src_file = NULL; -/* uschar message_subdir[2]; */ -int ddfd, sdfd, dfd_oflags; -queuefile_transport_options_block *ob = - (queuefile_transport_options_block *)(tblock->options_block); +queuefile_transport_options_block * ob = + (queuefile_transport_options_block *) tblock->options_block; +BOOL can_link; +uschar * sourcedir = spool_dname(US"input", message_subdir); +uschar * s, * dstdir; +struct stat dstatbuf, sstatbuf; +int ddfd = -1, sdfd = -1; DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name); -# ifndef O_DIRECTORY -# define O_DIRECTORY 0 -# endif - -dfd_oflags = O_RDONLY|O_DIRECTORY; -#ifdef O_NOFOLLOW -dfd_oflags |= O_NOFOLLOW; +#ifndef O_DIRECTORY +# define O_DIRECTORY 0 +#endif +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 #endif -if (ob->dirname[0] != '/') +if (!(dstdir = expand_string(ob->dirname))) { - addr->transport_return = PANIC; - addr->message = string_sprintf("%s transport directory: " - "%s is not absolute", tblock->name, ob->dirname); + addr->message = string_sprintf("%s transport: failed to expand dirname option", + tblock->name); + addr->transport_return = DEFER; return FALSE; } - -if ((ddfd = Uopen(ob->dirname, dfd_oflags, 0)) < 0) +if (*dstdir != '/') { addr->transport_return = PANIC; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport accessing directory: %s " - "failed with error: %s", tblock->name, ob->dirname, strerror(errno)); + addr->message = string_sprintf("%s transport directory: " + "%s is not absolute", tblock->name, dstdir); return FALSE; } +/* Open the source and destination directories and check if they are +on the same filesystem, so we can hard-link files rather than copying. */ -if ((fstat(ddfd, &dstatbuf)) < 0) - { - addr->transport_return = PANIC; - addr->basic_errno = errno; - addr->message = string_sprintf("%s transport fstat on directory fd: " - "%s failed with error: %s", tblock->name, ob->dirname, strerror(errno)); - goto RETURN; - } - -sourcedir = spool_dname(US"input", message_subdir); -/* -message_subdir[1] = '\0'; -message_subdir[0] = split_spool_directory? message_id[5] : 0; -sourcedir = string_sprintf("%s/%s/%s", spool_directory, - US"input", message_subdir); -*/ - -if ((sdfd = Uopen(sourcedir, dfd_oflags, 0)) < 0) +if ( (s = dstdir, + (ddfd = Uopen(s, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0) + || (s = sourcedir, + (sdfd = Uopen(sourcedir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0) + ) { addr->transport_return = PANIC; addr->basic_errno = errno; addr->message = string_sprintf("%s transport accessing directory: %s " - "failed with error: %s", tblock->name, sourcedir, strerror(errno)); - goto RETURN; + "failed with error: %s", tblock->name, s, strerror(errno)); + if (ddfd >= 0) (void) close(ddfd); + return FALSE; } -if ((fstat(sdfd, &sstatbuf)) < 0) +if ( (s = dstdir, fstat(ddfd, &dstatbuf) < 0) + || (s = sourcedir, fstat(sdfd, &sstatbuf) < 0) + ) { addr->transport_return = PANIC; addr->basic_errno = errno; addr->message = string_sprintf("%s transport fstat on directory fd: " - "%s failed with error: %s", tblock->name, sourcedir, strerror(errno)); + "%s failed with error: %s", tblock->name, s, strerror(errno)); goto RETURN; } +can_link = (dstatbuf.st_dev == sstatbuf.st_dev); -if (dont_deliver) +if (f.dont_deliver) { DEBUG(D_transport) debug_printf("*** delivery by %s transport bypassed by -N option\n", @@ -292,48 +248,40 @@ if (dont_deliver) goto RETURN; } -/* process the header file */ +/* Link or copy the header and data spool files */ + DEBUG(D_transport) debug_printf("%s transport, copying header file\n", tblock->name); -is_data_file = FALSE; -link_file = (dstatbuf.st_dev == sstatbuf.st_dev); - -if ((copy_spool_files(tblock->name, addr, sdfd, ddfd, US"H", ob->dirname, - link_file, is_data_file, dst_file, src_file)) == FALSE) +if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link, -1)) goto RETURN; -/* process the data file */ DEBUG(D_transport) debug_printf("%s transport, copying data file\n", tblock->name); -is_data_file = TRUE; - -if ((copy_spool_files(tblock->name, addr, sdfd, ddfd, US"D", ob->dirname, - link_file, is_data_file, dst_file, src_file)) == FALSE) +if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link, + deliver_datafile)) { DEBUG(D_transport) debug_printf("%s transport, copying data file failed, " "unlinking the header file\n", tblock->name); - Uunlink(string_sprintf("%s/%s-H", ob->dirname, message_id)); + Uunlink(string_sprintf("%s/%s-H", dstdir, message_id)); goto RETURN; } -(void)close(ddfd); -(void)close(sdfd); - DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name); addr->transport_return = OK; RETURN: -if (dst_file) (void)fclose(dst_file); -if (src_file && !is_data_file) (void)fclose(src_file); -if (ddfd) (void)close(ddfd); -if (sdfd) (void)close(sdfd); +if (ddfd >= 0) (void) close(ddfd); +if (sdfd >= 0) (void) close(sdfd); /* A return of FALSE means that if there was an error, a common error was put in the first address of a batch. */ return FALSE; } + +#endif /*!MACRO_PREDEF*/ +#endif /*EXPERIMENTAL_QUEUEFILE*/