2 * spillbuf.c : an in-memory buffer that can spill to disk
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 #include <apr_file_io.h>
27 #include "svn_pools.h"
29 #include "private/svn_subr_private.h"
36 struct memblock_t
*next
;
40 struct svn_spillbuf_t
{
41 /* Pool for allocating blocks and the spill file. */
44 /* Size of in-memory blocks. */
47 /* Maximum in-memory size; start spilling when we reach this size. */
50 /* The amount of content in memory. */
51 apr_size_t memory_size
;
53 /* HEAD points to the first block of the linked list of buffers.
54 TAIL points to the last block, for quickly appending more blocks
55 to the overall list. */
56 struct memblock_t
*head
;
57 struct memblock_t
*tail
;
59 /* Available blocks for storing pending data. These were allocated
60 previously, then the data consumed and returned to this list. */
61 struct memblock_t
*avail
;
63 /* When a block is borrowed for reading, it is listed here. */
64 struct memblock_t
*out_for_reading
;
66 /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be
67 appended to the (temporary) file indicated by SPILL. */
70 /* As we consume content from SPILL, this value indicates where we
71 will begin reading. */
72 apr_off_t spill_start
;
74 /* How much content remains in SPILL. */
75 svn_filesize_t spill_size
;
77 /* When false, do not delete the spill file when it is closed. */
78 svn_boolean_t delete_on_close
;
80 /* When true, and the amount of data written to the spillbuf is
81 larger than MAXSIZE, all spillbuf contents will be written to the
83 svn_boolean_t spill_all_contents
;
85 /* The directory in which the spill file is created. */
88 /* The name of the temporary spill file. */
93 struct svn_spillbuf_reader_t
{
94 /* Embed the spill-buffer within the reader. */
95 struct svn_spillbuf_t
*buf
;
97 /* When we read content from the underlying spillbuf, these fields store
98 the ptr/len pair. The ptr will be incremented as we "read" out of this
99 buffer since we don't have to retain the original pointer (it is
100 managed inside of the spillbuf). */
104 /* If a write comes in, then we may need to save content from our
105 borrowed buffer (since that buffer may be destroyed by our call into
106 the spillbuf code). Note that we retain the original pointer since
107 this buffer is allocated by the reader code and re-used. The SAVE_POS
108 field indicates the current position within this save buffer. The
109 SAVE_LEN field describes how much content is present. */
116 /* Extended spillbuf initialization. */
118 init_spillbuf_extended(svn_spillbuf_t
*buf
,
119 apr_size_t blocksize
,
121 svn_boolean_t delete_on_close
,
122 svn_boolean_t spill_all_contents
,
124 apr_pool_t
*result_pool
)
126 buf
->pool
= result_pool
;
127 buf
->blocksize
= blocksize
;
128 buf
->maxsize
= maxsize
;
129 buf
->delete_on_close
= delete_on_close
;
130 buf
->spill_all_contents
= spill_all_contents
;
131 buf
->dirpath
= dirpath
;
134 /* Common constructor for initializing spillbufs.
135 Used by svn_spillbuf__create, svn_spilbuff__reader_create. */
137 init_spillbuf(svn_spillbuf_t
*buf
,
138 apr_size_t blocksize
,
140 apr_pool_t
*result_pool
)
142 init_spillbuf_extended(buf
, blocksize
, maxsize
,
148 svn_spillbuf__create(apr_size_t blocksize
,
150 apr_pool_t
*result_pool
)
152 svn_spillbuf_t
*buf
= apr_pcalloc(result_pool
, sizeof(*buf
));
153 init_spillbuf(buf
, blocksize
, maxsize
, result_pool
);
159 svn_spillbuf__create_extended(apr_size_t blocksize
,
161 svn_boolean_t delete_on_close
,
162 svn_boolean_t spill_all_contents
,
164 apr_pool_t
*result_pool
)
166 svn_spillbuf_t
*buf
= apr_pcalloc(result_pool
, sizeof(*buf
));
167 init_spillbuf_extended(buf
, blocksize
, maxsize
,
168 delete_on_close
, spill_all_contents
, dirpath
,
174 svn_spillbuf__get_size(const svn_spillbuf_t
*buf
)
176 return buf
->memory_size
+ buf
->spill_size
;
180 svn_spillbuf__get_memory_size(const svn_spillbuf_t
*buf
)
182 return buf
->memory_size
;
186 svn_spillbuf__get_filename(const svn_spillbuf_t
*buf
)
188 return buf
->filename
;
192 svn_spillbuf__get_file(const svn_spillbuf_t
*buf
)
197 /* Get a memblock from the spill-buffer. It will be the block that we
198 passed out for reading, come from the free list, or allocated. */
199 static struct memblock_t
*
200 get_buffer(svn_spillbuf_t
*buf
)
202 struct memblock_t
*mem
= buf
->out_for_reading
;
206 buf
->out_for_reading
= NULL
;
210 if (buf
->avail
== NULL
)
212 mem
= apr_palloc(buf
->pool
, sizeof(*mem
));
213 mem
->data
= apr_palloc(buf
->pool
, buf
->blocksize
);
218 buf
->avail
= mem
->next
;
223 /* Return MEM to the list of available buffers in BUF. */
225 return_buffer(svn_spillbuf_t
*buf
,
226 struct memblock_t
*mem
)
228 mem
->next
= buf
->avail
;
234 svn_spillbuf__write(svn_spillbuf_t
*buf
,
237 apr_pool_t
*scratch_pool
)
239 struct memblock_t
*mem
;
241 /* We do not (yet) have a spill file, but the amount stored in memory
242 will grow too large. Create the file and place the pending data into
243 the temporary file. */
244 if (buf
->spill
== NULL
245 && ((buf
->maxsize
- buf
->memory_size
) < len
))
247 SVN_ERR(svn_io_open_unique_file3(&buf
->spill
,
250 (buf
->delete_on_close
251 ? svn_io_file_del_on_close
252 : svn_io_file_del_none
),
253 buf
->pool
, scratch_pool
));
255 /* Optionally write the memory contents into the file. */
256 if (buf
->spill_all_contents
)
261 SVN_ERR(svn_io_file_write_full(buf
->spill
, mem
->data
, mem
->size
,
262 NULL
, scratch_pool
));
266 /* Adjust the start offset for reading from the spill file.
268 This way, the first `buf->memory_size` bytes of data will
269 be read from the existing in-memory buffers, which makes
270 more sense than discarding the buffers and re-reading
271 data from the file. */
272 buf
->spill_start
= buf
->memory_size
;
276 /* Once a spill file has been constructed, then we need to put all
277 arriving data into the file. We will no longer attempt to hold it
279 if (buf
->spill
!= NULL
)
281 apr_off_t output_unused
= 0; /* ### stupid API */
283 /* Seek to the end of the spill file. We don't know if a read has
284 occurred since our last write, and moved the file position. */
285 SVN_ERR(svn_io_file_seek(buf
->spill
,
286 APR_END
, &output_unused
,
289 SVN_ERR(svn_io_file_write_full(buf
->spill
, data
, len
,
290 NULL
, scratch_pool
));
291 buf
->spill_size
+= len
;
300 if (buf
->tail
== NULL
|| buf
->tail
->size
== buf
->blocksize
)
302 /* There is no existing memblock (that may have space), or the
303 tail memblock has no space, so we need a new memblock. */
304 mem
= get_buffer(buf
);
313 /* Compute how much to write into the memblock. */
314 amt
= buf
->blocksize
- mem
->size
;
318 /* Copy some data into this memblock. */
319 memcpy(&mem
->data
[mem
->size
], data
, amt
);
324 /* We need to record how much is buffered in memory. Once we reach
325 buf->maxsize (or thereabouts, it doesn't have to be precise), then
326 we'll switch to putting the content into a file. */
327 buf
->memory_size
+= amt
;
329 /* Start a list of buffers, or (if we're not writing into the tail)
330 append to the end of the linked list of buffers. */
331 if (buf
->tail
== NULL
)
336 else if (mem
!= buf
->tail
)
338 buf
->tail
->next
= mem
;
347 /* Return a memblock of content, if any is available. *mem will be NULL if
348 no further content is available. The memblock should eventually be
349 passed to return_buffer() (or stored into buf->out_for_reading which
350 will grab that block at the next get_buffer() call). */
352 read_data(struct memblock_t
**mem
,
354 apr_pool_t
*scratch_pool
)
358 /* If we have some in-memory blocks, then return one. */
359 if (buf
->head
!= NULL
)
362 if (buf
->tail
== *mem
)
363 buf
->head
= buf
->tail
= NULL
;
365 buf
->head
= (*mem
)->next
;
367 /* We're using less memory now. If we haven't hit the spill file,
368 then we may be able to keep using memory. */
369 buf
->memory_size
-= (*mem
)->size
;
375 if (buf
->spill
== NULL
)
381 /* Assume that the caller has seeked the spill file to the correct pos. */
383 /* Get a buffer that we can read content into. */
384 *mem
= get_buffer(buf
);
385 /* NOTE: mem's size/next are uninitialized. */
387 if ((apr_uint64_t
)buf
->spill_size
< (apr_uint64_t
)buf
->blocksize
)
388 (*mem
)->size
= (apr_size_t
)buf
->spill_size
;
390 (*mem
)->size
= buf
->blocksize
; /* The size of (*mem)->data */
393 /* Read some data from the spill file into the memblock. */
394 err
= svn_io_file_read(buf
->spill
, (*mem
)->data
, &(*mem
)->size
,
398 return_buffer(buf
, *mem
);
399 return svn_error_trace(err
);
402 /* Mark the data that we consumed from the spill file. */
403 buf
->spill_start
+= (*mem
)->size
;
405 /* Did we consume all the data from the spill file? */
406 if ((buf
->spill_size
-= (*mem
)->size
) == 0)
408 /* Close and reset our spill file information. */
409 SVN_ERR(svn_io_file_close(buf
->spill
, scratch_pool
));
411 buf
->spill_start
= 0;
414 /* *mem has been initialized. Done. */
419 /* If the next read would consume data from the file, then seek to the
422 maybe_seek(svn_boolean_t
*seeked
,
423 const svn_spillbuf_t
*buf
,
424 apr_pool_t
*scratch_pool
)
426 if (buf
->head
== NULL
&& buf
->spill
!= NULL
)
428 apr_off_t output_unused
;
430 /* Seek to where we left off reading. */
431 output_unused
= buf
->spill_start
; /* ### stupid API */
432 SVN_ERR(svn_io_file_seek(buf
->spill
,
433 APR_SET
, &output_unused
,
438 else if (seeked
!= NULL
)
448 svn_spillbuf__read(const char **data
,
451 apr_pool_t
*scratch_pool
)
453 struct memblock_t
*mem
;
455 /* Possibly seek... */
456 SVN_ERR(maybe_seek(NULL
, buf
, scratch_pool
));
458 SVN_ERR(read_data(&mem
, buf
, scratch_pool
));
469 /* If a block was out for reading, then return it. */
470 if (buf
->out_for_reading
!= NULL
)
471 return_buffer(buf
, buf
->out_for_reading
);
473 /* Remember that we've passed this block out for reading. */
474 buf
->out_for_reading
= mem
;
482 svn_spillbuf__process(svn_boolean_t
*exhausted
,
484 svn_spillbuf_read_t read_func
,
486 apr_pool_t
*scratch_pool
)
488 svn_boolean_t has_seeked
= FALSE
;
489 apr_pool_t
*iterpool
= svn_pool_create(scratch_pool
);
495 struct memblock_t
*mem
;
499 svn_pool_clear(iterpool
);
501 /* If this call to read_data() will read from the spill file, and we
502 have not seek'd the file... then do it now. */
504 SVN_ERR(maybe_seek(&has_seeked
, buf
, iterpool
));
506 /* Get some content to pass to the read callback. */
507 SVN_ERR(read_data(&mem
, buf
, iterpool
));
514 err
= read_func(&stop
, read_baton
, mem
->data
, mem
->size
, iterpool
);
516 return_buffer(buf
, mem
);
519 return svn_error_trace(err
);
521 /* If the callbacks told us to stop, then we're done for now. */
526 svn_pool_destroy(iterpool
);
531 svn_spillbuf_reader_t
*
532 svn_spillbuf__reader_create(apr_size_t blocksize
,
534 apr_pool_t
*result_pool
)
536 svn_spillbuf_reader_t
*sbr
= apr_pcalloc(result_pool
, sizeof(*sbr
));
537 sbr
->buf
= svn_spillbuf__create(blocksize
, maxsize
, result_pool
);
542 svn_spillbuf__reader_read(apr_size_t
*amt
,
543 svn_spillbuf_reader_t
*reader
,
546 apr_pool_t
*scratch_pool
)
549 return svn_error_create(SVN_ERR_INCORRECT_PARAMS
, NULL
, NULL
);
557 if (reader
->save_len
> 0)
559 /* We have some saved content, so use this first. */
561 if (len
< reader
->save_len
)
564 copy_amt
= reader
->save_len
;
566 memcpy(data
, reader
->save_ptr
+ reader
->save_pos
, copy_amt
);
567 reader
->save_pos
+= copy_amt
;
568 reader
->save_len
-= copy_amt
;
572 /* No saved content. We should now copy from spillbuf-provided
573 buffers of content. */
575 /* We may need more content from the spillbuf. */
576 if (reader
->sb_len
== 0)
578 SVN_ERR(svn_spillbuf__read(&reader
->sb_ptr
, &reader
->sb_len
,
582 /* We've run out of content, so return with whatever has
583 been copied into DATA and stored into AMT. */
584 if (reader
->sb_ptr
== NULL
)
586 /* For safety, read() may not have set SB_LEN. We use it
587 as an indicator, so it needs to be cleared. */
593 if (len
< reader
->sb_len
)
596 copy_amt
= reader
->sb_len
;
598 memcpy(data
, reader
->sb_ptr
, copy_amt
);
599 reader
->sb_ptr
+= copy_amt
;
600 reader
->sb_len
-= copy_amt
;
613 svn_spillbuf__reader_getc(char *c
,
614 svn_spillbuf_reader_t
*reader
,
615 apr_pool_t
*scratch_pool
)
619 SVN_ERR(svn_spillbuf__reader_read(&amt
, reader
, c
, 1, scratch_pool
));
621 return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF
, NULL
, NULL
);
628 svn_spillbuf__reader_write(svn_spillbuf_reader_t
*reader
,
631 apr_pool_t
*scratch_pool
)
633 /* If we have a buffer of content from the spillbuf, then we need to
634 move that content to a safe place. */
635 if (reader
->sb_len
> 0)
637 if (reader
->save_ptr
== NULL
)
638 reader
->save_ptr
= apr_palloc(reader
->buf
->pool
,
639 reader
->buf
->blocksize
);
641 memcpy(reader
->save_ptr
, reader
->sb_ptr
, reader
->sb_len
);
642 reader
->save_len
= reader
->sb_len
;
643 reader
->save_pos
= 0;
645 /* No more content in the spillbuf-borrowed buffer. */
649 return svn_error_trace(svn_spillbuf__write(reader
->buf
, data
, len
,
654 struct spillbuf_baton
656 svn_spillbuf_reader_t
*reader
;
657 apr_pool_t
*scratch_pool
;
662 read_handler_spillbuf(void *baton
, char *buffer
, apr_size_t
*len
)
664 struct spillbuf_baton
*sb
= baton
;
666 SVN_ERR(svn_spillbuf__reader_read(len
, sb
->reader
, buffer
, *len
,
669 svn_pool_clear(sb
->scratch_pool
);
675 write_handler_spillbuf(void *baton
, const char *data
, apr_size_t
*len
)
677 struct spillbuf_baton
*sb
= baton
;
679 SVN_ERR(svn_spillbuf__reader_write(sb
->reader
, data
, *len
,
682 svn_pool_clear(sb
->scratch_pool
);
688 svn_stream__from_spillbuf(svn_spillbuf_t
*buf
,
689 apr_pool_t
*result_pool
)
691 svn_stream_t
*stream
;
692 struct spillbuf_baton
*sb
= apr_palloc(result_pool
, sizeof(*sb
));
694 sb
->reader
= apr_pcalloc(result_pool
, sizeof(*sb
->reader
));
695 sb
->reader
->buf
= buf
;
696 sb
->scratch_pool
= svn_pool_create(result_pool
);
698 stream
= svn_stream_create(sb
, result_pool
);
700 svn_stream_set_read2(stream
, NULL
/* only full read support */,
701 read_handler_spillbuf
);
702 svn_stream_set_write(stream
, write_handler_spillbuf
);