blob: 5dafac5b26b2e25541283a76dea7dea52a65d22a [file] [log] [blame]
/*
* stream.c: svn_stream operations
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <assert.h>
#include <stdio.h>
#include <apr.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_file_io.h>
#include <apr_errno.h>
#include <apr_poll.h>
#include <apr_portable.h>
#include <zlib.h>
#include "svn_pools.h"
#include "svn_io.h"
#include "svn_error.h"
#include "svn_string.h"
#include "svn_utf.h"
#include "svn_checksum.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "svn_sorts.h"
#include "private/svn_atomic.h"
#include "private/svn_error_private.h"
#include "private/svn_eol_private.h"
#include "private/svn_io_private.h"
#include "private/svn_subr_private.h"
#include "private/svn_utf_private.h"
struct svn_stream_t {
void *baton;
svn_read_fn_t read_fn;
svn_read_fn_t read_full_fn;
svn_stream_skip_fn_t skip_fn;
svn_write_fn_t write_fn;
svn_close_fn_t close_fn;
svn_stream_mark_fn_t mark_fn;
svn_stream_seek_fn_t seek_fn;
svn_stream_data_available_fn_t data_available_fn;
svn_stream_readline_fn_t readline_fn;
apr_file_t *file; /* Maybe NULL */
};
/*** Forward declarations. ***/
static svn_error_t *
skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_full_fn);
/*** Generic streams. ***/
svn_stream_t *
svn_stream_create(void *baton, apr_pool_t *pool)
{
svn_stream_t *stream;
stream = apr_pcalloc(pool, sizeof(*stream));
stream->baton = baton;
return stream;
}
void
svn_stream_set_baton(svn_stream_t *stream, void *baton)
{
stream->baton = baton;
}
void
svn_stream_set_read2(svn_stream_t *stream,
svn_read_fn_t read_fn,
svn_read_fn_t read_full_fn)
{
stream->read_fn = read_fn;
stream->read_full_fn = read_full_fn;
}
void
svn_stream_set_skip(svn_stream_t *stream, svn_stream_skip_fn_t skip_fn)
{
stream->skip_fn = skip_fn;
}
void
svn_stream_set_write(svn_stream_t *stream, svn_write_fn_t write_fn)
{
stream->write_fn = write_fn;
}
void
svn_stream_set_close(svn_stream_t *stream, svn_close_fn_t close_fn)
{
stream->close_fn = close_fn;
}
void
svn_stream_set_mark(svn_stream_t *stream, svn_stream_mark_fn_t mark_fn)
{
stream->mark_fn = mark_fn;
}
void
svn_stream_set_seek(svn_stream_t *stream, svn_stream_seek_fn_t seek_fn)
{
stream->seek_fn = seek_fn;
}
void
svn_stream_set_data_available(svn_stream_t *stream,
svn_stream_data_available_fn_t data_available_fn)
{
stream->data_available_fn = data_available_fn;
}
void
svn_stream_set_readline(svn_stream_t *stream,
svn_stream_readline_fn_t readline_fn)
{
stream->readline_fn = readline_fn;
}
/* Standard implementation for svn_stream_read_full() based on
multiple svn_stream_read2() calls (in separate function to make
it more likely for svn_stream_read_full to be inlined) */
static svn_error_t *
full_read_fallback(svn_stream_t *stream, char *buffer, apr_size_t *len)
{
apr_size_t remaining = *len;
while (remaining > 0)
{
apr_size_t length = remaining;
SVN_ERR(svn_stream_read2(stream, buffer, &length));
if (length == 0)
{
*len -= remaining;
return SVN_NO_ERROR;
}
remaining -= length;
buffer += length;
}
return SVN_NO_ERROR;
}
svn_boolean_t
svn_stream_supports_partial_read(svn_stream_t *stream)
{
return stream->read_fn != NULL;
}
svn_error_t *
svn_stream_read2(svn_stream_t *stream, char *buffer, apr_size_t *len)
{
if (stream->read_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(stream->read_fn(stream->baton, buffer, len));
}
svn_error_t *
svn_stream_read_full(svn_stream_t *stream, char *buffer, apr_size_t *len)
{
if (stream->read_full_fn == NULL)
return svn_error_trace(full_read_fallback(stream, buffer, len));
return svn_error_trace(stream->read_full_fn(stream->baton, buffer, len));
}
svn_error_t *
svn_stream_skip(svn_stream_t *stream, apr_size_t len)
{
if (stream->skip_fn == NULL)
{
svn_read_fn_t read_fn = stream->read_full_fn ? stream->read_full_fn
: stream->read_fn;
if (read_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(skip_default_handler(stream->baton, len,
read_fn));
}
return svn_error_trace(stream->skip_fn(stream->baton, len));
}
svn_error_t *
svn_stream_write(svn_stream_t *stream, const char *data, apr_size_t *len)
{
if (stream->write_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(stream->write_fn(stream->baton, data, len));
}
svn_error_t *
svn_stream_reset(svn_stream_t *stream)
{
return svn_error_trace(
svn_stream_seek(stream, NULL));
}
svn_boolean_t
svn_stream_supports_mark(svn_stream_t *stream)
{
return stream->mark_fn != NULL;
}
svn_boolean_t
svn_stream_supports_reset(svn_stream_t *stream)
{
return stream->seek_fn != NULL;
}
svn_error_t *
svn_stream_mark(svn_stream_t *stream, svn_stream_mark_t **mark,
apr_pool_t *pool)
{
if (stream->mark_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(stream->mark_fn(stream->baton, mark, pool));
}
svn_error_t *
svn_stream_seek(svn_stream_t *stream, const svn_stream_mark_t *mark)
{
if (stream->seek_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(stream->seek_fn(stream->baton, mark));
}
svn_error_t *
svn_stream_data_available(svn_stream_t *stream,
svn_boolean_t *data_available)
{
if (stream->data_available_fn == NULL)
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED, NULL, NULL);
return svn_error_trace(stream->data_available_fn(stream->baton,
data_available));
}
svn_error_t *
svn_stream_close(svn_stream_t *stream)
{
if (stream->close_fn == NULL)
return SVN_NO_ERROR;
return svn_error_trace(stream->close_fn(stream->baton));
}
svn_error_t *
svn_stream_puts(svn_stream_t *stream,
const char *str)
{
apr_size_t len;
len = strlen(str);
return svn_error_trace(svn_stream_write(stream, str, &len));
}
svn_error_t *
svn_stream_printf(svn_stream_t *stream,
apr_pool_t *pool,
const char *fmt,
...)
{
const char *message;
va_list ap;
va_start(ap, fmt);
message = apr_pvsprintf(pool, fmt, ap);
va_end(ap);
return svn_error_trace(svn_stream_puts(stream, message));
}
svn_error_t *
svn_stream_printf_from_utf8(svn_stream_t *stream,
const char *encoding,
apr_pool_t *pool,
const char *fmt,
...)
{
const char *message, *translated;
va_list ap;
va_start(ap, fmt);
message = apr_pvsprintf(pool, fmt, ap);
va_end(ap);
SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated, message, encoding,
pool));
return svn_error_trace(svn_stream_puts(stream, translated));
}
/* Default implementation for svn_stream_readline().
* Returns the line read from STREAM in *STRINGBUF, and indicates
* end-of-file in *EOF. EOL must point to the desired end-of-line
* indicator. STRINGBUF is allocated in POOL. */
static svn_error_t *
stream_readline_bytewise(svn_stringbuf_t **stringbuf,
svn_boolean_t *eof,
const char *eol,
svn_stream_t *stream,
apr_pool_t *pool)
{
svn_stringbuf_t *str;
apr_size_t numbytes;
const char *match;
char c;
/* Since we're reading one character at a time, let's at least
optimize for the 90% case. 90% of the time, we can avoid the
stringbuf ever having to realloc() itself if we start it out at
80 chars. */
str = svn_stringbuf_create_ensure(SVN__LINE_CHUNK_SIZE, pool);
/* Read into STR up to and including the next EOL sequence. */
match = eol;
while (*match)
{
numbytes = 1;
SVN_ERR(svn_stream_read_full(stream, &c, &numbytes));
if (numbytes != 1)
{
/* a 'short' read means the stream has run out. */
*eof = TRUE;
*stringbuf = str;
return SVN_NO_ERROR;
}
if (c == *match)
match++;
else
match = eol;
svn_stringbuf_appendbyte(str, c);
}
*eof = FALSE;
svn_stringbuf_chop(str, match - eol);
*stringbuf = str;
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_readline(svn_stream_t *stream,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
if (stream->readline_fn)
{
/* Use the specific implementation when it's available. */
SVN_ERR(stream->readline_fn(stream->baton, stringbuf, eol, eof, pool));
}
else
{
/* Use the default implementation. */
SVN_ERR(stream_readline_bytewise(stringbuf, eof, eol, stream, pool));
}
return SVN_NO_ERROR;
}
svn_error_t *svn_stream_copy3(svn_stream_t *from, svn_stream_t *to,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
char *buf = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
svn_error_t *err;
svn_error_t *err2;
/* Read and write chunks until we get a short read, indicating the
end of the stream. (We can't get a short write without an
associated error.) */
while (1)
{
apr_size_t len = SVN__STREAM_CHUNK_SIZE;
if (cancel_func)
{
err = cancel_func(cancel_baton);
if (err)
break;
}
err = svn_stream_read_full(from, buf, &len);
if (err)
break;
if (len > 0)
err = svn_stream_write(to, buf, &len);
if (err || (len != SVN__STREAM_CHUNK_SIZE))
break;
}
err2 = svn_error_compose_create(svn_stream_close(from),
svn_stream_close(to));
return svn_error_compose_create(err, err2);
}
svn_error_t *
svn_stream_contents_same2(svn_boolean_t *same,
svn_stream_t *stream1,
svn_stream_t *stream2,
apr_pool_t *pool)
{
char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
apr_size_t bytes_read1 = SVN__STREAM_CHUNK_SIZE;
apr_size_t bytes_read2 = SVN__STREAM_CHUNK_SIZE;
svn_error_t *err = NULL;
*same = TRUE; /* assume TRUE, until disproved below */
while (bytes_read1 == SVN__STREAM_CHUNK_SIZE
&& bytes_read2 == SVN__STREAM_CHUNK_SIZE)
{
err = svn_stream_read_full(stream1, buf1, &bytes_read1);
if (err)
break;
err = svn_stream_read_full(stream2, buf2, &bytes_read2);
if (err)
break;
if ((bytes_read1 != bytes_read2)
|| (memcmp(buf1, buf2, bytes_read1)))
{
*same = FALSE;
break;
}
}
return svn_error_compose_create(err,
svn_error_compose_create(
svn_stream_close(stream1),
svn_stream_close(stream2)));
}
/*** Stream implementation utilities ***/
/* Skip data from any stream by reading and simply discarding it. */
static svn_error_t *
skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_full_fn)
{
apr_size_t bytes_read = 1;
char buffer[4096];
apr_size_t to_read = len;
while ((to_read > 0) && (bytes_read > 0))
{
bytes_read = sizeof(buffer) < to_read ? sizeof(buffer) : to_read;
SVN_ERR(read_full_fn(baton, buffer, &bytes_read));
to_read -= bytes_read;
}
return SVN_NO_ERROR;
}
/*** Generic readable empty stream ***/
static svn_error_t *
read_handler_empty(void *baton, char *buffer, apr_size_t *len)
{
*len = 0;
return SVN_NO_ERROR;
}
static svn_error_t *
write_handler_empty(void *baton, const char *data, apr_size_t *len)
{
return SVN_NO_ERROR;
}
static svn_error_t *
mark_handler_empty(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
{
*mark = NULL; /* Seek to start of stream marker */
return SVN_NO_ERROR;
}
static svn_error_t *
seek_handler_empty(void *baton, const svn_stream_mark_t *mark)
{
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_empty(apr_pool_t *pool)
{
svn_stream_t *stream;
stream = svn_stream_create(NULL, pool);
svn_stream_set_read2(stream, read_handler_empty, read_handler_empty);
svn_stream_set_write(stream, write_handler_empty);
svn_stream_set_mark(stream, mark_handler_empty);
svn_stream_set_seek(stream, seek_handler_empty);
return stream;
}
/*** Stream duplication support ***/
struct baton_tee {
svn_stream_t *out1;
svn_stream_t *out2;
};
static svn_error_t *
write_handler_tee(void *baton, const char *data, apr_size_t *len)
{
struct baton_tee *bt = baton;
SVN_ERR(svn_stream_write(bt->out1, data, len));
SVN_ERR(svn_stream_write(bt->out2, data, len));
return SVN_NO_ERROR;
}
static svn_error_t *
close_handler_tee(void *baton)
{
struct baton_tee *bt = baton;
SVN_ERR(svn_stream_close(bt->out1));
SVN_ERR(svn_stream_close(bt->out2));
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_tee(svn_stream_t *out1,
svn_stream_t *out2,
apr_pool_t *pool)
{
struct baton_tee *baton;
svn_stream_t *stream;
if (out1 == NULL)
return out2;
if (out2 == NULL)
return out1;
baton = apr_palloc(pool, sizeof(*baton));
baton->out1 = out1;
baton->out2 = out2;
stream = svn_stream_create(baton, pool);
svn_stream_set_write(stream, write_handler_tee);
svn_stream_set_close(stream, close_handler_tee);
return stream;
}
/*** Ownership detaching stream ***/
static svn_error_t *
read_handler_disown(void *baton, char *buffer, apr_size_t *len)
{
return svn_error_trace(svn_stream_read2(baton, buffer, len));
}
static svn_error_t *
read_full_handler_disown(void *baton, char *buffer, apr_size_t *len)
{
return svn_error_trace(svn_stream_read_full(baton, buffer, len));
}
static svn_error_t *
skip_handler_disown(void *baton, apr_size_t len)
{
return svn_error_trace(svn_stream_skip(baton, len));
}
static svn_error_t *
write_handler_disown(void *baton, const char *buffer, apr_size_t *len)
{
return svn_error_trace(svn_stream_write(baton, buffer, len));
}
static svn_error_t *
mark_handler_disown(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
{
return svn_error_trace(svn_stream_mark(baton, mark, pool));
}
static svn_error_t *
seek_handler_disown(void *baton, const svn_stream_mark_t *mark)
{
return svn_error_trace(svn_stream_seek(baton, mark));
}
static svn_error_t *
data_available_disown(void *baton, svn_boolean_t *data_available)
{
return svn_error_trace(svn_stream_data_available(baton, data_available));
}
static svn_error_t *
readline_handler_disown(void *baton,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
return svn_error_trace(svn_stream_readline(baton, stringbuf, eol,
eof, pool));
}
svn_stream_t *
svn_stream_disown(svn_stream_t *stream, apr_pool_t *pool)
{
svn_stream_t *s = svn_stream_create(stream, pool);
svn_stream_set_read2(s, read_handler_disown, read_full_handler_disown);
svn_stream_set_skip(s, skip_handler_disown);
svn_stream_set_write(s, write_handler_disown);
svn_stream_set_mark(s, mark_handler_disown);
svn_stream_set_seek(s, seek_handler_disown);
svn_stream_set_data_available(s, data_available_disown);
svn_stream_set_readline(s, readline_handler_disown);
return s;
}
/*** Generic stream for APR files ***/
struct baton_apr {
apr_file_t *file;
apr_pool_t *pool;
svn_boolean_t truncate_on_seek;
};
/* svn_stream_mark_t for streams backed by APR files. */
struct mark_apr {
apr_off_t off;
};
static svn_error_t *
read_handler_apr(void *baton, char *buffer, apr_size_t *len)
{
struct baton_apr *btn = baton;
svn_error_t *err;
if (*len == 1)
{
err = svn_io_file_getc(buffer, btn->file, btn->pool);
if (err)
{
*len = 0;
if (APR_STATUS_IS_EOF(err->apr_err))
{
svn_error_clear(err);
err = SVN_NO_ERROR;
}
}
}
else
{
err = svn_io_file_read(btn->file, buffer, len, btn->pool);
if (err && APR_STATUS_IS_EOF(err->apr_err))
{
svn_error_clear(err);
err = NULL;
}
}
return svn_error_trace(err);
}
static svn_error_t *
read_full_handler_apr(void *baton, char *buffer, apr_size_t *len)
{
struct baton_apr *btn = baton;
svn_error_t *err;
svn_boolean_t eof;
if (*len == 1)
{
err = svn_io_file_getc(buffer, btn->file, btn->pool);
if (err)
{
*len = 0;
if (APR_STATUS_IS_EOF(err->apr_err))
{
svn_error_clear(err);
err = SVN_NO_ERROR;
}
}
}
else
err = svn_io_file_read_full2(btn->file, buffer, *len, len,
&eof, btn->pool);
return svn_error_trace(err);
}
static svn_error_t *
skip_handler_apr(void *baton, apr_size_t len)
{
struct baton_apr *btn = baton;
apr_off_t offset = len;
return svn_error_trace(
svn_io_file_seek(btn->file, APR_CUR, &offset, btn->pool));
}
static svn_error_t *
write_handler_apr(void *baton, const char *data, apr_size_t *len)
{
struct baton_apr *btn = baton;
svn_error_t *err;
if (*len == 1)
{
err = svn_io_file_putc(*data, btn->file, btn->pool);
if (err)
*len = 0;
}
else
err = svn_io_file_write_full(btn->file, data, *len, len, btn->pool);
return svn_error_trace(err);
}
static svn_error_t *
close_handler_apr(void *baton)
{
struct baton_apr *btn = baton;
return svn_error_trace(svn_io_file_close(btn->file, btn->pool));
}
static svn_error_t *
mark_handler_apr(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
{
struct baton_apr *btn = baton;
struct mark_apr *mark_apr;
mark_apr = apr_palloc(pool, sizeof(*mark_apr));
SVN_ERR(svn_io_file_get_offset(&mark_apr->off, btn->file, btn->pool));
*mark = (svn_stream_mark_t *)mark_apr;
return SVN_NO_ERROR;
}
static svn_error_t *
seek_handler_apr(void *baton, const svn_stream_mark_t *mark)
{
struct baton_apr *btn = baton;
apr_off_t offset = (mark != NULL) ? ((const struct mark_apr *)mark)->off : 0;
if (btn->truncate_on_seek)
{
/* The apr_file_trunc() function always does seek + trunc,
* and this is documented, so don't seek when truncating. */
SVN_ERR(svn_io_file_trunc(btn->file, offset, btn->pool));
}
else
{
SVN_ERR(svn_io_file_seek(btn->file, APR_SET, &offset, btn->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
data_available_handler_apr(void *baton, svn_boolean_t *data_available)
{
struct baton_apr *btn = baton;
apr_status_t status;
#if !defined(WIN32) || APR_FILES_AS_SOCKETS
apr_pollfd_t pfd;
int n;
pfd.desc_type = APR_POLL_FILE;
pfd.desc.f = btn->file;
pfd.p = btn->pool; /* If we had a scratch pool... Luckily apr doesn't
store anything in this pool at this time */
pfd.reqevents = APR_POLLIN;
status = apr_poll(&pfd, 1, &n, 0);
if (status == APR_SUCCESS)
{
*data_available = (n > 0);
return SVN_NO_ERROR;
}
else if (APR_STATUS_IS_EOF(status) || APR_STATUS_IS_TIMEUP(status))
{
*data_available = FALSE;
return SVN_NO_ERROR;
}
else
{
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED,
svn_error_wrap_apr(
status,
_("Polling for available data on filestream "
"failed")),
NULL);
}
#else
HANDLE h;
DWORD dwAvail;
status = apr_os_file_get(&h, btn->file);
if (status)
return svn_error_wrap_apr(status, NULL);
if (PeekNamedPipe(h, NULL, 0, NULL, &dwAvail, NULL))
{
*data_available = (dwAvail > 0);
return SVN_NO_ERROR;
}
return svn_error_create(SVN_ERR_STREAM_NOT_SUPPORTED,
svn_error_wrap_apr(apr_get_os_error(), NULL),
_("Windows doesn't support polling on files"));
#endif
}
static svn_error_t *
readline_apr_lf(apr_file_t *file,
svn_stringbuf_t **stringbuf,
svn_boolean_t *eof,
apr_pool_t *pool)
{
svn_stringbuf_t *buf;
buf = svn_stringbuf_create_ensure(SVN__LINE_CHUNK_SIZE, pool);
while (1)
{
apr_status_t status;
status = apr_file_gets(buf->data + buf->len,
(int) (buf->blocksize - buf->len),
file);
buf->len += strlen(buf->data + buf->len);
if (APR_STATUS_IS_EOF(status))
{
/* apr_file_gets() keeps the newline; strip it if necessary. */
if (buf->len > 0 && buf->data[buf->len - 1] == '\n')
svn_stringbuf_chop(buf, 1);
*eof = TRUE;
*stringbuf = buf;
return SVN_NO_ERROR;
}
else if (status != APR_SUCCESS)
{
const char *fname;
svn_error_t *err = svn_io_file_name_get(&fname, file, pool);
if (err)
fname = NULL;
svn_error_clear(err);
if (fname)
return svn_error_wrap_apr(status,
_("Can't read a line from file '%s'"),
svn_dirent_local_style(fname, pool));
else
return svn_error_wrap_apr(status,
_("Can't read a line from stream"));
}
/* Do we have the EOL? If yes, strip it and return. */
if (buf->len > 0 && buf->data[buf->len - 1] == '\n')
{
svn_stringbuf_chop(buf, 1);
*eof = FALSE;
*stringbuf = buf;
return SVN_NO_ERROR;
}
/* Otherwise, prepare to read the next chunk. */
svn_stringbuf_ensure(buf, buf->len + SVN__LINE_CHUNK_SIZE);
}
}
static svn_error_t *
readline_apr_generic(apr_file_t *file,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
apr_size_t eol_len = strlen(eol);
apr_off_t offset;
svn_stringbuf_t *buf;
SVN_ERR(svn_io_file_get_offset(&offset, file, pool));
buf = svn_stringbuf_create_ensure(SVN__LINE_CHUNK_SIZE, pool);
while (1)
{
apr_size_t bytes_read;
svn_boolean_t hit_eof;
const char *search_start;
const char *eol_pos;
/* We look for the EOL in the new data plus the last part of the
previous chunk because the EOL may span over the boundary
between both chunks. */
if (buf->len < eol_len)
search_start = buf->data;
else
search_start = buf->data + buf->len - eol_len;
SVN_ERR(svn_io_file_read_full2(file, buf->data + buf->len,
buf->blocksize - buf->len - 1,
&bytes_read, &hit_eof, pool));
buf->len += bytes_read;
buf->data[buf->len] = '\0';
/* Do we have the EOL now? */
eol_pos = strstr(search_start, eol);
if (eol_pos)
{
svn_stringbuf_chop(buf, buf->data + buf->len - eol_pos);
/* Seek to the first position behind the EOL. */
offset += (buf->len + eol_len);
SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
*eof = FALSE;
*stringbuf = buf;
return SVN_NO_ERROR;
}
else if (eol_pos == NULL && hit_eof)
{
*eof = TRUE;
*stringbuf = buf;
return SVN_NO_ERROR;
}
/* Prepare to read the next chunk. */
svn_stringbuf_ensure(buf, buf->len + SVN__LINE_CHUNK_SIZE);
}
}
static svn_error_t *
readline_handler_apr(void *baton,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
struct baton_apr *btn = baton;
if (eol[0] == '\n' && eol[1] == '\0')
{
/* Optimize the common case when we're looking for an LF ("\n")
end-of-line sequence by using apr_file_gets(). */
return svn_error_trace(readline_apr_lf(btn->file, stringbuf,
eof, pool));
}
else
{
return svn_error_trace(readline_apr_generic(btn->file, stringbuf,
eol, eof, pool));
}
}
svn_error_t *
svn_stream_open_readonly(svn_stream_t **stream,
const char *path,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *file;
SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
APR_OS_DEFAULT, result_pool));
*stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_open_writable(svn_stream_t **stream,
const char *path,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *file;
SVN_ERR(svn_io_file_open(&file, path,
APR_WRITE
| APR_BUFFERED
| APR_CREATE
| APR_EXCL,
APR_OS_DEFAULT, result_pool));
*stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_open_unique(svn_stream_t **stream,
const char **temp_path,
const char *dirpath,
svn_io_file_del_t delete_when,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *file;
SVN_ERR(svn_io_open_unique_file3(&file, temp_path, dirpath,
delete_when, result_pool, scratch_pool));
*stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
return SVN_NO_ERROR;
}
/* Helper function that creates a stream from an APR file. */
static svn_stream_t *
make_stream_from_apr_file(apr_file_t *file,
svn_boolean_t disown,
svn_boolean_t supports_seek,
svn_boolean_t truncate_on_seek,
apr_pool_t *pool)
{
struct baton_apr *baton;
svn_stream_t *stream;
if (file == NULL)
return svn_stream_empty(pool);
baton = apr_palloc(pool, sizeof(*baton));
baton->file = file;
baton->pool = pool;
baton->truncate_on_seek = truncate_on_seek;
stream = svn_stream_create(baton, pool);
svn_stream_set_read2(stream, read_handler_apr, read_full_handler_apr);
svn_stream_set_write(stream, write_handler_apr);
if (supports_seek)
{
svn_stream_set_skip(stream, skip_handler_apr);
svn_stream_set_mark(stream, mark_handler_apr);
svn_stream_set_seek(stream, seek_handler_apr);
svn_stream_set_readline(stream, readline_handler_apr);
}
svn_stream_set_data_available(stream, data_available_handler_apr);
stream->file = file;
if (! disown)
svn_stream_set_close(stream, close_handler_apr);
return stream;
}
svn_stream_t *
svn_stream__from_aprfile(apr_file_t *file,
svn_boolean_t disown,
svn_boolean_t truncate_on_seek,
apr_pool_t *pool)
{
return make_stream_from_apr_file(file, disown, TRUE, truncate_on_seek, pool);
}
svn_stream_t *
svn_stream_from_aprfile2(apr_file_t *file,
svn_boolean_t disown,
apr_pool_t *pool)
{
return make_stream_from_apr_file(file, disown, TRUE, FALSE, pool);
}
apr_file_t *
svn_stream__aprfile(svn_stream_t *stream)
{
return stream->file;
}
/* Compressed stream support */
#define ZBUFFER_SIZE 4096 /* The size of the buffer the
compressed stream uses to read from
the substream. Basically an
arbitrary value, picked to be about
page-sized. */
struct zbaton {
z_stream *in; /* compressed stream for reading */
z_stream *out; /* compressed stream for writing */
void *substream; /* The substream */
void *read_buffer; /* buffer used for reading from
substream */
int read_flush; /* what flush mode to use while
reading */
apr_pool_t *pool; /* The pool this baton is allocated
on */
};
/* zlib alloc function. opaque is the pool we need. */
static voidpf
zalloc(voidpf opaque, uInt items, uInt size)
{
apr_pool_t *pool = opaque;
return apr_palloc(pool, items * size);
}
/* zlib free function */
static void
zfree(voidpf opaque, voidpf address)
{
/* Empty, since we allocate on the pool */
}
/* Helper function to figure out the sync mode */
static svn_error_t *
read_helper_gz(svn_stream_t *substream,
char *buffer,
uInt *len, int *zflush)
{
uInt orig_len = *len;
/* There's no reason this value should grow bigger than the range of
uInt, but Subversion's API requires apr_size_t. */
apr_size_t apr_len = (apr_size_t) *len;
SVN_ERR(svn_stream_read_full(substream, buffer, &apr_len));
/* Type cast back to uInt type that zlib uses. On LP64 platforms
apr_size_t will be bigger than uInt. */
*len = (uInt) apr_len;
/* I wanted to use Z_FINISH here, but we need to know our buffer is
big enough */
*zflush = (*len) < orig_len ? Z_SYNC_FLUSH : Z_SYNC_FLUSH;
return SVN_NO_ERROR;
}
/* Handle reading from a compressed stream */
static svn_error_t *
read_handler_gz(void *baton, char *buffer, apr_size_t *len)
{
struct zbaton *btn = baton;
int zerr;
if (btn->in == NULL)
{
btn->in = apr_palloc(btn->pool, sizeof(z_stream));
btn->in->zalloc = zalloc;
btn->in->zfree = zfree;
btn->in->opaque = btn->pool;
btn->read_buffer = apr_palloc(btn->pool, ZBUFFER_SIZE);
btn->in->next_in = btn->read_buffer;
btn->in->avail_in = ZBUFFER_SIZE;
SVN_ERR(read_helper_gz(btn->substream, btn->read_buffer,
&btn->in->avail_in, &btn->read_flush));
zerr = inflateInit(btn->in);
SVN_ERR(svn_error__wrap_zlib(zerr, "inflateInit", btn->in->msg));
}
btn->in->next_out = (Bytef *) buffer;
btn->in->avail_out = (uInt) *len;
while (btn->in->avail_out > 0)
{
if (btn->in->avail_in <= 0)
{
btn->in->avail_in = ZBUFFER_SIZE;
btn->in->next_in = btn->read_buffer;
SVN_ERR(read_helper_gz(btn->substream, btn->read_buffer,
&btn->in->avail_in, &btn->read_flush));
}
/* Short read means underlying stream has run out. */
if (btn->in->avail_in == 0)
{
*len = 0;
return SVN_NO_ERROR;
}
zerr = inflate(btn->in, btn->read_flush);
if (zerr == Z_STREAM_END)
break;
else if (zerr != Z_OK)
return svn_error_trace(svn_error__wrap_zlib(zerr, "inflate",
btn->in->msg));
}
*len -= btn->in->avail_out;
return SVN_NO_ERROR;
}
/* Compress data and write it to the substream */
static svn_error_t *
write_handler_gz(void *baton, const char *buffer, apr_size_t *len)
{
struct zbaton *btn = baton;
apr_pool_t *subpool;
void *write_buf;
apr_size_t buf_size, write_len;
int zerr;
if (btn->out == NULL)
{
btn->out = apr_palloc(btn->pool, sizeof(z_stream));
btn->out->zalloc = zalloc;
btn->out->zfree = zfree;
btn->out->opaque = btn->pool;
zerr = deflateInit(btn->out, Z_DEFAULT_COMPRESSION);
SVN_ERR(svn_error__wrap_zlib(zerr, "deflateInit", btn->out->msg));
}
/* The largest buffer we should need is 0.1% larger than the
compressed data, + 12 bytes. This info comes from zlib.h. */
buf_size = *len + (*len / 1000) + 13;
subpool = svn_pool_create(btn->pool);
write_buf = apr_palloc(subpool, buf_size);
btn->out->next_in = (Bytef *) buffer; /* Casting away const! */
btn->out->avail_in = (uInt) *len;
while (btn->out->avail_in > 0)
{
btn->out->next_out = write_buf;
btn->out->avail_out = (uInt) buf_size;
zerr = deflate(btn->out, Z_NO_FLUSH);
SVN_ERR(svn_error__wrap_zlib(zerr, "deflate", btn->out->msg));
write_len = buf_size - btn->out->avail_out;
if (write_len > 0)
SVN_ERR(svn_stream_write(btn->substream, write_buf, &write_len));
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
/* Handle flushing and closing the stream */
static svn_error_t *
close_handler_gz(void *baton)
{
struct zbaton *btn = baton;
int zerr;
if (btn->in != NULL)
{
zerr = inflateEnd(btn->in);
SVN_ERR(svn_error__wrap_zlib(zerr, "inflateEnd", btn->in->msg));
}
if (btn->out != NULL)
{
void *buf;
apr_size_t write_len;
buf = apr_palloc(btn->pool, ZBUFFER_SIZE);
while (TRUE)
{
btn->out->next_out = buf;
btn->out->avail_out = ZBUFFER_SIZE;
zerr = deflate(btn->out, Z_FINISH);
if (zerr != Z_STREAM_END && zerr != Z_OK)
return svn_error_trace(svn_error__wrap_zlib(zerr, "deflate",
btn->out->msg));
write_len = ZBUFFER_SIZE - btn->out->avail_out;
if (write_len > 0)
SVN_ERR(svn_stream_write(btn->substream, buf, &write_len));
if (zerr == Z_STREAM_END)
break;
}
zerr = deflateEnd(btn->out);
SVN_ERR(svn_error__wrap_zlib(zerr, "deflateEnd", btn->out->msg));
}
return svn_error_trace(svn_stream_close(btn->substream));
}
svn_stream_t *
svn_stream_compressed(svn_stream_t *stream, apr_pool_t *pool)
{
struct svn_stream_t *zstream;
struct zbaton *baton;
assert(stream != NULL);
baton = apr_palloc(pool, sizeof(*baton));
baton->in = baton->out = NULL;
baton->substream = stream;
baton->pool = pool;
baton->read_buffer = NULL;
baton->read_flush = Z_SYNC_FLUSH;
zstream = svn_stream_create(baton, pool);
svn_stream_set_read2(zstream, NULL /* only full read support */,
read_handler_gz);
svn_stream_set_write(zstream, write_handler_gz);
svn_stream_set_close(zstream, close_handler_gz);
return zstream;
}
/* Checksummed stream support */
struct checksum_stream_baton
{
svn_checksum_ctx_t *read_ctx, *write_ctx;
svn_checksum_t **read_checksum; /* Output value. */
svn_checksum_t **write_checksum; /* Output value. */
svn_stream_t *proxy;
/* True if more data should be read when closing the stream. */
svn_boolean_t read_more;
/* Pool to allocate read buffer and output values from. */
apr_pool_t *pool;
};
static svn_error_t *
read_handler_checksum(void *baton, char *buffer, apr_size_t *len)
{
struct checksum_stream_baton *btn = baton;
SVN_ERR(svn_stream_read2(btn->proxy, buffer, len));
if (btn->read_checksum)
SVN_ERR(svn_checksum_update(btn->read_ctx, buffer, *len));
return SVN_NO_ERROR;
}
static svn_error_t *
read_full_handler_checksum(void *baton, char *buffer, apr_size_t *len)
{
struct checksum_stream_baton *btn = baton;
apr_size_t saved_len = *len;
SVN_ERR(svn_stream_read_full(btn->proxy, buffer, len));
if (btn->read_checksum)
SVN_ERR(svn_checksum_update(btn->read_ctx, buffer, *len));
if (saved_len != *len)
btn->read_more = FALSE;
return SVN_NO_ERROR;
}
static svn_error_t *
write_handler_checksum(void *baton, const char *buffer, apr_size_t *len)
{
struct checksum_stream_baton *btn = baton;
if (btn->write_checksum && *len > 0)
SVN_ERR(svn_checksum_update(btn->write_ctx, buffer, *len));
return svn_error_trace(svn_stream_write(btn->proxy, buffer, len));
}
static svn_error_t *
data_available_handler_checksum(void *baton, svn_boolean_t *data_available)
{
struct checksum_stream_baton *btn = baton;
return svn_error_trace(svn_stream_data_available(btn->proxy,
data_available));
}
static svn_error_t *
close_handler_checksum(void *baton)
{
struct checksum_stream_baton *btn = baton;
/* If we're supposed to drain the stream, do so before finalizing the
checksum. */
if (btn->read_more)
{
char *buf = apr_palloc(btn->pool, SVN__STREAM_CHUNK_SIZE);
apr_size_t len = SVN__STREAM_CHUNK_SIZE;
do
{
SVN_ERR(read_full_handler_checksum(baton, buf, &len));
}
while (btn->read_more);
}
if (btn->read_ctx)
SVN_ERR(svn_checksum_final(btn->read_checksum, btn->read_ctx, btn->pool));
if (btn->write_ctx)
SVN_ERR(svn_checksum_final(btn->write_checksum, btn->write_ctx, btn->pool));
return svn_error_trace(svn_stream_close(btn->proxy));
}
static svn_error_t *
seek_handler_checksum(void *baton, const svn_stream_mark_t *mark)
{
struct checksum_stream_baton *btn = baton;
/* Only reset support. */
if (mark)
{
return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED,
NULL, NULL);
}
else
{
if (btn->read_ctx)
SVN_ERR(svn_checksum_ctx_reset(btn->read_ctx));
if (btn->write_ctx)
SVN_ERR(svn_checksum_ctx_reset(btn->write_ctx));
SVN_ERR(svn_stream_reset(btn->proxy));
}
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_checksummed2(svn_stream_t *stream,
svn_checksum_t **read_checksum,
svn_checksum_t **write_checksum,
svn_checksum_kind_t checksum_kind,
svn_boolean_t read_all,
apr_pool_t *pool)
{
svn_stream_t *s;
struct checksum_stream_baton *baton;
if (read_checksum == NULL && write_checksum == NULL)
return stream;
baton = apr_palloc(pool, sizeof(*baton));
if (read_checksum)
baton->read_ctx = svn_checksum_ctx_create(checksum_kind, pool);
else
baton->read_ctx = NULL;
if (write_checksum)
baton->write_ctx = svn_checksum_ctx_create(checksum_kind, pool);
else
baton->write_ctx = NULL;
baton->read_checksum = read_checksum;
baton->write_checksum = write_checksum;
baton->proxy = stream;
baton->read_more = read_all;
baton->pool = pool;
s = svn_stream_create(baton, pool);
svn_stream_set_read2(s, read_handler_checksum, read_full_handler_checksum);
svn_stream_set_write(s, write_handler_checksum);
svn_stream_set_data_available(s, data_available_handler_checksum);
svn_stream_set_close(s, close_handler_checksum);
if (svn_stream_supports_reset(stream))
svn_stream_set_seek(s, seek_handler_checksum);
return s;
}
/* Helper for svn_stream_contents_checksum() to compute checksum of
* KIND of STREAM. This function doesn't close source stream. */
static svn_error_t *
compute_stream_checksum(svn_checksum_t **checksum,
svn_stream_t *stream,
svn_checksum_kind_t kind,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_checksum_ctx_t *ctx = svn_checksum_ctx_create(kind, scratch_pool);
char *buf = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
while (1)
{
apr_size_t len = SVN__STREAM_CHUNK_SIZE;
SVN_ERR(svn_stream_read_full(stream, buf, &len));
if (len > 0)
SVN_ERR(svn_checksum_update(ctx, buf, len));
if (len != SVN__STREAM_CHUNK_SIZE)
break;
}
SVN_ERR(svn_checksum_final(checksum, ctx, result_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_contents_checksum(svn_checksum_t **checksum,
svn_stream_t *stream,
svn_checksum_kind_t kind,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
err = compute_stream_checksum(checksum, stream, kind,
result_pool, scratch_pool);
/* Close source stream in all cases. */
return svn_error_compose_create(err, svn_stream_close(stream));
}
/* Miscellaneous stream functions. */
/*
* [JAF] By considering the buffer size doubling algorithm we use, I think
* the performance characteristics of this implementation are as follows:
*
* When the effective hint is big enough for the actual data, it uses
* minimal time and allocates space roughly equal to the effective hint.
* Otherwise, it incurs a time overhead for copying an additional 1x to 2x
* the actual length of data, and a space overhead of an additional 2x to
* 3x the actual length.
*/
svn_error_t *
svn_stringbuf_from_stream(svn_stringbuf_t **result,
svn_stream_t *stream,
apr_size_t len_hint,
apr_pool_t *result_pool)
{
#define MIN_READ_SIZE 64
svn_stringbuf_t *text
= svn_stringbuf_create_ensure(MAX(len_hint + 1, MIN_READ_SIZE),
result_pool);
while(TRUE)
{
apr_size_t to_read = text->blocksize - 1 - text->len;
apr_size_t actually_read = to_read;
SVN_ERR(svn_stream_read_full(stream, text->data + text->len, &actually_read));
text->len += actually_read;
if (actually_read < to_read)
break;
if (text->blocksize - text->len < MIN_READ_SIZE)
svn_stringbuf_ensure(text, text->blocksize * 2);
}
text->data[text->len] = '\0';
*result = text;
return SVN_NO_ERROR;
}
struct stringbuf_stream_baton
{
svn_stringbuf_t *str;
apr_size_t amt_read;
};
/* svn_stream_mark_t for streams backed by stringbufs. */
struct stringbuf_stream_mark {
apr_size_t pos;
};
static svn_error_t *
read_handler_stringbuf(void *baton, char *buffer, apr_size_t *len)
{
struct stringbuf_stream_baton *btn = baton;
apr_size_t left_to_read = btn->str->len - btn->amt_read;
*len = (*len > left_to_read) ? left_to_read : *len;
memcpy(buffer, btn->str->data + btn->amt_read, *len);
btn->amt_read += *len;
return SVN_NO_ERROR;
}
static svn_error_t *
skip_handler_stringbuf(void *baton, apr_size_t len)
{
struct stringbuf_stream_baton *btn = baton;
apr_size_t left_to_read = btn->str->len - btn->amt_read;
len = (len > left_to_read) ? left_to_read : len;
btn->amt_read += len;
return SVN_NO_ERROR;
}
static svn_error_t *
write_handler_stringbuf(void *baton, const char *data, apr_size_t *len)
{
struct stringbuf_stream_baton *btn = baton;
svn_stringbuf_appendbytes(btn->str, data, *len);
return SVN_NO_ERROR;
}
static svn_error_t *
mark_handler_stringbuf(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
{
struct stringbuf_stream_baton *btn;
struct stringbuf_stream_mark *stringbuf_stream_mark;
btn = baton;
stringbuf_stream_mark = apr_palloc(pool, sizeof(*stringbuf_stream_mark));
stringbuf_stream_mark->pos = btn->amt_read;
*mark = (svn_stream_mark_t *)stringbuf_stream_mark;
return SVN_NO_ERROR;
}
static svn_error_t *
seek_handler_stringbuf(void *baton, const svn_stream_mark_t *mark)
{
struct stringbuf_stream_baton *btn = baton;
if (mark != NULL)
{
const struct stringbuf_stream_mark *stringbuf_stream_mark;
stringbuf_stream_mark = (const struct stringbuf_stream_mark *)mark;
btn->amt_read = stringbuf_stream_mark->pos;
}
else
btn->amt_read = 0;
return SVN_NO_ERROR;
}
static svn_error_t *
data_available_handler_stringbuf(void *baton, svn_boolean_t *data_available)
{
struct stringbuf_stream_baton *btn = baton;
*data_available = ((btn->str->len - btn->amt_read) > 0);
return SVN_NO_ERROR;
}
static svn_error_t *
readline_handler_stringbuf(void *baton,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
struct stringbuf_stream_baton *btn = baton;
const char *pos = btn->str->data + btn->amt_read;
const char *eol_pos;
eol_pos = strstr(pos, eol);
if (eol_pos)
{
apr_size_t eol_len = strlen(eol);
*eof = FALSE;
*stringbuf = svn_stringbuf_ncreate(pos, eol_pos - pos, pool);
btn->amt_read += (eol_pos - pos + eol_len);
}
else
{
*eof = TRUE;
*stringbuf = svn_stringbuf_ncreate(pos, btn->str->len - btn->amt_read,
pool);
btn->amt_read = btn->str->len;
}
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_from_stringbuf(svn_stringbuf_t *str,
apr_pool_t *pool)
{
svn_stream_t *stream;
struct stringbuf_stream_baton *baton;
if (! str)
return svn_stream_empty(pool);
baton = apr_palloc(pool, sizeof(*baton));
baton->str = str;
baton->amt_read = 0;
stream = svn_stream_create(baton, pool);
svn_stream_set_read2(stream, read_handler_stringbuf, read_handler_stringbuf);
svn_stream_set_skip(stream, skip_handler_stringbuf);
svn_stream_set_write(stream, write_handler_stringbuf);
svn_stream_set_mark(stream, mark_handler_stringbuf);
svn_stream_set_seek(stream, seek_handler_stringbuf);
svn_stream_set_data_available(stream, data_available_handler_stringbuf);
svn_stream_set_readline(stream, readline_handler_stringbuf);
return stream;
}
struct string_stream_baton
{
const svn_string_t *str;
apr_size_t amt_read;
};
/* svn_stream_mark_t for streams backed by stringbufs. */
struct string_stream_mark {
apr_size_t pos;
};
static svn_error_t *
read_handler_string(void *baton, char *buffer, apr_size_t *len)
{
struct string_stream_baton *btn = baton;
apr_size_t left_to_read = btn->str->len - btn->amt_read;
*len = (*len > left_to_read) ? left_to_read : *len;
memcpy(buffer, btn->str->data + btn->amt_read, *len);
btn->amt_read += *len;
return SVN_NO_ERROR;
}
static svn_error_t *
mark_handler_string(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
{
struct string_stream_baton *btn;
struct string_stream_mark *marker;
btn = baton;
marker = apr_palloc(pool, sizeof(*marker));
marker->pos = btn->amt_read;
*mark = (svn_stream_mark_t *)marker;
return SVN_NO_ERROR;
}
static svn_error_t *
seek_handler_string(void *baton, const svn_stream_mark_t *mark)
{
struct string_stream_baton *btn = baton;
if (mark != NULL)
{
const struct string_stream_mark *marker;
marker = (const struct string_stream_mark *)mark;
btn->amt_read = marker->pos;
}
else
btn->amt_read = 0;
return SVN_NO_ERROR;
}
static svn_error_t *
skip_handler_string(void *baton, apr_size_t len)
{
struct string_stream_baton *btn = baton;
apr_size_t left_to_read = btn->str->len - btn->amt_read;
len = (len > left_to_read) ? left_to_read : len;
btn->amt_read += len;
return SVN_NO_ERROR;
}
static svn_error_t *
data_available_handler_string(void *baton, svn_boolean_t *data_available)
{
struct string_stream_baton *btn = baton;
*data_available = ((btn->str->len - btn->amt_read) > 0);
return SVN_NO_ERROR;
}
static svn_error_t *
readline_handler_string(void *baton,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
struct string_stream_baton *btn = baton;
const char *pos = btn->str->data + btn->amt_read;
const char *eol_pos;
eol_pos = strstr(pos, eol);
if (eol_pos)
{
apr_size_t eol_len = strlen(eol);
*eof = FALSE;
*stringbuf = svn_stringbuf_ncreate(pos, eol_pos - pos, pool);
btn->amt_read += (eol_pos - pos + eol_len);
}
else
{
*eof = TRUE;
*stringbuf = svn_stringbuf_ncreate(pos, btn->str->len - btn->amt_read,
pool);
btn->amt_read = btn->str->len;
}
return SVN_NO_ERROR;
}
svn_stream_t *
svn_stream_from_string(const svn_string_t *str,
apr_pool_t *pool)
{
svn_stream_t *stream;
struct string_stream_baton *baton;
if (! str)
return svn_stream_empty(pool);
baton = apr_palloc(pool, sizeof(*baton));
baton->str = str;
baton->amt_read = 0;
stream = svn_stream_create(baton, pool);
svn_stream_set_read2(stream, read_handler_string, read_handler_string);
svn_stream_set_mark(stream, mark_handler_string);
svn_stream_set_seek(stream, seek_handler_string);
svn_stream_set_skip(stream, skip_handler_string);
svn_stream_set_data_available(stream, data_available_handler_string);
svn_stream_set_readline(stream, readline_handler_string);
return stream;
}
svn_error_t *
svn_stream_for_stdin2(svn_stream_t **in,
svn_boolean_t buffered,
apr_pool_t *pool)
{
apr_file_t *stdin_file;
apr_status_t apr_err;
apr_uint32_t flags = buffered ? APR_BUFFERED : 0;
apr_err = apr_file_open_flags_stdin(&stdin_file, flags, pool);
if (apr_err)
return svn_error_wrap_apr(apr_err, "Can't open stdin");
/* STDIN may or may not support positioning requests, but generally
it does not, or the behavior is implementation-specific. Hence,
we cannot safely advertise mark(), seek() and non-default skip()
support. */
*in = make_stream_from_apr_file(stdin_file, TRUE, FALSE, FALSE, pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_for_stdout(svn_stream_t **out, apr_pool_t *pool)
{
apr_file_t *stdout_file;
apr_status_t apr_err;
apr_err = apr_file_open_stdout(&stdout_file, pool);
if (apr_err)
return svn_error_wrap_apr(apr_err, "Can't open stdout");
/* STDOUT may or may not support positioning requests, but generally
it does not, or the behavior is implementation-specific. Hence,
we cannot safely advertise mark(), seek() and non-default skip()
support. */
*out = make_stream_from_apr_file(stdout_file, TRUE, FALSE, FALSE, pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream_for_stderr(svn_stream_t **err, apr_pool_t *pool)
{
apr_file_t *stderr_file;
apr_status_t apr_err;
apr_err = apr_file_open_stderr(&stderr_file, pool);
if (apr_err)
return svn_error_wrap_apr(apr_err, "Can't open stderr");
/* STDERR may or may not support positioning requests, but generally
it does not, or the behavior is implementation-specific. Hence,
we cannot safely advertise mark(), seek() and non-default skip()
support. */
*err = make_stream_from_apr_file(stderr_file, TRUE, FALSE, FALSE, pool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_string_from_stream2(svn_string_t **result,
svn_stream_t *stream,
apr_size_t len_hint,
apr_pool_t *result_pool)
{
svn_stringbuf_t *buf;
SVN_ERR(svn_stringbuf_from_stream(&buf, stream, len_hint, result_pool));
*result = svn_stringbuf__morph_into_string(buf);
SVN_ERR(svn_stream_close(stream));
return SVN_NO_ERROR;
}
/* These are somewhat arbitrary, if we ever get good empirical data as to
actually valid values, feel free to update them. */
#define BUFFER_BLOCK_SIZE 1024
#define BUFFER_MAX_SIZE 100000
svn_stream_t *
svn_stream_buffered(apr_pool_t *result_pool)
{
return svn_stream__from_spillbuf(svn_spillbuf__create(BUFFER_BLOCK_SIZE,
BUFFER_MAX_SIZE,
result_pool),
result_pool);
}
/*** Lazyopen Streams ***/
/* Custom baton for lazyopen-style wrapper streams. */
typedef struct lazyopen_baton_t {
/* Callback function and baton for opening the wrapped stream. */
svn_stream_lazyopen_func_t open_func;
void *open_baton;
/* The wrapped stream, or NULL if the stream hasn't yet been
opened. */
svn_stream_t *real_stream;
apr_pool_t *pool;
/* Whether to open the wrapped stream on a close call. */
svn_boolean_t open_on_close;
} lazyopen_baton_t;
/* Use B->open_func/baton to create and set B->real_stream iff it
isn't already set. */
static svn_error_t *
lazyopen_if_unopened(lazyopen_baton_t *b)
{
if (b->real_stream == NULL)
{
svn_stream_t *stream;
apr_pool_t *scratch_pool = svn_pool_create(b->pool);
SVN_ERR(b->open_func(&stream, b->open_baton,
b->pool, scratch_pool));
svn_pool_destroy(scratch_pool);
b->real_stream = stream;
}
return SVN_NO_ERROR;
}
/* Implements svn_read_fn_t */
static svn_error_t *
read_handler_lazyopen(void *baton,
char *buffer,
apr_size_t *len)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_read2(b->real_stream, buffer, len));
return SVN_NO_ERROR;
}
/* Implements svn_read_fn_t */
static svn_error_t *
read_full_handler_lazyopen(void *baton,
char *buffer,
apr_size_t *len)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_read_full(b->real_stream, buffer, len));
return SVN_NO_ERROR;
}
/* Implements svn_stream_skip_fn_t */
static svn_error_t *
skip_handler_lazyopen(void *baton,
apr_size_t len)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_skip(b->real_stream, len));
return SVN_NO_ERROR;
}
/* Implements svn_write_fn_t */
static svn_error_t *
write_handler_lazyopen(void *baton,
const char *data,
apr_size_t *len)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_write(b->real_stream, data, len));
return SVN_NO_ERROR;
}
/* Implements svn_close_fn_t */
static svn_error_t *
close_handler_lazyopen(void *baton)
{
lazyopen_baton_t *b = baton;
if (b->open_on_close)
SVN_ERR(lazyopen_if_unopened(b));
if (b->real_stream)
SVN_ERR(svn_stream_close(b->real_stream));
return SVN_NO_ERROR;
}
/* Implements svn_stream_mark_fn_t */
static svn_error_t *
mark_handler_lazyopen(void *baton,
svn_stream_mark_t **mark,
apr_pool_t *pool)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_mark(b->real_stream, mark, pool));
return SVN_NO_ERROR;
}
/* Implements svn_stream_seek_fn_t */
static svn_error_t *
seek_handler_lazyopen(void *baton,
const svn_stream_mark_t *mark)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
SVN_ERR(svn_stream_seek(b->real_stream, mark));
return SVN_NO_ERROR;
}
static svn_error_t *
data_available_handler_lazyopen(void *baton,
svn_boolean_t *data_available)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
return svn_error_trace(svn_stream_data_available(b->real_stream,
data_available));
}
/* Implements svn_stream_readline_fn_t */
static svn_error_t *
readline_handler_lazyopen(void *baton,
svn_stringbuf_t **stringbuf,
const char *eol,
svn_boolean_t *eof,
apr_pool_t *pool)
{
lazyopen_baton_t *b = baton;
SVN_ERR(lazyopen_if_unopened(b));
return svn_error_trace(svn_stream_readline(b->real_stream, stringbuf,
eol, eof, pool));
}
svn_stream_t *
svn_stream_lazyopen_create(svn_stream_lazyopen_func_t open_func,
void *open_baton,
svn_boolean_t open_on_close,
apr_pool_t *result_pool)
{
lazyopen_baton_t *lob = apr_pcalloc(result_pool, sizeof(*lob));
svn_stream_t *stream;
lob->open_func = open_func;
lob->open_baton = open_baton;
lob->real_stream = NULL;
lob->pool = result_pool;
lob->open_on_close = open_on_close;
stream = svn_stream_create(lob, result_pool);
svn_stream_set_read2(stream, read_handler_lazyopen,
read_full_handler_lazyopen);
svn_stream_set_skip(stream, skip_handler_lazyopen);
svn_stream_set_write(stream, write_handler_lazyopen);
svn_stream_set_close(stream, close_handler_lazyopen);
svn_stream_set_mark(stream, mark_handler_lazyopen);
svn_stream_set_seek(stream, seek_handler_lazyopen);
svn_stream_set_data_available(stream, data_available_handler_lazyopen);
svn_stream_set_readline(stream, readline_handler_lazyopen);
return stream;
}
/* Baton for install streams */
struct install_baton_t
{
struct baton_apr baton_apr;
const char *tmp_path;
svn_boolean_t set_read_only;
svn_boolean_t set_executable;
apr_time_t set_mtime;
};
#ifdef WIN32
/* Create and open a tempfile in DIRECTORY. Return its handle and path */
static svn_error_t *
create_tempfile(HANDLE *hFile,
const char **file_path,
const char *directory,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *unique_name;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
static svn_atomic_t tempname_counter;
int baseNr = (GetTickCount() << 11) + 13 * svn_atomic_inc(&tempname_counter)
+ GetCurrentProcessId();
int i = 0;
HANDLE h;
/* Shares common idea with io.c's temp_file_create */
do
{
apr_uint32_t unique_nr;
WCHAR *w_name;
/* Generate a number that should be unique for this application and
usually for the entire computer to reduce the number of cycles
through this loop. (A bit of calculation is much cheaper than
disk io) */
unique_nr = baseNr + 7 * i++;
svn_pool_clear(iterpool);
unique_name = svn_dirent_join(directory,
apr_psprintf(iterpool, "svn-%X",
unique_nr),
iterpool);
SVN_ERR(svn_io__utf8_to_unicode_longpath(&w_name, unique_name,
iterpool));
/* Create a completely not-sharable file to avoid indexers, and other
filesystem watchers locking the file while we are still writing.
We need DELETE privileges to move the file. */
h = CreateFileW(w_name, GENERIC_WRITE | DELETE, 0 /* share */,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE)
{
apr_status_t status = apr_get_os_error();
if (i > 1000)
return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
svn_error_wrap_apr(status, NULL),
_("Unable to make name in '%s'"),
svn_dirent_local_style(directory, scratch_pool));
if (!APR_STATUS_IS_EEXIST(status) && !APR_STATUS_IS_EACCES(status))
return svn_error_wrap_apr(status, NULL);
}
}
while (h == INVALID_HANDLE_VALUE);
*hFile = h;
*file_path = apr_pstrdup(result_pool, unique_name);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
#endif /* WIN32 */
/* Implements svn_close_fn_t */
static svn_error_t *
install_close(void *baton)
{
struct install_baton_t *ib = baton;
/* Flush the data cached in APR, but don't close the file yet */
SVN_ERR(svn_io_file_flush(ib->baton_apr.file, ib->baton_apr.pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream__create_for_install(svn_stream_t **install_stream,
const char *tmp_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_file_t *file;
struct install_baton_t *ib;
const char *tmp_path;
#ifdef WIN32
HANDLE hInstall;
apr_status_t status;
SVN_ERR_ASSERT(svn_dirent_is_absolute(tmp_abspath));
SVN_ERR(create_tempfile(&hInstall, &tmp_path, tmp_abspath,
scratch_pool, scratch_pool));
/* Wrap as a standard APR file to allow sharing implementation.
But do note that some file functions (such as retrieving the name)
don't work on this wrapper.
Use buffered mode to match svn_io_open_unique_file3() behavior. */
status = apr_os_file_put(&file, &hInstall,
APR_WRITE | APR_BINARY | APR_BUFFERED,
result_pool);
if (status)
{
CloseHandle(hInstall);
return svn_error_wrap_apr(status, NULL);
}
tmp_path = svn_dirent_internal_style(tmp_path, result_pool);
#else
SVN_ERR_ASSERT(svn_dirent_is_absolute(tmp_abspath));
SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, tmp_abspath,
svn_io_file_del_none,
result_pool, scratch_pool));
#endif
/* Set the temporary file to be truncated on seeks. */
*install_stream = svn_stream__from_aprfile(file, FALSE, TRUE,
result_pool);
ib = apr_pcalloc(result_pool, sizeof(*ib));
ib->baton_apr = *(struct baton_apr*)(*install_stream)->baton;
assert((void*)&ib->baton_apr == (void*)ib); /* baton pointer is the same */
(*install_stream)->baton = ib;
ib->tmp_path = tmp_path;
ib->set_read_only = FALSE;
ib->set_executable = FALSE;
ib->set_mtime = -1;
/* Don't close the file on stream close; flush instead */
svn_stream_set_close(*install_stream, install_close);
return SVN_NO_ERROR;
}
void
svn_stream__install_set_read_only(svn_stream_t *install_stream,
svn_boolean_t read_only)
{
struct install_baton_t *ib = install_stream->baton;
ib->set_read_only = read_only;
}
void
svn_stream__install_set_executable(svn_stream_t *install_stream,
svn_boolean_t executable)
{
struct install_baton_t *ib = install_stream->baton;
ib->set_executable = executable;
}
void
svn_stream__install_set_affected_time(svn_stream_t *install_stream,
apr_time_t mtime)
{
struct install_baton_t *ib = install_stream->baton;
ib->set_mtime = mtime;
}
/* Helper function that closes the underlying file of the install stream
and update the state in the baton. */
static svn_error_t *
install_stream_close_file(struct install_baton_t *ib,
apr_pool_t *scratch_pool)
{
if (ib->baton_apr.file)
{
SVN_ERR(svn_io_file_close(ib->baton_apr.file, scratch_pool));
ib->baton_apr.file = NULL;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream__install_finalize(apr_time_t *mtime_p,
apr_off_t *size_p,
svn_stream_t *install_stream,
apr_pool_t *scratch_pool)
{
struct install_baton_t *ib = install_stream->baton;
svn_boolean_t finalized = FALSE;
apr_finfo_t finfo;
apr_int32_t wanted;
#ifdef WIN32
/* If the caller asked us for the timestamp with a non-null MTIME_P,
ensure that subsequent I/O operations won't change it; see below.
*/
if (ib->set_mtime >= 0 || ib->set_read_only || mtime_p)
{
apr_time_t set_mtime;
svn_error_t *err;
/* On Windows, the file systems may defer processing of timestamps until
the file handle is closed, as specified in [1]. Since we peek and
return the current timestamp, we MUST ensure that the timestamp does
not change after the call to finalize().
Luckily, there are two options that guarantee that the file will keep
its current timestamp after close. We can either explicitly set a new
timestamp, or use a special option that instructs the file system to
suspend updates for timestamp values for all subsequent I/O operations.
Both of these options guarantee [2, 3] that no other operation will
change the final timestamp. So we use both of them, depending on
whether the caller wants us to set a specific timestamp, or not.
[1: MS-FSA, 2.1.4.17, Note <42>]
File systems may choose to defer processing for a file that has been
modified to a later time, favoring performance over accuracy. The NTFS
file system on versions prior to Windows 10 v1809 operating system,
Windows Server v1809 operating system, and Windows Server 2019, and
non-NTFS file systems on all versions of Windows, defer this processing
until the Open gets closed.
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fsa/4e3695bd-7574-4f24-a223-b4679c065b63#Appendix_A_42
[2: MS-FSA 2.1.5.14.2]
If InputBuffer.LastWriteTime != 0:
If InputBuffer.LastWriteTime != -2:
The object store MUST set Open.UserSetModificationTime to TRUE.
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fsa/a36513b4-73c8-4888-ad29-8f3a196567e8
[3: MS-FSA 2.1.4.17]
If Open.UserSetModificationTime is FALSE, set Open.File.LastModificationTime
to the current system time.
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fsa/75cdaba1-4401-4c53-b09c-69ba6cd50ce6
*/
if (ib->set_mtime >= 0)
set_mtime = ib->set_mtime;
else
set_mtime = SVN_IO__WIN_TIME_SUSPEND_UPDATE;
err = svn_io__win_set_file_basic_info(ib->baton_apr.file, ib->tmp_path,
set_mtime, ib->set_read_only,
scratch_pool);
if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
{
/* Setting information by handle is not supported on this platform:
fallback to setting it by path. */
svn_error_clear(err);
}
else if (err)
{
return svn_error_trace(err);
}
else
{
finalized = TRUE;
}
}
else
{
finalized = TRUE;
}
#endif
if (!finalized)
{
SVN_ERR(install_stream_close_file(ib, scratch_pool));
if (ib->set_read_only)
SVN_ERR(svn_io_set_file_read_only(ib->tmp_path, FALSE,
scratch_pool));
if (ib->set_executable)
SVN_ERR(svn_io_set_file_executable(ib->tmp_path, TRUE, FALSE,
scratch_pool));
if (ib->set_mtime >= 0)
SVN_ERR(svn_io_set_file_affected_time(ib->set_mtime, ib->tmp_path,
scratch_pool));
finalized = TRUE;
}
wanted = 0;
if (mtime_p)
wanted |= APR_FINFO_MTIME;
if (size_p)
wanted |= APR_FINFO_SIZE;
/* Note that we always fetch the values such as MTIME_P from the filesystem,
because it might have a lower timestamp granularity than apr_time_t.
*/
if (wanted)
{
apr_status_t status;
if (ib->baton_apr.file)
status = apr_file_info_get(&finfo, wanted, ib->baton_apr.file);
else
status = apr_stat(&finfo, ib->tmp_path, wanted, scratch_pool);
if (status)
return svn_error_wrap_apr(status, NULL);
}
if (mtime_p)
*mtime_p = finfo.mtime;
if (size_p)
*size_p = finfo.size;
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream__install_stream(svn_stream_t *install_stream,
const char *final_abspath,
svn_boolean_t make_parents,
apr_pool_t *scratch_pool)
{
struct install_baton_t *ib = install_stream->baton;
svn_error_t *err;
SVN_ERR_ASSERT(svn_dirent_is_absolute(final_abspath));
if (ib->baton_apr.file)
{
#ifdef WIN32
err = svn_io__win_rename_open_file(ib->baton_apr.file, ib->tmp_path,
final_abspath, scratch_pool);
if (make_parents && err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_t *err2;
err2 = svn_io_make_dir_recursively(svn_dirent_dirname(final_abspath,
scratch_pool),
scratch_pool);
if (err2)
return svn_error_trace(svn_error_compose_create(err, err2));
else
svn_error_clear(err);
err = svn_io__win_rename_open_file(ib->baton_apr.file, ib->tmp_path,
final_abspath, scratch_pool);
}
/* ### rhuijben: I wouldn't be surprised if we later find out that we
have to fall back to close+rename on some specific
error values here, to support some non standard NAS
and filesystem scenarios. */
if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
{
/* Rename open files is not supported on this platform: fallback to
svn_io_file_rename2(). */
svn_error_clear(err);
err = SVN_NO_ERROR;
}
else
{
return svn_error_compose_create(
err, install_stream_close_file(ib, scratch_pool));
}
#endif
SVN_ERR(install_stream_close_file(ib, scratch_pool));
}
err = svn_io_file_rename2(ib->tmp_path, final_abspath, FALSE, scratch_pool);
/* A missing directory is too common to not cover here. */
if (make_parents && err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_t *err2;
err2 = svn_io_make_dir_recursively(svn_dirent_dirname(final_abspath,
scratch_pool),
scratch_pool);
if (err2)
/* Creating directory didn't work: Return all errors */
return svn_error_trace(svn_error_compose_create(err, err2));
else
/* We could create a directory: retry install */
svn_error_clear(err);
SVN_ERR(svn_io_file_rename2(ib->tmp_path, final_abspath, FALSE, scratch_pool));
}
else
SVN_ERR(err);
return SVN_NO_ERROR;
}
svn_error_t *
svn_stream__install_delete(svn_stream_t *install_stream,
apr_pool_t *scratch_pool)
{
struct install_baton_t *ib = install_stream->baton;
if (ib->baton_apr.file)
{
#ifdef WIN32
svn_error_t *err;
/* Mark the file as delete on close to avoid having to reopen
the file as part of the delete handling. */
err = svn_io__win_delete_file_on_close(ib->baton_apr.file, ib->tmp_path,
scratch_pool);
if (err == SVN_NO_ERROR)
{
SVN_ERR(install_stream_close_file(ib, scratch_pool));
return SVN_NO_ERROR; /* File is already gone */
}
/* Deleting file on close may be unsupported, so ignore errors and
fallback to svn_io_remove_file2(). */
svn_error_clear(err);
#endif
SVN_ERR(install_stream_close_file(ib, scratch_pool));
}
return svn_error_trace(svn_io_remove_file2(ib->tmp_path, FALSE,
scratch_pool));
}