#ifdef EVENT__HAVE_VASPRINTF /* If we have vasprintf, we need to define _GNU_SOURCE before we include *stdio.h.Thiscomesfromevconfig-private.h.
*/ #endif
if (size > EVBUFFER_CHAIN_MAX - EVBUFFER_CHAIN_SIZE) return (NULL);
size += EVBUFFER_CHAIN_SIZE;
/* get the next largest memory that can hold the buffer */ if (size < EVBUFFER_CHAIN_MAX / 2) {
to_alloc = MIN_BUFFER_SIZE; while (to_alloc < size) {
to_alloc <<= 1;
}
} else {
to_alloc = size;
}
/* we get everything in one chunk */ if ((chain = mm_malloc(to_alloc)) == NULL) return (NULL);
/* this way we can manipulate the buffer to different addresses, *whichisrequiredformmapforexample.
*/
chain->buffer = EVBUFFER_CHAIN_EXTRA(unsignedchar, chain);
chain->refcnt = 1;
return (chain);
}
staticinlinevoid
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;
}
/* 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
evbuffer_file_segment_free(info->segment);
}
} if (chain->flags & EVBUFFER_MULTICAST) { struct evbuffer_multicast_parent *info =
EVBUFFER_CHAIN_EXTRA( struct evbuffer_multicast_parent,
chain); /* referencing chain is being freed, decrease *refcountsofsourcechainandassociated *evbuffer(whichgetfreedoncebothreach
* 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);
}
#ifndef NDEBUG staticint
evbuffer_chains_all_empty(struct evbuffer_chain *chain)
{ for (; chain; chain = chain->next) { if (chain->off) return0;
} return1;
} #else /* The definition is needed for EVUTIL_ASSERT, which uses sizeof to avoid
"unused variable" warnings. */ staticinlineint evbuffer_chains_all_empty(struct evbuffer_chain *chain) { return1;
} #endif
/* Free all trailing chains in 'buf' that are neither pinned nor empty, prior *toreplacingthemallwithanewchain.Returnapointertotheplace *wherethenewchainwillgo. * *Internal;requireslock.Thecallermustfixupbuf->lastandbuf->first *asneeded;theymighthavebeenfreed.
*/ staticstruct evbuffer_chain **
evbuffer_free_trailing_empty_chains(struct evbuffer *buf)
{ 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) {
EVUTIL_ASSERT(evbuffer_chains_all_empty(*ch));
evbuffer_free_all_chains(*ch);
*ch = NULL;
} return ch;
}
/* Add a single chain 'chain' to the end of 'buf', freeing trailing empty *chainsasnecessary.Requireslock.Doesnotschedulecallbacks.
*/ staticvoid
evbuffer_chain_insert(struct evbuffer *buf, struct evbuffer_chain *chain)
{
ASSERT_EVBUFFER_LOCKED(buf); if (*buf->last_with_datap == NULL) { /* There are no chains data on the buffer at all. */
EVUTIL_ASSERT(buf->last_with_datap == &buf->first);
EVUTIL_ASSERT(buf->first == NULL);
buf->first = buf->last = chain;
} else { 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;
}
if (buffer->deferred_cbs) { if (event_deferred_cb_schedule_(buffer->cb_queue, &buffer->deferred)) {
evbuffer_incref_and_lock_(buffer); if (buffer->parent)
bufferevent_incref_(buffer->parent);
EVBUFFER_UNLOCK(buffer);
}
}
/* XXXX It would be better to run these callbacks without holding the
* lock */
EVBUFFER_LOCK(buffer);
parent = buffer->parent;
evbuffer_run_callbacks(buffer, 1);
evbuffer_decref_and_unlock_(buffer); if (parent)
bufferevent_decref_(parent);
}
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 *obviatedbyevbuffer_expand_fast_,andsomecleanupthatwe *wouldliketodoonlyonce.Insteadweshouldjustextract
* 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)
{ struct evbuffer_chain *chain, **chainp; int n = -1;
EVBUFFER_LOCK(buf); if (buf->freeze_end) goto done; if (n_vecs < 1) goto done; if (n_vecs == 1) { if ((chain = evbuffer_expand_singlechain(buf, size)) == NULL) goto done;
staticint
advance_last_with_data(struct evbuffer *buf)
{ int n = 0; struct evbuffer_chain **chainp = buf->last_with_datap;
ASSERT_EVBUFFER_LOCKED(buf);
if (!*chainp) return0;
while ((*chainp)->next) {
chainp = &(*chainp)->next; if ((*chainp)->off)
buf->last_with_datap = chainp;
++n;
} return n;
}
int
evbuffer_commit_space(struct evbuffer *buf, struct evbuffer_iovec *vec, int n_vecs)
{ struct evbuffer_chain *chain, **firstchainp, **chainp; int result = -1;
size_t added = 0; int i;
EVBUFFER_LOCK(buf);
if (buf->freeze_end) goto done; if (n_vecs == 0) {
result = 0; goto done;
} elseif (n_vecs == 1 &&
(buf->last && vec[0].iov_base == (void *)CHAIN_SPACE_PTR(buf->last))) { /* The user only got or used one chain; it might not
* be the first one with space in it. */ if ((size_t)vec[0].iov_len > (size_t)CHAIN_SPACE_LEN(buf->last)) goto done;
buf->last->off += vec[0].iov_len;
added = vec[0].iov_len; if (added)
advance_last_with_data(buf); goto okay;
}
/* Advance 'firstchain' to the first chain with space in it. */
firstchainp = buf->last_with_datap; if (!*firstchainp) goto done; if (CHAIN_SPACE_LEN(*firstchainp) == 0) {
firstchainp = &(*firstchainp)->next;
}
chain = *firstchainp; /* pass 1: make sure that the pointers and lengths of vecs[] are in
* bounds before we try to commit anything. */ for (i=0; i<n_vecs; ++i) { if (!chain) goto done; if (vec[i].iov_base != (void *)CHAIN_SPACE_PTR(chain) ||
(size_t)vec[i].iov_len > CHAIN_SPACE_LEN(chain)) goto done;
chain = chain->next;
} /* pass 2: actually adjust all the chains. */
chainp = firstchainp; for (i=0; i<n_vecs; ++i) {
(*chainp)->off += vec[i].iov_len;
added += vec[i].iov_len; if (vec[i].iov_len) {
buf->last_with_datap = chainp;
}
chainp = &(*chainp)->next;
}
/* Prepares the contents of src to be moved to another buffer by removing *read-pinnedchains.Thefirstpinnedchainissavedinfirst,andthe *lastinlast.Ifsrchasnoread-pinnedchains,firstandlastareset
* to NULL. */ staticint
PRESERVE_PINNED(struct evbuffer *src, struct evbuffer_chain **first, struct evbuffer_chain **last)
{ struct evbuffer_chain *chain, **pinned;
ASSERT_EVBUFFER_LOCKED(src);
if (!HAS_PINNED_R(src)) {
*first = *last = NULL; return0;
}
/* If there's data in the first pinned chain, we need to allocate
* a new chain and copy the data over. */ if (chain->off) { struct evbuffer_chain *tmp;
if (in_total_len == 0 || outbuf == inbuf) goto done;
if (outbuf->freeze_end || inbuf->freeze_start) {
result = -1; goto done;
}
if (PRESERVE_PINNED(inbuf, &pinned, &last) < 0) {
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);
COPY_CHAIN(outbuf, inbuf);
} else {
APPEND_CHAIN(outbuf, inbuf);
}
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);
if (outbuf->freeze_start || inbuf->freeze_start) {
result = -1; goto done;
}
if (PRESERVE_PINNED(inbuf, &pinned, &last) < 0) {
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);
COPY_CHAIN(outbuf, inbuf);
} else {
PREPEND_CHAIN(outbuf, inbuf);
}
buf->n_del_for_cb += len; /* Tell someone about changes in this buffer */
evbuffer_invoke_callbacks_(buf);
done:
EVBUFFER_UNLOCK(buf); return result;
}
/* Reads data from an event buffer and drains the bytes read */ int
evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)
{
ev_ssize_t n;
EVBUFFER_LOCK(buf);
n = evbuffer_copyout_from(buf, NULL, data_out, datlen); if (n > 0) { if (evbuffer_drain(buf, n)<0)
n = -1;
}
EVBUFFER_UNLOCK(buf); return (int)n;
}
result = nread;
done:
EVBUFFER_UNLOCK(buf); return result;
}
/* reads data from the src buffer to the dst buffer, avoids memcpy as
* possible. */ /* XXXX should return ev_ssize_t */ int
evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
size_t datlen)
{ /*XXX We should have an option to force this to be zero-copy.*/
/*XXX can fail badly on sendfile case. */ struct evbuffer_chain *chain, *previous;
size_t nread = 0; int result;
EVBUFFER_LOCK2(src, dst);
chain = previous = src->first;
if (datlen == 0 || dst == src) {
result = 0; 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_add_buffer(dst, src);
result = (int)datlen; /*XXXX should return ev_ssize_t*/ goto done;
}
/* removes chains if possible */ while (chain->off <= datlen) { /* We can't remove the last with data from src unless we *removeallchains,inwhichcasewewouldhavedonetheif
* block above */
EVUTIL_ASSERT(chain != *src->last_with_datap);
nread += chain->off;
datlen -= chain->off;
previous = chain; if (src->last_with_datap == &chain->next)
src->last_with_datap = &src->first;
chain = chain->next;
}
if (chain != src->first) { /* we can remove the chain */ struct evbuffer_chain **chp;
chp = evbuffer_free_trailing_empty_chains(dst);
/* we know that there is more data in the src buffer than
* we want to read, so we manually drain the chain */
evbuffer_add(dst, chain->buffer + chain->misalign, datlen);
chain->misalign += datlen;
chain->off -= datlen;
nread += datlen;
/* You might think we would want to increment dst->n_add_for_cb *heretoo.Butevbuffer_addabovealreadytookcareofthat.
*/
src->total_len -= nread;
src->n_del_for_cb += nread;
if (nread) {
evbuffer_invoke_callbacks_(dst);
evbuffer_invoke_callbacks_(src);
}
result = (int)nread;/*XXXX should change return type */
if (size < 0)
size = buf->total_len; /* if size > buf->total_len, we cannot guarantee to the user that she *isgoingtohavealongenoughbufferafterwards;sowereturn
* NULL */ if (size == 0 || (size_t)size > buf->total_len) goto done;
/* No need to pull up anything; the first size bytes are
* already here. */ if (chain->off >= (size_t)size) {
result = chain->buffer + chain->misalign; goto done;
}
/* Make sure that none of the chains we need to copy from is pinned. */
remaining = size - chain->off;
EVUTIL_ASSERT(remaining >= 0); for (tmp=chain->next; tmp; tmp=tmp->next) { if (CHAIN_PINNED(tmp)) goto done; if (tmp->off >= (size_t)remaining) break;
remaining -= tmp->off;
}
if (CHAIN_PINNED(chain)) {
size_t old_off = chain->off; if (CHAIN_SPACE_LEN(chain) < size - chain->off) { /* not enough room at end of chunk. */ goto done;
}
buffer = CHAIN_SPACE_PTR(chain);
tmp = chain;
tmp->off = size;
size -= old_off;
chain = chain->next;
} elseif (chain->buffer_len - chain->misalign >= (size_t)size) { /* already have enough space in the first chain */
size_t old_off = chain->off;
buffer = chain->buffer + chain->misalign + chain->off;
tmp = chain;
tmp->off = size;
size -= old_off;
chain = chain->next;
} else { if ((tmp = evbuffer_chain_new(size)) == NULL) {
event_warn("%s: out of memory", __func__); goto done;
}
buffer = tmp->buffer;
tmp->off = size;
buf->first = tmp;
}
/* TODO(niels): deal with buffers that point to NULL like sendfile */
/* Copy and free every chunk that will be entirely pulled into tmp */
last_with_data = *buf->last_with_datap; for (; chain != NULL && (size_t)size >= chain->off; chain = next) {
next = chain->next;
if (chain->buffer) {
memcpy(buffer, chain->buffer + chain->misalign, chain->off);
size -= chain->off;
buffer += chain->off;
} if (chain == last_with_data)
removed_last_with_data = 1; if (&chain->next == buf->last_with_datap)
removed_last_with_datap = 1;
/* the eol_style determines our first stop character and how many
* characters we are going to drain afterwards. */ switch (eol_style) { case EVBUFFER_EOL_ANY: if (evbuffer_find_eol_char(&it) < 0) goto done;
memcpy(&it2, &it, sizeof(it));
extra_drain = evbuffer_strspn(&it2, "\r\n"); break; case EVBUFFER_EOL_CRLF_STRICT: {
it = evbuffer_search(buffer, "\r\n", 2, &it); if (it.pos < 0) goto done;
extra_drain = 2; break;
} 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 *fewchains.Probably,that'snottooexpensiveunlessyou
* 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;
}
ok = 1;
done:
EVBUFFER_UNLOCK(buffer);
if (!ok)
PTR_NOT_FOUND(&it); if (eol_len_out)
*eol_len_out = extra_drain;
/* If there are no chains allocated for this buffer, allocate one
* big enough to hold all the data. */ if (chain == NULL) {
chain = evbuffer_chain_new(datlen); if (!chain) goto done;
evbuffer_chain_insert(buf, chain);
}
if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) { /* Always true for mutable buffers */
EVUTIL_ASSERT(chain->misalign >= 0 &&
(ev_uint64_t)chain->misalign <= EVBUFFER_CHAIN_MAX);
remain = chain->buffer_len - (size_t)chain->misalign - chain->off; if (remain >= datlen) { /* there's enough space to hold all the data in the
* current last chain */
memcpy(chain->buffer + chain->misalign + chain->off,
data, datlen);
chain->off += datlen;
buf->total_len += datlen;
buf->n_add_for_cb += datlen; goto out;
} elseif (!CHAIN_PINNED(chain) &&
evbuffer_chain_should_realign(chain, datlen)) { /* we can fit the data into the misalignment */
evbuffer_chain_align(chain);
memcpy(chain->buffer + chain->off, data, datlen);
chain->off += datlen;
buf->total_len += datlen;
buf->n_add_for_cb += datlen; goto out;
}
} else { /* we cannot write any data to the last chain */
remain = 0;
}
/* we need to add another chain */
to_alloc = chain->buffer_len; if (to_alloc <= EVBUFFER_CHAIN_MAX_AUTO_SIZE/2)
to_alloc <<= 1; if (datlen > to_alloc)
to_alloc = datlen;
tmp = evbuffer_chain_new(to_alloc); if (tmp == NULL) goto done;
out:
evbuffer_invoke_callbacks_(buf);
result = 0;
done:
EVBUFFER_UNLOCK(buf); return result;
}
int
evbuffer_prepend(struct evbuffer *buf, constvoid *data, size_t datlen)
{ struct evbuffer_chain *chain, *tmp; int result = -1;
EVBUFFER_LOCK(buf);
if (datlen == 0) {
result = 0; goto done;
} if (buf->freeze_start) { goto done;
} if (datlen > EV_SIZE_MAX - buf->total_len) { goto done;
}
chain = buf->first;
if (chain == NULL) {
chain = evbuffer_chain_new(datlen); if (!chain) goto done;
evbuffer_chain_insert(buf, chain);
}
/* we cannot touch immutable buffers */ if ((chain->flags & EVBUFFER_IMMUTABLE) == 0) { /* Always true for mutable buffers */
EVUTIL_ASSERT(chain->misalign >= 0 &&
(ev_uint64_t)chain->misalign <= EVBUFFER_CHAIN_MAX);
/* If this chain is empty, we can treat it as
* 'empty at the beginning' rather than 'empty at the end' */ if (chain->off == 0)
chain->misalign = chain->buffer_len;
if ((size_t)chain->misalign >= datlen) { /* we have enough space to fit everything */
memcpy(chain->buffer + chain->misalign - datlen,
data, datlen);
chain->off += datlen;
chain->misalign -= datlen;
buf->total_len += datlen;
buf->n_add_for_cb += datlen; goto out;
} elseif (chain->misalign) { /* we can only fit some of the data. */
memcpy(chain->buffer,
(char*)data + datlen - chain->misalign,
(size_t)chain->misalign);
chain->off += (size_t)chain->misalign;
buf->total_len += (size_t)chain->misalign;
buf->n_add_for_cb += (size_t)chain->misalign;
datlen -= (size_t)chain->misalign;
chain->misalign = 0;
}
}
/* we need to add another chain */ if ((tmp = evbuffer_chain_new(datlen)) == NULL) goto done;
buf->first = tmp; if (buf->last_with_datap == &buf->first && chain->off)
buf->last_with_datap = &tmp->next;
/** Helper: return true iff we should realign chain to fit datalen bytes of
data in it. */ staticint
evbuffer_chain_should_realign(struct evbuffer_chain *chain,
size_t datlen)
{ return chain->buffer_len - chain->off >= datlen &&
(chain->off < chain->buffer_len / 2) &&
(chain->off <= MAX_TO_REALIGN_IN_EXPAND);
}
/* Expands the available space in the event buffer to at least datlen, all in
* a single chunk. Return that chunk. */ staticstruct evbuffer_chain *
evbuffer_expand_singlechain(struct evbuffer *buf, size_t datlen)
{ struct evbuffer_chain *chain, **chainp; struct evbuffer_chain *result = NULL;
ASSERT_EVBUFFER_LOCKED(buf);
chainp = buf->last_with_datap;
/* XXX If *chainp is no longer writeable, but has enough space in its *misalign,thismightbeabadidea:wecouldstilluse*chainp,not
* (*chainp)->next. */ if (*chainp && CHAIN_SPACE_LEN(*chainp) == 0)
chainp = &(*chainp)->next;
/* 'chain' now points to the first chain with writable space (if any)
* We will either use it, realign it, replace it, or resize it. */
chain = *chainp;
if (chain == NULL ||
(chain->flags & (EVBUFFER_IMMUTABLE|EVBUFFER_MEM_PINNED_ANY))) { /* We can't use the last_with_data chain at all. Just add a
* new one that's big enough. */ goto insert_new;
}
/* If we can fit all the data, then we don't have to do anything */ if (CHAIN_SPACE_LEN(chain) >= datlen) {
result = chain; goto ok;
}
/* If the chain is completely empty, just replace it by adding a new
* empty chain. */ if (chain->off == 0) { goto insert_new;
}
/* If the misalignment plus the remaining space fulfills our data *needs,wecouldjustforceanalignmenttohappen.Afterwards,we *haveenoughspace.Butonlydothisifwe'resavingalotofspace *andnotmovingtoomuchdata.Otherwisethespacesavingsare *probablyoffsetbythetimelostincopying. */ if(evbuffer_chain_should_realign(chain,datlen)){ evbuffer_chain_align(chain); result=chain; gotook; }
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.66Bemerkung:
(vorverarbeitet am 2026-06-10)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.