summaryrefslogtreecommitdiff
path: root/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'buffer.c')
-rw-r--r--buffer.c947
1 files changed, 603 insertions, 344 deletions
diff --git a/buffer.c b/buffer.c
index ff64aede..00035745 100644
--- a/buffer.c
+++ b/buffer.c
@@ -26,16 +26,18 @@
*/
#include "event2/event-config.h"
+#include "evconfig-private.h"
-#ifdef WIN32
+#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <io.h>
#endif
#ifdef _EVENT_HAVE_VASPRINTF
-/* If we have vasprintf, we need to define this before we include stdio.h. */
-#define _GNU_SOURCE
+/* If we have vasprintf, we need to define _GNU_SOURCE before we include
+ * stdio.h. This comes from evconfig-private.h.
+ */
#endif
#include <sys/types.h>
@@ -63,6 +65,10 @@
#ifdef _EVENT_HAVE_SYS_SENDFILE_H
#include <sys/sendfile.h>
#endif
+#ifdef _EVENT_HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
#include <errno.h>
#include <stdio.h>
@@ -83,7 +89,6 @@
#include "event2/bufferevent_compat.h"
#include "event2/bufferevent_struct.h"
#include "event2/thread.h"
-#include "event2/event-config.h"
#include "log-internal.h"
#include "mm-internal.h"
#include "util-internal.h"
@@ -111,14 +116,6 @@
#define SENDFILE_IS_SOLARIS 1
#endif
-#ifdef USE_SENDFILE
-static int use_sendfile = 1;
-#endif
-#ifdef _EVENT_HAVE_MMAP
-static int use_mmap = 1;
-#endif
-
-
/* Mask of user-selectable callback flags. */
#define EVBUFFER_CB_USER_FLAGS 0xffff
/* Mask of all internal-use-only flags. */
@@ -135,6 +132,13 @@ static int use_mmap = 1;
#define CHAIN_PINNED(ch) (((ch)->flags & EVBUFFER_MEM_PINNED_ANY) != 0)
#define CHAIN_PINNED_R(ch) (((ch)->flags & EVBUFFER_MEM_PINNED_R) != 0)
+/* evbuffer_ptr support */
+#define PTR_NOT_FOUND(ptr) do { \
+ (ptr)->pos = -1; \
+ (ptr)->_internal.chain = NULL; \
+ (ptr)->_internal.pos_in_chain = 0; \
+} while (0)
+
static void evbuffer_chain_align(struct evbuffer_chain *chain);
static int evbuffer_chain_should_realign(struct evbuffer_chain *chain,
size_t datalen);
@@ -143,13 +147,10 @@ static int evbuffer_ptr_memcmp(const struct evbuffer *buf,
const struct evbuffer_ptr *pos, const char *mem, size_t len);
static struct evbuffer_chain *evbuffer_expand_singlechain(struct evbuffer *buf,
size_t datlen);
-
-#ifdef WIN32
-static int evbuffer_readfile(struct evbuffer *buf, evutil_socket_t fd,
- ev_ssize_t howmuch);
-#else
-#define evbuffer_readfile evbuffer_read
-#endif
+static int evbuffer_ptr_subtract(struct evbuffer *buf, struct evbuffer_ptr *pos,
+ size_t howfar);
+static int evbuffer_file_segment_materialize(struct evbuffer_file_segment *seg);
+static inline void evbuffer_chain_incref(struct evbuffer_chain *chain);
static struct evbuffer_chain *
evbuffer_chain_new(size_t size)
@@ -177,50 +178,66 @@ evbuffer_chain_new(size_t size)
*/
chain->buffer = EVBUFFER_CHAIN_EXTRA(u_char, chain);
+ chain->refcnt = 1;
+
return (chain);
}
static inline void
evbuffer_chain_free(struct evbuffer_chain *chain)
{
+ EVUTIL_ASSERT(chain->refcnt > 0);
+ if (--chain->refcnt > 0) {
+ /* chain is still referenced by other chains */
+ return;
+ }
+
if (CHAIN_PINNED(chain)) {
+ /* will get freed once no longer dangling */
+ chain->refcnt++;
chain->flags |= EVBUFFER_DANGLING;
return;
}
- if (chain->flags & (EVBUFFER_MMAP|EVBUFFER_SENDFILE|
- EVBUFFER_REFERENCE)) {
- if (chain->flags & EVBUFFER_REFERENCE) {
- struct evbuffer_chain_reference *info =
- EVBUFFER_CHAIN_EXTRA(
- struct evbuffer_chain_reference,
- chain);
- if (info->cleanupfn)
- (*info->cleanupfn)(chain->buffer,
- chain->buffer_len,
- info->extra);
- }
-#ifdef _EVENT_HAVE_MMAP
- if (chain->flags & EVBUFFER_MMAP) {
- struct evbuffer_chain_fd *info =
- EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd,
- chain);
- if (munmap(chain->buffer, chain->buffer_len) == -1)
- event_warn("%s: munmap failed", __func__);
- if (close(info->fd) == -1)
- event_warn("%s: close(%d) failed",
- __func__, info->fd);
- }
+
+ /* safe to release chain, it's either a referencing
+ * chain or all references to it have been freed */
+ if (chain->flags & EVBUFFER_REFERENCE) {
+ struct evbuffer_chain_reference *info =
+ EVBUFFER_CHAIN_EXTRA(
+ struct evbuffer_chain_reference,
+ chain);
+ if (info->cleanupfn)
+ (*info->cleanupfn)(chain->buffer,
+ chain->buffer_len,
+ info->extra);
+ }
+ if (chain->flags & EVBUFFER_FILESEGMENT) {
+ struct evbuffer_chain_file_segment *info =
+ EVBUFFER_CHAIN_EXTRA(
+ struct evbuffer_chain_file_segment,
+ chain);
+ if (info->segment) {
+#ifdef _WIN32
+ if (info->segment->is_mapping)
+ UnmapViewOfFile(chain->buffer);
#endif
-#ifdef USE_SENDFILE
- if (chain->flags & EVBUFFER_SENDFILE) {
- struct evbuffer_chain_fd *info =
- EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd,
- chain);
- if (close(info->fd) == -1)
- event_warn("%s: close(%d) failed",
- __func__, info->fd);
+ evbuffer_file_segment_free(info->segment);
}
-#endif
+ }
+ if (chain->flags & EVBUFFER_MULTICAST) {
+ struct evbuffer_multicast_parent *info =
+ EVBUFFER_CHAIN_EXTRA(
+ struct evbuffer_multicast_parent,
+ chain);
+ /* referencing chain is being freed, decrease
+ * refcounts of source chain and associated
+ * evbuffer (which get freed once both reach
+ * zero) */
+ EVUTIL_ASSERT(info->source != NULL);
+ EVUTIL_ASSERT(info->parent != NULL);
+ EVBUFFER_LOCK(info->source);
+ evbuffer_chain_free(info->parent);
+ _evbuffer_decref_and_unlock(info->source);
}
mm_free(chain);
@@ -290,21 +307,11 @@ evbuffer_chain_insert(struct evbuffer *buf,
EVUTIL_ASSERT(buf->first == NULL);
buf->first = buf->last = chain;
} else {
- struct evbuffer_chain **ch = buf->last_with_datap;
- /* Find the first victim chain. It might be *last_with_datap */
- while ((*ch) && ((*ch)->off != 0 || CHAIN_PINNED(*ch)))
- ch = &(*ch)->next;
- if (*ch == NULL) {
- /* There is no victim; just append this new chain. */
- buf->last->next = chain;
- if (chain->off)
- buf->last_with_datap = &buf->last->next;
- } else {
- /* Replace all victim chains with this chain. */
- EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
- evbuffer_free_all_chains(*ch);
- *ch = chain;
- }
+ struct evbuffer_chain **chp;
+ chp = evbuffer_free_trailing_empty_chains(buf);
+ *chp = chain;
+ if (chain->off)
+ buf->last_with_datap = chp;
buf->last = chain;
}
buf->total_len += chain->off;
@@ -336,6 +343,12 @@ _evbuffer_chain_unpin(struct evbuffer_chain *chain, unsigned flag)
evbuffer_chain_free(chain);
}
+static inline void
+evbuffer_chain_incref(struct evbuffer_chain *chain)
+{
+ ++chain->refcnt;
+}
+
struct evbuffer *
evbuffer_new(void)
{
@@ -345,7 +358,7 @@ evbuffer_new(void)
if (buffer == NULL)
return (NULL);
- TAILQ_INIT(&buffer->callbacks);
+ LIST_INIT(&buffer->callbacks);
buffer->refcnt = 1;
buffer->last_with_datap = &buffer->first;
@@ -454,7 +467,7 @@ evbuffer_run_callbacks(struct evbuffer *buffer, int running_deferred)
ASSERT_EVBUFFER_LOCKED(buffer);
- if (TAILQ_EMPTY(&buffer->callbacks)) {
+ if (LIST_EMPTY(&buffer->callbacks)) {
buffer->n_add_for_cb = buffer->n_del_for_cb = 0;
return;
}
@@ -469,12 +482,12 @@ evbuffer_run_callbacks(struct evbuffer *buffer, int running_deferred)
buffer->n_add_for_cb = 0;
buffer->n_del_for_cb = 0;
}
- for (cbent = TAILQ_FIRST(&buffer->callbacks);
- cbent != TAILQ_END(&buffer->callbacks);
+ for (cbent = LIST_FIRST(&buffer->callbacks);
+ cbent != LIST_END(&buffer->callbacks);
cbent = next) {
/* Get the 'next' pointer now in case this callback decides
* to remove itself or something. */
- next = TAILQ_NEXT(cbent, next);
+ next = LIST_NEXT(cbent, next);
if ((cbent->flags & mask) != masked_val)
continue;
@@ -490,7 +503,7 @@ evbuffer_run_callbacks(struct evbuffer *buffer, int running_deferred)
void
evbuffer_invoke_callbacks(struct evbuffer *buffer)
{
- if (TAILQ_EMPTY(&buffer->callbacks)) {
+ if (LIST_EMPTY(&buffer->callbacks)) {
buffer->n_add_for_cb = buffer->n_del_for_cb = 0;
return;
}
@@ -529,9 +542,9 @@ evbuffer_remove_all_callbacks(struct evbuffer *buffer)
{
struct evbuffer_cb_entry *cbent;
- while ((cbent = TAILQ_FIRST(&buffer->callbacks))) {
- TAILQ_REMOVE(&buffer->callbacks, cbent, next);
- mm_free(cbent);
+ while ((cbent = LIST_FIRST(&buffer->callbacks))) {
+ LIST_REMOVE(cbent, next);
+ mm_free(cbent);
}
}
@@ -609,6 +622,42 @@ evbuffer_get_contiguous_space(const struct evbuffer *buf)
return result;
}
+size_t
+evbuffer_add_iovec(struct evbuffer * buf, struct evbuffer_iovec * vec, int n_vec) {
+ int n;
+ size_t res;
+ size_t to_alloc;
+
+ EVBUFFER_LOCK(buf);
+
+ res = to_alloc = 0;
+
+ for (n = 0; n < n_vec; n++) {
+ to_alloc += vec[n].iov_len;
+ }
+
+ if (_evbuffer_expand_fast(buf, to_alloc, 2) < 0) {
+ goto done;
+ }
+
+ for (n = 0; n < n_vec; n++) {
+ /* XXX each 'add' call here does a bunch of setup that's
+ * obviated by _evbuffer_expand_fast, and some cleanup that we
+ * would like to do only once. Instead we should just extract
+ * the part of the code that's needed. */
+
+ if (evbuffer_add(buf, vec[n].iov_base, vec[n].iov_len) < 0) {
+ goto done;
+ }
+
+ res += vec[n].iov_len;
+ }
+
+done:
+ EVBUFFER_UNLOCK(buf);
+ return res;
+}
+
int
evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
struct evbuffer_iovec *vec, int n_vecs)
@@ -837,6 +886,46 @@ APPEND_CHAIN(struct evbuffer *dst, struct evbuffer *src)
dst->total_len += src->total_len;
}
+static inline void
+APPEND_CHAIN_MULTICAST(struct evbuffer *dst, struct evbuffer *src)
+{
+ struct evbuffer_chain *tmp;
+ struct evbuffer_chain *chain = src->first;
+ struct evbuffer_multicast_parent *extra;
+
+ ASSERT_EVBUFFER_LOCKED(dst);
+ ASSERT_EVBUFFER_LOCKED(src);
+
+ for (; chain; chain = chain->next) {
+ if (!chain->off || chain->flags & EVBUFFER_DANGLING) {
+ /* skip empty chains */
+ continue;
+ }
+
+ tmp = evbuffer_chain_new(sizeof(struct evbuffer_multicast_parent));
+ if (!tmp) {
+ event_warn("%s: out of memory", __func__);
+ return;
+ }
+ extra = EVBUFFER_CHAIN_EXTRA(struct evbuffer_multicast_parent, tmp);
+ /* reference evbuffer containing source chain so it
+ * doesn't get released while the chain is still
+ * being referenced to */
+ _evbuffer_incref(src);
+ extra->source = src;
+ /* reference source chain which now becomes immutable */
+ evbuffer_chain_incref(chain);
+ extra->parent = chain;
+ chain->flags |= EVBUFFER_IMMUTABLE;
+ tmp->buffer_len = chain->buffer_len;
+ tmp->misalign = chain->misalign;
+ tmp->off = chain->off;
+ tmp->flags |= EVBUFFER_MULTICAST|EVBUFFER_IMMUTABLE;
+ tmp->buffer = chain->buffer;
+ evbuffer_chain_insert(dst, tmp);
+ }
+}
+
static void
PREPEND_CHAIN(struct evbuffer *dst, struct evbuffer *src)
{
@@ -902,6 +991,49 @@ done:
}
int
+evbuffer_add_buffer_reference(struct evbuffer *outbuf, struct evbuffer *inbuf)
+{
+ size_t in_total_len, out_total_len;
+ struct evbuffer_chain *chain;
+ int result = 0;
+
+ EVBUFFER_LOCK2(inbuf, outbuf);
+ in_total_len = inbuf->total_len;
+ out_total_len = outbuf->total_len;
+ chain = inbuf->first;
+
+ if (in_total_len == 0)
+ goto done;
+
+ if (outbuf->freeze_end || outbuf == inbuf) {
+ result = -1;
+ goto done;
+ }
+
+ for (; chain; chain = chain->next) {
+ if ((chain->flags & (EVBUFFER_FILESEGMENT|EVBUFFER_SENDFILE|EVBUFFER_MULTICAST)) != 0) {
+ /* chain type can not be referenced */
+ result = -1;
+ goto done;
+ }
+ }
+
+ if (out_total_len == 0) {
+ /* There might be an empty chain at the start of outbuf; free
+ * it. */
+ evbuffer_free_all_chains(outbuf->first);
+ }
+ APPEND_CHAIN_MULTICAST(outbuf, inbuf);
+
+ outbuf->n_add_for_cb += in_total_len;
+ evbuffer_invoke_callbacks(outbuf);
+
+done:
+ EVBUFFER_UNLOCK2(inbuf, outbuf);
+ return result;
+}
+
+int
evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
struct evbuffer_chain *pinned, *last;
@@ -1001,10 +1133,8 @@ evbuffer_drain(struct evbuffer *buf, size_t len)
}
buf->first = chain;
- if (chain) {
- chain->misalign += remaining;
- chain->off -= remaining;
- }
+ chain->misalign += remaining;
+ chain->off -= remaining;
}
buf->n_del_for_cb += len;
@@ -1022,7 +1152,7 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
{
ev_ssize_t n;
EVBUFFER_LOCK(buf);
- n = evbuffer_copyout(buf, data_out, datlen);
+ n = evbuffer_copyout_from(buf, NULL, data_out, datlen);
if (n > 0) {
if (evbuffer_drain(buf, n)<0)
n = -1;
@@ -1034,18 +1164,34 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
ev_ssize_t
evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen)
{
+ return evbuffer_copyout_from(buf, NULL, data_out, datlen);
+}
+
+ev_ssize_t
+evbuffer_copyout_from(struct evbuffer *buf, const struct evbuffer_ptr *pos,
+ void *data_out, size_t datlen)
+{
/*XXX fails badly on sendfile case. */
struct evbuffer_chain *chain;
char *data = data_out;
size_t nread;
ev_ssize_t result = 0;
+ size_t pos_in_chain;
EVBUFFER_LOCK(buf);
- chain = buf->first;
+ if (pos) {
+ chain = pos->_internal.chain;
+ pos_in_chain = pos->_internal.pos_in_chain;
+ if (datlen + pos->pos > buf->total_len)
+ datlen = buf->total_len - pos->pos;
+ } else {
+ chain = buf->first;
+ pos_in_chain = 0;
+ if (datlen > buf->total_len)
+ datlen = buf->total_len;
+ }
- if (datlen >= buf->total_len)
- datlen = buf->total_len;
if (datlen == 0)
goto done;
@@ -1057,18 +1203,23 @@ evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen)
nread = datlen;
- while (datlen && datlen >= chain->off) {
- memcpy(data, chain->buffer + chain->misalign, chain->off);
- data += chain->off;
- datlen -= chain->off;
+ while (datlen && datlen >= chain->off - pos_in_chain) {
+ size_t copylen = chain->off - pos_in_chain;
+ memcpy(data,
+ chain->buffer + chain->misalign + pos_in_chain,
+ copylen);
+ data += copylen;
+ datlen -= copylen;
chain = chain->next;
+ pos_in_chain = 0;
EVUTIL_ASSERT(chain || datlen==0);
}
if (datlen) {
EVUTIL_ASSERT(chain);
- memcpy(data, chain->buffer + chain->misalign, datlen);
+ memcpy(data, chain->buffer + chain->misalign + pos_in_chain,
+ datlen);
}
result = nread;
@@ -1372,7 +1523,7 @@ evbuffer_strspn(
size_t i = ptr->_internal.pos_in_chain;
if (!chain)
- return -1;
+ return 0;
while (1) {
char *buffer = (char *)chain->buffer + chain->misalign;
@@ -1403,13 +1554,16 @@ evbuffer_strspn(
}
-static inline char
+static inline int
evbuffer_getchr(struct evbuffer_ptr *it)
{
struct evbuffer_chain *chain = it->_internal.chain;
size_t off = it->_internal.pos_in_chain;
- return chain->buffer[chain->misalign + off];
+ if (chain == NULL)
+ return -1;
+
+ return (unsigned char)chain->buffer[chain->misalign + off];
}
struct evbuffer_ptr
@@ -1421,6 +1575,14 @@ evbuffer_search_eol(struct evbuffer *buffer,
size_t extra_drain = 0;
int ok = 0;
+ /* Avoid locking in trivial edge cases */
+ if (start && start->_internal.chain == NULL) {
+ PTR_NOT_FOUND(&it);
+ if (eol_len_out)
+ *eol_len_out = extra_drain;
+ return it;
+ }
+
EVBUFFER_LOCK(buffer);
if (start) {
@@ -1447,29 +1609,37 @@ evbuffer_search_eol(struct evbuffer *buffer,
extra_drain = 2;
break;
}
- case EVBUFFER_EOL_CRLF:
- while (1) {
- if (evbuffer_find_eol_char(&it) < 0)
- goto done;
- if (evbuffer_getchr(&it) == '\n') {
- extra_drain = 1;
- break;
- } else if (!evbuffer_ptr_memcmp(
- buffer, &it, "\r\n", 2)) {
- extra_drain = 2;
- break;
- } else {
- if (evbuffer_ptr_set(buffer, &it, 1,
- EVBUFFER_PTR_ADD)<0)
- goto done;
- }
+ case EVBUFFER_EOL_CRLF: {
+ ev_ssize_t start_pos = it.pos;
+ /* Look for a LF ... */
+ if (evbuffer_strchr(&it, '\n') < 0)
+ goto done;
+ extra_drain = 1;
+ /* ... optionally preceeded by a CR. */
+ if (it.pos == start_pos)
+ break; /* If the first character is \n, don't back up */
+ /* This potentially does an extra linear walk over the first
+ * few chains. Probably, that's not too expensive unless you
+ * have a really pathological setup. */
+ memcpy(&it2, &it, sizeof(it));
+ if (evbuffer_ptr_subtract(buffer, &it2, 1)<0)
+ break;
+ if (evbuffer_getchr(&it2) == '\r') {
+ memcpy(&it, &it2, sizeof(it));
+ extra_drain = 2;
}
break;
+ }
case EVBUFFER_EOL_LF:
if (evbuffer_strchr(&it, '\n') < 0)
goto done;
extra_drain = 1;
break;
+ case EVBUFFER_EOL_NUL:
+ if (evbuffer_strchr(&it, '\0') < 0)
+ goto done;
+ extra_drain = 1;
+ break;
default:
goto done;
}
@@ -1478,9 +1648,8 @@ evbuffer_search_eol(struct evbuffer *buffer,
done:
EVBUFFER_UNLOCK(buffer);
- if (!ok) {
- it.pos = -1;
- }
+ if (!ok)
+ PTR_NOT_FOUND(&it);
if (eol_len_out)
*eol_len_out = extra_drain;
@@ -1949,7 +2118,7 @@ evbuffer_expand(struct evbuffer *buf, size_t datlen)
* Reads data from a file descriptor into a buffer.
*/
-#if defined(_EVENT_HAVE_SYS_UIO_H) || defined(WIN32)
+#if defined(_EVENT_HAVE_SYS_UIO_H) || defined(_WIN32)
#define USE_IOVEC_IMPL
#endif
@@ -2037,7 +2206,7 @@ _evbuffer_read_setup_vecs(struct evbuffer *buf, ev_ssize_t howmuch,
static int
get_n_bytes_readable_on_socket(evutil_socket_t fd)
{
-#if defined(FIONREAD) && defined(WIN32)
+#if defined(FIONREAD) && defined(_WIN32)
unsigned long lng = EVBUFFER_MAX_READ;
if (ioctlsocket(fd, FIONREAD, &lng) < 0)
return -1;
@@ -2103,7 +2272,7 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
WSABUF_FROM_EVBUFFER_IOV(&vecs[i], &ev_vecs[i]);
#endif
-#ifdef WIN32
+#ifdef _WIN32
{
DWORD bytesRead;
DWORD flags=0;
@@ -2134,7 +2303,7 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch)
/* We can append new data at this point */
p = chain->buffer + chain->misalign + chain->off;
-#ifndef WIN32
+#ifndef _WIN32
n = read(fd, p, howmuch);
#else
n = recv(fd, p, howmuch, 0);
@@ -2179,56 +2348,6 @@ done:
return result;
}
-#ifdef WIN32
-static int
-evbuffer_readfile(struct evbuffer *buf, evutil_socket_t fd, ev_ssize_t howmuch)
-{
- int result;
- int nchains, n;
- struct evbuffer_iovec v[2];
-
- EVBUFFER_LOCK(buf);
-
- if (buf->freeze_end) {
- result = -1;
- goto done;
- }
-
- if (howmuch < 0)
- howmuch = 16384;
-
-
- /* XXX we _will_ waste some space here if there is any space left
- * over on buf->last. */
- nchains = evbuffer_reserve_space(buf, howmuch, v, 2);
- if (nchains < 1 || nchains > 2) {
- result = -1;
- goto done;
- }
- n = read((int)fd, v[0].iov_base, (unsigned int)v[0].iov_len);
- if (n <= 0) {
- result = n;
- goto done;
- }
- v[0].iov_len = (IOV_LEN_TYPE) n; /* XXXX another problem with big n.*/
- if (nchains > 1) {
- n = read((int)fd, v[1].iov_base, (unsigned int)v[1].iov_len);
- if (n <= 0) {
- result = (unsigned long) v[0].iov_len;
- evbuffer_commit_space(buf, v, 1);
- goto done;
- }
- v[1].iov_len = n;
- }
- evbuffer_commit_space(buf, v, nchains);
-
- result = n;
-done:
- EVBUFFER_UNLOCK(buf);
- return result;
-}
-#endif
-
#ifdef USE_IOVEC_IMPL
static inline int
evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
@@ -2263,7 +2382,7 @@ evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
}
chain = chain->next;
}
-#ifdef WIN32
+#ifdef _WIN32
{
DWORD bytesSent;
if (WSASend(fd, iov, i, &bytesSent, 0, NULL, NULL))
@@ -2280,37 +2399,39 @@ evbuffer_write_iovec(struct evbuffer *buffer, evutil_socket_t fd,
#ifdef USE_SENDFILE
static inline int
-evbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t fd,
+evbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t dest_fd,
ev_ssize_t howmuch)
{
struct evbuffer_chain *chain = buffer->first;
- struct evbuffer_chain_fd *info =
- EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
+ struct evbuffer_chain_file_segment *info =
+ EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_file_segment,
+ chain);
+ const int source_fd = info->segment->fd;
#if defined(SENDFILE_IS_MACOSX) || defined(SENDFILE_IS_FREEBSD)
int res;
- off_t len = chain->off;
+ ev_off_t len = chain->off;
#elif defined(SENDFILE_IS_LINUX) || defined(SENDFILE_IS_SOLARIS)
ev_ssize_t res;
- off_t offset = chain->misalign;
+ ev_off_t offset = chain->misalign;
#endif
ASSERT_EVBUFFER_LOCKED(buffer);
#if defined(SENDFILE_IS_MACOSX)
- res = sendfile(info->fd, fd, chain->misalign, &len, NULL, 0);
+ res = sendfile(source_fd, dest_fd, chain->misalign, &len, NULL, 0);
if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno))
return (-1);
return (len);
#elif defined(SENDFILE_IS_FREEBSD)
- res = sendfile(info->fd, fd, chain->misalign, chain->off, NULL, &len, 0);
+ res = sendfile(source_fd, dest_fd, chain->misalign, chain->off, NULL, &len, 0);
if (res == -1 && !EVUTIL_ERR_RW_RETRIABLE(errno))
return (-1);
return (len);
#elif defined(SENDFILE_IS_LINUX)
/* TODO(niels): implement splice */
- res = sendfile(fd, info->fd, &offset, chain->off);
+ res = sendfile(dest_fd, source_fd, &offset, chain->off);
if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) {
/* if this is EAGAIN or EINTR return 0; otherwise, -1 */
return (0);
@@ -2319,7 +2440,7 @@ evbuffer_write_sendfile(struct evbuffer *buffer, evutil_socket_t fd,
#elif defined(SENDFILE_IS_SOLARIS)
{
const off_t offset_orig = offset;
- res = sendfile(fd, info->fd, &offset, chain->off);
+ res = sendfile(dest_fd, source_fd, &offset, chain->off);
if (res == -1 && EVUTIL_ERR_RW_RETRIABLE(errno)) {
if (offset - offset_orig)
return offset - offset_orig;
@@ -2357,7 +2478,7 @@ evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
#endif
#ifdef USE_IOVEC_IMPL
n = evbuffer_write_iovec(buffer, fd, howmuch);
-#elif defined(WIN32)
+#elif defined(_WIN32)
/* XXX(nickm) Don't disable this code until we know if
* the WSARecv code above works. */
void *p = evbuffer_pullup(buffer, howmuch);
@@ -2405,12 +2526,36 @@ evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len)
return search;
}
+/* Subract <b>howfar</b> from the position of <b>pos</b> within
+ * <b>buf</b>. Returns 0 on success, -1 on failure.
+ *
+ * This isn't exposed yet, because of potential inefficiency issues.
+ * Maybe it should be. */
+static int
+evbuffer_ptr_subtract(struct evbuffer *buf, struct evbuffer_ptr *pos,
+ size_t howfar)
+{
+ if (howfar > (size_t)pos->pos)
+ return -1;
+ if (pos->_internal.chain && howfar <= pos->_internal.pos_in_chain) {
+ pos->_internal.pos_in_chain -= howfar;
+ pos->pos -= howfar;
+ return 0;
+ } else {
+ const size_t newpos = pos->pos - howfar;
+ /* Here's the inefficient part: it walks over the
+ * chains until we hit newpos. */
+ return evbuffer_ptr_set(buf, pos, newpos, EVBUFFER_PTR_SET);
+ }
+}
+
int
evbuffer_ptr_set(struct evbuffer *buf, struct evbuffer_ptr *pos,
size_t position, enum evbuffer_ptr_how how)
{
size_t left = position;
struct evbuffer_chain *chain = NULL;
+ int result = 0;
EVBUFFER_LOCK(buf);
@@ -2437,14 +2582,18 @@ evbuffer_ptr_set(struct evbuffer *buf, struct evbuffer_ptr *pos,
if (chain) {
pos->_internal.chain = chain;
pos->_internal.pos_in_chain = position + left;
- } else {
+ } else if (left == 0) {
+ /* The first byte in the (nonexistent) chain after the last chain */
pos->_internal.chain = NULL;
- pos->pos = -1;
+ pos->_internal.pos_in_chain = 0;
+ } else {
+ PTR_NOT_FOUND(pos);
+ result = -1;
}
EVBUFFER_UNLOCK(buf);
- return chain != NULL ? 0 : -1;
+ return result;
}
/**
@@ -2549,8 +2698,7 @@ evbuffer_search_range(struct evbuffer *buffer, const char *what, size_t len, con
}
not_found:
- pos.pos = -1;
- pos._internal.chain = NULL;
+ PTR_NOT_FOUND(&pos);
done:
EVBUFFER_UNLOCK(buffer);
return pos;
@@ -2565,6 +2713,10 @@ evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len,
int idx = 0;
ev_ssize_t len_so_far = 0;
+ /* Avoid locking in trivial edge cases */
+ if (start_at && start_at->_internal.chain == NULL)
+ return 0;
+
EVBUFFER_LOCK(buffer);
if (start_at) {
@@ -2722,169 +2874,323 @@ done:
return result;
}
-/* TODO(niels): maybe we don't want to own the fd, however, in that
- * case, we should dup it - dup is cheap. Perhaps, we should use a
- * callback instead?
- */
/* TODO(niels): we may want to add to automagically convert to mmap, in
* case evbuffer_remove() or evbuffer_pullup() are being used.
*/
-int
-evbuffer_add_file(struct evbuffer *outbuf, int fd,
- ev_off_t offset, ev_off_t length)
+struct evbuffer_file_segment *
+evbuffer_file_segment_new(
+ int fd, ev_off_t offset, ev_off_t length, unsigned flags)
{
-#if defined(USE_SENDFILE) || defined(_EVENT_HAVE_MMAP)
- struct evbuffer_chain *chain;
- struct evbuffer_chain_fd *info;
+ struct evbuffer_file_segment *seg =
+ mm_calloc(sizeof(struct evbuffer_file_segment), 1);
+ if (!seg)
+ return NULL;
+ seg->refcnt = 1;
+ seg->fd = fd;
+ seg->flags = flags;
+ seg->file_offset = offset;
+
+#ifdef _WIN32
+#define lseek _lseeki64
+#define fstat _fstat
+#define stat _stat
#endif
+ if (length == -1) {
+ struct stat st;
+ if (fstat(fd, &st) < 0)
+ goto err;
+ length = st.st_size;
+ }
+ seg->length = length;
+
#if defined(USE_SENDFILE)
- int sendfile_okay = 1;
+ if (!(flags & EVBUF_FS_DISABLE_SENDFILE)) {
+ seg->can_sendfile = 1;
+ goto done;
+ }
#endif
- int ok = 1;
+
+ if (evbuffer_file_segment_materialize(seg)<0)
+ goto err;
#if defined(USE_SENDFILE)
- if (use_sendfile) {
- EVBUFFER_LOCK(outbuf);
- sendfile_okay = outbuf->flags & EVBUFFER_FLAG_DRAINS_TO_FD;
- EVBUFFER_UNLOCK(outbuf);
+done:
+#endif
+ if (!(flags & EVBUF_FS_DISABLE_LOCKING)) {
+ EVTHREAD_ALLOC_LOCK(seg->lock, 0);
}
+ return seg;
+err:
+ mm_free(seg);
+ return NULL;
+}
- if (use_sendfile && sendfile_okay) {
- chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd));
- if (chain == NULL) {
- event_warn("%s: out of memory", __func__);
- return (-1);
- }
-
- chain->flags |= EVBUFFER_SENDFILE | EVBUFFER_IMMUTABLE;
- chain->buffer = NULL; /* no reading possible */
- chain->buffer_len = length + offset;
- chain->off = length;
- chain->misalign = offset;
+/* DOCDOC */
+/* Requires lock */
+static int
+evbuffer_file_segment_materialize(struct evbuffer_file_segment *seg)
+{
+ const unsigned flags = seg->flags;
+ const int fd = seg->fd;
+ const ev_off_t length = seg->length;
+ const ev_off_t offset = seg->file_offset;
- info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
- info->fd = fd;
+ if (seg->contents)
+ return 0; /* already materialized */
- EVBUFFER_LOCK(outbuf);
- if (outbuf->freeze_end) {
- mm_free(chain);
- ok = 0;
- } else {
- outbuf->n_add_for_cb += length;
- evbuffer_chain_insert(outbuf, chain);
- }
- } else
-#endif
#if defined(_EVENT_HAVE_MMAP)
- if (use_mmap) {
- void *mapped = mmap(NULL, length + offset, PROT_READ,
+ if (!(flags & EVBUF_FS_DISABLE_MMAP)) {
+ off_t offset_rounded = 0, offset_leftover = 0;
+ void *mapped;
+ if (offset) {
+ /* mmap implementations don't generally like us
+ * to have an offset that isn't a round */
+#ifdef SC_PAGE_SIZE
+ long page_size = sysconf(SC_PAGE_SIZE);
+#elif defined(_SC_PAGE_SIZE)
+ long page_size = sysconf(_SC_PAGE_SIZE);
+#else
+ long page_size = 1;
+#endif
+ if (page_size == -1)
+ goto err;
+ offset_leftover = offset % page_size;
+ offset_rounded = offset - offset_leftover;
+ }
+ mapped = mmap(NULL, length + offset_leftover,
+ PROT_READ,
#ifdef MAP_NOCACHE
- MAP_NOCACHE |
+ MAP_NOCACHE | /* ??? */
#endif
#ifdef MAP_FILE
MAP_FILE |
#endif
MAP_PRIVATE,
- fd, 0);
- /* some mmap implementations require offset to be a multiple of
- * the page size. most users of this api, are likely to use 0
- * so mapping everything is not likely to be a problem.
- * TODO(niels): determine page size and round offset to that
- * page size to avoid mapping too much memory.
- */
+ fd, offset_rounded);
if (mapped == MAP_FAILED) {
event_warn("%s: mmap(%d, %d, %zu) failed",
__func__, fd, 0, (size_t)(offset + length));
- return (-1);
+ } else {
+ seg->mapping = mapped;
+ seg->contents = (char*)mapped+offset_leftover;
+ seg->mmap_offset = 0;
+ seg->is_mapping = 1;
+ goto done;
}
- chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_fd));
- if (chain == NULL) {
- event_warn("%s: out of memory", __func__);
- munmap(mapped, length);
- return (-1);
+ }
+#endif
+#ifdef _WIN32
+ if (!(flags & EVBUF_FS_DISABLE_MMAP)) {
+ long h = (long)_get_osfhandle(fd);
+ HANDLE m;
+ ev_uint64_t total_size = length+offset;
+ if (h == (long)INVALID_HANDLE_VALUE)
+ goto err;
+ m = CreateFileMapping((HANDLE)h, NULL, PAGE_READONLY,
+ (total_size >> 32), total_size & 0xfffffffful,
+ NULL);
+ if (m != INVALID_HANDLE_VALUE) { /* Does h leak? */
+ seg->mapping_handle = m;
+ seg->mmap_offset = offset;
+ seg->is_mapping = 1;
+ goto done;
+ }
+ }
+#endif
+ {
+ ev_off_t start_pos = lseek(fd, 0, SEEK_CUR), pos;
+ ev_off_t read_so_far = 0;
+ char *mem;
+ int e;
+ ev_ssize_t n = 0;
+ if (!(mem = mm_malloc(length)))
+ goto err;
+ if (start_pos < 0) {
+ mm_free(mem);
+ goto err;
+ }
+ if (lseek(fd, offset, SEEK_SET) < 0) {
+ mm_free(mem);
+ goto err;
+ }
+ while (read_so_far < length) {
+ n = read(fd, mem+read_so_far, length-read_so_far);
+ if (n <= 0)
+ break;
+ read_so_far += n;
}
- chain->flags |= EVBUFFER_MMAP | EVBUFFER_IMMUTABLE;
- chain->buffer = mapped;
- chain->buffer_len = length + offset;
- chain->off = length + offset;
-
- info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain);
- info->fd = fd;
+ e = errno;
+ pos = lseek(fd, start_pos, SEEK_SET);
+ if (n < 0 || (n == 0 && length > read_so_far)) {
+ mm_free(mem);
+ errno = e;
+ goto err;
+ } else if (pos < 0) {
+ mm_free(mem);
+ goto err;
+ }
- EVBUFFER_LOCK(outbuf);
- if (outbuf->freeze_end) {
- info->fd = -1;
- evbuffer_chain_free(chain);
- ok = 0;
- } else {
- outbuf->n_add_for_cb += length;
+ seg->contents = mem;
+ }
- evbuffer_chain_insert(outbuf, chain);
+done:
+ return 0;
+err:
+ return -1;
+}
- /* we need to subtract whatever we don't need */
- evbuffer_drain(outbuf, offset);
- }
- } else
+void
+evbuffer_file_segment_free(struct evbuffer_file_segment *seg)
+{
+ int refcnt;
+ EVLOCK_LOCK(seg->lock, 0);
+ refcnt = --seg->refcnt;
+ EVLOCK_UNLOCK(seg->lock, 0);
+ if (refcnt > 0)
+ return;
+ EVUTIL_ASSERT(refcnt == 0);
+
+ if (seg->is_mapping) {
+#ifdef _WIN32
+ CloseHandle(seg->mapping_handle);
+#elif defined (_EVENT_HAVE_MMAP)
+ if (munmap(seg->mapping, seg->length) == -1)
+ event_warn("%s: munmap failed", __func__);
#endif
- {
- /* the default implementation */
- struct evbuffer *tmp = evbuffer_new();
- ev_ssize_t read;
+ } else if (seg->contents) {
+ mm_free(seg->contents);
+ }
- if (tmp == NULL)
- return (-1);
+ if ((seg->flags & EVBUF_FS_CLOSE_ON_FREE) && seg->fd >= 0) {
+ close(seg->fd);
+ }
-#ifdef WIN32
-#define lseek _lseeki64
-#endif
- if (lseek(fd, offset, SEEK_SET) == -1) {
- evbuffer_free(tmp);
- return (-1);
- }
+ EVTHREAD_FREE_LOCK(seg->lock, 0);
+ mm_free(seg);
+}
- /* we add everything to a temporary buffer, so that we
- * can abort without side effects if the read fails.
- */
- while (length) {
- read = evbuffer_readfile(tmp, fd, (ev_ssize_t)length);
- if (read == -1) {
- evbuffer_free(tmp);
- return (-1);
- }
+int
+evbuffer_add_file_segment(struct evbuffer *buf,
+ struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length)
+{
+ struct evbuffer_chain *chain;
+ struct evbuffer_chain_file_segment *extra;
+ int can_use_sendfile = 0;
- length -= read;
+ EVBUFFER_LOCK(buf);
+ EVLOCK_LOCK(seg->lock, 0);
+ if (buf->flags & EVBUFFER_FLAG_DRAINS_TO_FD) {
+ can_use_sendfile = 1;
+ } else {
+ if (!seg->contents) {
+ if (evbuffer_file_segment_materialize(seg)<0) {
+ EVLOCK_UNLOCK(seg->lock, 0);
+ EVBUFFER_UNLOCK(buf);
+ return -1;
+ }
}
+ }
+ ++seg->refcnt;
+ EVLOCK_UNLOCK(seg->lock, 0);
- EVBUFFER_LOCK(outbuf);
- if (outbuf->freeze_end) {
- evbuffer_free(tmp);
- ok = 0;
- } else {
- evbuffer_add_buffer(outbuf, tmp);
- evbuffer_free(tmp);
+ if (buf->freeze_end)
+ goto err;
-#ifdef WIN32
-#define close _close
-#endif
- close(fd);
+ if (length < 0) {
+ if (offset > seg->length)
+ goto err;
+ length = seg->length - offset;
+ }
+
+ /* Can we actually add this? */
+ if (offset+length > seg->length)
+ goto err;
+
+ chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_file_segment));
+ if (!chain)
+ goto err;
+ extra = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_file_segment, chain);
+
+ chain->flags |= EVBUFFER_IMMUTABLE|EVBUFFER_FILESEGMENT;
+ if (can_use_sendfile && seg->can_sendfile) {
+ chain->flags |= EVBUFFER_SENDFILE;
+ chain->misalign = seg->file_offset + offset;
+ chain->off = length;
+ chain->buffer_len = chain->misalign + length;
+ } else if (seg->is_mapping) {
+#ifdef _WIN32
+ ev_uint64_t total_offset = seg->mmap_offset+offset;
+ ev_uint64_t offset_rounded=0, offset_remaining=0;
+ LPVOID data;
+ if (total_offset) {
+ SYSTEM_INFO si;
+ memset(&si, 0, sizeof(si)); /* cargo cult */
+ GetSystemInfo(&si);
+ offset_remaining = total_offset % si.dwAllocationGranularity;
+ offset_rounded = total_offset - offset_remaining;
}
+ data = MapViewOfFile(
+ seg->mapping_handle,
+ FILE_MAP_READ,
+ offset_rounded >> 32,
+ offset_rounded & 0xfffffffful,
+ length + offset_remaining);
+ if (data == NULL) {
+ mm_free(chain);
+ goto err;
+ }
+ chain->buffer = (unsigned char*) data;
+ chain->buffer_len = length+offset_remaining;
+ chain->misalign = offset_remaining;
+ chain->off = length;
+#else
+ chain->buffer = (unsigned char*)(seg->contents + offset);
+ chain->buffer_len = length;
+ chain->off = length;
+#endif
+ } else {
+ chain->buffer = (unsigned char*)(seg->contents + offset);
+ chain->buffer_len = length;
+ chain->off = length;
}
- if (ok)
- evbuffer_invoke_callbacks(outbuf);
- EVBUFFER_UNLOCK(outbuf);
+ extra->segment = seg;
+ buf->n_add_for_cb += length;
+ evbuffer_chain_insert(buf, chain);
+
+ evbuffer_invoke_callbacks(buf);
+
+ EVBUFFER_UNLOCK(buf);
- return ok ? 0 : -1;
+ return 0;
+err:
+ EVBUFFER_UNLOCK(buf);
+ evbuffer_file_segment_free(seg);
+ return -1;
}
+int
+evbuffer_add_file(struct evbuffer *buf, int fd, ev_off_t offset, ev_off_t length)
+{
+ struct evbuffer_file_segment *seg;
+ unsigned flags = EVBUF_FS_CLOSE_ON_FREE;
+ int r;
+
+ seg = evbuffer_file_segment_new(fd, offset, length, flags);
+ if (!seg)
+ return -1;
+ r = evbuffer_add_file_segment(buf, seg, 0, length);
+ evbuffer_file_segment_free(seg);
+ return r;
+}
void
evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg)
{
EVBUFFER_LOCK(buffer);
- if (!TAILQ_EMPTY(&buffer->callbacks))
+ if (!LIST_EMPTY(&buffer->callbacks))
evbuffer_remove_all_callbacks(buffer);
if (cb) {
@@ -2906,7 +3212,7 @@ evbuffer_add_cb(struct evbuffer *buffer, evbuffer_cb_func cb, void *cbarg)
e->cb.cb_func = cb;
e->cbarg = cbarg;
e->flags = EVBUFFER_CB_ENABLED;
- TAILQ_INSERT_HEAD(&buffer->callbacks, e, next);
+ LIST_INSERT_HEAD(&buffer->callbacks, e, next);
EVBUFFER_UNLOCK(buffer);
return e;
}
@@ -2916,7 +3222,7 @@ evbuffer_remove_cb_entry(struct evbuffer *buffer,
struct evbuffer_cb_entry *ent)
{
EVBUFFER_LOCK(buffer);
- TAILQ_REMOVE(&buffer->callbacks, ent, next);
+ LIST_REMOVE(ent, next);
EVBUFFER_UNLOCK(buffer);
mm_free(ent);
return 0;
@@ -2928,7 +3234,7 @@ evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb_func cb, void *cbarg)
struct evbuffer_cb_entry *cbent;
int result = -1;
EVBUFFER_LOCK(buffer);
- TAILQ_FOREACH(cbent, &buffer->callbacks, next) {
+ LIST_FOREACH(cbent, &buffer->callbacks, next) {
if (cb == cbent->cb.cb_func && cbarg == cbent->cbarg) {
result = evbuffer_remove_cb_entry(buffer, cbent);
goto done;
@@ -3013,50 +3319,3 @@ evbuffer_cb_unsuspend(struct evbuffer *buffer, struct evbuffer_cb_entry *cb)
}
#endif
-/* These hooks are exposed so that the unit tests can temporarily disable
- * sendfile support in order to test mmap, or both to test linear
- * access. Don't use it; if we need to add a way to disable sendfile support
- * in the future, it will probably be via an alternate version of
- * evbuffer_add_file() with a 'flags' argument.
- */
-int _evbuffer_testing_use_sendfile(void);
-int _evbuffer_testing_use_mmap(void);
-int _evbuffer_testing_use_linear_file_access(void);
-
-int
-_evbuffer_testing_use_sendfile(void)
-{
- int ok = 0;
-#ifdef USE_SENDFILE
- use_sendfile = 1;
- ok = 1;
-#endif
-#ifdef _EVENT_HAVE_MMAP
- use_mmap = 0;
-#endif
- return ok;
-}
-int
-_evbuffer_testing_use_mmap(void)
-{
- int ok = 0;
-#ifdef USE_SENDFILE
- use_sendfile = 0;
-#endif
-#ifdef _EVENT_HAVE_MMAP
- use_mmap = 1;
- ok = 1;
-#endif
- return ok;
-}
-int
-_evbuffer_testing_use_linear_file_access(void)
-{
-#ifdef USE_SENDFILE
- use_sendfile = 0;
-#endif
-#ifdef _EVENT_HAVE_MMAP
- use_mmap = 0;
-#endif
- return 1;
-}