summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2020-12-27 07:57:31 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2020-12-27 09:17:33 -0500
commit2639e5ae431a54c7d32b3a62b71a25151f10d871 (patch)
treea0c9d1a161bad0c65043e5eebdedbe134313c1df
parent15bfe5ef0e5cc6bdd4fff541783f804d84df90c8 (diff)
downloadlighttpd-git-2639e5ae431a54c7d32b3a62b71a25151f10d871.tar.gz
[multiple] chunkqueue_write_chunk()
create API in chunk.[ch] for writing a chunk to an fd (pull similar code from mod_cgi and mod_webdav) This new API is intended for use on request body input, which is written to size-limited temporary files controlled by lighttpd and written to files or pipes. (network_backend_write() is for writing chunkqueues to sockets)
-rw-r--r--src/chunk.c185
-rw-r--r--src/chunk.h3
-rw-r--r--src/mod_cgi.c178
-rw-r--r--src/mod_webdav.c114
4 files changed, 208 insertions, 272 deletions
diff --git a/src/chunk.c b/src/chunk.c
index de7e73fb..8c688f69 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -885,16 +885,6 @@ void chunkqueue_compact_mem(chunkqueue *cq, size_t clen) {
}
static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const restrict errh) {
- off_t offset, toSend;
- struct stat st;
-
- force_assert(NULL != c);
- force_assert(FILE_CHUNK == c->type);
- force_assert(c->offset >= 0 && c->offset <= c->file.length);
-
- offset = c->offset;
- toSend = c->file.length - c->offset;
-
if (-1 == c->file.fd) {
/* (permit symlinks; should already have been checked. However, TOC-TOU remains) */
if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, 1, O_RDONLY, 0))) {
@@ -906,11 +896,17 @@ static int chunk_open_file_chunk(chunk * const restrict c, log_error_st * const
/*(skip file size checks if file is temp file created by lighttpd)*/
if (c->file.is_temp) return 0;
+ force_assert(FILE_CHUNK == c->type);
+ force_assert(c->offset >= 0 && c->offset <= c->file.length);
+
+ struct stat st;
if (-1 == fstat(c->file.fd, &st)) {
log_perror(errh, __FILE__, __LINE__, "fstat failed");
return -1;
}
+ const off_t offset = c->offset;
+ const off_t toSend = c->file.length - c->offset;
if (offset > st.st_size || toSend > st.st_size || offset > st.st_size - toSend) {
log_error(errh, __FILE__, __LINE__, "file shrunk: %s", c->mem->ptr);
return -1;
@@ -924,6 +920,165 @@ int chunkqueue_open_file_chunk(chunkqueue * const restrict cq, log_error_st * co
}
+#if defined(HAVE_MMAP)
+__attribute_cold__
+#endif
+__attribute_noinline__
+static ssize_t
+chunkqueue_write_chunk_file_intermed (const int fd, chunk * const restrict c, log_error_st * const errh)
+{
+ char buf[16384];
+ char *data = buf;
+ const off_t count = c->file.length - c->offset;
+ uint32_t dlen = count < (off_t)sizeof(buf) ? (uint32_t)count : sizeof(buf);
+ chunkqueue cq = {c,c,0,0,0,0,0}; /*(fake cq for chunkqueue_peek_data())*/
+ if (0 != chunkqueue_peek_data(&cq, &data, &dlen, errh) && 0 == dlen)
+ return -1;
+ ssize_t wr;
+ do { wr = write(fd, data, dlen); } while (-1 == wr && errno == EINTR);
+ return wr;
+}
+
+
+#if defined(HAVE_MMAP)
+/*(improved from network_write_mmap.c)*/
+static off_t
+mmap_align_offset (off_t start)
+{
+ static off_t pagemask = 0;
+ if (0 == pagemask) {
+ long pagesize = sysconf(_SC_PAGESIZE);
+ if (-1 == pagesize) pagesize = 4096;
+ pagemask = ~((off_t)pagesize - 1); /* pagesize always power-of-2 */
+ }
+ return (start & pagemask);
+}
+#endif
+
+
+#if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
+ && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
+ && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
+#include <sys/sendfile.h>
+#include <stdint.h>
+#endif
+static ssize_t
+chunkqueue_write_chunk_file (const int fd, chunk * const restrict c, log_error_st * const errh)
+{
+ /*(similar to network_write_file_chunk_mmap(), but does not use send() on
+ * Windows because fd is expected to be file or pipe here, not socket)*/
+
+ if (0 != chunk_open_file_chunk(c, errh))
+ return -1;
+
+ const off_t count = c->file.length - c->offset;
+ if (0 == count) return 0; /*(sanity check)*/
+
+ ssize_t wr;
+ #if defined HAVE_SYS_SENDFILE_H && defined HAVE_SENDFILE \
+ && (!defined _LARGEFILE_SOURCE || defined HAVE_SENDFILE64) \
+ && defined(__linux__) && !defined HAVE_SENDFILE_BROKEN
+ /* Linux kernel >= 2.6.33 supports sendfile() between most fd types */
+ off_t offset = c->offset;
+ wr = sendfile(fd, c->file.fd, &offset, count<INT32_MAX ? count : INT32_MAX);
+ if (wr >= 0) return wr;
+
+ if (wr < 0 && (errno == EINVAL || errno == ENOSYS))
+ #endif
+ {
+ #if defined(HAVE_MMAP)
+ /*(caller is responsible for handling SIGBUS if chunkqueue might contain
+ * untrusted file, i.e. any file other than lighttpd-created tempfile)*/
+ /*(tempfiles are expected for input, MAP_PRIVATE used for portability)*/
+ /*(mmaps and writes complete chunk instead of only small parts; files
+ * are expected to be temp files with reasonable chunk sizes)*/
+
+ /* (re)mmap the buffer if range is not covered completely */
+ if (MAP_FAILED == c->file.mmap.start
+ || c->offset < c->file.mmap.offset
+ || c->file.length
+ > (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
+
+ if (MAP_FAILED != c->file.mmap.start) {
+ munmap(c->file.mmap.start, c->file.mmap.length);
+ c->file.mmap.start = MAP_FAILED;
+ }
+
+ c->file.mmap.offset = mmap_align_offset(c->offset);
+ c->file.mmap.length = c->file.length - c->file.mmap.offset;
+ c->file.mmap.start =
+ mmap(NULL, c->file.mmap.length, PROT_READ, MAP_PRIVATE,
+ c->file.fd, c->file.mmap.offset);
+
+ #if 0
+ /* close() fd as soon as fully mmap() rather than when done w/ chunk
+ * (possibly worthwhile to keep active fd count lower) */
+ if (c->file.is_temp && !c->file.refchg) {
+ close(c->file.fd);
+ c->file.fd = -1;
+ }
+ #endif
+ }
+
+ if (MAP_FAILED != c->file.mmap.start) {
+ const char * const data =
+ c->file.mmap.start + c->offset - c->file.mmap.offset;
+ do { wr = write(fd,data,count); } while (-1 == wr && errno==EINTR);
+ }
+ else
+ #endif
+ wr = chunkqueue_write_chunk_file_intermed(fd, c, errh);
+ }
+ return wr;
+}
+
+
+static ssize_t
+chunkqueue_write_chunk_mem (const int fd, const chunk * const restrict c)
+{
+ const void * const buf = c->mem->ptr + c->offset;
+ const size_t count = chunk_buffer_string_length(c->mem) - (size_t)c->offset;
+ ssize_t wr;
+ do { wr = write(fd, buf, count); } while (-1 == wr && errno == EINTR);
+ return wr;
+}
+
+
+ssize_t
+chunkqueue_write_chunk (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh)
+{
+ /*(note: expects non-empty cq->first)*/
+ chunk * const c = cq->first;
+ switch (c->type) {
+ case MEM_CHUNK:
+ return chunkqueue_write_chunk_mem(fd, c);
+ case FILE_CHUNK:
+ return chunkqueue_write_chunk_file(fd, c, errh);
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+
+ssize_t
+chunkqueue_write_chunk_to_pipe (const int fd, chunkqueue * const restrict cq, log_error_st * const restrict errh)
+{
+ /*(note: expects non-empty cq->first)*/
+ #ifdef SPLICE_F_NONBLOCK /* splice() temp files to pipe on Linux */
+ chunk * const c = cq->first;
+ if (c->type == FILE_CHUNK) {
+ loff_t abs_offset = c->offset;
+ return (0 == chunk_open_file_chunk(c, errh))
+ ? splice(c->file.fd, &abs_offset, fd, NULL,
+ (size_t)(c->file.length - c->offset), SPLICE_F_NONBLOCK)
+ : -1;
+ }
+ #endif
+ return chunkqueue_write_chunk(fd, cq, errh);
+}
+
+
void
chunkqueue_small_resp_optim (chunkqueue * const restrict cq)
{
@@ -1004,12 +1159,16 @@ chunkqueue_peek_data (chunkqueue * const cq,
toSend = (off_t)space;
if (-1 == lseek(c->file.fd, offset, SEEK_SET)) {
- log_perror(errh, __FILE__, __LINE__, "lseek");
+ log_perror(errh, __FILE__, __LINE__, "lseek(\"%s\")",
+ c->mem->ptr);
return -1;
}
- toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend);
+ do {
+ toSend = read(c->file.fd, data_in + *dlen, (size_t)toSend);
+ } while (-1 == toSend && errno == EINTR);
if (toSend <= 0) { /* -1 error; 0 EOF (unexpected) */
- log_perror(errh, __FILE__, __LINE__, "read");
+ log_perror(errh, __FILE__, __LINE__, "read(\"%s\")",
+ c->mem->ptr);
return -1;
}
diff --git a/src/chunk.h b/src/chunk.h
index f1a61051..34ddfbac 100644
--- a/src/chunk.h
+++ b/src/chunk.h
@@ -127,6 +127,9 @@ void chunkqueue_compact_mem(chunkqueue *cq, size_t clen);
void chunkqueue_small_resp_optim (chunkqueue * restrict cq);
+ssize_t chunkqueue_write_chunk (int fd, chunkqueue * restrict cq, struct log_error_st * restrict errh);
+ssize_t chunkqueue_write_chunk_to_pipe (int fd, chunkqueue * restrict cq, struct log_error_st * restrict errh);
+
int chunkqueue_peek_data (chunkqueue *cq, char **data, uint32_t *dlen, struct log_error_st * restrict errh);
int chunkqueue_read_data (chunkqueue *cq, char *data, uint32_t dlen, struct log_error_st * restrict errh);
diff --git a/src/mod_cgi.c b/src/mod_cgi.c
index 92812029..8415126e 100644
--- a/src/mod_cgi.c
+++ b/src/mod_cgi.c
@@ -12,7 +12,6 @@
#include "plugin.h"
#include <sys/types.h>
-#include "sys-mmap.h"
#include "sys-socket.h"
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
@@ -528,187 +527,50 @@ static int cgi_env_add(void *venv, const char *key, size_t key_len, const char *
return 0;
}
-#ifndef SPLICE_F_NONBLOCK
-/*(improved from network_write_mmap.c)*/
-static off_t mmap_align_offset(off_t start) {
- static off_t pagemask = 0;
- if (0 == pagemask) {
- long pagesize = sysconf(_SC_PAGESIZE);
- if (-1 == pagesize) pagesize = 4096;
- pagemask = ~((off_t)pagesize - 1); /* pagesize always power-of-2 */
- }
- return (start & pagemask);
-}
-#endif
-
-/* returns: 0: continue, -1: fatal error, -2: connection reset */
-/* similar to network_write_file_chunk_mmap, but doesn't use send on windows (because we're on pipes),
- * also mmaps and sends complete chunk instead of only small parts - the files
- * are supposed to be temp files with reasonable chunk sizes.
- *
- * Also always use mmap; the files are "trusted", as we created them.
- */
-static ssize_t cgi_write_file_chunk_mmap(request_st * const r, int fd, chunkqueue *cq) {
- chunk* const c = cq->first;
- off_t offset, toSend;
- ssize_t wr;
-
- force_assert(NULL != c);
- force_assert(FILE_CHUNK == c->type);
- force_assert(c->offset >= 0 && c->offset <= c->file.length);
-
- offset = c->offset;
- toSend = c->file.length - c->offset;
-
- if (0 == toSend) {
- chunkqueue_remove_finished_chunks(cq);
- return 0;
- }
-
- /*(simplified from chunk.c:chunkqueue_open_file_chunk())*/
- if (-1 == c->file.fd) {
- if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, r->conf.follow_symlink, O_RDONLY, 0))) {
- log_perror(r->conf.errh, __FILE__, __LINE__, "open failed: %s", c->mem->ptr);
- return -1;
- }
- }
-
- #ifdef SPLICE_F_NONBLOCK
- loff_t abs_offset = offset;
- wr = splice(c->file.fd, &abs_offset, fd, NULL,
- (size_t)toSend, SPLICE_F_NONBLOCK);
- #else
- size_t mmap_offset, mmap_avail;
- char *data = NULL;
- off_t file_end = c->file.length; /* offset to file end in this chunk */
-
- /* (re)mmap the buffer if range is not covered completely */
- if (MAP_FAILED == c->file.mmap.start
- || offset < c->file.mmap.offset
- || file_end > (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
-
- if (MAP_FAILED != c->file.mmap.start) {
- munmap(c->file.mmap.start, c->file.mmap.length);
- c->file.mmap.start = MAP_FAILED;
- }
-
- c->file.mmap.offset = mmap_align_offset(offset);
- c->file.mmap.length = file_end - c->file.mmap.offset;
-
- if (MAP_FAILED == (c->file.mmap.start = mmap(NULL, c->file.mmap.length, PROT_READ, MAP_PRIVATE, c->file.fd, c->file.mmap.offset))) {
- if (toSend > 65536) toSend = 65536;
- data = malloc(toSend);
- force_assert(data);
- if (-1 == lseek(c->file.fd, offset, SEEK_SET)
- || 0 >= (toSend = read(c->file.fd, data, toSend))) {
- if (-1 == toSend) {
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "lseek/read %s %d %lld failed:",
- c->mem->ptr, c->file.fd, (long long)offset);
- } else { /*(0 == toSend)*/
- log_error(r->conf.errh, __FILE__, __LINE__,
- "unexpected EOF (input truncated?): %s %d %lld",
- c->mem->ptr, c->file.fd, (long long)offset);
- }
- free(data);
- return -1;
- }
- }
- }
-
- if (MAP_FAILED != c->file.mmap.start) {
- force_assert(offset >= c->file.mmap.offset);
- mmap_offset = offset - c->file.mmap.offset;
- force_assert(c->file.mmap.length > mmap_offset);
- mmap_avail = c->file.mmap.length - mmap_offset;
- force_assert(toSend <= (off_t) mmap_avail);
-
- data = c->file.mmap.start + mmap_offset;
- }
-
- wr = write(fd, data, toSend);
-
- if (MAP_FAILED == c->file.mmap.start) free(data);
- #endif
-
- if (wr < 0) {
- switch (errno) {
- case EAGAIN:
- case EINTR:
- return 0;
- case EPIPE:
- case ECONNRESET:
- return -2;
- default:
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "write %d failed", fd);
- return -1;
- }
- }
-
- chunkqueue_mark_written(cq, wr);
- return wr;
-}
-
static int cgi_write_request(handler_ctx *hctx, int fd) {
request_st * const r = hctx->r;
chunkqueue *cq = &r->reqbody_queue;
chunk *c;
+ chunkqueue_remove_finished_chunks(cq); /* unnecessary? */
+
/* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms.
* solution: if this is still a problem on windows, then substitute
* socketpair() for pipe() and closesocket() for close() on windows.
*/
for (c = cq->first; c; c = cq->first) {
- ssize_t wr = -1;
-
- switch(c->type) {
- case FILE_CHUNK:
- wr = cgi_write_file_chunk_mmap(r, fd, cq);
- break;
-
- case MEM_CHUNK:
- if ((wr = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
+ ssize_t wr = chunkqueue_write_chunk_to_pipe(fd, cq, r->conf.errh);
+ if (wr > 0) {
+ chunkqueue_mark_written(cq, wr);
+ /* continue if wrote whole chunk or wrote 16k block
+ * (see chunkqueue_write_chunk_file_intermed()) */
+ if (c != cq->first || wr == 16384)
+ continue;
+ /*(else partial write)*/
+ }
+ else if (wr < 0) {
switch(errno) {
case EAGAIN:
case EINTR:
- /* ignore and try again */
- wr = 0;
+ /* ignore and try again later */
break;
case EPIPE:
case ECONNRESET:
/* connection closed */
- wr = -2;
+ log_error(r->conf.errh, __FILE__, __LINE__,
+ "failed to send post data to cgi, connection closed by CGI");
+ /* skip all remaining data */
+ chunkqueue_mark_written(cq, chunkqueue_length(cq));
break;
default:
/* fatal error */
log_perror(r->conf.errh, __FILE__, __LINE__, "write() failed");
- wr = -1;
- break;
+ return -1;
}
- } else if (wr > 0) {
- chunkqueue_mark_written(cq, wr);
- }
- break;
- }
-
- if (0 == wr) break; /*(might block)*/
-
- switch (wr) {
- case -1:
- /* fatal error */
- return -1;
- case -2:
- /* connection reset */
- log_error(r->conf.errh, __FILE__, __LINE__,
- "failed to send post data to cgi, connection closed by CGI");
- /* skip all remaining data */
- chunkqueue_mark_written(cq, chunkqueue_length(cq));
- break;
- default:
- break;
}
+ /*if (0 == wr) break;*/ /*(might block)*/
+ break;
}
if (cq->bytes_out == (off_t)r->reqbody_length && !hctx->conf.upgrade) {
diff --git a/src/mod_webdav.c b/src/mod_webdav.c
index 591bfb89..ab52d6b1 100644
--- a/src/mod_webdav.c
+++ b/src/mod_webdav.c
@@ -3553,36 +3553,13 @@ webdav_parse_chunkqueue (request_st * const r,
weHave = c->file.length - c->offset;
}
else {
- switch (errno) {
- case ENOSYS: case ENODEV: case EINVAL: break;
- default:
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "open() or mmap() '%*.s'",
- BUFFER_INTLEN_PTR(c->mem));
- }
- if (webdav_open_chunk_file_rd(c) < 0) {
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "open() '%*.s'",
- BUFFER_INTLEN_PTR(c->mem));
- err = XML_IO_UNKNOWN;
- break;
- }
- ssize_t rd = -1;
- do {
- if (-1 == lseek(c->file.fd, c->offset, SEEK_SET))
- break;
- off_t len = c->file.length - c->offset;
- if (len > (off_t)sizeof(buf)) len = (off_t)sizeof(buf);
- rd = read(c->file.fd, buf, (size_t)len);
- } while (-1 == rd && errno == EINTR);
- if (rd >= 0) {
- xmlstr = buf;
- weHave = (size_t)rd;
+ char *data = buf;
+ uint32_t dlen = sizeof(buf);
+ if (0 == chunkqueue_peek_data(cq, &data, &dlen, r->conf.errh)) {
+ xmlstr = data;
+ weHave = dlen;
}
else {
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "read() '%*.s'",
- BUFFER_INTLEN_PTR(c->mem));
err = XML_IO_UNKNOWN;
break;
}
@@ -4229,85 +4206,20 @@ mod_webdav_delete (request_st * const r, const plugin_config * const pconf)
}
-static ssize_t
-mod_webdav_write_cq_first_chunk (request_st * const r, chunkqueue * const cq,
- const int fd)
-{
- /* (Note: copying might take some time, temporarily pausing server) */
- chunk *c = cq->first;
- ssize_t wr = 0;
-
- switch(c->type) {
- case FILE_CHUNK:
- if (NULL != webdav_mmap_file_chunk(c)) {
- do {
- wr = write(fd,
- c->file.mmap.start + c->offset - c->file.mmap.offset,
- c->file.length - c->offset);
- } while (-1 == wr && errno == EINTR);
- break;
- }
- else {
- switch (errno) {
- case ENOSYS: case ENODEV: case EINVAL: break;
- default:
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "open() or mmap() '%*.s'",
- BUFFER_INTLEN_PTR(c->mem));
- }
-
- if (webdav_open_chunk_file_rd(c) < 0) {
- http_status_set_error(r, 500); /* Internal Server Error */
- return -1;
- }
- ssize_t rd = -1;
- char buf[16384];
- do {
- if (-1 == lseek(c->file.fd, c->offset, SEEK_SET))
- break;
- off_t len = c->file.length - c->offset;
- if (len > (off_t)sizeof(buf)) len = (off_t)sizeof(buf);
- rd = read(c->file.fd, buf, (size_t)len);
- } while (-1 == rd && errno == EINTR);
- if (rd >= 0) {
- do {
- wr = write(fd, buf, (size_t)rd);
- } while (-1 == wr && errno == EINTR);
- break;
- }
- else {
- log_perror(r->conf.errh, __FILE__, __LINE__,
- "read() '%*.s'",
- BUFFER_INTLEN_PTR(c->mem));
- http_status_set_error(r, 500); /* Internal Server Error */
- return -1;
- }
- }
- case MEM_CHUNK:
- do {
- wr = write(fd, c->mem->ptr + c->offset,
- buffer_string_length(c->mem) - c->offset);
- } while (-1 == wr && errno == EINTR);
- break;
- }
-
- if (wr > 0) {
- chunkqueue_mark_written(cq, wr);
- }
- else if (wr < 0)
- http_status_set_error(r, (errno == ENOSPC) ? 507 : 403);
-
- return wr;
-}
-
-
__attribute_noinline__
static int
mod_webdav_write_cq (request_st * const r, chunkqueue * const cq, const int fd)
{
+ /* (Note: copying might take some time, temporarily pausing server) */
chunkqueue_remove_finished_chunks(cq);
while (!chunkqueue_is_empty(cq)) {
- if (mod_webdav_write_cq_first_chunk(r, cq, fd) < 0) return 0;
+ ssize_t wr = chunkqueue_write_chunk(fd, cq, r->conf.errh);
+ if (wr > 0)
+ chunkqueue_mark_written(cq, wr);
+ else if (wr < 0) {
+ http_status_set_error(r, (errno == ENOSPC) ? 507 : 403);
+ return 0;
+ }
}
return 1;
}