1890
svn_fs_fs__revision_exists(svn_revnum_t rev,
1894
/* Different order of parameters. */
1895
SVN_ERR(ensure_revision_exists(fs, rev, pool));
1896
return SVN_NO_ERROR;
1899
/* Open the correct revision file for REV. If the filesystem FS has
1900
been packed, *FILE will be set to the packed file; otherwise, set *FILE
1901
to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the
1904
TODO: Consider returning an indication of whether this is a packed rev
1905
file, so the caller need not rely on is_packed_rev() which in turn
1906
relies on the cached FFD->min_unpacked_rev value not having changed
1907
since the rev file was opened.
1909
Use POOL for allocations. */
1910
static svn_error_t *
1911
open_pack_or_rev_file(apr_file_t **file,
1916
fs_fs_data_t *ffd = fs->fsap_data;
1919
svn_boolean_t retry = FALSE;
1923
err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
1925
/* open the revision file in buffered r/o mode */
1927
err = svn_io_file_open(file, path,
1928
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1930
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1370
svn_fs_fs__file_length(svn_filesize_t *length,
1371
node_revision_t *noderev,
1374
representation_t *data_rep = noderev->data_rep;
1377
/* Treat "no representation" as "empty file". */
1380
else if (data_rep->expanded_size)
1382
/* Standard case: a non-empty file. */
1383
*length = data_rep->expanded_size;
1387
/* Work around a FSFS format quirk (see issue #4554).
1389
A plain representation may specify its EXPANDED LENGTH as "0"
1390
in which case, the SIZE value is what we want.
1392
Because EXPANDED_LENGTH will also be 0 for empty files, while
1393
SIZE is non-null, we need to check wether the content is
1394
actually empty. We simply compare with the MD5 checksum of
1395
empty content (sha-1 is not always available).
1397
svn_checksum_t *empty_md5
1398
= svn_checksum_empty_checksum(svn_checksum_md5, pool);
1400
if (memcmp(empty_md5->digest, data_rep->md5_digest,
1401
sizeof(data_rep->md5_digest)))
1932
if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1934
/* Could not open the file. This may happen if the
1935
* file once existed but got packed later. */
1936
svn_error_clear(err);
1938
/* if that was our 2nd attempt, leave it at that. */
1940
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1941
_("No such revision %ld"), rev);
1943
/* We failed for the first time. Refresh cache & retry. */
1944
SVN_ERR(update_min_unpacked_rev(fs, pool));
1950
svn_error_clear(err);
1951
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1952
_("No such revision %ld"), rev);
1403
/* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
1404
actual file length. */
1405
*length = data_rep->size;
1409
/* Contents is empty. */
1962
return svn_error_trace(err);
1965
/* Reads a line from STREAM and converts it to a 64 bit integer to be
1966
* returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
1967
* *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
1969
* SCRATCH_POOL is used for temporary allocations.
1971
static svn_error_t *
1972
read_number_from_stream(apr_int64_t *result,
1973
svn_boolean_t *hit_eof,
1974
svn_stream_t *stream,
1975
apr_pool_t *scratch_pool)
1977
svn_stringbuf_t *sb;
1981
SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
1986
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
1990
err = svn_cstring_atoi64(result, sb->data);
1992
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1993
_("Number '%s' invalid or too large"),
1997
return SVN_NO_ERROR;
2000
/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
2001
Use POOL for temporary allocations. */
2002
static svn_error_t *
2003
get_packed_offset(apr_off_t *rev_offset,
2008
fs_fs_data_t *ffd = fs->fsap_data;
2009
svn_stream_t *manifest_stream;
2010
svn_boolean_t is_cached;
2012
apr_int64_t shard_pos;
2013
apr_array_header_t *manifest;
2014
apr_pool_t *iterpool;
2016
shard = rev / ffd->max_files_per_dir;
2018
/* position of the shard within the manifest */
2019
shard_pos = rev % ffd->max_files_per_dir;
2021
/* fetch exactly that element into *rev_offset, if the manifest is found
2023
SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
2024
ffd->packed_offset_cache, &shard,
2025
svn_fs_fs__get_sharded_offset, &shard_pos,
2029
return SVN_NO_ERROR;
2031
/* Open the manifest file. */
2032
SVN_ERR(svn_stream_open_readonly(&manifest_stream,
2033
path_rev_packed(fs, rev, PATH_MANIFEST,
2037
/* While we're here, let's just read the entire manifest file into an array,
2038
so we can cache the entire thing. */
2039
iterpool = svn_pool_create(pool);
2040
manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
2046
svn_pool_clear(iterpool);
2047
SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
2051
APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
2053
svn_pool_destroy(iterpool);
2055
*rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
2058
/* Close up shop and cache the array. */
2059
SVN_ERR(svn_stream_close(manifest_stream));
2060
return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
2063
/* Open the revision file for revision REV in filesystem FS and store
2064
the newly opened file in FILE. Seek to location OFFSET before
2065
returning. Perform temporary allocations in POOL. */
2066
static svn_error_t *
2067
open_and_seek_revision(apr_file_t **file,
2073
apr_file_t *rev_file;
2075
SVN_ERR(ensure_revision_exists(fs, rev, pool));
2077
SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
2079
if (is_packed_rev(fs, rev))
2081
apr_off_t rev_offset;
2083
SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2084
offset += rev_offset;
2087
SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2091
return SVN_NO_ERROR;
2094
/* Open the representation for a node-revision in transaction TXN_ID
2095
in filesystem FS and store the newly opened file in FILE. Seek to
2096
location OFFSET before returning. Perform temporary allocations in
2097
POOL. Only appropriate for file contents, nor props or directory
2099
static svn_error_t *
2100
open_and_seek_transaction(apr_file_t **file,
1414
return SVN_NO_ERROR;
1418
svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
1422
svn_boolean_t strict,
1423
apr_pool_t *scratch_pool)
1425
svn_stream_t *contents_a, *contents_b;
1426
representation_t *rep_a = a->data_rep;
1427
representation_t *rep_b = b->data_rep;
1428
svn_boolean_t a_empty = !rep_a || rep_a->expanded_size == 0;
1429
svn_boolean_t b_empty = !rep_b || rep_b->expanded_size == 0;
1431
/* This makes sure that neither rep will be NULL later on */
1432
if (a_empty && b_empty)
1435
return SVN_NO_ERROR;
1438
if (a_empty != b_empty)
1441
return SVN_NO_ERROR;
1444
/* File text representations always know their checksums - even in a txn. */
1445
if (memcmp(rep_a->md5_digest, rep_b->md5_digest, sizeof(rep_a->md5_digest)))
1448
return SVN_NO_ERROR;
1451
/* Paranoia. Compare SHA1 checksums because that's the level of
1452
confidence we require for e.g. the working copy. */
1453
if (rep_a->has_sha1 && rep_b->has_sha1)
1455
*equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
1456
sizeof(rep_a->sha1_digest)) == 0;
1457
return SVN_NO_ERROR;
1460
/* Same path in same rev or txn? */
1461
if (svn_fs_fs__id_eq(a->id, b->id))
1464
return SVN_NO_ERROR;
1467
/* Old repositories may not have the SHA1 checksum handy.
1468
This check becomes expensive. Skip it unless explicitly required.
1470
We already have seen that the ID is different, so produce a likely
1471
false negative as allowed by the API description - even though the
1472
MD5 matched, there is an extremely slim chance that the SHA1 wouldn't.
1477
return SVN_NO_ERROR;
1480
SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
1482
SVN_ERR(svn_fs_fs__get_contents(&contents_b, fs, rep_b, TRUE,
1484
SVN_ERR(svn_stream_contents_same2(equal, contents_a, contents_b,
1487
return SVN_NO_ERROR;
1491
svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
2103
representation_t *rep,
2106
apr_file_t *rev_file;
2109
SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
2110
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
2112
offset = rep->offset;
2113
SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2117
return SVN_NO_ERROR;
2120
/* Given a node-id ID, and a representation REP in filesystem FS, open
2121
the correct file and seek to the correction location. Store this
2122
file in *FILE_P. Perform any allocations in POOL. */
2123
static svn_error_t *
2124
open_and_seek_representation(apr_file_t **file_p,
2126
representation_t *rep,
2130
return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
2133
return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
2136
/* Parse the description of a representation from STRING and store it
2137
into *REP_P. If the representation is mutable (the revision is
2138
given as -1), then use TXN_ID for the representation's txn_id
2139
field. If MUTABLE_REP_TRUNCATED is true, then this representation
2140
is for property or directory contents, and no information will be
2141
expected except the "-1" revision number for a mutable
2142
representation. Allocate *REP_P in POOL. */
2143
static svn_error_t *
2144
read_rep_offsets_body(representation_t **rep_p,
2147
svn_boolean_t mutable_rep_truncated,
2150
representation_t *rep;
2154
rep = apr_pcalloc(pool, sizeof(*rep));
2157
str = svn_cstring_tokenize(" ", &string);
2159
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2160
_("Malformed text representation offset line in node-rev"));
2163
rep->revision = SVN_STR_TO_REV(str);
2164
if (rep->revision == SVN_INVALID_REVNUM)
2166
rep->txn_id = txn_id;
2167
if (mutable_rep_truncated)
2168
return SVN_NO_ERROR;
2171
str = svn_cstring_tokenize(" ", &string);
2173
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2174
_("Malformed text representation offset line in node-rev"));
2176
SVN_ERR(svn_cstring_atoi64(&val, str));
2177
rep->offset = (apr_off_t)val;
2179
str = svn_cstring_tokenize(" ", &string);
2181
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2182
_("Malformed text representation offset line in node-rev"));
2184
SVN_ERR(svn_cstring_atoi64(&val, str));
2185
rep->size = (svn_filesize_t)val;
2187
str = svn_cstring_tokenize(" ", &string);
2189
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2190
_("Malformed text representation offset line in node-rev"));
2192
SVN_ERR(svn_cstring_atoi64(&val, str));
2193
rep->expanded_size = (svn_filesize_t)val;
2195
/* Read in the MD5 hash. */
2196
str = svn_cstring_tokenize(" ", &string);
2197
if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
2198
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2199
_("Malformed text representation offset line in node-rev"));
2201
SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
2204
/* The remaining fields are only used for formats >= 4, so check that. */
2205
str = svn_cstring_tokenize(" ", &string);
2207
return SVN_NO_ERROR;
2209
/* Read the SHA1 hash. */
2210
if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
2211
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2212
_("Malformed text representation offset line in node-rev"));
2214
SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
2217
/* Read the uniquifier. */
2218
str = svn_cstring_tokenize(" ", &string);
2220
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2221
_("Malformed text representation offset line in node-rev"));
2223
rep->uniquifier = apr_pstrdup(pool, str);
2225
return SVN_NO_ERROR;
2228
/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
2229
and adding an error message. */
2230
static svn_error_t *
2231
read_rep_offsets(representation_t **rep_p,
2233
const svn_fs_id_t *noderev_id,
2234
svn_boolean_t mutable_rep_truncated,
2241
txn_id = svn_fs_fs__id_txn_id(noderev_id);
2245
err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
2249
const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
2251
where = apr_psprintf(pool,
2252
_("While reading representation offsets "
2253
"for node-revision '%s':"),
2254
noderev_id ? id_unparsed->data : "(null)");
2256
return svn_error_quick_wrap(err, where);
2259
return SVN_NO_ERROR;
2262
static svn_error_t *
2263
err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
2265
svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
2266
return svn_error_createf
2267
(SVN_ERR_FS_ID_NOT_FOUND, 0,
2268
_("Reference to non-existent node '%s' in filesystem '%s'"),
2269
id_str->data, fs->path);
2272
/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
2273
* caching has been enabled and the data can be found, IS_CACHED will
2274
* be set to TRUE. The noderev will be allocated from POOL.
2276
* Non-permanent ids (e.g. ids within a TXN) will not be cached.
2278
static svn_error_t *
2279
get_cached_node_revision_body(node_revision_t **noderev_p,
2281
const svn_fs_id_t *id,
2282
svn_boolean_t *is_cached,
2285
fs_fs_data_t *ffd = fs->fsap_data;
2286
if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
2292
pair_cache_key_t key = { 0 };
2294
key.revision = svn_fs_fs__id_rev(id);
2295
key.second = svn_fs_fs__id_offset(id);
2296
SVN_ERR(svn_cache__get((void **) noderev_p,
2298
ffd->node_revision_cache,
2303
return SVN_NO_ERROR;
2306
/* If noderev caching has been enabled, store the NODEREV_P for the given ID
2307
* in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
2309
* Non-permanent ids (e.g. ids within a TXN) will not be cached.
2311
static svn_error_t *
2312
set_cached_node_revision_body(node_revision_t *noderev_p,
2314
const svn_fs_id_t *id,
2315
apr_pool_t *scratch_pool)
2317
fs_fs_data_t *ffd = fs->fsap_data;
2319
if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
2321
pair_cache_key_t key = { 0 };
2323
key.revision = svn_fs_fs__id_rev(id);
2324
key.second = svn_fs_fs__id_offset(id);
2325
return svn_cache__set(ffd->node_revision_cache,
2331
return SVN_NO_ERROR;
2334
/* Get the node-revision for the node ID in FS.
2335
Set *NODEREV_P to the new node-revision structure, allocated in POOL.
2336
See svn_fs_fs__get_node_revision, which wraps this and adds another
2338
static svn_error_t *
2339
get_node_revision_body(node_revision_t **noderev_p,
2341
const svn_fs_id_t *id,
2344
apr_file_t *revision_file;
2346
svn_boolean_t is_cached = FALSE;
2348
/* First, try a cache lookup. If that succeeds, we are done here. */
2349
SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
2351
return SVN_NO_ERROR;
2353
if (svn_fs_fs__id_txn_id(id))
2355
/* This is a transaction node-rev. */
2356
err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
2357
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2361
/* This is a revision node-rev. */
2362
err = open_and_seek_revision(&revision_file, fs,
2363
svn_fs_fs__id_rev(id),
2364
svn_fs_fs__id_offset(id),
2370
if (APR_STATUS_IS_ENOENT(err->apr_err))
2372
svn_error_clear(err);
2373
return svn_error_trace(err_dangling_id(fs, id));
2376
return svn_error_trace(err);
2379
SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
2380
svn_stream_from_aprfile2(revision_file, FALSE,
2384
/* The noderev is not in cache, yet. Add it, if caching has been enabled. */
2385
return set_cached_node_revision_body(*noderev_p, fs, id, pool);
2389
svn_fs_fs__read_noderev(node_revision_t **noderev_p,
2390
svn_stream_t *stream,
2393
apr_hash_t *headers;
2394
node_revision_t *noderev;
2396
const char *noderev_id;
2398
SVN_ERR(read_header_block(&headers, stream, pool));
2400
noderev = apr_pcalloc(pool, sizeof(*noderev));
2402
/* Read the node-rev id. */
2403
value = svn_hash_gets(headers, HEADER_ID);
2405
/* ### More information: filename/offset coordinates */
2406
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407
_("Missing id field in node-rev"));
2409
SVN_ERR(svn_stream_close(stream));
2411
noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
2412
noderev_id = value; /* for error messages later */
2414
/* Read the type. */
2415
value = svn_hash_gets(headers, HEADER_TYPE);
2417
if ((value == NULL) ||
2418
(strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
2419
/* ### s/kind/type/ */
2420
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2421
_("Missing kind field in node-rev '%s'"),
2424
noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
2427
/* Read the 'count' field. */
2428
value = svn_hash_gets(headers, HEADER_COUNT);
2430
SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
2432
noderev->predecessor_count = 0;
2434
/* Get the properties location. */
2435
value = svn_hash_gets(headers, HEADER_PROPS);
2438
SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
2439
noderev->id, TRUE, pool));
2442
/* Get the data location. */
2443
value = svn_hash_gets(headers, HEADER_TEXT);
2446
SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
2448
(noderev->kind == svn_node_dir), pool));
2451
/* Get the created path. */
2452
value = svn_hash_gets(headers, HEADER_CPATH);
2455
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2456
_("Missing cpath field in node-rev '%s'"),
2461
noderev->created_path = apr_pstrdup(pool, value);
2464
/* Get the predecessor ID. */
2465
value = svn_hash_gets(headers, HEADER_PRED);
2467
noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
2470
/* Get the copyroot. */
2471
value = svn_hash_gets(headers, HEADER_COPYROOT);
2474
noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
2475
noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
2481
str = svn_cstring_tokenize(" ", &value);
2483
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2484
_("Malformed copyroot line in node-rev '%s'"),
2487
noderev->copyroot_rev = SVN_STR_TO_REV(str);
2490
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2491
_("Malformed copyroot line in node-rev '%s'"),
2493
noderev->copyroot_path = apr_pstrdup(pool, value);
2496
/* Get the copyfrom. */
2497
value = svn_hash_gets(headers, HEADER_COPYFROM);
2500
noderev->copyfrom_path = NULL;
2501
noderev->copyfrom_rev = SVN_INVALID_REVNUM;
2505
char *str = svn_cstring_tokenize(" ", &value);
2507
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2508
_("Malformed copyfrom line in node-rev '%s'"),
2511
noderev->copyfrom_rev = SVN_STR_TO_REV(str);
2514
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2515
_("Malformed copyfrom line in node-rev '%s'"),
2517
noderev->copyfrom_path = apr_pstrdup(pool, value);
2520
/* Get whether this is a fresh txn root. */
2521
value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
2522
noderev->is_fresh_txn_root = (value != NULL);
2524
/* Get the mergeinfo count. */
2525
value = svn_hash_gets(headers, HEADER_MINFO_CNT);
2527
SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
2529
noderev->mergeinfo_count = 0;
2531
/* Get whether *this* node has mergeinfo. */
2532
value = svn_hash_gets(headers, HEADER_MINFO_HERE);
2533
noderev->has_mergeinfo = (value != NULL);
2535
*noderev_p = noderev;
2537
return SVN_NO_ERROR;
2541
svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
2543
const svn_fs_id_t *id,
2546
svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
2547
if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2549
svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
2550
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2551
"Corrupt node-revision '%s'",
2554
return svn_error_trace(err);
2558
/* Return a formatted string, compatible with filesystem format FORMAT,
2559
that represents the location of representation REP. If
2560
MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
2561
and only a "-1" revision number will be given for a mutable rep.
2562
If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
2563
Perform the allocation from POOL. */
2565
representation_string(representation_t *rep,
2567
svn_boolean_t mutable_rep_truncated,
2568
svn_boolean_t may_be_corrupt,
2571
if (rep->txn_id && mutable_rep_truncated)
2574
#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum) \
2575
((!may_be_corrupt || (checksum) != NULL) \
2576
? svn_checksum_to_cstring_display((checksum), pool) \
2579
if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
2580
return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2581
" %" SVN_FILESIZE_T_FMT " %s",
2582
rep->revision, rep->offset, rep->size,
2584
DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
2586
return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2587
" %" SVN_FILESIZE_T_FMT " %s %s %s",
2588
rep->revision, rep->offset, rep->size,
2590
DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
2591
DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
2594
#undef DISPLAY_MAYBE_NULL_CHECKSUM
2600
svn_fs_fs__write_noderev(svn_stream_t *outfile,
2601
node_revision_t *noderev,
2603
svn_boolean_t include_mergeinfo,
2606
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
2607
svn_fs_fs__id_unparse(noderev->id,
2610
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
2611
(noderev->kind == svn_node_file) ?
2612
KIND_FILE : KIND_DIR));
2614
if (noderev->predecessor_id)
2615
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
2616
svn_fs_fs__id_unparse(noderev->predecessor_id,
2619
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
2620
noderev->predecessor_count));
2622
if (noderev->data_rep)
2623
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
2624
representation_string(noderev->data_rep,
2631
if (noderev->prop_rep)
2632
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
2633
representation_string(noderev->prop_rep, format,
2634
TRUE, FALSE, pool)));
2636
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
2637
noderev->created_path));
2639
if (noderev->copyfrom_path)
2640
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
2642
noderev->copyfrom_rev,
2643
noderev->copyfrom_path));
2645
if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
2646
(strcmp(noderev->copyroot_path, noderev->created_path) != 0))
2647
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
2649
noderev->copyroot_rev,
2650
noderev->copyroot_path));
2652
if (noderev->is_fresh_txn_root)
2653
SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
2655
if (include_mergeinfo)
2657
if (noderev->mergeinfo_count > 0)
2658
SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
2659
APR_INT64_T_FMT "\n",
2660
noderev->mergeinfo_count));
2662
if (noderev->has_mergeinfo)
2663
SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
2666
return svn_stream_puts(outfile, "\n");
2670
svn_fs_fs__put_node_revision(svn_fs_t *fs,
2671
const svn_fs_id_t *id,
2672
node_revision_t *noderev,
2673
svn_boolean_t fresh_txn_root,
2676
fs_fs_data_t *ffd = fs->fsap_data;
2677
apr_file_t *noderev_file;
2678
const char *txn_id = svn_fs_fs__id_txn_id(id);
2680
noderev->is_fresh_txn_root = fresh_txn_root;
2683
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2684
_("Attempted to write to non-transaction '%s'"),
2685
svn_fs_fs__id_unparse(id, pool)->data);
2687
SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
2688
APR_WRITE | APR_CREATE | APR_TRUNCATE
2689
| APR_BUFFERED, APR_OS_DEFAULT, pool));
2691
SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
2693
noderev, ffd->format,
2694
svn_fs_fs__fs_supports_mergeinfo(fs),
2697
SVN_ERR(svn_io_file_close(noderev_file, pool));
2699
return SVN_NO_ERROR;
2702
/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
2703
* file in the respective transaction, if rep sharing has been enabled etc.
2704
* Use POOL for temporary allocations.
2706
static svn_error_t *
2707
store_sha1_rep_mapping(svn_fs_t *fs,
2708
node_revision_t *noderev,
2711
fs_fs_data_t *ffd = fs->fsap_data;
2713
/* if rep sharing has been enabled and the noderev has a data rep and
2714
* its SHA-1 is known, store the rep struct under its SHA1. */
2715
if ( ffd->rep_sharing_allowed
2716
&& noderev->data_rep
2717
&& noderev->data_rep->sha1_checksum)
2719
apr_file_t *rep_file;
2720
const char *file_name = path_txn_sha1(fs,
2721
svn_fs_fs__id_txn_id(noderev->id),
2722
noderev->data_rep->sha1_checksum,
2724
const char *rep_string = representation_string(noderev->data_rep,
2730
SVN_ERR(svn_io_file_open(&rep_file, file_name,
2731
APR_WRITE | APR_CREATE | APR_TRUNCATE
2732
| APR_BUFFERED, APR_OS_DEFAULT, pool));
2734
SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
2735
strlen(rep_string), NULL, pool));
2737
SVN_ERR(svn_io_file_close(rep_file, pool));
2740
return SVN_NO_ERROR;
2744
/* This structure is used to hold the information associated with a
2748
svn_boolean_t is_delta;
2749
svn_boolean_t is_delta_vs_empty;
2751
svn_revnum_t base_revision;
2752
apr_off_t base_offset;
2753
svn_filesize_t base_length;
2756
/* Read the next line from file FILE and parse it as a text
2757
representation entry. Return the parsed entry in *REP_ARGS_P.
2758
Perform all allocations in POOL. */
2759
static svn_error_t *
2760
read_rep_line(struct rep_args **rep_args_p,
2766
struct rep_args *rep_args;
2767
char *str, *last_str = buffer;
2770
limit = sizeof(buffer);
2771
SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
2773
rep_args = apr_pcalloc(pool, sizeof(*rep_args));
2774
rep_args->is_delta = FALSE;
2776
if (strcmp(buffer, REP_PLAIN) == 0)
2778
*rep_args_p = rep_args;
2779
return SVN_NO_ERROR;
2782
if (strcmp(buffer, REP_DELTA) == 0)
2784
/* This is a delta against the empty stream. */
2785
rep_args->is_delta = TRUE;
2786
rep_args->is_delta_vs_empty = TRUE;
2787
*rep_args_p = rep_args;
2788
return SVN_NO_ERROR;
2791
rep_args->is_delta = TRUE;
2792
rep_args->is_delta_vs_empty = FALSE;
2794
/* We have hopefully a DELTA vs. a non-empty base revision. */
2795
str = svn_cstring_tokenize(" ", &last_str);
2796
if (! str || (strcmp(str, REP_DELTA) != 0))
2799
str = svn_cstring_tokenize(" ", &last_str);
2802
rep_args->base_revision = SVN_STR_TO_REV(str);
2804
str = svn_cstring_tokenize(" ", &last_str);
2807
SVN_ERR(svn_cstring_atoi64(&val, str));
2808
rep_args->base_offset = (apr_off_t)val;
2810
str = svn_cstring_tokenize(" ", &last_str);
2813
SVN_ERR(svn_cstring_atoi64(&val, str));
2814
rep_args->base_length = (svn_filesize_t)val;
2816
*rep_args_p = rep_args;
2817
return SVN_NO_ERROR;
2820
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2821
_("Malformed representation header at %s"),
2822
path_and_offset_of(file, pool));
2825
/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
2826
of the header located at OFFSET and store it in *ID_P. Allocate
2827
temporary variables from POOL. */
2828
static svn_error_t *
2829
get_fs_id_at_offset(svn_fs_id_t **id_p,
2830
apr_file_t *rev_file,
2837
apr_hash_t *headers;
2838
const char *node_id_str;
2840
SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2842
SVN_ERR(read_header_block(&headers,
2843
svn_stream_from_aprfile2(rev_file, TRUE, pool),
2846
/* In error messages, the offset is relative to the pack file,
2847
not to the rev file. */
2849
node_id_str = svn_hash_gets(headers, HEADER_ID);
2851
if (node_id_str == NULL)
2852
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2853
_("Missing node-id in node-rev at r%ld "
2856
apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2858
id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2861
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2862
_("Corrupt node-id '%s' in node-rev at r%ld "
2865
apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2869
/* ### assert that the txn_id is REV/OFFSET ? */
2871
return SVN_NO_ERROR;
2875
/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
2876
specifies the offset to the root node-id and to the changed path
2877
information. Store the root node offset in *ROOT_OFFSET and the
2878
changed path offset in *CHANGES_OFFSET. If either of these
2879
pointers is NULL, do nothing with it.
2881
If PACKED is true, REV_FILE should be a packed shard file.
2882
### There is currently no such parameter. This function assumes that
2883
is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
2884
file. Therefore FS->fsap_data->min_unpacked_rev must not have been
2885
refreshed since REV_FILE was opened if there is a possibility that
2886
revision REV may have become packed since then.
2887
TODO: Take an IS_PACKED parameter instead, in order to remove this
2890
Allocate temporary variables from POOL. */
2891
static svn_error_t *
2892
get_root_changes_offset(apr_off_t *root_offset,
2893
apr_off_t *changes_offset,
2894
apr_file_t *rev_file,
2899
fs_fs_data_t *ffd = fs->fsap_data;
2901
apr_off_t rev_offset;
2906
apr_seek_where_t seek_relative;
2908
/* Determine where to seek to in the file.
2910
If we've got a pack file, we want to seek to the end of the desired
2911
revision. But we don't track that, so we seek to the beginning of the
2914
Unless the next revision is in a different file, in which case, we can
2915
just seek to the end of the pack file -- just like we do in the
2917
if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
2919
SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
2920
seek_relative = APR_SET;
2924
seek_relative = APR_END;
2928
/* Offset of the revision from the start of the pack file, if applicable. */
2929
if (is_packed_rev(fs, rev))
2930
SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2934
/* We will assume that the last line containing the two offsets
2935
will never be longer than 64 characters. */
2936
SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
2938
offset -= sizeof(buf);
2939
SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2941
/* Read in this last block, from which we will identify the last line. */
2943
SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2945
/* This cast should be safe since the maximum amount read, 64, will
2946
never be bigger than the size of an int. */
2947
num_bytes = (int) len;
2949
/* The last byte should be a newline. */
2950
if (buf[num_bytes - 1] != '\n')
2952
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2953
_("Revision file (r%ld) lacks trailing newline"),
2957
/* Look for the next previous newline. */
2958
for (i = num_bytes - 2; i >= 0; i--)
2966
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2967
_("Final line in revision file (r%ld) longer "
2968
"than 64 characters"),
2975
/* find the next space */
2976
for ( ; i < (num_bytes - 2) ; i++)
2980
if (i == (num_bytes - 2))
2981
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2982
_("Final line in revision file r%ld missing space"),
2990
SVN_ERR(svn_cstring_atoi64(&val, str));
2991
*root_offset = rev_offset + (apr_off_t)val;
2997
/* find the next newline */
2998
for ( ; i < num_bytes; i++)
3007
SVN_ERR(svn_cstring_atoi64(&val, str));
3008
*changes_offset = rev_offset + (apr_off_t)val;
3011
return SVN_NO_ERROR;
3014
/* Move a file into place from OLD_FILENAME in the transactions
3015
directory to its final location NEW_FILENAME in the repository. On
3016
Unix, match the permissions of the new file to the permissions of
3017
PERMS_REFERENCE. Temporary allocations are from POOL.
3019
This function almost duplicates svn_io_file_move(), but it tries to
3020
guarantee a flush. */
3021
static svn_error_t *
3022
move_into_place(const char *old_filename,
3023
const char *new_filename,
3024
const char *perms_reference,
3029
SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
3031
/* Move the file into place. */
3032
err = svn_io_file_rename(old_filename, new_filename, pool);
3033
if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3037
/* Can't rename across devices; fall back to copying. */
3038
svn_error_clear(err);
3040
SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
3042
/* Flush the target of the copy to disk. */
3043
SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
3044
APR_OS_DEFAULT, pool));
3045
/* ### BH: Does this really guarantee a flush of the data written
3046
### via a completely different handle on all operating systems?
3048
### Maybe we should perform the copy ourselves instead of making
3049
### apr do that and flush the real handle? */
3050
SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3051
SVN_ERR(svn_io_file_close(file, pool));
3054
return svn_error_trace(err);
3058
/* Linux has the unusual feature that fsync() on a file is not
3059
enough to ensure that a file's directory entries have been
3060
flushed to disk; you have to fsync the directory as well.
3061
On other operating systems, we'd only be asking for trouble
3062
by trying to open and fsync a directory. */
3063
const char *dirname;
3066
dirname = svn_dirent_dirname(new_filename, pool);
3067
SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
3069
SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3070
SVN_ERR(svn_io_file_close(file, pool));
3074
return SVN_NO_ERROR;
3078
svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
3083
fs_fs_data_t *ffd = fs->fsap_data;
3084
apr_file_t *revision_file;
3085
apr_off_t root_offset;
3086
svn_fs_id_t *root_id = NULL;
3087
svn_boolean_t is_cached;
3089
SVN_ERR(ensure_revision_exists(fs, rev, pool));
3091
SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
3092
ffd->rev_root_id_cache, &rev, pool));
3094
return SVN_NO_ERROR;
3096
SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
3097
SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
3100
SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
3101
root_offset, pool));
3103
SVN_ERR(svn_io_file_close(revision_file, pool));
3105
SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
3107
*root_id_p = root_id;
3109
return SVN_NO_ERROR;
3112
/* Revprop caching management.
3117
* Revprop caching needs to be activated and will be deactivated for the
3118
* respective FS instance if the necessary infrastructure could not be
3119
* initialized. In deactivated mode, there is almost no runtime overhead
3120
* associated with revprop caching. As long as no revprops are being read
3121
* or changed, revprop caching imposes no overhead.
3123
* When activated, we cache revprops using (revision, generation) pairs
3124
* as keys with the generation being incremented upon every revprop change.
3125
* Since the cache is process-local, the generation needs to be tracked
3126
* for at least as long as the process lives but may be reset afterwards.
3128
* To track the revprop generation, we use two-layer approach. On the lower
3129
* level, we use named atomics to have a system-wide consistent value for
3130
* the current revprop generation. However, those named atomics will only
3131
* remain valid for as long as at least one process / thread in the system
3132
* accesses revprops in the respective repository. The underlying shared
3133
* memory gets cleaned up afterwards.
3135
* On the second level, we will use a persistent file to track the latest
3136
* revprop generation. It will be written upon each revprop change but
3137
* only be read if we are the first process to initialize the named atomics
3140
* The overhead for the second and following accesses to revprops is
3141
* almost zero on most systems.
3147
* A problem is that we need to provide a globally available file name to
3148
* back the SHM implementation on OSes that need it. We can only assume
3149
* write access to some file within the respective repositories. Because
3150
* a given server process may access thousands of repositories during its
3151
* lifetime, keeping the SHM data alive for all of them is also not an
3154
* So, we store the new revprop generation on disk as part of each
3155
* setrevprop call, i.e. this write will be serialized and the write order
3156
* be guaranteed by the repository write lock.
3158
* The only racy situation occurs when the data is being read again by two
3159
* processes concurrently but in that situation, the first process to
3160
* finish that procedure is guaranteed to be the only one that initializes
3161
* the SHM data. Since even writers will first go through that
3162
* initialization phase, they will never operate on stale data.
3165
/* Read revprop generation as stored on disk for repository FS. The result
3166
* is returned in *CURRENT. Default to 2 if no such file is available.
3168
static svn_error_t *
3169
read_revprop_generation_file(apr_int64_t *current,
3177
const char *path = path_revprop_generation(fs, pool);
3179
err = svn_io_file_open(&file, path,
3180
APR_READ | APR_BUFFERED,
3181
APR_OS_DEFAULT, pool);
3182
if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3184
svn_error_clear(err);
3187
return SVN_NO_ERROR;
3192
SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3194
/* Check that the first line contains only digits. */
3195
SVN_ERR(check_file_buffer_numeric(buf, 0, path,
3196
"Revprop Generation", pool));
3197
SVN_ERR(svn_cstring_atoi64(current, buf));
3199
return svn_io_file_close(file, pool);
3202
/* Write the CURRENT revprop generation to disk for repository FS.
3204
static svn_error_t *
3205
write_revprop_generation_file(svn_fs_t *fs,
3206
apr_int64_t current,
3210
const char *tmp_path;
3212
char buf[SVN_INT64_BUFFER_SIZE];
3213
apr_size_t len = svn__i64toa(buf, current);
3216
SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
3217
svn_io_file_del_none, pool, pool));
3218
SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
3219
SVN_ERR(svn_io_file_close(file, pool));
3221
return move_into_place(tmp_path, path_revprop_generation(fs, pool),
3225
/* Make sure the revprop_namespace member in FS is set. */
3226
static svn_error_t *
3227
ensure_revprop_namespace(svn_fs_t *fs)
3229
fs_fs_data_t *ffd = fs->fsap_data;
3231
return ffd->revprop_namespace == NULL
3232
? svn_atomic_namespace__create(&ffd->revprop_namespace,
3233
svn_dirent_join(fs->path,
3234
ATOMIC_REVPROP_NAMESPACE,
3240
/* Make sure the revprop_namespace member in FS is set. */
3241
static svn_error_t *
3242
cleanup_revprop_namespace(svn_fs_t *fs)
3244
const char *name = svn_dirent_join(fs->path,
3245
ATOMIC_REVPROP_NAMESPACE,
3247
return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
3250
/* Make sure the revprop_generation member in FS is set and, if necessary,
3251
* initialized with the latest value stored on disk.
3253
static svn_error_t *
3254
ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
3256
fs_fs_data_t *ffd = fs->fsap_data;
3258
SVN_ERR(ensure_revprop_namespace(fs));
3259
if (ffd->revprop_generation == NULL)
3261
apr_int64_t current = 0;
3263
SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
3264
ffd->revprop_namespace,
3265
ATOMIC_REVPROP_GENERATION,
3268
/* If the generation is at 0, we just created a new namespace
3269
* (it would be at least 2 otherwise). Read the latest generation
3270
* from disk and if we are the first one to initialize the atomic
3271
* (i.e. is still 0), set it to the value just gotten.
3273
SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation));
3276
SVN_ERR(read_revprop_generation_file(¤t, fs, pool));
3277
SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
3278
ffd->revprop_generation));
3282
return SVN_NO_ERROR;
3285
/* Make sure the revprop_timeout member in FS is set. */
3286
static svn_error_t *
3287
ensure_revprop_timeout(svn_fs_t *fs)
3289
fs_fs_data_t *ffd = fs->fsap_data;
3291
SVN_ERR(ensure_revprop_namespace(fs));
3292
return ffd->revprop_timeout == NULL
3293
? svn_named_atomic__get(&ffd->revprop_timeout,
3294
ffd->revprop_namespace,
3295
ATOMIC_REVPROP_TIMEOUT,
3300
/* Create an error object with the given MESSAGE and pass it to the
3301
WARNING member of FS. */
3303
log_revprop_cache_init_warning(svn_fs_t *fs,
3304
svn_error_t *underlying_err,
3305
const char *message)
3307
svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
3312
(fs->warning)(fs->warning_baton, err);
3314
svn_error_clear(err);
3317
/* Test whether revprop cache and necessary infrastructure are
3319
static svn_boolean_t
3320
has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
3322
fs_fs_data_t *ffd = fs->fsap_data;
3325
/* is the cache (still) enabled? */
3326
if (ffd->revprop_cache == NULL)
3329
/* is it efficient? */
3330
if (!svn_named_atomic__is_efficient())
3332
/* access to it would be quite slow
3333
* -> disable the revprop cache for good
3335
ffd->revprop_cache = NULL;
3336
log_revprop_cache_init_warning(fs, NULL,
3337
"Revprop caching for '%s' disabled"
3338
" because it would be inefficient.");
3343
/* try to access our SHM-backed infrastructure */
3344
error = ensure_revprop_generation(fs, pool);
3347
/* failure -> disable revprop cache for good */
3349
ffd->revprop_cache = NULL;
3350
log_revprop_cache_init_warning(fs, error,
3351
"Revprop caching for '%s' disabled "
3352
"because SHM infrastructure for revprop "
3353
"caching failed to initialize.");
3361
/* Baton structure for revprop_generation_fixup. */
3362
typedef struct revprop_generation_fixup_t
3364
/* revprop generation to read */
3365
apr_int64_t *generation;
3367
/* containing the revprop_generation member to query */
3369
} revprop_generation_upgrade_t;
3371
/* If the revprop generation has an odd value, it means the original writer
3372
of the revprop got killed. We don't know whether that process as able
3373
to change the revprop data but we assume that it was. Therefore, we
3374
increase the generation in that case to basically invalidate everyones
3376
Execute this onlx while holding the write lock to the repo in baton->FFD.
3378
static svn_error_t *
3379
revprop_generation_fixup(void *void_baton,
3382
revprop_generation_upgrade_t *baton = void_baton;
3383
assert(baton->ffd->has_write_lock);
3385
/* Maybe, either the original revprop writer or some other reader has
3386
already corrected / bumped the revprop generation. Thus, we need
3387
to read it again. */
3388
SVN_ERR(svn_named_atomic__read(baton->generation,
3389
baton->ffd->revprop_generation));
3391
/* Cause everyone to re-read revprops upon their next access, if the
3392
last revprop write did not complete properly. */
3393
while (*baton->generation % 2)
3394
SVN_ERR(svn_named_atomic__add(baton->generation,
3396
baton->ffd->revprop_generation));
3398
return SVN_NO_ERROR;
3401
/* Read the current revprop generation and return it in *GENERATION.
3402
Also, detect aborted / crashed writers and recover from that.
3403
Use the access object in FS to set the shared mem values. */
3404
static svn_error_t *
3405
read_revprop_generation(apr_int64_t *generation,
3409
apr_int64_t current = 0;
3410
fs_fs_data_t *ffd = fs->fsap_data;
3412
/* read the current revprop generation number */
3413
SVN_ERR(ensure_revprop_generation(fs, pool));
3414
SVN_ERR(svn_named_atomic__read(¤t, ffd->revprop_generation));
3416
/* is an unfinished revprop write under the way? */
3419
apr_int64_t timeout = 0;
3421
/* read timeout for the write operation */
3422
SVN_ERR(ensure_revprop_timeout(fs));
3423
SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
3425
/* has the writer process been aborted,
3426
* i.e. has the timeout been reached?
3428
if (apr_time_now() > timeout)
3430
revprop_generation_upgrade_t baton;
3431
baton.generation = ¤t;
3434
/* Ensure that the original writer process no longer exists by
3435
* acquiring the write lock to this repository. Then, fix up
3436
* the revprop generation.
3438
if (ffd->has_write_lock)
3439
SVN_ERR(revprop_generation_fixup(&baton, pool));
3441
SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
3446
/* return the value we just got */
3447
*generation = current;
3448
return SVN_NO_ERROR;
3451
/* Set the revprop generation to the next odd number to indicate that
3452
there is a revprop write process under way. If that times out,
3453
readers shall recover from that state & re-read revprops.
3454
Use the access object in FS to set the shared mem value. */
3455
static svn_error_t *
3456
begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3458
apr_int64_t current;
3459
fs_fs_data_t *ffd = fs->fsap_data;
3461
/* set the timeout for the write operation */
3462
SVN_ERR(ensure_revprop_timeout(fs));
3463
SVN_ERR(svn_named_atomic__write(NULL,
3464
apr_time_now() + REVPROP_CHANGE_TIMEOUT,
3465
ffd->revprop_timeout));
3467
/* set the revprop generation to an odd value to indicate
3468
* that a write is in progress
3470
SVN_ERR(ensure_revprop_generation(fs, pool));
3473
SVN_ERR(svn_named_atomic__add(¤t,
3475
ffd->revprop_generation));
3477
while (current % 2 == 0);
3479
return SVN_NO_ERROR;
3482
/* Set the revprop generation to the next even number to indicate that
3483
a) readers shall re-read revprops, and
3484
b) the write process has been completed (no recovery required)
3485
Use the access object in FS to set the shared mem value. */
3486
static svn_error_t *
3487
end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3489
apr_int64_t current = 1;
3490
fs_fs_data_t *ffd = fs->fsap_data;
3492
/* set the revprop generation to an even value to indicate
3493
* that a write has been completed
3495
SVN_ERR(ensure_revprop_generation(fs, pool));
3498
SVN_ERR(svn_named_atomic__add(¤t,
3500
ffd->revprop_generation));
3502
while (current % 2);
3504
/* Save the latest generation to disk. FS is currently in a "locked"
3505
* state such that we can be sure the be the only ones to write that
3508
return write_revprop_generation_file(fs, current, pool);
3511
/* Container for all data required to access the packed revprop file
3512
* for a given REVISION. This structure will be filled incrementally
3513
* by read_pack_revprops() its sub-routines.
3515
typedef struct packed_revprops_t
3517
/* revision number to read (not necessarily the first in the pack) */
3518
svn_revnum_t revision;
3520
/* current revprop generation. Used when populating the revprop cache */
3521
apr_int64_t generation;
3523
/* the actual revision properties */
3524
apr_hash_t *properties;
3526
/* their size when serialized to a single string
3527
* (as found in PACKED_REVPROPS) */
3528
apr_size_t serialized_size;
3531
/* name of the pack file (without folder path) */
3532
const char *filename;
3534
/* packed shard folder path */
3537
/* sum of values in SIZES */
3538
apr_size_t total_size;
3540
/* first revision in the pack (>= MANIFEST_START) */
3541
svn_revnum_t start_revision;
3543
/* size of the revprops in PACKED_REVPROPS */
3544
apr_array_header_t *sizes;
3546
/* offset of the revprops in PACKED_REVPROPS */
3547
apr_array_header_t *offsets;
3550
/* concatenation of the serialized representation of all revprops
3551
* in the pack, i.e. the pack content without header and compression */
3552
svn_stringbuf_t *packed_revprops;
3554
/* First revision covered by MANIFEST.
3555
* Will equal the shard start revision or 1, for the 1st shard. */
3556
svn_revnum_t manifest_start;
3558
/* content of the manifest.
3559
* Maps long(rev - MANIFEST_START) to const char* pack file name */
3560
apr_array_header_t *manifest;
3561
} packed_revprops_t;
3563
/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
3564
* Also, put them into the revprop cache, if activated, for future use.
3565
* Three more parameters are being used to update the revprop cache: FS is
3566
* our file system, the revprops belong to REVISION and the global revprop
3567
* GENERATION is used as well.
3569
* The returned hash will be allocated in POOL, SCRATCH_POOL is being used
3570
* for temporary allocations.
3572
static svn_error_t *
3573
parse_revprop(apr_hash_t **properties,
3575
svn_revnum_t revision,
3576
apr_int64_t generation,
3577
svn_string_t *content,
3579
apr_pool_t *scratch_pool)
3581
svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
3582
*properties = apr_hash_make(pool);
3584
SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
3585
if (has_revprop_cache(fs, pool))
3587
fs_fs_data_t *ffd = fs->fsap_data;
3588
pair_cache_key_t key = { 0 };
3590
key.revision = revision;
3591
key.second = generation;
3592
SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
3596
return SVN_NO_ERROR;
3599
/* Read the non-packed revprops for revision REV in FS, put them into the
3600
* revprop cache if activated and return them in *PROPERTIES. GENERATION
3601
* is the current revprop generation.
3603
* If the data could not be read due to an otherwise recoverable error,
3604
* leave *PROPERTIES unchanged. No error will be returned in that case.
3606
* Allocations will be done in POOL.
3608
static svn_error_t *
3609
read_non_packed_revprop(apr_hash_t **properties,
3612
apr_int64_t generation,
3615
svn_stringbuf_t *content = NULL;
3616
apr_pool_t *iterpool = svn_pool_create(pool);
3617
svn_boolean_t missing = FALSE;
3620
for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
3622
svn_pool_clear(iterpool);
3623
SVN_ERR(try_stringbuf_from_file(&content,
3625
path_revprops(fs, rev, iterpool),
3626
i + 1 < RECOVERABLE_RETRY_COUNT,
3631
SVN_ERR(parse_revprop(properties, fs, rev, generation,
3632
svn_stringbuf__morph_into_string(content),
3635
svn_pool_clear(iterpool);
3637
return SVN_NO_ERROR;
3640
/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
3641
* members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
3643
static svn_error_t *
3644
get_revprop_packname(svn_fs_t *fs,
3645
packed_revprops_t *revprops,
3647
apr_pool_t *scratch_pool)
3649
fs_fs_data_t *ffd = fs->fsap_data;
3650
svn_stringbuf_t *content = NULL;
3651
const char *manifest_file_path;
3654
/* read content of the manifest file */
3655
revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
3656
manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
3658
SVN_ERR(read_content(&content, manifest_file_path, pool));
3660
/* parse the manifest. Every line is a file name */
3661
revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
3662
sizeof(const char*));
3664
/* Read all lines. Since the last line ends with a newline, we will
3665
end up with a valid but empty string after the last entry. */
3666
while (content->data && *content->data)
3668
APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
3669
content->data = strchr(content->data, '\n');
3677
/* Index for our revision. Rev 0 is excluded from the first shard. */
3678
revprops->manifest_start = revprops->revision
3679
- (revprops->revision % ffd->max_files_per_dir);
3680
if (revprops->manifest_start == 0)
3681
++revprops->manifest_start;
3682
idx = (int)(revprops->revision - revprops->manifest_start);
3684
if (revprops->manifest->nelts <= idx)
3685
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3686
_("Packed revprop manifest for r%ld too "
3687
"small"), revprops->revision);
3689
/* Now get the file name */
3690
revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
3692
return SVN_NO_ERROR;
3695
/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
3697
static svn_boolean_t
3698
same_shard(svn_fs_t *fs,
3702
fs_fs_data_t *ffd = fs->fsap_data;
3703
return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
3706
/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
3707
* fill the START_REVISION, SIZES, OFFSETS members. Also, make
3708
* PACKED_REVPROPS point to the first serialized revprop.
3710
* Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
3711
* well as the SERIALIZED_SIZE member. If revprop caching has been
3712
* enabled, parse all revprops in the pack and cache them.
3714
static svn_error_t *
3715
parse_packed_revprops(svn_fs_t *fs,
3716
packed_revprops_t *revprops,
3718
apr_pool_t *scratch_pool)
3720
svn_stream_t *stream;
3721
apr_int64_t first_rev, count, i;
3723
const char *header_end;
3724
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3726
/* decompress (even if the data is only "stored", there is still a
3727
* length header to remove) */
3728
svn_string_t *compressed
3729
= svn_stringbuf__morph_into_string(revprops->packed_revprops);
3730
svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
3731
SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
3733
/* read first revision number and number of revisions in the pack */
3734
stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
3735
SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
3736
SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
3738
/* Check revision range for validity. */
3739
if ( !same_shard(fs, revprops->revision, first_rev)
3740
|| !same_shard(fs, revprops->revision, first_rev + count - 1)
3742
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3743
_("Revprop pack for revision r%ld"
3744
" contains revprops for r%ld .. r%ld"),
3746
(svn_revnum_t)first_rev,
3747
(svn_revnum_t)(first_rev + count -1));
3749
/* Since start & end are in the same shard, it is enough to just test
3750
* the FIRST_REV for being actually packed. That will also cover the
3751
* special case of rev 0 never being packed. */
3752
if (!is_packed_revprop(fs, first_rev))
3753
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3754
_("Revprop pack for revision r%ld"
3755
" starts at non-packed revisions r%ld"),
3756
revprops->revision, (svn_revnum_t)first_rev);
3758
/* make PACKED_REVPROPS point to the first char after the header.
3759
* This is where the serialized revprops are. */
3760
header_end = strstr(uncompressed->data, "\n\n");
3761
if (header_end == NULL)
3762
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3763
_("Header end not found"));
3765
offset = header_end - uncompressed->data + 2;
3767
revprops->packed_revprops = svn_stringbuf_create_empty(pool);
3768
revprops->packed_revprops->data = uncompressed->data + offset;
3769
revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
3770
revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
3772
/* STREAM still points to the first entry in the sizes list.
3773
* Init / construct REVPROPS members. */
3774
revprops->start_revision = (svn_revnum_t)first_rev;
3775
revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
3776
revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
3778
/* Now parse, revision by revision, the size and content of each
3779
* revisions' revprops. */
3780
for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
3783
svn_string_t serialized;
3784
apr_hash_t *properties;
3785
svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
3787
/* read & check the serialized size */
3788
SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
3789
if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
3790
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3791
_("Packed revprop size exceeds pack file size"));
3793
/* Parse this revprops list, if necessary */
3794
serialized.data = revprops->packed_revprops->data + offset;
3795
serialized.len = (apr_size_t)size;
3797
if (revision == revprops->revision)
3799
SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
3800
revprops->generation, &serialized,
3802
revprops->serialized_size = serialized.len;
3806
/* If revprop caching is enabled, parse any revprops.
3807
* They will get cached as a side-effect of this. */
3808
if (has_revprop_cache(fs, pool))
3809
SVN_ERR(parse_revprop(&properties, fs, revision,
3810
revprops->generation, &serialized,
3811
iterpool, iterpool));
3814
/* fill REVPROPS data structures */
3815
APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
3816
APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
3817
revprops->total_size += serialized.len;
3819
offset += serialized.len;
3821
svn_pool_clear(iterpool);
3824
return SVN_NO_ERROR;
3827
/* In filesystem FS, read the packed revprops for revision REV into
3828
* *REVPROPS. Use GENERATION to populate the revprop cache, if enabled.
3829
* Allocate data in POOL.
3831
static svn_error_t *
3832
read_pack_revprop(packed_revprops_t **revprops,
3835
apr_int64_t generation,
3838
apr_pool_t *iterpool = svn_pool_create(pool);
3839
svn_boolean_t missing = FALSE;
3841
packed_revprops_t *result;
3844
/* someone insisted that REV is packed. Double-check if necessary */
3845
if (!is_packed_revprop(fs, rev))
3846
SVN_ERR(update_min_unpacked_rev(fs, iterpool));
3848
if (!is_packed_revprop(fs, rev))
3849
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3850
_("No such packed revision %ld"), rev);
3852
/* initialize the result data structure */
3853
result = apr_pcalloc(pool, sizeof(*result));
3854
result->revision = rev;
3855
result->generation = generation;
3857
/* try to read the packed revprops. This may require retries if we have
3858
* concurrent writers. */
3859
for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
3861
const char *file_path;
3863
/* there might have been concurrent writes.
3864
* Re-read the manifest and the pack file.
3866
SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
3867
file_path = svn_dirent_join(result->folder,
3870
SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
3873
i + 1 < RECOVERABLE_RETRY_COUNT,
3876
/* If we could not find the file, there was a write.
3877
* So, we should refresh our revprop generation info as well such
3878
* that others may find data we will put into the cache. They would
3879
* consider it outdated, otherwise.
3881
if (missing && has_revprop_cache(fs, pool))
3882
SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
3884
svn_pool_clear(iterpool);
3887
/* the file content should be available now */
3888
if (!result->packed_revprops)
3889
return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
3890
_("Failed to read revprop pack file for r%ld"), rev);
3892
/* parse it. RESULT will be complete afterwards. */
3893
err = parse_packed_revprops(fs, result, pool, iterpool);
3894
svn_pool_destroy(iterpool);
3896
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
3897
_("Revprop pack file for r%ld is corrupt"), rev);
3901
return SVN_NO_ERROR;
3904
/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
3906
* Allocations will be done in POOL.
3908
static svn_error_t *
3909
get_revision_proplist(apr_hash_t **proplist_p,
3914
fs_fs_data_t *ffd = fs->fsap_data;
3915
apr_int64_t generation = 0;
3917
/* not found, yet */
3920
/* should they be available at all? */
3921
SVN_ERR(ensure_revision_exists(fs, rev, pool));
3923
/* Try cache lookup first. */
3924
if (has_revprop_cache(fs, pool))
3926
svn_boolean_t is_cached;
3927
pair_cache_key_t key = { 0 };
3929
SVN_ERR(read_revprop_generation(&generation, fs, pool));
3932
key.second = generation;
3933
SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
3934
ffd->revprop_cache, &key, pool));
3936
return SVN_NO_ERROR;
3939
/* if REV had not been packed when we began, try reading it from the
3940
* non-packed shard. If that fails, we will fall through to packed
3942
if (!is_packed_revprop(fs, rev))
3944
svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
3948
if (!APR_STATUS_IS_ENOENT(err->apr_err)
3949
|| ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
3950
return svn_error_trace(err);
3952
svn_error_clear(err);
3953
*proplist_p = NULL; /* in case read_non_packed_revprop changed it */
3957
/* if revprop packing is available and we have not read the revprops, yet,
3958
* try reading them from a packed shard. If that fails, REV is most
3959
* likely invalid (or its revprops highly contested). */
3960
if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
3962
packed_revprops_t *packed_revprops;
3963
SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
3964
*proplist_p = packed_revprops->properties;
3967
/* The revprops should have been there. Did we get them? */
3969
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3970
_("Could not read revprops for revision %ld"),
3973
return SVN_NO_ERROR;
3976
/* Serialize the revision property list PROPLIST of revision REV in
3977
* filesystem FS to a non-packed file. Return the name of that temporary
3978
* file in *TMP_PATH and the file path that it must be moved to in
3981
* Use POOL for allocations.
3983
static svn_error_t *
3984
write_non_packed_revprop(const char **final_path,
3985
const char **tmp_path,
3988
apr_hash_t *proplist,
3991
svn_stream_t *stream;
3992
*final_path = path_revprops(fs, rev, pool);
3994
/* ### do we have a directory sitting around already? we really shouldn't
3995
### have to get the dirname here. */
3996
SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
3997
svn_dirent_dirname(*final_path, pool),
3998
svn_io_file_del_none, pool, pool));
3999
SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4000
SVN_ERR(svn_stream_close(stream));
4002
return SVN_NO_ERROR;
4005
/* After writing the new revprop file(s), call this function to move the
4006
* file at TMP_PATH to FINAL_PATH and give it the permissions from
4009
* If indicated in BUMP_GENERATION, increase FS' revprop generation.
4010
* Finally, delete all the temporary files given in FILES_TO_DELETE.
4011
* The latter may be NULL.
4013
* Use POOL for temporary allocations.
4015
static svn_error_t *
4016
switch_to_new_revprop(svn_fs_t *fs,
4017
const char *final_path,
4018
const char *tmp_path,
4019
const char *perms_reference,
4020
apr_array_header_t *files_to_delete,
4021
svn_boolean_t bump_generation,
4024
/* Now, we may actually be replacing revprops. Make sure that all other
4025
threads and processes will know about this. */
4026
if (bump_generation)
4027
SVN_ERR(begin_revprop_change(fs, pool));
4029
SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4031
/* Indicate that the update (if relevant) has been completed. */
4032
if (bump_generation)
4033
SVN_ERR(end_revprop_change(fs, pool));
4035
/* Clean up temporary files, if necessary. */
4036
if (files_to_delete)
4038
apr_pool_t *iterpool = svn_pool_create(pool);
4041
for (i = 0; i < files_to_delete->nelts; ++i)
4043
const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4044
SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4045
svn_pool_clear(iterpool);
4048
svn_pool_destroy(iterpool);
4050
return SVN_NO_ERROR;
4053
/* Write a pack file header to STREAM that starts at revision START_REVISION
4054
* and contains the indexes [START,END) of SIZES.
4056
static svn_error_t *
4057
serialize_revprops_header(svn_stream_t *stream,
4058
svn_revnum_t start_revision,
4059
apr_array_header_t *sizes,
4064
apr_pool_t *iterpool = svn_pool_create(pool);
4067
SVN_ERR_ASSERT(start < end);
4069
/* start revision and entry count */
4070
SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4071
SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4073
/* the sizes array */
4074
for (i = start; i < end; ++i)
4076
apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4077
SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4081
/* the double newline char indicates the end of the header */
4082
SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4084
svn_pool_clear(iterpool);
4085
return SVN_NO_ERROR;
4088
/* Writes the a pack file to FILE_STREAM. It copies the serialized data
4089
* from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4091
* The data for the latter is taken from NEW_SERIALIZED. Note, that
4092
* CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4093
* taken in that case but only a subset of the old data will be copied.
4095
* NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4096
* POOL is used for temporary allocations.
4098
static svn_error_t *
4099
repack_revprops(svn_fs_t *fs,
4100
packed_revprops_t *revprops,
4104
svn_stringbuf_t *new_serialized,
4105
apr_off_t new_total_size,
4106
svn_stream_t *file_stream,
4109
fs_fs_data_t *ffd = fs->fsap_data;
4110
svn_stream_t *stream;
4113
/* create data empty buffers and the stream object */
4114
svn_stringbuf_t *uncompressed
4115
= svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4116
svn_stringbuf_t *compressed
4117
= svn_stringbuf_create_empty(pool);
4118
stream = svn_stream_from_stringbuf(uncompressed, pool);
4120
/* write the header*/
4121
SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4122
revprops->sizes, start, end, pool));
4124
/* append the serialized revprops */
4125
for (i = start; i < end; ++i)
4126
if (i == changed_index)
4128
SVN_ERR(svn_stream_write(stream,
4129
new_serialized->data,
4130
&new_serialized->len));
4135
= (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4137
= (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4139
SVN_ERR(svn_stream_write(stream,
4140
revprops->packed_revprops->data + offset,
4144
/* flush the stream buffer (if any) to our underlying data buffer */
4145
SVN_ERR(svn_stream_close(stream));
4147
/* compress / store the data */
4148
SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4150
ffd->compress_packed_revprops
4151
? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4152
: SVN_DELTA_COMPRESSION_LEVEL_NONE));
4154
/* finally, write the content to the target stream and close it */
4155
SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
4156
SVN_ERR(svn_stream_close(file_stream));
4158
return SVN_NO_ERROR;
4161
/* Allocate a new pack file name for revisions
4162
* [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4163
* of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE,
4164
* auto-create that array if necessary. Return an open file stream to
4165
* the new file in *STREAM allocated in POOL.
4167
static svn_error_t *
4168
repack_stream_open(svn_stream_t **stream,
4170
packed_revprops_t *revprops,
4173
apr_array_header_t **files_to_delete,
4177
const char *tag_string;
4178
svn_string_t *new_filename;
4182
= (int)(revprops->start_revision - revprops->manifest_start);
4184
/* get the old (= current) file name and enlist it for later deletion */
4185
const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4186
start + manifest_offset,
4189
if (*files_to_delete == NULL)
4190
*files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4192
APR_ARRAY_PUSH(*files_to_delete, const char*)
4193
= svn_dirent_join(revprops->folder, old_filename, pool);
4195
/* increase the tag part, i.e. the counter after the dot */
4196
tag_string = strchr(old_filename, '.');
4197
if (tag_string == NULL)
4198
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4199
_("Packed file '%s' misses a tag"),
4202
SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4203
new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4204
revprops->start_revision + start,
4207
/* update the manifest to point to the new file */
4208
for (i = start; i < end; ++i)
4209
APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4210
= new_filename->data;
4212
/* create a file stream for the new file */
4213
SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
4216
APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4217
*stream = svn_stream_from_aprfile2(file, FALSE, pool);
4219
return SVN_NO_ERROR;
4222
/* For revision REV in filesystem FS, set the revision properties to
4223
* PROPLIST. Return a new file in *TMP_PATH that the caller shall move
4224
* to *FINAL_PATH to make the change visible. Files to be deleted will
4225
* be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4226
* Use POOL for allocations.
4228
static svn_error_t *
4229
write_packed_revprop(const char **final_path,
4230
const char **tmp_path,
4231
apr_array_header_t **files_to_delete,
4234
apr_hash_t *proplist,
4237
fs_fs_data_t *ffd = fs->fsap_data;
4238
packed_revprops_t *revprops;
4239
apr_int64_t generation = 0;
4240
svn_stream_t *stream;
4241
svn_stringbuf_t *serialized;
4242
apr_off_t new_total_size;
4245
/* read the current revprop generation. This value will not change
4246
* while we hold the global write lock to this FS. */
4247
if (has_revprop_cache(fs, pool))
4248
SVN_ERR(read_revprop_generation(&generation, fs, pool));
4250
/* read contents of the current pack file */
4251
SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4253
/* serialize the new revprops */
4254
serialized = svn_stringbuf_create_empty(pool);
4255
stream = svn_stream_from_stringbuf(serialized, pool);
4256
SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4257
SVN_ERR(svn_stream_close(stream));
4259
/* calculate the size of the new data */
4260
changed_index = (int)(rev - revprops->start_revision);
4261
new_total_size = revprops->total_size - revprops->serialized_size
4263
+ (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4265
APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4267
/* can we put the new data into the same pack as the before? */
4268
if ( new_total_size < ffd->revprop_pack_size
4269
|| revprops->sizes->nelts == 1)
4271
/* simply replace the old pack file with new content as we do it
4272
* in the non-packed case */
4274
*final_path = svn_dirent_join(revprops->folder, revprops->filename,
4276
SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4277
svn_io_file_del_none, pool, pool));
4278
SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4279
changed_index, serialized, new_total_size,
4284
/* split the pack file into two of roughly equal size */
4285
int right_count, left_count, i;
4288
int right = revprops->sizes->nelts - 1;
4289
apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4290
apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4292
/* let left and right side grow such that their size difference
4293
* is minimal after each step. */
4294
while (left <= right)
4295
if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4296
< right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4298
left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4299
+ SVN_INT64_BUFFER_SIZE;
4304
right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4305
+ SVN_INT64_BUFFER_SIZE;
4309
/* since the items need much less than SVN_INT64_BUFFER_SIZE
4310
* bytes to represent their length, the split may not be optimal */
4312
right_count = revprops->sizes->nelts - left;
4314
/* if new_size is large, one side may exceed the pack size limit.
4315
* In that case, split before and after the modified revprop.*/
4316
if ( left_size > ffd->revprop_pack_size
4317
|| right_size > ffd->revprop_pack_size)
4319
left_count = changed_index;
4320
right_count = revprops->sizes->nelts - left_count - 1;
4323
/* write the new, split files */
4326
SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
4327
left_count, files_to_delete, pool));
4328
SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4329
changed_index, serialized, new_total_size,
4333
if (left_count + right_count < revprops->sizes->nelts)
4335
SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
4336
changed_index + 1, files_to_delete,
4338
SVN_ERR(repack_revprops(fs, revprops, changed_index,
4340
changed_index, serialized, new_total_size,
4346
SVN_ERR(repack_stream_open(&stream, fs, revprops,
4347
revprops->sizes->nelts - right_count,
4348
revprops->sizes->nelts,
4349
files_to_delete, pool));
4350
SVN_ERR(repack_revprops(fs, revprops,
4351
revprops->sizes->nelts - right_count,
4352
revprops->sizes->nelts, changed_index,
4353
serialized, new_total_size, stream,
4357
/* write the new manifest */
4358
*final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4359
SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4360
svn_io_file_del_none, pool, pool));
4362
for (i = 0; i < revprops->manifest->nelts; ++i)
4364
const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4366
SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
4369
SVN_ERR(svn_stream_close(stream));
4372
return SVN_NO_ERROR;
4375
/* Set the revision property list of revision REV in filesystem FS to
4376
PROPLIST. Use POOL for temporary allocations. */
4377
static svn_error_t *
4378
set_revision_proplist(svn_fs_t *fs,
4380
apr_hash_t *proplist,
4383
svn_boolean_t is_packed;
4384
svn_boolean_t bump_generation = FALSE;
4385
const char *final_path;
4386
const char *tmp_path;
4387
const char *perms_reference;
4388
apr_array_header_t *files_to_delete = NULL;
4390
SVN_ERR(ensure_revision_exists(fs, rev, pool));
4392
/* this info will not change while we hold the global FS write lock */
4393
is_packed = is_packed_revprop(fs, rev);
4395
/* Test whether revprops already exist for this revision.
4396
* Only then will we need to bump the revprop generation. */
4397
if (has_revprop_cache(fs, pool))
4401
bump_generation = TRUE;
4405
svn_node_kind_t kind;
4406
SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4408
bump_generation = kind != svn_node_none;
4412
/* Serialize the new revprop data */
4414
SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4415
fs, rev, proplist, pool));
4417
SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4418
fs, rev, proplist, pool));
4420
/* We use the rev file of this revision as the perms reference,
4421
* because when setting revprops for the first time, the revprop
4422
* file won't exist and therefore can't serve as its own reference.
4423
* (Whereas the rev file should already exist at this point.)
4425
SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4427
/* Now, switch to the new revprop data. */
4428
SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4429
files_to_delete, bump_generation, pool));
4431
return SVN_NO_ERROR;
4435
svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4440
SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4442
return SVN_NO_ERROR;
4445
/* Represents where in the current svndiff data block each
4446
representation is. */
4450
/* The txdelta window cache to use or NULL. */
4451
svn_cache__t *window_cache;
4452
/* Caches un-deltified windows. May be NULL. */
4453
svn_cache__t *combined_cache;
4454
apr_off_t start; /* The starting offset for the raw
4455
svndiff/plaintext data minus header. */
4456
apr_off_t off; /* The current offset into the file. */
4457
apr_off_t end; /* The end offset of the raw data. */
4458
int ver; /* If a delta, what svndiff version? */
4462
/* See create_rep_state, which wraps this and adds another error. */
4463
static svn_error_t *
4464
create_rep_state_body(struct rep_state **rep_state,
4465
struct rep_args **rep_args,
4466
apr_file_t **file_hint,
4467
svn_revnum_t *rev_hint,
4468
representation_t *rep,
4472
fs_fs_data_t *ffd = fs->fsap_data;
4473
struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4474
struct rep_args *ra;
4475
unsigned char buf[4];
4479
* - refers to a valid revision,
4480
* - refers to a packed revision,
4481
* - as does the rep we want to read, and
4482
* - refers to the same pack file as the rep
4485
if ( file_hint && rev_hint && *file_hint
4486
&& SVN_IS_VALID_REVNUM(*rev_hint)
4487
&& *rev_hint < ffd->min_unpacked_rev
4488
&& rep->revision < ffd->min_unpacked_rev
4489
&& ( (*rev_hint / ffd->max_files_per_dir)
4490
== (rep->revision / ffd->max_files_per_dir)))
4492
/* ... we can re-use the same, already open file object
4495
SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4497
offset += rep->offset;
4498
SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4500
rs->file = *file_hint;
4504
/* otherwise, create a new file object
4506
SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4509
/* remember the current file, if suggested by the caller */
4511
*file_hint = rs->file;
4513
*rev_hint = rep->revision;
4515
/* continue constructing RS and RA */
4516
rs->window_cache = ffd->txdelta_window_cache;
4517
rs->combined_cache = ffd->combined_window_cache;
4519
SVN_ERR(read_rep_line(&ra, rs->file, pool));
4520
SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4521
rs->off = rs->start;
4522
rs->end = rs->start + rep->size;
4527
/* This is a plaintext, so just return the current rep_state. */
4528
return SVN_NO_ERROR;
4530
/* We are dealing with a delta, find out what version. */
4531
SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4533
/* ### Layering violation */
4534
if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4535
return svn_error_create
4536
(SVN_ERR_FS_CORRUPT, NULL,
4537
_("Malformed svndiff data in representation"));
4539
rs->chunk_index = 0;
4542
return SVN_NO_ERROR;
4545
/* Read the rep args for REP in filesystem FS and create a rep_state
4546
for reading the representation. Return the rep_state in *REP_STATE
4547
and the rep args in *REP_ARGS, both allocated in POOL.
4549
When reading multiple reps, i.e. a skip delta chain, you may provide
4550
non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first
4551
call it should be a pointer to NULL.) The function will use these variables
4552
to store the previous call results and tries to re-use them. This may
4553
result in significant savings in I/O for packed files.
4555
static svn_error_t *
4556
create_rep_state(struct rep_state **rep_state,
4557
struct rep_args **rep_args,
4558
apr_file_t **file_hint,
4559
svn_revnum_t *rev_hint,
4560
representation_t *rep,
4564
svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4565
file_hint, rev_hint,
4567
if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4569
fs_fs_data_t *ffd = fs->fsap_data;
4571
/* ### This always returns "-1" for transaction reps, because
4572
### this particular bit of code doesn't know if the rep is
4573
### stored in the protorev or in the mutable area (for props
4574
### or dir contents). It is pretty rare for FSFS to *read*
4575
### from the protorev file, though, so this is probably OK.
4576
### And anyone going to debug corruption errors is probably
4577
### going to jump straight to this comment anyway! */
4578
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4579
"Corrupt representation '%s'",
4581
? representation_string(rep, ffd->format, TRUE,
4585
/* ### Call representation_string() ? */
4586
return svn_error_trace(err);
4589
struct rep_read_baton
4591
/* The FS from which we're reading. */
4594
/* If not NULL, this is the base for the first delta window in rs_list */
4595
svn_stringbuf_t *base_window;
4597
/* The state of all prior delta representations. */
4598
apr_array_header_t *rs_list;
4600
/* The plaintext state, if there is a plaintext. */
4601
struct rep_state *src_state;
4603
/* The index of the current delta chunk, if we are reading a delta. */
4606
/* The buffer where we store undeltified data. */
4611
/* A checksum context for summing the data read in order to verify it.
4612
Note: we don't need to use the sha1 checksum because we're only doing
4613
data verification, for which md5 is perfectly safe. */
4614
svn_checksum_ctx_t *md5_checksum_ctx;
4616
svn_boolean_t checksum_finalized;
4618
/* The stored checksum of the representation we are reading, its
4619
length, and the amount we've read so far. Some of this
4620
information is redundant with rs_list and src_state, but it's
4621
convenient for the checksumming code to have it here. */
4622
svn_checksum_t *md5_checksum;
4627
/* The key for the fulltext cache for this rep, if there is a
4629
pair_cache_key_t fulltext_cache_key;
4630
/* The text we've been reading, if we're going to cache it. */
4631
svn_stringbuf_t *current_fulltext;
4633
/* Used for temporary allocations during the read. */
4636
/* Pool used to store file handles and other data that is persistant
4637
for the entire stream read. */
4638
apr_pool_t *filehandle_pool;
4641
/* Combine the name of the rev file in RS with the given OFFSET to form
4642
* a cache lookup key. Allocations will be made from POOL. May return
4643
* NULL if the key cannot be constructed. */
4645
get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4648
const char *last_part;
4649
const char *name_last;
4651
/* the rev file name containing the txdelta window.
4652
* If this fails we are in serious trouble anyways.
4653
* And if nobody else detects the problems, the file content checksum
4654
* comparison _will_ find them.
4656
if (apr_file_name_get(&name, rs->file))
4659
/* Handle packed files as well by scanning backwards until we find the
4660
* revision or pack number. */
4661
name_last = name + strlen(name) - 1;
4662
while (! svn_ctype_isdigit(*name_last))
4665
last_part = name_last;
4666
while (svn_ctype_isdigit(*last_part))
4669
/* We must differentiate between packed files (as of today, the number
4670
* is being followed by a dot) and non-packed files (followed by \0).
4671
* Otherwise, there might be overlaps in the numbering range if the
4672
* repo gets packed after caching the txdeltas of non-packed revs.
4673
* => add the first non-digit char to the packed number. */
4674
if (name_last[1] != '\0')
4677
/* copy one char MORE than the actual number to mark packed files,
4678
* i.e. packed revision file content uses different key space then
4679
* non-packed ones: keys for packed rev file content ends with a dot
4680
* for non-packed rev files they end with a digit. */
4681
name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4682
return svn_fs_fs__combine_number_and_string(offset, name, pool);
4685
/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4686
* cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4687
* cache has been given. If a cache is available IS_CACHED will inform
4688
* the caller about the success of the lookup. Allocations (of the window
4689
* in particualar) will be made from POOL.
4691
* If the information could be found, put RS and the position within the
4692
* rev file into the same state as if the data had just been read from it.
4694
static svn_error_t *
4695
get_cached_window(svn_txdelta_window_t **window_p,
4696
struct rep_state *rs,
4697
svn_boolean_t *is_cached,
4700
if (! rs->window_cache)
4702
/* txdelta window has not been enabled */
4707
/* ask the cache for the desired txdelta window */
4708
svn_fs_fs__txdelta_cached_window_t *cached_window;
4709
SVN_ERR(svn_cache__get((void **) &cached_window,
4712
get_window_key(rs, rs->off, pool),
4717
/* found it. Pass it back to the caller. */
4718
*window_p = cached_window->window;
4720
/* manipulate the RS as if we just read the data */
4722
rs->off = cached_window->end_offset;
4724
/* manipulate the rev file as if we just read from it */
4725
SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4729
return SVN_NO_ERROR;
4732
/* Store the WINDOW read at OFFSET for the rep state RS in the current
4733
* FSFS session's cache. This will be a no-op if no cache has been given.
4734
* Temporary allocations will be made from SCRATCH_POOL. */
4735
static svn_error_t *
4736
set_cached_window(svn_txdelta_window_t *window,
4737
struct rep_state *rs,
4739
apr_pool_t *scratch_pool)
4741
if (rs->window_cache)
4743
/* store the window and the first offset _past_ it */
4744
svn_fs_fs__txdelta_cached_window_t cached_window;
4746
cached_window.window = window;
4747
cached_window.end_offset = rs->off;
4749
/* but key it with the start offset because that is the known state
4750
* when we will look it up */
4751
return svn_cache__set(rs->window_cache,
4752
get_window_key(rs, offset, scratch_pool),
4757
return SVN_NO_ERROR;
4760
/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4761
* cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4762
* cache has been given. If a cache is available IS_CACHED will inform
4763
* the caller about the success of the lookup. Allocations (of the window
4764
* in particualar) will be made from POOL.
4766
static svn_error_t *
4767
get_cached_combined_window(svn_stringbuf_t **window_p,
4768
struct rep_state *rs,
4769
svn_boolean_t *is_cached,
4772
if (! rs->combined_cache)
4774
/* txdelta window has not been enabled */
4779
/* ask the cache for the desired txdelta window */
4780
return svn_cache__get((void **)window_p,
4783
get_window_key(rs, rs->start, pool),
4787
return SVN_NO_ERROR;
4790
/* Store the WINDOW read at OFFSET for the rep state RS in the current
4791
* FSFS session's cache. This will be a no-op if no cache has been given.
4792
* Temporary allocations will be made from SCRATCH_POOL. */
4793
static svn_error_t *
4794
set_cached_combined_window(svn_stringbuf_t *window,
4795
struct rep_state *rs,
4797
apr_pool_t *scratch_pool)
4799
if (rs->combined_cache)
4801
/* but key it with the start offset because that is the known state
4802
* when we will look it up */
4803
return svn_cache__set(rs->combined_cache,
4804
get_window_key(rs, offset, scratch_pool),
4809
return SVN_NO_ERROR;
4812
/* Build an array of rep_state structures in *LIST giving the delta
4813
reps from first_rep to a plain-text or self-compressed rep. Set
4814
*SRC_STATE to the plain-text rep we find at the end of the chain,
4815
or to NULL if the final delta representation is self-compressed.
4816
The representation to start from is designated by filesystem FS, id
4817
ID, and representation REP.
4818
Also, set *WINDOW_P to the base window content for *LIST, if it
4819
could be found in cache. Otherwise, *LIST will contain the base
4820
representation for the whole delta chain.
4821
Finally, return the expanded size of the representation in
4822
*EXPANDED_SIZE. It will take care of cases where only the on-disk
4824
static svn_error_t *
4825
build_rep_list(apr_array_header_t **list,
4826
svn_stringbuf_t **window_p,
4827
struct rep_state **src_state,
4828
svn_filesize_t *expanded_size,
4830
representation_t *first_rep,
4833
representation_t rep;
4834
struct rep_state *rs = NULL;
4835
struct rep_args *rep_args;
4836
svn_boolean_t is_cached = FALSE;
4837
apr_file_t *last_file = NULL;
4838
svn_revnum_t last_revision;
4840
*list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4843
/* The value as stored in the data struct.
4844
0 is either for unknown length or actually zero length. */
4845
*expanded_size = first_rep->expanded_size;
4847
/* for the top-level rep, we need the rep_args */
4848
SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4849
&last_revision, &rep, fs, pool));
4851
/* Unknown size or empty representation?
4852
That implies the this being the first iteration.
4853
Usually size equals on-disk size, except for empty,
4854
compressed representations (delta, size = 4).
4855
Please note that for all non-empty deltas have
4856
a 4-byte header _plus_ some data. */
4857
if (*expanded_size == 0)
4858
if (! rep_args->is_delta || first_rep->size != 4)
4859
*expanded_size = first_rep->size;
4863
/* fetch state, if that has not been done already */
4865
SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4866
&last_revision, &rep, fs, pool));
4868
SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4871
/* We already have a reconstructed window in our cache.
4872
Write a pseudo rep_state with the full length. */
4873
rs->off = rs->start;
4874
rs->end = rs->start + (*window_p)->len;
4876
return SVN_NO_ERROR;
4879
if (!rep_args->is_delta)
4881
/* This is a plaintext, so just return the current rep_state. */
4883
return SVN_NO_ERROR;
4886
/* Push this rep onto the list. If it's self-compressed, we're done. */
4887
APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4888
if (rep_args->is_delta_vs_empty)
4891
return SVN_NO_ERROR;
4894
rep.revision = rep_args->base_revision;
4895
rep.offset = rep_args->base_offset;
4896
rep.size = rep_args->base_length;
4904
/* Create a rep_read_baton structure for node revision NODEREV in
4905
filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not
4906
NULL, it is the rep's key in the fulltext cache, and a stringbuf
4907
must be allocated to store the text. Perform all allocations in
4908
POOL. If rep is mutable, it must be for file contents. */
4909
static svn_error_t *
4910
rep_read_get_baton(struct rep_read_baton **rb_p,
4912
representation_t *rep,
4913
pair_cache_key_t fulltext_cache_key,
4916
struct rep_read_baton *b;
4918
b = apr_pcalloc(pool, sizeof(*b));
4920
b->base_window = NULL;
4923
b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4924
b->checksum_finalized = FALSE;
4925
b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4926
b->len = rep->expanded_size;
4928
b->fulltext_cache_key = fulltext_cache_key;
4929
b->pool = svn_pool_create(pool);
4930
b->filehandle_pool = svn_pool_create(pool);
4932
SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4933
&b->src_state, &b->len, fs, rep,
4934
b->filehandle_pool));
4936
if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4937
b->current_fulltext = svn_stringbuf_create_ensure
4938
((apr_size_t)b->len,
4939
b->filehandle_pool);
4941
b->current_fulltext = NULL;
4943
/* Save our output baton. */
4946
return SVN_NO_ERROR;
4949
/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4950
window into *NWIN. */
4951
static svn_error_t *
4952
read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4953
struct rep_state *rs, apr_pool_t *pool)
4955
svn_stream_t *stream;
4956
svn_boolean_t is_cached;
4957
apr_off_t old_offset;
4959
SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4961
/* RS->FILE may be shared between RS instances -> make sure we point
4962
* to the right data. */
4963
SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4965
/* Skip windows to reach the current chunk if we aren't there yet. */
4966
while (rs->chunk_index < this_chunk)
4968
SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4970
SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4971
if (rs->off >= rs->end)
4972
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4973
_("Reading one svndiff window read "
4974
"beyond the end of the "
4978
/* Read the next window. But first, try to find it in the cache. */
4979
SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4981
return SVN_NO_ERROR;
4983
/* Actually read the next window. */
4984
old_offset = rs->off;
4985
stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4986
SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4988
SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4990
if (rs->off > rs->end)
4991
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4992
_("Reading one svndiff window read beyond "
4993
"the end of the representation"));
4995
/* the window has not been cached before, thus cache it now
4996
* (if caching is used for them at all) */
4997
return set_cached_window(*nwin, rs, old_offset, pool);
5000
/* Read SIZE bytes from the representation RS and return it in *NWIN. */
5001
static svn_error_t *
5002
read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5003
apr_size_t size, apr_pool_t *pool)
5005
/* RS->FILE may be shared between RS instances -> make sure we point
5006
* to the right data. */
5007
SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5009
/* Read the plain data. */
5010
*nwin = svn_stringbuf_create_ensure(size, pool);
5011
SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5013
(*nwin)->data[size] = 0;
5016
rs->off += (apr_off_t)size;
5018
return SVN_NO_ERROR;
5021
/* Get the undeltified window that is a result of combining all deltas
5022
from the current desired representation identified in *RB with its
5023
base representation. Store the window in *RESULT. */
5024
static svn_error_t *
5025
get_combined_window(svn_stringbuf_t **result,
5026
struct rep_read_baton *rb)
5028
apr_pool_t *pool, *new_pool, *window_pool;
5030
svn_txdelta_window_t *window;
5031
apr_array_header_t *windows;
5032
svn_stringbuf_t *source, *buf = rb->base_window;
5033
struct rep_state *rs;
5035
/* Read all windows that we need to combine. This is fine because
5036
the size of each window is relatively small (100kB) and skip-
5037
delta limits the number of deltas in a chain to well under 100.
5038
Stop early if one of them does not depend on its predecessors. */
5039
window_pool = svn_pool_create(rb->pool);
5040
windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5041
for (i = 0; i < rb->rs_list->nelts; ++i)
5043
rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5044
SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5046
APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5047
if (window->src_ops == 0)
5054
/* Combine in the windows from the other delta reps. */
5055
pool = svn_pool_create(rb->pool);
5056
for (--i; i >= 0; --i)
5059
rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5060
window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5062
/* Maybe, we've got a PLAIN start representation. If we do, read
5063
as much data from it as the needed for the txdelta window's source
5065
Note that BUF / SOURCE may only be NULL in the first iteration. */
5067
if (source == NULL && rb->src_state != NULL)
5068
SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5071
/* Combine this window with the current one. */
5072
new_pool = svn_pool_create(rb->pool);
5073
buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5074
buf->len = window->tview_len;
5076
svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5077
buf->data, &buf->len);
5078
if (buf->len != window->tview_len)
5079
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5080
_("svndiff window length is "
5083
/* Cache windows only if the whole rep content could be read as a
5084
single chunk. Only then will no other chunk need a deeper RS
5085
list than the cached chunk. */
5086
if ((rb->chunk_index == 0) && (rs->off == rs->end))
5087
SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5089
/* Cycle pools so that we only need to hold three windows at a time. */
5090
svn_pool_destroy(pool);
5094
svn_pool_destroy(window_pool);
5097
return SVN_NO_ERROR;
5100
/* Returns whether or not the expanded fulltext of the file is cachable
5101
* based on its size SIZE. The decision depends on the cache used by RB.
5103
static svn_boolean_t
5104
fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5106
return (size < APR_SIZE_MAX)
5107
&& svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5110
/* Close method used on streams returned by read_representation().
5112
static svn_error_t *
5113
rep_read_contents_close(void *baton)
5115
struct rep_read_baton *rb = baton;
5117
svn_pool_destroy(rb->pool);
5118
svn_pool_destroy(rb->filehandle_pool);
5120
return SVN_NO_ERROR;
5123
/* Return the next *LEN bytes of the rep and store them in *BUF. */
5124
static svn_error_t *
5125
get_contents(struct rep_read_baton *rb,
5129
apr_size_t copy_len, remaining = *len;
5131
struct rep_state *rs;
5133
/* Special case for when there are no delta reps, only a plain
5135
if (rb->rs_list->nelts == 0)
5137
copy_len = remaining;
5140
if (rb->base_window != NULL)
5142
/* We got the desired rep directly from the cache.
5143
This is where we need the pseudo rep_state created
5144
by build_rep_list(). */
5145
apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5146
if (copy_len + offset > rb->base_window->len)
5147
copy_len = offset < rb->base_window->len
5148
? rb->base_window->len - offset
5151
memcpy (cur, rb->base_window->data + offset, copy_len);
5155
if (((apr_off_t) copy_len) > rs->end - rs->off)
5156
copy_len = (apr_size_t) (rs->end - rs->off);
5157
SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5161
rs->off += copy_len;
5163
return SVN_NO_ERROR;
5166
while (remaining > 0)
5168
/* If we have buffered data from a previous chunk, use that. */
5171
/* Determine how much to copy from the buffer. */
5172
copy_len = rb->buf_len - rb->buf_pos;
5173
if (copy_len > remaining)
5174
copy_len = remaining;
5176
/* Actually copy the data. */
5177
memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5178
rb->buf_pos += copy_len;
5180
remaining -= copy_len;
5182
/* If the buffer is all used up, clear it and empty the
5184
if (rb->buf_pos == rb->buf_len)
5186
svn_pool_clear(rb->pool);
5192
svn_stringbuf_t *sbuf = NULL;
5194
rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5195
if (rs->off == rs->end)
5198
/* Get more buffered data by evaluating a chunk. */
5199
SVN_ERR(get_combined_window(&sbuf, rb));
5202
rb->buf_len = sbuf->len;
5203
rb->buf = sbuf->data;
5210
return SVN_NO_ERROR;
5213
/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5214
representation and store them in *BUF. Sum as we read and verify
5215
the MD5 sum at the end. */
5216
static svn_error_t *
5217
rep_read_contents(void *baton,
5221
struct rep_read_baton *rb = baton;
5223
/* Get the next block of data. */
5224
SVN_ERR(get_contents(rb, buf, len));
5226
if (rb->current_fulltext)
5227
svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5229
/* Perform checksumming. We want to check the checksum as soon as
5230
the last byte of data is read, in case the caller never performs
5231
a short read, but we don't want to finalize the MD5 context
5233
if (!rb->checksum_finalized)
5235
SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5237
if (rb->off == rb->len)
5239
svn_checksum_t *md5_checksum;
5241
rb->checksum_finalized = TRUE;
5242
SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5244
if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5245
return svn_error_create(SVN_ERR_FS_CORRUPT,
5246
svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5248
_("Checksum mismatch while reading representation")),
5253
if (rb->off == rb->len && rb->current_fulltext)
5255
fs_fs_data_t *ffd = rb->fs->fsap_data;
5256
SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5257
rb->current_fulltext, rb->pool));
5258
rb->current_fulltext = NULL;
5261
return SVN_NO_ERROR;
5265
/* Return a stream in *CONTENTS_P that will read the contents of a
5266
representation stored at the location given by REP. Appropriate
5267
for any kind of immutable representation, but only for file
5268
contents (not props or directory contents) in mutable
5271
If REP is NULL, the representation is assumed to be empty, and the
5272
empty stream is returned.
5274
static svn_error_t *
5275
read_representation(svn_stream_t **contents_p,
5277
representation_t *rep,
5282
*contents_p = svn_stream_empty(pool);
5286
fs_fs_data_t *ffd = fs->fsap_data;
5287
pair_cache_key_t fulltext_cache_key = { 0 };
5288
svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5289
struct rep_read_baton *rb;
5291
fulltext_cache_key.revision = rep->revision;
5292
fulltext_cache_key.second = rep->offset;
5293
if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5294
&& fulltext_size_is_cachable(ffd, len))
5296
svn_stringbuf_t *fulltext;
5297
svn_boolean_t is_cached;
5298
SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5299
ffd->fulltext_cache, &fulltext_cache_key,
5303
*contents_p = svn_stream_from_stringbuf(fulltext, pool);
5304
return SVN_NO_ERROR;
5308
fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5310
SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5312
*contents_p = svn_stream_create(rb, pool);
5313
svn_stream_set_read(*contents_p, rep_read_contents);
5314
svn_stream_set_close(*contents_p, rep_read_contents_close);
5317
return SVN_NO_ERROR;
5321
svn_fs_fs__get_contents(svn_stream_t **contents_p,
5323
node_revision_t *noderev,
5326
return read_representation(contents_p, fs, noderev->data_rep, pool);
5329
/* Baton used when reading delta windows. */
5330
struct delta_read_baton
5332
struct rep_state *rs;
5333
svn_checksum_t *checksum;
5336
/* This implements the svn_txdelta_next_window_fn_t interface. */
5337
static svn_error_t *
5338
delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5341
struct delta_read_baton *drb = baton;
5343
if (drb->rs->off == drb->rs->end)
5346
return SVN_NO_ERROR;
5349
return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5352
/* This implements the svn_txdelta_md5_digest_fn_t interface. */
5353
static const unsigned char *
5354
delta_read_md5_digest(void *baton)
5356
struct delta_read_baton *drb = baton;
5358
if (drb->checksum->kind == svn_checksum_md5)
5359
return drb->checksum->digest;
5365
svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5367
node_revision_t *source,
5368
node_revision_t *target,
5371
svn_stream_t *source_stream, *target_stream;
5373
/* Try a shortcut: if the target is stored as a delta against the source,
5374
then just use that delta. */
5375
if (source && source->data_rep && target->data_rep)
5377
struct rep_state *rep_state;
5378
struct rep_args *rep_args;
5380
/* Read target's base rep if any. */
5381
SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5382
target->data_rep, fs, pool));
5384
/* If that matches source, then use this delta as is.
5385
Note that we want an actual delta here. E.g. a self-delta would
5386
not be good enough. */
5387
if (rep_args->is_delta
5388
&& rep_args->base_revision == source->data_rep->revision
5389
&& rep_args->base_offset == source->data_rep->offset)
5391
/* Create the delta read baton. */
5392
struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5393
drb->rs = rep_state;
5394
drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5396
*stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5397
delta_read_md5_digest, pool);
5398
return SVN_NO_ERROR;
5401
SVN_ERR(svn_io_file_close(rep_state->file, pool));
5404
/* Read both fulltexts and construct a delta. */
5406
SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5408
source_stream = svn_stream_empty(pool);
5409
SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5411
/* Because source and target stream will already verify their content,
5412
* there is no need to do this once more. In particular if the stream
5413
* content is being fetched from cache. */
5414
svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5416
return SVN_NO_ERROR;
5419
/* Baton for cache_access_wrapper. Wraps the original parameters of
5420
* svn_fs_fs__try_process_file_content().
5422
typedef struct cache_access_wrapper_baton_t
5424
svn_fs_process_contents_func_t func;
5426
} cache_access_wrapper_baton_t;
5428
/* Wrapper to translate between svn_fs_process_contents_func_t and
5429
* svn_cache__partial_getter_func_t.
5431
static svn_error_t *
5432
cache_access_wrapper(void **out,
5434
apr_size_t data_len,
5438
cache_access_wrapper_baton_t *wrapper_baton = baton;
5440
SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5441
data_len - 1, /* cache adds terminating 0 */
5442
wrapper_baton->baton,
5445
/* non-NULL value to signal the calling cache that all went well */
5448
return SVN_NO_ERROR;
5452
svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5454
node_revision_t *noderev,
5455
svn_fs_process_contents_func_t processor,
5459
representation_t *rep = noderev->data_rep;
5462
fs_fs_data_t *ffd = fs->fsap_data;
5463
pair_cache_key_t fulltext_cache_key = { 0 };
5465
fulltext_cache_key.revision = rep->revision;
5466
fulltext_cache_key.second = rep->offset;
5467
if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5468
&& fulltext_size_is_cachable(ffd, rep->expanded_size))
5470
cache_access_wrapper_baton_t wrapper_baton;
5473
wrapper_baton.func = processor;
5474
wrapper_baton.baton = baton;
5475
return svn_cache__get_partial(&dummy, success,
5476
ffd->fulltext_cache,
5477
&fulltext_cache_key,
5478
cache_access_wrapper,
5485
return SVN_NO_ERROR;
5488
/* Fetch the contents of a directory into ENTRIES. Values are stored
5489
as filename to string mappings; further conversion is necessary to
5490
convert them into svn_fs_dirent_t values. */
5491
static svn_error_t *
5492
get_dir_contents(apr_hash_t *entries,
5494
node_revision_t *noderev,
5497
svn_stream_t *contents;
5499
if (noderev->data_rep && noderev->data_rep->txn_id)
5501
const char *filename = path_txn_node_children(fs, noderev->id, pool);
5503
/* The representation is mutable. Read the old directory
5504
contents from the mutable children file, followed by the
5505
changes we've made in this transaction. */
5506
SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5507
SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5508
SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5509
SVN_ERR(svn_stream_close(contents));
5511
else if (noderev->data_rep)
5513
/* use a temporary pool for temp objects.
5514
* Also undeltify content before parsing it. Otherwise, we could only
5515
* parse it byte-by-byte.
5517
apr_pool_t *text_pool = svn_pool_create(pool);
5518
apr_size_t len = noderev->data_rep->expanded_size
5519
? (apr_size_t)noderev->data_rep->expanded_size
5520
: (apr_size_t)noderev->data_rep->size;
5521
svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5524
/* The representation is immutable. Read it normally. */
5525
SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5526
SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5527
SVN_ERR(svn_stream_close(contents));
5529
/* de-serialize hash */
5530
contents = svn_stream_from_stringbuf(text, text_pool);
5531
SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5533
svn_pool_destroy(text_pool);
5536
return SVN_NO_ERROR;
5541
unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5544
return apr_psprintf(pool, "%s %s",
5545
(kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5546
svn_fs_fs__id_unparse(id, pool)->data);
5549
/* Given a hash ENTRIES of dirent structions, return a hash in
5550
*STR_ENTRIES_P, that has svn_string_t as the values in the format
5551
specified by the fs_fs directory contents file. Perform
5552
allocations in POOL. */
5553
static svn_error_t *
5554
unparse_dir_entries(apr_hash_t **str_entries_p,
5555
apr_hash_t *entries,
5558
apr_hash_index_t *hi;
5560
/* For now, we use a our own hash function to ensure that we get a
5561
* (largely) stable order when serializing the data. It also gives
5562
* us some performance improvement.
5565
* Use some sorted or other fixed order data container.
5567
*str_entries_p = svn_hash__make(pool);
5569
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5573
svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5574
const char *new_val;
5576
apr_hash_this(hi, &key, &klen, NULL);
5577
new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5578
apr_hash_set(*str_entries_p, key, klen,
5579
svn_string_create(new_val, pool));
5582
return SVN_NO_ERROR;
5586
/* Given a hash STR_ENTRIES with values as svn_string_t as specified
5587
in an FSFS directory contents listing, return a hash of dirents in
5588
*ENTRIES_P. Perform allocations in POOL. */
5589
static svn_error_t *
5590
parse_dir_entries(apr_hash_t **entries_p,
5591
apr_hash_t *str_entries,
5592
const char *unparsed_id,
5595
apr_hash_index_t *hi;
5597
*entries_p = apr_hash_make(pool);
5599
/* Translate the string dir entries into real entries. */
5600
for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5602
const char *name = svn__apr_hash_index_key(hi);
5603
svn_string_t *str_val = svn__apr_hash_index_val(hi);
5604
char *str, *last_str;
5605
svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5607
last_str = apr_pstrdup(pool, str_val->data);
5608
dirent->name = apr_pstrdup(pool, name);
5610
str = svn_cstring_tokenize(" ", &last_str);
5612
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5613
_("Directory entry corrupt in '%s'"),
5616
if (strcmp(str, KIND_FILE) == 0)
5618
dirent->kind = svn_node_file;
5620
else if (strcmp(str, KIND_DIR) == 0)
5622
dirent->kind = svn_node_dir;
5626
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5627
_("Directory entry corrupt in '%s'"),
5631
str = svn_cstring_tokenize(" ", &last_str);
5633
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5634
_("Directory entry corrupt in '%s'"),
5637
dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5639
svn_hash_sets(*entries_p, dirent->name, dirent);
5642
return SVN_NO_ERROR;
5645
/* Return the cache object in FS responsible to storing the directory
5646
* the NODEREV. If none exists, return NULL. */
5647
static svn_cache__t *
5648
locate_dir_cache(svn_fs_t *fs,
5649
node_revision_t *noderev)
5651
fs_fs_data_t *ffd = fs->fsap_data;
5652
return svn_fs_fs__id_txn_id(noderev->id)
5653
? ffd->txn_dir_cache
5658
svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5660
node_revision_t *noderev,
5663
const char *unparsed_id = NULL;
5664
apr_hash_t *unparsed_entries, *parsed_entries;
5666
/* find the cache we may use */
5667
svn_cache__t *cache = locate_dir_cache(fs, noderev);
5670
svn_boolean_t found;
5672
unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5673
SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5674
unparsed_id, pool));
5676
return SVN_NO_ERROR;
5679
/* Read in the directory hash. */
5680
unparsed_entries = apr_hash_make(pool);
5681
SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5682
SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5683
unparsed_id, pool));
5685
/* Update the cache, if we are to use one. */
5687
SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5689
*entries_p = parsed_entries;
5690
return SVN_NO_ERROR;
5694
svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5696
node_revision_t *noderev,
5698
apr_pool_t *result_pool,
5699
apr_pool_t *scratch_pool)
5701
svn_boolean_t found = FALSE;
5703
/* find the cache we may use */
5704
svn_cache__t *cache = locate_dir_cache(fs, noderev);
5707
const char *unparsed_id =
5708
svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5711
SVN_ERR(svn_cache__get_partial((void **)dirent,
5715
svn_fs_fs__extract_dir_entry,
5720
/* fetch data from disk if we did not find it in the cache */
5723
apr_hash_t *entries;
5724
svn_fs_dirent_t *entry;
5725
svn_fs_dirent_t *entry_copy = NULL;
5727
/* read the dir from the file system. It will probably be put it
5728
into the cache for faster lookup in future calls. */
5729
SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5732
/* find desired entry and return a copy in POOL, if found */
5733
entry = svn_hash_gets(entries, name);
5736
entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5737
entry_copy->name = apr_pstrdup(result_pool, entry->name);
5738
entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5739
entry_copy->kind = entry->kind;
5742
*dirent = entry_copy;
5745
return SVN_NO_ERROR;
5749
svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5751
node_revision_t *noderev,
5754
apr_hash_t *proplist;
5755
svn_stream_t *stream;
5757
if (noderev->prop_rep && noderev->prop_rep->txn_id)
5759
const char *filename = path_txn_node_props(fs, noderev->id, pool);
5760
proplist = apr_hash_make(pool);
5762
SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5763
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5764
SVN_ERR(svn_stream_close(stream));
5766
else if (noderev->prop_rep)
5768
fs_fs_data_t *ffd = fs->fsap_data;
5769
representation_t *rep = noderev->prop_rep;
5770
pair_cache_key_t key = { 0 };
5772
key.revision = rep->revision;
5773
key.second = rep->offset;
5774
if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5776
svn_boolean_t is_cached;
5777
SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5778
ffd->properties_cache, &key, pool));
5780
return SVN_NO_ERROR;
5783
proplist = apr_hash_make(pool);
5784
SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5785
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5786
SVN_ERR(svn_stream_close(stream));
5788
if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5789
SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5793
/* return an empty prop list if the node doesn't have any props */
5794
proplist = apr_hash_make(pool);
5797
*proplist_p = proplist;
5799
return SVN_NO_ERROR;
5803
svn_fs_fs__file_length(svn_filesize_t *length,
5804
node_revision_t *noderev,
5807
if (noderev->data_rep)
5808
*length = noderev->data_rep->expanded_size;
5812
return SVN_NO_ERROR;
5816
svn_fs_fs__noderev_same_rep_key(representation_t *a,
5817
representation_t *b)
5822
if (a == NULL || b == NULL)
5825
if (a->offset != b->offset)
5828
if (a->revision != b->revision)
5831
if (a->uniquifier == b->uniquifier)
5834
if (a->uniquifier == NULL || b->uniquifier == NULL)
5837
return strcmp(a->uniquifier, b->uniquifier) == 0;
1495
svn_boolean_t strict,
1496
apr_pool_t *scratch_pool)
1498
representation_t *rep_a = a->prop_rep;
1499
representation_t *rep_b = b->prop_rep;
1500
apr_hash_t *proplist_a;
1501
apr_hash_t *proplist_b;
1503
/* Mainly for a==b==NULL */
1507
return SVN_NO_ERROR;
1510
/* Committed property lists can be compared quickly */
1512
&& !svn_fs_fs__id_txn_used(&rep_a->txn_id)
1513
&& !svn_fs_fs__id_txn_used(&rep_b->txn_id))
1515
/* MD5 must be given. Having the same checksum is good enough for
1516
accepting the prop lists as equal. */
1517
*equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
1518
sizeof(rep_a->md5_digest)) == 0;
1519
return SVN_NO_ERROR;
1522
/* Same path in same txn? */
1523
if (svn_fs_fs__id_eq(a->id, b->id))
1526
return SVN_NO_ERROR;
1529
/* Skip the expensive bits unless we are in strict mode.
1530
Simply assume that there is a difference. */
1534
return SVN_NO_ERROR;
1537
/* At least one of the reps has been modified in a txn.
1538
Fetch and compare them. */
1539
SVN_ERR(svn_fs_fs__get_proplist(&proplist_a, fs, a, scratch_pool));
1540
SVN_ERR(svn_fs_fs__get_proplist(&proplist_b, fs, b, scratch_pool));
1542
*equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
1543
return SVN_NO_ERROR;
5841
1548
svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5869
1584
svn_fs_fs__rep_copy(representation_t *rep,
5870
1585
apr_pool_t *pool)
5872
representation_t *rep_new;
5874
1587
if (rep == NULL)
5877
rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5879
memcpy(rep_new, rep, sizeof(*rep_new));
5880
rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5881
rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5882
rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5887
/* Merge the internal-use-only CHANGE into a hash of public-FS
5888
svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5889
single summarical (is that real word?) change per path. Also keep
5890
the COPYFROM_CACHE up to date with new adds and replaces. */
5891
static svn_error_t *
5892
fold_change(apr_hash_t *changes,
5893
const change_t *change,
5894
apr_hash_t *copyfrom_cache)
5896
apr_pool_t *pool = apr_hash_pool_get(changes);
5897
svn_fs_path_change2_t *old_change, *new_change;
5899
apr_size_t path_len = strlen(change->path);
5901
if ((old_change = apr_hash_get(changes, change->path, path_len)))
5903
/* This path already exists in the hash, so we have to merge
5904
this change into the already existing one. */
5906
/* Sanity check: only allow NULL node revision ID in the
5908
if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5909
return svn_error_create
5910
(SVN_ERR_FS_CORRUPT, NULL,
5911
_("Missing required node revision ID"));
5913
/* Sanity check: we should be talking about the same node
5914
revision ID as our last change except where the last change
5916
if (change->noderev_id
5917
&& (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5918
&& (old_change->change_kind != svn_fs_path_change_delete))
5919
return svn_error_create
5920
(SVN_ERR_FS_CORRUPT, NULL,
5921
_("Invalid change ordering: new node revision ID "
5924
/* Sanity check: an add, replacement, or reset must be the first
5925
thing to follow a deletion. */
5926
if ((old_change->change_kind == svn_fs_path_change_delete)
5927
&& (! ((change->kind == svn_fs_path_change_replace)
5928
|| (change->kind == svn_fs_path_change_reset)
5929
|| (change->kind == svn_fs_path_change_add))))
5930
return svn_error_create
5931
(SVN_ERR_FS_CORRUPT, NULL,
5932
_("Invalid change ordering: non-add change on deleted path"));
5934
/* Sanity check: an add can't follow anything except
5935
a delete or reset. */
5936
if ((change->kind == svn_fs_path_change_add)
5937
&& (old_change->change_kind != svn_fs_path_change_delete)
5938
&& (old_change->change_kind != svn_fs_path_change_reset))
5939
return svn_error_create
5940
(SVN_ERR_FS_CORRUPT, NULL,
5941
_("Invalid change ordering: add change on preexisting path"));
5943
/* Now, merge that change in. */
5944
switch (change->kind)
5946
case svn_fs_path_change_reset:
5947
/* A reset here will simply remove the path change from the
5952
case svn_fs_path_change_delete:
5953
if (old_change->change_kind == svn_fs_path_change_add)
5955
/* If the path was introduced in this transaction via an
5956
add, and we are deleting it, just remove the path
5962
/* A deletion overrules all previous changes. */
5963
old_change->change_kind = svn_fs_path_change_delete;
5964
old_change->text_mod = change->text_mod;
5965
old_change->prop_mod = change->prop_mod;
5966
old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5967
old_change->copyfrom_path = NULL;
5971
case svn_fs_path_change_add:
5972
case svn_fs_path_change_replace:
5973
/* An add at this point must be following a previous delete,
5974
so treat it just like a replace. */
5975
old_change->change_kind = svn_fs_path_change_replace;
5976
old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5978
old_change->text_mod = change->text_mod;
5979
old_change->prop_mod = change->prop_mod;
5980
if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5982
old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5983
old_change->copyfrom_path = NULL;
5987
old_change->copyfrom_rev = change->copyfrom_rev;
5988
old_change->copyfrom_path = apr_pstrdup(pool,
5989
change->copyfrom_path);
5993
case svn_fs_path_change_modify:
5995
if (change->text_mod)
5996
old_change->text_mod = TRUE;
5997
if (change->prop_mod)
5998
old_change->prop_mod = TRUE;
6002
/* Point our new_change to our (possibly modified) old_change. */
6003
new_change = old_change;
6007
/* This change is new to the hash, so make a new public change
6008
structure from the internal one (in the hash's pool), and dup
6009
the path into the hash's pool, too. */
6010
new_change = apr_pcalloc(pool, sizeof(*new_change));
6011
new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6012
new_change->change_kind = change->kind;
6013
new_change->text_mod = change->text_mod;
6014
new_change->prop_mod = change->prop_mod;
6015
/* In FSFS, copyfrom_known is *always* true, since we've always
6016
* stored copyfroms in changed paths lists. */
6017
new_change->copyfrom_known = TRUE;
6018
if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6020
new_change->copyfrom_rev = change->copyfrom_rev;
6021
new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6025
new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6026
new_change->copyfrom_path = NULL;
6031
new_change->node_kind = change->node_kind;
6033
/* Add (or update) this path.
6035
Note: this key might already be present, and it would be nice to
6036
re-use its value, but there is no way to fetch it. The API makes no
6037
guarantees that this (new) key will not be retained. Thus, we (again)
6038
copy the key into the target pool to ensure a proper lifetime. */
6039
path = apr_pstrmemdup(pool, change->path, path_len);
6040
apr_hash_set(changes, path, path_len, new_change);
6042
/* Update the copyfrom cache, if any. */
6045
apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6046
const char *copyfrom_string = NULL, *copyfrom_key = path;
6049
if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6050
copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6051
new_change->copyfrom_rev,
6052
new_change->copyfrom_path);
6054
copyfrom_string = "";
6056
/* We need to allocate a copy of the key in the copyfrom_pool if
6057
* we're not doing a deletion and if it isn't already there. */
6058
if ( copyfrom_string
6059
&& ( ! apr_hash_count(copyfrom_cache)
6060
|| ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6061
copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6063
apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6067
return SVN_NO_ERROR;
6070
/* The 256 is an arbitrary size large enough to hold the node id and the
6072
#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6074
/* Read the next entry in the changes record from file FILE and store
6075
the resulting change in *CHANGE_P. If there is no next record,
6076
store NULL there. Perform all allocations from POOL. */
6077
static svn_error_t *
6078
read_change(change_t **change_p,
6082
char buf[MAX_CHANGE_LINE_LEN];
6083
apr_size_t len = sizeof(buf);
6085
char *str, *last_str = buf, *kind_str;
6088
/* Default return value. */
6091
err = svn_io_read_length_line(file, buf, &len, pool);
6093
/* Check for a blank line. */
6094
if (err || (len == 0))
6096
if (err && APR_STATUS_IS_EOF(err->apr_err))
6098
svn_error_clear(err);
6099
return SVN_NO_ERROR;
6101
if ((len == 0) && (! err))
6102
return SVN_NO_ERROR;
6103
return svn_error_trace(err);
6106
change = apr_pcalloc(pool, sizeof(*change));
6108
/* Get the node-id of the change. */
6109
str = svn_cstring_tokenize(" ", &last_str);
6111
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6112
_("Invalid changes line in rev-file"));
6114
change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6115
if (change->noderev_id == NULL)
6116
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6117
_("Invalid changes line in rev-file"));
6119
/* Get the change type. */
6120
str = svn_cstring_tokenize(" ", &last_str);
6122
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6123
_("Invalid changes line in rev-file"));
6125
/* Don't bother to check the format number before looking for
6126
* node-kinds: just read them if you find them. */
6127
change->node_kind = svn_node_unknown;
6128
kind_str = strchr(str, '-');
6131
/* Cap off the end of "str" (the action). */
6134
if (strcmp(kind_str, KIND_FILE) == 0)
6135
change->node_kind = svn_node_file;
6136
else if (strcmp(kind_str, KIND_DIR) == 0)
6137
change->node_kind = svn_node_dir;
6139
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6140
_("Invalid changes line in rev-file"));
6143
if (strcmp(str, ACTION_MODIFY) == 0)
6145
change->kind = svn_fs_path_change_modify;
6147
else if (strcmp(str, ACTION_ADD) == 0)
6149
change->kind = svn_fs_path_change_add;
6151
else if (strcmp(str, ACTION_DELETE) == 0)
6153
change->kind = svn_fs_path_change_delete;
6155
else if (strcmp(str, ACTION_REPLACE) == 0)
6157
change->kind = svn_fs_path_change_replace;
6159
else if (strcmp(str, ACTION_RESET) == 0)
6161
change->kind = svn_fs_path_change_reset;
6165
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6166
_("Invalid change kind in rev file"));
6169
/* Get the text-mod flag. */
6170
str = svn_cstring_tokenize(" ", &last_str);
6172
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6173
_("Invalid changes line in rev-file"));
6175
if (strcmp(str, FLAG_TRUE) == 0)
6177
change->text_mod = TRUE;
6179
else if (strcmp(str, FLAG_FALSE) == 0)
6181
change->text_mod = FALSE;
6185
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6186
_("Invalid text-mod flag in rev-file"));
6189
/* Get the prop-mod flag. */
6190
str = svn_cstring_tokenize(" ", &last_str);
6192
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6193
_("Invalid changes line in rev-file"));
6195
if (strcmp(str, FLAG_TRUE) == 0)
6197
change->prop_mod = TRUE;
6199
else if (strcmp(str, FLAG_FALSE) == 0)
6201
change->prop_mod = FALSE;
6205
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6206
_("Invalid prop-mod flag in rev-file"));
6209
/* Get the changed path. */
6210
change->path = apr_pstrdup(pool, last_str);
6213
/* Read the next line, the copyfrom line. */
6215
SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6219
change->copyfrom_rev = SVN_INVALID_REVNUM;
6220
change->copyfrom_path = NULL;
6225
str = svn_cstring_tokenize(" ", &last_str);
6227
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6228
_("Invalid changes line in rev-file"));
6229
change->copyfrom_rev = SVN_STR_TO_REV(str);
6232
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6233
_("Invalid changes line in rev-file"));
6235
change->copyfrom_path = apr_pstrdup(pool, last_str);
6240
return SVN_NO_ERROR;
6243
/* Examine all the changed path entries in CHANGES and store them in
6244
*CHANGED_PATHS. Folding is done to remove redundant or unnecessary
6245
*data. Store a hash of paths to copyfrom "REV PATH" strings in
6246
COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
6247
the changed-path entries have already been folded (by
6248
write_final_changed_path_info) and may be out of order, so we shouldn't
6249
remove children of replaced or deleted directories. Do all
6250
allocations in POOL. */
6251
static svn_error_t *
6252
process_changes(apr_hash_t *changed_paths,
6253
apr_hash_t *copyfrom_cache,
6254
apr_array_header_t *changes,
6255
svn_boolean_t prefolded,
6258
apr_pool_t *iterpool = svn_pool_create(pool);
6261
/* Read in the changes one by one, folding them into our local hash
6264
for (i = 0; i < changes->nelts; ++i)
6266
change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6268
SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6270
/* Now, if our change was a deletion or replacement, we have to
6271
blow away any changes thus far on paths that are (or, were)
6272
children of this path.
6273
### i won't bother with another iteration pool here -- at
6274
most we talking about a few extra dups of paths into what
6275
is already a temporary subpool.
6278
if (((change->kind == svn_fs_path_change_delete)
6279
|| (change->kind == svn_fs_path_change_replace))
6282
apr_hash_index_t *hi;
6284
/* a potential child path must contain at least 2 more chars
6285
(the path separator plus at least one char for the name).
6286
Also, we should not assume that all paths have been normalized
6287
i.e. some might have trailing path separators.
6289
apr_ssize_t change_path_len = strlen(change->path);
6290
apr_ssize_t min_child_len = change_path_len == 0
6292
: change->path[change_path_len-1] == '/'
6293
? change_path_len + 1
6294
: change_path_len + 2;
6296
/* CAUTION: This is the inner loop of an O(n^2) algorithm.
6297
The number of changes to process may be >> 1000.
6298
Therefore, keep the inner loop as tight as possible.
6300
for (hi = apr_hash_first(iterpool, changed_paths);
6302
hi = apr_hash_next(hi))
6304
/* KEY is the path. */
6307
apr_hash_this(hi, &path, &klen, NULL);
6309
/* If we come across a child of our path, remove it.
6310
Call svn_dirent_is_child only if there is a chance that
6311
this is actually a sub-path.
6313
if ( klen >= min_child_len
6314
&& svn_dirent_is_child(change->path, path, iterpool))
6315
apr_hash_set(changed_paths, path, klen, NULL);
6319
/* Clear the per-iteration subpool. */
6320
svn_pool_clear(iterpool);
6323
/* Destroy the per-iteration subpool. */
6324
svn_pool_destroy(iterpool);
6326
return SVN_NO_ERROR;
6329
/* Fetch all the changes from FILE and store them in *CHANGES. Do all
6330
allocations in POOL. */
6331
static svn_error_t *
6332
read_all_changes(apr_array_header_t **changes,
6338
/* pre-allocate enough room for most change lists
6339
(will be auto-expanded as necessary) */
6340
*changes = apr_array_make(pool, 30, sizeof(change_t *));
6342
SVN_ERR(read_change(&change, file, pool));
6345
APR_ARRAY_PUSH(*changes, change_t*) = change;
6346
SVN_ERR(read_change(&change, file, pool));
6349
return SVN_NO_ERROR;
6353
svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6359
apr_hash_t *changed_paths = apr_hash_make(pool);
6360
apr_array_header_t *changes;
6361
apr_pool_t *scratch_pool = svn_pool_create(pool);
6363
SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6364
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6366
SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6367
SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6368
svn_pool_destroy(scratch_pool);
6370
SVN_ERR(svn_io_file_close(file, pool));
6372
*changed_paths_p = changed_paths;
6374
return SVN_NO_ERROR;
6377
/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6378
* Allocate the result in POOL.
6380
static svn_error_t *
6381
get_changes(apr_array_header_t **changes,
6386
apr_off_t changes_offset;
6387
apr_file_t *revision_file;
6388
svn_boolean_t found;
6389
fs_fs_data_t *ffd = fs->fsap_data;
6391
/* try cache lookup first */
6393
if (ffd->changes_cache)
6395
SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6398
return SVN_NO_ERROR;
6401
/* read changes from revision file */
6403
SVN_ERR(ensure_revision_exists(fs, rev, pool));
6405
SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6407
SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6410
SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6411
SVN_ERR(read_all_changes(changes, revision_file, pool));
6413
SVN_ERR(svn_io_file_close(revision_file, pool));
6415
/* cache for future reference */
6417
if (ffd->changes_cache)
6418
SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6420
return SVN_NO_ERROR;
6425
svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6428
apr_hash_t *copyfrom_cache,
6431
apr_hash_t *changed_paths;
6432
apr_array_header_t *changes;
6433
apr_pool_t *scratch_pool = svn_pool_create(pool);
6435
SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6437
changed_paths = svn_hash__make(pool);
6439
SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6441
svn_pool_destroy(scratch_pool);
6443
*changed_paths_p = changed_paths;
6445
return SVN_NO_ERROR;
6448
/* Copy a revision node-rev SRC into the current transaction TXN_ID in
6449
the filesystem FS. This is only used to create the root of a transaction.
6450
Allocations are from POOL. */
6451
static svn_error_t *
6452
create_new_txn_noderev_from_rev(svn_fs_t *fs,
6457
node_revision_t *noderev;
6458
const char *node_id, *copy_id;
6460
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6462
if (svn_fs_fs__id_txn_id(noderev->id))
6463
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6464
_("Copying from transactions not allowed"));
6466
noderev->predecessor_id = noderev->id;
6467
noderev->predecessor_count++;
6468
noderev->copyfrom_path = NULL;
6469
noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6471
/* For the transaction root, the copyroot never changes. */
6473
node_id = svn_fs_fs__id_node_id(noderev->id);
6474
copy_id = svn_fs_fs__id_copy_id(noderev->id);
6475
noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6477
return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6480
/* A structure used by get_and_increment_txn_key_body(). */
6481
struct get_and_increment_txn_key_baton {
6487
/* Callback used in the implementation of create_txn_dir(). This gets
6488
the current base 36 value in PATH_TXN_CURRENT and increments it.
6489
It returns the original value by the baton. */
6490
static svn_error_t *
6491
get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6493
struct get_and_increment_txn_key_baton *cb = baton;
6494
const char *txn_current_filename = path_txn_current(cb->fs, pool);
6495
const char *tmp_filename;
6496
char next_txn_id[MAX_KEY_SIZE+3];
6499
svn_stringbuf_t *buf;
6500
SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6502
/* remove trailing newlines */
6503
svn_stringbuf_strip_whitespace(buf);
6504
cb->txn_id = buf->data;
6507
/* Increment the key and add a trailing \n to the string so the
6508
txn-current file has a newline in it. */
6509
svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6510
next_txn_id[len] = '\n';
6512
next_txn_id[len] = '\0';
6514
SVN_ERR(svn_io_write_unique(&tmp_filename,
6515
svn_dirent_dirname(txn_current_filename, pool),
6516
next_txn_id, len, svn_io_file_del_none, pool));
6517
SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6518
txn_current_filename, pool));
6520
return SVN_NO_ERROR;
6523
/* Create a unique directory for a transaction in FS based on revision
6524
REV. Return the ID for this transaction in *ID_P. Use a sequence
6525
value in the transaction ID to prevent reuse of transaction IDs. */
6526
static svn_error_t *
6527
create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6530
struct get_and_increment_txn_key_baton cb;
6531
const char *txn_dir;
6533
/* Get the current transaction sequence value, which is a base-36
6534
number, from the txn-current file, and write an
6535
incremented value back out to the file. Place the revision
6536
number the transaction is based off into the transaction id. */
6539
SVN_ERR(with_txn_current_lock(fs,
6540
get_and_increment_txn_key_body,
6543
*id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6545
txn_dir = svn_dirent_join_many(pool,
6548
apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6552
return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6555
/* Create a unique directory for a transaction in FS based on revision
6556
REV. Return the ID for this transaction in *ID_P. This
6557
implementation is used in svn 1.4 and earlier repositories and is
6558
kept in 1.5 and greater to support the --pre-1.4-compatible and
6559
--pre-1.5-compatible repository creation options. Reused
6560
transaction IDs are possible with this implementation. */
6561
static svn_error_t *
6562
create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6566
apr_pool_t *subpool;
6567
const char *unique_path, *prefix;
6569
/* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6570
prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6571
apr_psprintf(pool, "%ld", rev), NULL);
6573
subpool = svn_pool_create(pool);
6574
for (i = 1; i <= 99999; i++)
6578
svn_pool_clear(subpool);
6579
unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6580
err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6583
/* We succeeded. Return the basename minus the ".txn" extension. */
6584
const char *name = svn_dirent_basename(unique_path, subpool);
6585
*id_p = apr_pstrndup(pool, name,
6586
strlen(name) - strlen(PATH_EXT_TXN));
6587
svn_pool_destroy(subpool);
6588
return SVN_NO_ERROR;
6590
if (! APR_STATUS_IS_EEXIST(err->apr_err))
6591
return svn_error_trace(err);
6592
svn_error_clear(err);
6595
return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6597
_("Unable to create transaction directory "
6598
"in '%s' for revision %ld"),
6599
svn_dirent_local_style(fs->path, pool),
6604
svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6609
fs_fs_data_t *ffd = fs->fsap_data;
6611
svn_fs_id_t *root_id;
6613
txn = apr_pcalloc(pool, sizeof(*txn));
6615
/* Get the txn_id. */
6616
if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6617
SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6619
SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6622
txn->base_rev = rev;
6624
txn->vtable = &txn_vtable;
6627
/* Create a new root node for this transaction. */
6628
SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6629
SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6631
/* Create an empty rev file. */
6632
SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6635
/* Create an empty rev-lock file. */
6636
SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6639
/* Create an empty changes file. */
6640
SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6643
/* Create the next-ids file. */
6644
return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6648
/* Store the property list for transaction TXN_ID in PROPLIST.
6649
Perform temporary allocations in POOL. */
6650
static svn_error_t *
6651
get_txn_proplist(apr_hash_t *proplist,
6656
svn_stream_t *stream;
6658
/* Check for issue #3696. (When we find and fix the cause, we can change
6659
* this to an assertion.) */
6661
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6662
_("Internal error: a null transaction id was "
6663
"passed to get_txn_proplist()"));
6665
/* Open the transaction properties file. */
6666
SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6669
/* Read in the property list. */
6670
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6672
return svn_stream_close(stream);
6676
svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6678
const svn_string_t *value,
6681
apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6686
APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6688
return svn_fs_fs__change_txn_props(txn, props, pool);
6692
svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6693
const apr_array_header_t *props,
6696
const char *txn_prop_filename;
6697
svn_stringbuf_t *buf;
6698
svn_stream_t *stream;
6699
apr_hash_t *txn_prop = apr_hash_make(pool);
6703
err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6704
/* Here - and here only - we need to deal with the possibility that the
6705
transaction property file doesn't yet exist. The rest of the
6706
implementation assumes that the file exists, but we're called to set the
6707
initial transaction properties as the transaction is being created. */
6708
if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6709
svn_error_clear(err);
6711
return svn_error_trace(err);
6713
for (i = 0; i < props->nelts; i++)
6715
svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6717
svn_hash_sets(txn_prop, prop->name, prop->value);
6720
/* Create a new version of the file and write out the new props. */
6721
/* Open the transaction properties file. */
6722
buf = svn_stringbuf_create_ensure(1024, pool);
6723
stream = svn_stream_from_stringbuf(buf, pool);
6724
SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6725
SVN_ERR(svn_stream_close(stream));
6726
SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6727
path_txn_dir(txn->fs, txn->id, pool),
6730
svn_io_file_del_none,
6732
return svn_io_file_rename(txn_prop_filename,
6733
path_txn_props(txn->fs, txn->id, pool),
6738
svn_fs_fs__get_txn(transaction_t **txn_p,
6744
node_revision_t *noderev;
6745
svn_fs_id_t *root_id;
6747
txn = apr_pcalloc(pool, sizeof(*txn));
6748
txn->proplist = apr_hash_make(pool);
6750
SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6751
root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6753
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6755
txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6756
txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6761
return SVN_NO_ERROR;
6764
/* Write out the currently available next node_id NODE_ID and copy_id
6765
COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
6766
used both for creating new unique nodes for the given transaction, as
6767
well as uniquifying representations. Perform temporary allocations in
6769
static svn_error_t *
6770
write_next_ids(svn_fs_t *fs,
6772
const char *node_id,
6773
const char *copy_id,
6777
svn_stream_t *out_stream;
6779
SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6780
APR_WRITE | APR_TRUNCATE,
6781
APR_OS_DEFAULT, pool));
6783
out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6785
SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6787
SVN_ERR(svn_stream_close(out_stream));
6788
return svn_io_file_close(file, pool);
6791
/* Find out what the next unique node-id and copy-id are for
6792
transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
6793
and *COPY_ID. The next node-id is used both for creating new unique
6794
nodes for the given transaction, as well as uniquifying representations.
6795
Perform all allocations in POOL. */
6796
static svn_error_t *
6797
read_next_ids(const char **node_id,
6798
const char **copy_id,
6804
char buf[MAX_KEY_SIZE*2+3];
6806
char *str, *last_str = buf;
6808
SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6809
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6811
limit = sizeof(buf);
6812
SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6814
SVN_ERR(svn_io_file_close(file, pool));
6816
/* Parse this into two separate strings. */
6818
str = svn_cstring_tokenize(" ", &last_str);
6820
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6821
_("next-id file corrupt"));
6823
*node_id = apr_pstrdup(pool, str);
6825
str = svn_cstring_tokenize(" ", &last_str);
6827
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6828
_("next-id file corrupt"));
6830
*copy_id = apr_pstrdup(pool, str);
6832
return SVN_NO_ERROR;
6835
/* Get a new and unique to this transaction node-id for transaction
6836
TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
6837
Node-ids are guaranteed to be unique to this transction, but may
6838
not necessarily be sequential. Perform all allocations in POOL. */
6839
static svn_error_t *
6840
get_new_txn_node_id(const char **node_id_p,
6845
const char *cur_node_id, *cur_copy_id;
6849
/* First read in the current next-ids file. */
6850
SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6852
node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6854
len = strlen(cur_node_id);
6855
svn_fs_fs__next_key(cur_node_id, &len, node_id);
6857
SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6859
*node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6861
return SVN_NO_ERROR;
6865
svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6867
node_revision_t *noderev,
6868
const char *copy_id,
6872
const char *node_id;
6873
const svn_fs_id_t *id;
6875
/* Get a new node-id for this node. */
6876
SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6878
id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6882
SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6886
return SVN_NO_ERROR;
6890
svn_fs_fs__purge_txn(svn_fs_t *fs,
6894
fs_fs_data_t *ffd = fs->fsap_data;
6896
/* Remove the shared transaction object associated with this transaction. */
6897
SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6898
/* Remove the directory associated with this transaction. */
6899
SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6901
if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6903
/* Delete protorev and its lock, which aren't in the txn
6904
directory. It's OK if they don't exist (for example, if this
6905
is post-commit and the proto-rev has been moved into
6907
SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6909
SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6912
return SVN_NO_ERROR;
6917
svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6920
SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6922
/* Now, purge the transaction. */
6923
SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6924
apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6927
return SVN_NO_ERROR;
6932
svn_fs_fs__set_entry(svn_fs_t *fs,
6934
node_revision_t *parent_noderev,
6936
const svn_fs_id_t *id,
6937
svn_node_kind_t kind,
6940
representation_t *rep = parent_noderev->data_rep;
6941
const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6944
fs_fs_data_t *ffd = fs->fsap_data;
6945
apr_pool_t *subpool = svn_pool_create(pool);
6947
if (!rep || !rep->txn_id)
6949
const char *unique_suffix;
6950
apr_hash_t *entries;
6952
/* Before we can modify the directory, we need to dump its old
6953
contents into a mutable representation file. */
6954
SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6956
SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6957
SVN_ERR(svn_io_file_open(&file, filename,
6958
APR_WRITE | APR_CREATE | APR_BUFFERED,
6959
APR_OS_DEFAULT, pool));
6960
out = svn_stream_from_aprfile2(file, TRUE, pool);
6961
SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6963
svn_pool_clear(subpool);
6965
/* Mark the node-rev's data rep as mutable. */
6966
rep = apr_pcalloc(pool, sizeof(*rep));
6967
rep->revision = SVN_INVALID_REVNUM;
6968
rep->txn_id = txn_id;
6970
if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
6972
SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6973
rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6976
parent_noderev->data_rep = rep;
6977
SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6978
parent_noderev, FALSE, pool));
6982
/* The directory rep is already mutable, so just open it for append. */
6983
SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6984
APR_OS_DEFAULT, pool));
6985
out = svn_stream_from_aprfile2(file, TRUE, pool);
6988
/* if we have a directory cache for this transaction, update it */
6989
if (ffd->txn_dir_cache)
6991
/* build parameters: (name, new entry) pair */
6993
svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
6994
replace_baton_t baton;
6997
baton.new_entry = NULL;
7001
baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
7002
baton.new_entry->name = name;
7003
baton.new_entry->kind = kind;
7004
baton.new_entry->id = id;
7007
/* actually update the cached directory (if cached) */
7008
SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7009
svn_fs_fs__replace_dir_entry, &baton,
7012
svn_pool_clear(subpool);
7014
/* Append an incremental hash entry for the entry change. */
7017
const char *val = unparse_dir_entry(kind, id, subpool);
7019
SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7020
"V %" APR_SIZE_T_FMT "\n%s\n",
7026
SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7027
strlen(name), name));
7030
SVN_ERR(svn_io_file_close(file, subpool));
7031
svn_pool_destroy(subpool);
7032
return SVN_NO_ERROR;
7035
/* Write a single change entry, path PATH, change CHANGE, and copyfrom
7036
string COPYFROM, into the file specified by FILE. Only include the
7037
node kind field if INCLUDE_NODE_KIND is true. All temporary
7038
allocations are in POOL. */
7039
static svn_error_t *
7040
write_change_entry(apr_file_t *file,
7042
svn_fs_path_change2_t *change,
7043
svn_boolean_t include_node_kind,
7046
const char *idstr, *buf;
7047
const char *change_string = NULL;
7048
const char *kind_string = "";
7050
switch (change->change_kind)
7052
case svn_fs_path_change_modify:
7053
change_string = ACTION_MODIFY;
7055
case svn_fs_path_change_add:
7056
change_string = ACTION_ADD;
7058
case svn_fs_path_change_delete:
7059
change_string = ACTION_DELETE;
7061
case svn_fs_path_change_replace:
7062
change_string = ACTION_REPLACE;
7064
case svn_fs_path_change_reset:
7065
change_string = ACTION_RESET;
7068
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7069
_("Invalid change type %d"),
7070
change->change_kind);
7073
if (change->node_rev_id)
7074
idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7076
idstr = ACTION_RESET;
7078
if (include_node_kind)
7080
SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7081
|| change->node_kind == svn_node_file);
7082
kind_string = apr_psprintf(pool, "-%s",
7083
change->node_kind == svn_node_dir
7084
? KIND_DIR : KIND_FILE);
7086
buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7087
idstr, change_string, kind_string,
7088
change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7089
change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7092
SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7094
if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7096
buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7097
change->copyfrom_path);
7098
SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7101
return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7105
svn_fs_fs__add_change(svn_fs_t *fs,
7108
const svn_fs_id_t *id,
7109
svn_fs_path_change_kind_t change_kind,
7110
svn_boolean_t text_mod,
7111
svn_boolean_t prop_mod,
7112
svn_node_kind_t node_kind,
7113
svn_revnum_t copyfrom_rev,
7114
const char *copyfrom_path,
7118
svn_fs_path_change2_t *change;
7120
SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7121
APR_APPEND | APR_WRITE | APR_CREATE
7122
| APR_BUFFERED, APR_OS_DEFAULT, pool));
7124
change = svn_fs__path_change_create_internal(id, change_kind, pool);
7125
change->text_mod = text_mod;
7126
change->prop_mod = prop_mod;
7127
change->node_kind = node_kind;
7128
change->copyfrom_rev = copyfrom_rev;
7129
change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7131
SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7133
return svn_io_file_close(file, pool);
7136
/* This baton is used by the representation writing streams. It keeps
7137
track of the checksum information as well as the total size of the
7138
representation so far. */
7139
struct rep_write_baton
7141
/* The FS we are writing to. */
7144
/* Actual file to which we are writing. */
7145
svn_stream_t *rep_stream;
7147
/* A stream from the delta combiner. Data written here gets
7148
deltified, then eventually written to rep_stream. */
7149
svn_stream_t *delta_stream;
7151
/* Where is this representation header stored. */
7152
apr_off_t rep_offset;
7154
/* Start of the actual data. */
7155
apr_off_t delta_start;
7157
/* How many bytes have been written to this rep already. */
7158
svn_filesize_t rep_size;
7160
/* The node revision for which we're writing out info. */
7161
node_revision_t *noderev;
7163
/* Actual output file. */
7165
/* Lock 'cookie' used to unlock the output file once we've finished
7169
svn_checksum_ctx_t *md5_checksum_ctx;
7170
svn_checksum_ctx_t *sha1_checksum_ctx;
7174
apr_pool_t *parent_pool;
7177
/* Handler for the write method of the representation writable stream.
7178
BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7179
the length of this data. */
7180
static svn_error_t *
7181
rep_write_contents(void *baton,
7185
struct rep_write_baton *b = baton;
7187
SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7188
SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7189
b->rep_size += *len;
7191
/* If we are writing a delta, use that stream. */
7192
if (b->delta_stream)
7193
return svn_stream_write(b->delta_stream, data, len);
7195
return svn_stream_write(b->rep_stream, data, len);
7198
/* Given a node-revision NODEREV in filesystem FS, return the
7199
representation in *REP to use as the base for a text representation
7200
delta if PROPS is FALSE. If PROPS has been set, a suitable props
7201
base representation will be returned. Perform temporary allocations
7203
static svn_error_t *
7204
choose_delta_base(representation_t **rep,
7206
node_revision_t *noderev,
7207
svn_boolean_t props,
7212
node_revision_t *base;
7213
fs_fs_data_t *ffd = fs->fsap_data;
7214
svn_boolean_t maybe_shared_rep = FALSE;
7216
/* If we have no predecessors, then use the empty stream as a
7218
if (! noderev->predecessor_count)
7221
return SVN_NO_ERROR;
7224
/* Flip the rightmost '1' bit of the predecessor count to determine
7225
which file rev (counting from 0) we want to use. (To see why
7226
count & (count - 1) unsets the rightmost set bit, think about how
7227
you decrement a binary number.) */
7228
count = noderev->predecessor_count;
7229
count = count & (count - 1);
7231
/* We use skip delta for limiting the number of delta operations
7232
along very long node histories. Close to HEAD however, we create
7233
a linear history to minimize delta size. */
7234
walk = noderev->predecessor_count - count;
7235
if (walk < (int)ffd->max_linear_deltification)
7236
count = noderev->predecessor_count - 1;
7238
/* Finding the delta base over a very long distance can become extremely
7239
expensive for very deep histories, possibly causing client timeouts etc.
7240
OTOH, this is a rare operation and its gains are minimal. Lets simply
7241
start deltification anew close every other 1000 changes or so. */
7242
if (walk > (int)ffd->max_deltification_walk)
7245
return SVN_NO_ERROR;
7248
/* Walk back a number of predecessors equal to the difference
7249
between count and the original predecessor count. (For example,
7250
if noderev has ten predecessors and we want the eighth file rev,
7251
walk back two predecessors.) */
7253
while ((count++) < noderev->predecessor_count)
7255
SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7256
base->predecessor_id, pool));
7258
/* If there is a shared rep along the way, we need to limit the
7259
* length of the deltification chain.
7261
* Please note that copied nodes - such as branch directories - will
7262
* look the same (false positive) while reps shared within the same
7263
* revision will not be caught (false negative).
7268
&& svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7269
maybe_shared_rep = TRUE;
7274
&& svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7275
maybe_shared_rep = TRUE;
7279
/* return a suitable base representation */
7280
*rep = props ? base->prop_rep : base->data_rep;
7282
/* if we encountered a shared rep, it's parent chain may be different
7283
* from the node-rev parent chain. */
7284
if (*rep && maybe_shared_rep)
7286
/* Check whether the length of the deltification chain is acceptable.
7287
* Otherwise, shared reps may form a non-skipping delta chain in
7289
apr_pool_t *sub_pool = svn_pool_create(pool);
7290
representation_t base_rep = **rep;
7292
/* Some reasonable limit, depending on how acceptable longer linear
7293
* chains are in this repo. Also, allow for some minimal chain. */
7294
int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7296
/* re-use open files between iterations */
7297
svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7298
apr_file_t *file_hint = NULL;
7300
/* follow the delta chain towards the end but for at most
7301
* MAX_CHAIN_LENGTH steps. */
7302
for (; max_chain_length; --max_chain_length)
7304
struct rep_state *rep_state;
7305
struct rep_args *rep_args;
7307
SVN_ERR(create_rep_state_body(&rep_state,
7314
if (!rep_args->is_delta || !rep_args->base_revision)
7317
base_rep.revision = rep_args->base_revision;
7318
base_rep.offset = rep_args->base_offset;
7319
base_rep.size = rep_args->base_length;
7320
base_rep.txn_id = NULL;
7323
/* start new delta chain if the current one has grown too long */
7324
if (max_chain_length == 0)
7327
svn_pool_destroy(sub_pool);
7330
/* verify that the reps don't form a degenerated '*/
7331
return SVN_NO_ERROR;
7334
/* Something went wrong and the pool for the rep write is being
7335
cleared before we've finished writing the rep. So we need
7336
to remove the rep from the protorevfile and we need to unlock
7337
the protorevfile. */
7339
rep_write_cleanup(void *data)
7341
struct rep_write_baton *b = data;
7342
const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7345
/* Truncate and close the protorevfile. */
7346
err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7347
err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7349
/* Remove our lock regardless of any preceeding errors so that the
7350
being_written flag is always removed and stays consistent with the
7351
file lock which will be removed no matter what since the pool is
7353
err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7354
b->lockcookie, b->pool));
7357
apr_status_t rc = err->apr_err;
7358
svn_error_clear(err);
7366
/* Get a rep_write_baton and store it in *WB_P for the representation
7367
indicated by NODEREV in filesystem FS. Perform allocations in
7368
POOL. Only appropriate for file contents, not for props or
7369
directory contents. */
7370
static svn_error_t *
7371
rep_write_get_baton(struct rep_write_baton **wb_p,
7373
node_revision_t *noderev,
7376
struct rep_write_baton *b;
7378
representation_t *base_rep;
7379
svn_stream_t *source;
7381
svn_txdelta_window_handler_t wh;
7383
fs_fs_data_t *ffd = fs->fsap_data;
7384
int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7386
b = apr_pcalloc(pool, sizeof(*b));
7388
b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7389
b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7392
b->parent_pool = pool;
7393
b->pool = svn_pool_create(pool);
7395
b->noderev = noderev;
7397
/* Open the prototype rev file and seek to its end. */
7398
SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7399
fs, svn_fs_fs__id_txn_id(noderev->id),
7403
b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7405
SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7407
/* Get the base for this delta. */
7408
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7409
SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7411
/* Write out the rep header. */
7414
header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7415
SVN_FILESIZE_T_FMT "\n",
7416
base_rep->revision, base_rep->offset,
7421
header = REP_DELTA "\n";
7423
SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7426
/* Now determine the offset of the actual svndiff data. */
7427
SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7429
/* Cleanup in case something goes wrong. */
7430
apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7431
apr_pool_cleanup_null);
7433
/* Prepare to write the svndiff data. */
7434
svn_txdelta_to_svndiff3(&wh,
7438
SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7441
b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7445
return SVN_NO_ERROR;
7448
/* For the hash REP->SHA1, try to find an already existing representation
7449
in FS and return it in *OUT_REP. If no such representation exists or
7450
if rep sharing has been disabled for FS, NULL will be returned. Since
7451
there may be new duplicate representations within the same uncommitted
7452
revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7453
representation_t*), otherwise pass in NULL for REPS_HASH.
7454
POOL will be used for allocations. The lifetime of the returned rep is
7455
limited by both, POOL and REP lifetime.
7457
static svn_error_t *
7458
get_shared_rep(representation_t **old_rep,
7460
representation_t *rep,
7461
apr_hash_t *reps_hash,
7465
fs_fs_data_t *ffd = fs->fsap_data;
7467
/* Return NULL, if rep sharing has been disabled. */
7469
if (!ffd->rep_sharing_allowed)
7470
return SVN_NO_ERROR;
7472
/* Check and see if we already have a representation somewhere that's
7473
identical to the one we just wrote out. Start with the hash lookup
7474
because it is cheepest. */
7476
*old_rep = apr_hash_get(reps_hash,
7477
rep->sha1_checksum->digest,
7478
APR_SHA1_DIGESTSIZE);
7480
/* If we haven't found anything yet, try harder and consult our DB. */
7481
if (*old_rep == NULL)
7483
err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7485
/* ### Other error codes that we shouldn't mask out? */
7486
if (err == SVN_NO_ERROR)
7489
SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7491
else if (err->apr_err == SVN_ERR_FS_CORRUPT
7492
|| SVN_ERROR_IN_CATEGORY(err->apr_err,
7493
SVN_ERR_MALFUNC_CATEGORY_START))
7495
/* Fatal error; don't mask it.
7497
In particular, this block is triggered when the rep-cache refers
7498
to revisions in the future. We signal that as a corruption situation
7499
since, once those revisions are less than youngest (because of more
7500
commits), the rep-cache would be invalid.
7506
/* Something's wrong with the rep-sharing index. We can continue
7507
without rep-sharing, but warn.
7509
(fs->warning)(fs->warning_baton, err);
7510
svn_error_clear(err);
7515
/* look for intra-revision matches (usually data reps but not limited
7516
to them in case props happen to look like some data rep)
7518
if (*old_rep == NULL && rep->txn_id)
7520
svn_node_kind_t kind;
7521
const char *file_name
7522
= path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7524
/* in our txn, is there a rep file named with the wanted SHA1?
7525
If so, read it and use that rep.
7527
SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7528
if (kind == svn_node_file)
7530
svn_stringbuf_t *rep_string;
7531
SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7532
SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7533
rep->txn_id, FALSE, pool));
7537
/* Add information that is missing in the cached data. */
7540
/* Use the old rep for this content. */
7541
(*old_rep)->md5_checksum = rep->md5_checksum;
7542
(*old_rep)->uniquifier = rep->uniquifier;
7545
return SVN_NO_ERROR;
7548
/* Close handler for the representation write stream. BATON is a
7549
rep_write_baton. Writes out a new node-rev that correctly
7550
references the representation we just finished writing. */
7551
static svn_error_t *
7552
rep_write_contents_close(void *baton)
7554
struct rep_write_baton *b = baton;
7555
const char *unique_suffix;
7556
representation_t *rep;
7557
representation_t *old_rep;
7559
fs_fs_data_t *ffd = b->fs->fsap_data;
7561
rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7562
rep->offset = b->rep_offset;
7564
/* Close our delta stream so the last bits of svndiff are written
7566
if (b->delta_stream)
7567
SVN_ERR(svn_stream_close(b->delta_stream));
7569
/* Determine the length of the svndiff data. */
7570
SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7571
rep->size = offset - b->delta_start;
7573
/* Fill in the rest of the representation field. */
7574
rep->expanded_size = b->rep_size;
7575
rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7577
if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
7579
SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7580
rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7583
rep->revision = SVN_INVALID_REVNUM;
7585
/* Finalize the checksum. */
7586
SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7588
SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7591
/* Check and see if we already have a representation somewhere that's
7592
identical to the one we just wrote out. */
7593
SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7597
/* We need to erase from the protorev the data we just wrote. */
7598
SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7600
/* Use the old rep for this content. */
7601
b->noderev->data_rep = old_rep;
7605
/* Write out our cosmetic end marker. */
7606
SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7608
b->noderev->data_rep = rep;
7611
/* Remove cleanup callback. */
7612
apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7614
/* Write out the new node-rev information. */
7615
SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7618
SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7620
SVN_ERR(svn_io_file_close(b->file, b->pool));
7621
SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7622
svn_pool_destroy(b->pool);
7624
return SVN_NO_ERROR;
7627
/* Store a writable stream in *CONTENTS_P that will receive all data
7628
written and store it as the file data representation referenced by
7629
NODEREV in filesystem FS. Perform temporary allocations in
7630
POOL. Only appropriate for file data, not props or directory
7632
static svn_error_t *
7633
set_representation(svn_stream_t **contents_p,
7635
node_revision_t *noderev,
7638
struct rep_write_baton *wb;
7640
if (! svn_fs_fs__id_txn_id(noderev->id))
7641
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7642
_("Attempted to write to non-transaction '%s'"),
7643
svn_fs_fs__id_unparse(noderev->id, pool)->data);
7645
SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7647
*contents_p = svn_stream_create(wb, pool);
7648
svn_stream_set_write(*contents_p, rep_write_contents);
7649
svn_stream_set_close(*contents_p, rep_write_contents_close);
7651
return SVN_NO_ERROR;
7655
svn_fs_fs__set_contents(svn_stream_t **stream,
7657
node_revision_t *noderev,
7660
if (noderev->kind != svn_node_file)
7661
return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7662
_("Can't set text contents of a directory"));
7664
return set_representation(stream, fs, noderev, pool);
7668
svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7670
const svn_fs_id_t *old_idp,
7671
node_revision_t *new_noderev,
7672
const char *copy_id,
7676
const svn_fs_id_t *id;
7679
copy_id = svn_fs_fs__id_copy_id(old_idp);
7680
id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7683
new_noderev->id = id;
7685
if (! new_noderev->copyroot_path)
7687
new_noderev->copyroot_path = apr_pstrdup(pool,
7688
new_noderev->created_path);
7689
new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7692
SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7697
return SVN_NO_ERROR;
7701
svn_fs_fs__set_proplist(svn_fs_t *fs,
7702
node_revision_t *noderev,
7703
apr_hash_t *proplist,
7706
const char *filename = path_txn_node_props(fs, noderev->id, pool);
7710
/* Dump the property list to the mutable property file. */
7711
SVN_ERR(svn_io_file_open(&file, filename,
7712
APR_WRITE | APR_CREATE | APR_TRUNCATE
7713
| APR_BUFFERED, APR_OS_DEFAULT, pool));
7714
out = svn_stream_from_aprfile2(file, TRUE, pool);
7715
SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7716
SVN_ERR(svn_io_file_close(file, pool));
7718
/* Mark the node-rev's prop rep as mutable, if not already done. */
7719
if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7721
noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7722
noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7723
SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7726
return SVN_NO_ERROR;
7729
/* Read the 'current' file for filesystem FS and store the next
7730
available node id in *NODE_ID, and the next available copy id in
7731
*COPY_ID. Allocations are performed from POOL. */
7732
static svn_error_t *
7733
get_next_revision_ids(const char **node_id,
7734
const char **copy_id,
7740
svn_stringbuf_t *content;
7742
SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7743
buf = content->data;
7745
str = svn_cstring_tokenize(" ", &buf);
7747
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7748
_("Corrupt 'current' file"));
7750
str = svn_cstring_tokenize(" ", &buf);
7752
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7753
_("Corrupt 'current' file"));
7755
*node_id = apr_pstrdup(pool, str);
7757
str = svn_cstring_tokenize(" \n", &buf);
7759
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7760
_("Corrupt 'current' file"));
7762
*copy_id = apr_pstrdup(pool, str);
7764
return SVN_NO_ERROR;
7767
/* This baton is used by the stream created for write_hash_rep. */
7768
struct write_hash_baton
7770
svn_stream_t *stream;
7774
svn_checksum_ctx_t *md5_ctx;
7775
svn_checksum_ctx_t *sha1_ctx;
7778
/* The handler for the write_hash_rep stream. BATON is a
7779
write_hash_baton, DATA has the data to write and *LEN is the number
7780
of bytes to write. */
7781
static svn_error_t *
7782
write_hash_handler(void *baton,
7786
struct write_hash_baton *whb = baton;
7788
SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7789
SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7791
SVN_ERR(svn_stream_write(whb->stream, data, len));
7794
return SVN_NO_ERROR;
7797
/* Write out the hash HASH as a text representation to file FILE. In
7798
the process, record position, the total size of the dump and MD5 as
7799
well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH
7800
is not NULL, it will be used in addition to the on-disk cache to find
7801
earlier reps with the same content. When such existing reps can be
7802
found, we will truncate the one just written from the file and return
7803
the existing rep. Perform temporary allocations in POOL. */
7804
static svn_error_t *
7805
write_hash_rep(representation_t *rep,
7809
apr_hash_t *reps_hash,
7812
svn_stream_t *stream;
7813
struct write_hash_baton *whb;
7814
representation_t *old_rep;
7816
SVN_ERR(get_file_offset(&rep->offset, file, pool));
7818
whb = apr_pcalloc(pool, sizeof(*whb));
7820
whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7822
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7823
whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7825
stream = svn_stream_create(whb, pool);
7826
svn_stream_set_write(stream, write_hash_handler);
7828
SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7830
SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7832
/* Store the results. */
7833
SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7834
SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7836
/* Check and see if we already have a representation somewhere that's
7837
identical to the one we just wrote out. */
7838
SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7842
/* We need to erase from the protorev the data we just wrote. */
7843
SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7845
/* Use the old rep for this content. */
7846
memcpy(rep, old_rep, sizeof (*rep));
7850
/* Write out our cosmetic end marker. */
7851
SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7853
/* update the representation */
7854
rep->size = whb->size;
7855
rep->expanded_size = whb->size;
7858
return SVN_NO_ERROR;
7861
/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7862
text representation to file FILE. In the process, record the total size
7863
and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH
7864
is not NULL, it will be used in addition to the on-disk cache to find
7865
earlier reps with the same content. When such existing reps can be found,
7866
we will truncate the one just written from the file and return the existing
7867
rep. If PROPS is set, assume that we want to a props representation as
7868
the base for our delta. Perform temporary allocations in POOL. */
7869
static svn_error_t *
7870
write_hash_delta_rep(representation_t *rep,
7874
node_revision_t *noderev,
7875
apr_hash_t *reps_hash,
7876
svn_boolean_t props,
7879
svn_txdelta_window_handler_t diff_wh;
7882
svn_stream_t *file_stream;
7883
svn_stream_t *stream;
7884
representation_t *base_rep;
7885
representation_t *old_rep;
7886
svn_stream_t *source;
7889
apr_off_t rep_end = 0;
7890
apr_off_t delta_start = 0;
7892
struct write_hash_baton *whb;
7893
fs_fs_data_t *ffd = fs->fsap_data;
7894
int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7896
/* Get the base for this delta. */
7897
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7898
SVN_ERR(read_representation(&source, fs, base_rep, pool));
7900
SVN_ERR(get_file_offset(&rep->offset, file, pool));
7902
/* Write out the rep header. */
7905
header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7906
SVN_FILESIZE_T_FMT "\n",
7907
base_rep->revision, base_rep->offset,
7912
header = REP_DELTA "\n";
7914
SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7917
SVN_ERR(get_file_offset(&delta_start, file, pool));
7918
file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7920
/* Prepare to write the svndiff data. */
7921
svn_txdelta_to_svndiff3(&diff_wh,
7925
SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7928
whb = apr_pcalloc(pool, sizeof(*whb));
7929
whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7931
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7932
whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7934
/* serialize the hash */
7935
stream = svn_stream_create(whb, pool);
7936
svn_stream_set_write(stream, write_hash_handler);
7938
SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7939
SVN_ERR(svn_stream_close(whb->stream));
7941
/* Store the results. */
7942
SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7943
SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7945
/* Check and see if we already have a representation somewhere that's
7946
identical to the one we just wrote out. */
7947
SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7951
/* We need to erase from the protorev the data we just wrote. */
7952
SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7954
/* Use the old rep for this content. */
7955
memcpy(rep, old_rep, sizeof (*rep));
7959
/* Write out our cosmetic end marker. */
7960
SVN_ERR(get_file_offset(&rep_end, file, pool));
7961
SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7963
/* update the representation */
7964
rep->expanded_size = whb->size;
7965
rep->size = rep_end - delta_start;
7968
return SVN_NO_ERROR;
7971
/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7972
of (not yet committed) revision REV in FS. Use POOL for temporary
7975
If you change this function, consider updating svn_fs_fs__verify() too.
7977
static svn_error_t *
7978
validate_root_noderev(svn_fs_t *fs,
7979
node_revision_t *root_noderev,
7983
svn_revnum_t head_revnum = rev-1;
7984
int head_predecessor_count;
7986
SVN_ERR_ASSERT(rev > 0);
7988
/* Compute HEAD_PREDECESSOR_COUNT. */
7990
svn_fs_root_t *head_revision;
7991
const svn_fs_id_t *head_root_id;
7992
node_revision_t *head_root_noderev;
7994
/* Get /@HEAD's noderev. */
7995
SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
7996
SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
7997
SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
8000
head_predecessor_count = head_root_noderev->predecessor_count;
8003
/* Check that the root noderev's predecessor count equals REV.
8005
This kind of corruption was seen on svn.apache.org (both on
8006
the root noderev and on other fspaths' noderevs); see
8009
Normally (rev == root_noderev->predecessor_count), but here we
8010
use a more roundabout check that should only trigger on new instances
8011
of the corruption, rather then trigger on each and every new commit
8012
to a repository that has triggered the bug somewhere in its root
8015
if (root_noderev->predecessor_count != -1
8016
&& (root_noderev->predecessor_count - head_predecessor_count)
8017
!= (rev - head_revnum))
8019
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8020
_("predecessor count for "
8021
"the root node-revision is wrong: "
8022
"found (%d+%ld != %d), committing r%ld"),
8023
head_predecessor_count,
8024
rev - head_revnum, /* This is equal to 1. */
8025
root_noderev->predecessor_count,
8029
return SVN_NO_ERROR;
8032
/* Copy a node-revision specified by id ID in fileystem FS from a
8033
transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
8034
pointer to the new node-id which will be allocated in POOL.
8035
If this is a directory, copy all children as well.
8037
START_NODE_ID and START_COPY_ID are
8038
the first available node and copy ids for this filesystem, for older
8041
REV is the revision number that this proto-rev-file will represent.
8043
INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8046
If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8047
REPS_POOL) of each data rep that is new in this revision.
8049
If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8050
of the representations of each property rep that is new in this
8053
AT_ROOT is true if the node revision being written is the root
8054
node-revision. It is only controls additional sanity checking
8057
Temporary allocations are also from POOL. */
8058
static svn_error_t *
8059
write_final_rev(const svn_fs_id_t **new_id_p,
8063
const svn_fs_id_t *id,
8064
const char *start_node_id,
8065
const char *start_copy_id,
8066
apr_off_t initial_offset,
8067
apr_array_header_t *reps_to_cache,
8068
apr_hash_t *reps_hash,
8069
apr_pool_t *reps_pool,
8070
svn_boolean_t at_root,
8073
node_revision_t *noderev;
8074
apr_off_t my_offset;
8075
char my_node_id_buf[MAX_KEY_SIZE + 2];
8076
char my_copy_id_buf[MAX_KEY_SIZE + 2];
8077
const svn_fs_id_t *new_id;
8078
const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8079
fs_fs_data_t *ffd = fs->fsap_data;
8083
/* Check to see if this is a transaction node. */
8084
if (! svn_fs_fs__id_txn_id(id))
8085
return SVN_NO_ERROR;
8087
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8089
if (noderev->kind == svn_node_dir)
8091
apr_pool_t *subpool;
8092
apr_hash_t *entries, *str_entries;
8093
apr_array_header_t *sorted_entries;
8096
/* This is a directory. Write out all the children first. */
8097
subpool = svn_pool_create(pool);
8099
SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8100
/* For the sake of the repository administrator sort the entries
8101
so that the final file is deterministic and repeatable,
8102
however the rest of the FSFS code doesn't require any
8103
particular order here. */
8104
sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8106
for (i = 0; i < sorted_entries->nelts; ++i)
8108
svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8109
svn_sort__item_t).value;
8111
svn_pool_clear(subpool);
8112
SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8113
start_node_id, start_copy_id, initial_offset,
8114
reps_to_cache, reps_hash, reps_pool, FALSE,
8116
if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8117
dirent->id = svn_fs_fs__id_copy(new_id, pool);
8119
svn_pool_destroy(subpool);
8121
if (noderev->data_rep && noderev->data_rep->txn_id)
8123
/* Write out the contents of this directory as a text rep. */
8124
SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8126
noderev->data_rep->txn_id = NULL;
8127
noderev->data_rep->revision = rev;
8129
if (ffd->deltify_directories)
8130
SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8131
str_entries, fs, noderev, NULL,
8134
SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8140
/* This is a file. We should make sure the data rep, if it
8141
exists in a "this" state, gets rewritten to our new revision
8144
if (noderev->data_rep && noderev->data_rep->txn_id)
8146
noderev->data_rep->txn_id = NULL;
8147
noderev->data_rep->revision = rev;
8149
/* See issue 3845. Some unknown mechanism caused the
8150
protorev file to get truncated, so check for that
8152
if (noderev->data_rep->offset + noderev->data_rep->size
8154
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8155
_("Truncated protorev file detected"));
8159
/* Fix up the property reps. */
8160
if (noderev->prop_rep && noderev->prop_rep->txn_id)
8162
apr_hash_t *proplist;
8163
SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8165
noderev->prop_rep->txn_id = NULL;
8166
noderev->prop_rep->revision = rev;
8168
if (ffd->deltify_properties)
8169
SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8170
proplist, fs, noderev, reps_hash,
8173
SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8174
fs, reps_hash, pool));
8178
/* Convert our temporary ID into a permanent revision one. */
8179
SVN_ERR(get_file_offset(&my_offset, file, pool));
8181
node_id = svn_fs_fs__id_node_id(noderev->id);
8182
if (*node_id == '_')
8184
if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8185
my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8188
svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8189
my_node_id = my_node_id_buf;
8193
my_node_id = node_id;
8195
copy_id = svn_fs_fs__id_copy_id(noderev->id);
8196
if (*copy_id == '_')
8198
if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8199
my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8202
svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8203
my_copy_id = my_copy_id_buf;
8207
my_copy_id = copy_id;
8209
if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8210
noderev->copyroot_rev = rev;
8212
new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8215
noderev->id = new_id;
8217
if (ffd->rep_sharing_allowed)
8219
/* Save the data representation's hash in the rep cache. */
8220
if ( noderev->data_rep && noderev->kind == svn_node_file
8221
&& noderev->data_rep->revision == rev)
8223
SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8224
APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8225
= svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8228
if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8230
/* Add new property reps to hash and on-disk cache. */
8231
representation_t *copy
8232
= svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8234
SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8235
APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8237
apr_hash_set(reps_hash,
8238
copy->sha1_checksum->digest,
8239
APR_SHA1_DIGESTSIZE,
8244
/* don't serialize SHA1 for dirs to disk (waste of space) */
8245
if (noderev->data_rep && noderev->kind == svn_node_dir)
8246
noderev->data_rep->sha1_checksum = NULL;
8248
/* don't serialize SHA1 for props to disk (waste of space) */
8249
if (noderev->prop_rep)
8250
noderev->prop_rep->sha1_checksum = NULL;
8252
/* Workaround issue #4031: is-fresh-txn-root in revision files. */
8253
noderev->is_fresh_txn_root = FALSE;
8255
/* Write out our new node-revision. */
8257
SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8259
SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8260
noderev, ffd->format,
8261
svn_fs_fs__fs_supports_mergeinfo(fs),
8264
/* Return our ID that references the revision file. */
8265
*new_id_p = noderev->id;
8267
return SVN_NO_ERROR;
8270
/* Write the changed path info from transaction TXN_ID in filesystem
8271
FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
8272
in the file of the beginning of this information. Perform
8273
temporary allocations in POOL. */
8274
static svn_error_t *
8275
write_final_changed_path_info(apr_off_t *offset_p,
8281
apr_hash_t *changed_paths;
8283
apr_pool_t *iterpool = svn_pool_create(pool);
8284
fs_fs_data_t *ffd = fs->fsap_data;
8285
svn_boolean_t include_node_kinds =
8286
ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8287
apr_array_header_t *sorted_changed_paths;
8290
SVN_ERR(get_file_offset(&offset, file, pool));
8292
SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8293
/* For the sake of the repository administrator sort the changes so
8294
that the final file is deterministic and repeatable, however the
8295
rest of the FSFS code doesn't require any particular order here. */
8296
sorted_changed_paths = svn_sort__hash(changed_paths,
8297
svn_sort_compare_items_lexically, pool);
8299
/* Iterate through the changed paths one at a time, and convert the
8300
temporary node-id into a permanent one for each change entry. */
8301
for (i = 0; i < sorted_changed_paths->nelts; ++i)
8303
node_revision_t *noderev;
8304
const svn_fs_id_t *id;
8305
svn_fs_path_change2_t *change;
8308
svn_pool_clear(iterpool);
8310
change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8311
path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8313
id = change->node_rev_id;
8315
/* If this was a delete of a mutable node, then it is OK to
8316
leave the change entry pointing to the non-existent temporary
8317
node, since it will never be used. */
8318
if ((change->change_kind != svn_fs_path_change_delete) &&
8319
(! svn_fs_fs__id_txn_id(id)))
8321
SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8323
/* noderev has the permanent node-id at this point, so we just
8324
substitute it for the temporary one. */
8325
change->node_rev_id = noderev->id;
8328
/* Write out the new entry into the final rev-file. */
8329
SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8333
svn_pool_destroy(iterpool);
8337
return SVN_NO_ERROR;
8340
/* Atomically update the 'current' file to hold the specifed REV,
8341
NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
8342
ignored and may be NULL if the FS format does not use them.)
8343
Perform temporary allocations in POOL. */
8344
static svn_error_t *
8345
write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8346
const char *next_copy_id, apr_pool_t *pool)
8349
const char *tmp_name, *name;
8350
fs_fs_data_t *ffd = fs->fsap_data;
8352
/* Now we can just write out this line. */
8353
if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8354
buf = apr_psprintf(pool, "%ld\n", rev);
8356
buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8358
name = svn_fs_fs__path_current(fs, pool);
8359
SVN_ERR(svn_io_write_unique(&tmp_name,
8360
svn_dirent_dirname(name, pool),
8362
svn_io_file_del_none, pool));
8364
return move_into_place(tmp_name, name, name, pool);
8367
/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8368
youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8369
NEW_REV's revision root.
8371
Intended to be called as the very last step in a commit before 'current'
8372
is bumped. This implies that we are holding the write lock. */
8373
static svn_error_t *
8374
verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8375
svn_revnum_t new_rev,
8379
fs_fs_data_t *ffd = fs->fsap_data;
8380
svn_fs_t *ft; /* fs++ == ft */
8381
svn_fs_root_t *root;
8382
fs_fs_data_t *ft_ffd;
8383
apr_hash_t *fs_config;
8385
SVN_ERR_ASSERT(ffd->svn_fs_open_);
8387
/* make sure FT does not simply return data cached by other instances
8388
* but actually retrieves it from disk at least once.
8390
fs_config = apr_hash_make(pool);
8391
svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8392
svn_uuid_generate(pool));
8393
SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8396
ft_ffd = ft->fsap_data;
8397
/* Don't let FT consult rep-cache.db, either. */
8398
ft_ffd->rep_sharing_allowed = FALSE;
8401
ft_ffd->youngest_rev_cache = new_rev;
8403
SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8404
SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8405
SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8406
SVN_ERR(svn_fs_fs__verify_root(root, pool));
8407
#endif /* SVN_DEBUG */
8409
return SVN_NO_ERROR;
8412
/* Update the 'current' file to hold the correct next node and copy_ids
8413
from transaction TXN_ID in filesystem FS. The current revision is
8414
set to REV. Perform temporary allocations in POOL. */
8415
static svn_error_t *
8416
write_final_current(svn_fs_t *fs,
8419
const char *start_node_id,
8420
const char *start_copy_id,
8423
const char *txn_node_id, *txn_copy_id;
8424
char new_node_id[MAX_KEY_SIZE + 2];
8425
char new_copy_id[MAX_KEY_SIZE + 2];
8426
fs_fs_data_t *ffd = fs->fsap_data;
8428
if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8429
return write_current(fs, rev, NULL, NULL, pool);
8431
/* To find the next available ids, we add the id that used to be in
8432
the 'current' file, to the next ids from the transaction file. */
8433
SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8435
svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8436
svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8438
return write_current(fs, rev, new_node_id, new_copy_id, pool);
8441
/* Verify that the user registed with FS has all the locks necessary to
8442
permit all the changes associate with TXN_NAME.
8443
The FS write lock is assumed to be held by the caller. */
8444
static svn_error_t *
8445
verify_locks(svn_fs_t *fs,
8446
const char *txn_name,
8449
apr_pool_t *subpool = svn_pool_create(pool);
8450
apr_hash_t *changes;
8451
apr_hash_index_t *hi;
8452
apr_array_header_t *changed_paths;
8453
svn_stringbuf_t *last_recursed = NULL;
8456
/* Fetch the changes for this transaction. */
8457
SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8459
/* Make an array of the changed paths, and sort them depth-first-ily. */
8460
changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8461
sizeof(const char *));
8462
for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8463
APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8464
qsort(changed_paths->elts, changed_paths->nelts,
8465
changed_paths->elt_size, svn_sort_compare_paths);
8467
/* Now, traverse the array of changed paths, verify locks. Note
8468
that if we need to do a recursive verification a path, we'll skip
8469
over children of that path when we get to them. */
8470
for (i = 0; i < changed_paths->nelts; i++)
8473
svn_fs_path_change2_t *change;
8474
svn_boolean_t recurse = TRUE;
8476
svn_pool_clear(subpool);
8477
path = APR_ARRAY_IDX(changed_paths, i, const char *);
8479
/* If this path has already been verified as part of a recursive
8480
check of one of its parents, no need to do it again. */
8482
&& svn_dirent_is_child(last_recursed->data, path, subpool))
8485
/* Fetch the change associated with our path. */
8486
change = svn_hash_gets(changes, path);
8488
/* What does it mean to succeed at lock verification for a given
8489
path? For an existing file or directory getting modified
8490
(text, props), it means we hold the lock on the file or
8491
directory. For paths being added or removed, we need to hold
8492
the locks for that path and any children of that path.
8494
WHEW! We have no reliable way to determine the node kind
8495
of deleted items, but fortunately we are going to do a
8496
recursive check on deleted paths regardless of their kind. */
8497
if (change->change_kind == svn_fs_path_change_modify)
8499
SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8502
/* If we just did a recursive check, remember the path we
8503
checked (so children can be skipped). */
8506
if (! last_recursed)
8507
last_recursed = svn_stringbuf_create(path, pool);
8509
svn_stringbuf_set(last_recursed, path);
8512
svn_pool_destroy(subpool);
8513
return SVN_NO_ERROR;
8516
/* Baton used for commit_body below. */
8517
struct commit_baton {
8518
svn_revnum_t *new_rev_p;
8521
apr_array_header_t *reps_to_cache;
8522
apr_hash_t *reps_hash;
8523
apr_pool_t *reps_pool;
8526
/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8527
This implements the svn_fs_fs__with_write_lock() 'body' callback
8528
type. BATON is a 'struct commit_baton *'. */
8529
static svn_error_t *
8530
commit_body(void *baton, apr_pool_t *pool)
8532
struct commit_baton *cb = baton;
8533
fs_fs_data_t *ffd = cb->fs->fsap_data;
8534
const char *old_rev_filename, *rev_filename, *proto_filename;
8535
const char *revprop_filename, *final_revprop;
8536
const svn_fs_id_t *root_id, *new_root_id;
8537
const char *start_node_id = NULL, *start_copy_id = NULL;
8538
svn_revnum_t old_rev, new_rev;
8539
apr_file_t *proto_file;
8540
void *proto_file_lockcookie;
8541
apr_off_t initial_offset, changed_path_offset;
8543
apr_hash_t *txnprops;
8544
apr_array_header_t *txnprop_list;
8548
/* Get the current youngest revision. */
8549
SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8551
/* Check to make sure this transaction is based off the most recent
8553
if (cb->txn->base_rev != old_rev)
8554
return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8555
_("Transaction out of date"));
8557
/* Locks may have been added (or stolen) between the calling of
8558
previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8559
to re-examine every changed-path in the txn and re-verify all
8560
discovered locks. */
8561
SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8563
/* Get the next node_id and copy_id to use. */
8564
if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8565
SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8568
/* We are going to be one better than this puny old revision. */
8569
new_rev = old_rev + 1;
8571
/* Get a write handle on the proto revision file. */
8572
SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8573
cb->fs, cb->txn->id, pool));
8574
SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8576
/* Write out all the node-revisions and directory contents. */
8577
root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8578
SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8579
start_node_id, start_copy_id, initial_offset,
8580
cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8583
/* Write the changed-path information. */
8584
SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8585
cb->fs, cb->txn->id, pool));
8587
/* Write the final line. */
8588
buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8589
svn_fs_fs__id_offset(new_root_id),
8590
changed_path_offset);
8591
SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8593
SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8594
SVN_ERR(svn_io_file_close(proto_file, pool));
8596
/* We don't unlock the prototype revision file immediately to avoid a
8597
race with another caller writing to the prototype revision file
8598
before we commit it. */
8600
/* Remove any temporary txn props representing 'flags'. */
8601
SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8602
txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8605
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8607
prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8608
APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8611
if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8613
prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8614
APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8617
if (! apr_is_empty_array(txnprop_list))
8618
SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8620
/* Create the shard for the rev and revprop file, if we're sharding and
8621
this is the first revision of a new shard. We don't care if this
8622
fails because the shard already existed for some reason. */
8623
if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8625
/* Create the revs shard. */
8627
const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8628
svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8629
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8630
return svn_error_trace(err);
8631
svn_error_clear(err);
8632
SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8638
/* Create the revprops shard. */
8639
SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8641
const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8642
svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8643
if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8644
return svn_error_trace(err);
8645
svn_error_clear(err);
8646
SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8653
/* Move the finished rev file into place. */
8654
SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8655
cb->fs, old_rev, pool));
8656
rev_filename = path_rev(cb->fs, new_rev, pool);
8657
proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8658
SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8661
/* Now that we've moved the prototype revision file out of the way,
8662
we can unlock it (since further attempts to write to the file
8663
will fail as it no longer exists). We must do this so that we can
8664
remove the transaction directory later. */
8665
SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8667
/* Update commit time to ensure that svn:date revprops remain ordered. */
8668
date.data = svn_time_to_cstring(apr_time_now(), pool);
8669
date.len = strlen(date.data);
8671
SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8674
/* Move the revprops file into place. */
8675
SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8676
revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8677
final_revprop = path_revprops(cb->fs, new_rev, pool);
8678
SVN_ERR(move_into_place(revprop_filename, final_revprop,
8679
old_rev_filename, pool));
8681
/* Update the 'current' file. */
8682
SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8683
SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8684
start_copy_id, pool));
8686
/* At this point the new revision is committed and globally visible
8687
so let the caller know it succeeded by giving it the new revision
8688
number, which fulfills svn_fs_commit_txn() contract. Any errors
8689
after this point do not change the fact that a new revision was
8691
*cb->new_rev_p = new_rev;
8693
ffd->youngest_rev_cache = new_rev;
8695
/* Remove this transaction directory. */
8696
SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8698
return SVN_NO_ERROR;
8701
/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8702
* to the rep-cache database of FS. */
8703
static svn_error_t *
8704
write_reps_to_cache(svn_fs_t *fs,
8705
const apr_array_header_t *reps_to_cache,
1590
return apr_pmemdup(pool, rep, sizeof(*rep));
1594
/* Write out the zeroth revision for filesystem FS.
1595
Perform temporary allocations in SCRATCH_POOL. */
1596
static svn_error_t *
1597
write_revision_zero(svn_fs_t *fs,
8706
1598
apr_pool_t *scratch_pool)
8710
for (i = 0; i < reps_to_cache->nelts; i++)
8712
representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8714
/* FALSE because we don't care if another parallel commit happened to
8715
* collide with us. (Non-parallel collisions will not be detected.) */
8716
SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8719
return SVN_NO_ERROR;
8723
svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8728
struct commit_baton cb;
8729
fs_fs_data_t *ffd = fs->fsap_data;
8731
cb.new_rev_p = new_rev_p;
8735
if (ffd->rep_sharing_allowed)
8737
cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8738
cb.reps_hash = apr_hash_make(pool);
8739
cb.reps_pool = pool;
8743
cb.reps_to_cache = NULL;
8744
cb.reps_hash = NULL;
8745
cb.reps_pool = NULL;
8748
SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8750
/* At this point, *NEW_REV_P has been set, so errors below won't affect
8751
the success of the commit. (See svn_fs_commit_txn().) */
8753
if (ffd->rep_sharing_allowed)
8755
SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8757
/* Write new entries to the rep-sharing database.
8759
* We use an sqlite transaction to speed things up;
8760
* see <http://www.sqlite.org/faq.html#q19>.
8762
SVN_SQLITE__WITH_TXN(
8763
write_reps_to_cache(fs, cb.reps_to_cache, pool),
8767
return SVN_NO_ERROR;
8772
svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8777
const char *cur_node_id, *cur_copy_id;
8781
/* First read in the current next-ids file. */
8782
SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8784
copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8786
len = strlen(cur_copy_id);
8787
svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8789
SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8791
*copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8793
return SVN_NO_ERROR;
8796
/* Write out the zeroth revision for filesystem FS. */
8797
static svn_error_t *
8798
write_revision_zero(svn_fs_t *fs)
8800
const char *path_revision_zero = path_rev(fs, 0, fs->pool);
1600
/* Use an explicit sub-pool to have full control over temp file lifetimes.
1601
* Since we have it, use it for everything else as well. */
1602
apr_pool_t *subpool = svn_pool_create(scratch_pool);
1603
const char *path_revision_zero = svn_fs_fs__path_rev(fs, 0, subpool);
8801
1604
apr_hash_t *proplist;
8802
1605
svn_string_t date;
8804
1607
/* Write out a rev file for revision 0. */
8805
SVN_ERR(svn_io_file_create(path_revision_zero,
8806
"PLAIN\nEND\nENDREP\n"
8811
"2d2977d1c96f487abe4a1e202dd03b4e\n"
8813
"\n\n17 107\n", fs->pool));
8814
SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
1608
if (svn_fs_fs__use_log_addressing(fs))
1610
apr_array_header_t *index_entries;
1611
svn_fs_fs__p2l_entry_t *entry;
1612
svn_fs_fs__revision_file_t *rev_file;
1613
const char *l2p_proto_index, *p2l_proto_index;
1615
/* Write a skeleton r0 with no indexes. */
1616
SVN_ERR(svn_io_file_create(path_revision_zero,
1617
"PLAIN\nEND\nENDREP\n"
1622
"2d2977d1c96f487abe4a1e202dd03b4e\n"
1626
/* Construct the index P2L contents: describe the 3 items we have.
1627
Be sure to create them in on-disk order. */
1628
index_entries = apr_array_make(subpool, 3, sizeof(entry));
1630
entry = apr_pcalloc(subpool, sizeof(*entry));
1633
entry->type = SVN_FS_FS__ITEM_TYPE_DIR_REP;
1634
entry->item.revision = 0;
1635
entry->item.number = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1636
APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1638
entry = apr_pcalloc(subpool, sizeof(*entry));
1641
entry->type = SVN_FS_FS__ITEM_TYPE_NODEREV;
1642
entry->item.revision = 0;
1643
entry->item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
1644
APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1646
entry = apr_pcalloc(subpool, sizeof(*entry));
1647
entry->offset = 106;
1649
entry->type = SVN_FS_FS__ITEM_TYPE_CHANGES;
1650
entry->item.revision = 0;
1651
entry->item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
1652
APR_ARRAY_PUSH(index_entries, svn_fs_fs__p2l_entry_t *) = entry;
1654
/* Now re-open r0, create proto-index files from our entries and
1655
rewrite the index section of r0. */
1656
SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, 0,
1658
SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
1659
rev_file, index_entries,
1661
SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
1664
SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
1665
p2l_proto_index, 0, subpool));
1666
SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
1669
SVN_ERR(svn_io_file_create(path_revision_zero,
1670
"PLAIN\nEND\nENDREP\n"
1675
"2d2977d1c96f487abe4a1e202dd03b4e\n"
1677
"\n\n17 107\n", subpool));
1679
SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, subpool));
8816
1681
/* Set a date on revision 0. */
8817
date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
1682
date.data = svn_time_to_cstring(apr_time_now(), subpool);
8818
1683
date.len = strlen(date.data);
8819
proplist = apr_hash_make(fs->pool);
1684
proplist = apr_hash_make(subpool);
8820
1685
svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8821
return set_revision_proplist(fs, 0, proplist, fs->pool);
1686
SVN_ERR(svn_fs_fs__set_revision_proplist(fs, 0, proplist, subpool));
1688
svn_pool_destroy(subpool);
1689
return SVN_NO_ERROR;
8825
svn_fs_fs__create(svn_fs_t *fs,
1693
svn_fs_fs__create_file_tree(svn_fs_t *fs,
1697
svn_boolean_t use_log_addressing,
8829
int format = SVN_FS_FS__FORMAT_NUMBER;
8830
1700
fs_fs_data_t *ffd = fs->fsap_data;
8832
fs->path = apr_pstrdup(pool, path);
8833
/* See if compatibility with older versions was explicitly requested. */
8836
if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8838
else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8840
else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8842
else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
1702
fs->path = apr_pstrdup(fs->pool, path);
8845
1703
ffd->format = format;
8847
/* Override the default linear layout if this is a new-enough format. */
1705
/* Use an appropriate sharding mode if supported by the format. */
8848
1706
if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8849
ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
1707
ffd->max_files_per_dir = shard_size;
1709
ffd->max_files_per_dir = 0;
1711
/* Select the addressing mode depending on the format. */
1712
if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT)
1713
ffd->use_log_addressing = use_log_addressing;
1715
ffd->use_log_addressing = FALSE;
8851
1717
/* Create the revision data directories. */
8852
1718
if (ffd->max_files_per_dir)
8853
SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
1719
SVN_ERR(svn_io_make_dir_recursively(svn_fs_fs__path_rev_shard(fs, 0,
8855
1723
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
9804
2147
return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9809
/*** Transactions ***/
9812
svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9813
const svn_fs_id_t **base_root_id_p,
2152
svn_fs_fs__info_format(int *fs_format,
2153
svn_version_t **supports_version,
9815
const char *txn_name,
9819
SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9820
*root_id_p = txn->root_id;
9821
*base_root_id_p = txn->base_id;
9822
return SVN_NO_ERROR;
9826
/* Generic transaction operations. */
9829
svn_fs_fs__txn_prop(svn_string_t **value_p,
9831
const char *propname,
9835
svn_fs_t *fs = txn->fs;
9837
SVN_ERR(svn_fs__check_fs(fs, TRUE));
9838
SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9840
*value_p = svn_hash_gets(table, propname);
9842
return SVN_NO_ERROR;
9846
svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9854
apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9856
SVN_ERR(svn_fs__check_fs(fs, TRUE));
9858
SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9860
/* Put a datestamp on the newly created txn, so we always know
9861
exactly how old it is. (This will help sysadmins identify
9862
long-abandoned txns that may need to be manually removed.) When
9863
a txn is promoted to a revision, this property will be
9864
automatically overwritten with a revision datestamp. */
9865
date.data = svn_time_to_cstring(apr_time_now(), pool);
9866
date.len = strlen(date.data);
9868
prop.name = SVN_PROP_REVISION_DATE;
9870
APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9872
/* Set temporary txn props that represent the requested 'flags'
9874
if (flags & SVN_FS_TXN_CHECK_OOD)
9876
prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9877
prop.value = svn_string_create("true", pool);
9878
APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9881
if (flags & SVN_FS_TXN_CHECK_LOCKS)
9883
prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9884
prop.value = svn_string_create("true", pool);
9885
APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9888
return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9892
/****** Packing FSFS shards *********/
9894
/* Write a file FILENAME in directory FS_PATH, containing a single line
9895
* with the number REVNUM in ASCII decimal. Move the file into place
9896
* atomically, overwriting any existing file.
9898
* Similar to write_current(). */
9899
static svn_error_t *
9900
write_revnum_file(const char *fs_path,
9901
const char *filename,
9902
svn_revnum_t revnum,
9903
apr_pool_t *scratch_pool)
9905
const char *final_path, *tmp_path;
9906
svn_stream_t *tmp_stream;
9908
final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9909
SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9910
svn_io_file_del_none,
9911
scratch_pool, scratch_pool));
9912
SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9913
SVN_ERR(svn_stream_close(tmp_stream));
9914
SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9915
return SVN_NO_ERROR;
9918
/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9919
* from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9920
* CANCEL_FUNC and CANCEL_BATON are what you think they are.
9922
* If for some reason we detect a partial packing already performed, we
9923
* remove the pack file and start again.
9925
static svn_error_t *
9926
pack_rev_shard(const char *pack_file_dir,
9927
const char *shard_path,
9929
int max_files_per_dir,
9930
svn_cancel_func_t cancel_func,
9934
const char *pack_file_path, *manifest_file_path;
9935
svn_stream_t *pack_stream, *manifest_stream;
9936
svn_revnum_t start_rev, end_rev, rev;
9937
apr_off_t next_offset;
9938
apr_pool_t *iterpool;
9940
/* Some useful paths. */
9941
pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9942
manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9944
/* Remove any existing pack file for this shard, since it is incomplete. */
9945
SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9948
/* Create the new directory and pack and manifest files. */
9949
SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9950
SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9952
SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9955
start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9956
end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9958
iterpool = svn_pool_create(pool);
9960
/* Iterate over the revisions in this shard, squashing them together. */
9961
for (rev = start_rev; rev <= end_rev; rev++)
9963
svn_stream_t *rev_stream;
9967
svn_pool_clear(iterpool);
9969
/* Get the size of the file. */
9970
path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9972
SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9974
/* Update the manifest. */
9975
SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9976
"\n", next_offset));
9977
next_offset += finfo.size;
9979
/* Copy all the bits from the rev file to the end of the pack file. */
9980
SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9981
SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9983
cancel_func, cancel_baton, iterpool));
9986
SVN_ERR(svn_stream_close(manifest_stream));
9987
SVN_ERR(svn_stream_close(pack_stream));
9988
SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
9989
SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
9990
SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
9992
svn_pool_destroy(iterpool);
9994
return SVN_NO_ERROR;
9997
/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
9998
* to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
10000
* The file sizes have already been determined and written to SIZES.
10001
* Please note that this function will be executed while the filesystem
10002
* has been locked and that revprops files will therefore not be modified
10003
* while the pack is in progress.
10005
* COMPRESSION_LEVEL defines how well the resulting pack file shall be
10006
* compressed or whether is shall be compressed at all. TOTAL_SIZE is
10007
* a hint on which initial buffer size we should use to hold the pack file
10010
* CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
10011
* are done in SCRATCH_POOL.
10013
static svn_error_t *
10014
copy_revprops(const char *pack_file_dir,
10015
const char *pack_filename,
10016
const char *shard_path,
10017
svn_revnum_t start_rev,
10018
svn_revnum_t end_rev,
10019
apr_array_header_t *sizes,
10020
apr_size_t total_size,
10021
int compression_level,
10022
svn_cancel_func_t cancel_func,
10023
void *cancel_baton,
10024
apr_pool_t *scratch_pool)
10026
svn_stream_t *pack_stream;
10027
apr_file_t *pack_file;
10029
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10030
svn_stream_t *stream;
10032
/* create empty data buffer and a write stream on top of it */
10033
svn_stringbuf_t *uncompressed
10034
= svn_stringbuf_create_ensure(total_size, scratch_pool);
10035
svn_stringbuf_t *compressed
10036
= svn_stringbuf_create_empty(scratch_pool);
10037
pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10039
/* write the pack file header */
10040
SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10041
sizes->nelts, iterpool));
10043
/* Some useful paths. */
10044
SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10047
APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10050
/* Iterate over the revisions in this shard, squashing them together. */
10051
for (rev = start_rev; rev <= end_rev; rev++)
10055
svn_pool_clear(iterpool);
10057
/* Construct the file name. */
10058
path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10061
/* Copy all the bits from the non-packed revprop file to the end of
10062
* the pack file. */
10063
SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10064
SVN_ERR(svn_stream_copy3(stream, pack_stream,
10065
cancel_func, cancel_baton, iterpool));
10068
/* flush stream buffers to content buffer */
10069
SVN_ERR(svn_stream_close(pack_stream));
10071
/* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10072
SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10073
compressed, compression_level));
10075
/* write the pack file content to disk */
10076
stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10077
SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10078
SVN_ERR(svn_stream_close(stream));
10080
svn_pool_destroy(iterpool);
10082
return SVN_NO_ERROR;
10085
/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10086
* revprop files in it, create a packed shared at PACK_FILE_DIR.
10088
* COMPRESSION_LEVEL defines how well the resulting pack file shall be
10089
* compressed or whether is shall be compressed at all. Individual pack
10090
* file containing more than one revision will be limited to a size of
10091
* MAX_PACK_SIZE bytes before compression.
10093
* CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
10094
* allocations are done in SCRATCH_POOL.
10096
static svn_error_t *
10097
pack_revprops_shard(const char *pack_file_dir,
10098
const char *shard_path,
10100
int max_files_per_dir,
10101
apr_off_t max_pack_size,
10102
int compression_level,
10103
svn_cancel_func_t cancel_func,
10104
void *cancel_baton,
10105
apr_pool_t *scratch_pool)
10107
const char *manifest_file_path, *pack_filename = NULL;
10108
svn_stream_t *manifest_stream;
10109
svn_revnum_t start_rev, end_rev, rev;
10110
apr_off_t total_size;
10111
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10112
apr_array_header_t *sizes;
10114
/* Some useful paths. */
10115
manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10118
/* Remove any existing pack file for this shard, since it is incomplete. */
10119
SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10122
/* Create the new directory and manifest file stream. */
10123
SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10124
SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10125
scratch_pool, scratch_pool));
10127
/* revisions to handle. Special case: revision 0 */
10128
start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10129
end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10130
if (start_rev == 0)
10133
/* initialize the revprop size info */
10134
sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10135
total_size = 2 * SVN_INT64_BUFFER_SIZE;
10137
/* Iterate over the revisions in this shard, determine their size and
10138
* squashing them together into pack files. */
10139
for (rev = start_rev; rev <= end_rev; rev++)
10144
svn_pool_clear(iterpool);
10146
/* Get the size of the file. */
10147
path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10149
SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10151
/* if we already have started a pack file and this revprop cannot be
10152
* appended to it, write the previous pack file. */
10153
if (sizes->nelts != 0 &&
10154
total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10156
SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10157
start_rev, rev-1, sizes, (apr_size_t)total_size,
10158
compression_level, cancel_func, cancel_baton,
10161
/* next pack file starts empty again */
10162
apr_array_clear(sizes);
10163
total_size = 2 * SVN_INT64_BUFFER_SIZE;
10167
/* Update the manifest. Allocate a file name for the current pack
10168
* file if it is a new one */
10169
if (sizes->nelts == 0)
10170
pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10172
SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10175
/* add to list of files to put into the current pack file */
10176
APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10177
total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10180
/* write the last pack file */
10181
if (sizes->nelts != 0)
10182
SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10183
start_rev, rev-1, sizes, (apr_size_t)total_size,
10184
compression_level, cancel_func, cancel_baton,
10187
/* flush the manifest file and update permissions */
10188
SVN_ERR(svn_stream_close(manifest_stream));
10189
SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10191
svn_pool_destroy(iterpool);
10193
return SVN_NO_ERROR;
10196
/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10197
* MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the
10198
* revprop file for revision 0.
10200
* CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
10201
* allocations are done in SCRATCH_POOL.
10203
static svn_error_t *
10204
delete_revprops_shard(const char *shard_path,
10206
int max_files_per_dir,
10207
svn_cancel_func_t cancel_func,
10208
void *cancel_baton,
10209
apr_pool_t *scratch_pool)
10213
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10216
/* delete all files except the one for revision 0 */
10217
for (i = 1; i < max_files_per_dir; ++i)
10219
const char *path = svn_dirent_join(shard_path,
10220
apr_psprintf(iterpool, "%d", i),
10223
SVN_ERR((*cancel_func)(cancel_baton));
10225
SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10226
svn_pool_clear(iterpool);
10229
svn_pool_destroy(iterpool);
10232
SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10233
cancel_func, cancel_baton, scratch_pool));
10235
return SVN_NO_ERROR;
10238
/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10239
* REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10240
* for allocations. REVPROPS_DIR will be NULL if revprop packing is not
10241
* supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10244
* CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10245
* NOTIFY_FUNC and NOTIFY_BATON.
10247
* If for some reason we detect a partial packing already performed, we
10248
* remove the pack file and start again.
10250
static svn_error_t *
10251
pack_shard(const char *revs_dir,
10252
const char *revsprops_dir,
10253
const char *fs_path,
10255
int max_files_per_dir,
10256
apr_off_t max_pack_size,
10257
int compression_level,
10258
svn_fs_pack_notify_t notify_func,
10259
void *notify_baton,
10260
svn_cancel_func_t cancel_func,
10261
void *cancel_baton,
10264
const char *rev_shard_path, *rev_pack_file_dir;
10265
const char *revprops_shard_path, *revprops_pack_file_dir;
10267
/* Notify caller we're starting to pack this shard. */
10269
SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10272
/* Some useful paths. */
10273
rev_pack_file_dir = svn_dirent_join(revs_dir,
10275
"%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10278
rev_shard_path = svn_dirent_join(revs_dir,
10279
apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10282
/* pack the revision content */
10283
SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10284
shard, max_files_per_dir,
10285
cancel_func, cancel_baton, pool));
10287
/* if enabled, pack the revprops in an equivalent way */
10290
revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10292
"%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10295
revprops_shard_path = svn_dirent_join(revsprops_dir,
10296
apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10299
SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10300
shard, max_files_per_dir,
10301
(int)(0.9 * max_pack_size),
10303
cancel_func, cancel_baton, pool));
10306
/* Update the min-unpacked-rev file to reflect our newly packed shard.
10307
* (This doesn't update ffd->min_unpacked_rev. That will be updated by
10308
* update_min_unpacked_rev() when necessary.) */
10309
SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10310
(svn_revnum_t)((shard + 1) * max_files_per_dir),
10313
/* Finally, remove the existing shard directories. */
10314
SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10315
cancel_func, cancel_baton, pool));
10317
SVN_ERR(delete_revprops_shard(revprops_shard_path,
10318
shard, max_files_per_dir,
10319
cancel_func, cancel_baton, pool));
10321
/* Notify caller we're starting to pack this shard. */
10323
SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10326
return SVN_NO_ERROR;
10332
svn_fs_pack_notify_t notify_func;
10333
void *notify_baton;
10334
svn_cancel_func_t cancel_func;
10335
void *cancel_baton;
10339
/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10340
This implements the svn_fs_fs__with_write_lock() 'body' callback
10341
type. BATON is a 'struct pack_baton *'.
10343
WARNING: if you add a call to this function, please note:
10344
The code currently assumes that any piece of code running with
10345
the write-lock set can rely on the ffd->min_unpacked_rev and
10346
ffd->min_unpacked_revprop caches to be up-to-date (and, by
10347
extension, on not having to use a retry when calling
10348
svn_fs_fs__path_rev_absolute() and friends). If you add a call
10349
to this function, consider whether you have to call
10350
update_min_unpacked_rev().
10351
See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10353
static svn_error_t *
10354
pack_body(void *baton,
10357
struct pack_baton *pb = baton;
10358
fs_fs_data_t ffd = {0};
10359
apr_int64_t completed_shards;
10361
svn_revnum_t youngest;
10362
apr_pool_t *iterpool;
10363
const char *rev_data_path;
10364
const char *revprops_data_path = NULL;
10366
/* read repository settings */
10367
SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10368
path_format(pb->fs, pool), pool));
10369
SVN_ERR(check_format(ffd.format));
10370
SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10372
/* If the repository isn't a new enough format, we don't support packing.
10373
Return a friendly error to that effect. */
10374
if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10375
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10376
_("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10379
/* If we aren't using sharding, we can't do any packing, so quit. */
10380
if (!ffd.max_files_per_dir)
10381
return SVN_NO_ERROR;
10383
SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10384
path_min_unpacked_rev(pb->fs, pool),
10387
SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10388
completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10390
/* See if we've already completed all possible shards thus far. */
10391
if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10392
return SVN_NO_ERROR;
10394
rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10395
if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10396
revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10399
iterpool = svn_pool_create(pool);
10400
for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10401
i < completed_shards;
10404
svn_pool_clear(iterpool);
10406
if (pb->cancel_func)
10407
SVN_ERR(pb->cancel_func(pb->cancel_baton));
10409
SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10410
pb->fs->path, i, ffd.max_files_per_dir,
10411
ffd.revprop_pack_size,
10412
ffd.compress_packed_revprops
10413
? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10414
: SVN_DELTA_COMPRESSION_LEVEL_NONE,
10415
pb->notify_func, pb->notify_baton,
10416
pb->cancel_func, pb->cancel_baton, iterpool));
10419
svn_pool_destroy(iterpool);
10420
return SVN_NO_ERROR;
10424
svn_fs_fs__pack(svn_fs_t *fs,
10425
svn_fs_pack_notify_t notify_func,
10426
void *notify_baton,
10427
svn_cancel_func_t cancel_func,
10428
void *cancel_baton,
10431
struct pack_baton pb = { 0 };
10433
pb.notify_func = notify_func;
10434
pb.notify_baton = notify_baton;
10435
pb.cancel_func = cancel_func;
10436
pb.cancel_baton = cancel_baton;
10437
return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10443
/* Baton type expected by verify_walker(). The purpose is to reuse open
10444
* rev / pack file handles between calls. Its contents need to be cleaned
10445
* periodically to limit resource usage.
10447
typedef struct verify_walker_baton_t
10449
/* number of calls to verify_walker() since the last clean */
10450
int iteration_count;
10452
/* number of files opened since the last clean */
10455
/* progress notification callback to invoke periodically (may be NULL) */
10456
svn_fs_progress_notify_func_t notify_func;
10458
/* baton to use with NOTIFY_FUNC */
10459
void *notify_baton;
10461
/* remember the last revision for which we called notify_func */
10462
svn_revnum_t last_notified_revision;
10464
/* current file handle (or NULL) */
10465
apr_file_t *file_hint;
10467
/* corresponding revision (or SVN_INVALID_REVNUM) */
10468
svn_revnum_t rev_hint;
10470
/* pool to use for the file handles etc. */
10472
} verify_walker_baton_t;
10474
/* Used by svn_fs_fs__verify().
10475
Implements svn_fs_fs__walk_rep_reference().walker. */
10476
static svn_error_t *
10477
verify_walker(representation_t *rep,
10480
apr_pool_t *scratch_pool)
10482
struct rep_state *rs;
10483
struct rep_args *rep_args;
10487
verify_walker_baton_t *walker_baton = baton;
10488
apr_file_t * previous_file;
10490
/* notify and free resources periodically */
10491
if ( walker_baton->iteration_count > 1000
10492
|| walker_baton->file_count > 16)
10494
if ( walker_baton->notify_func
10495
&& rep->revision != walker_baton->last_notified_revision)
10497
walker_baton->notify_func(rep->revision,
10498
walker_baton->notify_baton,
10500
walker_baton->last_notified_revision = rep->revision;
10503
svn_pool_clear(walker_baton->pool);
10505
walker_baton->iteration_count = 0;
10506
walker_baton->file_count = 0;
10507
walker_baton->file_hint = NULL;
10508
walker_baton->rev_hint = SVN_INVALID_REVNUM;
10511
/* access the repo data */
10512
previous_file = walker_baton->file_hint;
10513
SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10514
&walker_baton->rev_hint, rep, fs,
10515
walker_baton->pool));
10517
/* update resource usage counters */
10518
walker_baton->iteration_count++;
10519
if (previous_file != walker_baton->file_hint)
10520
walker_baton->file_count++;
10524
/* ### Should this be using read_rep_line() directly? */
10525
SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10529
return SVN_NO_ERROR;
10533
svn_fs_fs__verify(svn_fs_t *fs,
10534
svn_revnum_t start,
10536
svn_fs_progress_notify_func_t notify_func,
10537
void *notify_baton,
10538
svn_cancel_func_t cancel_func,
10539
void *cancel_baton,
10542
fs_fs_data_t *ffd = fs->fsap_data;
10543
svn_boolean_t exists;
10544
svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10546
if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10547
return SVN_NO_ERROR;
10549
/* Input validation. */
10550
if (! SVN_IS_VALID_REVNUM(start))
10552
if (! SVN_IS_VALID_REVNUM(end))
10554
SVN_ERR(ensure_revision_exists(fs, start, pool));
10555
SVN_ERR(ensure_revision_exists(fs, end, pool));
10557
/* rep-cache verification. */
10558
SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10561
/* provide a baton to allow the reuse of open file handles between
10562
iterations (saves 2/3 of OS level file operations). */
10563
verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10564
baton->rev_hint = SVN_INVALID_REVNUM;
10565
baton->pool = svn_pool_create(pool);
10566
baton->last_notified_revision = SVN_INVALID_REVNUM;
10567
baton->notify_func = notify_func;
10568
baton->notify_baton = notify_baton;
10570
/* tell the user that we are now ready to do *something* */
10572
notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10574
/* Do not attempt to walk the rep-cache database if its file does
10575
not exist, since doing so would create it --- which may confuse
10576
the administrator. Don't take any lock. */
10577
SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10578
verify_walker, baton,
10579
cancel_func, cancel_baton,
10582
/* walker resource cleanup */
10583
svn_pool_destroy(baton->pool);
10586
return SVN_NO_ERROR;
10592
/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10593
* the destination and do not differ in terms of kind, size, and mtime. */
10594
static svn_error_t *
10595
hotcopy_io_dir_file_copy(const char *src_path,
10596
const char *dst_path,
10598
apr_pool_t *scratch_pool)
10600
const svn_io_dirent2_t *src_dirent;
10601
const svn_io_dirent2_t *dst_dirent;
10602
const char *src_target;
10603
const char *dst_target;
10605
/* Does the destination already exist? If not, we must copy it. */
10606
dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10607
SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10608
scratch_pool, scratch_pool));
10609
if (dst_dirent->kind != svn_node_none)
10611
/* If the destination's stat information indicates that the file
10612
* is equal to the source, don't bother copying the file again. */
10613
src_target = svn_dirent_join(src_path, file, scratch_pool);
10614
SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10615
scratch_pool, scratch_pool));
10616
if (src_dirent->kind == dst_dirent->kind &&
10617
src_dirent->special == dst_dirent->special &&
10618
src_dirent->filesize == dst_dirent->filesize &&
10619
src_dirent->mtime <= dst_dirent->mtime)
10620
return SVN_NO_ERROR;
10623
return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10627
/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10628
* NAME is in the internal encoding used by APR; PARENT is in
10629
* UTF-8 and in internal (not local) style.
10631
* Use PARENT only for generating an error string if the conversion
10632
* fails because NAME could not be represented in UTF-8. In that
10633
* case, return a two-level error in which the outer error's message
10634
* mentions PARENT, but the inner error's message does not mention
10635
* NAME (except possibly in hex) since NAME may not be printable.
10636
* Such a compound error at least allows the user to go looking in the
10637
* right directory for the problem.
10639
* If there is any other error, just return that error directly.
10641
* If there is any error, the effect on *NAME_P is undefined.
10643
* *NAME_P and NAME may refer to the same storage.
10645
static svn_error_t *
10646
entry_name_to_utf8(const char **name_p,
10648
const char *parent,
10651
svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10652
if (err && err->apr_err == APR_EINVAL)
10654
return svn_error_createf(err->apr_err, err,
10655
_("Error converting entry "
10656
"in directory '%s' to UTF-8"),
10657
svn_dirent_local_style(parent, pool));
10662
/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10663
* exist in the destination and do not differ from the source in terms of
10664
* kind, size, and mtime. */
10665
static svn_error_t *
10666
hotcopy_io_copy_dir_recursively(const char *src,
10667
const char *dst_parent,
10668
const char *dst_basename,
10669
svn_boolean_t copy_perms,
10670
svn_cancel_func_t cancel_func,
10671
void *cancel_baton,
10674
svn_node_kind_t kind;
10675
apr_status_t status;
10676
const char *dst_path;
10677
apr_dir_t *this_dir;
10678
apr_finfo_t this_entry;
10679
apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10681
/* Make a subpool for recursion */
10682
apr_pool_t *subpool = svn_pool_create(pool);
10684
/* The 'dst_path' is simply dst_parent/dst_basename */
10685
dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10687
/* Sanity checks: SRC and DST_PARENT are directories, and
10688
DST_BASENAME doesn't already exist in DST_PARENT. */
10689
SVN_ERR(svn_io_check_path(src, &kind, subpool));
10690
if (kind != svn_node_dir)
10691
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10692
_("Source '%s' is not a directory"),
10693
svn_dirent_local_style(src, pool));
10695
SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10696
if (kind != svn_node_dir)
10697
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10698
_("Destination '%s' is not a directory"),
10699
svn_dirent_local_style(dst_parent, pool));
10701
SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10703
/* Create the new directory. */
10704
/* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10705
SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10707
/* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
10708
SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10710
for (status = apr_dir_read(&this_entry, flags, this_dir);
10711
status == APR_SUCCESS;
10712
status = apr_dir_read(&this_entry, flags, this_dir))
10714
if ((this_entry.name[0] == '.')
10715
&& ((this_entry.name[1] == '\0')
10716
|| ((this_entry.name[1] == '.')
10717
&& (this_entry.name[2] == '\0'))))
10723
const char *entryname_utf8;
10726
SVN_ERR(cancel_func(cancel_baton));
10728
SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10730
if (this_entry.filetype == APR_REG) /* regular file */
10732
SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10735
else if (this_entry.filetype == APR_LNK) /* symlink */
10737
const char *src_target = svn_dirent_join(src, entryname_utf8,
10739
const char *dst_target = svn_dirent_join(dst_path,
10742
SVN_ERR(svn_io_copy_link(src_target, dst_target,
10745
else if (this_entry.filetype == APR_DIR) /* recurse */
10747
const char *src_target;
10749
/* Prevent infinite recursion by filtering off our
10750
newly created destination path. */
10751
if (strcmp(src, dst_parent) == 0
10752
&& strcmp(entryname_utf8, dst_basename) == 0)
10755
src_target = svn_dirent_join(src, entryname_utf8, subpool);
10756
SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10764
/* ### support other APR node types someday?? */
10769
if (! (APR_STATUS_IS_ENOENT(status)))
10770
return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10771
svn_dirent_local_style(src, pool));
10773
status = apr_dir_close(this_dir);
10775
return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10776
svn_dirent_local_style(src, pool));
10778
/* Free any memory used by recursion */
10779
svn_pool_destroy(subpool);
10781
return SVN_NO_ERROR;
10784
/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10785
* to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10786
* Use SCRATCH_POOL for temporary allocations. */
10787
static svn_error_t *
10788
hotcopy_copy_shard_file(const char *src_subdir,
10789
const char *dst_subdir,
10791
int max_files_per_dir,
10792
apr_pool_t *scratch_pool)
10794
const char *src_subdir_shard = src_subdir,
10795
*dst_subdir_shard = dst_subdir;
10797
if (max_files_per_dir)
10799
const char *shard = apr_psprintf(scratch_pool, "%ld",
10800
rev / max_files_per_dir);
10801
src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10802
dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10804
if (rev % max_files_per_dir == 0)
10806
SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10807
SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10812
SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10813
apr_psprintf(scratch_pool, "%ld", rev),
10815
return SVN_NO_ERROR;
10819
/* Copy a packed shard containing revision REV, and which contains
10820
* MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10821
* Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10822
* Do not re-copy data which already exists in DST_FS.
10823
* Use SCRATCH_POOL for temporary allocations. */
10824
static svn_error_t *
10825
hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10829
int max_files_per_dir,
10830
apr_pool_t *scratch_pool)
10832
const char *src_subdir;
10833
const char *dst_subdir;
10834
const char *packed_shard;
10835
const char *src_subdir_packed_shard;
10836
svn_revnum_t revprop_rev;
10837
apr_pool_t *iterpool;
10838
fs_fs_data_t *src_ffd = src_fs->fsap_data;
10840
/* Copy the packed shard. */
10841
src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10842
dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10843
packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10844
rev / max_files_per_dir);
10845
src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10847
SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10848
dst_subdir, packed_shard,
10849
TRUE /* copy_perms */,
10850
NULL /* cancel_func */, NULL,
10853
/* Copy revprops belonging to revisions in this pack. */
10854
src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10855
dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10857
if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10858
|| src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10860
/* copy unpacked revprops rev by rev */
10861
iterpool = svn_pool_create(scratch_pool);
10862
for (revprop_rev = rev;
10863
revprop_rev < rev + max_files_per_dir;
10866
svn_pool_clear(iterpool);
10868
SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10869
revprop_rev, max_files_per_dir,
10872
svn_pool_destroy(iterpool);
10876
/* revprop for revision 0 will never be packed */
10878
SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10879
0, max_files_per_dir,
10882
/* packed revprops folder */
10883
packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10884
rev / max_files_per_dir);
10885
src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10887
SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10888
dst_subdir, packed_shard,
10889
TRUE /* copy_perms */,
10890
NULL /* cancel_func */, NULL,
10894
/* If necessary, update the min-unpacked rev file in the hotcopy. */
10895
if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10897
*dst_min_unpacked_rev = rev + max_files_per_dir;
10898
SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10899
*dst_min_unpacked_rev,
10903
return SVN_NO_ERROR;
10906
/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10907
* file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10908
* Use SCRATCH_POOL for temporary allocations. */
10909
static svn_error_t *
10910
hotcopy_update_current(svn_revnum_t *dst_youngest,
10912
svn_revnum_t new_youngest,
2155
apr_pool_t *result_pool,
10913
2156
apr_pool_t *scratch_pool)
10915
char next_node_id[MAX_KEY_SIZE] = "0";
10916
char next_copy_id[MAX_KEY_SIZE] = "0";
10917
fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10919
if (*dst_youngest >= new_youngest)
10920
return SVN_NO_ERROR;
10922
/* If necessary, get new current next_node and next_copy IDs. */
10923
if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10925
apr_off_t root_offset;
10926
apr_file_t *rev_file;
10927
char max_node_id[MAX_KEY_SIZE] = "0";
10928
char max_copy_id[MAX_KEY_SIZE] = "0";
10931
if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10932
SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10934
SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10936
SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10937
dst_fs, new_youngest, scratch_pool));
10938
SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10939
root_offset, max_node_id, max_copy_id,
10941
SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10943
/* We store the _next_ ids. */
10944
len = strlen(max_node_id);
10945
svn_fs_fs__next_key(max_node_id, &len, next_node_id);
10946
len = strlen(max_copy_id);
10947
svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
10950
/* Update 'current'. */
10951
SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10954
*dst_youngest = new_youngest;
10956
return SVN_NO_ERROR;
10960
/* Remove revision or revprop files between START_REV (inclusive) and
10961
* END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS. Assume
10962
* sharding as per MAX_FILES_PER_DIR.
10963
* Use SCRATCH_POOL for temporary allocations. */
10964
static svn_error_t *
10965
hotcopy_remove_files(svn_fs_t *dst_fs,
10966
const char *dst_subdir,
10967
svn_revnum_t start_rev,
10968
svn_revnum_t end_rev,
10969
int max_files_per_dir,
10970
apr_pool_t *scratch_pool)
10973
const char *dst_subdir_shard;
10975
apr_pool_t *iterpool;
10977
/* Pre-compute paths for initial shard. */
10978
shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10979
dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10981
iterpool = svn_pool_create(scratch_pool);
10982
for (rev = start_rev; rev < end_rev; rev++)
10985
svn_pool_clear(iterpool);
10987
/* If necessary, update paths for shard. */
10988
if (rev != start_rev && rev % max_files_per_dir == 0)
10990
shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
10991
dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10994
/* remove files for REV */
10995
path = svn_dirent_join(dst_subdir_shard,
10996
apr_psprintf(iterpool, "%ld", rev),
10999
/* Make the rev file writable and remove it. */
11000
SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
11001
SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
11004
svn_pool_destroy(iterpool);
11006
return SVN_NO_ERROR;
11009
/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
11010
* from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11011
* Use SCRATCH_POOL for temporary allocations. */
11012
static svn_error_t *
11013
hotcopy_remove_rev_files(svn_fs_t *dst_fs,
11014
svn_revnum_t start_rev,
11015
svn_revnum_t end_rev,
11016
int max_files_per_dir,
11017
apr_pool_t *scratch_pool)
11019
SVN_ERR_ASSERT(start_rev <= end_rev);
11020
SVN_ERR(hotcopy_remove_files(dst_fs,
11021
svn_dirent_join(dst_fs->path,
11024
start_rev, end_rev,
11025
max_files_per_dir, scratch_pool));
11027
return SVN_NO_ERROR;
11030
/* Remove revision properties between START_REV (inclusive) and END_REV
11031
* (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11032
* Use SCRATCH_POOL for temporary allocations. Revision 0 revprops will
11033
* not be deleted. */
11034
static svn_error_t *
11035
hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11036
svn_revnum_t start_rev,
11037
svn_revnum_t end_rev,
11038
int max_files_per_dir,
2158
fs_fs_data_t *ffd = fs->fsap_data;
2159
*fs_format = ffd->format;
2160
*supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
2162
(*supports_version)->major = SVN_VER_MAJOR;
2163
(*supports_version)->minor = 1;
2164
(*supports_version)->patch = 0;
2165
(*supports_version)->tag = "";
2167
switch (ffd->format)
2172
(*supports_version)->minor = 4;
2175
(*supports_version)->minor = 5;
2178
(*supports_version)->minor = 6;
2181
(*supports_version)->minor = 8;
2184
(*supports_version)->minor = 9;
2187
# if SVN_FS_FS__FORMAT_NUMBER != 7
2188
# error "Need to add a 'case' statement here"
2193
return SVN_NO_ERROR;
2197
svn_fs_fs__info_config_files(apr_array_header_t **files,
2199
apr_pool_t *result_pool,
11039
2200
apr_pool_t *scratch_pool)
11041
SVN_ERR_ASSERT(start_rev <= end_rev);
11043
/* don't delete rev 0 props */
11044
SVN_ERR(hotcopy_remove_files(dst_fs,
11045
svn_dirent_join(dst_fs->path,
11048
start_rev ? start_rev : 1, end_rev,
11049
max_files_per_dir, scratch_pool));
11051
return SVN_NO_ERROR;
11054
/* Verify that DST_FS is a suitable destination for an incremental
11055
* hotcopy from SRC_FS. */
11056
static svn_error_t *
11057
hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11061
fs_fs_data_t *src_ffd = src_fs->fsap_data;
11062
fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11064
/* We only support incremental hotcopy between the same format. */
11065
if (src_ffd->format != dst_ffd->format)
11066
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11067
_("The FSFS format (%d) of the hotcopy source does not match the "
11068
"FSFS format (%d) of the hotcopy destination; please upgrade "
11069
"both repositories to the same format"),
11070
src_ffd->format, dst_ffd->format);
11072
/* Make sure the UUID of source and destination match up.
11073
* We don't want to copy over a different repository. */
11074
if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11075
return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11076
_("The UUID of the hotcopy source does "
11077
"not match the UUID of the hotcopy "
11080
/* Also require same shard size. */
11081
if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11082
return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11083
_("The sharding layout configuration "
11084
"of the hotcopy source does not match "
11085
"the sharding layout configuration of "
11086
"the hotcopy destination"));
11087
return SVN_NO_ERROR;
11090
/* Remove folder PATH. Ignore errors due to the sub-tree not being empty.
11091
* CANCEL_FUNC and CANCEL_BATON do the usual thing.
11092
* Use POOL for temporary allocations.
11094
static svn_error_t *
11095
remove_folder(const char *path,
11096
svn_cancel_func_t cancel_func,
11097
void *cancel_baton,
11100
svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11101
cancel_func, cancel_baton, pool);
11103
if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11105
svn_error_clear(err);
11106
err = SVN_NO_ERROR;
11109
return svn_error_trace(err);
11112
/* Baton for hotcopy_body(). */
11113
struct hotcopy_body_baton {
11116
svn_boolean_t incremental;
11117
svn_cancel_func_t cancel_func;
11118
void *cancel_baton;
11119
} hotcopy_body_baton;
11121
/* Perform a hotcopy, either normal or incremental.
11123
* Normal hotcopy assumes that the destination exists as an empty
11124
* directory. It behaves like an incremental hotcopy except that
11125
* none of the copied files already exist in the destination.
11127
* An incremental hotcopy copies only changed or new files to the destination,
11128
* and removes files from the destination no longer present in the source.
11129
* While the incremental hotcopy is running, readers should still be able
11130
* to access the destintation repository without error and should not see
11131
* revisions currently in progress of being copied. Readers are able to see
11132
* new fully copied revisions even if the entire incremental hotcopy procedure
11133
* has not yet completed.
11135
* Writers are blocked out completely during the entire incremental hotcopy
11136
* process to ensure consistency. This function assumes that the repository
11137
* write-lock is held.
11139
static svn_error_t *
11140
hotcopy_body(void *baton, apr_pool_t *pool)
11142
struct hotcopy_body_baton *hbb = baton;
11143
svn_fs_t *src_fs = hbb->src_fs;
11144
fs_fs_data_t *src_ffd = src_fs->fsap_data;
11145
svn_fs_t *dst_fs = hbb->dst_fs;
11146
fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11147
int max_files_per_dir = src_ffd->max_files_per_dir;
11148
svn_boolean_t incremental = hbb->incremental;
11149
svn_cancel_func_t cancel_func = hbb->cancel_func;
11150
void* cancel_baton = hbb->cancel_baton;
11151
svn_revnum_t src_youngest;
11152
svn_revnum_t dst_youngest;
11154
svn_revnum_t src_min_unpacked_rev;
11155
svn_revnum_t dst_min_unpacked_rev;
11156
const char *src_subdir;
11157
const char *dst_subdir;
11158
const char *revprop_src_subdir;
11159
const char *revprop_dst_subdir;
11160
apr_pool_t *iterpool;
11161
svn_node_kind_t kind;
11163
/* Try to copy the config.
11165
* ### We try copying the config file before doing anything else,
11166
* ### because higher layers will abort the hotcopy if we throw
11167
* ### an error from this function, and that renders the hotcopy
11168
* ### unusable anyway. */
11169
if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11173
err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11177
if (APR_STATUS_IS_ENOENT(err->apr_err))
11179
/* 1.6.0 to 1.6.11 did not copy the configuration file during
11180
* hotcopy. So if we're hotcopying a repository which has been
11181
* created as a hotcopy itself, it's possible that fsfs.conf
11182
* does not exist. Ask the user to re-create it.
11184
* ### It would be nice to make this a non-fatal error,
11185
* ### but this function does not get an svn_fs_t object
11186
* ### so we have no way of just printing a warning via
11187
* ### the fs->warning() callback. */
11190
const char *src_abspath;
11191
const char *dst_abspath;
11192
const char *config_relpath;
11195
config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11196
err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11198
return svn_error_trace(svn_error_compose_create(err, err2));
11199
err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11201
return svn_error_trace(svn_error_compose_create(err, err2));
11203
/* ### hack: strip off the 'db/' directory from paths so
11204
* ### they make sense to the user */
11205
src_abspath = svn_dirent_dirname(src_abspath, pool);
11206
dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11208
msg = apr_psprintf(pool,
11209
_("Failed to create hotcopy at '%s'. "
11210
"The file '%s' is missing from the source "
11211
"repository. Please create this file, for "
11212
"instance by running 'svnadmin upgrade %s'"),
11213
dst_abspath, config_relpath, src_abspath);
11214
return svn_error_quick_wrap(err, msg);
11217
return svn_error_trace(err);
11222
SVN_ERR(cancel_func(cancel_baton));
11224
/* Find the youngest revision in the source and destination.
11225
* We only support hotcopies from sources with an equal or greater amount
11226
* of revisions than the destination.
11227
* This also catches the case where users accidentally swap the
11228
* source and destination arguments. */
11229
SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11232
SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11233
if (src_youngest < dst_youngest)
11234
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11235
_("The hotcopy destination already contains more revisions "
11236
"(%lu) than the hotcopy source contains (%lu); are source "
11237
"and destination swapped?"),
11238
dst_youngest, src_youngest);
11244
SVN_ERR(cancel_func(cancel_baton));
11246
/* Copy the min unpacked rev, and read its value. */
11247
if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11249
const char *min_unpacked_rev_path;
11251
min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11252
PATH_MIN_UNPACKED_REV,
11254
SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11255
min_unpacked_rev_path,
11258
min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11259
PATH_MIN_UNPACKED_REV,
11261
SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11262
min_unpacked_rev_path,
11265
/* We only support packs coming from the hotcopy source.
11266
* The destination should not be packed independently from
11267
* the source. This also catches the case where users accidentally
11268
* swap the source and destination arguments. */
11269
if (src_min_unpacked_rev < dst_min_unpacked_rev)
11270
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11271
_("The hotcopy destination already contains "
11272
"more packed revisions (%lu) than the "
11273
"hotcopy source contains (%lu)"),
11274
dst_min_unpacked_rev - 1,
11275
src_min_unpacked_rev - 1);
11277
SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11278
PATH_MIN_UNPACKED_REV, pool));
11282
src_min_unpacked_rev = 0;
11283
dst_min_unpacked_rev = 0;
11287
SVN_ERR(cancel_func(cancel_baton));
11290
* Copy the necessary rev files.
11293
src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11294
dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11295
SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11297
iterpool = svn_pool_create(pool);
11298
/* First, copy packed shards. */
11299
for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11301
svn_pool_clear(iterpool);
11304
SVN_ERR(cancel_func(cancel_baton));
11306
/* Copy the packed shard. */
11307
SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11309
rev, max_files_per_dir,
11312
/* If necessary, update 'current' to the most recent packed rev,
11313
* so readers can see new revisions which arrived in this pack. */
11314
SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11315
rev + max_files_per_dir - 1,
11318
/* Remove revision files which are now packed. */
11321
SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11322
rev + max_files_per_dir,
11323
max_files_per_dir, iterpool));
11324
if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11325
SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11326
rev + max_files_per_dir,
11331
/* Now that all revisions have moved into the pack, the original
11332
* rev dir can be removed. */
11333
SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11334
cancel_func, cancel_baton, iterpool));
11335
if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11336
SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11337
cancel_func, cancel_baton, iterpool));
11341
SVN_ERR(cancel_func(cancel_baton));
11343
/* Now, copy pairs of non-packed revisions and revprop files.
11344
* If necessary, update 'current' after copying all files from a shard. */
11345
SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11346
SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11347
revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11348
revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11349
SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11350
for (; rev <= src_youngest; rev++)
11354
svn_pool_clear(iterpool);
11357
SVN_ERR(cancel_func(cancel_baton));
11359
/* Copy the rev file. */
11360
err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11361
rev, max_files_per_dir,
11365
if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11366
src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11368
svn_error_clear(err);
11370
/* The source rev file does not exist. This can happen if the
11371
* source repository is being packed concurrently with this
11372
* hotcopy operation.
11374
* If the new revision is now packed, and the youngest revision
11375
* we're interested in is not inside this pack, try to copy the
11378
* If the youngest revision ended up being packed, don't try
11379
* to be smart and work around this. Just abort the hotcopy. */
11380
SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11381
if (is_packed_rev(src_fs, rev))
11383
if (is_packed_rev(src_fs, src_youngest))
11384
return svn_error_createf(
11385
SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11386
_("The assumed HEAD revision (%lu) of the "
11387
"hotcopy source has been packed while the "
11388
"hotcopy was in progress; please restart "
11389
"the hotcopy operation"),
11392
SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11394
rev, max_files_per_dir,
11396
rev = dst_min_unpacked_rev;
11400
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11401
_("Revision %lu disappeared from the "
11402
"hotcopy source while hotcopy was "
11403
"in progress"), rev);
11406
return svn_error_trace(err);
11409
/* Copy the revprop file. */
11410
SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11411
revprop_dst_subdir,
11412
rev, max_files_per_dir,
11415
/* After completing a full shard, update 'current'. */
11416
if (max_files_per_dir && rev % max_files_per_dir == 0)
11417
SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11419
svn_pool_destroy(iterpool);
11422
SVN_ERR(cancel_func(cancel_baton));
11424
/* We assume that all revisions were copied now, i.e. we didn't exit the
11425
* above loop early. 'rev' was last incremented during exit of the loop. */
11426
SVN_ERR_ASSERT(rev == src_youngest + 1);
11428
/* All revisions were copied. Update 'current'. */
11429
SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11431
/* Replace the locks tree.
11432
* This is racy in case readers are currently trying to list locks in
11433
* the destination. However, we need to get rid of stale locks.
11434
* This is the simplest way of doing this, so we accept this small race. */
11435
dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11436
SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11438
src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11439
SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11440
if (kind == svn_node_dir)
11441
SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11442
PATH_LOCKS_DIR, TRUE,
11443
cancel_func, cancel_baton, pool));
11445
/* Now copy the node-origins cache tree. */
11446
src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11447
SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11448
if (kind == svn_node_dir)
11449
SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11450
PATH_NODE_ORIGINS_DIR, TRUE,
11451
cancel_func, cancel_baton, pool));
11454
* NB: Data copied below is only read by writers, not readers.
11455
* Writers are still locked out at this point.
11458
if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11460
/* Copy the rep cache and then remove entries for revisions
11461
* younger than the destination's youngest revision. */
11462
src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11463
dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11464
SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11465
if (kind == svn_node_file)
11467
SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11468
SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11472
/* Copy the txn-current file. */
11473
if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11474
SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11475
PATH_TXN_CURRENT, pool));
11477
/* If a revprop generation file exists in the source filesystem,
11478
* reset it to zero (since this is on a different path, it will not
11479
* overlap with data already in cache). Also, clean up stale files
11480
* used for the named atomics implementation. */
11481
SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11483
if (kind == svn_node_file)
11484
SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11486
SVN_ERR(cleanup_revprop_namespace(dst_fs));
11488
/* Hotcopied FS is complete. Stamp it with a format file. */
11489
SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11490
dst_ffd->format, max_files_per_dir, TRUE, pool));
11492
return SVN_NO_ERROR;
11496
/* Set up shared data between SRC_FS and DST_FS. */
11498
hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11500
fs_fs_data_t *src_ffd = src_fs->fsap_data;
11501
fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11503
/* The common pool and mutexes are shared between src and dst filesystems.
11504
* During hotcopy we only grab the mutexes for the destination, so there
11505
* is no risk of dead-lock. We don't write to the src filesystem. Shared
11506
* data for the src_fs has already been initialised in fs_hotcopy(). */
11507
dst_ffd->shared = src_ffd->shared;
11510
/* Create an empty filesystem at DST_FS at DST_PATH with the same
11511
* configuration as SRC_FS (uuid, format, and other parameters).
11512
* After creation DST_FS has no revisions, not even revision zero. */
11513
static svn_error_t *
11514
hotcopy_create_empty_dest(svn_fs_t *src_fs,
11516
const char *dst_path,
11519
fs_fs_data_t *src_ffd = src_fs->fsap_data;
11520
fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11522
dst_fs->path = apr_pstrdup(pool, dst_path);
11524
dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11525
dst_ffd->config = src_ffd->config;
11526
dst_ffd->format = src_ffd->format;
11528
/* Create the revision data directories. */
11529
if (dst_ffd->max_files_per_dir)
11530
SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11533
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11534
PATH_REVS_DIR, pool),
11537
/* Create the revprops directory. */
11538
if (src_ffd->max_files_per_dir)
11539
SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11542
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11547
/* Create the transaction directory. */
11548
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11552
/* Create the protorevs directory. */
11553
if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11554
SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11555
PATH_TXN_PROTOS_DIR,
11559
/* Create the 'current' file. */
11560
SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11561
(dst_ffd->format >=
11562
SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11563
? "0\n" : "0 1 1\n"),
11566
/* Create lock file and UUID. */
11567
SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11568
SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11570
/* Create the min unpacked rev file. */
11571
if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11572
SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11574
/* Create the txn-current file if the repository supports
11575
the transaction sequence file. */
11576
if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11578
SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11580
SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11584
dst_ffd->youngest_rev_cache = 0;
11586
hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11587
SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11589
return SVN_NO_ERROR;
11593
svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11595
const char *src_path,
11596
const char *dst_path,
11597
svn_boolean_t incremental,
11598
svn_cancel_func_t cancel_func,
11599
void *cancel_baton,
11602
struct hotcopy_body_baton hbb;
11605
SVN_ERR(cancel_func(cancel_baton));
11607
SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11611
const char *dst_format_abspath;
11612
svn_node_kind_t dst_format_kind;
11614
/* Check destination format to be sure we know how to incrementally
11615
* hotcopy to the destination FS. */
11616
dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11617
SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11618
if (dst_format_kind == svn_node_none)
11620
/* Destination doesn't exist yet. Perform a normal hotcopy to a
11621
* empty destination using the same configuration as the source. */
11622
SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11626
/* Check the existing repository. */
11627
SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11628
SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11630
hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11631
SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11636
/* Start out with an empty destination using the same configuration
11637
* as the source. */
11638
SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11642
SVN_ERR(cancel_func(cancel_baton));
11644
hbb.src_fs = src_fs;
11645
hbb.dst_fs = dst_fs;
11646
hbb.incremental = incremental;
11647
hbb.cancel_func = cancel_func;
11648
hbb.cancel_baton = cancel_baton;
11649
SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
2202
*files = apr_array_make(result_pool, 1, sizeof(const char *));
2203
APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
11651
2205
return SVN_NO_ERROR;