1251881Speter/*
2251881Speter * old-and-busted.c:  routines for reading pre-1.7 working copies.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include "svn_time.h"
27251881Speter#include "svn_xml.h"
28251881Speter#include "svn_dirent_uri.h"
29251881Speter#include "svn_hash.h"
30251881Speter#include "svn_path.h"
31251881Speter#include "svn_ctype.h"
32251881Speter#include "svn_pools.h"
33251881Speter
34251881Speter#include "wc.h"
35251881Speter#include "adm_files.h"
36251881Speter#include "entries.h"
37251881Speter#include "lock.h"
38251881Speter
39251881Speter#include "private/svn_wc_private.h"
40251881Speter#include "svn_private_config.h"
41251881Speter
42251881Speter
43251881Speter/* Within the (old) entries file, boolean values have a specific string
44251881Speter   value (thus, TRUE), or they are missing (for FALSE). Below are the
45251881Speter   values for each of the booleans stored.  */
46251881Speter#define ENTRIES_BOOL_COPIED     "copied"
47251881Speter#define ENTRIES_BOOL_DELETED    "deleted"
48251881Speter#define ENTRIES_BOOL_ABSENT     "absent"
49251881Speter#define ENTRIES_BOOL_INCOMPLETE "incomplete"
50251881Speter#define ENTRIES_BOOL_KEEP_LOCAL "keep-local"
51251881Speter
52251881Speter/* Tag names used in our old XML entries file.  */
53251881Speter#define ENTRIES_TAG_ENTRY "entry"
54251881Speter
55251881Speter/* Attribute names used in our old XML entries file.  */
56251881Speter#define ENTRIES_ATTR_NAME               "name"
57251881Speter#define ENTRIES_ATTR_REPOS              "repos"
58251881Speter#define ENTRIES_ATTR_UUID               "uuid"
59251881Speter#define ENTRIES_ATTR_INCOMPLETE         "incomplete"
60251881Speter#define ENTRIES_ATTR_LOCK_TOKEN         "lock-token"
61251881Speter#define ENTRIES_ATTR_LOCK_OWNER         "lock-owner"
62251881Speter#define ENTRIES_ATTR_LOCK_COMMENT       "lock-comment"
63251881Speter#define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date"
64251881Speter#define ENTRIES_ATTR_DELETED            "deleted"
65251881Speter#define ENTRIES_ATTR_ABSENT             "absent"
66251881Speter#define ENTRIES_ATTR_CMT_REV            "committed-rev"
67251881Speter#define ENTRIES_ATTR_CMT_DATE           "committed-date"
68251881Speter#define ENTRIES_ATTR_CMT_AUTHOR         "last-author"
69251881Speter#define ENTRIES_ATTR_REVISION           "revision"
70251881Speter#define ENTRIES_ATTR_URL                "url"
71251881Speter#define ENTRIES_ATTR_KIND               "kind"
72251881Speter#define ENTRIES_ATTR_SCHEDULE           "schedule"
73251881Speter#define ENTRIES_ATTR_COPIED             "copied"
74251881Speter#define ENTRIES_ATTR_COPYFROM_URL       "copyfrom-url"
75251881Speter#define ENTRIES_ATTR_COPYFROM_REV       "copyfrom-rev"
76251881Speter#define ENTRIES_ATTR_CHECKSUM           "checksum"
77251881Speter#define ENTRIES_ATTR_WORKING_SIZE       "working-size"
78251881Speter#define ENTRIES_ATTR_TEXT_TIME          "text-time"
79251881Speter#define ENTRIES_ATTR_CONFLICT_OLD       "conflict-old" /* saved old file */
80251881Speter#define ENTRIES_ATTR_CONFLICT_NEW       "conflict-new" /* saved new file */
81251881Speter#define ENTRIES_ATTR_CONFLICT_WRK       "conflict-wrk" /* saved wrk file */
82251881Speter#define ENTRIES_ATTR_PREJFILE           "prop-reject-file"
83251881Speter
84251881Speter/* Attribute values used in our old XML entries file.  */
85251881Speter#define ENTRIES_VALUE_FILE     "file"
86251881Speter#define ENTRIES_VALUE_DIR      "dir"
87251881Speter#define ENTRIES_VALUE_ADD      "add"
88251881Speter#define ENTRIES_VALUE_DELETE   "delete"
89251881Speter#define ENTRIES_VALUE_REPLACE  "replace"
90251881Speter
91251881Speter
92251881Speter/* */
93251881Speterstatic svn_wc_entry_t *
94251881Speteralloc_entry(apr_pool_t *pool)
95251881Speter{
96251881Speter  svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
97251881Speter  entry->revision = SVN_INVALID_REVNUM;
98251881Speter  entry->copyfrom_rev = SVN_INVALID_REVNUM;
99251881Speter  entry->cmt_rev = SVN_INVALID_REVNUM;
100251881Speter  entry->kind = svn_node_none;
101251881Speter  entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
102251881Speter  entry->depth = svn_depth_infinity;
103251881Speter  entry->file_external_path = NULL;
104251881Speter  entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
105251881Speter  entry->file_external_rev.kind = svn_opt_revision_unspecified;
106251881Speter  return entry;
107251881Speter}
108251881Speter
109251881Speter
110251881Speter
111251881Speter/* Read an escaped byte on the form 'xHH' from [*BUF, END), placing
112251881Speter   the byte in *RESULT.  Advance *BUF to point after the escape
113251881Speter   sequence. */
114251881Speterstatic svn_error_t *
115251881Speterread_escaped(char *result, char **buf, const char *end)
116251881Speter{
117251881Speter  apr_uint64_t val;
118251881Speter  char digits[3];
119251881Speter
120251881Speter  if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1])
121251881Speter      || ! svn_ctype_isxdigit((*buf)[2]))
122251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
123251881Speter                            _("Invalid escape sequence"));
124251881Speter  (*buf)++;
125251881Speter  digits[0] = *((*buf)++);
126251881Speter  digits[1] = *((*buf)++);
127251881Speter  digits[2] = 0;
128251881Speter  if ((val = apr_strtoi64(digits, NULL, 16)) == 0)
129251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
130251881Speter                            _("Invalid escaped character"));
131251881Speter  *result = (char) val;
132251881Speter  return SVN_NO_ERROR;
133251881Speter}
134251881Speter
135251881Speter/* Read a field, possibly with escaped bytes, from [*BUF, END),
136251881Speter   stopping at the terminator.  Place the read string in *RESULT, or set
137251881Speter   *RESULT to NULL if it is the empty string.  Allocate the returned string
138251881Speter   in POOL.  Advance *BUF to point after the terminator. */
139251881Speterstatic svn_error_t *
140251881Speterread_str(const char **result,
141251881Speter         char **buf, const char *end,
142251881Speter         apr_pool_t *pool)
143251881Speter{
144251881Speter  svn_stringbuf_t *s = NULL;
145251881Speter  const char *start;
146251881Speter  if (*buf == end)
147251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
148251881Speter                            _("Unexpected end of entry"));
149251881Speter  if (**buf == '\n')
150251881Speter    {
151251881Speter      *result = NULL;
152251881Speter      (*buf)++;
153251881Speter      return SVN_NO_ERROR;
154251881Speter    }
155251881Speter
156251881Speter  start = *buf;
157251881Speter  while (*buf != end && **buf != '\n')
158251881Speter    {
159251881Speter      if (**buf == '\\')
160251881Speter        {
161251881Speter          char c;
162251881Speter          if (! s)
163251881Speter            s = svn_stringbuf_ncreate(start, *buf - start, pool);
164251881Speter          else
165251881Speter            svn_stringbuf_appendbytes(s, start, *buf - start);
166251881Speter          (*buf)++;
167251881Speter          SVN_ERR(read_escaped(&c, buf, end));
168251881Speter          svn_stringbuf_appendbyte(s, c);
169251881Speter          start = *buf;
170251881Speter        }
171251881Speter      else
172251881Speter        (*buf)++;
173251881Speter    }
174251881Speter
175251881Speter  if (*buf == end)
176251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
177251881Speter                            _("Unexpected end of entry"));
178251881Speter
179251881Speter  if (s)
180251881Speter    {
181251881Speter      svn_stringbuf_appendbytes(s, start, *buf - start);
182251881Speter      *result = s->data;
183251881Speter    }
184251881Speter  else
185251881Speter    *result = apr_pstrndup(pool, start, *buf - start);
186251881Speter  (*buf)++;
187251881Speter  return SVN_NO_ERROR;
188251881Speter}
189251881Speter
190251881Speter/* This is wrapper around read_str() (which see for details); it
191251881Speter   simply asks svn_path_is_canonical() of the string it reads,
192251881Speter   returning an error if the test fails.
193251881Speter   ### It seems this is only called for entrynames now
194251881Speter   */
195251881Speterstatic svn_error_t *
196251881Speterread_path(const char **result,
197251881Speter          char **buf, const char *end,
198251881Speter          apr_pool_t *pool)
199251881Speter{
200251881Speter  SVN_ERR(read_str(result, buf, end, pool));
201251881Speter  if (*result && **result && !svn_relpath_is_canonical(*result))
202251881Speter    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
203251881Speter                             _("Entry contains non-canonical path '%s'"),
204251881Speter                             *result);
205251881Speter  return SVN_NO_ERROR;
206251881Speter}
207251881Speter
208251881Speter/* This is read_path() for urls. This function does not do the is_canonical
209251881Speter   test for entries from working copies older than version 10, as since that
210251881Speter   version the canonicalization of urls has been changed. See issue #2475.
211251881Speter   If the test is done and fails, read_url returs an error. */
212251881Speterstatic svn_error_t *
213251881Speterread_url(const char **result,
214251881Speter         char **buf, const char *end,
215251881Speter         int wc_format,
216251881Speter         apr_pool_t *pool)
217251881Speter{
218251881Speter  SVN_ERR(read_str(result, buf, end, pool));
219251881Speter
220251881Speter  /* Always canonicalize the url, as we have stricter canonicalization rules
221251881Speter     in 1.7+ then before */
222251881Speter  if (*result && **result)
223251881Speter    *result = svn_uri_canonicalize(*result, pool);
224251881Speter
225251881Speter  return SVN_NO_ERROR;
226251881Speter}
227251881Speter
228251881Speter/* Read a field from [*BUF, END), terminated by a newline character.
229251881Speter   The field may not contain escape sequences.  The field is not
230251881Speter   copied and the buffer is modified in place, by replacing the
231251881Speter   terminator with a NUL byte.  Make *BUF point after the original
232251881Speter   terminator. */
233251881Speterstatic svn_error_t *
234251881Speterread_val(const char **result,
235251881Speter          char **buf, const char *end)
236251881Speter{
237251881Speter  const char *start = *buf;
238251881Speter
239251881Speter  if (*buf == end)
240251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
241251881Speter                            _("Unexpected end of entry"));
242251881Speter  if (**buf == '\n')
243251881Speter    {
244251881Speter      (*buf)++;
245251881Speter      *result = NULL;
246251881Speter      return SVN_NO_ERROR;
247251881Speter    }
248251881Speter
249251881Speter  while (*buf != end && **buf != '\n')
250251881Speter    (*buf)++;
251251881Speter  if (*buf == end)
252251881Speter    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
253251881Speter                            _("Unexpected end of entry"));
254251881Speter  **buf = '\0';
255251881Speter  *result = start;
256251881Speter  (*buf)++;
257251881Speter  return SVN_NO_ERROR;
258251881Speter}
259251881Speter
260251881Speter/* Read a boolean field from [*BUF, END), placing the result in
261251881Speter   *RESULT.  If there is no boolean value (just a terminator), it
262251881Speter   defaults to false.  Else, the value must match FIELD_NAME, in which
263251881Speter   case *RESULT will be set to true.  Advance *BUF to point after the
264251881Speter   terminator. */
265251881Speterstatic svn_error_t *
266251881Speterread_bool(svn_boolean_t *result, const char *field_name,
267251881Speter          char **buf, const char *end)
268251881Speter{
269251881Speter  const char *val;
270251881Speter  SVN_ERR(read_val(&val, buf, end));
271251881Speter  if (val)
272251881Speter    {
273251881Speter      if (strcmp(val, field_name) != 0)
274251881Speter        return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
275251881Speter                                 _("Invalid value for field '%s'"),
276251881Speter                                 field_name);
277251881Speter      *result = TRUE;
278251881Speter    }
279251881Speter  else
280251881Speter    *result = FALSE;
281251881Speter  return SVN_NO_ERROR;
282251881Speter}
283251881Speter
284251881Speter/* Read a revision number from [*BUF, END) stopping at the
285251881Speter   terminator.  Set *RESULT to the revision number, or
286251881Speter   SVN_INVALID_REVNUM if there is none.  Use POOL for temporary
287251881Speter   allocations.  Make *BUF point after the terminator.  */
288251881Speterstatic svn_error_t *
289251881Speterread_revnum(svn_revnum_t *result,
290251881Speter            char **buf,
291251881Speter            const char *end,
292251881Speter            apr_pool_t *pool)
293251881Speter{
294251881Speter  const char *val;
295251881Speter
296251881Speter  SVN_ERR(read_val(&val, buf, end));
297251881Speter
298251881Speter  if (val)
299251881Speter    *result = SVN_STR_TO_REV(val);
300251881Speter  else
301251881Speter    *result = SVN_INVALID_REVNUM;
302251881Speter
303251881Speter  return SVN_NO_ERROR;
304251881Speter}
305251881Speter
306251881Speter/* Read a timestamp from [*BUF, END) stopping at the terminator.
307251881Speter   Set *RESULT to the resulting timestamp, or 0 if there is none.  Use
308251881Speter   POOL for temporary allocations.  Make *BUF point after the
309251881Speter   terminator. */
310251881Speterstatic svn_error_t *
311251881Speterread_time(apr_time_t *result,
312251881Speter          char **buf, const char *end,
313251881Speter          apr_pool_t *pool)
314251881Speter{
315251881Speter  const char *val;
316251881Speter
317251881Speter  SVN_ERR(read_val(&val, buf, end));
318251881Speter  if (val)
319251881Speter    SVN_ERR(svn_time_from_cstring(result, val, pool));
320251881Speter  else
321251881Speter    *result = 0;
322251881Speter
323251881Speter  return SVN_NO_ERROR;
324251881Speter}
325251881Speter
326251881Speter/**
327251881Speter * Parse the string at *STR as an revision and save the result in
328251881Speter * *OPT_REV.  After returning successfully, *STR points at next
329251881Speter * character in *STR where further parsing can be done.
330251881Speter */
331251881Speterstatic svn_error_t *
332251881Speterstring_to_opt_revision(svn_opt_revision_t *opt_rev,
333251881Speter                       const char **str,
334251881Speter                       apr_pool_t *pool)
335251881Speter{
336251881Speter  const char *s = *str;
337251881Speter
338251881Speter  SVN_ERR_ASSERT(opt_rev);
339251881Speter
340251881Speter  while (*s && *s != ':')
341251881Speter    ++s;
342251881Speter
343251881Speter  /* Should not find a \0. */
344251881Speter  if (!*s)
345251881Speter    return svn_error_createf
346251881Speter      (SVN_ERR_INCORRECT_PARAMS, NULL,
347251881Speter       _("Found an unexpected \\0 in the file external '%s'"), *str);
348251881Speter
349251881Speter  if (0 == strncmp(*str, "HEAD:", 5))
350251881Speter    {
351251881Speter      opt_rev->kind = svn_opt_revision_head;
352251881Speter    }
353251881Speter  else
354251881Speter    {
355251881Speter      svn_revnum_t rev;
356251881Speter      const char *endptr;
357251881Speter
358251881Speter      SVN_ERR(svn_revnum_parse(&rev, *str, &endptr));
359251881Speter      SVN_ERR_ASSERT(endptr == s);
360251881Speter      opt_rev->kind = svn_opt_revision_number;
361251881Speter      opt_rev->value.number = rev;
362251881Speter    }
363251881Speter
364251881Speter  *str = s + 1;
365251881Speter
366251881Speter  return SVN_NO_ERROR;
367251881Speter}
368251881Speter
369251881Speter/**
370251881Speter * Given a revision, return a string for the revision, either "HEAD"
371251881Speter * or a string representation of the revision value.  All other
372251881Speter * revision kinds return an error.
373251881Speter */
374251881Speterstatic svn_error_t *
375251881Speteropt_revision_to_string(const char **str,
376251881Speter                       const char *path,
377251881Speter                       const svn_opt_revision_t *rev,
378251881Speter                       apr_pool_t *pool)
379251881Speter{
380251881Speter  switch (rev->kind)
381251881Speter    {
382251881Speter    case svn_opt_revision_head:
383251881Speter      *str = apr_pstrmemdup(pool, "HEAD", 4);
384251881Speter      break;
385251881Speter    case svn_opt_revision_number:
386251881Speter      *str = apr_ltoa(pool, rev->value.number);
387251881Speter      break;
388251881Speter    default:
389251881Speter      return svn_error_createf
390251881Speter        (SVN_ERR_INCORRECT_PARAMS, NULL,
391251881Speter         _("Illegal file external revision kind %d for path '%s'"),
392251881Speter         rev->kind, path);
393251881Speter      break;
394251881Speter    }
395251881Speter
396251881Speter  return SVN_NO_ERROR;
397251881Speter}
398251881Speter
399251881Spetersvn_error_t *
400251881Spetersvn_wc__unserialize_file_external(const char **path_result,
401251881Speter                                  svn_opt_revision_t *peg_rev_result,
402251881Speter                                  svn_opt_revision_t *rev_result,
403251881Speter                                  const char *str,
404251881Speter                                  apr_pool_t *pool)
405251881Speter{
406251881Speter  if (str)
407251881Speter    {
408251881Speter      svn_opt_revision_t peg_rev;
409251881Speter      svn_opt_revision_t op_rev;
410251881Speter      const char *s = str;
411251881Speter
412251881Speter      SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool));
413251881Speter      SVN_ERR(string_to_opt_revision(&op_rev, &s, pool));
414251881Speter
415251881Speter      *path_result = apr_pstrdup(pool, s);
416251881Speter      *peg_rev_result = peg_rev;
417251881Speter      *rev_result = op_rev;
418251881Speter    }
419251881Speter  else
420251881Speter    {
421251881Speter      *path_result = NULL;
422251881Speter      peg_rev_result->kind = svn_opt_revision_unspecified;
423251881Speter      rev_result->kind = svn_opt_revision_unspecified;
424251881Speter    }
425251881Speter
426251881Speter  return SVN_NO_ERROR;
427251881Speter}
428251881Speter
429251881Spetersvn_error_t *
430251881Spetersvn_wc__serialize_file_external(const char **str,
431251881Speter                                const char *path,
432251881Speter                                const svn_opt_revision_t *peg_rev,
433251881Speter                                const svn_opt_revision_t *rev,
434251881Speter                                apr_pool_t *pool)
435251881Speter{
436251881Speter  const char *s;
437251881Speter
438251881Speter  if (path)
439251881Speter    {
440251881Speter      const char *s1;
441251881Speter      const char *s2;
442251881Speter
443251881Speter      SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool));
444251881Speter      SVN_ERR(opt_revision_to_string(&s2, path, rev, pool));
445251881Speter
446289180Speter      s = apr_pstrcat(pool, s1, ":", s2, ":", path, SVN_VA_NULL);
447251881Speter    }
448251881Speter  else
449251881Speter    s = NULL;
450251881Speter
451251881Speter  *str = s;
452251881Speter
453251881Speter  return SVN_NO_ERROR;
454251881Speter}
455251881Speter
456251881Speter/* Allocate an entry from POOL and read it from [*BUF, END).  The
457251881Speter   buffer may be modified in place while parsing.  Return the new
458251881Speter   entry in *NEW_ENTRY.  Advance *BUF to point at the end of the entry
459251881Speter   record.
460251881Speter   The entries file format should be provided in ENTRIES_FORMAT. */
461251881Speterstatic svn_error_t *
462251881Speterread_entry(svn_wc_entry_t **new_entry,
463251881Speter           char **buf, const char *end,
464251881Speter           int entries_format,
465251881Speter           apr_pool_t *pool)
466251881Speter{
467251881Speter  svn_wc_entry_t *entry = alloc_entry(pool);
468251881Speter  const char *name;
469251881Speter
470251881Speter#define MAYBE_DONE if (**buf == '\f') goto done
471251881Speter
472251881Speter  /* Find the name and set up the entry under that name. */
473251881Speter  SVN_ERR(read_path(&name, buf, end, pool));
474251881Speter  entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR;
475251881Speter
476251881Speter  /* Set up kind. */
477251881Speter  {
478251881Speter    const char *kindstr;
479251881Speter    SVN_ERR(read_val(&kindstr, buf, end));
480251881Speter    if (kindstr)
481251881Speter      {
482251881Speter        if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
483251881Speter          entry->kind = svn_node_file;
484251881Speter        else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
485251881Speter          entry->kind = svn_node_dir;
486251881Speter        else
487251881Speter          return svn_error_createf
488251881Speter            (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
489251881Speter             _("Entry '%s' has invalid node kind"),
490251881Speter             (name ? name : SVN_WC_ENTRY_THIS_DIR));
491251881Speter      }
492251881Speter    else
493251881Speter      entry->kind = svn_node_none;
494251881Speter  }
495251881Speter  MAYBE_DONE;
496251881Speter
497251881Speter  /* Attempt to set revision (resolve_to_defaults may do it later, too) */
498251881Speter  SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
499251881Speter  MAYBE_DONE;
500251881Speter
501251881Speter  /* Attempt to set up url path (again, see resolve_to_defaults). */
502251881Speter  SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool));
503251881Speter  MAYBE_DONE;
504251881Speter
505251881Speter  /* Set up repository root.  Make sure it is a prefix of url. */
506251881Speter  SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool));
507251881Speter  if (entry->repos && entry->url
508251881Speter      && ! svn_uri__is_ancestor(entry->repos, entry->url))
509251881Speter    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
510251881Speter                             _("Entry for '%s' has invalid repository "
511251881Speter                               "root"),
512251881Speter                             name ? name : SVN_WC_ENTRY_THIS_DIR);
513251881Speter  MAYBE_DONE;
514251881Speter
515251881Speter  /* Look for a schedule attribute on this entry. */
516251881Speter  {
517251881Speter    const char *schedulestr;
518251881Speter    SVN_ERR(read_val(&schedulestr, buf, end));
519251881Speter    entry->schedule = svn_wc_schedule_normal;
520251881Speter    if (schedulestr)
521251881Speter      {
522251881Speter        if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
523251881Speter          entry->schedule = svn_wc_schedule_add;
524251881Speter        else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
525251881Speter          entry->schedule = svn_wc_schedule_delete;
526251881Speter        else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
527251881Speter          entry->schedule = svn_wc_schedule_replace;
528251881Speter        else
529251881Speter          return svn_error_createf(
530251881Speter            SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
531251881Speter            _("Entry '%s' has invalid 'schedule' value"),
532251881Speter            name ? name : SVN_WC_ENTRY_THIS_DIR);
533251881Speter      }
534251881Speter  }
535251881Speter  MAYBE_DONE;
536251881Speter
537251881Speter  /* Attempt to set up text timestamp. */
538251881Speter  SVN_ERR(read_time(&entry->text_time, buf, end, pool));
539251881Speter  MAYBE_DONE;
540251881Speter
541251881Speter  /* Checksum. */
542251881Speter  SVN_ERR(read_str(&entry->checksum, buf, end, pool));
543251881Speter  MAYBE_DONE;
544251881Speter
545251881Speter  /* Setup last-committed values. */
546251881Speter  SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
547251881Speter  MAYBE_DONE;
548251881Speter
549251881Speter  SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
550251881Speter  MAYBE_DONE;
551251881Speter
552251881Speter  SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
553251881Speter  MAYBE_DONE;
554251881Speter
555251881Speter  /* has-props, has-prop-mods, cachable-props, present-props are all
556251881Speter     deprecated. Read any values that may be in the 'entries' file, but
557251881Speter     discard them, and just put default values into the entry. */
558251881Speter  {
559251881Speter    const char *unused_value;
560251881Speter
561251881Speter    /* has-props flag. */
562251881Speter    SVN_ERR(read_val(&unused_value, buf, end));
563251881Speter    entry->has_props = FALSE;
564251881Speter    MAYBE_DONE;
565251881Speter
566251881Speter    /* has-prop-mods flag. */
567251881Speter    SVN_ERR(read_val(&unused_value, buf, end));
568251881Speter    entry->has_prop_mods = FALSE;
569251881Speter    MAYBE_DONE;
570251881Speter
571251881Speter    /* Use the empty string for cachable_props, indicating that we no
572251881Speter       longer attempt to cache any properties. An empty string for
573251881Speter       present_props means that no cachable props are present. */
574251881Speter
575251881Speter    /* cachable-props string. */
576251881Speter    SVN_ERR(read_val(&unused_value, buf, end));
577251881Speter    entry->cachable_props = "";
578251881Speter    MAYBE_DONE;
579251881Speter
580251881Speter    /* present-props string. */
581251881Speter    SVN_ERR(read_val(&unused_value, buf, end));
582251881Speter    entry->present_props = "";
583251881Speter    MAYBE_DONE;
584251881Speter  }
585251881Speter
586251881Speter  /* Is this entry in a state of mental torment (conflict)? */
587251881Speter  {
588251881Speter    SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
589251881Speter    MAYBE_DONE;
590251881Speter    SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
591251881Speter    MAYBE_DONE;
592251881Speter    SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
593251881Speter    MAYBE_DONE;
594251881Speter    SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
595251881Speter    MAYBE_DONE;
596251881Speter  }
597251881Speter
598251881Speter  /* Is this entry copied? */
599251881Speter  SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end));
600251881Speter  MAYBE_DONE;
601251881Speter
602251881Speter  SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool));
603251881Speter  MAYBE_DONE;
604251881Speter  SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
605251881Speter  MAYBE_DONE;
606251881Speter
607251881Speter  /* Is this entry deleted? */
608251881Speter  SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end));
609251881Speter  MAYBE_DONE;
610251881Speter
611251881Speter  /* Is this entry absent? */
612251881Speter  SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end));
613251881Speter  MAYBE_DONE;
614251881Speter
615251881Speter  /* Is this entry incomplete? */
616251881Speter  SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end));
617251881Speter  MAYBE_DONE;
618251881Speter
619251881Speter  /* UUID. */
620251881Speter  SVN_ERR(read_str(&entry->uuid, buf, end, pool));
621251881Speter  MAYBE_DONE;
622251881Speter
623251881Speter  /* Lock token. */
624251881Speter  SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
625251881Speter  MAYBE_DONE;
626251881Speter
627251881Speter  /* Lock owner. */
628251881Speter  SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
629251881Speter  MAYBE_DONE;
630251881Speter
631251881Speter  /* Lock comment. */
632251881Speter  SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
633251881Speter  MAYBE_DONE;
634251881Speter
635251881Speter  /* Lock creation date. */
636251881Speter  SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
637251881Speter  MAYBE_DONE;
638251881Speter
639251881Speter  /* Changelist. */
640251881Speter  SVN_ERR(read_str(&entry->changelist, buf, end, pool));
641251881Speter  MAYBE_DONE;
642251881Speter
643251881Speter  /* Keep entry in working copy after deletion? */
644251881Speter  SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end));
645251881Speter  MAYBE_DONE;
646251881Speter
647251881Speter  /* Translated size */
648251881Speter  {
649251881Speter    const char *val;
650251881Speter
651251881Speter    /* read_val() returns NULL on an empty (e.g. default) entry line,
652251881Speter       and entry has already been initialized accordingly already */
653251881Speter    SVN_ERR(read_val(&val, buf, end));
654251881Speter    if (val)
655251881Speter      entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
656251881Speter  }
657251881Speter  MAYBE_DONE;
658251881Speter
659251881Speter  /* Depth. */
660251881Speter  {
661251881Speter    const char *result;
662251881Speter    SVN_ERR(read_val(&result, buf, end));
663251881Speter    if (result)
664251881Speter      {
665251881Speter        svn_boolean_t invalid;
666251881Speter        svn_boolean_t is_this_dir;
667251881Speter
668251881Speter        entry->depth = svn_depth_from_word(result);
669251881Speter
670251881Speter        /* Verify the depth value:
671251881Speter           THIS_DIR should not have an excluded value and SUB_DIR should only
672251881Speter           have excluded value. Remember that infinity value is not stored and
673251881Speter           should not show up here. Otherwise, something bad may have
674251881Speter           happened. However, infinity value itself will always be okay. */
675251881Speter        is_this_dir = !name;
676251881Speter        /* '!=': XOR */
677251881Speter        invalid = is_this_dir != (entry->depth != svn_depth_exclude);
678251881Speter        if (entry->depth != svn_depth_infinity && invalid)
679251881Speter          return svn_error_createf(
680251881Speter            SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
681251881Speter            _("Entry '%s' has invalid 'depth' value"),
682251881Speter            name ? name : SVN_WC_ENTRY_THIS_DIR);
683251881Speter      }
684251881Speter    else
685251881Speter      entry->depth = svn_depth_infinity;
686251881Speter
687251881Speter  }
688251881Speter  MAYBE_DONE;
689251881Speter
690251881Speter  /* Tree conflict data. */
691251881Speter  SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool));
692251881Speter  MAYBE_DONE;
693251881Speter
694251881Speter  /* File external URL and revision. */
695251881Speter  {
696251881Speter    const char *str;
697251881Speter    SVN_ERR(read_str(&str, buf, end, pool));
698251881Speter    SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path,
699251881Speter                                              &entry->file_external_peg_rev,
700251881Speter                                              &entry->file_external_rev,
701251881Speter                                              str,
702251881Speter                                              pool));
703251881Speter  }
704251881Speter  MAYBE_DONE;
705251881Speter
706251881Speter done:
707251881Speter  *new_entry = entry;
708251881Speter  return SVN_NO_ERROR;
709251881Speter}
710251881Speter
711251881Speter
712251881Speter/* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its
713251881Speter   boolean value, else set *ENTRY_FLAG false.  ENTRY_NAME is the name
714251881Speter   of the WC-entry. */
715251881Speterstatic svn_error_t *
716251881Speterdo_bool_attr(svn_boolean_t *entry_flag,
717251881Speter             apr_hash_t *atts, const char *attr_name,
718251881Speter             const char *entry_name)
719251881Speter{
720251881Speter  const char *str = svn_hash_gets(atts, attr_name);
721251881Speter
722251881Speter  *entry_flag = FALSE;
723251881Speter  if (str)
724251881Speter    {
725251881Speter      if (strcmp(str, "true") == 0)
726251881Speter        *entry_flag = TRUE;
727251881Speter      else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
728251881Speter        *entry_flag = FALSE;
729251881Speter      else
730251881Speter        return svn_error_createf
731251881Speter          (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
732251881Speter           _("Entry '%s' has invalid '%s' value"),
733251881Speter           (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name);
734251881Speter    }
735251881Speter  return SVN_NO_ERROR;
736251881Speter}
737251881Speter
738251881Speter
739251881Speter/* */
740251881Speterstatic const char *
741251881Speterextract_string(apr_hash_t *atts,
742251881Speter               const char *att_name,
743251881Speter               apr_pool_t *result_pool)
744251881Speter{
745251881Speter  const char *value = svn_hash_gets(atts, att_name);
746251881Speter
747251881Speter  if (value == NULL)
748251881Speter    return NULL;
749251881Speter
750251881Speter  return apr_pstrdup(result_pool, value);
751251881Speter}
752251881Speter
753251881Speter
754251881Speter/* Like extract_string(), but normalizes empty strings to NULL.  */
755251881Speterstatic const char *
756251881Speterextract_string_normalize(apr_hash_t *atts,
757251881Speter                         const char *att_name,
758251881Speter                         apr_pool_t *result_pool)
759251881Speter{
760251881Speter  const char *value = svn_hash_gets(atts, att_name);
761251881Speter
762251881Speter  if (value == NULL)
763251881Speter    return NULL;
764251881Speter
765251881Speter  if (*value == '\0')
766251881Speter    return NULL;
767251881Speter
768251881Speter  return apr_pstrdup(result_pool, value);
769251881Speter}
770251881Speter
771251881Speter
772251881Speter/* NOTE: this is used for upgrading old XML-based entries file. Be wary of
773251881Speter         removing items.
774251881Speter
775251881Speter   ### many attributes are no longer used within the old-style log files.
776251881Speter   ### These attrs need to be recognized for old entries, however. For these
777251881Speter   ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS
778251881Speter   ### for that particular field. MODIFY_FLAGS is *only* used by the
779251881Speter   ### log-based entry modification system, and will go way once we
780251881Speter   ### completely move away from loggy.
781251881Speter
782251881Speter   Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose
783251881Speter   keys and values are both char *.  Allocate the entry and copy
784251881Speter   attributes into POOL as needed. */
785251881Speterstatic svn_error_t *
786251881Speteratts_to_entry(svn_wc_entry_t **new_entry,
787251881Speter              apr_hash_t *atts,
788251881Speter              apr_pool_t *pool)
789251881Speter{
790251881Speter  svn_wc_entry_t *entry = alloc_entry(pool);
791251881Speter  const char *name;
792251881Speter
793251881Speter  /* Find the name and set up the entry under that name. */
794251881Speter  name = svn_hash_gets(atts, ENTRIES_ATTR_NAME);
795251881Speter  entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR;
796251881Speter
797251881Speter  /* Attempt to set revision (resolve_to_defaults may do it later, too)
798251881Speter
799251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
800251881Speter  {
801251881Speter    const char *revision_str
802251881Speter      = svn_hash_gets(atts, ENTRIES_ATTR_REVISION);
803251881Speter
804251881Speter    if (revision_str)
805251881Speter      entry->revision = SVN_STR_TO_REV(revision_str);
806251881Speter    else
807251881Speter      entry->revision = SVN_INVALID_REVNUM;
808251881Speter  }
809251881Speter
810251881Speter  /* Attempt to set up url path (again, see resolve_to_defaults).
811251881Speter
812251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
813251881Speter  entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool);
814257936Speter  if (entry->url)
815257936Speter    entry->url = svn_uri_canonicalize(entry->url, pool);
816251881Speter
817251881Speter  /* Set up repository root.  Make sure it is a prefix of url.
818251881Speter
819251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
820251881Speter  entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool);
821257936Speter  if (entry->repos)
822257936Speter    entry->repos = svn_uri_canonicalize(entry->repos, pool);
823251881Speter
824251881Speter  if (entry->url && entry->repos
825251881Speter      && !svn_uri__is_ancestor(entry->repos, entry->url))
826251881Speter    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
827251881Speter                             _("Entry for '%s' has invalid repository "
828251881Speter                               "root"),
829251881Speter                             name ? name : SVN_WC_ENTRY_THIS_DIR);
830251881Speter
831251881Speter  /* Set up kind. */
832251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
833251881Speter  {
834251881Speter    const char *kindstr
835251881Speter      = svn_hash_gets(atts, ENTRIES_ATTR_KIND);
836251881Speter
837251881Speter    entry->kind = svn_node_none;
838251881Speter    if (kindstr)
839251881Speter      {
840251881Speter        if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
841251881Speter          entry->kind = svn_node_file;
842251881Speter        else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
843251881Speter          entry->kind = svn_node_dir;
844251881Speter        else
845251881Speter          return svn_error_createf
846251881Speter            (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
847251881Speter             _("Entry '%s' has invalid node kind"),
848251881Speter             (name ? name : SVN_WC_ENTRY_THIS_DIR));
849251881Speter      }
850251881Speter  }
851251881Speter
852251881Speter  /* Look for a schedule attribute on this entry. */
853251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
854251881Speter  {
855251881Speter    const char *schedulestr
856251881Speter      = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE);
857251881Speter
858251881Speter    entry->schedule = svn_wc_schedule_normal;
859251881Speter    if (schedulestr)
860251881Speter      {
861251881Speter        if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
862251881Speter          entry->schedule = svn_wc_schedule_add;
863251881Speter        else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
864251881Speter          entry->schedule = svn_wc_schedule_delete;
865251881Speter        else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
866251881Speter          entry->schedule = svn_wc_schedule_replace;
867251881Speter        else if (strcmp(schedulestr, "") == 0)
868251881Speter          entry->schedule = svn_wc_schedule_normal;
869251881Speter        else
870251881Speter          return svn_error_createf(
871251881Speter                   SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
872251881Speter                   _("Entry '%s' has invalid 'schedule' value"),
873251881Speter                   (name ? name : SVN_WC_ENTRY_THIS_DIR));
874251881Speter      }
875251881Speter  }
876251881Speter
877251881Speter  /* Is this entry in a state of mental torment (conflict)? */
878251881Speter  entry->prejfile = extract_string_normalize(atts,
879251881Speter                                             ENTRIES_ATTR_PREJFILE,
880251881Speter                                             pool);
881251881Speter  entry->conflict_old = extract_string_normalize(atts,
882251881Speter                                                 ENTRIES_ATTR_CONFLICT_OLD,
883251881Speter                                                 pool);
884251881Speter  entry->conflict_new = extract_string_normalize(atts,
885251881Speter                                                 ENTRIES_ATTR_CONFLICT_NEW,
886251881Speter                                                 pool);
887251881Speter  entry->conflict_wrk = extract_string_normalize(atts,
888251881Speter                                                 ENTRIES_ATTR_CONFLICT_WRK,
889251881Speter                                                 pool);
890251881Speter
891251881Speter  /* Is this entry copied? */
892251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
893251881Speter  SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name));
894251881Speter
895251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
896251881Speter  entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool);
897251881Speter
898251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
899251881Speter  {
900251881Speter    const char *revstr;
901251881Speter
902251881Speter    revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV);
903251881Speter    if (revstr)
904251881Speter      entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
905251881Speter  }
906251881Speter
907251881Speter  /* Is this entry deleted?
908251881Speter
909251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
910251881Speter  SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name));
911251881Speter
912251881Speter  /* Is this entry absent?
913251881Speter
914251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
915251881Speter  SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name));
916251881Speter
917251881Speter  /* Is this entry incomplete?
918251881Speter
919251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
920251881Speter  SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE,
921251881Speter                       name));
922251881Speter
923251881Speter  /* Attempt to set up timestamps. */
924251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
925251881Speter  {
926251881Speter    const char *text_timestr;
927251881Speter
928251881Speter    text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME);
929251881Speter    if (text_timestr)
930251881Speter      SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool));
931251881Speter
932251881Speter    /* Note: we do not persist prop_time, so there is no need to attempt
933251881Speter       to parse a new prop_time value from the log. Certainly, on any
934251881Speter       recent working copy, there will not be a log record to alter
935251881Speter       the prop_time value. */
936251881Speter  }
937251881Speter
938251881Speter  /* Checksum. */
939251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
940251881Speter  entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool);
941251881Speter
942251881Speter  /* UUID.
943251881Speter
944251881Speter     ### not used by loggy; no need to set MODIFY_FLAGS  */
945251881Speter  entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool);
946251881Speter
947251881Speter  /* Setup last-committed values. */
948251881Speter  {
949251881Speter    const char *cmt_datestr, *cmt_revstr;
950251881Speter
951251881Speter    cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE);
952251881Speter    if (cmt_datestr)
953251881Speter      {
954251881Speter        SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
955251881Speter      }
956251881Speter    else
957251881Speter      entry->cmt_date = 0;
958251881Speter
959251881Speter    cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV);
960251881Speter    if (cmt_revstr)
961251881Speter      {
962251881Speter        entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
963251881Speter      }
964251881Speter    else
965251881Speter      entry->cmt_rev = SVN_INVALID_REVNUM;
966251881Speter
967251881Speter    entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool);
968251881Speter  }
969251881Speter
970251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
971251881Speter  entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool);
972251881Speter  entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool);
973251881Speter  entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool);
974251881Speter  {
975251881Speter    const char *cdate_str =
976251881Speter      svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE);
977251881Speter    if (cdate_str)
978251881Speter      {
979251881Speter        SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
980251881Speter                                      cdate_str, pool));
981251881Speter      }
982251881Speter  }
983251881Speter  /* ----- end of lock handling.  */
984251881Speter
985251881Speter  /* Note: if there are attributes for the (deprecated) has_props,
986251881Speter     has_prop_mods, cachable_props, or present_props, then we're just
987251881Speter     going to ignore them. */
988251881Speter
989251881Speter  /* Translated size */
990251881Speter  /* ### not used by loggy; no need to set MODIFY_FLAGS  */
991251881Speter  {
992251881Speter    const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE);
993251881Speter    if (val)
994251881Speter      {
995251881Speter        /* Cast to off_t; it's safe: we put in an off_t to start with... */
996251881Speter        entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
997251881Speter      }
998251881Speter  }
999251881Speter
1000251881Speter  *new_entry = entry;
1001251881Speter  return SVN_NO_ERROR;
1002251881Speter}
1003251881Speter
1004251881Speter/* Used when reading an entries file in XML format. */
1005251881Speterstruct entries_accumulator
1006251881Speter{
1007251881Speter  /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
1008251881Speter  apr_hash_t *entries;
1009251881Speter
1010251881Speter  /* The parser that's parsing it, for signal_expat_bailout(). */
1011251881Speter  svn_xml_parser_t *parser;
1012251881Speter
1013251881Speter  /* Don't leave home without one. */
1014251881Speter  apr_pool_t *pool;
1015251881Speter
1016251881Speter  /* Cleared before handling each entry. */
1017251881Speter  apr_pool_t *scratch_pool;
1018251881Speter};
1019251881Speter
1020251881Speter
1021251881Speter
1022251881Speter/* Called whenever we find an <open> tag of some kind. */
1023251881Speterstatic void
1024251881Speterhandle_start_tag(void *userData, const char *tagname, const char **atts)
1025251881Speter{
1026251881Speter  struct entries_accumulator *accum = userData;
1027251881Speter  apr_hash_t *attributes;
1028251881Speter  svn_wc_entry_t *entry;
1029251881Speter  svn_error_t *err;
1030251881Speter
1031251881Speter  /* We only care about the `entry' tag; all other tags, such as `xml'
1032251881Speter     and `wc-entries', are ignored. */
1033251881Speter  if (strcmp(tagname, ENTRIES_TAG_ENTRY))
1034251881Speter    return;
1035251881Speter
1036251881Speter  svn_pool_clear(accum->scratch_pool);
1037251881Speter  /* Make an entry from the attributes. */
1038251881Speter  attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
1039251881Speter  err = atts_to_entry(&entry, attributes, accum->pool);
1040251881Speter  if (err)
1041251881Speter    {
1042251881Speter      svn_xml_signal_bailout(err, accum->parser);
1043251881Speter      return;
1044251881Speter    }
1045251881Speter
1046251881Speter  /* Find the name and set up the entry under that name.  This
1047251881Speter     should *NOT* be NULL, since svn_wc__atts_to_entry() should
1048251881Speter     have made it into SVN_WC_ENTRY_THIS_DIR. */
1049251881Speter  svn_hash_sets(accum->entries, entry->name, entry);
1050251881Speter}
1051251881Speter
1052251881Speter/* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
1053251881Speter   entries in ENTRIES.  Use SCRATCH_POOL for temporary allocations and
1054251881Speter   RESULT_POOL for the returned entries.  */
1055251881Speterstatic svn_error_t *
1056251881Speterparse_entries_xml(const char *dir_abspath,
1057251881Speter                  apr_hash_t *entries,
1058251881Speter                  const char *buf,
1059251881Speter                  apr_size_t size,
1060251881Speter                  apr_pool_t *result_pool,
1061251881Speter                  apr_pool_t *scratch_pool)
1062251881Speter{
1063251881Speter  svn_xml_parser_t *svn_parser;
1064251881Speter  struct entries_accumulator accum;
1065251881Speter
1066251881Speter  /* Set up userData for the XML parser. */
1067251881Speter  accum.entries = entries;
1068251881Speter  accum.pool = result_pool;
1069251881Speter  accum.scratch_pool = svn_pool_create(scratch_pool);
1070251881Speter
1071251881Speter  /* Create the XML parser */
1072251881Speter  svn_parser = svn_xml_make_parser(&accum,
1073251881Speter                                   handle_start_tag,
1074251881Speter                                   NULL,
1075251881Speter                                   NULL,
1076251881Speter                                   scratch_pool);
1077251881Speter
1078251881Speter  /* Store parser in its own userdata, so callbacks can call
1079251881Speter     svn_xml_signal_bailout() */
1080251881Speter  accum.parser = svn_parser;
1081251881Speter
1082251881Speter  /* Parse. */
1083251881Speter  SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
1084251881Speter            apr_psprintf(scratch_pool,
1085251881Speter                         _("XML parser failed in '%s'"),
1086251881Speter                         svn_dirent_local_style(dir_abspath, scratch_pool)));
1087251881Speter
1088251881Speter  svn_pool_destroy(accum.scratch_pool);
1089251881Speter
1090251881Speter  /* Clean up the XML parser */
1091251881Speter  svn_xml_free_parser(svn_parser);
1092251881Speter
1093251881Speter  return SVN_NO_ERROR;
1094251881Speter}
1095251881Speter
1096251881Speter
1097251881Speter
1098251881Speter/* Use entry SRC to fill in blank portions of entry DST.  SRC itself
1099251881Speter   may not have any blanks, of course.
1100251881Speter   Typically, SRC is a parent directory's own entry, and DST is some
1101251881Speter   child in that directory. */
1102251881Speterstatic void
1103251881Spetertake_from_entry(const svn_wc_entry_t *src,
1104251881Speter                svn_wc_entry_t *dst,
1105251881Speter                apr_pool_t *pool)
1106251881Speter{
1107251881Speter  /* Inherits parent's revision if doesn't have a revision of one's
1108251881Speter     own, unless this is a subdirectory. */
1109251881Speter  if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
1110251881Speter    dst->revision = src->revision;
1111251881Speter
1112251881Speter  /* Inherits parent's url if doesn't have a url of one's own. */
1113251881Speter  if (! dst->url)
1114251881Speter    dst->url = svn_path_url_add_component2(src->url, dst->name, pool);
1115251881Speter
1116251881Speter  if (! dst->repos)
1117251881Speter    dst->repos = src->repos;
1118251881Speter
1119251881Speter  if ((! dst->uuid)
1120251881Speter      && (! ((dst->schedule == svn_wc_schedule_add)
1121251881Speter             || (dst->schedule == svn_wc_schedule_replace))))
1122251881Speter    {
1123251881Speter      dst->uuid = src->uuid;
1124251881Speter    }
1125251881Speter}
1126251881Speter
1127251881Speter/* Resolve any missing information in ENTRIES by deducing from the
1128251881Speter   directory's own entry (which must already be present in ENTRIES). */
1129251881Speterstatic svn_error_t *
1130251881Speterresolve_to_defaults(apr_hash_t *entries,
1131251881Speter                    apr_pool_t *pool)
1132251881Speter{
1133251881Speter  apr_hash_index_t *hi;
1134251881Speter  svn_wc_entry_t *default_entry
1135251881Speter    = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
1136251881Speter
1137251881Speter  /* First check the dir's own entry for consistency. */
1138251881Speter  if (! default_entry)
1139251881Speter    return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
1140251881Speter                            NULL,
1141251881Speter                            _("Missing default entry"));
1142251881Speter
1143251881Speter  if (default_entry->revision == SVN_INVALID_REVNUM)
1144251881Speter    return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
1145251881Speter                            NULL,
1146251881Speter                            _("Default entry has no revision number"));
1147251881Speter
1148251881Speter  if (! default_entry->url)
1149251881Speter    return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
1150251881Speter                            NULL,
1151251881Speter                            _("Default entry is missing URL"));
1152251881Speter
1153251881Speter
1154251881Speter  /* Then use it to fill in missing information in other entries. */
1155251881Speter  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1156251881Speter    {
1157289180Speter      svn_wc_entry_t *this_entry = apr_hash_this_val(hi);
1158251881Speter
1159251881Speter      if (this_entry == default_entry)
1160251881Speter        /* THIS_DIR already has all the information it can possibly
1161251881Speter           have.  */
1162251881Speter        continue;
1163251881Speter
1164251881Speter      if (this_entry->kind == svn_node_dir)
1165251881Speter        /* Entries that are directories have everything but their
1166251881Speter           name, kind, and state stored in the THIS_DIR entry of the
1167251881Speter           directory itself.  However, we are disallowing the perusing
1168251881Speter           of any entries outside of the current entries file.  If a
1169251881Speter           caller wants more info about a directory, it should look in
1170251881Speter           the entries file in the directory.  */
1171251881Speter        continue;
1172251881Speter
1173251881Speter      if (this_entry->kind == svn_node_file)
1174251881Speter        /* For file nodes that do not explicitly have their ancestry
1175251881Speter           stated, this can be derived from the default entry of the
1176251881Speter           directory in which those files reside.  */
1177251881Speter        take_from_entry(default_entry, this_entry, pool);
1178251881Speter    }
1179251881Speter
1180251881Speter  return SVN_NO_ERROR;
1181251881Speter}
1182251881Speter
1183251881Speter
1184251881Speter
1185251881Speter/* Read and parse an old-style 'entries' file in the administrative area
1186251881Speter   of PATH, filling in ENTRIES with the contents. The results will be
1187251881Speter   allocated in RESULT_POOL, and temporary allocations will be made in
1188251881Speter   SCRATCH_POOL.  */
1189251881Spetersvn_error_t *
1190251881Spetersvn_wc__read_entries_old(apr_hash_t **entries,
1191251881Speter                         const char *dir_abspath,
1192251881Speter                         apr_pool_t *result_pool,
1193251881Speter                         apr_pool_t *scratch_pool)
1194251881Speter{
1195251881Speter  char *curp;
1196251881Speter  const char *endp;
1197251881Speter  svn_wc_entry_t *entry;
1198251881Speter  svn_stream_t *stream;
1199251881Speter  svn_string_t *buf;
1200251881Speter
1201251881Speter  *entries = apr_hash_make(result_pool);
1202251881Speter
1203251881Speter  /* Open the entries file. */
1204251881Speter  SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES,
1205251881Speter                                  scratch_pool, scratch_pool));
1206251881Speter  SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool));
1207251881Speter
1208251881Speter  /* We own the returned data; it is modifiable, so cast away... */
1209251881Speter  curp = (char *)buf->data;
1210251881Speter  endp = buf->data + buf->len;
1211251881Speter
1212251881Speter  /* If the first byte of the file is not a digit, then it is probably in XML
1213251881Speter     format. */
1214251881Speter  if (curp != endp && !svn_ctype_isdigit(*curp))
1215251881Speter    SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len,
1216251881Speter                              result_pool, scratch_pool));
1217251881Speter  else
1218251881Speter    {
1219251881Speter      int entryno, entries_format;
1220251881Speter      const char *val;
1221251881Speter
1222251881Speter      /* Read the format line from the entries file. In case we're in the
1223251881Speter         middle of upgrading a working copy, this line will contain the
1224251881Speter         original format pre-upgrade. */
1225251881Speter      SVN_ERR(read_val(&val, &curp, endp));
1226251881Speter      if (val)
1227251881Speter        entries_format = (int)apr_strtoi64(val, NULL, 0);
1228251881Speter      else
1229251881Speter        return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1230251881Speter                                 _("Invalid version line in entries file "
1231251881Speter                                   "of '%s'"),
1232251881Speter                                 svn_dirent_local_style(dir_abspath,
1233251881Speter                                                        scratch_pool));
1234251881Speter      entryno = 1;
1235251881Speter
1236251881Speter      while (curp != endp)
1237251881Speter        {
1238251881Speter          svn_error_t *err = read_entry(&entry, &curp, endp,
1239251881Speter                                        entries_format, result_pool);
1240251881Speter          if (! err)
1241251881Speter            {
1242251881Speter              /* We allow extra fields at the end of the line, for
1243251881Speter                 extensibility. */
1244251881Speter              curp = memchr(curp, '\f', endp - curp);
1245251881Speter              if (! curp)
1246251881Speter                err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1247251881Speter                                       _("Missing entry terminator"));
1248251881Speter              if (! err && (curp == endp || *(++curp) != '\n'))
1249251881Speter                err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1250251881Speter                                       _("Invalid entry terminator"));
1251251881Speter            }
1252251881Speter          if (err)
1253251881Speter            return svn_error_createf(err->apr_err, err,
1254251881Speter                                     _("Error at entry %d in entries file for "
1255251881Speter                                       "'%s':"),
1256251881Speter                                     entryno,
1257251881Speter                                     svn_dirent_local_style(dir_abspath,
1258251881Speter                                                            scratch_pool));
1259251881Speter
1260251881Speter          ++curp;
1261251881Speter          ++entryno;
1262251881Speter
1263251881Speter          svn_hash_sets(*entries, entry->name, entry);
1264251881Speter        }
1265251881Speter    }
1266251881Speter
1267251881Speter  /* Fill in any implied fields. */
1268251881Speter  return svn_error_trace(resolve_to_defaults(*entries, result_pool));
1269251881Speter}
1270251881Speter
1271251881Speter
1272251881Speter/* For non-directory PATHs full entry information is obtained by reading
1273251881Speter * the entries for the parent directory of PATH and then extracting PATH's
1274251881Speter * entry.  If PATH is a directory then only abrieviated information is
1275251881Speter * available in the parent directory, more complete information is
1276251881Speter * available by reading the entries for PATH itself.
1277251881Speter *
1278251881Speter * Note: There is one bit of information about directories that is only
1279251881Speter * available in the parent directory, that is the "deleted" state.  If PATH
1280251881Speter * is a versioned directory then the "deleted" state information will not
1281251881Speter * be returned in ENTRY.  This means some bits of the code (e.g. revert)
1282251881Speter * need to obtain it by directly extracting the directory entry from the
1283251881Speter * parent directory's entries.  I wonder if this function should handle
1284251881Speter * that?
1285251881Speter */
1286251881Spetersvn_error_t *
1287251881Spetersvn_wc_entry(const svn_wc_entry_t **entry,
1288251881Speter             const char *path,
1289251881Speter             svn_wc_adm_access_t *adm_access,
1290251881Speter             svn_boolean_t show_hidden,
1291251881Speter             apr_pool_t *pool)
1292251881Speter{
1293251881Speter  svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
1294251881Speter  const char *local_abspath;
1295251881Speter  svn_wc_adm_access_t *dir_access;
1296251881Speter  const char *entry_name;
1297251881Speter  apr_hash_t *entries;
1298251881Speter
1299251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
1300251881Speter
1301251881Speter  /* Does the provided path refer to a directory with an associated
1302251881Speter     access baton?  */
1303251881Speter  dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool);
1304251881Speter  if (dir_access == NULL)
1305251881Speter    {
1306251881Speter      /* Damn. Okay. Assume the path is to a child, and let's look for
1307251881Speter         a baton associated with its parent.  */
1308251881Speter
1309251881Speter      const char *dir_abspath;
1310251881Speter
1311251881Speter      svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool);
1312251881Speter
1313251881Speter      dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool);
1314251881Speter    }
1315251881Speter  else
1316251881Speter    {
1317251881Speter      /* Woo! Got one. Look for "this dir" in the entries hash.  */
1318251881Speter      entry_name = "";
1319251881Speter    }
1320251881Speter
1321251881Speter  if (dir_access == NULL)
1322251881Speter    {
1323251881Speter      /* Early exit.  */
1324251881Speter      *entry = NULL;
1325251881Speter      return SVN_NO_ERROR;
1326251881Speter    }
1327251881Speter
1328251881Speter  /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
1329251881Speter     fetch all entries here (optimization) since we know how to filter
1330251881Speter     out a "hidden" node.  */
1331251881Speter  SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
1332251881Speter  *entry = svn_hash_gets(entries, entry_name);
1333251881Speter
1334251881Speter  if (!show_hidden && *entry != NULL)
1335251881Speter    {
1336251881Speter      svn_boolean_t hidden;
1337251881Speter
1338251881Speter      SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry));
1339251881Speter      if (hidden)
1340251881Speter        *entry = NULL;
1341251881Speter    }
1342251881Speter
1343251881Speter  return SVN_NO_ERROR;
1344251881Speter}
1345