From: Nick Mathewson Date: Thu, 21 Oct 2010 23:45:49 +0000 (-0400) Subject: Add evbuffer_add_file_segment() so one fd can be used efficiently in more than one... X-Git-Tag: release-2.1.1-alpha~323^2~3 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e72afae068c31c62fa125f99b9c5102ba2e7ec7c;p=libevent Add evbuffer_add_file_segment() so one fd can be used efficiently in more than one evbuffer_add_file at a time --- diff --git a/buffer.c b/buffer.c index 50908b19..4f788b61 100644 --- a/buffer.c +++ b/buffer.c @@ -63,6 +63,10 @@ #ifdef _EVENT_HAVE_SYS_SENDFILE_H #include #endif +#ifdef _EVENT_HAVE_SYS_STAT_H +#include +#endif + #include #include @@ -111,14 +115,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. */ @@ -144,13 +140,6 @@ static int evbuffer_ptr_memcmp(const struct evbuffer *buf, 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 struct evbuffer_chain * evbuffer_chain_new(size_t size) { @@ -187,40 +176,24 @@ evbuffer_chain_free(struct evbuffer_chain *chain) 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); - } -#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); - } -#endif + + 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) + evbuffer_file_segment_free(info->segment); } mm_free(chain); @@ -2124,56 +2097,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, @@ -2225,44 +2148,46 @@ 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); } return (res); #elif defined(SENDFILE_IS_SOLARIS) - 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); @@ -2654,153 +2579,226 @@ 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; + +#ifdef WIN32 +#define lseek _lseeki64 +#define fstat _fstat +#define stat _stat #endif - int ok = 1; + if (length == -1) { + struct stat st; + if (fstat(fd, &st) < 0) + goto err; + length = st.st_size; + } + seg->length = length; #if defined(USE_SENDFILE) - if (use_sendfile) { - 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; - - info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); - info->fd = fd; - - 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 + if (!(flags & EVBUF_FS_DISABLE_SENDFILE)) { + seg->offset = offset; + seg->type = EVBUF_FS_SENDFILE; + goto done; + } #endif #if defined(_EVENT_HAVE_MMAP) - if (use_mmap) { + /* TODO: Implement an mmap-alike for windows. */ + if (!(flags & EVBUF_FS_DISABLE_MMAP)) { + /* 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. + */ void *mapped = mmap(NULL, length + offset, 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. - */ 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; + seg->offset = offset; + seg->type = EVBUF_FS_MMAP; + 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 + + { + 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; + 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; + } - info = EVBUFFER_CHAIN_EXTRA(struct evbuffer_chain_fd, chain); - info->fd = fd; + seg->contents = mem; + seg->type = EVBUF_FS_IO; + } - EVBUFFER_LOCK(outbuf); - if (outbuf->freeze_end) { - info->fd = -1; - evbuffer_chain_free(chain); - ok = 0; - } else { - outbuf->n_add_for_cb += length; +done: + if (!(flags & EVBUF_FS_DISABLE_LOCKING)) { + EVTHREAD_ALLOC_LOCK(seg->lock, 0); + } + return seg; +err: + mm_free(seg); + return NULL; +} - evbuffer_chain_insert(outbuf, chain); +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); - /* we need to subtract whatever we don't need */ - evbuffer_drain(outbuf, offset); - } - } else -#endif - { - /* the default implementation */ - struct evbuffer *tmp = evbuffer_new(); - ev_ssize_t read; + if (seg->type == EVBUF_FS_SENDFILE) { + ; + } else if (seg->type == EVBUF_FS_MMAP) { + if (munmap(seg->mapping, seg->length) == -1) + event_warn("%s: munmap failed", __func__); + } else { + EVUTIL_ASSERT(seg->type == EVBUF_FS_IO); + 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; - length -= read; - } + EVLOCK_LOCK(seg->lock, 0); + ++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); + EVBUFFER_LOCK(buf); -#ifdef WIN32 -#define close _close -#endif - close(fd); - } + if (buf->freeze_end) + goto err; + + if (length < 0) { + if (offset > seg->length) + goto err; + length = seg->length - offset; } - if (ok) - evbuffer_invoke_callbacks(outbuf); - EVBUFFER_UNLOCK(outbuf); + /* Can we actually add this? */ + if (offset+length > seg->length) + goto err; - return ok ? 0 : -1; + 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 (seg->type == EVBUF_FS_SENDFILE) { + chain->flags |= EVBUFFER_SENDFILE; + chain->misalign = seg->offset + offset; + chain->off = length; + chain->buffer_len = chain->misalign + length; + } else if (seg->type == EVBUF_FS_MMAP) { + chain->buffer = (unsigned char*)(seg->contents + offset); + chain->buffer_len = length; + chain->off = length; + } else { + EVUTIL_ASSERT(seg->type == EVBUF_FS_IO); + chain->buffer = (unsigned char*)(seg->contents + offset); + chain->buffer_len = length; + chain->off = length; + } + + extra->segment = seg; + buf->n_add_for_cb += length; + evbuffer_chain_insert(buf, chain); + + evbuffer_invoke_callbacks(buf); + + EVBUFFER_UNLOCK(buf); + + 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) @@ -2936,50 +2934,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; -} diff --git a/evbuffer-internal.h b/evbuffer-internal.h index 7fc8b914..ebbbe58e 100644 --- a/evbuffer-internal.h +++ b/evbuffer-internal.h @@ -170,8 +170,8 @@ struct evbuffer_chain { /** Set if special handling is required for this chain */ unsigned flags; -#define EVBUFFER_MMAP 0x0001 /**< memory in buffer is mmaped */ -#define EVBUFFER_SENDFILE 0x0002 /**< a chain used for sendfile */ +#define EVBUFFER_FILESEGMENT 0x0001 /**< A chain used for a file segment */ +#define EVBUFFER_SENDFILE 0x0002 /**< a chain used with sendfile */ #define EVBUFFER_REFERENCE 0x0004 /**< a chain with a mem reference */ #define EVBUFFER_IMMUTABLE 0x0008 /**< read-only chain */ /** a chain that mustn't be reallocated or freed, or have its contents @@ -192,21 +192,45 @@ struct evbuffer_chain { unsigned char *buffer; }; -/* this is currently used by both mmap and sendfile */ -/* TODO(niels): something strange needs to happen for Windows here, I am not - * sure what that is, but it needs to get looked into. - */ -struct evbuffer_chain_fd { - int fd; /**< the fd associated with this chain */ -}; - -/** callback for a reference buffer; lets us know what to do with it when - * we're done with it. */ +/** callback for a reference chain; lets us know what to do with it when + * we're done with it. Lives at the end of an evbuffer_chain with the + * EVBUFFER_REFERENCE flag set */ struct evbuffer_chain_reference { evbuffer_ref_cleanup_cb cleanupfn; void *extra; }; +/** File segment for a file-segment chain. Lives at the end of an + * evbuffer_chain with the EVBUFFER_FILESEGMENT flag set. */ +struct evbuffer_chain_file_segment { + struct evbuffer_file_segment *segment; +}; + +/* Declared in event2/buffer.h; defined here. */ +struct evbuffer_file_segment { + void *lock; /**< lock prevent concurrent access to refcnt */ + int refcnt; /**< Reference count for this file segment */ + unsigned flags; /**< combination of EVBUF_FS_* flags */ + + /** What kind of file segment is this? */ + enum {EVBUF_FS_MMAP, EVBUF_FS_SENDFILE, EVBUF_FS_IO} type; + + /** The fd that we read the data from. */ + int fd; + /** If we're using mmap, this is the raw mapped memory. */ + void *mapping; + /** If we're using mmap or IO, this is the content of the file + * segment. */ + char *contents; + /** If we're using mmap, this is the offset within 'mapping' where + * this data segment begins. If we're using sendfile, this is the + * offset within the file where this data begins. If we're using IO, + * this is 0. */ + off_t offset; + /** The length of this segment. */ + off_t length; +}; + #define EVBUFFER_CHAIN_SIZE sizeof(struct evbuffer_chain) /** Return a pointer to extra data allocated along with an evbuffer. */ #define EVBUFFER_CHAIN_EXTRA(t, c) (t *)((struct evbuffer_chain *)(c) + 1) diff --git a/include/event2/buffer.h b/include/event2/buffer.h index d6538071..7e2fc315 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -368,16 +368,117 @@ int evbuffer_add_reference(struct evbuffer *outbuf, The results of using evbuffer_remove() or evbuffer_pullup() are undefined. + For more fine-grained control, use evbuffer_add_file_segment. + @param outbuf the output buffer @param fd the file descriptor @param off the offset from which to read data - @param length how much data to read + @param length how much data to read, or -1 to read as much as possible. + (-1 requires that 'fd' support fstat.) @return 0 if successful, or -1 if an error occurred */ int evbuffer_add_file(struct evbuffer *output, int fd, ev_off_t offset, ev_off_t length); +/** + An evbuffer_file_segment holds a reference to a range of a file -- + possibly the whole file! -- for use in writing from an evbuffer to a + socket. It could be implemented with mmap, sendfile, splice, or (if all + else fails) by just pulling all the data into RAM. A single + evbuffer_file_segment can be added more than once, and to more than one + evbuffer. + */ +struct evbuffer_file_segment; + +/** + Flag for creating evbuffer_file_segment: If this flag is set, then when + the evbuffer_file_segment is freed and no longer in use by any + evbuffer, the underlying fd is closed. + */ +#define EVBUF_FS_CLOSE_ON_FREE 0x01 +/** + Flag for creating evbuffer_file_segment: Disable memory-map based + implementations. + */ +#define EVBUF_FS_DISABLE_MMAP 0x02 +/** + Flag for creating evbuffer_file_segment: Disable direct fd-to-fd + implementations (including sendfile and splice). + + You might want to use this option if data needs to be taken from the + evbuffer by any means other than writing it to the network: the sendfile + backend is fast, but it only works for sending files directly to the + network. + */ +#define EVBUF_FS_DISABLE_SENDFILE 0x04 +/** + Flag for creating evbuffer_file_segment: Do not allocate a lock for this + segment. If this option is set, then neither the segment nor any + evbuffer it is added to may ever be accessed from more than one thread + at a time. + */ +#define EVBUF_FS_DISABLE_LOCKING 0x08 + +/** + Create and return a new evbuffer_file_segment for reading data from a + file and sending it out via an evbuffer. + + This function avoids unnecessary data copies between userland and + kernel. Where available, it uses sendfile or splice. + + The file descriptor must not be closed so long as any evbuffer is using + this segment. + + The results of using evbuffer_remove() or evbuffer_pullup() or any other + function that reads bytes from an evbuffer on any evbuffer containing + the newly returned segment are undefined, unless you pass the + EVBUF_FS_DISABLE_SENDFILE flag to this function. + + @param fd an open file to read from. + @param offset an index within the file at which to start reading + @param length how much data to read, or -1 to read as much as possible. + (-1 requires that 'fd' support fstat.) + @param flags any number of the EVBUF_FS_* flags + @return a new evbuffer_file_segment, or NULL on failure. + **/ +struct evbuffer_file_segment *evbuffer_file_segment_new( + int fd, ev_off_t offset, ev_off_t length, unsigned flags); + +/** + Free an evbuffer_file_segment + + It is safe to call this function even if the segment has been added to + one or more evbuffers. The evbuffer_file_segment will not be freed + until no more references to it exist. + */ +void evbuffer_file_segment_free(struct evbuffer_file_segment *seg); + +/** + Insert some or all of an evbuffer_file_segment at the end of an evbuffer + + Note that the offset and length parameters of this function have a + different meaning from those provided to evbuffer_file_segment_new: When + you create the segment, the offset is the offset _within the file_, and + the length is the length _of the segment_, whereas when you add a + segment to an evbuffer, the offset is _within the segment_ and the + length is the length of the _part of the segment you want to use. + + In other words, if you have a 10 KiB file, and you create an + evbuffer_file_segment for it with offset 20 and length 1000, it will + refer to bytes 20..1019 inclusive. If you then pass this segment to + evbuffer_add_file_segment and specify an offset of 20 and a length of + 50, you will be adding bytes 40..99 inclusive. + + @param buf the evbuffer to append to + @param seg the segment to add + @param offset the offset within the segment to start from + @param length the amount of data to add, or -1 to add it all. + @return 0 on success, -1 on failure. + */ +int evbuffer_add_file_segment(struct evbuffer *buf, + struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length); + /** Append a formatted string to the end of an evbuffer. diff --git a/test/regress_buffer.c b/test/regress_buffer.c index 4f4a8303..5f98a68d 100644 --- a/test/regress_buffer.c +++ b/test/regress_buffer.c @@ -583,10 +583,6 @@ test_evbuffer_reference(void *ptr) evbuffer_free(src); } -int _evbuffer_testing_use_sendfile(void); -int _evbuffer_testing_use_mmap(void); -int _evbuffer_testing_use_linear_file_access(void); - static void test_evbuffer_add_file(void *ptr) { @@ -598,26 +594,39 @@ test_evbuffer_add_file(void *ptr) int fd = -1; evutil_socket_t pair[2] = {-1, -1}; int r=0, n_written=0; + int want_type = 0; + unsigned flags = 0; + int use_segment = 1; + struct evbuffer_file_segment *seg = NULL; /* Add a test for a big file. XXXX */ tt_assert(impl); - if (!strcmp(impl, "sendfile")) { - if (!_evbuffer_testing_use_sendfile()) - tt_skip(); - TT_BLATHER(("Using sendfile-based implementaion")); + if (!strcmp(impl, "nosegment")) { + use_segment = 0; + } else if (!strcmp(impl, "sendfile")) { + flags = EVBUF_FS_DISABLE_MMAP; + want_type = EVBUF_FS_SENDFILE; } else if (!strcmp(impl, "mmap")) { - if (!_evbuffer_testing_use_mmap()) - tt_skip(); - TT_BLATHER(("Using mmap-based implementaion")); + flags = EVBUF_FS_DISABLE_SENDFILE; + want_type = EVBUF_FS_MMAP; } else if (!strcmp(impl, "linear")) { - if (!_evbuffer_testing_use_linear_file_access()) - tt_skip(); - TT_BLATHER(("Using read-based implementaion")); + flags = EVBUF_FS_DISABLE_SENDFILE|EVBUF_FS_DISABLE_MMAP; + want_type = EVBUF_FS_IO; } else { TT_DIE(("Didn't recognize the implementation")); } + datalen = strlen(data); + fd = regress_make_tmpfile(data, datalen); + + if (use_segment) { + seg = evbuffer_file_segment_new(fd, 0, datalen, flags); + tt_assert(seg); + if ((int)seg->type != (int)want_type) + tt_skip(); + } + #if defined(_EVENT_HAVE_SENDFILE) && defined(__sun__) && defined(__svr4__) /* We need to use a pair of AF_INET sockets, since Solaris doesn't support sendfile() over AF_UNIX. */ @@ -628,12 +637,13 @@ test_evbuffer_add_file(void *ptr) tt_abort_msg("socketpair failed"); #endif - datalen = strlen(data); - fd = regress_make_tmpfile(data, datalen); - tt_assert(fd != -1); - tt_assert(evbuffer_add_file(src, fd, 0, datalen) != -1); + if (use_segment) { + tt_assert(evbuffer_add_file_segment(src, seg, 0, -1)!=-1); + } else { + tt_assert(evbuffer_add_file(src, fd, 0, -1) != -1); + } evbuffer_validate(src); @@ -650,8 +660,9 @@ test_evbuffer_add_file(void *ptr) evbuffer_validate(src); compare = (char *)evbuffer_pullup(src, datalen); tt_assert(compare != NULL); - if (memcmp(compare, data, datalen)) + if (memcmp(compare, data, datalen)) { tt_abort_msg("Data from add_file differs."); + } evbuffer_validate(src); end: @@ -660,6 +671,8 @@ test_evbuffer_add_file(void *ptr) if (pair[1] >= 0) evutil_closesocket(pair[1]); evbuffer_free(src); + if (seg) + evbuffer_file_segment_free(seg); } #ifndef _EVENT_DISABLE_MM_REPLACEMENT @@ -1562,6 +1575,8 @@ struct testcase_t evbuffer_testcases[] = { (void*)"mmap" }, { "add_file_linear", test_evbuffer_add_file, TT_FORK, &nil_setup, (void*)"linear" }, + { "add_file_nosegment", test_evbuffer_add_file, TT_FORK, &nil_setup, + (void*)"nosegment" }, END_OF_TESTCASES };