diff options
Diffstat (limited to 'subversion/libsvn_ra_svn/marshal.c')
-rw-r--r-- | subversion/libsvn_ra_svn/marshal.c | 880 |
1 files changed, 707 insertions, 173 deletions
diff --git a/subversion/libsvn_ra_svn/marshal.c b/subversion/libsvn_ra_svn/marshal.c index 0778269fa404e..41caaa51a553c 100644 --- a/subversion/libsvn_ra_svn/marshal.c +++ b/subversion/libsvn_ra_svn/marshal.c @@ -60,9 +60,10 @@ /* We don't use "words" longer than this in our protocol. The longest word * we are currently using is only about 16 chars long but we leave room for - * longer future capability and command names. + * longer future capability and command names. See read_item() to understand + * why MAX_WORD_LENGTH - 1 should be a multiple of 8. */ -#define MAX_WORD_LENGTH 31 +#define MAX_WORD_LENGTH 25 /* The generic parsers will use the following value to limit the recursion * depth to some reasonable value. The current protocol implementation @@ -71,6 +72,10 @@ */ #define ITEM_NESTING_LIMIT 64 +/* The protocol words for booleans. */ +static const svn_string_t str_true = SVN__STATIC_STRING("true"); +static const svn_string_t str_false = SVN__STATIC_STRING("false"); + /* Return the APR socket timeout to be used for the connection depending * on whether there is a blockage handler or zero copy has been activated. */ static apr_interval_time_t @@ -79,14 +84,109 @@ get_timeout(svn_ra_svn_conn_t *conn) return conn->block_handler ? 0 : -1; } +/* --- Public / private API data conversion --- */ + +void +svn_ra_svn__to_public_item(svn_ra_svn_item_t *target, + const svn_ra_svn__item_t *source, + apr_pool_t *result_pool) +{ + target->kind = source->kind; + switch (source->kind) + { + case SVN_RA_SVN_STRING: + target->u.string = svn_string_dup(&source->u.string, result_pool); + break; + case SVN_RA_SVN_NUMBER: + target->u.number = source->u.number; + break; + case SVN_RA_SVN_WORD: + target->u.word = source->u.word.data; + break; + case SVN_RA_SVN_LIST: + target->u.list = svn_ra_svn__to_public_array(&source->u.list, + result_pool); + break; + } +} + +apr_array_header_t * +svn_ra_svn__to_public_array(const svn_ra_svn__list_t *source, + apr_pool_t *result_pool) +{ + apr_array_header_t *result = apr_array_make(result_pool, source->nelts, + sizeof(svn_ra_svn_item_t)); + + int i; + for (i = 0; i < source->nelts; ++i) + { + svn_ra_svn_item_t *sub_target = apr_array_push(result); + svn_ra_svn__item_t *sub_source = &SVN_RA_SVN__LIST_ITEM(source, i); + + svn_ra_svn__to_public_item(sub_target, sub_source, result_pool); + } + + return result; +} + +void +svn_ra_svn__to_private_item(svn_ra_svn__item_t *target, + const svn_ra_svn_item_t *source, + apr_pool_t *result_pool) +{ + target->kind = source->kind; + switch (source->kind) + { + case SVN_RA_SVN_STRING: + target->u.string = *source->u.string; + break; + case SVN_RA_SVN_NUMBER: + target->u.number = source->u.number; + break; + case SVN_RA_SVN_WORD: + target->u.word.data = source->u.word; + target->u.word.len = strlen(source->u.word); + break; + case SVN_RA_SVN_LIST: + target->u.list = *svn_ra_svn__to_private_array(source->u.list, + result_pool); + break; + } +} + +svn_ra_svn__list_t * +svn_ra_svn__to_private_array(const apr_array_header_t *source, + apr_pool_t *result_pool) +{ + int i; + + svn_ra_svn__list_t *result = apr_pcalloc(result_pool, sizeof(*result)); + result->nelts = source->nelts; + result->items = apr_palloc(result_pool, + source->nelts * sizeof(*result->items)); + + for (i = 0; i < source->nelts; ++i) + { + svn_ra_svn__item_t *sub_target = &result->items[i]; + svn_ra_svn_item_t *sub_source = &APR_ARRAY_IDX(source, i, + svn_ra_svn_item_t); + + svn_ra_svn__to_private_item(sub_target, sub_source, result_pool); + } + + return result; +} + /* --- CONNECTION INITIALIZATION --- */ -svn_ra_svn_conn_t *svn_ra_svn_create_conn4(apr_socket_t *sock, +svn_ra_svn_conn_t *svn_ra_svn_create_conn5(apr_socket_t *sock, svn_stream_t *in_stream, svn_stream_t *out_stream, int compression_level, apr_size_t zero_copy_limit, apr_size_t error_check_interval, + apr_uint64_t max_in, + apr_uint64_t max_out, apr_pool_t *result_pool) { svn_ra_svn_conn_t *conn; @@ -106,6 +206,10 @@ svn_ra_svn_conn_t *svn_ra_svn_create_conn4(apr_socket_t *sock, conn->written_since_error_check = 0; conn->error_check_interval = error_check_interval; conn->may_check_for_error = error_check_interval == 0; + conn->max_in = max_in; + conn->current_in = 0; + conn->max_out = max_out; + conn->current_out = 0; conn->block_handler = NULL; conn->block_baton = NULL; conn->capabilities = apr_hash_make(result_pool); @@ -132,25 +236,53 @@ svn_ra_svn_conn_t *svn_ra_svn_create_conn4(apr_socket_t *sock, return conn; } -svn_error_t *svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn, - const apr_array_header_t *list) +svn_error_t * +svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn, + const apr_array_header_t *list) +{ + svn_ra_svn__list_t *internal + = svn_ra_svn__to_private_array(list, list->pool); + return svn_error_trace(svn_ra_svn__set_capabilities(conn, internal)); +} + +svn_error_t * +svn_ra_svn__set_capabilities(svn_ra_svn_conn_t *conn, + const svn_ra_svn__list_t *list) { int i; - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; const char *word; for (i = 0; i < list->nelts; i++) { - item = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + item = &SVN_RA_SVN__LIST_ITEM(list, i); if (item->kind != SVN_RA_SVN_WORD) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Capability entry is not a word")); - word = apr_pstrdup(conn->pool, item->u.word); - svn_hash_sets(conn->capabilities, word, word); + word = apr_pstrmemdup(conn->pool, item->u.word.data, item->u.word.len); + apr_hash_set(conn->capabilities, word, item->u.word.len, word); } return SVN_NO_ERROR; } +int +svn_ra_svn__svndiff_version(svn_ra_svn_conn_t *conn) +{ + /* If we don't want to use compression, use the non-compressing + * "version 0" implementation. */ + if (svn_ra_svn_compression_level(conn) <= 0) + return 0; + + /* Prefer SVNDIFF2 over SVNDIFF1. */ + if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_SVNDIFF2_ACCEPTED)) + return 2; + if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_SVNDIFF1)) + return 1; + + /* The connection does not support SVNDIFF1/2; default to "version 0". */ + return 0; +} + apr_pool_t * svn_ra_svn__get_pool(svn_ra_svn_conn_t *conn) { @@ -204,8 +336,33 @@ svn_error_t *svn_ra_svn__data_available(svn_ra_svn_conn_t *conn, return svn_ra_svn__stream_data_available(conn->stream, data_available); } +void +svn_ra_svn__reset_command_io_counters(svn_ra_svn_conn_t *conn) +{ + conn->current_in = 0; + conn->current_out = 0; +} + + /* --- WRITE BUFFER MANAGEMENT --- */ +/* Return an error object if CONN exceeded its send or receive limits. */ +static svn_error_t * +check_io_limits(svn_ra_svn_conn_t *conn) +{ + if (conn->max_in && (conn->current_in > conn->max_in)) + return svn_error_create(SVN_ERR_RA_SVN_REQUEST_SIZE, NULL, + "The client request size exceeds the " + "configured limit"); + + if (conn->max_out && (conn->current_out > conn->max_out)) + return svn_error_create(SVN_ERR_RA_SVN_RESPONSE_SIZE, NULL, + "The server response size exceeds the " + "configured limit"); + + return SVN_NO_ERROR; +} + /* Write data to socket or output file as appropriate. */ static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *data, apr_size_t len) @@ -215,6 +372,12 @@ static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool, apr_pool_t *subpool = NULL; svn_ra_svn__session_baton_t *session = conn->session; + /* Limit the size of the response, if a limit has been configured. + * This is to limit the server load in case users e.g. accidentally ran + * an export on the root folder. */ + conn->current_out += len; + SVN_ERR(check_io_limits(conn)); + while (data < end) { count = end - data; @@ -333,12 +496,19 @@ static svn_error_t *readbuf_input(svn_ra_svn_conn_t *conn, char *data, { svn_ra_svn__session_baton_t *session = conn->session; + /* First, give the user a chance to cancel the request before we do. */ if (session && session->callbacks && session->callbacks->cancel_func) SVN_ERR((session->callbacks->cancel_func)(session->callbacks_baton)); + /* Limit our memory usage, if a limit has been configured. Note that + * we first read the whole request into memory before process it. */ + SVN_ERR(check_io_limits(conn)); + + /* Actually fill the buffer. */ SVN_ERR(svn_ra_svn__stream_read(conn->stream, data, len)); if (*len == 0) return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL); + conn->current_in += *len; if (session) { @@ -384,9 +554,13 @@ static svn_error_t *readbuf_fill(svn_ra_svn_conn_t *conn, apr_pool_t *pool) apr_size_t len; SVN_ERR_ASSERT(conn->read_ptr == conn->read_end); + + /* Make sure we tell the other side everything we have to say before + * reading / waiting for an answer. */ if (conn->write_pos) SVN_ERR(writebuf_flush(conn, pool)); + /* Fill (some of the) buffer. */ len = sizeof(conn->read_buf); SVN_ERR(readbuf_input(conn, conn->read_buf, &len, pool)); conn->read_ptr = conn->read_buf; @@ -512,22 +686,69 @@ svn_ra_svn__write_number(svn_ra_svn_conn_t *conn, return write_number(conn, pool, number, ' '); } +/* Write string S of length LEN to TARGET and return the first position + after the written data. + + NOTE: This function assumes that TARGET has enough room for S, the LEN + prefix and the required separators. The available buffer size + should be SVN_INT64_BUFFER_SIZE + LEN + 1 to avoid any chance of + overflow. + */ +static char * +write_ncstring_quick(char *target, + const char *s, + apr_size_t len) +{ + /* Write string length. */ + if (len < 10) + { + *target = (char)(len + '0'); + target++; + } + else + { + target += svn__ui64toa(target, len); + } + + /* Separator & contents. */ + target[0] = ':'; + memcpy(target + 1, s, len); + target[len + 1] = ' '; + + /* First location after the string. */ + return target + len + 2; +} + + static svn_error_t * svn_ra_svn__write_ncstring(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *s, apr_size_t len) { - if (len < 10) + /* Apart from LEN bytes of string contents, we need room for a number, + a colon and a space. */ + apr_size_t max_fill = sizeof(conn->write_buf) - SVN_INT64_BUFFER_SIZE - 2; + + /* In most cases, there is enough left room in the WRITE_BUF + the we can serialize directly into it. On platforms with + segmented memory, LEN might actually be close to APR_SIZE_MAX. + Blindly doing arithmetic on it might cause an overflow. */ + if ((len <= max_fill) && (conn->write_pos <= max_fill - len)) { - SVN_ERR(writebuf_writechar(conn, pool, (char)(len + '0'))); - SVN_ERR(writebuf_writechar(conn, pool, ':')); + /* Quick path. */ + conn->write_pos = write_ncstring_quick(conn->write_buf + + conn->write_pos, s, len) + - conn->write_buf; } else - SVN_ERR(write_number(conn, pool, len, ':')); + { + /* Slower fallback code. */ + SVN_ERR(write_number(conn, pool, len, ':')); - SVN_ERR(writebuf_write(conn, pool, s, len)); - SVN_ERR(writebuf_writechar(conn, pool, ' ')); + SVN_ERR(writebuf_write(conn, pool, s, len)); + SVN_ERR(writebuf_writechar(conn, pool, ' ')); + } return SVN_NO_ERROR; } @@ -755,6 +976,55 @@ write_tuple_string_opt(svn_ra_svn_conn_t *conn, return str ? svn_ra_svn__write_string(conn, pool, str) : SVN_NO_ERROR; } +/* Optimized sending code for the "(s?)" pattern. */ +static svn_error_t * +write_tuple_string_opt_list(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const svn_string_t *str) +{ + apr_size_t max_fill; + + /* Special case. */ + if (!str) + return writebuf_write(conn, pool, "( ) ", 4); + + /* If this how far we can fill the WRITE_BUF with string data and still + guarantee that the length info will fit in as well. */ + max_fill = sizeof(conn->write_buf) + - 2 /* open list */ + - SVN_INT64_BUFFER_SIZE /* string length + separator */ + - 2; /* close list */ + + /* On platforms with segmented memory, STR->LEN might actually be + close to APR_SIZE_MAX. Blindly doing arithmetic on it might + cause an overflow. */ + if ((str->len <= max_fill) && (conn->write_pos <= max_fill - str->len)) + { + /* Quick path. */ + /* Open list. */ + char *p = conn->write_buf + conn->write_pos; + p[0] = '('; + p[1] = ' '; + + /* Write string. */ + p = write_ncstring_quick(p + 2, str->data, str->len); + + /* Close list. */ + p[0] = ')'; + p[1] = ' '; + conn->write_pos = p + 2 - conn->write_buf; + } + else + { + /* Standard code path (fallback). */ + SVN_ERR(svn_ra_svn__start_list(conn, pool)); + SVN_ERR(svn_ra_svn__write_string(conn, pool, str)); + SVN_ERR(svn_ra_svn__end_list(conn, pool)); + } + + return SVN_NO_ERROR; +} + static svn_error_t * write_tuple_start_list(svn_ra_svn_conn_t *conn, apr_pool_t *pool) @@ -809,14 +1079,14 @@ static svn_error_t * write_cmd_add_node(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, const char *copy_path, svn_revnum_t copy_rev) { SVN_ERR(write_tuple_cstring(conn, pool, path)); - SVN_ERR(write_tuple_cstring(conn, pool, parent_token)); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, parent_token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_cstring_opt(conn, pool, copy_path)); SVN_ERR(write_tuple_revision_opt(conn, pool, copy_rev)); @@ -829,13 +1099,13 @@ static svn_error_t * write_cmd_open_node(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, svn_revnum_t rev) { SVN_ERR(write_tuple_cstring(conn, pool, path)); - SVN_ERR(write_tuple_cstring(conn, pool, parent_token)); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, parent_token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_revision_opt(conn, pool, rev)); SVN_ERR(write_tuple_end_list(conn, pool)); @@ -846,15 +1116,13 @@ write_cmd_open_node(svn_ra_svn_conn_t *conn, static svn_error_t * write_cmd_change_node_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const char *name, const svn_string_t *value) { - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_cstring(conn, pool, name)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_string_opt(conn, pool, value)); - SVN_ERR(write_tuple_end_list(conn, pool)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, value)); return SVN_NO_ERROR; } @@ -863,10 +1131,10 @@ static svn_error_t * write_cmd_absent_node(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *token) + const svn_string_t *token) { SVN_ERR(write_tuple_cstring(conn, pool, path)); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); return SVN_NO_ERROR; } @@ -892,10 +1160,17 @@ static svn_error_t *vwrite_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(opt ? vwrite_tuple_string_opt(conn, pool, ap) : vwrite_tuple_string(conn, pool, ap)); else if (*fmt == '(' && !opt) - SVN_ERR(write_tuple_start_list(conn, pool)); + { + /* Optional sub-tuples are not supported. + * If OPT was set, we would fall through to the malfunction call. */ + SVN_ERR(write_tuple_start_list(conn, pool)); + } else if (*fmt == ')') { SVN_ERR(write_tuple_end_list(conn, pool)); + + /* OPT could not have been set when opening the list (see above), + * hence this is correct and handles nested tuples just fine. */ opt = FALSE; } else if (*fmt == '?') @@ -939,7 +1214,7 @@ svn_ra_svn__write_tuple(svn_ra_svn_conn_t *conn, * Afterwards, *ITEM is of type 'SVN_RA_SVN_STRING', and its string * data is allocated in POOL. */ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - svn_ra_svn_item_t *item, apr_uint64_t len64) + svn_ra_svn__item_t *item, apr_uint64_t len64) { apr_size_t len = (apr_size_t)len64; apr_size_t readbuf_len; @@ -947,7 +1222,7 @@ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, apr_size_t buflen; /* We can't store strings longer than the maximum size of apr_size_t, - * so check for wrapping */ + * so check before using the truncated value. */ if (len64 > APR_SIZE_MAX) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("String length larger than maximum")); @@ -957,11 +1232,22 @@ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (len <= buflen) { item->kind = SVN_RA_SVN_STRING; - item->u.string = svn_string_ncreate(conn->read_ptr, len, pool); + item->u.string.data = apr_pstrmemdup(pool, conn->read_ptr, len); + item->u.string.len = len; conn->read_ptr += len; } else { + svn_stringbuf_t *stringbuf; + + /* Don't even attempt to read anything that exceeds the I/O limit. + * So, we can terminate the transfer at an early point, saving + * everybody's time and resources. */ + if (conn->max_in && (conn->max_in < len64)) + return svn_error_create(SVN_ERR_RA_SVN_REQUEST_SIZE, NULL, + "The client request size exceeds the " + "configured limit"); + /* Read the string in chunks. The chunk size is large enough to avoid * re-allocation in typical cases, and small enough to ensure we do * not pre-allocate an unreasonable amount of memory if (perhaps due @@ -970,7 +1256,7 @@ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, * start small and wait for all that data to actually show up. This * does not fully prevent DOS attacks but makes them harder (you have * to actually send gigabytes of data). */ - svn_stringbuf_t *stringbuf = svn_stringbuf_create_empty(pool); + stringbuf = svn_stringbuf_create_empty(pool); /* Read string data directly into the string structure. * Do it iteratively. */ @@ -998,7 +1284,8 @@ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, /* Return the string properly wrapped into an RA_SVN item. */ item->kind = SVN_RA_SVN_STRING; - item->u.string = svn_stringbuf__morph_into_string(stringbuf); + item->u.string.data = stringbuf->data; + item->u.string.len = stringbuf->len; } return SVN_NO_ERROR; @@ -1009,12 +1296,12 @@ static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, * to 0 for the first call and is used to enforce a recursion limit * on the parser. */ static svn_error_t *read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - svn_ra_svn_item_t *item, char first_char, + svn_ra_svn__item_t *item, char first_char, int level) { char c = first_char; apr_uint64_t val; - svn_ra_svn_item_t *listitem; + svn_ra_svn__item_t *listitem; if (++level >= ITEM_NESTING_LIMIT) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, @@ -1062,36 +1349,108 @@ static svn_error_t *read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool, char *p = buffer + 1; buffer[0] = c; - while (1) + if (conn->read_ptr + MAX_WORD_LENGTH <= conn->read_end) { - SVN_ERR(readbuf_getchar(conn, pool, p)); - if (!svn_ctype_isalnum(*p) && *p != '-') - break; - - if (++p == end) - return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, - _("Word is too long")); + /* Fast path: we can simply take a chunk from the read + * buffer and inspect it with no overflow checks etc. + * + * Copying these 24 bytes unconditionally is also faster + * than a variable-sized memcpy. Note that P is at BUFFER[1]. + */ + memcpy(p, conn->read_ptr, MAX_WORD_LENGTH - 1); + *end = 0; + + /* This will terminate at P == END because of *END == NUL. */ + while (svn_ctype_isalnum(*p) || *p == '-') + ++p; + + /* Only now do we mark data as actually read. */ + conn->read_ptr += p - buffer; + } + else + { + /* Slow path. Byte-by-byte copying and checking for + * input and output buffer boundaries. */ + for (p = buffer + 1; p != end; ++p) + { + SVN_ERR(readbuf_getchar(conn, pool, p)); + if (!svn_ctype_isalnum(*p) && *p != '-') + break; + } } + if (p == end) + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, + _("Word is too long")); + c = *p; *p = '\0'; + /* Store the word in ITEM. */ item->kind = SVN_RA_SVN_WORD; - item->u.word = buffer; + item->u.word.data = buffer; + item->u.word.len = p - buffer; } else if (c == '(') { + /* The largest struct that the protocol currently defines has 10 + * elements (log-entry) and add some headroom for future extensions. + * At a maximum nesting level of 64 this use <= 18kB of stack. + * + * All system-defined data structures will fit into this and will be + * copied into ITEM after a single apr_palloc with no over-provision. + * Unbounded lists with more than 12 but less than 25 entries will + * also see only a single allocation from POOL. However, there will + * be some over-provision. Longer lists will see log N resizes and + * O(N) total cost. + */ + svn_ra_svn__item_t stack_items[12]; + svn_ra_svn__item_t *items = stack_items; + int capacity = sizeof(stack_items) / sizeof(stack_items[0]); + int count = 0; + /* Read in the list items. */ item->kind = SVN_RA_SVN_LIST; - item->u.list = apr_array_make(pool, 4, sizeof(svn_ra_svn_item_t)); while (1) { SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c)); if (c == ')') break; - listitem = apr_array_push(item->u.list); + + /* Auto-expand the list. */ + if (count == capacity) + { + svn_ra_svn__item_t *new_items + = apr_palloc(pool, 2 * capacity * sizeof(*new_items)); + memcpy(new_items, items, capacity * sizeof(*new_items)); + items = new_items; + capacity = 2 * capacity; + } + + listitem = &items[count]; + ++count; + SVN_ERR(read_item(conn, pool, listitem, c, level)); } + + /* Store the list in ITEM - if not empty (= default). */ + if (count) + { + item->u.list.nelts = count; + + /* If we haven't allocated from POOL, yet, do it now. */ + if (items == stack_items) + item->u.list.items = apr_pmemdup(pool, items, + count * sizeof(*items)); + else + item->u.list.items = items; + } + else + { + item->u.list.items = NULL; + item->u.list.nelts = 0; + } + SVN_ERR(readbuf_getchar(conn, pool, &c)); } @@ -1193,7 +1552,7 @@ read_command_only(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t * svn_ra_svn__read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - svn_ra_svn_item_t **item) + svn_ra_svn__item_t **item) { char c; @@ -1243,36 +1602,38 @@ svn_ra_svn__skip_leading_garbage(svn_ra_svn_conn_t *conn, /* --- READING AND PARSING TUPLES --- */ -/* Parse a tuple of svn_ra_svn_item_t *'s. Advance *FMT to the end of the +/* Parse a tuple of svn_ra_svn__item_t *'s. Advance *FMT to the end of the * tuple specification and advance AP by the corresponding arguments. */ -static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *pool, - const char **fmt, va_list *ap) +static svn_error_t * +vparse_tuple(const svn_ra_svn__list_t *items, + const char **fmt, + va_list *ap) { int count, nesting_level; - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; for (count = 0; **fmt && count < items->nelts; (*fmt)++, count++) { /* '?' just means the tuple may stop; skip past it. */ if (**fmt == '?') (*fmt)++; - elt = &APR_ARRAY_IDX(items, count, svn_ra_svn_item_t); + elt = &SVN_RA_SVN__LIST_ITEM(items, count); if (**fmt == '(' && elt->kind == SVN_RA_SVN_LIST) { (*fmt)++; - SVN_ERR(vparse_tuple(elt->u.list, pool, fmt, ap)); + SVN_ERR(vparse_tuple(&elt->u.list, fmt, ap)); } else if (**fmt == 'c' && elt->kind == SVN_RA_SVN_STRING) - *va_arg(*ap, const char **) = elt->u.string->data; + *va_arg(*ap, const char **) = elt->u.string.data; else if (**fmt == 's' && elt->kind == SVN_RA_SVN_STRING) - *va_arg(*ap, svn_string_t **) = elt->u.string; + *va_arg(*ap, svn_string_t **) = &elt->u.string; else if (**fmt == 'w' && elt->kind == SVN_RA_SVN_WORD) - *va_arg(*ap, const char **) = elt->u.word; + *va_arg(*ap, const char **) = elt->u.word.data; else if (**fmt == 'b' && elt->kind == SVN_RA_SVN_WORD) { - if (strcmp(elt->u.word, "true") == 0) + if (svn_string_compare(&elt->u.word, &str_true)) *va_arg(*ap, svn_boolean_t *) = TRUE; - else if (strcmp(elt->u.word, "false") == 0) + else if (svn_string_compare(&elt->u.word, &str_false)) *va_arg(*ap, svn_boolean_t *) = FALSE; else break; @@ -1283,24 +1644,24 @@ static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *po *va_arg(*ap, svn_revnum_t *) = (svn_revnum_t) elt->u.number; else if (**fmt == 'B' && elt->kind == SVN_RA_SVN_WORD) { - if (strcmp(elt->u.word, "true") == 0) + if (svn_string_compare(&elt->u.word, &str_true)) *va_arg(*ap, apr_uint64_t *) = TRUE; - else if (strcmp(elt->u.word, "false") == 0) + else if (svn_string_compare(&elt->u.word, &str_false)) *va_arg(*ap, apr_uint64_t *) = FALSE; else break; } else if (**fmt == '3' && elt->kind == SVN_RA_SVN_WORD) { - if (strcmp(elt->u.word, "true") == 0) + if (svn_string_compare(&elt->u.word, &str_true)) *va_arg(*ap, svn_tristate_t *) = svn_tristate_true; - else if (strcmp(elt->u.word, "false") == 0) + else if (svn_string_compare(&elt->u.word, &str_false)) *va_arg(*ap, svn_tristate_t *) = svn_tristate_false; else break; } else if (**fmt == 'l' && elt->kind == SVN_RA_SVN_LIST) - *va_arg(*ap, apr_array_header_t **) = elt->u.list; + *va_arg(*ap, svn_ra_svn__list_t **) = &elt->u.list; else if (**fmt == ')') return SVN_NO_ERROR; else @@ -1326,7 +1687,7 @@ static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *po *va_arg(*ap, const char **) = NULL; break; case 'l': - *va_arg(*ap, apr_array_header_t **) = NULL; + *va_arg(*ap, svn_ra_svn__list_t **) = NULL; break; case 'B': case 'n': @@ -1335,6 +1696,9 @@ static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *po case '3': *va_arg(*ap, svn_tristate_t *) = svn_tristate_unknown; break; + case 'b': + *va_arg(*ap, svn_boolean_t *) = FALSE; + break; case '(': nesting_level++; break; @@ -1354,15 +1718,14 @@ static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *po } svn_error_t * -svn_ra_svn__parse_tuple(const apr_array_header_t *list, - apr_pool_t *pool, +svn_ra_svn__parse_tuple(const svn_ra_svn__list_t *list, const char *fmt, ...) { svn_error_t *err; va_list ap; va_start(ap, fmt); - err = vparse_tuple(list, pool, &fmt, &ap); + err = vparse_tuple(list, &fmt, &ap); va_end(ap); return err; } @@ -1373,7 +1736,7 @@ svn_ra_svn__read_tuple(svn_ra_svn_conn_t *conn, const char *fmt, ...) { va_list ap; - svn_ra_svn_item_t *item; + svn_ra_svn__item_t *item; svn_error_t *err; SVN_ERR(svn_ra_svn__read_item(conn, pool, &item)); @@ -1381,7 +1744,7 @@ svn_ra_svn__read_tuple(svn_ra_svn_conn_t *conn, return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Malformed network data")); va_start(ap, fmt); - err = vparse_tuple(item->u.list, pool, &fmt, &ap); + err = vparse_tuple(&item->u.list, &fmt, &ap); va_end(ap); return err; } @@ -1400,24 +1763,23 @@ svn_ra_svn__read_command_only(svn_ra_svn_conn_t *conn, svn_error_t * -svn_ra_svn__parse_proplist(const apr_array_header_t *list, +svn_ra_svn__parse_proplist(const svn_ra_svn__list_t *list, apr_pool_t *pool, apr_hash_t **props) { svn_string_t *name; svn_string_t *value; - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; int i; *props = svn_hash__make(pool); for (i = 0; i < list->nelts; i++) { - elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t); + elt = &SVN_RA_SVN__LIST_ITEM(list, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Proplist element not a list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "ss", - &name, &value)); + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "ss", &name, &value)); apr_hash_set(*props, name->data, name->len, value); } @@ -1442,15 +1804,14 @@ svn_error_t *svn_ra_svn__locate_real_error_child(svn_error_t *err) return this_link; } -svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params, - apr_pool_t *pool) +svn_error_t * +svn_ra_svn__handle_failure_status(const svn_ra_svn__list_t *params) { const char *message, *file; svn_error_t *err = NULL; - svn_ra_svn_item_t *elt; + svn_ra_svn__item_t *elt; int i; apr_uint64_t apr_err, line; - apr_pool_t *subpool = svn_pool_create(pool); if (params->nelts == 0) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, @@ -1459,12 +1820,11 @@ svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params, /* Rebuild the error list from the end, to avoid reversing the order. */ for (i = params->nelts - 1; i >= 0; i--) { - svn_pool_clear(subpool); - elt = &APR_ARRAY_IDX(params, i, svn_ra_svn_item_t); + elt = &SVN_RA_SVN__LIST_ITEM(params, i); if (elt->kind != SVN_RA_SVN_LIST) return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, _("Malformed error list")); - SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, subpool, "nccn", + SVN_ERR(svn_ra_svn__parse_tuple(&elt->u.list, "nccn", &apr_err, &message, &file, &line)); /* The message field should have been optional, but we can't easily change that, so "" means a nonexistent message. */ @@ -1483,8 +1843,6 @@ svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params, } } - svn_pool_destroy(subpool); - /* If we get here, then we failed to find a real error in the error chain that the server proported to be sending us. That's bad. */ if (! err) @@ -1501,20 +1859,20 @@ svn_ra_svn__read_cmd_response(svn_ra_svn_conn_t *conn, { va_list ap; const char *status; - apr_array_header_t *params; + svn_ra_svn__list_t *params; svn_error_t *err; SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &status, ¶ms)); if (strcmp(status, "success") == 0) { va_start(ap, fmt); - err = vparse_tuple(params, pool, &fmt, &ap); + err = vparse_tuple(params, &fmt, &ap); va_end(ap); return err; } else if (strcmp(status, "failure") == 0) { - return svn_error_trace(svn_ra_svn__handle_failure_status(params, pool)); + return svn_error_trace(svn_ra_svn__handle_failure_status(params)); } return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL, @@ -1528,7 +1886,12 @@ svn_ra_svn__has_command(svn_boolean_t *has_command, svn_ra_svn_conn_t *conn, apr_pool_t *pool) { - svn_error_t *err = svn_ra_svn__has_item(has_command, conn, pool); + svn_error_t *err; + + /* Don't make whitespace between commands trigger I/O limitiations. */ + svn_ra_svn__reset_command_io_counters(conn); + + err = svn_ra_svn__has_item(has_command, conn, pool); if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED) { *terminated = TRUE; @@ -1550,10 +1913,14 @@ svn_ra_svn__handle_command(svn_boolean_t *terminate, { const char *cmdname; svn_error_t *err, *write_err; - apr_array_header_t *params; - const svn_ra_svn_cmd_entry_t *command; + svn_ra_svn__list_t *params; + const svn_ra_svn__cmd_entry_t *command; *terminate = FALSE; + + /* Limit I/O for every command separately. */ + svn_ra_svn__reset_command_io_counters(conn); + err = svn_ra_svn__read_tuple(conn, pool, "wl", &cmdname, ¶ms); if (err) { @@ -1570,7 +1937,28 @@ svn_ra_svn__handle_command(svn_boolean_t *terminate, command = svn_hash_gets(cmd_hash, cmdname); if (command) { - err = (*command->handler)(conn, pool, params, baton); + /* Call the standard command handler. + * If that is not set, then this is a lecagy API call and we invoke + * the legacy command handler. */ + if (command->handler) + { + err = (*command->handler)(conn, pool, params, baton); + } + else + { + apr_array_header_t *deprecated_params + = svn_ra_svn__to_public_array(params, pool); + err = (*command->deprecated_handler)(conn, pool, deprecated_params, + baton); + } + + /* The command implementation may have swallowed or wrapped the I/O + * error not knowing that we may no longer be able to send data. + * + * So, check again for the limit violations and exit the command + * processing quickly if we may have truncated data. */ + err = svn_error_compose_create(check_io_limits(conn), err); + *terminate = command->terminate; } else @@ -1595,13 +1983,13 @@ svn_ra_svn__handle_command(svn_boolean_t *terminate, svn_error_t * svn_ra_svn__handle_commands2(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const svn_ra_svn_cmd_entry_t *commands, + const svn_ra_svn__cmd_entry_t *commands, void *baton, svn_boolean_t error_on_disconnect) { apr_pool_t *subpool = svn_pool_create(pool); apr_pool_t *iterpool = svn_pool_create(subpool); - const svn_ra_svn_cmd_entry_t *command; + const svn_ra_svn__cmd_entry_t *command; apr_hash_t *cmd_hash = apr_hash_make(subpool); for (command = commands; command->cmdname; command++) @@ -1644,13 +2032,13 @@ svn_error_t * svn_ra_svn__write_cmd_open_root(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_revnum_t rev, - const char *token) + const svn_string_t *token) { SVN_ERR(writebuf_write_literal(conn, pool, "( open-root ( ")); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_revision_opt(conn, pool, rev)); SVN_ERR(write_tuple_end_list(conn, pool)); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); return SVN_NO_ERROR; @@ -1661,14 +2049,14 @@ svn_ra_svn__write_cmd_delete_entry(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, svn_revnum_t rev, - const char *token) + const svn_string_t *token) { SVN_ERR(writebuf_write_literal(conn, pool, "( delete-entry ( ")); SVN_ERR(write_tuple_cstring(conn, pool, path)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_revision_opt(conn, pool, rev)); SVN_ERR(write_tuple_end_list(conn, pool)); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); return SVN_NO_ERROR; @@ -1678,8 +2066,8 @@ svn_error_t * svn_ra_svn__write_cmd_add_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, const char *copy_path, svn_revnum_t copy_rev) { @@ -1695,8 +2083,8 @@ svn_error_t * svn_ra_svn__write_cmd_open_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, svn_revnum_t rev) { SVN_ERR(writebuf_write_literal(conn, pool, "( open-dir ( ")); @@ -1709,7 +2097,7 @@ svn_ra_svn__write_cmd_open_dir(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_change_dir_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const char *name, const svn_string_t *value) { @@ -1723,10 +2111,10 @@ svn_ra_svn__write_cmd_change_dir_prop(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_close_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token) + const svn_string_t *token) { SVN_ERR(writebuf_write_literal(conn, pool, "( close-dir ( ")); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); return SVN_NO_ERROR; @@ -1736,7 +2124,7 @@ svn_error_t * svn_ra_svn__write_cmd_absent_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token) + const svn_string_t *parent_token) { SVN_ERR(writebuf_write_literal(conn, pool, "( absent-dir ( ")); SVN_ERR(write_cmd_absent_node(conn, pool, path, parent_token)); @@ -1749,8 +2137,8 @@ svn_error_t * svn_ra_svn__write_cmd_add_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, const char *copy_path, svn_revnum_t copy_rev) { @@ -1766,8 +2154,8 @@ svn_error_t * svn_ra_svn__write_cmd_open_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token, - const char *token, + const svn_string_t *parent_token, + const svn_string_t *token, svn_revnum_t rev) { SVN_ERR(writebuf_write_literal(conn, pool, "( open-file ( ")); @@ -1780,7 +2168,7 @@ svn_ra_svn__write_cmd_open_file(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_change_file_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const char *name, const svn_string_t *value) { @@ -1794,11 +2182,11 @@ svn_ra_svn__write_cmd_change_file_prop(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_close_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const char *text_checksum) { SVN_ERR(writebuf_write_literal(conn, pool, "( close-file ( ")); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_cstring_opt(conn, pool, text_checksum)); SVN_ERR(write_tuple_end_list(conn, pool)); @@ -1811,7 +2199,7 @@ svn_error_t * svn_ra_svn__write_cmd_absent_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *parent_token) + const svn_string_t *parent_token) { SVN_ERR(writebuf_write_literal(conn, pool, "( absent-file ( ")); SVN_ERR(write_cmd_absent_node(conn, pool, path, parent_token)); @@ -1823,11 +2211,11 @@ svn_ra_svn__write_cmd_absent_file(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_textdelta_chunk(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const svn_string_t *chunk) { SVN_ERR(writebuf_write_literal(conn, pool, "( textdelta-chunk ( ")); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_string(conn, pool, chunk)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); @@ -1837,10 +2225,10 @@ svn_ra_svn__write_cmd_textdelta_chunk(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_textdelta_end(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token) + const svn_string_t *token) { SVN_ERR(writebuf_write_literal(conn, pool, "( textdelta-end ( ")); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); return SVN_NO_ERROR; @@ -1849,11 +2237,11 @@ svn_ra_svn__write_cmd_textdelta_end(svn_ra_svn_conn_t *conn, svn_error_t * svn_ra_svn__write_cmd_apply_textdelta(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *token, + const svn_string_t *token, const char *base_checksum) { SVN_ERR(writebuf_write_literal(conn, pool, "( apply-textdelta ( ")); - SVN_ERR(write_tuple_cstring(conn, pool, token)); + SVN_ERR(write_tuple_string(conn, pool, token)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_cstring_opt(conn, pool, base_checksum)); SVN_ERR(write_tuple_end_list(conn, pool)); @@ -1991,9 +2379,7 @@ svn_ra_svn__write_cmd_change_rev_prop2(svn_ra_svn_conn_t *conn, SVN_ERR(writebuf_write_literal(conn, pool, "( change-rev-prop2 ( ")); SVN_ERR(write_tuple_revision(conn, pool, rev)); SVN_ERR(write_tuple_cstring(conn, pool, name)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_string_opt(conn, pool, value)); - SVN_ERR(write_tuple_end_list(conn, pool)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, value)); SVN_ERR(write_tuple_start_list(conn, pool)); SVN_ERR(write_tuple_boolean(conn, pool, dont_care)); SVN_ERR(write_tuple_string_opt(conn, pool, old_value)); @@ -2245,14 +2631,12 @@ svn_error_t * svn_ra_svn__write_cmd_unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path, - const char *token, + const svn_string_t *token, svn_boolean_t break_lock) { SVN_ERR(writebuf_write_literal(conn, pool, "( unlock ( ")); SVN_ERR(write_tuple_cstring(conn, pool, path)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_cstring_opt(conn, pool, token)); - SVN_ERR(write_tuple_end_list(conn, pool)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, token)); SVN_ERR(write_tuple_boolean(conn, pool, break_lock)); SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); @@ -2402,10 +2786,53 @@ svn_error_t *svn_ra_svn__write_cmd_failure(svn_ra_svn_conn_t *conn, return writebuf_write_literal(conn, pool, ") ) "); } +/* Initializer for static svn_string_t . */ +#define STATIC_SVN_STRING(x) { x, sizeof(x) - 1 } + +/* Return a pre-cooked serialized representation for the changed path + flags NODE_KIND, TEXT_MODIFIED and PROPS_MODIFIED. If we don't + have a suitable pre-cooked string, return an empty string. */ +static const svn_string_t * +changed_path_flags(svn_node_kind_t node_kind, + svn_boolean_t text_modified, + svn_boolean_t props_modified) +{ + static const svn_string_t file_flags[4] + = { STATIC_SVN_STRING(" ) ( 4:file false false ) ) "), + STATIC_SVN_STRING(" ) ( 4:file false true ) ) "), + STATIC_SVN_STRING(" ) ( 4:file true false ) ) "), + STATIC_SVN_STRING(" ) ( 4:file true true ) ) ") }; + + static const svn_string_t dir_flags[4] + = { STATIC_SVN_STRING(" ) ( 3:dir false false ) ) "), + STATIC_SVN_STRING(" ) ( 3:dir false true ) ) "), + STATIC_SVN_STRING(" ) ( 3:dir true false ) ) "), + STATIC_SVN_STRING(" ) ( 3:dir true true ) ) ") }; + + static const svn_string_t no_flags = STATIC_SVN_STRING(""); + + /* Select the array based on the NODE_KIND. */ + const svn_string_t *flags; + if (node_kind == svn_node_file) + flags = file_flags; + else if (node_kind == svn_node_dir) + flags = dir_flags; + else + return &no_flags; + + /* Select the correct array entry. */ + if (text_modified) + flags += 2; + if (props_modified) + flags++; + + return flags; +} + svn_error_t * svn_ra_svn__write_data_log_changed_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, - const char *path, + const svn_string_t *path, char action, const char *copyfrom_path, svn_revnum_t copyfrom_rev, @@ -2413,21 +2840,83 @@ svn_ra_svn__write_data_log_changed_path(svn_ra_svn_conn_t *conn, svn_boolean_t text_modified, svn_boolean_t props_modified) { - SVN_ERR(write_tuple_start_list(conn, pool)); + apr_size_t path_len = path->len; + apr_size_t copyfrom_len = copyfrom_path ? strlen(copyfrom_path) : 0; + const svn_string_t *flags_str = changed_path_flags(node_kind, + text_modified, + props_modified); + apr_size_t flags_len = flags_str->len; + + /* How much buffer space can we use for non-string data (worst case)? */ + apr_size_t max_fill = sizeof(conn->write_buf) + - 2 /* list start */ + - 2 - SVN_INT64_BUFFER_SIZE /* path */ + - 2 /* action */ + - 2 /* list start */ + - 2 - SVN_INT64_BUFFER_SIZE /* copy-from path */ + - 1 - SVN_INT64_BUFFER_SIZE; /* copy-from rev */ + + /* If the remaining buffer is big enough and we've got all parts, + directly copy into the buffer. On platforms with segmented memory, + PATH_LEN + COPYFROM_LEN might actually be close to APR_SIZE_MAX. + Blindly doing arithmetic on them might cause an overflow. + The sum in here cannot overflow because WRITE_BUF is small, i.e. + MAX_FILL and WRITE_POS are much smaller than APR_SIZE_MAX. */ + if ( (path_len <= max_fill) && (copyfrom_len <= max_fill) + && (conn->write_pos + path_len + copyfrom_len + flags_len <= max_fill) + && (flags_len > 0)) + { + /* Quick path. */ + /* Open list. */ + char *p = conn->write_buf + conn->write_pos; + p[0] = '('; + p[1] = ' '; + + /* Write path. */ + p = write_ncstring_quick(p + 2, path->data, path_len); + + /* Action */ + p[0] = action; + p[1] = ' '; + p[2] = '('; + + /* Copy-from info (if given) */ + if (copyfrom_path) + { + p[3] = ' '; + p = write_ncstring_quick(p + 4, copyfrom_path, copyfrom_len); + p += svn__ui64toa(p, copyfrom_rev); + } + else + { + p += 3; + } - SVN_ERR(write_tuple_cstring(conn, pool, path)); - SVN_ERR(writebuf_writechar(conn, pool, action)); - SVN_ERR(writebuf_writechar(conn, pool, ' ')); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_cstring_opt(conn, pool, copyfrom_path)); - SVN_ERR(write_tuple_revision_opt(conn, pool, copyfrom_rev)); - SVN_ERR(write_tuple_end_list(conn, pool)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_cstring(conn, pool, svn_node_kind_to_word(node_kind))); - SVN_ERR(write_tuple_boolean(conn, pool, text_modified)); - SVN_ERR(write_tuple_boolean(conn, pool, props_modified)); + /* Close with flags. */ + memcpy(p, flags_str->data, flags_str->len); + conn->write_pos = p + flags_str->len - conn->write_buf; + } + else + { + /* Standard code path (fallback). */ + SVN_ERR(write_tuple_start_list(conn, pool)); + + SVN_ERR(svn_ra_svn__write_ncstring(conn, pool, path->data, path_len)); + SVN_ERR(writebuf_writechar(conn, pool, action)); + SVN_ERR(writebuf_writechar(conn, pool, ' ')); + SVN_ERR(write_tuple_start_list(conn, pool)); + SVN_ERR(write_tuple_cstring_opt(conn, pool, copyfrom_path)); + SVN_ERR(write_tuple_revision_opt(conn, pool, copyfrom_rev)); + SVN_ERR(write_tuple_end_list(conn, pool)); + SVN_ERR(write_tuple_start_list(conn, pool)); + SVN_ERR(write_tuple_cstring(conn, pool, svn_node_kind_to_word(node_kind))); + SVN_ERR(write_tuple_boolean(conn, pool, text_modified)); + SVN_ERR(write_tuple_boolean(conn, pool, props_modified)); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); + } - return writebuf_write_literal(conn, pool, ") ) "); + return SVN_NO_ERROR; } svn_error_t * @@ -2442,15 +2931,9 @@ svn_ra_svn__write_data_log_entry(svn_ra_svn_conn_t *conn, unsigned revprop_count) { SVN_ERR(write_tuple_revision(conn, pool, revision)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_string_opt(conn, pool, author)); - SVN_ERR(write_tuple_end_list(conn, pool)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_string_opt(conn, pool, date)); - SVN_ERR(write_tuple_end_list(conn, pool)); - SVN_ERR(write_tuple_start_list(conn, pool)); - SVN_ERR(write_tuple_string_opt(conn, pool, message)); - SVN_ERR(write_tuple_end_list(conn, pool)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, author)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, date)); + SVN_ERR(write_tuple_string_opt_list(conn, pool, message)); SVN_ERR(write_tuple_boolean(conn, pool, has_children)); SVN_ERR(write_tuple_boolean(conn, pool, invalid_revnum)); SVN_ERR(svn_ra_svn__write_number(conn, pool, revprop_count)); @@ -2458,6 +2941,57 @@ svn_ra_svn__write_data_log_entry(svn_ra_svn_conn_t *conn, return SVN_NO_ERROR; } +svn_error_t * +svn_ra_svn__write_dirent(svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *path, + svn_dirent_t *dirent, + apr_uint32_t dirent_fields) +{ + const char *kind = (dirent_fields & SVN_DIRENT_KIND) + ? svn_node_kind_to_word(dirent->kind) + : "unknown"; + + if (dirent_fields & ~SVN_DIRENT_KIND) + { + SVN_ERR(write_tuple_start_list(conn, pool)); + SVN_ERR(write_tuple_cstring(conn, pool, path)); + SVN_ERR(writebuf_write(conn, pool, kind, strlen(kind))); + + SVN_ERR(writebuf_write_literal(conn, pool, " ( ")); + if (dirent_fields & SVN_DIRENT_SIZE) + SVN_ERR(svn_ra_svn__write_number(conn, pool, dirent->size)); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ( ")); + if (dirent_fields & SVN_DIRENT_HAS_PROPS) + SVN_ERR(write_tuple_boolean(conn, pool, dirent->has_props)); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ( ")); + if (dirent_fields & SVN_DIRENT_CREATED_REV) + SVN_ERR(write_tuple_revision(conn, pool, dirent->created_rev)); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ( ")); + if (dirent_fields & SVN_DIRENT_TIME) + SVN_ERR(write_tuple_cstring_opt(conn, pool, + svn_time_to_cstring(dirent->time, pool))); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ( ")); + if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) + SVN_ERR(write_tuple_cstring_opt(conn, pool, dirent->last_author)); + + SVN_ERR(writebuf_write_literal(conn, pool, ") ) ")); + } + else + { + SVN_ERR(write_tuple_start_list(conn, pool)); + SVN_ERR(write_tuple_cstring(conn, pool, path)); + SVN_ERR(writebuf_write(conn, pool, kind, strlen(kind))); + SVN_ERR(writebuf_write_literal(conn, pool, " ) ")); + } + + return SVN_NO_ERROR; +} + /* If condition COND is not met, return a "malformed network data" error. */ #define CHECK_PROTOCOL_COND(cond)\ @@ -2468,13 +3002,13 @@ svn_ra_svn__write_data_log_entry(svn_ra_svn_conn_t *conn, /* In *RESULT, return the SVN-style string at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_string(const apr_array_header_t *items, +svn_ra_svn__read_string(const svn_ra_svn__list_t *items, int idx, svn_string_t **result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_STRING); - *result = elt->u.string; + *result = &elt->u.string; return SVN_NO_ERROR; } @@ -2482,13 +3016,13 @@ svn_ra_svn__read_string(const apr_array_header_t *items, /* In *RESULT, return the C-style string at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_cstring(const apr_array_header_t *items, +svn_ra_svn__read_cstring(const svn_ra_svn__list_t *items, int idx, const char **result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_STRING); - *result = elt->u.string->data; + *result = elt->u.string.data; return SVN_NO_ERROR; } @@ -2496,13 +3030,13 @@ svn_ra_svn__read_cstring(const apr_array_header_t *items, /* In *RESULT, return the word at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_word(const apr_array_header_t *items, +svn_ra_svn__read_word(const svn_ra_svn__list_t *items, int idx, const char **result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_WORD); - *result = elt->u.word; + *result = elt->u.word.data; return SVN_NO_ERROR; } @@ -2510,11 +3044,11 @@ svn_ra_svn__read_word(const apr_array_header_t *items, /* In *RESULT, return the revision at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_revision(const apr_array_header_t *items, +svn_ra_svn__read_revision(const svn_ra_svn__list_t *items, int idx, svn_revnum_t *result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_NUMBER); *result = (svn_revnum_t)elt->u.number; @@ -2524,15 +3058,15 @@ svn_ra_svn__read_revision(const apr_array_header_t *items, /* In *RESULT, return the boolean at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_boolean(const apr_array_header_t *items, +svn_ra_svn__read_boolean(const svn_ra_svn__list_t *items, int idx, apr_uint64_t *result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_WORD); - if (elt->u.word[0] == 't' && strcmp(elt->u.word, "true") == 0) + if (svn_string_compare(&elt->u.word, &str_true)) *result = TRUE; - else if (strcmp(elt->u.word, "false") == 0) + else if (svn_string_compare(&elt->u.word, &str_false)) *result = FALSE; else CHECK_PROTOCOL_COND(FALSE); @@ -2543,21 +3077,21 @@ svn_ra_svn__read_boolean(const apr_array_header_t *items, /* In *RESULT, return the tuple at index IDX in tuple ITEMS. */ static svn_error_t * -svn_ra_svn__read_list(const apr_array_header_t *items, +svn_ra_svn__read_list(const svn_ra_svn__list_t *items, int idx, - const apr_array_header_t **result) + const svn_ra_svn__list_t **result) { - svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(items, idx, svn_ra_svn_item_t); + svn_ra_svn__item_t *elt = &SVN_RA_SVN__LIST_ITEM(items, idx); CHECK_PROTOCOL_COND(elt->kind == SVN_RA_SVN_LIST); - *result = elt->u.list; + *result = &elt->u.list; return SVN_NO_ERROR; } /* Verify the tuple ITEMS contains at least MIN and at most MAX elements. */ static svn_error_t * -svn_ra_svn__read_check_array_size(const apr_array_header_t *items, +svn_ra_svn__read_check_array_size(const svn_ra_svn__list_t *items, int min, int max) { @@ -2566,7 +3100,7 @@ svn_ra_svn__read_check_array_size(const apr_array_header_t *items, } svn_error_t * -svn_ra_svn__read_data_log_changed_entry(const apr_array_header_t *items, +svn_ra_svn__read_data_log_changed_entry(const svn_ra_svn__list_t *items, svn_string_t **cpath, const char **action, const char **copy_path, @@ -2575,7 +3109,7 @@ svn_ra_svn__read_data_log_changed_entry(const apr_array_header_t *items, apr_uint64_t *text_mods, apr_uint64_t *prop_mods) { - const apr_array_header_t *sub_items; + const svn_ra_svn__list_t *sub_items; /* initialize optional values */ *copy_path = NULL; |