/* skip up to length bytes in a chunkqueue, return number of bytes skipped */ goffset li_chunkqueue_skip(liChunkQueue *cq, goffset length) { liChunk *c; goffset bytes = 0; goffset we_have; while ( (NULL != (c = li_chunkqueue_first_chunk(cq))) && (0 == (we_have = li_chunk_length(c)) || length > 0) ) { if (we_have <= length) { /* skip (delete) complete chunk */ if (c->type == STRING_CHUNK) cqlimit_update(cq, - (goffset)c->data.str->len); else if (c->type == MEM_CHUNK) cqlimit_update(cq, - (goffset)c->mem->len); else if (c->type == BUFFER_CHUNK) cqlimit_update(cq, - (goffset)c->data.buffer.length); chunk_free(cq, c); bytes += we_have; length -= we_have; } else { /* skip first part of a chunk */ c->offset += length; bytes += length; length = 0; } } cq->bytes_out += bytes; cq->length -= bytes; return bytes; }
/* steal the first chunk from in and append it to out, return number of bytes stolen */ goffset li_chunkqueue_steal_chunk(liChunkQueue *out, liChunkQueue *in) { liChunk *c; goffset length; GList *l = g_queue_pop_head_link(&in->queue); if (!l) return 0; g_queue_push_tail_link(&out->queue, l); c = (liChunk*) l->data; length = li_chunk_length(c); in->bytes_out += length; in->length -= length; out->bytes_in += length; out->length += length; if (in->limit != out->limit) { if (c->type == STRING_CHUNK) { cqlimit_update(out, c->data.str->len); cqlimit_update(in, - (goffset)c->data.str->len); } else if (c->type == MEM_CHUNK) { cqlimit_update(out, c->mem->len); cqlimit_update(in, - (goffset)c->mem->len); } else if (c->type == BUFFER_CHUNK) { cqlimit_update(out, c->data.buffer.length); cqlimit_update(in, - (goffset)c->data.buffer.length); } } return length; }
/* first chunk must be a STRING_CHUNK ! */ liNetworkStatus li_network_backend_writev(int fd, liChunkQueue *cq, goffset *write_max, GError **err) { off_t we_have; ssize_t r; gboolean did_write_something = FALSE; liChunkIter ci; liChunk *c; liNetworkStatus res = LI_NETWORK_STATUS_FATAL_ERROR; GArray *chunks = g_array_sized_new(FALSE, TRUE, sizeof(struct iovec), UIO_MAXIOV); if (0 == cq->length) goto cleanup; /* FATAL ERROR */ do { ci = li_chunkqueue_iter(cq); if (STRING_CHUNK != (c = li_chunkiter_chunk(ci))->type && MEM_CHUNK != c->type && BUFFER_CHUNK != c->type) { res = did_write_something ? LI_NETWORK_STATUS_SUCCESS : LI_NETWORK_STATUS_FATAL_ERROR; goto cleanup; } we_have = 0; do { guint i = chunks->len; off_t len = li_chunk_length(c); struct iovec *v; g_array_set_size(chunks, i + 1); v = &g_array_index(chunks, struct iovec, i); if (c->type == STRING_CHUNK) { v->iov_base = c->data.str->str + c->offset; } else if (c->type == MEM_CHUNK) { v->iov_base = c->mem->data + c->offset; } else { /* if (c->type == BUFFER_CHUNK) */ v->iov_base = c->data.buffer.buffer->addr + c->data.buffer.offset + c->offset; } if (len > *write_max - we_have) len = *write_max - we_have; v->iov_len = len; we_have += len; } while (we_have < *write_max && li_chunkiter_next(&ci) && (STRING_CHUNK == (c = li_chunkiter_chunk(ci))->type || MEM_CHUNK == c->type || BUFFER_CHUNK == c->type) && chunks->len < UIO_MAXIOV); while (-1 == (r = writev(fd, (struct iovec*) chunks->data, chunks->len))) { switch (errno) { case EAGAIN: #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif res = LI_NETWORK_STATUS_WAIT_FOR_EVENT; goto cleanup; case ECONNRESET: case EPIPE: case ETIMEDOUT: res = LI_NETWORK_STATUS_CONNECTION_CLOSE; goto cleanup; case EINTR: break; /* try again */ default: g_set_error(err, LI_NETWORK_ERROR, 0, "li_network_backend_writev: oops, write to fd=%d failed: %s", fd, g_strerror(errno)); goto cleanup; } } if (0 == r) { res = LI_NETWORK_STATUS_WAIT_FOR_EVENT; goto cleanup; } li_chunkqueue_skip(cq, r); *write_max -= r; if (r != we_have) { res = LI_NETWORK_STATUS_WAIT_FOR_EVENT; goto cleanup; } if (0 == cq->length) { res = LI_NETWORK_STATUS_SUCCESS; goto cleanup; } did_write_something = TRUE; g_array_set_size(chunks, 0); } while (*write_max > 0); res = LI_NETWORK_STATUS_SUCCESS; cleanup: g_array_free(chunks, TRUE); return res; }
static void bod_handle_data(bod_state *state) { liChunkQueue *out = state->stream.out; liChunkQueue *in; if (out->is_closed) { li_stream_disconnect(&state->stream); bod_close(state); return; } in = (state->stream.source != NULL) ? state->stream.source->out : NULL; if (NULL == in) goto out; if (NULL == state->vr) { li_chunkqueue_steal_all(out, in); goto out; } while (in->length > 0) { liChunk *c = li_chunkqueue_first_chunk(in); liChunkIter ci; off_t length, data_len; char *data = NULL; GError *err; assert(UNUSED_CHUNK != c->type); switch (c->type) { case UNUSED_CHUNK: /* shouldn't happen anyway, but stealing it is ok here too */ case FILE_CHUNK: if (state->split_on_file_chunks) { bod_close(state); } else { bod_flush(state); } li_chunkqueue_steal_chunk(out, in); break; case STRING_CHUNK: case MEM_CHUNK: case BUFFER_CHUNK: if (!bod_open(state)) return; length = li_chunk_length(c); ci = li_chunkqueue_iter(in); err = NULL; if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) { if (NULL != err) { VR_ERROR(state->vr, "%s", err->message); g_error_free(err); } bod_error(state); return; } while ( data_len > 0 ) { ssize_t r; r = pwrite(state->tempfile->fd, data, data_len, state->write_pos); if (r < 0) { switch (errno) { case EINTR: continue; default: break; } VR_ERROR(state->vr, "pwrite failed: %s", g_strerror(errno)); bod_stop(state); /* write failures are not critical */ return; } data += r; data_len -= r; state->write_pos += r; } li_chunkqueue_skip(in, length); break; } } bod_autoflush(state); out: if (NULL == in || in->is_closed) { out->is_closed = TRUE; bod_close(state); /* close/flush ignores out->is_closed */ li_stream_notify(&state->stream); /* if no flush happened we still notify */ } }
liHandlerResult li_filter_buffer_on_disk(liVRequest *vr, liChunkQueue *out, liChunkQueue *in, bod_state *state) { UNUSED(vr); if (out->is_closed) { in->is_closed = TRUE; li_chunkqueue_skip_all(in); bod_close(state); return LI_HANDLER_GO_ON; } while (in->length > 0) { liChunk *c = li_chunkqueue_first_chunk(in); liChunkIter ci; off_t length, data_len; char *data = NULL; GError *err; switch (c->type) { case UNUSED_CHUNK: return LI_HANDLER_ERROR; case FILE_CHUNK: bod_flush(out, state); if (state->split_on_file_chunks) { bod_close(state); } li_chunkqueue_steal_chunk(out, in); break; case STRING_CHUNK: case MEM_CHUNK: case BUFFER_CHUNK: if (!bod_open(vr, state)) return LI_HANDLER_ERROR; length = li_chunk_length(c); ci = li_chunkqueue_iter(in); err = NULL; if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) { if (NULL != err) { VR_ERROR(vr, "%s", err->message); g_error_free(err); } return LI_HANDLER_ERROR; } while ( data_len > 0 ) { ssize_t r; r = pwrite(state->tempfile->fd, data, data_len, state->write_pos); if (r < 0) { switch (errno) { case EINTR: continue; default: break; } VR_ERROR(vr, "pwrite failed: %s", g_strerror(errno)); return LI_HANDLER_ERROR; } data += r; data_len -= r; state->write_pos += r; } li_chunkqueue_skip(in, length); break; } } bod_autoflush(out, state); if (in->is_closed) { bod_flush(out, state); out->is_closed = TRUE; bod_close(state); return LI_HANDLER_GO_ON; } return LI_HANDLER_GO_ON; }
/* get the data from a chunk; easy in case of a STRING_CHUNK, * but needs to do io in case of FILE_CHUNK; the data is _not_ marked as "done" * may return HANDLER_GO_ON, HANDLER_ERROR */ liHandlerResult li_chunkiter_read(liVRequest *vr, liChunkIter iter, off_t start, off_t length, char **data_start, off_t *data_len) { liChunk *c = li_chunkiter_chunk(iter); off_t we_have, our_start; liHandlerResult res = LI_HANDLER_GO_ON; if (!c) return LI_HANDLER_ERROR; if (!data_start || !data_len) return LI_HANDLER_ERROR; we_have = li_chunk_length(c) - start; if (length > we_have) length = we_have; if (length <= 0) return LI_HANDLER_ERROR; switch (c->type) { case UNUSED_CHUNK: return LI_HANDLER_ERROR; case STRING_CHUNK: *data_start = c->data.str->str + c->offset + start; *data_len = length; break; case MEM_CHUNK: *data_start = (char*) c->mem->data + c->offset + start; *data_len = length; break; case FILE_CHUNK: if (LI_HANDLER_GO_ON != (res = li_chunkfile_open(vr, c->data.file.file))) return res; if (length > MAX_MMAP_CHUNK) length = MAX_MMAP_CHUNK; if (!c->mem) { c->mem = g_byte_array_sized_new(length); } else { g_byte_array_set_size(c->mem, length); } our_start = start + c->offset + c->data.file.start; read_chunk: if (-1 == (we_have = pread(c->data.file.file->fd, c->mem->data, length, our_start))) { if (EINTR == errno) goto read_chunk; VR_ERROR(vr, "pread failed for '%s' (fd = %i): %s", GSTR_SAFE_STR(c->data.file.file->name), c->data.file.file->fd, g_strerror(errno)); g_byte_array_free(c->mem, TRUE); c->mem = NULL; return LI_HANDLER_ERROR; } else if (we_have != length) { /* may return less than requested bytes due to signals */ /* CON_TRACE(srv, "read return unexpected number of bytes"); */ if (we_have == 0) { VR_ERROR(vr, "pread returned 0 bytes for '%s' (fd = %i): unexpected end of file?", GSTR_SAFE_STR(c->data.file.file->name), c->data.file.file->fd); g_byte_array_free(c->mem, TRUE); c->mem = NULL; return LI_HANDLER_ERROR; } length = we_have; g_byte_array_set_size(c->mem, length); } *data_start = (char*) c->mem->data; *data_len = length; break; case BUFFER_CHUNK: *data_start = (char*) c->data.buffer.buffer->addr + c->data.buffer.offset + c->offset + start; *data_len = length; break; } return LI_HANDLER_GO_ON; }
/* steal up to length bytes from in and put them into out, return number of bytes stolen */ goffset li_chunkqueue_steal_len(liChunkQueue *out, liChunkQueue *in, goffset length) { liChunk *c, *cnew; GList* l; goffset bytes = 0, meminbytes = 0, memoutbytes = 0; goffset we_have; while ( (NULL != (c = li_chunkqueue_first_chunk(in))) && length > 0 ) { we_have = li_chunk_length(c); if (!we_have) { /* remove empty chunks */ if (c->type == STRING_CHUNK) meminbytes -= c->data.str->len; else if (c->type == MEM_CHUNK) meminbytes -= c->mem->len; else if (c->type == BUFFER_CHUNK) meminbytes -= c->data.buffer.length; chunk_free(in, c); continue; } if (we_have <= length) { /* move complete chunk */ l = g_queue_pop_head_link(&in->queue); g_queue_push_tail_link(&out->queue, l); bytes += we_have; if (c->type == STRING_CHUNK) { meminbytes -= c->data.str->len; memoutbytes += c->data.str->len; } else if (c->type == MEM_CHUNK) { meminbytes -= c->mem->len; memoutbytes += c->mem->len; } else if (c->type == BUFFER_CHUNK) { meminbytes -= c->data.buffer.length; memoutbytes += c->data.buffer.length; } length -= we_have; } else { /* copy first part of a chunk */ cnew = chunk_new(); switch (c->type) { case UNUSED_CHUNK: /* impossible, has length 0 */ /* remove "empty" chunks */ chunk_free(in, c); chunk_free(NULL, cnew); continue; case STRING_CHUNK: /* change type to MEM_CHUNK, as we copy it anyway */ cnew->type = MEM_CHUNK; cnew->mem = g_byte_array_sized_new(length); g_byte_array_append(cnew->mem, (guint8*) c->data.str->str + c->offset, length); memoutbytes += length; break; case MEM_CHUNK: cnew->type = MEM_CHUNK; cnew->mem = g_byte_array_sized_new(length); g_byte_array_append(cnew->mem, (guint8*) c->mem->data + c->offset, length); memoutbytes += length; break; case FILE_CHUNK: cnew->type = FILE_CHUNK; li_chunkfile_acquire(c->data.file.file); cnew->data.file.file = c->data.file.file; cnew->data.file.start = c->data.file.start + c->offset; cnew->data.file.length = length; break; case BUFFER_CHUNK: cnew->type = BUFFER_CHUNK; li_buffer_acquire(c->data.buffer.buffer); cnew->data.buffer.buffer = c->data.buffer.buffer; cnew->data.buffer.offset = c->data.buffer.offset + c->offset; cnew->data.buffer.length = length; memoutbytes += length; break; } c->offset += length; bytes += length; length = 0; g_queue_push_tail_link(&out->queue, &cnew->cq_link); } } in->bytes_out += bytes; in->length -= bytes; out->bytes_in += bytes; out->length += bytes; cqlimit_update(out, memoutbytes); cqlimit_update(in, meminbytes); return bytes; }
/* same as li_chunkiter_read, but tries mmap() first and falls back to pread(); * as accessing mmap()-ed areas may result in SIGBUS, you have to handle that signal somehow. */ liHandlerResult li_chunkiter_read_mmap(liVRequest *vr, liChunkIter iter, off_t start, off_t length, char **data_start, off_t *data_len) { liChunk *c = li_chunkiter_chunk(iter); off_t we_want, we_have, our_start, our_offset; liHandlerResult res = LI_HANDLER_GO_ON; int mmap_errno = 0; if (!c) return LI_HANDLER_ERROR; if (!data_start || !data_len) return LI_HANDLER_ERROR; we_have = li_chunk_length(c) - start; if (length > we_have) length = we_have; if (length <= 0) return LI_HANDLER_ERROR; switch (c->type) { case UNUSED_CHUNK: return LI_HANDLER_ERROR; case STRING_CHUNK: *data_start = c->data.str->str + c->offset + start; *data_len = length; break; case MEM_CHUNK: *data_start = (char*) c->mem->data + c->offset + start; *data_len = length; break; case FILE_CHUNK: if (LI_HANDLER_GO_ON != (res = li_chunkfile_open(vr, c->data.file.file))) return res; if (length > MAX_MMAP_CHUNK) length = MAX_MMAP_CHUNK; if ( !(c->data.file.mmap.data != MAP_FAILED || c->mem) /* no data present */ || !( /* or in the wrong range */ (start + c->offset + c->data.file.start >= c->data.file.mmap.offset) && (start + c->offset + c->data.file.start + length <= c->data.file.mmap.offset + (ssize_t) c->data.file.mmap.length)) ) { /* then find new range */ our_start = start + c->offset + c->data.file.start; /* "start" maps to this offset in the file */ our_offset = our_start % MMAP_CHUNK_ALIGN; /* offset for "start" in new mmap block */ our_start -= our_offset; /* file offset for mmap */ we_want = length + MAX_MMAP_CHUNK; if (we_want > we_have) we_want = we_have; we_want += our_offset; if (MAP_FAILED != c->data.file.mmap.data) { munmap(c->data.file.mmap.data, c->data.file.mmap.length); c->data.file.mmap.data = MAP_FAILED; } c->data.file.mmap.offset = our_start; c->data.file.mmap.length = we_want; if (!c->mem) { /* mmap did not fail till now */ c->data.file.mmap.data = mmap(0, we_want, PROT_READ, MAP_SHARED, c->data.file.file->fd, our_start); mmap_errno = errno; } if (MAP_FAILED == c->data.file.mmap.data) { /* fallback to pread(...) */ if (!c->mem) { c->mem = g_byte_array_sized_new(we_want); } else { g_byte_array_set_size(c->mem, we_want); } read_chunk: if (-1 == (we_have = pread(c->data.file.file->fd, c->mem->data, we_want, our_start))) { if (EINTR == errno) goto read_chunk; /* prefer the error of the first syscall */ if (0 != mmap_errno) { VR_ERROR(vr, "mmap failed for '%s' (fd = %i): %s", GSTR_SAFE_STR(c->data.file.file->name), c->data.file.file->fd, g_strerror(mmap_errno)); } else { VR_ERROR(vr, "pread failed for '%s' (fd = %i): %s", GSTR_SAFE_STR(c->data.file.file->name), c->data.file.file->fd, g_strerror(errno)); } g_byte_array_free(c->mem, TRUE); c->mem = NULL; return LI_HANDLER_ERROR; } else if (we_have != we_want) { /* may return less than requested bytes due to signals */ /* CON_TRACE(srv, "read return unexpected number of bytes"); */ we_want = we_have; if (length > we_have) length = we_have; c->data.file.mmap.length = we_want; g_byte_array_set_size(c->mem, we_want); } } else { #ifdef HAVE_MADVISE /* don't advise files < 64Kb */ if (c->data.file.mmap.length > (64*1024) && 0 != madvise(c->data.file.mmap.data, c->data.file.mmap.length, MADV_WILLNEED)) { VR_ERROR(vr, "madvise failed for '%s' (fd = %i): %s", GSTR_SAFE_STR(c->data.file.file->name), c->data.file.file->fd, g_strerror(errno)); } #endif } } *data_start = (c->mem ? (char*) c->mem->data : c->data.file.mmap.data) + start + c->offset + c->data.file.start - c->data.file.mmap.offset; *data_len = length; break; case BUFFER_CHUNK: *data_start = (char*) c->data.buffer.buffer->addr + c->data.buffer.offset + c->offset + start; *data_len = length; break; } return LI_HANDLER_GO_ON; }