/* * $Id: fcgi_buf.c,v 1.2 1999/02/11 02:15:33 roberts Exp $ */ #include "fcgi.h" /******************************************************************************* * Check buffer consistency with assertions. */ void fcgi_buf_check(Buffer *buf) { ap_assert(buf->size > 0); ap_assert(buf->length >= 0); ap_assert(buf->length <= buf->size); ap_assert(buf->begin >= buf->data); ap_assert(buf->begin < buf->data + buf->size); ap_assert(buf->end >= buf->data); ap_assert(buf->end < buf->data + buf->size); ap_assert(((buf->end - buf->begin + buf->size) % buf->size) == (buf->length % buf->size)); } /******************************************************************************* * Reset buffer, losing any data that's in it. */ void fcgi_buf_reset(Buffer *buf) { buf->length = 0; buf->begin = buf->end = buf->data; } /******************************************************************************* * Allocate and intialize a new buffer of the specified size. */ Buffer *fcgi_buf_new(pool *p, int size) { Buffer *buf; buf = (Buffer *)ap_pcalloc(p, sizeof(Buffer) + size); buf->size = size; fcgi_buf_reset(buf); return buf; } /******************************************************************************* * Read from an open file descriptor into buffer. * * The caller should disable the default Apache SIGPIPE handler, * otherwise a bad script could cause the request to abort and appear * as though the client's fd caused it. * * Results: * <0 error, errno is set * =0 EOF reached * >0 successful read or no room in buffer (NOT # of bytes read) */ int fcgi_buf_add_fd(Buffer *buf, int fd) { int len; fcgi_buf_check(buf); if (buf->length == buf->size) /* there's no room in the buffer, return "success" */ return 1; if (buf->length == 0) /* the buffer is empty so defrag */ buf->begin = buf->end = buf->data; len = min(buf->size - buf->length, buf->data + buf->size - buf->end); #ifndef NO_WRITEV /* assume there is a readv() if there is a writev() */ if (len == buf->size - buf->length) { /* its not wrapped, use read() instead of readv() */ #endif do len = read(fd, buf->end, len); while (len == -1 && errno == EINTR); if (len <= 0) return len; buf->end += len; buf->length += len; if (buf->end == (buf->data + buf->size)) { /* the buffer needs to be wrapped */ buf->end = buf->data; #ifndef NO_WRITEV } } else { /* the buffer is wrapped, use readv() */ struct iovec vec[2]; vec[0].iov_base = buf->end; vec[0].iov_len = len; vec[1].iov_base = buf->data; vec[1].iov_len = buf->size - buf->length - len; do len = readv(fd, vec, 2); while (len == -1 && errno == EINTR); if (len <= 0) return len; buf->end += len; if (buf->end >= (buf->data + buf->size)) buf->end -= buf->size; buf->length += len; } #else if (buf->length < buf->size) { /* There's still more buffer space to read into. */ fd_set read_set; int status; int numFDs = fd + 1; struct timeval timeOut; FD_ZERO(&read_set); FD_SET(fd, &read_set); timeOut.tv_sec = 0; timeOut.tv_usec = 0; status = ap_select(numFDs, &read_set, NULL, NULL, &timeOut); if (status < 0) return status; /* error, errno is set */ if (status > 0 && FD_ISSET(fd, &read_set)) { do len = read(fd, buf->end, buf->size - buf->length); while (len == -1 && errno == EINTR); if (len <= 0) return len; buf->end += len; buf->length += len; } } } #endif return len; /* this may not contain the number of bytes read */ } /******************************************************************************* * Write from the buffer to an open file descriptor. * * The caller should disable the default Apache SIGPIPE handler, * otherwise a bad script could cause the request to abort appearing * as though the client's fd caused it. * * Results: * <0 if an error occured (bytes may or may not have been written) * =0 if no bytes were written * >0 successful write */ int fcgi_buf_get_to_fd(Buffer *buf, int fd) { int len; fcgi_buf_check(buf); if (buf->length == 0) return 0; len = min(buf->length, buf->data + buf->size - buf->begin); #ifndef NO_WRITEV if (len == buf->length) { /* the buffer is not wrapped, we don't need to use writev() */ #endif do len = write(fd, buf->begin, len); while (len == -1 && errno == EINTR); if (len <= 0) goto Return; buf->begin += len; buf->length -= len; if (buf->begin == buf->data + buf->size) { /* the buffer needs to be wrapped */ buf->begin = buf->data; #ifndef NO_WRITEV } } else { /* the buffer is wrapped, use writev() */ struct iovec vec[2]; vec[0].iov_base = buf->begin; vec[0].iov_len = len; vec[1].iov_base = buf->data; vec[1].iov_len = buf->length - len; do len = writev(fd, vec, 2); while (len == -1 && errno == EINTR); if (len <= 0) goto Return; buf->begin += len; buf->length -= len; if (buf->begin >= buf->data + buf->size) buf->begin -= buf->size; } #else if (buf->length > 0) { /* there's still more data to write */ fd_set write_set; int status; int numFDs = fd + 1; struct timeval timeOut; FD_ZERO(&write_set); FD_SET(fd, &write_set); timeOut.tv_sec = 0; timeOut.tv_usec = 0; status = ap_select(numFDs, NULL, &write_set, NULL, &timeOut); if (status < 0) { len = status; /* error, errno is set */ goto Return; } if (status > 0 && FD_ISSET(fd, &write_set)) { int len2; do len2 = write(fd, buf->begin, buf->length); while (len == -1 && errno == EINTR); if (len2 < 0) { len = len2; goto Return; } if (len2 > 0) { buf->begin += len2; buf->length -= len2; len += len2; } } } } #endif Return: if (buf->length == 0) buf->begin = buf->end = buf->data; return len; } /******************************************************************************* * Return the data block start address and the length of the block. */ void fcgi_buf_get_block_info(Buffer *buf, char **beginPtr, int *countPtr) { fcgi_buf_check(buf); *beginPtr = buf->begin; *countPtr = min(buf->length, buf->data + buf->size - buf->begin); } /******************************************************************************* * Throw away bytes from buffer. */ void fcgi_buf_toss(Buffer *buf, int count) { fcgi_buf_check(buf); ap_assert(count >= 0 && count <= buf->length); buf->length -= count; buf->begin += count; if(buf->begin >= buf->data + buf->size) { buf->begin -= buf->size; } } /******************************************************************************* * Return the free data block start address and the length of the block. */ void fcgi_buf_get_free_block_info(Buffer *buf, char **endPtr, int *countPtr) { fcgi_buf_check(buf); *endPtr = buf->end; *countPtr = min(buf->size - buf->length, buf->data + buf->size - buf->end); } /******************************************************************************* * Updates the buf to reflect recently added data. */ void fcgi_buf_add_update(Buffer *buf, int count) { fcgi_buf_check(buf); ap_assert(count >= 0 && count <= BufferFree(buf)); buf->length += count; buf->end += count; if(buf->end >= buf->data + buf->size) { buf->end -= buf->size; } fcgi_buf_check(buf); } /******************************************************************************* * Adds a block of data to a buffer, returning the number of bytes added. */ int fcgi_buf_add_block(Buffer *buf, char *data, int datalen) { char *end; int copied = 0; /* Number of bytes actually copied. */ int canCopy; /* Number of bytes to copy in a given op. */ ap_assert(data != NULL); if(datalen == 0) { return 0; } ap_assert(datalen > 0); fcgi_buf_check(buf); end = buf->data + buf->size; /* * Copy the first part of the data: from here to the end of the * buffer, or the end of the data, whichever comes first. */ datalen = min(BufferFree(buf), datalen); canCopy = min(datalen, end - buf->end); memcpy(buf->end, data, canCopy); buf->length += canCopy; buf->end += canCopy; copied += canCopy; if (buf->end >= end) { buf->end = buf->data; } datalen -= canCopy; /* * If there's more to go, copy the second part starting from the * beginning of the buffer. */ if (datalen > 0) { data += canCopy; memcpy(buf->end, data, datalen); buf->length += datalen; buf->end += datalen; copied += datalen; } return(copied); } /******************************************************************************* * Add a string to a buffer, returning the number of bytes added. */ int fcgi_buf_add_string(Buffer *buf, char *str) { return fcgi_buf_add_block(buf, str, strlen(str)); } /******************************************************************************* * Gets a data block from a buffer, returning the number of bytes copied. */ int fcgi_buf_get_to_block(Buffer *buf, char *data, int datalen) { char *end; int copied = 0; /* Number of bytes actually copied. */ int canCopy; /* Number of bytes to copy in a given op. */ ap_assert(data != NULL); ap_assert(datalen > 0); fcgi_buf_check(buf); end = buf->data + buf->size; /* * Copy the first part out of the buffer: from here to the end * of the buffer, or all of the requested data. */ canCopy = min(buf->length, datalen); canCopy = min(canCopy, end - buf->begin); memcpy(data, buf->begin, canCopy); buf->length -= canCopy; buf->begin += canCopy; copied += canCopy; if (buf->begin >= end) { buf->begin = buf->data; } /* * If there's more to go, copy the second part starting from the * beginning of the buffer. */ if (copied < datalen && buf->length > 0) { data += copied; canCopy = min(buf->length, datalen - copied); memcpy(data, buf->begin, canCopy); buf->length -= canCopy; buf->begin += canCopy; copied += canCopy; } fcgi_buf_check(buf); return(copied); } /******************************************************************************* * Move 'len' bytes from 'src' buffer to 'dest' buffer. There must be at * least 'len' bytes available in the source buffer and space for 'len' * bytes in the destination buffer. */ void fcgi_buf_get_to_buf(Buffer *dest, Buffer *src, int len) { char *dest_end, *src_begin; int dest_len, src_len, move_len; ap_assert(len > 0); ap_assert(BufferLength(src) >= len); ap_assert(BufferFree(dest) >= len); fcgi_buf_check(src); fcgi_buf_check(dest); while (1) { if (len == 0) return; fcgi_buf_get_free_block_info(dest, &dest_end, &dest_len); fcgi_buf_get_block_info(src, &src_begin, &src_len); move_len = min(dest_len, src_len); move_len = min(move_len, len); if (move_len == 0) return; memcpy(dest_end, src_begin, move_len); fcgi_buf_toss(src, move_len); fcgi_buf_add_update(dest, move_len); len -= move_len; } } static void array_grow(array_header *arr, int n) { if (n <= 0) return; if (arr->nelts + n > arr->nalloc) { char *new_elts; int new_nalloc = (arr->nalloc <= 0) ? n : arr->nelts + n; new_elts = ap_pcalloc(arr->pool, arr->elt_size * new_nalloc); memcpy(new_elts, arr->elts, arr->nelts * arr->elt_size); arr->elts = new_elts; arr->nalloc = new_nalloc; } } static void array_cat_block(array_header *arr, void *block, int n) { array_grow(arr, n); memcpy(arr->elts + arr->nelts * arr->elt_size, block, n * arr->elt_size); arr->nelts += n; } /*---------------------------------------------------------------------- * Append "len" bytes from "buf" into "arr". Apache arrays are used * whenever the data being handled is binary (may contain null chars). */ void fcgi_buf_get_to_array(Buffer *buf, array_header *arr, int len) { int len1 = min(buf->length, buf->data + buf->size - buf->begin); fcgi_buf_check(buf); ap_assert(len > 0); ap_assert(len <= BufferLength(buf)); array_grow(arr, len); len1 = min(len1, len); array_cat_block(arr, buf->begin, len1); if (len1 < len) array_cat_block(arr, buf->data, len - len1); fcgi_buf_toss(buf, len); }