#include <apr_file_io.h>
#include "svn_io.h"
#include "svn_pools.h"
#include "private/svn_subr_private.h"
struct memblock_t {
apr_size_t size;
char *data;
struct memblock_t *next;
};
struct svn_spillbuf_t {
apr_pool_t *pool;
apr_size_t blocksize;
apr_size_t maxsize;
apr_size_t memory_size;
struct memblock_t *head;
struct memblock_t *tail;
struct memblock_t *avail;
struct memblock_t *out_for_reading;
apr_file_t *spill;
apr_off_t spill_start;
svn_filesize_t spill_size;
svn_boolean_t delete_on_close;
svn_boolean_t spill_all_contents;
const char *dirpath;
const char *filename;
};
struct svn_spillbuf_reader_t {
struct svn_spillbuf_t *buf;
const char *sb_ptr;
apr_size_t sb_len;
char *save_ptr;
apr_size_t save_len;
apr_size_t save_pos;
};
static void
init_spillbuf_extended(svn_spillbuf_t *buf,
apr_size_t blocksize,
apr_size_t maxsize,
svn_boolean_t delete_on_close,
svn_boolean_t spill_all_contents,
const char *dirpath,
apr_pool_t *result_pool)
{
buf->pool = result_pool;
buf->blocksize = blocksize;
buf->maxsize = maxsize;
buf->delete_on_close = delete_on_close;
buf->spill_all_contents = spill_all_contents;
buf->dirpath = dirpath;
}
static void
init_spillbuf(svn_spillbuf_t *buf,
apr_size_t blocksize,
apr_size_t maxsize,
apr_pool_t *result_pool)
{
init_spillbuf_extended(buf, blocksize, maxsize,
TRUE, FALSE, NULL,
result_pool);
}
svn_spillbuf_t *
svn_spillbuf__create(apr_size_t blocksize,
apr_size_t maxsize,
apr_pool_t *result_pool)
{
svn_spillbuf_t *buf = apr_pcalloc(result_pool, sizeof(*buf));
init_spillbuf(buf, blocksize, maxsize, result_pool);
return buf;
}
svn_spillbuf_t *
svn_spillbuf__create_extended(apr_size_t blocksize,
apr_size_t maxsize,
svn_boolean_t delete_on_close,
svn_boolean_t spill_all_contents,
const char *dirpath,
apr_pool_t *result_pool)
{
svn_spillbuf_t *buf = apr_pcalloc(result_pool, sizeof(*buf));
init_spillbuf_extended(buf, blocksize, maxsize,
delete_on_close, spill_all_contents, dirpath,
result_pool);
return buf;
}
svn_filesize_t
svn_spillbuf__get_size(const svn_spillbuf_t *buf)
{
return buf->memory_size + buf->spill_size;
}
svn_filesize_t
svn_spillbuf__get_memory_size(const svn_spillbuf_t *buf)
{
return buf->memory_size;
}
const char *
svn_spillbuf__get_filename(const svn_spillbuf_t *buf)
{
return buf->filename;
}
apr_file_t *
svn_spillbuf__get_file(const svn_spillbuf_t *buf)
{
return buf->spill;
}
static struct memblock_t *
get_buffer(svn_spillbuf_t *buf)
{
struct memblock_t *mem = buf->out_for_reading;
if (mem != NULL)
{
buf->out_for_reading = NULL;
return mem;
}
if (buf->avail == NULL)
{
mem = apr_palloc(buf->pool, sizeof(*mem));
mem->data = apr_palloc(buf->pool, buf->blocksize);
return mem;
}
mem = buf->avail;
buf->avail = mem->next;
return mem;
}
static void
return_buffer(svn_spillbuf_t *buf,
struct memblock_t *mem)
{
mem->next = buf->avail;
buf->avail = mem;
}
svn_error_t *
svn_spillbuf__write(svn_spillbuf_t *buf,
const char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
struct memblock_t *mem;
if (buf->spill == NULL
&& ((buf->maxsize - buf->memory_size) < len))
{
SVN_ERR(svn_io_open_unique_file3(&buf->spill,
&buf->filename,
buf->dirpath,
(buf->delete_on_close
? svn_io_file_del_on_close
: svn_io_file_del_none),
buf->pool, scratch_pool));
if (buf->spill_all_contents)
{
mem = buf->head;
while (mem != NULL)
{
SVN_ERR(svn_io_file_write_full(buf->spill, mem->data, mem->size,
NULL, scratch_pool));
mem = mem->next;
}
buf->spill_start = buf->memory_size;
}
}
if (buf->spill != NULL)
{
apr_off_t output_unused = 0;
SVN_ERR(svn_io_file_seek(buf->spill,
APR_END, &output_unused,
scratch_pool));
SVN_ERR(svn_io_file_write_full(buf->spill, data, len,
NULL, scratch_pool));
buf->spill_size += len;
return SVN_NO_ERROR;
}
while (len > 0)
{
apr_size_t amt;
if (buf->tail == NULL || buf->tail->size == buf->blocksize)
{
mem = get_buffer(buf);
mem->size = 0;
mem->next = NULL;
}
else
{
mem = buf->tail;
}
amt = buf->blocksize - mem->size;
if (amt > len)
amt = len;
memcpy(&mem->data[mem->size], data, amt);
mem->size += amt;
data += amt;
len -= amt;
buf->memory_size += amt;
if (buf->tail == NULL)
{
buf->head = mem;
buf->tail = mem;
}
else if (mem != buf->tail)
{
buf->tail->next = mem;
buf->tail = mem;
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
read_data(struct memblock_t **mem,
svn_spillbuf_t *buf,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
if (buf->head != NULL)
{
*mem = buf->head;
if (buf->tail == *mem)
buf->head = buf->tail = NULL;
else
buf->head = (*mem)->next;
buf->memory_size -= (*mem)->size;
return SVN_NO_ERROR;
}
if (buf->spill == NULL)
{
*mem = NULL;
return SVN_NO_ERROR;
}
*mem = get_buffer(buf);
if ((apr_uint64_t)buf->spill_size < (apr_uint64_t)buf->blocksize)
(*mem)->size = (apr_size_t)buf->spill_size;
else
(*mem)->size = buf->blocksize;
(*mem)->next = NULL;
err = svn_io_file_read(buf->spill, (*mem)->data, &(*mem)->size,
scratch_pool);
if (err)
{
return_buffer(buf, *mem);
return svn_error_trace(err);
}
buf->spill_start += (*mem)->size;
if ((buf->spill_size -= (*mem)->size) == 0)
{
SVN_ERR(svn_io_file_close(buf->spill, scratch_pool));
buf->spill = NULL;
buf->spill_start = 0;
}
return SVN_NO_ERROR;
}
static svn_error_t *
maybe_seek(svn_boolean_t *seeked,
const svn_spillbuf_t *buf,
apr_pool_t *scratch_pool)
{
if (buf->head == NULL && buf->spill != NULL)
{
apr_off_t output_unused;
output_unused = buf->spill_start;
SVN_ERR(svn_io_file_seek(buf->spill,
APR_SET, &output_unused,
scratch_pool));
if (seeked != NULL)
*seeked = TRUE;
}
else if (seeked != NULL)
{
*seeked = FALSE;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_spillbuf__read(const char **data,
apr_size_t *len,
svn_spillbuf_t *buf,
apr_pool_t *scratch_pool)
{
struct memblock_t *mem;
SVN_ERR(maybe_seek(NULL, buf, scratch_pool));
SVN_ERR(read_data(&mem, buf, scratch_pool));
if (mem == NULL)
{
*data = NULL;
*len = 0;
}
else
{
*data = mem->data;
*len = mem->size;
if (buf->out_for_reading != NULL)
return_buffer(buf, buf->out_for_reading);
buf->out_for_reading = mem;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_spillbuf__process(svn_boolean_t *exhausted,
svn_spillbuf_t *buf,
svn_spillbuf_read_t read_func,
void *read_baton,
apr_pool_t *scratch_pool)
{
svn_boolean_t has_seeked = FALSE;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
*exhausted = FALSE;
while (TRUE)
{
struct memblock_t *mem;
svn_error_t *err;
svn_boolean_t stop;
svn_pool_clear(iterpool);
if (!has_seeked)
SVN_ERR(maybe_seek(&has_seeked, buf, iterpool));
SVN_ERR(read_data(&mem, buf, iterpool));
if (mem == NULL)
{
*exhausted = TRUE;
break;
}
err = read_func(&stop, read_baton, mem->data, mem->size, iterpool);
return_buffer(buf, mem);
if (err)
return svn_error_trace(err);
if (stop)
break;
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_spillbuf_reader_t *
svn_spillbuf__reader_create(apr_size_t blocksize,
apr_size_t maxsize,
apr_pool_t *result_pool)
{
svn_spillbuf_reader_t *sbr = apr_pcalloc(result_pool, sizeof(*sbr));
sbr->buf = svn_spillbuf__create(blocksize, maxsize, result_pool);
return sbr;
}
svn_error_t *
svn_spillbuf__reader_read(apr_size_t *amt,
svn_spillbuf_reader_t *reader,
char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
if (len == 0)
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, NULL);
*amt = 0;
while (len > 0)
{
apr_size_t copy_amt;
if (reader->save_len > 0)
{
if (len < reader->save_len)
copy_amt = len;
else
copy_amt = reader->save_len;
memcpy(data, reader->save_ptr + reader->save_pos, copy_amt);
reader->save_pos += copy_amt;
reader->save_len -= copy_amt;
}
else
{
if (reader->sb_len == 0)
{
SVN_ERR(svn_spillbuf__read(&reader->sb_ptr, &reader->sb_len,
reader->buf,
scratch_pool));
if (reader->sb_ptr == NULL)
{
reader->sb_len = 0;
return SVN_NO_ERROR;
}
}
if (len < reader->sb_len)
copy_amt = len;
else
copy_amt = reader->sb_len;
memcpy(data, reader->sb_ptr, copy_amt);
reader->sb_ptr += copy_amt;
reader->sb_len -= copy_amt;
}
data += copy_amt;
len -= copy_amt;
(*amt) += copy_amt;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_spillbuf__reader_getc(char *c,
svn_spillbuf_reader_t *reader,
apr_pool_t *scratch_pool)
{
apr_size_t amt;
SVN_ERR(svn_spillbuf__reader_read(&amt, reader, c, 1, scratch_pool));
if (amt == 0)
return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, NULL);
return SVN_NO_ERROR;
}
svn_error_t *
svn_spillbuf__reader_write(svn_spillbuf_reader_t *reader,
const char *data,
apr_size_t len,
apr_pool_t *scratch_pool)
{
if (reader->sb_len > 0)
{
if (reader->save_ptr == NULL)
reader->save_ptr = apr_palloc(reader->buf->pool,
reader->buf->blocksize);
memcpy(reader->save_ptr, reader->sb_ptr, reader->sb_len);
reader->save_len = reader->sb_len;
reader->save_pos = 0;
reader->sb_len = 0;
}
return svn_error_trace(svn_spillbuf__write(reader->buf, data, len,
scratch_pool));
}
struct spillbuf_baton
{
svn_spillbuf_reader_t *reader;
apr_pool_t *scratch_pool;
};
static svn_error_t *
read_handler_spillbuf(void *baton, char *buffer, apr_size_t *len)
{
struct spillbuf_baton *sb = baton;
SVN_ERR(svn_spillbuf__reader_read(len, sb->reader, buffer, *len,
sb->scratch_pool));
svn_pool_clear(sb->scratch_pool);
return SVN_NO_ERROR;
}
static svn_error_t *
write_handler_spillbuf(void *baton, const char *data, apr_size_t *len)
{
struct spillbuf_baton *sb = baton;
SVN_ERR(svn_spillbuf__reader_write(sb->reader, data, *len,
sb->scratch_pool));
svn_pool_clear(sb->scratch_pool);
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream__from_spillbuf(svn_spillbuf_t *buf,
apr_pool_t *result_pool)
{
svn_stream_t *stream;
struct spillbuf_baton *sb = apr_palloc(result_pool, sizeof(*sb));
sb->reader = apr_pcalloc(result_pool, sizeof(*sb->reader));
sb->reader->buf = buf;
sb->scratch_pool = svn_pool_create(result_pool);
stream = svn_stream_create(sb, result_pool);
svn_stream_set_read2(stream, NULL ,
read_handler_spillbuf);
svn_stream_set_write(stream, write_handler_spillbuf);
return stream;
}