1289177Speter/* low_level.c --- low level r/w access to fs_x file structures
2289177Speter *
3289177Speter * ====================================================================
4289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
5289177Speter *    or more contributor license agreements.  See the NOTICE file
6289177Speter *    distributed with this work for additional information
7289177Speter *    regarding copyright ownership.  The ASF licenses this file
8289177Speter *    to you under the Apache License, Version 2.0 (the
9289177Speter *    "License"); you may not use this file except in compliance
10289177Speter *    with the License.  You may obtain a copy of the License at
11289177Speter *
12289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
13289177Speter *
14289177Speter *    Unless required by applicable law or agreed to in writing,
15289177Speter *    software distributed under the License is distributed on an
16289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17289177Speter *    KIND, either express or implied.  See the License for the
18289177Speter *    specific language governing permissions and limitations
19289177Speter *    under the License.
20289177Speter * ====================================================================
21289177Speter */
22289177Speter
23289177Speter#include "svn_private_config.h"
24289177Speter#include "svn_hash.h"
25289177Speter#include "svn_pools.h"
26289177Speter#include "svn_sorts.h"
27289177Speter#include "private/svn_sorts_private.h"
28289177Speter#include "private/svn_string_private.h"
29289177Speter#include "private/svn_subr_private.h"
30289177Speter#include "private/svn_fspath.h"
31289177Speter
32289177Speter#include "../libsvn_fs/fs-loader.h"
33289177Speter
34289177Speter#include "low_level.h"
35289177Speter#include "util.h"
36289177Speter#include "pack.h"
37289177Speter#include "cached_data.h"
38289177Speter
39289177Speter/* Headers used to describe node-revision in the revision file. */
40289177Speter#define HEADER_ID          "id"
41289177Speter#define HEADER_NODE        "node"
42289177Speter#define HEADER_COPY        "copy"
43289177Speter#define HEADER_TYPE        "type"
44289177Speter#define HEADER_COUNT       "count"
45289177Speter#define HEADER_PROPS       "props"
46289177Speter#define HEADER_TEXT        "text"
47289177Speter#define HEADER_CPATH       "cpath"
48289177Speter#define HEADER_PRED        "pred"
49289177Speter#define HEADER_COPYFROM    "copyfrom"
50289177Speter#define HEADER_COPYROOT    "copyroot"
51289177Speter#define HEADER_MINFO_HERE  "minfo-here"
52289177Speter#define HEADER_MINFO_CNT   "minfo-cnt"
53289177Speter
54289177Speter/* Kinds that a change can be. */
55289177Speter#define ACTION_MODIFY      "modify"
56289177Speter#define ACTION_ADD         "add"
57289177Speter#define ACTION_DELETE      "delete"
58289177Speter#define ACTION_REPLACE     "replace"
59289177Speter#define ACTION_RESET       "reset"
60289177Speter
61289177Speter/* True and False flags. */
62289177Speter#define FLAG_TRUE          "true"
63289177Speter#define FLAG_FALSE         "false"
64289177Speter
65289177Speter/* Kinds of representation. */
66289177Speter#define REP_DELTA          "DELTA"
67289177Speter
68289177Speter/* An arbitrary maximum path length, so clients can't run us out of memory
69289177Speter * by giving us arbitrarily large paths. */
70289177Speter#define FSX_MAX_PATH_LEN 4096
71289177Speter
72289177Speter/* The 256 is an arbitrary size large enough to hold the node id and the
73289177Speter * various flags. */
74289177Speter#define MAX_CHANGE_LINE_LEN FSX_MAX_PATH_LEN + 256
75289177Speter
76289177Speter/* Convert the C string in *TEXT to a revision number and return it in *REV.
77289177Speter * Overflows, negative values other than -1 and terminating characters other
78289177Speter * than 0x20 or 0x0 will cause an error.  Set *TEXT to the first char after
79289177Speter * the initial separator or to EOS.
80289177Speter */
81289177Speterstatic svn_error_t *
82289177Speterparse_revnum(svn_revnum_t *rev,
83289177Speter             const char **text)
84289177Speter{
85289177Speter  const char *string = *text;
86289177Speter  if ((string[0] == '-') && (string[1] == '1'))
87289177Speter    {
88289177Speter      *rev = SVN_INVALID_REVNUM;
89289177Speter      string += 2;
90289177Speter    }
91289177Speter  else
92289177Speter    {
93289177Speter      SVN_ERR(svn_revnum_parse(rev, string, &string));
94289177Speter    }
95289177Speter
96289177Speter  if (*string == ' ')
97289177Speter    ++string;
98289177Speter  else if (*string != '\0')
99289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
100289177Speter                            _("Invalid character in revision number"));
101289177Speter
102289177Speter  *text = string;
103289177Speter  return SVN_NO_ERROR;
104289177Speter}
105289177Speter
106289177Spetersvn_error_t *
107289177Spetersvn_fs_x__parse_footer(apr_off_t *l2p_offset,
108289177Speter                       svn_checksum_t **l2p_checksum,
109289177Speter                       apr_off_t *p2l_offset,
110289177Speter                       svn_checksum_t **p2l_checksum,
111289177Speter                       svn_stringbuf_t *footer,
112289177Speter                       svn_revnum_t rev,
113289177Speter                       apr_pool_t *result_pool)
114289177Speter{
115289177Speter  apr_int64_t val;
116289177Speter  char *last_str = footer->data;
117289177Speter
118289177Speter  /* Get the L2P offset. */
119289177Speter  const char *str = svn_cstring_tokenize(" ", &last_str);
120289177Speter  if (str == NULL)
121289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
122289177Speter                            _("Invalid revision footer"));
123289177Speter
124289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
125289177Speter  *l2p_offset = (apr_off_t)val;
126289177Speter
127289177Speter  /* Get the L2P checksum. */
128289177Speter  str = svn_cstring_tokenize(" ", &last_str);
129289177Speter  if (str == NULL)
130289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
131289177Speter                            _("Invalid revision footer"));
132289177Speter
133289177Speter  SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
134289177Speter                                 result_pool));
135289177Speter
136289177Speter  /* Get the P2L offset. */
137289177Speter  str = svn_cstring_tokenize(" ", &last_str);
138289177Speter  if (str == NULL)
139289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
140289177Speter                            _("Invalid revision footer"));
141289177Speter
142289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
143289177Speter  *p2l_offset = (apr_off_t)val;
144289177Speter
145289177Speter  /* Get the P2L checksum. */
146289177Speter  str = svn_cstring_tokenize(" ", &last_str);
147289177Speter  if (str == NULL)
148289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
149289177Speter                            _("Invalid revision footer"));
150289177Speter
151289177Speter  SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
152289177Speter                                 result_pool));
153289177Speter
154289177Speter  return SVN_NO_ERROR;
155289177Speter}
156289177Speter
157289177Spetersvn_stringbuf_t *
158289177Spetersvn_fs_x__unparse_footer(apr_off_t l2p_offset,
159289177Speter                         svn_checksum_t *l2p_checksum,
160289177Speter                         apr_off_t p2l_offset,
161289177Speter                         svn_checksum_t *p2l_checksum,
162289177Speter                         apr_pool_t *result_pool,
163289177Speter                         apr_pool_t *scratch_pool)
164289177Speter{
165289177Speter  return svn_stringbuf_createf(result_pool,
166289177Speter                               "%" APR_OFF_T_FMT " %s %" APR_OFF_T_FMT " %s",
167289177Speter                               l2p_offset,
168289177Speter                               svn_checksum_to_cstring(l2p_checksum,
169289177Speter                                                       scratch_pool),
170289177Speter                               p2l_offset,
171289177Speter                               svn_checksum_to_cstring(p2l_checksum,
172289177Speter                                                       scratch_pool));
173289177Speter}
174289177Speter
175289177Speter/* Given a revision file FILE that has been pre-positioned at the
176289177Speter   beginning of a Node-Rev header block, read in that header block and
177289177Speter   store it in the apr_hash_t HEADERS.  All allocations will be from
178289177Speter   RESULT_POOL. */
179289177Speterstatic svn_error_t *
180289177Speterread_header_block(apr_hash_t **headers,
181289177Speter                  svn_stream_t *stream,
182289177Speter                  apr_pool_t *result_pool)
183289177Speter{
184289177Speter  *headers = svn_hash__make(result_pool);
185289177Speter
186289177Speter  while (1)
187289177Speter    {
188289177Speter      svn_stringbuf_t *header_str;
189289177Speter      const char *name, *value;
190289177Speter      apr_size_t i = 0;
191289177Speter      apr_size_t name_len;
192289177Speter      svn_boolean_t eof;
193289177Speter
194289177Speter      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof,
195289177Speter                                  result_pool));
196289177Speter
197289177Speter      if (eof || header_str->len == 0)
198289177Speter        break; /* end of header block */
199289177Speter
200289177Speter      while (header_str->data[i] != ':')
201289177Speter        {
202289177Speter          if (header_str->data[i] == '\0')
203289177Speter            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
204289177Speter                                     _("Found malformed header '%s' in "
205289177Speter                                       "revision file"),
206289177Speter                                     header_str->data);
207289177Speter          i++;
208289177Speter        }
209289177Speter
210289177Speter      /* Create a 'name' string and point to it. */
211289177Speter      header_str->data[i] = '\0';
212289177Speter      name = header_str->data;
213289177Speter      name_len = i;
214289177Speter
215289177Speter      /* Check if we have enough data to parse. */
216289177Speter      if (i + 2 > header_str->len)
217289177Speter        {
218289177Speter          /* Restore the original line for the error. */
219289177Speter          header_str->data[i] = ':';
220289177Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
221289177Speter                                   _("Found malformed header '%s' in "
222289177Speter                                     "revision file"),
223289177Speter                                   header_str->data);
224289177Speter        }
225289177Speter
226289177Speter      /* Skip over the NULL byte and the space following it. */
227289177Speter      i += 2;
228289177Speter
229289177Speter      value = header_str->data + i;
230289177Speter
231289177Speter      /* header_str is safely in our pool, so we can use bits of it as
232289177Speter         key and value. */
233289177Speter      apr_hash_set(*headers, name, name_len, value);
234289177Speter    }
235289177Speter
236289177Speter  return SVN_NO_ERROR;
237289177Speter}
238289177Speter
239289177Spetersvn_error_t *
240289177Spetersvn_fs_x__parse_representation(svn_fs_x__representation_t **rep_p,
241289177Speter                               svn_stringbuf_t *text,
242289177Speter                               apr_pool_t *result_pool,
243289177Speter                               apr_pool_t *scratch_pool)
244289177Speter{
245289177Speter  svn_fs_x__representation_t *rep;
246289177Speter  char *str;
247289177Speter  apr_int64_t val;
248289177Speter  char *string = text->data;
249289177Speter  svn_checksum_t *checksum;
250289177Speter
251289177Speter  rep = apr_pcalloc(result_pool, sizeof(*rep));
252289177Speter  *rep_p = rep;
253289177Speter
254289177Speter  str = svn_cstring_tokenize(" ", &string);
255289177Speter  if (str == NULL)
256289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
257289177Speter                            _("Malformed text representation offset line in node-rev"));
258289177Speter
259289177Speter  SVN_ERR(svn_cstring_atoi64(&rep->id.change_set, str));
260289177Speter
261289177Speter  /* while in transactions, it is legal to simply write "-1" */
262289177Speter  if (rep->id.change_set == -1)
263289177Speter    return SVN_NO_ERROR;
264289177Speter
265289177Speter  str = svn_cstring_tokenize(" ", &string);
266289177Speter  if (str == NULL)
267289177Speter    {
268289177Speter      if (rep->id.change_set == SVN_FS_X__INVALID_CHANGE_SET)
269289177Speter        return SVN_NO_ERROR;
270289177Speter
271289177Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
272289177Speter                              _("Malformed text representation offset line in node-rev"));
273289177Speter    }
274289177Speter
275289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
276289177Speter  rep->id.number = (apr_off_t)val;
277289177Speter
278289177Speter  str = svn_cstring_tokenize(" ", &string);
279289177Speter  if (str == NULL)
280289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
281289177Speter                            _("Malformed text representation offset line in node-rev"));
282289177Speter
283289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
284289177Speter  rep->size = (svn_filesize_t)val;
285289177Speter
286289177Speter  str = svn_cstring_tokenize(" ", &string);
287289177Speter  if (str == NULL)
288289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
289289177Speter                            _("Malformed text representation offset line in node-rev"));
290289177Speter
291289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
292289177Speter  rep->expanded_size = (svn_filesize_t)val;
293289177Speter
294289177Speter  /* Read in the MD5 hash. */
295289177Speter  str = svn_cstring_tokenize(" ", &string);
296289177Speter  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
297289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
298289177Speter                            _("Malformed text representation offset line in node-rev"));
299289177Speter
300289177Speter  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_md5, str,
301289177Speter                                 scratch_pool));
302289177Speter  if (checksum)
303289177Speter    memcpy(rep->md5_digest, checksum->digest, sizeof(rep->md5_digest));
304289177Speter
305289177Speter  /* The remaining fields are only used for formats >= 4, so check that. */
306289177Speter  str = svn_cstring_tokenize(" ", &string);
307289177Speter  if (str == NULL)
308289177Speter    return SVN_NO_ERROR;
309289177Speter
310289177Speter  /* Read the SHA1 hash. */
311289177Speter  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
312289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
313289177Speter                            _("Malformed text representation offset line in node-rev"));
314289177Speter
315289177Speter  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
316289177Speter                                 scratch_pool));
317289177Speter  rep->has_sha1 = checksum != NULL;
318289177Speter  if (checksum)
319289177Speter    memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
320289177Speter
321289177Speter  return SVN_NO_ERROR;
322289177Speter}
323289177Speter
324289177Speter/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
325289177Speter   and adding an error message. */
326289177Speterstatic svn_error_t *
327289177Speterread_rep_offsets(svn_fs_x__representation_t **rep_p,
328289177Speter                 char *string,
329289177Speter                 const svn_fs_x__id_t *noderev_id,
330289177Speter                 apr_pool_t *result_pool,
331289177Speter                 apr_pool_t *scratch_pool)
332289177Speter{
333289177Speter  svn_error_t *err
334289177Speter    = svn_fs_x__parse_representation(rep_p,
335289177Speter                                     svn_stringbuf_create_wrap(string,
336289177Speter                                                               scratch_pool),
337289177Speter                                     result_pool,
338289177Speter                                     scratch_pool);
339289177Speter  if (err)
340289177Speter    {
341289177Speter      const svn_string_t *id_unparsed;
342289177Speter      const char *where;
343289177Speter
344289177Speter      id_unparsed = svn_fs_x__id_unparse(noderev_id, scratch_pool);
345289177Speter      where = apr_psprintf(scratch_pool,
346289177Speter                           _("While reading representation offsets "
347289177Speter                             "for node-revision '%s':"),
348289177Speter                           id_unparsed->data);
349289177Speter
350289177Speter      return svn_error_quick_wrap(err, where);
351289177Speter    }
352289177Speter
353289177Speter  return SVN_NO_ERROR;
354289177Speter}
355289177Speter
356289177Speter/* If PATH needs to be escaped, return an escaped version of it, allocated
357289177Speter * from RESULT_POOL. Otherwise, return PATH directly. */
358289177Speterstatic const char *
359289177Speterauto_escape_path(const char *path,
360289177Speter                 apr_pool_t *result_pool)
361289177Speter{
362289177Speter  apr_size_t len = strlen(path);
363289177Speter  apr_size_t i;
364289177Speter  const char esc = '\x1b';
365289177Speter
366289177Speter  for (i = 0; i < len; ++i)
367289177Speter    if (path[i] < ' ')
368289177Speter      {
369289177Speter        svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
370289177Speter                                                               result_pool);
371289177Speter        for (i = 0; i < len; ++i)
372289177Speter          if (path[i] < ' ')
373289177Speter            {
374289177Speter              svn_stringbuf_appendbyte(escaped, esc);
375289177Speter              svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
376289177Speter            }
377289177Speter          else
378289177Speter            {
379289177Speter              svn_stringbuf_appendbyte(escaped, path[i]);
380289177Speter            }
381289177Speter
382289177Speter        return escaped->data;
383289177Speter      }
384289177Speter
385289177Speter   return path;
386289177Speter}
387289177Speter
388289177Speter/* If PATH has been escaped, return the un-escaped version of it, allocated
389289177Speter * from RESULT_POOL. Otherwise, return PATH directly. */
390289177Speterstatic const char *
391289177Speterauto_unescape_path(const char *path,
392289177Speter                   apr_pool_t *result_pool)
393289177Speter{
394289177Speter  const char esc = '\x1b';
395289177Speter  if (strchr(path, esc))
396289177Speter    {
397289177Speter      apr_size_t len = strlen(path);
398289177Speter      apr_size_t i;
399289177Speter
400289177Speter      svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
401289177Speter                                                               result_pool);
402289177Speter      for (i = 0; i < len; ++i)
403289177Speter        if (path[i] == esc)
404289177Speter          svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
405289177Speter        else
406289177Speter          svn_stringbuf_appendbyte(unescaped, path[i]);
407289177Speter
408289177Speter      return unescaped->data;
409289177Speter    }
410289177Speter
411289177Speter   return path;
412289177Speter}
413289177Speter
414289177Speter/* Find entry HEADER_NAME in HEADERS and parse its value into *ID. */
415289177Speterstatic svn_error_t *
416289177Speterread_id_part(svn_fs_x__id_t *id,
417289177Speter             apr_hash_t *headers,
418289177Speter             const char *header_name)
419289177Speter{
420289177Speter  const char *value = svn_hash_gets(headers, header_name);
421289177Speter  if (value == NULL)
422289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
423289177Speter                             _("Missing %s field in node-rev"),
424289177Speter                             header_name);
425289177Speter
426289177Speter  SVN_ERR(svn_fs_x__id_parse(id, value));
427289177Speter  return SVN_NO_ERROR;
428289177Speter}
429289177Speter
430289177Spetersvn_error_t *
431289177Spetersvn_fs_x__read_noderev(svn_fs_x__noderev_t **noderev_p,
432289177Speter                       svn_stream_t *stream,
433289177Speter                       apr_pool_t *result_pool,
434289177Speter                       apr_pool_t *scratch_pool)
435289177Speter{
436289177Speter  apr_hash_t *headers;
437289177Speter  svn_fs_x__noderev_t *noderev;
438289177Speter  char *value;
439289177Speter  const char *noderev_id;
440289177Speter
441289177Speter  SVN_ERR(read_header_block(&headers, stream, scratch_pool));
442289177Speter  SVN_ERR(svn_stream_close(stream));
443289177Speter
444289177Speter  noderev = apr_pcalloc(result_pool, sizeof(*noderev));
445289177Speter
446289177Speter  /* for error messages later */
447289177Speter  noderev_id = svn_hash_gets(headers, HEADER_ID);
448289177Speter
449289177Speter  /* Read the node-rev id. */
450289177Speter  SVN_ERR(read_id_part(&noderev->noderev_id, headers, HEADER_ID));
451289177Speter  SVN_ERR(read_id_part(&noderev->node_id, headers, HEADER_NODE));
452289177Speter  SVN_ERR(read_id_part(&noderev->copy_id, headers, HEADER_COPY));
453289177Speter
454289177Speter  /* Read the type. */
455289177Speter  value = svn_hash_gets(headers, HEADER_TYPE);
456289177Speter
457289177Speter  if ((value == NULL) ||
458289177Speter      (   strcmp(value, SVN_FS_X__KIND_FILE)
459289177Speter       && strcmp(value, SVN_FS_X__KIND_DIR)))
460289177Speter    /* ### s/kind/type/ */
461289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
462289177Speter                             _("Missing kind field in node-rev '%s'"),
463289177Speter                             noderev_id);
464289177Speter
465289177Speter  noderev->kind = (strcmp(value, SVN_FS_X__KIND_FILE) == 0)
466289177Speter                ? svn_node_file
467289177Speter                : svn_node_dir;
468289177Speter
469289177Speter  /* Read the 'count' field. */
470289177Speter  value = svn_hash_gets(headers, HEADER_COUNT);
471289177Speter  if (value)
472289177Speter    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
473289177Speter  else
474289177Speter    noderev->predecessor_count = 0;
475289177Speter
476289177Speter  /* Get the properties location. */
477289177Speter  value = svn_hash_gets(headers, HEADER_PROPS);
478289177Speter  if (value)
479289177Speter    {
480289177Speter      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
481289177Speter                               &noderev->noderev_id, result_pool,
482289177Speter                               scratch_pool));
483289177Speter    }
484289177Speter
485289177Speter  /* Get the data location. */
486289177Speter  value = svn_hash_gets(headers, HEADER_TEXT);
487289177Speter  if (value)
488289177Speter    {
489289177Speter      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
490289177Speter                               &noderev->noderev_id, result_pool,
491289177Speter                               scratch_pool));
492289177Speter    }
493289177Speter
494289177Speter  /* Get the created path. */
495289177Speter  value = svn_hash_gets(headers, HEADER_CPATH);
496289177Speter  if (value == NULL)
497289177Speter    {
498289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
499289177Speter                               _("Missing cpath field in node-rev '%s'"),
500289177Speter                               noderev_id);
501289177Speter    }
502289177Speter  else
503289177Speter    {
504289177Speter      if (!svn_fspath__is_canonical(value))
505289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
506289177Speter                            _("Non-canonical cpath field in node-rev '%s'"),
507289177Speter                            noderev_id);
508289177Speter
509289177Speter      noderev->created_path = auto_unescape_path(apr_pstrdup(result_pool,
510289177Speter                                                              value),
511289177Speter                                                 result_pool);
512289177Speter    }
513289177Speter
514289177Speter  /* Get the predecessor ID. */
515289177Speter  value = svn_hash_gets(headers, HEADER_PRED);
516289177Speter  if (value)
517289177Speter    SVN_ERR(svn_fs_x__id_parse(&noderev->predecessor_id, value));
518289177Speter  else
519289177Speter    svn_fs_x__id_reset(&noderev->predecessor_id);
520289177Speter
521289177Speter  /* Get the copyroot. */
522289177Speter  value = svn_hash_gets(headers, HEADER_COPYROOT);
523289177Speter  if (value == NULL)
524289177Speter    {
525289177Speter      noderev->copyroot_path = noderev->created_path;
526289177Speter      noderev->copyroot_rev
527289177Speter        = svn_fs_x__get_revnum(noderev->noderev_id.change_set);
528289177Speter    }
529289177Speter  else
530289177Speter    {
531289177Speter      SVN_ERR(parse_revnum(&noderev->copyroot_rev, (const char **)&value));
532289177Speter
533289177Speter      if (!svn_fspath__is_canonical(value))
534289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
535289177Speter                                 _("Malformed copyroot line in node-rev '%s'"),
536289177Speter                                 noderev_id);
537289177Speter      noderev->copyroot_path = auto_unescape_path(apr_pstrdup(result_pool,
538289177Speter                                                              value),
539289177Speter                                                  result_pool);
540289177Speter    }
541289177Speter
542289177Speter  /* Get the copyfrom. */
543289177Speter  value = svn_hash_gets(headers, HEADER_COPYFROM);
544289177Speter  if (value == NULL)
545289177Speter    {
546289177Speter      noderev->copyfrom_path = NULL;
547289177Speter      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
548289177Speter    }
549289177Speter  else
550289177Speter    {
551289177Speter      SVN_ERR(parse_revnum(&noderev->copyfrom_rev, (const char **)&value));
552289177Speter
553289177Speter      if (*value == 0)
554289177Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
555289177Speter                                 _("Malformed copyfrom line in node-rev '%s'"),
556289177Speter                                 noderev_id);
557289177Speter      noderev->copyfrom_path = auto_unescape_path(apr_pstrdup(result_pool,
558289177Speter                                                              value),
559289177Speter                                                  result_pool);
560289177Speter    }
561289177Speter
562289177Speter  /* Get the mergeinfo count. */
563289177Speter  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
564289177Speter  if (value)
565289177Speter    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
566289177Speter  else
567289177Speter    noderev->mergeinfo_count = 0;
568289177Speter
569289177Speter  /* Get whether *this* node has mergeinfo. */
570289177Speter  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
571289177Speter  noderev->has_mergeinfo = (value != NULL);
572289177Speter
573289177Speter  *noderev_p = noderev;
574289177Speter
575289177Speter  return SVN_NO_ERROR;
576289177Speter}
577289177Speter
578289177Speter/* Return a textual representation of the DIGEST of given KIND.
579289177Speter * If IS_NULL is TRUE, no digest is available.
580289177Speter * Allocate the result in RESULT_POOL.
581289177Speter */
582289177Speterstatic const char *
583289177Speterformat_digest(const unsigned char *digest,
584289177Speter              svn_checksum_kind_t kind,
585289177Speter              svn_boolean_t is_null,
586289177Speter              apr_pool_t *result_pool)
587289177Speter{
588289177Speter  svn_checksum_t checksum;
589289177Speter  checksum.digest = digest;
590289177Speter  checksum.kind = kind;
591289177Speter
592289177Speter  if (is_null)
593289177Speter    return "(null)";
594289177Speter
595289177Speter  return svn_checksum_to_cstring_display(&checksum, result_pool);
596289177Speter}
597289177Speter
598289177Spetersvn_stringbuf_t *
599289177Spetersvn_fs_x__unparse_representation(svn_fs_x__representation_t *rep,
600289177Speter                                 svn_boolean_t mutable_rep_truncated,
601289177Speter                                 apr_pool_t *result_pool,
602289177Speter                                 apr_pool_t *scratch_pool)
603289177Speter{
604289177Speter  if (!rep->has_sha1)
605289177Speter    return svn_stringbuf_createf
606289177Speter            (result_pool,
607289177Speter             "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
608289177Speter             " %" SVN_FILESIZE_T_FMT " %s",
609289177Speter             rep->id.change_set, rep->id.number, rep->size,
610289177Speter             rep->expanded_size,
611289177Speter             format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
612289177Speter                           scratch_pool));
613289177Speter
614289177Speter  return svn_stringbuf_createf
615289177Speter          (result_pool,
616289177Speter           "%" APR_INT64_T_FMT " %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
617289177Speter           " %" SVN_FILESIZE_T_FMT " %s %s",
618289177Speter           rep->id.change_set, rep->id.number, rep->size,
619289177Speter           rep->expanded_size,
620289177Speter           format_digest(rep->md5_digest, svn_checksum_md5,
621289177Speter                         FALSE, scratch_pool),
622289177Speter           format_digest(rep->sha1_digest, svn_checksum_sha1,
623289177Speter                         !rep->has_sha1, scratch_pool));
624289177Speter}
625289177Speter
626289177Speter
627289177Spetersvn_error_t *
628289177Spetersvn_fs_x__write_noderev(svn_stream_t *outfile,
629289177Speter                        svn_fs_x__noderev_t *noderev,
630289177Speter                        apr_pool_t *scratch_pool)
631289177Speter{
632289177Speter  svn_string_t *str_id;
633289177Speter
634289177Speter  str_id = svn_fs_x__id_unparse(&noderev->noderev_id, scratch_pool);
635289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_ID ": %s\n",
636289177Speter                            str_id->data));
637289177Speter  str_id = svn_fs_x__id_unparse(&noderev->node_id, scratch_pool);
638289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_NODE ": %s\n",
639289177Speter                            str_id->data));
640289177Speter  str_id = svn_fs_x__id_unparse(&noderev->copy_id, scratch_pool);
641289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPY ": %s\n",
642289177Speter                            str_id->data));
643289177Speter
644289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TYPE ": %s\n",
645289177Speter                            (noderev->kind == svn_node_file) ?
646289177Speter                            SVN_FS_X__KIND_FILE : SVN_FS_X__KIND_DIR));
647289177Speter
648289177Speter  if (svn_fs_x__id_used(&noderev->predecessor_id))
649289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PRED ": %s\n",
650289177Speter                              svn_fs_x__id_unparse(&noderev->predecessor_id,
651289177Speter                                                   scratch_pool)->data));
652289177Speter
653289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COUNT ": %d\n",
654289177Speter                            noderev->predecessor_count));
655289177Speter
656289177Speter  if (noderev->data_rep)
657289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_TEXT ": %s\n",
658289177Speter                              svn_fs_x__unparse_representation
659289177Speter                                (noderev->data_rep,
660289177Speter                                 noderev->kind == svn_node_dir,
661289177Speter                                 scratch_pool, scratch_pool)->data));
662289177Speter
663289177Speter  if (noderev->prop_rep)
664289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_PROPS ": %s\n",
665289177Speter                              svn_fs_x__unparse_representation
666289177Speter                                (noderev->prop_rep,
667289177Speter                                 TRUE, scratch_pool, scratch_pool)->data));
668289177Speter
669289177Speter  SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_CPATH ": %s\n",
670289177Speter                            auto_escape_path(noderev->created_path,
671289177Speter                                             scratch_pool)));
672289177Speter
673289177Speter  if (noderev->copyfrom_path)
674289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYFROM ": %ld"
675289177Speter                              " %s\n",
676289177Speter                              noderev->copyfrom_rev,
677289177Speter                              auto_escape_path(noderev->copyfrom_path,
678289177Speter                                               scratch_pool)));
679289177Speter
680289177Speter  if (   (   noderev->copyroot_rev
681289177Speter           != svn_fs_x__get_revnum(noderev->noderev_id.change_set))
682289177Speter      || (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
683289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_COPYROOT ": %ld"
684289177Speter                              " %s\n",
685289177Speter                              noderev->copyroot_rev,
686289177Speter                              auto_escape_path(noderev->copyroot_path,
687289177Speter                                               scratch_pool)));
688289177Speter
689289177Speter  if (noderev->mergeinfo_count > 0)
690289177Speter    SVN_ERR(svn_stream_printf(outfile, scratch_pool, HEADER_MINFO_CNT ": %"
691289177Speter                              APR_INT64_T_FMT "\n",
692289177Speter                              noderev->mergeinfo_count));
693289177Speter
694289177Speter  if (noderev->has_mergeinfo)
695289177Speter    SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
696289177Speter
697289177Speter  return svn_stream_puts(outfile, "\n");
698289177Speter}
699289177Speter
700289177Spetersvn_error_t *
701289177Spetersvn_fs_x__read_rep_header(svn_fs_x__rep_header_t **header,
702289177Speter                          svn_stream_t *stream,
703289177Speter                          apr_pool_t *result_pool,
704289177Speter                          apr_pool_t *scratch_pool)
705289177Speter{
706289177Speter  svn_stringbuf_t *buffer;
707289177Speter  char *str, *last_str;
708289177Speter  apr_int64_t val;
709289177Speter  svn_boolean_t eol = FALSE;
710289177Speter
711289177Speter  SVN_ERR(svn_stream_readline(stream, &buffer, "\n", &eol, scratch_pool));
712289177Speter
713289177Speter  *header = apr_pcalloc(result_pool, sizeof(**header));
714289177Speter  (*header)->header_size = buffer->len + 1;
715289177Speter  if (strcmp(buffer->data, REP_DELTA) == 0)
716289177Speter    {
717289177Speter      /* This is a delta against the empty stream. */
718289177Speter      (*header)->type = svn_fs_x__rep_self_delta;
719289177Speter      return SVN_NO_ERROR;
720289177Speter    }
721289177Speter
722289177Speter  (*header)->type = svn_fs_x__rep_delta;
723289177Speter
724289177Speter  /* We have hopefully a DELTA vs. a non-empty base revision. */
725289177Speter  last_str = buffer->data;
726289177Speter  str = svn_cstring_tokenize(" ", &last_str);
727289177Speter  if (! str || (strcmp(str, REP_DELTA) != 0))
728289177Speter    goto error;
729289177Speter
730289177Speter  SVN_ERR(parse_revnum(&(*header)->base_revision, (const char **)&last_str));
731289177Speter
732289177Speter  str = svn_cstring_tokenize(" ", &last_str);
733289177Speter  if (! str)
734289177Speter    goto error;
735289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
736289177Speter  (*header)->base_item_index = (apr_off_t)val;
737289177Speter
738289177Speter  str = svn_cstring_tokenize(" ", &last_str);
739289177Speter  if (! str)
740289177Speter    goto error;
741289177Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
742289177Speter  (*header)->base_length = (svn_filesize_t)val;
743289177Speter
744289177Speter  return SVN_NO_ERROR;
745289177Speter
746289177Speter error:
747289177Speter  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
748289177Speter                           _("Malformed representation header"));
749289177Speter}
750289177Speter
751289177Spetersvn_error_t *
752289177Spetersvn_fs_x__write_rep_header(svn_fs_x__rep_header_t *header,
753289177Speter                           svn_stream_t *stream,
754289177Speter                           apr_pool_t *scratch_pool)
755289177Speter{
756289177Speter  const char *text;
757289177Speter
758289177Speter  switch (header->type)
759289177Speter    {
760289177Speter      case svn_fs_x__rep_self_delta:
761289177Speter        text = REP_DELTA "\n";
762289177Speter        break;
763289177Speter
764289177Speter      default:
765289177Speter        text = apr_psprintf(scratch_pool, REP_DELTA " %ld %" APR_OFF_T_FMT
766289177Speter                                          " %" SVN_FILESIZE_T_FMT "\n",
767289177Speter                            header->base_revision, header->base_item_index,
768289177Speter                            header->base_length);
769289177Speter    }
770289177Speter
771289177Speter  return svn_error_trace(svn_stream_puts(stream, text));
772289177Speter}
773289177Speter
774289177Speter/* Read the next entry in the changes record from file FILE and store
775289177Speter   the resulting change in *CHANGE_P.  If there is no next record,
776289177Speter   store NULL there.  Perform all allocations from POOL. */
777289177Speterstatic svn_error_t *
778289177Speterread_change(svn_fs_x__change_t **change_p,
779289177Speter            svn_stream_t *stream,
780289177Speter            apr_pool_t *result_pool,
781289177Speter            apr_pool_t *scratch_pool)
782289177Speter{
783289177Speter  svn_stringbuf_t *line;
784289177Speter  svn_boolean_t eof = TRUE;
785289177Speter  svn_fs_x__change_t *change;
786289177Speter  char *str, *last_str, *kind_str;
787289177Speter
788289177Speter  /* Default return value. */
789289177Speter  *change_p = NULL;
790289177Speter
791289177Speter  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
792289177Speter
793289177Speter  /* Check for a blank line. */
794289177Speter  if (eof || (line->len == 0))
795289177Speter    return SVN_NO_ERROR;
796289177Speter
797289177Speter  change = apr_pcalloc(result_pool, sizeof(*change));
798289177Speter  last_str = line->data;
799289177Speter
800289177Speter  /* Get the node-id of the change. */
801289177Speter  str = svn_cstring_tokenize(" ", &last_str);
802289177Speter  if (str == NULL)
803289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
804289177Speter                            _("Invalid changes line in rev-file"));
805289177Speter
806289177Speter  SVN_ERR(svn_fs_x__id_parse(&change->noderev_id, str));
807289177Speter
808289177Speter  /* Get the change type. */
809289177Speter  str = svn_cstring_tokenize(" ", &last_str);
810289177Speter  if (str == NULL)
811289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
812289177Speter                            _("Invalid changes line in rev-file"));
813289177Speter
814289177Speter  /* Don't bother to check the format number before looking for
815289177Speter   * node-kinds: just read them if you find them. */
816289177Speter  change->node_kind = svn_node_unknown;
817289177Speter  kind_str = strchr(str, '-');
818289177Speter  if (kind_str)
819289177Speter    {
820289177Speter      /* Cap off the end of "str" (the action). */
821289177Speter      *kind_str = '\0';
822289177Speter      kind_str++;
823289177Speter      if (strcmp(kind_str, SVN_FS_X__KIND_FILE) == 0)
824289177Speter        change->node_kind = svn_node_file;
825289177Speter      else if (strcmp(kind_str, SVN_FS_X__KIND_DIR) == 0)
826289177Speter        change->node_kind = svn_node_dir;
827289177Speter      else
828289177Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
829289177Speter                                _("Invalid changes line in rev-file"));
830289177Speter    }
831289177Speter
832289177Speter  if (strcmp(str, ACTION_MODIFY) == 0)
833289177Speter    {
834289177Speter      change->change_kind = svn_fs_path_change_modify;
835289177Speter    }
836289177Speter  else if (strcmp(str, ACTION_ADD) == 0)
837289177Speter    {
838289177Speter      change->change_kind = svn_fs_path_change_add;
839289177Speter    }
840289177Speter  else if (strcmp(str, ACTION_DELETE) == 0)
841289177Speter    {
842289177Speter      change->change_kind = svn_fs_path_change_delete;
843289177Speter    }
844289177Speter  else if (strcmp(str, ACTION_REPLACE) == 0)
845289177Speter    {
846289177Speter      change->change_kind = svn_fs_path_change_replace;
847289177Speter    }
848289177Speter  else if (strcmp(str, ACTION_RESET) == 0)
849289177Speter    {
850289177Speter      change->change_kind = svn_fs_path_change_reset;
851289177Speter    }
852289177Speter  else
853289177Speter    {
854289177Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
855289177Speter                              _("Invalid change kind in rev file"));
856289177Speter    }
857289177Speter
858289177Speter  /* Get the text-mod flag. */
859289177Speter  str = svn_cstring_tokenize(" ", &last_str);
860289177Speter  if (str == NULL)
861289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
862289177Speter                            _("Invalid changes line in rev-file"));
863289177Speter
864289177Speter  if (strcmp(str, FLAG_TRUE) == 0)
865289177Speter    {
866289177Speter      change->text_mod = TRUE;
867289177Speter    }
868289177Speter  else if (strcmp(str, FLAG_FALSE) == 0)
869289177Speter    {
870289177Speter      change->text_mod = FALSE;
871289177Speter    }
872289177Speter  else
873289177Speter    {
874289177Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
875289177Speter                              _("Invalid text-mod flag in rev-file"));
876289177Speter    }
877289177Speter
878289177Speter  /* Get the prop-mod flag. */
879289177Speter  str = svn_cstring_tokenize(" ", &last_str);
880289177Speter  if (str == NULL)
881289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
882289177Speter                            _("Invalid changes line in rev-file"));
883289177Speter
884289177Speter  if (strcmp(str, FLAG_TRUE) == 0)
885289177Speter    {
886289177Speter      change->prop_mod = TRUE;
887289177Speter    }
888289177Speter  else if (strcmp(str, FLAG_FALSE) == 0)
889289177Speter    {
890289177Speter      change->prop_mod = FALSE;
891289177Speter    }
892289177Speter  else
893289177Speter    {
894289177Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
895289177Speter                              _("Invalid prop-mod flag in rev-file"));
896289177Speter    }
897289177Speter
898289177Speter  /* Get the mergeinfo-mod flag. */
899289177Speter  str = svn_cstring_tokenize(" ", &last_str);
900289177Speter  if (str == NULL)
901289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
902289177Speter                            _("Invalid changes line in rev-file"));
903289177Speter
904289177Speter  if (strcmp(str, FLAG_TRUE) == 0)
905289177Speter    {
906289177Speter      change->mergeinfo_mod = svn_tristate_true;
907289177Speter    }
908289177Speter  else if (strcmp(str, FLAG_FALSE) == 0)
909289177Speter    {
910289177Speter      change->mergeinfo_mod = svn_tristate_false;
911289177Speter    }
912289177Speter  else
913289177Speter    {
914289177Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
915289177Speter                              _("Invalid mergeinfo-mod flag in rev-file"));
916289177Speter    }
917289177Speter
918289177Speter  /* Get the changed path. */
919289177Speter  if (!svn_fspath__is_canonical(last_str))
920289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
921289177Speter                            _("Invalid path in changes line"));
922289177Speter
923289177Speter  change->path.data = auto_unescape_path(apr_pstrmemdup(result_pool,
924289177Speter                                                        last_str,
925289177Speter                                                        strlen(last_str)),
926289177Speter                                         result_pool);
927289177Speter  change->path.len = strlen(change->path.data);
928289177Speter
929289177Speter  /* Read the next line, the copyfrom line. */
930289177Speter  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
931289177Speter  change->copyfrom_known = TRUE;
932289177Speter  if (eof || line->len == 0)
933289177Speter    {
934289177Speter      change->copyfrom_rev = SVN_INVALID_REVNUM;
935289177Speter      change->copyfrom_path = NULL;
936289177Speter    }
937289177Speter  else
938289177Speter    {
939289177Speter      last_str = line->data;
940289177Speter      SVN_ERR(parse_revnum(&change->copyfrom_rev, (const char **)&last_str));
941289177Speter
942289177Speter      if (!svn_fspath__is_canonical(last_str))
943289177Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
944289177Speter                                _("Invalid copy-from path in changes line"));
945289177Speter
946289177Speter      change->copyfrom_path = auto_unescape_path(last_str, result_pool);
947289177Speter    }
948289177Speter
949289177Speter  *change_p = change;
950289177Speter
951289177Speter  return SVN_NO_ERROR;
952289177Speter}
953289177Speter
954289177Spetersvn_error_t *
955289177Spetersvn_fs_x__read_changes(apr_array_header_t **changes,
956289177Speter                       svn_stream_t *stream,
957289177Speter                       apr_pool_t *result_pool,
958289177Speter                       apr_pool_t *scratch_pool)
959289177Speter{
960289177Speter  svn_fs_x__change_t *change;
961289177Speter  apr_pool_t *iterpool;
962289177Speter
963289177Speter  /* Pre-allocate enough room for most change lists.
964289177Speter     (will be auto-expanded as necessary).
965289177Speter
966289177Speter     Chose the default to just below 2^N such that the doubling reallocs
967289177Speter     will request roughly 2^M bytes from the OS without exceeding the
968289177Speter     respective two-power by just a few bytes (leaves room array and APR
969289177Speter     node overhead for large enough M).
970289177Speter   */
971289177Speter  *changes = apr_array_make(result_pool, 63, sizeof(svn_fs_x__change_t *));
972289177Speter
973289177Speter  SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
974289177Speter  iterpool = svn_pool_create(scratch_pool);
975289177Speter  while (change)
976289177Speter    {
977289177Speter      APR_ARRAY_PUSH(*changes, svn_fs_x__change_t*) = change;
978289177Speter      SVN_ERR(read_change(&change, stream, result_pool, iterpool));
979289177Speter      svn_pool_clear(iterpool);
980289177Speter    }
981289177Speter  svn_pool_destroy(iterpool);
982289177Speter
983289177Speter  return SVN_NO_ERROR;
984289177Speter}
985289177Speter
986289177Spetersvn_error_t *
987289177Spetersvn_fs_x__read_changes_incrementally(svn_stream_t *stream,
988289177Speter                                     svn_fs_x__change_receiver_t
989289177Speter                                       change_receiver,
990289177Speter                                     void *change_receiver_baton,
991289177Speter                                     apr_pool_t *scratch_pool)
992289177Speter{
993289177Speter  svn_fs_x__change_t *change;
994289177Speter  apr_pool_t *iterpool;
995289177Speter
996289177Speter  iterpool = svn_pool_create(scratch_pool);
997289177Speter  do
998289177Speter    {
999289177Speter      svn_pool_clear(iterpool);
1000289177Speter
1001289177Speter      SVN_ERR(read_change(&change, stream, iterpool, iterpool));
1002289177Speter      if (change)
1003289177Speter        SVN_ERR(change_receiver(change_receiver_baton, change, iterpool));
1004289177Speter    }
1005289177Speter  while (change);
1006289177Speter  svn_pool_destroy(iterpool);
1007289177Speter
1008289177Speter  return SVN_NO_ERROR;
1009289177Speter}
1010289177Speter
1011289177Speter/* Write a single change entry, path PATH, change CHANGE, to STREAM.
1012289177Speter
1013289177Speter   All temporary allocations are in SCRATCH_POOL. */
1014289177Speterstatic svn_error_t *
1015289177Speterwrite_change_entry(svn_stream_t *stream,
1016289177Speter                   svn_fs_x__change_t *change,
1017289177Speter                   apr_pool_t *scratch_pool)
1018289177Speter{
1019289177Speter  const char *idstr;
1020289177Speter  const char *change_string = NULL;
1021289177Speter  const char *kind_string = "";
1022289177Speter  svn_stringbuf_t *buf;
1023289177Speter  apr_size_t len;
1024289177Speter
1025289177Speter  switch (change->change_kind)
1026289177Speter    {
1027289177Speter    case svn_fs_path_change_modify:
1028289177Speter      change_string = ACTION_MODIFY;
1029289177Speter      break;
1030289177Speter    case svn_fs_path_change_add:
1031289177Speter      change_string = ACTION_ADD;
1032289177Speter      break;
1033289177Speter    case svn_fs_path_change_delete:
1034289177Speter      change_string = ACTION_DELETE;
1035289177Speter      break;
1036289177Speter    case svn_fs_path_change_replace:
1037289177Speter      change_string = ACTION_REPLACE;
1038289177Speter      break;
1039289177Speter    case svn_fs_path_change_reset:
1040289177Speter      change_string = ACTION_RESET;
1041289177Speter      break;
1042289177Speter    default:
1043289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1044289177Speter                               _("Invalid change type %d"),
1045289177Speter                               change->change_kind);
1046289177Speter    }
1047289177Speter
1048289177Speter  idstr = svn_fs_x__id_unparse(&change->noderev_id, scratch_pool)->data;
1049289177Speter
1050289177Speter  SVN_ERR_ASSERT(change->node_kind == svn_node_dir
1051289177Speter                 || change->node_kind == svn_node_file);
1052289177Speter  kind_string = apr_psprintf(scratch_pool, "-%s",
1053289177Speter                             change->node_kind == svn_node_dir
1054289177Speter                             ? SVN_FS_X__KIND_DIR
1055289177Speter                             : SVN_FS_X__KIND_FILE);
1056289177Speter
1057289177Speter  buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s %s %s\n",
1058289177Speter                              idstr, change_string, kind_string,
1059289177Speter                              change->text_mod ? FLAG_TRUE : FLAG_FALSE,
1060289177Speter                              change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
1061289177Speter                              change->mergeinfo_mod == svn_tristate_true
1062289177Speter                                               ? FLAG_TRUE : FLAG_FALSE,
1063289177Speter                              auto_escape_path(change->path.data, scratch_pool));
1064289177Speter
1065289177Speter  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
1066289177Speter    {
1067289177Speter      svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
1068289177Speter                               change->copyfrom_rev,
1069289177Speter                               auto_escape_path(change->copyfrom_path,
1070289177Speter                                                scratch_pool)));
1071289177Speter    }
1072289177Speter
1073289177Speter  svn_stringbuf_appendbyte(buf, '\n');
1074289177Speter
1075289177Speter  /* Write all change info in one write call. */
1076289177Speter  len = buf->len;
1077289177Speter  return svn_error_trace(svn_stream_write(stream, buf->data, &len));
1078289177Speter}
1079289177Speter
1080289177Spetersvn_error_t *
1081289177Spetersvn_fs_x__write_changes(svn_stream_t *stream,
1082289177Speter                        svn_fs_t *fs,
1083289177Speter                        apr_hash_t *changes,
1084289177Speter                        svn_boolean_t terminate_list,
1085289177Speter                        apr_pool_t *scratch_pool)
1086289177Speter{
1087289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1088289177Speter  apr_array_header_t *sorted_changed_paths;
1089289177Speter  int i;
1090289177Speter
1091289177Speter  /* For the sake of the repository administrator sort the changes so
1092289177Speter     that the final file is deterministic and repeatable, however the
1093289177Speter     rest of the FSX code doesn't require any particular order here.
1094289177Speter
1095289177Speter     Also, this sorting is only effective in writing all entries with
1096289177Speter     a single call as write_final_changed_path_info() does.  For the
1097289177Speter     list being written incrementally during transaction, we actually
1098289177Speter     *must not* change the order of entries from different calls.
1099289177Speter   */
1100289177Speter  sorted_changed_paths = svn_sort__hash(changes,
1101289177Speter                                        svn_sort_compare_items_lexically,
1102289177Speter                                        scratch_pool);
1103289177Speter
1104289177Speter  /* Write all items to disk in the new order. */
1105289177Speter  for (i = 0; i < sorted_changed_paths->nelts; ++i)
1106289177Speter    {
1107289177Speter      svn_fs_x__change_t *change;
1108289177Speter
1109289177Speter      svn_pool_clear(iterpool);
1110289177Speter      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
1111289177Speter
1112289177Speter      /* Write out the new entry into the final rev-file. */
1113289177Speter      SVN_ERR(write_change_entry(stream, change, iterpool));
1114289177Speter    }
1115289177Speter
1116289177Speter  if (terminate_list)
1117289177Speter    svn_stream_puts(stream, "\n");
1118289177Speter
1119289177Speter  svn_pool_destroy(iterpool);
1120289177Speter
1121289177Speter  return SVN_NO_ERROR;
1122289177Speter}
1123289177Speter
1124