From: Nick Mathewson Date: Wed, 8 Apr 2009 03:04:39 +0000 (+0000) Subject: Add freeze support to evbuffers. X-Git-Tag: release-2.0.1-alpha~46 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=747331d164ea4839a89e168f9f712159509984db;p=libevent Add freeze support to evbuffers. From the documentation: 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. We'll use this to ensure correctness on an evbuffer when we're waiting for an overlapped IO call to finish. svn:r1143 --- diff --git a/ChangeLog b/ChangeLog index 3192bbff..01f0bb8b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -143,6 +143,8 @@ Changes in current version: 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. diff --git a/buffer.c b/buffer.c index c1656cfb..2b42c384 100644 --- a/buffer.c +++ b/buffer.c @@ -404,6 +404,9 @@ evbuffer_reserve_space(struct evbuffer *buf, size_t size) EVBUFFER_LOCK(buf, EVTHREAD_WRITE); + if (buf->freeze_end) + goto done; + if (evbuffer_expand(buf, size) == -1) goto done; @@ -424,6 +427,10 @@ evbuffer_commit_space(struct evbuffer *buf, size_t size) int result = -1; EVBUFFER_LOCK(buf, EVTHREAD_WRITE); + if (buf->freeze_end) { + goto done; + } + chain = buf->last; if (chain == NULL || @@ -481,15 +488,20 @@ int 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 { @@ -506,13 +518,14 @@ evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) 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); @@ -522,6 +535,11 @@ evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) 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 { @@ -537,13 +555,15 @@ evbuffer_prepend_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf) 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; @@ -551,6 +571,11 @@ evbuffer_drain(struct evbuffer *buf, size_t 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. */ @@ -586,6 +611,7 @@ evbuffer_drain(struct evbuffer *buf, size_t len) done: EVBUFFER_UNLOCK(buf, EVTHREAD_WRITE); + return result; } /* Reads data from an event buffer and drains the bytes read */ @@ -609,6 +635,11 @@ evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen) if (datlen == 0) goto done; + if (buf->freeze_start) { + result = -1; + goto done; + } + nread = datlen; while (datlen && datlen >= chain->off) { @@ -667,6 +698,11 @@ evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst, 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; @@ -939,6 +975,10 @@ evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, EVBUFFER_LOCK(buffer, EVTHREAD_WRITE); + if (buffer->freeze_start) { + goto done; + } + it.chain = buffer->first; it.off = 0; @@ -1019,6 +1059,10 @@ evbuffer_add(struct evbuffer *buf, const void *data_in, size_t datlen) 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 @@ -1095,6 +1139,11 @@ evbuffer_prepend(struct evbuffer *buf, const void *data, size_t datlen) int result = -1; EVBUFFER_LOCK(buf, EVTHREAD_WRITE); + + if (buf->freeze_start) { + goto done; + } + chain = buf->first; if (chain == NULL) { @@ -1344,6 +1393,11 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) 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) { @@ -1567,10 +1621,14 @@ int 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; @@ -1597,6 +1655,7 @@ evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd, if (n > 0) evbuffer_drain(buffer, n); +done: EVBUFFER_UNLOCK(buffer, EVTHREAD_WRITE); return (n); } @@ -1620,7 +1679,8 @@ evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len) 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; @@ -1772,6 +1832,10 @@ evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap) 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; @@ -1831,12 +1895,13 @@ evbuffer_add_reference(struct evbuffer *outbuf, 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; @@ -1847,13 +1912,22 @@ evbuffer_add_reference(struct evbuffer *outbuf, 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 @@ -1871,6 +1945,7 @@ evbuffer_add_file(struct evbuffer *outbuf, int fd, struct evbuffer_chain *chain; struct evbuffer_chain_fd *info; #endif + int ok = 1; #if defined(USE_SENDFILE) if (use_sendfile) { @@ -1890,8 +1965,13 @@ evbuffer_add_file(struct evbuffer *outbuf, int fd, 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) @@ -1929,12 +2009,18 @@ evbuffer_add_file(struct evbuffer *outbuf, int fd, 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 { @@ -1964,16 +2050,22 @@ evbuffer_add_file(struct evbuffer *outbuf, int fd, } 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; } @@ -2047,6 +2139,30 @@ evbuffer_cb_set_flags(struct evbuffer *buffer, 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) diff --git a/evbuffer-internal.h b/evbuffer-internal.h index 026b19a0..521fae07 100644 --- a/evbuffer-internal.h +++ b/evbuffer-internal.h @@ -76,6 +76,8 @@ struct evbuffer { void *lock; #endif unsigned own_lock : 1; + unsigned freeze_start : 1; + unsigned freeze_end : 1; int lock_count; diff --git a/include/event2/buffer.h b/include/event2/buffer.h index 8e94acf1..786fd2df 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -194,6 +194,7 @@ int evbuffer_commit_space(struct evbuffer *buf, size_t size); @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); @@ -204,7 +205,7 @@ 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); @@ -337,8 +338,9 @@ int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap); @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); /** @@ -542,9 +544,10 @@ int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size); @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: @@ -559,6 +562,31 @@ void evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src); #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 diff --git a/test/regress_buffer.c b/test/regress_buffer.c index 135b933c..eebfbbbd 100644 --- a/test/regress_buffer.c +++ b/test/regress_buffer.c @@ -853,6 +853,110 @@ end: } +/* 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 }, @@ -865,6 +969,8 @@ struct testcase_t evbuffer_testcases[] = { { "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 },