o sendfile, mmap and memory reference support for evbuffers.
o New evutil_make_listen_socket_reuseable() to abstract SO_REUSEADDR.
o New bind-to option to allow DNS clients to bind to an arbitrary port for outgoing requests.
+ o evbuffers can now be "frozen" to prevent operations at one or both ends.
+
Changes in 1.4.0:
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+ if (buf->freeze_end)
+ goto done;
+
if (evbuffer_expand(buf, size) == -1)
goto done;
int result = -1;
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+ if (buf->freeze_end) {
+ goto done;
+ }
+
chain = buf->last;
if (chain == NULL ||
evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
size_t in_total_len, out_total_len;
+ int result = 0;
EVBUFFER_LOCK2(inbuf, outbuf);
-
in_total_len = inbuf->total_len;
out_total_len = outbuf->total_len;
if (in_total_len == 0 || outbuf == inbuf)
goto done;
+ if (outbuf->freeze_end || inbuf->freeze_start) {
+ result = -1;
+ goto done;
+ }
+
if (out_total_len == 0) {
COPY_CHAIN(outbuf, inbuf);
} else {
done:
EVBUFFER_UNLOCK2(inbuf, outbuf);
- return (0);
+ return result;
}
-void
+int
evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf)
{
size_t in_total_len, out_total_len;
+ int result = 0;
EVBUFFER_LOCK2(inbuf, outbuf);
if (!in_total_len || inbuf == outbuf)
goto done;
+ if (outbuf->freeze_start || inbuf->freeze_start) {
+ result = -1;
+ goto done;
+ }
+
if (out_total_len == 0) {
COPY_CHAIN(outbuf, inbuf);
} else {
evbuffer_invoke_callbacks(outbuf);
done:
EVBUFFER_UNLOCK2(inbuf, outbuf);
+ return result;
}
-void
+int
evbuffer_drain(struct evbuffer *buf, size_t len)
{
struct evbuffer_chain *chain, *next;
size_t old_len;
+ int result = 0;
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
old_len = buf->total_len;
if (old_len == 0)
goto done;
+ if (buf->freeze_start) {
+ result = -1;
+ goto done;
+ }
+
/* TODO(nickm) when we drain the last byte from a chain, we
* should not unlink or free it if it is pinned. */
done:
EVBUFFER_UNLOCK(buf, EVTHREAD_WRITE);
+ return result;
}
/* Reads data from an event buffer and drains the bytes read */
if (datlen == 0)
goto done;
+ if (buf->freeze_start) {
+ result = -1;
+ goto done;
+ }
+
nread = datlen;
while (datlen && datlen >= chain->off) {
goto done;
}
+ if (dst->freeze_end || src->freeze_start) {
+ result = -1;
+ goto done;
+ }
+
/* short-cut if there is no more data buffered */
if (datlen >= src->total_len) {
datlen = src->total_len;
EVBUFFER_LOCK(buffer, EVTHREAD_WRITE);
+ if (buffer->freeze_start) {
+ goto done;
+ }
+
it.chain = buffer->first;
it.off = 0;
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+ if (buf->freeze_end) {
+ goto done;
+ }
+
chain = buf->last;
/* If there are no chains allocated for this buffer, allocate one
int result = -1;
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+
+ if (buf->freeze_start) {
+ goto done;
+ }
+
chain = buf->first;
if (chain == NULL) {
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+ if (buf->freeze_end) {
+ result = -1;
+ goto done;
+ }
+
#if defined(FIONREAD)
#ifdef WIN32
if (ioctlsocket(fd, FIONREAD, &lng) == -1 || (n=lng) == 0) {
evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
ssize_t howmuch)
{
- int n;
+ int n = -1;
EVBUFFER_LOCK(buffer, EVTHREAD_WRITE);
+ if (buffer->freeze_start) {
+ goto done;
+ }
+
if (howmuch < 0)
howmuch = buffer->total_len;
if (n > 0)
evbuffer_drain(buffer, n);
+done:
EVBUFFER_UNLOCK(buffer, EVTHREAD_WRITE);
return (n);
}
search = NULL;
} else {
search = evbuffer_pullup(buffer, ptr.pos + len);
- search += ptr.pos;
+ if (search)
+ search += ptr.pos;
}
EVBUFFER_UNLOCK(buffer,EVTHREAD_WRITE);
return search;
EVBUFFER_LOCK(buf, EVTHREAD_WRITE);
+ if (buf->freeze_end) {
+ goto done;
+ }
+
/* make sure that at least some space is available */
if (evbuffer_expand(buf, 64) == -1)
goto done;
const void *data, size_t datlen,
void (*cleanupfn)(void *extra), void *extra)
{
- struct evbuffer_chain *chain =
- evbuffer_chain_new(sizeof(struct evbuffer_chain_reference));
+ struct evbuffer_chain *chain;
struct evbuffer_chain_reference *info;
- if (chain == NULL)
- return (-1);
+ int result = -1;
+ chain = evbuffer_chain_new(sizeof(struct evbuffer_chain_reference));
+ if (!chain)
+ return (-1);
chain->flags |= EVBUFFER_REFERENCE | EVBUFFER_IMMUTABLE;
chain->buffer = (u_char *)data;
chain->buffer_len = datlen;
info->extra = extra;
EVBUFFER_LOCK(outbuf, EVTHREAD_WRITE);
+ if (outbuf->freeze_end) {
+ /* don't call chain_free; we do not want to actually invoke
+ * the cleanup function */
+ mm_free(chain);
+ goto done;
+ }
evbuffer_chain_insert(outbuf, chain);
outbuf->n_add_for_cb += datlen;
evbuffer_invoke_callbacks(outbuf);
+
+ result = 0;
+done:
EVBUFFER_UNLOCK(outbuf, EVTHREAD_WRITE);
- return (0);
+ return result;
}
/* TODO(niels): maybe we don't want to own the fd, however, in that
struct evbuffer_chain *chain;
struct evbuffer_chain_fd *info;
#endif
+ int ok = 1;
#if defined(USE_SENDFILE)
if (use_sendfile) {
info->fd = fd;
EVBUFFER_LOCK(outbuf, EVTHREAD_WRITE);
- outbuf->n_add_for_cb += length;
- evbuffer_chain_insert(outbuf, chain);
+ 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)
info->fd = fd;
EVBUFFER_LOCK(outbuf, EVTHREAD_WRITE);
- outbuf->n_add_for_cb += length;
+ if (outbuf->freeze_end) {
+ info->fd = -1;
+ evbuffer_chain_free(chain);
+ ok = 0;
+ } else {
+ outbuf->n_add_for_cb += length;
- evbuffer_chain_insert(outbuf, chain);
+ evbuffer_chain_insert(outbuf, chain);
- /* we need to subtract whatever we don't need */
- evbuffer_drain(outbuf, offset);
+ /* we need to subtract whatever we don't need */
+ evbuffer_drain(outbuf, offset);
+ }
} else
#endif
{
}
EVBUFFER_LOCK(outbuf, EVTHREAD_WRITE);
- evbuffer_add_buffer(outbuf, tmp);
- evbuffer_free(tmp);
+ if (outbuf->freeze_end) {
+ evbuffer_free(tmp);
+ ok = 0;
+ } else {
+ evbuffer_add_buffer(outbuf, tmp);
+ evbuffer_free(tmp);
- close(fd);
+ close(fd);
+ }
}
- evbuffer_invoke_callbacks(outbuf);
+ if (ok)
+ evbuffer_invoke_callbacks(outbuf);
EVBUFFER_UNLOCK(outbuf, EVTHREAD_WRITE);
- return (0);
+ return ok ? 0 : -1;
}
return 0;
}
+int
+evbuffer_freeze(struct evbuffer *buffer, int start)
+{
+ EVBUFFER_LOCK(buffer, EVTHREAD_WRITE);
+ if (start)
+ buffer->freeze_start = 1;
+ else
+ buffer->freeze_end = 1;
+ EVBUFFER_UNLOCK(buffer, EVTHREAD_WRITE);
+ return 0;
+}
+
+int
+evbuffer_unfreeze(struct evbuffer *buffer, int start)
+{
+ EVBUFFER_LOCK(buffer, EVTHREAD_WRITE);
+ if (start)
+ buffer->freeze_start = 0;
+ else
+ buffer->freeze_end = 0;
+ EVBUFFER_UNLOCK(buffer, EVTHREAD_WRITE);
+ return 0;
+}
+
#if 0
void
evbuffer_cb_suspend(struct evbuffer *buffer, struct evbuffer_cb_entry *cb)
void *lock;
#endif
unsigned own_lock : 1;
+ unsigned freeze_start : 1;
+ unsigned freeze_end : 1;
int lock_count;
@param buf the event buffer to be appended to
@param data pointer to the beginning of the data buffer
@param datlen the number of bytes to be copied from the data buffer
+ @return 0 on success, -1 on failure.
*/
int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
@param buf the event buffer to be read from
@param data the destination buffer to store the result
@param datlen the maximum size of the destination buffer
- @return the number of bytes read
+ @return the number of bytes read, or -1 if we can't drain the buffer.
*/
int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);
@param buf the evbuffer to be drained
@param len the number of bytes to drain from the beginning of the buffer
+ @return 0 on success, -1 on failure.
*/
-void evbuffer_drain(struct evbuffer *buf, size_t len);
+int evbuffer_drain(struct evbuffer *buf, size_t len);
/**
@param dst the evbuffer to which to prepend data
@param src the evbuffer to prepend; it will be emptied as a result
+ @return 0 if successful, or -1 otherwise
*/
-void evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);
+int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);
/* XXX missing APIs:
#define EVBUFFER_LENGTH(x) evbuffer_get_length(x)
#define EVBUFFER_DATA(x) evbuffer_pullup(x, -1)
+/**
+ Prevent calls that modify an evbuffer from succeeding. A buffer may
+ frozen at the front, at the back, or at both the front and the back.
+
+ If the front of a buffer is frozen, operations that drain data from
+ the front of the buffer, or that prepend data to the buffer, will
+ fail until it is unfrozen. If the back a buffer is frozen, operations
+ that append data from the buffer will fail until it is unfrozen.
+
+ @param buf The buffer to freeze
+ @param at_front If true, we freeze the front of the buffer. If false,
+ we freeze the back.
+ @return 0 on success, -1 on failure.
+*/
+int evbuffer_freeze(struct evbuffer *buf, int at_front);
+/**
+ Re-enable calls that modify an evbuffer.
+
+ @param buf The buffer to un-freeze
+ @param at_front If true, we unfreeze the front of the buffer. If false,
+ we unfreeze the back.
+ @return 0 on success, -1 on failure.
+ */
+int evbuffer_unfreeze(struct evbuffer *buf, int at_front);
+
#ifdef __cplusplus
}
#endif
}
+/* Check whether evbuffer freezing works right. This is called twice,
+ once with the argument "start" and once with the argument "end".
+ When we test "start", we freeze the start of an evbuffer and make sure
+ that modifying the start of the buffer doesn't work. When we test
+ "end", we freeze the end of an evbuffer and make sure that modifying
+ the end of the buffer doesn't work.
+ */
+static void
+test_evbuffer_freeze(void *ptr)
+{
+ struct evbuffer *buf = NULL, *tmp_buf=NULL;
+ const char string[] = /* Year's End, Richard Wilbur */
+ "I've known the wind by water banks to shake\n"
+ "The late leaves down, which frozen where they fell\n"
+ "And held in ice as dancers in a spell\n"
+ "Fluttered all winter long into a lake...";
+ const int start = !strcmp(ptr, "start");
+ char *cp;
+ char charbuf[128];
+ int r;
+ size_t orig_length;
+
+ if (!start)
+ tt_str_op(ptr, ==, "end");
+
+ buf = evbuffer_new();
+ tmp_buf = evbuffer_new();
+ tt_assert(tmp_buf);
+
+ evbuffer_add(buf, string, strlen(string));
+ evbuffer_freeze(buf, start); /* Freeze the start or the end.*/
+
+#define FREEZE_EQ(a, startcase, endcase) \
+ do { \
+ if (start) { \
+ tt_int_op((a), ==, (startcase)); \
+ } else { \
+ tt_int_op((a), ==, (endcase)); \
+ } \
+ } while (0)
+
+
+ orig_length = evbuffer_get_length(buf);
+
+ /* These functions all manipulate the end of buf. */
+ r = evbuffer_add(buf, "abc", 0);
+ FREEZE_EQ(r, 0, -1);
+ cp = (char*)evbuffer_reserve_space(buf, 10);
+ FREEZE_EQ(cp==NULL, 0, 1);
+ if (cp)
+ memset(cp, 'X', 10);
+ r = evbuffer_commit_space(buf, 10);
+ FREEZE_EQ(r, 0, -1);
+ r = evbuffer_add_reference(buf, string, 5, NULL, NULL);
+ FREEZE_EQ(r, 0, -1);
+ r = evbuffer_add_printf(buf, "Hello %s", "world");
+ FREEZE_EQ(r, 11, -1);
+ // TODO: test add_buffer, add_file, read
+
+ if (!start)
+ tt_int_op(orig_length, ==, evbuffer_get_length(buf));
+
+ orig_length = evbuffer_get_length(buf);
+
+ /* These functions all manipulate the start of buf. */
+ r = evbuffer_remove(buf, charbuf, 1);
+ FREEZE_EQ(r, -1, 1);
+ r = evbuffer_drain(buf, 3);
+ FREEZE_EQ(r, -1, 0);
+ r = evbuffer_prepend(buf, "dummy", 5);
+ FREEZE_EQ(r, -1, 0);
+ cp = evbuffer_readln(buf, NULL, EVBUFFER_EOL_LF);
+ FREEZE_EQ(cp==NULL, 1, 0);
+ if (cp)
+ free(cp);
+ // TODO: Test remove_buffer, add_buffer, write, prepend_buffer
+
+ if (start)
+ tt_int_op(orig_length, ==, evbuffer_get_length(buf));
+
+end:
+ if (buf)
+ evbuffer_free(buf);
+
+ if (tmp_buf)
+ evbuffer_free(tmp_buf);
+}
+
+static void *
+setup_passthrough(const struct testcase_t *testcase)
+{
+ return testcase->setup_data;
+}
+static int
+cleanup_passthrough(const struct testcase_t *testcase, void *ptr)
+{
+ (void) ptr;
+ return 1;
+}
+
+static const struct testcase_setup_t nil_setup = {
+ setup_passthrough,
+ cleanup_passthrough
+};
struct testcase_t evbuffer_testcases[] = {
{ "evbuffer", test_evbuffer, 0, NULL, NULL },
{ "callbacks", test_evbuffer_callbacks, 0, NULL, NULL },
{ "add_reference", test_evbuffer_add_reference, 0, NULL, NULL },
{ "prepend", test_evbuffer_prepend, 0, NULL, NULL },
+ { "freeze_start", test_evbuffer_freeze, 0, &nil_setup, (void*)"start" },
+ { "freeze_end", test_evbuffer_freeze, 0, &nil_setup, (void*)"end" },
#ifndef WIN32
/* TODO: need a temp file implementation for Windows */
{ "add_file", test_evbuffer_add_file, 0, NULL, NULL },