From 27e222559a3eab400f7290a4abc568b0505d3adc Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 7 Dec 2011 13:04:35 -0500 Subject: [PATCH] Add evbuffer_copyout_from to copy data from the middle of a buffer You could previously do this with evbuffer_peek() and some memcpys, but it was a bit more work than most folks wanted to get into. Closes sourceforge ticket 3108072 --- buffer.c | 39 +++++++++++++++----- include/event2/buffer.h | 14 +++++++ test/regress_buffer.c | 82 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 9 deletions(-) diff --git a/buffer.c b/buffer.c index 9b4edda5..4c5bc1bf 100644 --- a/buffer.c +++ b/buffer.c @@ -1036,7 +1036,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; @@ -1047,19 +1047,35 @@ 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; @@ -1071,18 +1087,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; diff --git a/include/event2/buffer.h b/include/event2/buffer.h index 0f4c03b1..0e31bb8a 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -352,6 +352,20 @@ int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen); */ ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen); +/** + Read data from the middle of an evbuffer, and leave the buffer unchanged. + + If more bytes are requested than are available in the evbuffer, we + only extract as many bytes as were available. + + @param buf the evbuffer to be read from + @param pos the position to start reading from + @param data_out the destination buffer to store the result + @param datlen the maximum size of the destination buffer + @return the number of bytes read, or -1 if we can't drain the buffer. + */ +ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf, const struct evbuffer_ptr *pos, void *data_out, size_t datlen); + /** Read data from an evbuffer into another evbuffer, draining the bytes from the source buffer. This function avoids copy diff --git a/test/regress_buffer.c b/test/regress_buffer.c index cdad8cd2..21fdf060 100644 --- a/test/regress_buffer.c +++ b/test/regress_buffer.c @@ -1897,6 +1897,87 @@ end: } } +static void +test_evbuffer_copyout(void *dummy) +{ + const char string[] = + "Still they skirmish to and fro, men my messmates on the snow " + "When we headed off the aurochs turn for turn; " + "When the rich Allobrogenses never kept amanuenses, " + "And our only plots were piled in lakes at Berne."; + /* -- Kipling, "In The Neolithic Age" */ + char tmp[256]; + struct evbuffer_ptr ptr; + struct evbuffer *buf; + + (void)dummy; + + buf = evbuffer_new(); + tt_assert(buf); + + tt_int_op(strlen(string), ==, 206); + + /* Ensure separate chains */ + evbuffer_add_reference(buf, string, 80, no_cleanup, NULL); + evbuffer_add_reference(buf, string+80, 80, no_cleanup, NULL); + evbuffer_add(buf, string+160, strlen(string)-160); + + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* First, let's test plain old copyout. */ + + /* Copy a little from the beginning. */ + tt_int_op(10, ==, evbuffer_copyout(buf, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "Still they", 10)); + + /* Now copy more than a little from the beginning */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(100, ==, evbuffer_copyout(buf, tmp, 100)); + tt_int_op(0, ==, memcmp(tmp, string, 100)); + + /* Copy too much; ensure truncation. */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206, ==, evbuffer_copyout(buf, tmp, 230)); + tt_int_op(0, ==, memcmp(tmp, string, 206)); + + /* That was supposed to be nondestructive, btw */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* Now it's time to test copyout_from! First, let's start in the + * first chain. */ + evbuffer_ptr_set(buf, &ptr, 15, EVBUFFER_PTR_SET); + memset(tmp, 0, sizeof(tmp)); + tt_int_op(10, ==, evbuffer_copyout_from(buf, &ptr, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "mish to an", 10)); + + /* Right up to the end of the first chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(65, ==, evbuffer_copyout_from(buf, &ptr, tmp, 65)); + tt_int_op(0, ==, memcmp(tmp, string+15, 65)); + + /* Span into the second chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(90, ==, evbuffer_copyout_from(buf, &ptr, tmp, 90)); + tt_int_op(0, ==, memcmp(tmp, string+15, 90)); + + /* Span into the third chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(160, ==, evbuffer_copyout_from(buf, &ptr, tmp, 160)); + tt_int_op(0, ==, memcmp(tmp, string+15, 160)); + + /* Overrun */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206-15, ==, evbuffer_copyout_from(buf, &ptr, tmp, 999)); + tt_int_op(0, ==, memcmp(tmp, string+15, 206-15)); + + /* That was supposed to be nondestructive, too */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + +end: + if (buf) + evbuffer_free(buf); +} + static void * setup_passthrough(const struct testcase_t *testcase) { @@ -1936,6 +2017,7 @@ struct testcase_t evbuffer_testcases[] = { { "freeze_start", test_evbuffer_freeze, 0, &nil_setup, (void*)"start" }, { "freeze_end", test_evbuffer_freeze, 0, &nil_setup, (void*)"end" }, { "add_iovec", test_evbuffer_add_iovec, 0, NULL, NULL}, + { "copyout", test_evbuffer_copyout, 0, NULL, NULL}, #define ADDFILE_TEST(name, parameters) \ { name, test_evbuffer_add_file, TT_FORK|TT_NEED_BASE, \