1 /* util.c --- utility functions for FSX repo access
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include <assert.h>
24 
25 #include "svn_ctype.h"
26 #include "svn_dirent_uri.h"
27 #include "private/svn_string_private.h"
28 
29 #include "fs_x.h"
30 #include "id.h"
31 #include "util.h"
32 
33 #include "../libsvn_fs/fs-loader.h"
34 
35 #include "svn_private_config.h"
36 
37 /* Following are defines that specify the textual elements of the
38    native filesystem directories and revision files. */
39 
40 /* Notes:
41 
42 To avoid opening and closing the rev-files all the time, it would
43 probably be advantageous to keep each rev-file open for the
44 lifetime of the transaction object.  I'll leave that as a later
45 optimization for now.
46 
47 I didn't keep track of pool lifetimes at all in this code.  There
48 are likely some errors because of that.
49 
50 */
51 
52 /* Pathname helper functions */
53 
54 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
55 svn_boolean_t
svn_fs_x__is_packed_rev(svn_fs_t * fs,svn_revnum_t rev)56 svn_fs_x__is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
57 {
58   svn_fs_x__data_t *ffd = fs->fsap_data;
59 
60   return (rev < ffd->min_unpacked_rev);
61 }
62 
63 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
64 svn_boolean_t
svn_fs_x__is_packed_revprop(svn_fs_t * fs,svn_revnum_t rev)65 svn_fs_x__is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
66 {
67   svn_fs_x__data_t *ffd = fs->fsap_data;
68 
69   /* rev 0 will not be packed */
70   return (rev < ffd->min_unpacked_rev) && (rev != 0);
71 }
72 
73 svn_revnum_t
svn_fs_x__packed_base_rev(svn_fs_t * fs,svn_revnum_t rev)74 svn_fs_x__packed_base_rev(svn_fs_t *fs, svn_revnum_t rev)
75 {
76   svn_fs_x__data_t *ffd = fs->fsap_data;
77 
78   return rev < ffd->min_unpacked_rev
79        ? rev - (rev % ffd->max_files_per_dir)
80        : rev;
81 }
82 
83 svn_revnum_t
svn_fs_x__pack_size(svn_fs_t * fs,svn_revnum_t rev)84 svn_fs_x__pack_size(svn_fs_t *fs, svn_revnum_t rev)
85 {
86   svn_fs_x__data_t *ffd = fs->fsap_data;
87 
88   return rev < ffd->min_unpacked_rev ? ffd->max_files_per_dir : 1;
89 }
90 
91 const char *
svn_fs_x__path_format(svn_fs_t * fs,apr_pool_t * result_pool)92 svn_fs_x__path_format(svn_fs_t *fs,
93                       apr_pool_t *result_pool)
94 {
95   return svn_dirent_join(fs->path, PATH_FORMAT, result_pool);
96 }
97 
98 const char *
svn_fs_x__path_uuid(svn_fs_t * fs,apr_pool_t * result_pool)99 svn_fs_x__path_uuid(svn_fs_t *fs,
100                     apr_pool_t *result_pool)
101 {
102   return svn_dirent_join(fs->path, PATH_UUID, result_pool);
103 }
104 
105 const char *
svn_fs_x__path_current(svn_fs_t * fs,apr_pool_t * result_pool)106 svn_fs_x__path_current(svn_fs_t *fs,
107                        apr_pool_t *result_pool)
108 {
109   return svn_dirent_join(fs->path, PATH_CURRENT, result_pool);
110 }
111 
112 const char *
svn_fs_x__path_next(svn_fs_t * fs,apr_pool_t * result_pool)113 svn_fs_x__path_next(svn_fs_t *fs,
114                        apr_pool_t *result_pool)
115 {
116   return svn_dirent_join(fs->path, PATH_NEXT, result_pool);
117 }
118 
119 const char *
svn_fs_x__path_txn_current(svn_fs_t * fs,apr_pool_t * result_pool)120 svn_fs_x__path_txn_current(svn_fs_t *fs,
121                            apr_pool_t *result_pool)
122 {
123   return svn_dirent_join(fs->path, PATH_TXN_CURRENT, result_pool);
124 }
125 
126 const char *
svn_fs_x__path_txn_current_lock(svn_fs_t * fs,apr_pool_t * result_pool)127 svn_fs_x__path_txn_current_lock(svn_fs_t *fs,
128                                 apr_pool_t *result_pool)
129 {
130   return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, result_pool);
131 }
132 
133 const char *
svn_fs_x__path_lock(svn_fs_t * fs,apr_pool_t * result_pool)134 svn_fs_x__path_lock(svn_fs_t *fs,
135                     apr_pool_t *result_pool)
136 {
137   return svn_dirent_join(fs->path, PATH_LOCK_FILE, result_pool);
138 }
139 
140 const char *
svn_fs_x__path_pack_lock(svn_fs_t * fs,apr_pool_t * result_pool)141 svn_fs_x__path_pack_lock(svn_fs_t *fs,
142                          apr_pool_t *result_pool)
143 {
144   return svn_dirent_join(fs->path, PATH_PACK_LOCK_FILE, result_pool);
145 }
146 
147 const char *
svn_fs_x__path_revprop_generation(svn_fs_t * fs,apr_pool_t * result_pool)148 svn_fs_x__path_revprop_generation(svn_fs_t *fs,
149                                   apr_pool_t *result_pool)
150 {
151   return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, result_pool);
152 }
153 
154 /* Return the full path of the file FILENAME within revision REV's shard in
155  * FS.  If FILENAME is NULL, return the shard directory directory itself.
156  * PACKED says whether we want the packed shard's name.
157  *
158  * Allocate the result in RESULT_POOL.
159  */static const char*
construct_shard_sub_path(svn_fs_t * fs,svn_revnum_t rev,svn_boolean_t packed,const char * filename,apr_pool_t * result_pool)160 construct_shard_sub_path(svn_fs_t *fs,
161                          svn_revnum_t rev,
162                          svn_boolean_t packed,
163                          const char *filename,
164                          apr_pool_t *result_pool)
165 {
166   svn_fs_x__data_t *ffd = fs->fsap_data;
167   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_PACKED_SHARD)] = { 0 };
168 
169   /* String containing the shard number. */
170   apr_size_t len = svn__i64toa(buffer, rev / ffd->max_files_per_dir);
171 
172   /* Append the suffix.  Limit it to the buffer size (should never hit it). */
173   if (packed)
174     strncpy(buffer + len, PATH_EXT_PACKED_SHARD, sizeof(buffer) - len - 1);
175 
176   /* This will also work for NULL FILENAME as well. */
177   return svn_dirent_join_many(result_pool, fs->path, PATH_REVS_DIR, buffer,
178                               filename, SVN_VA_NULL);
179 }
180 
181 const char *
svn_fs_x__path_rev_packed(svn_fs_t * fs,svn_revnum_t rev,const char * kind,apr_pool_t * result_pool)182 svn_fs_x__path_rev_packed(svn_fs_t *fs,
183                           svn_revnum_t rev,
184                           const char *kind,
185                           apr_pool_t *result_pool)
186 {
187   assert(svn_fs_x__is_packed_rev(fs, rev));
188   return construct_shard_sub_path(fs, rev, TRUE, kind, result_pool);
189 }
190 
191 const char *
svn_fs_x__path_shard(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * result_pool)192 svn_fs_x__path_shard(svn_fs_t *fs,
193                      svn_revnum_t rev,
194                      apr_pool_t *result_pool)
195 {
196   return construct_shard_sub_path(fs, rev, FALSE, NULL, result_pool);
197 }
198 
199 const char *
svn_fs_x__path_rev(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * result_pool)200 svn_fs_x__path_rev(svn_fs_t *fs,
201                    svn_revnum_t rev,
202                    apr_pool_t *result_pool)
203 {
204   char buffer[SVN_INT64_BUFFER_SIZE + 1];
205   buffer[0] = 'r';
206   svn__i64toa(buffer + 1, rev);
207 
208   assert(! svn_fs_x__is_packed_rev(fs, rev));
209   return construct_shard_sub_path(fs, rev, FALSE, buffer, result_pool);
210 }
211 
212 const char *
svn_fs_x__path_rev_absolute(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * result_pool)213 svn_fs_x__path_rev_absolute(svn_fs_t *fs,
214                             svn_revnum_t rev,
215                             apr_pool_t *result_pool)
216 {
217   return svn_fs_x__is_packed_rev(fs, rev)
218        ? svn_fs_x__path_rev_packed(fs, rev, PATH_PACKED, result_pool)
219        : svn_fs_x__path_rev(fs, rev, result_pool);
220 }
221 
222 const char *
svn_fs_x__path_pack_shard(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * result_pool)223 svn_fs_x__path_pack_shard(svn_fs_t *fs,
224                                    svn_revnum_t rev,
225                                    apr_pool_t *result_pool)
226 {
227   return construct_shard_sub_path(fs, rev, TRUE, NULL, result_pool);
228 }
229 
230 const char *
svn_fs_x__path_revprops(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * result_pool)231 svn_fs_x__path_revprops(svn_fs_t *fs,
232                         svn_revnum_t rev,
233                         apr_pool_t *result_pool)
234 {
235   char buffer[SVN_INT64_BUFFER_SIZE + 1];
236   buffer[0] = 'p';
237   svn__i64toa(buffer + 1, rev);
238 
239   assert(! svn_fs_x__is_packed_revprop(fs, rev));
240 
241   /* Revprops for packed r0 are not packed, yet stored in the packed shard.
242      Hence, the second flag must check for packed _rev_ - not revprop. */
243   return construct_shard_sub_path(fs, rev,
244                                   svn_fs_x__is_packed_rev(fs, rev) /* sic! */,
245                                   buffer, result_pool);
246 }
247 
248 const char *
svn_fs_x__txn_name(svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)249 svn_fs_x__txn_name(svn_fs_x__txn_id_t txn_id,
250                    apr_pool_t *result_pool)
251 {
252   char *p = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
253   svn__ui64tobase36(p, txn_id);
254   return p;
255 }
256 
257 svn_error_t *
svn_fs_x__txn_by_name(svn_fs_x__txn_id_t * txn_id,const char * txn_name)258 svn_fs_x__txn_by_name(svn_fs_x__txn_id_t *txn_id,
259                       const char *txn_name)
260 {
261   const char *next;
262   apr_uint64_t id = svn__base36toui64(&next, txn_name);
263   if (next == NULL || *next != 0 || *txn_name == 0)
264     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
265                              "Malformed TXN name '%s'", txn_name);
266 
267   *txn_id = id;
268   return SVN_NO_ERROR;
269 }
270 
271 const char *
svn_fs_x__path_txns_dir(svn_fs_t * fs,apr_pool_t * result_pool)272 svn_fs_x__path_txns_dir(svn_fs_t *fs,
273                         apr_pool_t *result_pool)
274 {
275   return svn_dirent_join(fs->path, PATH_TXNS_DIR, result_pool);
276 }
277 
278 /* Return the full path of the file FILENAME within transaction TXN_ID's
279  * transaction directory in FS.  If FILENAME is NULL, return the transaction
280  * directory itself.
281  *
282  * Allocate the result in RESULT_POOL.
283  */
284 static const char *
construct_txn_path(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,const char * filename,apr_pool_t * result_pool)285 construct_txn_path(svn_fs_t *fs,
286                    svn_fs_x__txn_id_t txn_id,
287                    const char *filename,
288                    apr_pool_t *result_pool)
289 {
290   /* Construct the transaction directory name without temp. allocations. */
291   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_TXN)] = { 0 };
292   apr_size_t len = svn__ui64tobase36(buffer, txn_id);
293   strncpy(buffer + len, PATH_EXT_TXN, sizeof(buffer) - len - 1);
294 
295   /* If FILENAME is NULL, it will terminate the list of segments
296      to concatenate. */
297   return svn_dirent_join_many(result_pool, fs->path, PATH_TXNS_DIR,
298                               buffer, filename, SVN_VA_NULL);
299 }
300 
301 const char *
svn_fs_x__path_txn_dir(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)302 svn_fs_x__path_txn_dir(svn_fs_t *fs,
303                        svn_fs_x__txn_id_t txn_id,
304                        apr_pool_t *result_pool)
305 {
306   return construct_txn_path(fs, txn_id, NULL, result_pool);
307 }
308 
309 /* Return the name of the sha1->rep mapping file in transaction TXN_ID
310  * within FS for the given SHA1 checksum.  Use POOL for allocations.
311  */
312 const char *
svn_fs_x__path_txn_sha1(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,const unsigned char * sha1,apr_pool_t * pool)313 svn_fs_x__path_txn_sha1(svn_fs_t *fs,
314                         svn_fs_x__txn_id_t txn_id,
315                         const unsigned char *sha1,
316                         apr_pool_t *pool)
317 {
318   svn_checksum_t checksum;
319   checksum.digest = sha1;
320   checksum.kind = svn_checksum_sha1;
321 
322   return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, pool),
323                          svn_checksum_to_cstring(&checksum, pool),
324                          pool);
325 }
326 
327 const char *
svn_fs_x__path_txn_changes(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)328 svn_fs_x__path_txn_changes(svn_fs_t *fs,
329                            svn_fs_x__txn_id_t txn_id,
330                            apr_pool_t *result_pool)
331 {
332   return construct_txn_path(fs, txn_id, PATH_CHANGES, result_pool);
333 }
334 
335 const char *
svn_fs_x__path_txn_props(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)336 svn_fs_x__path_txn_props(svn_fs_t *fs,
337                          svn_fs_x__txn_id_t txn_id,
338                          apr_pool_t *result_pool)
339 {
340   return construct_txn_path(fs, txn_id, PATH_TXN_PROPS, result_pool);
341 }
342 
343 const char*
svn_fs_x__path_l2p_proto_index(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)344 svn_fs_x__path_l2p_proto_index(svn_fs_t *fs,
345                                svn_fs_x__txn_id_t txn_id,
346                                apr_pool_t *result_pool)
347 {
348   return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_L2P_INDEX,
349                             result_pool);
350 }
351 
352 const char*
svn_fs_x__path_p2l_proto_index(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)353 svn_fs_x__path_p2l_proto_index(svn_fs_t *fs,
354                                svn_fs_x__txn_id_t txn_id,
355                                apr_pool_t *result_pool)
356 {
357   return construct_txn_path(fs, txn_id, PATH_INDEX PATH_EXT_P2L_INDEX,
358                             result_pool);
359 }
360 
361 const char *
svn_fs_x__path_txn_next_ids(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)362 svn_fs_x__path_txn_next_ids(svn_fs_t *fs,
363                             svn_fs_x__txn_id_t txn_id,
364                             apr_pool_t *result_pool)
365 {
366   return construct_txn_path(fs, txn_id, PATH_NEXT_IDS, result_pool);
367 }
368 
369 const char *
svn_fs_x__path_min_unpacked_rev(svn_fs_t * fs,apr_pool_t * result_pool)370 svn_fs_x__path_min_unpacked_rev(svn_fs_t *fs,
371                                 apr_pool_t *result_pool)
372 {
373   return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, result_pool);
374 }
375 
376 const char *
svn_fs_x__path_txn_proto_revs(svn_fs_t * fs,apr_pool_t * result_pool)377 svn_fs_x__path_txn_proto_revs(svn_fs_t *fs,
378                               apr_pool_t *result_pool)
379 {
380   return svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, result_pool);
381 }
382 
383 const char *
svn_fs_x__path_txn_item_index(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)384 svn_fs_x__path_txn_item_index(svn_fs_t *fs,
385                               svn_fs_x__txn_id_t txn_id,
386                               apr_pool_t *result_pool)
387 {
388   return construct_txn_path(fs, txn_id, PATH_TXN_ITEM_INDEX, result_pool);
389 }
390 
391 /* Return the full path of the proto-rev file / lock file for transaction
392  * TXN_ID in FS.  The SUFFIX determines what file (rev / lock) it will be.
393  *
394  * Allocate the result in RESULT_POOL.
395  */
396 static const char *
construct_proto_rev_path(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,const char * suffix,apr_pool_t * result_pool)397 construct_proto_rev_path(svn_fs_t *fs,
398                          svn_fs_x__txn_id_t txn_id,
399                          const char *suffix,
400                          apr_pool_t *result_pool)
401 {
402   /* Construct the file name without temp. allocations. */
403   char buffer[SVN_INT64_BUFFER_SIZE + sizeof(PATH_EXT_REV_LOCK)] = { 0 };
404   apr_size_t len = svn__ui64tobase36(buffer, txn_id);
405   strncpy(buffer + len, suffix, sizeof(buffer) - len - 1);
406 
407   /* If FILENAME is NULL, it will terminate the list of segments
408      to concatenate. */
409   return svn_dirent_join_many(result_pool, fs->path, PATH_TXN_PROTOS_DIR,
410                               buffer, SVN_VA_NULL);
411 }
412 
413 const char *
svn_fs_x__path_txn_proto_rev(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)414 svn_fs_x__path_txn_proto_rev(svn_fs_t *fs,
415                              svn_fs_x__txn_id_t txn_id,
416                              apr_pool_t *result_pool)
417 {
418   return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV, result_pool);
419 }
420 
421 const char *
svn_fs_x__path_txn_proto_rev_lock(svn_fs_t * fs,svn_fs_x__txn_id_t txn_id,apr_pool_t * result_pool)422 svn_fs_x__path_txn_proto_rev_lock(svn_fs_t *fs,
423                                   svn_fs_x__txn_id_t txn_id,
424                                   apr_pool_t *result_pool)
425 {
426   return construct_proto_rev_path(fs, txn_id, PATH_EXT_REV_LOCK, result_pool);
427 }
428 
429 /* Return the full path of the noderev-related file with the extension SUFFIX
430  * for noderev *ID in transaction TXN_ID in FS.
431  *
432  * Allocate the result in RESULT_POOL and temporaries in SCRATCH_POOL.
433  */
434 static const char *
construct_txn_node_path(svn_fs_t * fs,const svn_fs_x__id_t * id,const char * suffix,apr_pool_t * result_pool,apr_pool_t * scratch_pool)435 construct_txn_node_path(svn_fs_t *fs,
436                         const svn_fs_x__id_t *id,
437                         const char *suffix,
438                         apr_pool_t *result_pool,
439                         apr_pool_t *scratch_pool)
440 {
441   const char *filename = svn_fs_x__id_unparse(id, result_pool)->data;
442   apr_int64_t txn_id = svn_fs_x__get_txn_id(id->change_set);
443 
444   return svn_dirent_join(svn_fs_x__path_txn_dir(fs, txn_id, scratch_pool),
445                          apr_psprintf(scratch_pool, PATH_PREFIX_NODE "%s%s",
446                                       filename, suffix),
447                          result_pool);
448 }
449 
450 const char *
svn_fs_x__path_txn_node_rev(svn_fs_t * fs,const svn_fs_x__id_t * id,apr_pool_t * result_pool,apr_pool_t * scratch_pool)451 svn_fs_x__path_txn_node_rev(svn_fs_t *fs,
452                             const svn_fs_x__id_t *id,
453                             apr_pool_t *result_pool,
454                             apr_pool_t *scratch_pool)
455 {
456   return construct_txn_node_path(fs, id, "", result_pool, scratch_pool);
457 }
458 
459 const char *
svn_fs_x__path_txn_node_props(svn_fs_t * fs,const svn_fs_x__id_t * id,apr_pool_t * result_pool,apr_pool_t * scratch_pool)460 svn_fs_x__path_txn_node_props(svn_fs_t *fs,
461                               const svn_fs_x__id_t *id,
462                               apr_pool_t *result_pool,
463                               apr_pool_t *scratch_pool)
464 {
465   return construct_txn_node_path(fs, id, PATH_EXT_PROPS, result_pool,
466                                  scratch_pool);
467 }
468 
469 const char *
svn_fs_x__path_txn_node_children(svn_fs_t * fs,const svn_fs_x__id_t * id,apr_pool_t * result_pool,apr_pool_t * scratch_pool)470 svn_fs_x__path_txn_node_children(svn_fs_t *fs,
471                                  const svn_fs_x__id_t *id,
472                                  apr_pool_t *result_pool,
473                                  apr_pool_t *scratch_pool)
474 {
475   return construct_txn_node_path(fs, id, PATH_EXT_CHILDREN, result_pool,
476                                  scratch_pool);
477 }
478 
479 svn_error_t *
svn_fs_x__check_file_buffer_numeric(const char * buf,apr_off_t offset,const char * path,const char * title,apr_pool_t * scratch_pool)480 svn_fs_x__check_file_buffer_numeric(const char *buf,
481                                     apr_off_t offset,
482                                     const char *path,
483                                     const char *title,
484                                     apr_pool_t *scratch_pool)
485 {
486   const char *p;
487 
488   for (p = buf + offset; *p; p++)
489     if (!svn_ctype_isdigit(*p))
490       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
491         _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
492         title, svn_dirent_local_style(path, scratch_pool), *p, buf);
493 
494   return SVN_NO_ERROR;
495 }
496 
497 svn_error_t *
svn_fs_x__read_min_unpacked_rev(svn_revnum_t * min_unpacked_rev,svn_fs_t * fs,apr_pool_t * scratch_pool)498 svn_fs_x__read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
499                                 svn_fs_t *fs,
500                                 apr_pool_t *scratch_pool)
501 {
502   char buf[80];
503   apr_file_t *file;
504   apr_size_t len;
505 
506   SVN_ERR(svn_io_file_open(&file,
507                            svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
508                            APR_READ | APR_BUFFERED,
509                            APR_OS_DEFAULT,
510                            scratch_pool));
511   len = sizeof(buf);
512   SVN_ERR(svn_io_read_length_line(file, buf, &len, scratch_pool));
513   SVN_ERR(svn_io_file_close(file, scratch_pool));
514 
515   SVN_ERR(svn_revnum_parse(min_unpacked_rev, buf, NULL));
516   return SVN_NO_ERROR;
517 }
518 
519 svn_error_t *
svn_fs_x__update_min_unpacked_rev(svn_fs_t * fs,apr_pool_t * scratch_pool)520 svn_fs_x__update_min_unpacked_rev(svn_fs_t *fs,
521                                   apr_pool_t *scratch_pool)
522 {
523   svn_fs_x__data_t *ffd = fs->fsap_data;
524   return svn_fs_x__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs,
525                                          scratch_pool);
526 }
527 
528 /* Write a file FILENAME in directory FS_PATH, containing a single line
529  * with the number REVNUM in ASCII decimal.  Move the file into place
530  * atomically, overwriting any existing file.
531  *
532  * Similar to write_current(). */
533 svn_error_t *
svn_fs_x__write_min_unpacked_rev(svn_fs_t * fs,svn_revnum_t revnum,apr_pool_t * scratch_pool)534 svn_fs_x__write_min_unpacked_rev(svn_fs_t *fs,
535                                  svn_revnum_t revnum,
536                                  apr_pool_t *scratch_pool)
537 {
538   svn_fs_x__data_t *ffd = fs->fsap_data;
539   const char *final_path;
540   char buf[SVN_INT64_BUFFER_SIZE];
541   apr_size_t len = svn__i64toa(buf, revnum);
542   buf[len] = '\n';
543 
544   final_path = svn_fs_x__path_min_unpacked_rev(fs, scratch_pool);
545 
546   SVN_ERR(svn_io_write_atomic2(final_path, buf, len + 1,
547                                final_path /* copy_perms */,
548                                ffd->flush_to_disk, scratch_pool));
549 
550   return SVN_NO_ERROR;
551 }
552 
553 svn_error_t *
svn_fs_x__read_current(svn_revnum_t * rev,svn_fs_t * fs,apr_pool_t * scratch_pool)554 svn_fs_x__read_current(svn_revnum_t *rev,
555                        svn_fs_t *fs,
556                        apr_pool_t *scratch_pool)
557 {
558   const char *str;
559   svn_stringbuf_t *content;
560   SVN_ERR(svn_fs_x__read_content(&content,
561                                  svn_fs_x__path_current(fs, scratch_pool),
562                                  scratch_pool));
563   SVN_ERR(svn_revnum_parse(rev, content->data, &str));
564   if (*str != '\n')
565     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
566                             _("Corrupt 'current' file"));
567 
568   return SVN_NO_ERROR;
569 }
570 
571 /* Atomically update the 'current' file to hold the specified REV.
572    Perform temporary allocations in SCRATCH_POOL. */
573 svn_error_t *
svn_fs_x__write_current(svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * scratch_pool)574 svn_fs_x__write_current(svn_fs_t *fs,
575                         svn_revnum_t rev,
576                         apr_pool_t *scratch_pool)
577 {
578   char *buf;
579   const char *tmp_name, *name;
580   apr_file_t *file;
581 
582   /* Now we can just write out this line. */
583   buf = apr_psprintf(scratch_pool, "%ld\n", rev);
584 
585   name = svn_fs_x__path_current(fs, scratch_pool);
586   tmp_name = svn_fs_x__path_next(fs, scratch_pool);
587 
588   SVN_ERR(svn_io_file_open(&file, tmp_name,
589                            APR_WRITE | APR_CREATE | APR_BUFFERED,
590                            APR_OS_DEFAULT, scratch_pool));
591   SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL,
592                                  scratch_pool));
593   SVN_ERR(svn_io_file_close(file, scratch_pool));
594 
595   /* Copying permissions is a no-op on WIN32. */
596   SVN_ERR(svn_io_copy_perms(name, tmp_name, scratch_pool));
597 
598   /* Move the file into place. */
599   SVN_ERR(svn_io_file_rename2(tmp_name, name, TRUE, scratch_pool));
600 
601   return SVN_NO_ERROR;
602 }
603 
604 
605 svn_error_t *
svn_fs_x__try_stringbuf_from_file(svn_stringbuf_t ** content,svn_boolean_t * missing,const char * path,svn_boolean_t last_attempt,apr_pool_t * result_pool)606 svn_fs_x__try_stringbuf_from_file(svn_stringbuf_t **content,
607                                   svn_boolean_t *missing,
608                                   const char *path,
609                                   svn_boolean_t last_attempt,
610                                   apr_pool_t *result_pool)
611 {
612   svn_error_t *err = svn_stringbuf_from_file2(content, path, result_pool);
613   if (missing)
614     *missing = FALSE;
615 
616   if (err)
617     {
618       *content = NULL;
619 
620       if (APR_STATUS_IS_ENOENT(err->apr_err))
621         {
622           if (!last_attempt)
623             {
624               svn_error_clear(err);
625               if (missing)
626                 *missing = TRUE;
627               return SVN_NO_ERROR;
628             }
629         }
630 #ifdef ESTALE
631       else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
632                 || APR_TO_OS_ERROR(err->apr_err) == EIO)
633         {
634           if (!last_attempt)
635             {
636               svn_error_clear(err);
637               return SVN_NO_ERROR;
638             }
639         }
640 #endif
641     }
642 
643   return svn_error_trace(err);
644 }
645 
646 /* Fetch the current offset of FILE into *OFFSET_P. */
647 svn_error_t *
svn_fs_x__read_content(svn_stringbuf_t ** content,const char * fname,apr_pool_t * result_pool)648 svn_fs_x__read_content(svn_stringbuf_t **content,
649                        const char *fname,
650                        apr_pool_t *result_pool)
651 {
652   int i;
653   *content = NULL;
654 
655   for (i = 0; !*content && (i < SVN_FS_X__RECOVERABLE_RETRY_COUNT); ++i)
656     SVN_ERR(svn_fs_x__try_stringbuf_from_file(content, NULL,
657                            fname, i + 1 < SVN_FS_X__RECOVERABLE_RETRY_COUNT,
658                            result_pool));
659 
660   if (!*content)
661     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
662                              _("Can't read '%s'"),
663                              svn_dirent_local_style(fname, result_pool));
664 
665   return SVN_NO_ERROR;
666 }
667 
668 /* Reads a line from STREAM and converts it to a 64 bit integer to be
669  * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
670  * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
671  * error return.
672  * SCRATCH_POOL is used for temporary allocations.
673  */
674 svn_error_t *
svn_fs_x__read_number_from_stream(apr_int64_t * result,svn_boolean_t * hit_eof,svn_stream_t * stream,apr_pool_t * scratch_pool)675 svn_fs_x__read_number_from_stream(apr_int64_t *result,
676                                   svn_boolean_t *hit_eof,
677                                   svn_stream_t *stream,
678                                   apr_pool_t *scratch_pool)
679 {
680   svn_stringbuf_t *sb;
681   svn_boolean_t eof;
682   svn_error_t *err;
683 
684   SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
685   if (hit_eof)
686     *hit_eof = eof;
687   else
688     if (eof)
689       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
690 
691   if (!eof)
692     {
693       err = svn_cstring_atoi64(result, sb->data);
694       if (err)
695         return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
696                                  _("Number '%s' invalid or too large"),
697                                  sb->data);
698     }
699 
700   return SVN_NO_ERROR;
701 }
702 
703 svn_error_t *
svn_fs_x__move_into_place(const char * old_filename,const char * new_filename,const char * perms_reference,svn_fs_x__batch_fsync_t * batch,apr_pool_t * scratch_pool)704 svn_fs_x__move_into_place(const char *old_filename,
705                           const char *new_filename,
706                           const char *perms_reference,
707                           svn_fs_x__batch_fsync_t *batch,
708                           apr_pool_t *scratch_pool)
709 {
710   /* Copying permissions is a no-op on WIN32. */
711   SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, scratch_pool));
712 
713   /* We use specific 'fsyncing move' Win32 API calls on Windows while the
714    * directory update fsync is POSIX-only.  Moreover, there tend to be only
715    * a few moved files (1 or 2) per batch.
716    *
717    * Therefore, we use the platform-optimized "immediate" fsyncs on all
718    * non-POSIX platforms and the "scheduled" fsyncs on POSIX only.
719    */
720 #if defined(SVN_ON_POSIX)
721   /* Move the file into place. */
722   SVN_ERR(svn_io_file_rename2(old_filename, new_filename, FALSE,
723                               scratch_pool));
724 
725   /* Schedule for synchronization. */
726   SVN_ERR(svn_fs_x__batch_fsync_new_path(batch, new_filename, scratch_pool));
727 #else
728   SVN_ERR(svn_io_file_rename2(old_filename, new_filename, TRUE,
729                               scratch_pool));
730 #endif
731 
732   return SVN_NO_ERROR;
733 }
734