1 /*
2  * old-and-busted.c:  routines for reading pre-1.7 working copies.
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #include "svn_time.h"
27 #include "svn_xml.h"
28 #include "svn_dirent_uri.h"
29 #include "svn_hash.h"
30 #include "svn_path.h"
31 #include "svn_ctype.h"
32 #include "svn_pools.h"
33 
34 #include "wc.h"
35 #include "adm_files.h"
36 #include "entries.h"
37 #include "lock.h"
38 
39 #include "private/svn_wc_private.h"
40 #include "svn_private_config.h"
41 
42 
43 /* Within the (old) entries file, boolean values have a specific string
44    value (thus, TRUE), or they are missing (for FALSE). Below are the
45    values for each of the booleans stored.  */
46 #define ENTRIES_BOOL_COPIED     "copied"
47 #define ENTRIES_BOOL_DELETED    "deleted"
48 #define ENTRIES_BOOL_ABSENT     "absent"
49 #define ENTRIES_BOOL_INCOMPLETE "incomplete"
50 #define ENTRIES_BOOL_KEEP_LOCAL "keep-local"
51 
52 /* Tag names used in our old XML entries file.  */
53 #define ENTRIES_TAG_ENTRY "entry"
54 
55 /* Attribute names used in our old XML entries file.  */
56 #define ENTRIES_ATTR_NAME               "name"
57 #define ENTRIES_ATTR_REPOS              "repos"
58 #define ENTRIES_ATTR_UUID               "uuid"
59 #define ENTRIES_ATTR_INCOMPLETE         "incomplete"
60 #define ENTRIES_ATTR_LOCK_TOKEN         "lock-token"
61 #define ENTRIES_ATTR_LOCK_OWNER         "lock-owner"
62 #define ENTRIES_ATTR_LOCK_COMMENT       "lock-comment"
63 #define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date"
64 #define ENTRIES_ATTR_DELETED            "deleted"
65 #define ENTRIES_ATTR_ABSENT             "absent"
66 #define ENTRIES_ATTR_CMT_REV            "committed-rev"
67 #define ENTRIES_ATTR_CMT_DATE           "committed-date"
68 #define ENTRIES_ATTR_CMT_AUTHOR         "last-author"
69 #define ENTRIES_ATTR_REVISION           "revision"
70 #define ENTRIES_ATTR_URL                "url"
71 #define ENTRIES_ATTR_KIND               "kind"
72 #define ENTRIES_ATTR_SCHEDULE           "schedule"
73 #define ENTRIES_ATTR_COPIED             "copied"
74 #define ENTRIES_ATTR_COPYFROM_URL       "copyfrom-url"
75 #define ENTRIES_ATTR_COPYFROM_REV       "copyfrom-rev"
76 #define ENTRIES_ATTR_CHECKSUM           "checksum"
77 #define ENTRIES_ATTR_WORKING_SIZE       "working-size"
78 #define ENTRIES_ATTR_TEXT_TIME          "text-time"
79 #define ENTRIES_ATTR_CONFLICT_OLD       "conflict-old" /* saved old file */
80 #define ENTRIES_ATTR_CONFLICT_NEW       "conflict-new" /* saved new file */
81 #define ENTRIES_ATTR_CONFLICT_WRK       "conflict-wrk" /* saved wrk file */
82 #define ENTRIES_ATTR_PREJFILE           "prop-reject-file"
83 
84 /* Attribute values used in our old XML entries file.  */
85 #define ENTRIES_VALUE_FILE     "file"
86 #define ENTRIES_VALUE_DIR      "dir"
87 #define ENTRIES_VALUE_ADD      "add"
88 #define ENTRIES_VALUE_DELETE   "delete"
89 #define ENTRIES_VALUE_REPLACE  "replace"
90 
91 
92 /* */
93 static svn_wc_entry_t *
alloc_entry(apr_pool_t * pool)94 alloc_entry(apr_pool_t *pool)
95 {
96   svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
97   entry->revision = SVN_INVALID_REVNUM;
98   entry->copyfrom_rev = SVN_INVALID_REVNUM;
99   entry->cmt_rev = SVN_INVALID_REVNUM;
100   entry->kind = svn_node_none;
101   entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
102   entry->depth = svn_depth_infinity;
103   entry->file_external_path = NULL;
104   entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
105   entry->file_external_rev.kind = svn_opt_revision_unspecified;
106   return entry;
107 }
108 
109 
110 
111 /* Read an escaped byte on the form 'xHH' from [*BUF, END), placing
112    the byte in *RESULT.  Advance *BUF to point after the escape
113    sequence. */
114 static svn_error_t *
read_escaped(char * result,char ** buf,const char * end)115 read_escaped(char *result, char **buf, const char *end)
116 {
117   apr_uint64_t val;
118   char digits[3];
119 
120   if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1])
121       || ! svn_ctype_isxdigit((*buf)[2]))
122     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
123                             _("Invalid escape sequence"));
124   (*buf)++;
125   digits[0] = *((*buf)++);
126   digits[1] = *((*buf)++);
127   digits[2] = 0;
128   if ((val = apr_strtoi64(digits, NULL, 16)) == 0)
129     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
130                             _("Invalid escaped character"));
131   *result = (char) val;
132   return SVN_NO_ERROR;
133 }
134 
135 /* Read a field, possibly with escaped bytes, from [*BUF, END),
136    stopping at the terminator.  Place the read string in *RESULT, or set
137    *RESULT to NULL if it is the empty string.  Allocate the returned string
138    in POOL.  Advance *BUF to point after the terminator. */
139 static svn_error_t *
read_str(const char ** result,char ** buf,const char * end,apr_pool_t * pool)140 read_str(const char **result,
141          char **buf, const char *end,
142          apr_pool_t *pool)
143 {
144   svn_stringbuf_t *s = NULL;
145   const char *start;
146   if (*buf == end)
147     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
148                             _("Unexpected end of entry"));
149   if (**buf == '\n')
150     {
151       *result = NULL;
152       (*buf)++;
153       return SVN_NO_ERROR;
154     }
155 
156   start = *buf;
157   while (*buf != end && **buf != '\n')
158     {
159       if (**buf == '\\')
160         {
161           char c;
162           if (! s)
163             s = svn_stringbuf_ncreate(start, *buf - start, pool);
164           else
165             svn_stringbuf_appendbytes(s, start, *buf - start);
166           (*buf)++;
167           SVN_ERR(read_escaped(&c, buf, end));
168           svn_stringbuf_appendbyte(s, c);
169           start = *buf;
170         }
171       else
172         (*buf)++;
173     }
174 
175   if (*buf == end)
176     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
177                             _("Unexpected end of entry"));
178 
179   if (s)
180     {
181       svn_stringbuf_appendbytes(s, start, *buf - start);
182       *result = s->data;
183     }
184   else
185     *result = apr_pstrndup(pool, start, *buf - start);
186   (*buf)++;
187   return SVN_NO_ERROR;
188 }
189 
190 /* This is wrapper around read_str() (which see for details); it
191    simply asks svn_path_is_canonical() of the string it reads,
192    returning an error if the test fails.
193    ### It seems this is only called for entrynames now
194    */
195 static svn_error_t *
read_path(const char ** result,char ** buf,const char * end,apr_pool_t * pool)196 read_path(const char **result,
197           char **buf, const char *end,
198           apr_pool_t *pool)
199 {
200   SVN_ERR(read_str(result, buf, end, pool));
201   if (*result && **result && !svn_relpath_is_canonical(*result))
202     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
203                              _("Entry contains non-canonical path '%s'"),
204                              *result);
205   return SVN_NO_ERROR;
206 }
207 
208 /* This is read_path() for urls. This function does not do the is_canonical
209    test for entries from working copies older than version 10, as since that
210    version the canonicalization of urls has been changed. See issue #2475.
211    If the test is done and fails, read_url returs an error. */
212 static svn_error_t *
read_url(const char ** result,char ** buf,const char * end,int wc_format,apr_pool_t * pool)213 read_url(const char **result,
214          char **buf, const char *end,
215          int wc_format,
216          apr_pool_t *pool)
217 {
218   SVN_ERR(read_str(result, buf, end, pool));
219 
220   /* Always canonicalize the url, as we have stricter canonicalization rules
221      in 1.7+ then before */
222   if (*result && **result)
223     *result = svn_uri_canonicalize(*result, pool);
224 
225   return SVN_NO_ERROR;
226 }
227 
228 /* Read a field from [*BUF, END), terminated by a newline character.
229    The field may not contain escape sequences.  The field is not
230    copied and the buffer is modified in place, by replacing the
231    terminator with a NUL byte.  Make *BUF point after the original
232    terminator. */
233 static svn_error_t *
read_val(const char ** result,char ** buf,const char * end)234 read_val(const char **result,
235           char **buf, const char *end)
236 {
237   const char *start = *buf;
238 
239   if (*buf == end)
240     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
241                             _("Unexpected end of entry"));
242   if (**buf == '\n')
243     {
244       (*buf)++;
245       *result = NULL;
246       return SVN_NO_ERROR;
247     }
248 
249   while (*buf != end && **buf != '\n')
250     (*buf)++;
251   if (*buf == end)
252     return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
253                             _("Unexpected end of entry"));
254   **buf = '\0';
255   *result = start;
256   (*buf)++;
257   return SVN_NO_ERROR;
258 }
259 
260 /* Read a boolean field from [*BUF, END), placing the result in
261    *RESULT.  If there is no boolean value (just a terminator), it
262    defaults to false.  Else, the value must match FIELD_NAME, in which
263    case *RESULT will be set to true.  Advance *BUF to point after the
264    terminator. */
265 static svn_error_t *
read_bool(svn_boolean_t * result,const char * field_name,char ** buf,const char * end)266 read_bool(svn_boolean_t *result, const char *field_name,
267           char **buf, const char *end)
268 {
269   const char *val;
270   SVN_ERR(read_val(&val, buf, end));
271   if (val)
272     {
273       if (strcmp(val, field_name) != 0)
274         return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
275                                  _("Invalid value for field '%s'"),
276                                  field_name);
277       *result = TRUE;
278     }
279   else
280     *result = FALSE;
281   return SVN_NO_ERROR;
282 }
283 
284 /* Read a revision number from [*BUF, END) stopping at the
285    terminator.  Set *RESULT to the revision number, or
286    SVN_INVALID_REVNUM if there is none.  Use POOL for temporary
287    allocations.  Make *BUF point after the terminator.  */
288 static svn_error_t *
read_revnum(svn_revnum_t * result,char ** buf,const char * end,apr_pool_t * pool)289 read_revnum(svn_revnum_t *result,
290             char **buf,
291             const char *end,
292             apr_pool_t *pool)
293 {
294   const char *val;
295 
296   SVN_ERR(read_val(&val, buf, end));
297 
298   if (val)
299     *result = SVN_STR_TO_REV(val);
300   else
301     *result = SVN_INVALID_REVNUM;
302 
303   return SVN_NO_ERROR;
304 }
305 
306 /* Read a timestamp from [*BUF, END) stopping at the terminator.
307    Set *RESULT to the resulting timestamp, or 0 if there is none.  Use
308    POOL for temporary allocations.  Make *BUF point after the
309    terminator. */
310 static svn_error_t *
read_time(apr_time_t * result,char ** buf,const char * end,apr_pool_t * pool)311 read_time(apr_time_t *result,
312           char **buf, const char *end,
313           apr_pool_t *pool)
314 {
315   const char *val;
316 
317   SVN_ERR(read_val(&val, buf, end));
318   if (val)
319     SVN_ERR(svn_time_from_cstring(result, val, pool));
320   else
321     *result = 0;
322 
323   return SVN_NO_ERROR;
324 }
325 
326 /**
327  * Parse the string at *STR as an revision and save the result in
328  * *OPT_REV.  After returning successfully, *STR points at next
329  * character in *STR where further parsing can be done.
330  */
331 static svn_error_t *
string_to_opt_revision(svn_opt_revision_t * opt_rev,const char ** str,apr_pool_t * pool)332 string_to_opt_revision(svn_opt_revision_t *opt_rev,
333                        const char **str,
334                        apr_pool_t *pool)
335 {
336   const char *s = *str;
337 
338   SVN_ERR_ASSERT(opt_rev);
339 
340   while (*s && *s != ':')
341     ++s;
342 
343   /* Should not find a \0. */
344   if (!*s)
345     return svn_error_createf
346       (SVN_ERR_INCORRECT_PARAMS, NULL,
347        _("Found an unexpected \\0 in the file external '%s'"), *str);
348 
349   if (0 == strncmp(*str, "HEAD:", 5))
350     {
351       opt_rev->kind = svn_opt_revision_head;
352     }
353   else
354     {
355       svn_revnum_t rev;
356       const char *endptr;
357 
358       SVN_ERR(svn_revnum_parse(&rev, *str, &endptr));
359       SVN_ERR_ASSERT(endptr == s);
360       opt_rev->kind = svn_opt_revision_number;
361       opt_rev->value.number = rev;
362     }
363 
364   *str = s + 1;
365 
366   return SVN_NO_ERROR;
367 }
368 
369 /**
370  * Given a revision, return a string for the revision, either "HEAD"
371  * or a string representation of the revision value.  All other
372  * revision kinds return an error.
373  */
374 static svn_error_t *
opt_revision_to_string(const char ** str,const char * path,const svn_opt_revision_t * rev,apr_pool_t * pool)375 opt_revision_to_string(const char **str,
376                        const char *path,
377                        const svn_opt_revision_t *rev,
378                        apr_pool_t *pool)
379 {
380   switch (rev->kind)
381     {
382     case svn_opt_revision_head:
383       *str = apr_pstrmemdup(pool, "HEAD", 4);
384       break;
385     case svn_opt_revision_number:
386       *str = apr_ltoa(pool, rev->value.number);
387       break;
388     default:
389       return svn_error_createf
390         (SVN_ERR_INCORRECT_PARAMS, NULL,
391          _("Illegal file external revision kind %d for path '%s'"),
392          rev->kind, path);
393       break;
394     }
395 
396   return SVN_NO_ERROR;
397 }
398 
399 svn_error_t *
svn_wc__unserialize_file_external(const char ** path_result,svn_opt_revision_t * peg_rev_result,svn_opt_revision_t * rev_result,const char * str,apr_pool_t * pool)400 svn_wc__unserialize_file_external(const char **path_result,
401                                   svn_opt_revision_t *peg_rev_result,
402                                   svn_opt_revision_t *rev_result,
403                                   const char *str,
404                                   apr_pool_t *pool)
405 {
406   if (str)
407     {
408       svn_opt_revision_t peg_rev;
409       svn_opt_revision_t op_rev;
410       const char *s = str;
411 
412       SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool));
413       SVN_ERR(string_to_opt_revision(&op_rev, &s, pool));
414 
415       *path_result = apr_pstrdup(pool, s);
416       *peg_rev_result = peg_rev;
417       *rev_result = op_rev;
418     }
419   else
420     {
421       *path_result = NULL;
422       peg_rev_result->kind = svn_opt_revision_unspecified;
423       rev_result->kind = svn_opt_revision_unspecified;
424     }
425 
426   return SVN_NO_ERROR;
427 }
428 
429 svn_error_t *
svn_wc__serialize_file_external(const char ** str,const char * path,const svn_opt_revision_t * peg_rev,const svn_opt_revision_t * rev,apr_pool_t * pool)430 svn_wc__serialize_file_external(const char **str,
431                                 const char *path,
432                                 const svn_opt_revision_t *peg_rev,
433                                 const svn_opt_revision_t *rev,
434                                 apr_pool_t *pool)
435 {
436   const char *s;
437 
438   if (path)
439     {
440       const char *s1;
441       const char *s2;
442 
443       SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool));
444       SVN_ERR(opt_revision_to_string(&s2, path, rev, pool));
445 
446       s = apr_pstrcat(pool, s1, ":", s2, ":", path, SVN_VA_NULL);
447     }
448   else
449     s = NULL;
450 
451   *str = s;
452 
453   return SVN_NO_ERROR;
454 }
455 
456 /* Allocate an entry from POOL and read it from [*BUF, END).  The
457    buffer may be modified in place while parsing.  Return the new
458    entry in *NEW_ENTRY.  Advance *BUF to point at the end of the entry
459    record.
460    The entries file format should be provided in ENTRIES_FORMAT. */
461 static svn_error_t *
read_entry(svn_wc_entry_t ** new_entry,char ** buf,const char * end,int entries_format,apr_pool_t * pool)462 read_entry(svn_wc_entry_t **new_entry,
463            char **buf, const char *end,
464            int entries_format,
465            apr_pool_t *pool)
466 {
467   svn_wc_entry_t *entry = alloc_entry(pool);
468   const char *name;
469 
470 #define MAYBE_DONE if (**buf == '\f') goto done
471 
472   /* Find the name and set up the entry under that name. */
473   SVN_ERR(read_path(&name, buf, end, pool));
474   entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR;
475 
476   /* Set up kind. */
477   {
478     const char *kindstr;
479     SVN_ERR(read_val(&kindstr, buf, end));
480     if (kindstr)
481       {
482         if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
483           entry->kind = svn_node_file;
484         else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
485           entry->kind = svn_node_dir;
486         else
487           return svn_error_createf
488             (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
489              _("Entry '%s' has invalid node kind"),
490              (name ? name : SVN_WC_ENTRY_THIS_DIR));
491       }
492     else
493       entry->kind = svn_node_none;
494   }
495   MAYBE_DONE;
496 
497   /* Attempt to set revision (resolve_to_defaults may do it later, too) */
498   SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
499   MAYBE_DONE;
500 
501   /* Attempt to set up url path (again, see resolve_to_defaults). */
502   SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool));
503   MAYBE_DONE;
504 
505   /* Set up repository root.  Make sure it is a prefix of url. */
506   SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool));
507   if (entry->repos && entry->url
508       && ! svn_uri__is_ancestor(entry->repos, entry->url))
509     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
510                              _("Entry for '%s' has invalid repository "
511                                "root"),
512                              name ? name : SVN_WC_ENTRY_THIS_DIR);
513   MAYBE_DONE;
514 
515   /* Look for a schedule attribute on this entry. */
516   {
517     const char *schedulestr;
518     SVN_ERR(read_val(&schedulestr, buf, end));
519     entry->schedule = svn_wc_schedule_normal;
520     if (schedulestr)
521       {
522         if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
523           entry->schedule = svn_wc_schedule_add;
524         else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
525           entry->schedule = svn_wc_schedule_delete;
526         else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
527           entry->schedule = svn_wc_schedule_replace;
528         else
529           return svn_error_createf(
530             SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
531             _("Entry '%s' has invalid 'schedule' value"),
532             name ? name : SVN_WC_ENTRY_THIS_DIR);
533       }
534   }
535   MAYBE_DONE;
536 
537   /* Attempt to set up text timestamp. */
538   SVN_ERR(read_time(&entry->text_time, buf, end, pool));
539   MAYBE_DONE;
540 
541   /* Checksum. */
542   SVN_ERR(read_str(&entry->checksum, buf, end, pool));
543   MAYBE_DONE;
544 
545   /* Setup last-committed values. */
546   SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
547   MAYBE_DONE;
548 
549   SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
550   MAYBE_DONE;
551 
552   SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
553   MAYBE_DONE;
554 
555   /* has-props, has-prop-mods, cachable-props, present-props are all
556      deprecated. Read any values that may be in the 'entries' file, but
557      discard them, and just put default values into the entry. */
558   {
559     const char *unused_value;
560 
561     /* has-props flag. */
562     SVN_ERR(read_val(&unused_value, buf, end));
563     entry->has_props = FALSE;
564     MAYBE_DONE;
565 
566     /* has-prop-mods flag. */
567     SVN_ERR(read_val(&unused_value, buf, end));
568     entry->has_prop_mods = FALSE;
569     MAYBE_DONE;
570 
571     /* Use the empty string for cachable_props, indicating that we no
572        longer attempt to cache any properties. An empty string for
573        present_props means that no cachable props are present. */
574 
575     /* cachable-props string. */
576     SVN_ERR(read_val(&unused_value, buf, end));
577     entry->cachable_props = "";
578     MAYBE_DONE;
579 
580     /* present-props string. */
581     SVN_ERR(read_val(&unused_value, buf, end));
582     entry->present_props = "";
583     MAYBE_DONE;
584   }
585 
586   /* Is this entry in a state of mental torment (conflict)? */
587   {
588     SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
589     MAYBE_DONE;
590     SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
591     MAYBE_DONE;
592     SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
593     MAYBE_DONE;
594     SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
595     MAYBE_DONE;
596   }
597 
598   /* Is this entry copied? */
599   SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end));
600   MAYBE_DONE;
601 
602   SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool));
603   MAYBE_DONE;
604   SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
605   MAYBE_DONE;
606 
607   /* Is this entry deleted? */
608   SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end));
609   MAYBE_DONE;
610 
611   /* Is this entry absent? */
612   SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end));
613   MAYBE_DONE;
614 
615   /* Is this entry incomplete? */
616   SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end));
617   MAYBE_DONE;
618 
619   /* UUID. */
620   SVN_ERR(read_str(&entry->uuid, buf, end, pool));
621   MAYBE_DONE;
622 
623   /* Lock token. */
624   SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
625   MAYBE_DONE;
626 
627   /* Lock owner. */
628   SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
629   MAYBE_DONE;
630 
631   /* Lock comment. */
632   SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
633   MAYBE_DONE;
634 
635   /* Lock creation date. */
636   SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
637   MAYBE_DONE;
638 
639   /* Changelist. */
640   SVN_ERR(read_str(&entry->changelist, buf, end, pool));
641   MAYBE_DONE;
642 
643   /* Keep entry in working copy after deletion? */
644   SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end));
645   MAYBE_DONE;
646 
647   /* Translated size */
648   {
649     const char *val;
650 
651     /* read_val() returns NULL on an empty (e.g. default) entry line,
652        and entry has already been initialized accordingly already */
653     SVN_ERR(read_val(&val, buf, end));
654     if (val)
655       entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
656   }
657   MAYBE_DONE;
658 
659   /* Depth. */
660   {
661     const char *result;
662     SVN_ERR(read_val(&result, buf, end));
663     if (result)
664       {
665         svn_boolean_t invalid;
666         svn_boolean_t is_this_dir;
667 
668         entry->depth = svn_depth_from_word(result);
669 
670         /* Verify the depth value:
671            THIS_DIR should not have an excluded value and SUB_DIR should only
672            have excluded value. Remember that infinity value is not stored and
673            should not show up here. Otherwise, something bad may have
674            happened. However, infinity value itself will always be okay. */
675         is_this_dir = !name;
676         /* '!=': XOR */
677         invalid = is_this_dir != (entry->depth != svn_depth_exclude);
678         if (entry->depth != svn_depth_infinity && invalid)
679           return svn_error_createf(
680             SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
681             _("Entry '%s' has invalid 'depth' value"),
682             name ? name : SVN_WC_ENTRY_THIS_DIR);
683       }
684     else
685       entry->depth = svn_depth_infinity;
686 
687   }
688   MAYBE_DONE;
689 
690   /* Tree conflict data. */
691   SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool));
692   MAYBE_DONE;
693 
694   /* File external URL and revision. */
695   {
696     const char *str;
697     SVN_ERR(read_str(&str, buf, end, pool));
698     SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path,
699                                               &entry->file_external_peg_rev,
700                                               &entry->file_external_rev,
701                                               str,
702                                               pool));
703   }
704   MAYBE_DONE;
705 
706  done:
707   *new_entry = entry;
708   return SVN_NO_ERROR;
709 }
710 
711 
712 /* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its
713    boolean value, else set *ENTRY_FLAG false.  ENTRY_NAME is the name
714    of the WC-entry. */
715 static svn_error_t *
do_bool_attr(svn_boolean_t * entry_flag,apr_hash_t * atts,const char * attr_name,const char * entry_name)716 do_bool_attr(svn_boolean_t *entry_flag,
717              apr_hash_t *atts, const char *attr_name,
718              const char *entry_name)
719 {
720   const char *str = svn_hash_gets(atts, attr_name);
721 
722   *entry_flag = FALSE;
723   if (str)
724     {
725       if (strcmp(str, "true") == 0)
726         *entry_flag = TRUE;
727       else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
728         *entry_flag = FALSE;
729       else
730         return svn_error_createf
731           (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
732            _("Entry '%s' has invalid '%s' value"),
733            (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name);
734     }
735   return SVN_NO_ERROR;
736 }
737 
738 
739 /* */
740 static const char *
extract_string(apr_hash_t * atts,const char * att_name,apr_pool_t * result_pool)741 extract_string(apr_hash_t *atts,
742                const char *att_name,
743                apr_pool_t *result_pool)
744 {
745   const char *value = svn_hash_gets(atts, att_name);
746 
747   if (value == NULL)
748     return NULL;
749 
750   return apr_pstrdup(result_pool, value);
751 }
752 
753 
754 /* Like extract_string(), but normalizes empty strings to NULL.  */
755 static const char *
extract_string_normalize(apr_hash_t * atts,const char * att_name,apr_pool_t * result_pool)756 extract_string_normalize(apr_hash_t *atts,
757                          const char *att_name,
758                          apr_pool_t *result_pool)
759 {
760   const char *value = svn_hash_gets(atts, att_name);
761 
762   if (value == NULL)
763     return NULL;
764 
765   if (*value == '\0')
766     return NULL;
767 
768   return apr_pstrdup(result_pool, value);
769 }
770 
771 
772 /* NOTE: this is used for upgrading old XML-based entries file. Be wary of
773          removing items.
774 
775    ### many attributes are no longer used within the old-style log files.
776    ### These attrs need to be recognized for old entries, however. For these
777    ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS
778    ### for that particular field. MODIFY_FLAGS is *only* used by the
779    ### log-based entry modification system, and will go way once we
780    ### completely move away from loggy.
781 
782    Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose
783    keys and values are both char *.  Allocate the entry and copy
784    attributes into POOL as needed. */
785 static svn_error_t *
atts_to_entry(svn_wc_entry_t ** new_entry,apr_hash_t * atts,apr_pool_t * pool)786 atts_to_entry(svn_wc_entry_t **new_entry,
787               apr_hash_t *atts,
788               apr_pool_t *pool)
789 {
790   svn_wc_entry_t *entry = alloc_entry(pool);
791   const char *name;
792 
793   /* Find the name and set up the entry under that name. */
794   name = svn_hash_gets(atts, ENTRIES_ATTR_NAME);
795   entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR;
796 
797   /* Attempt to set revision (resolve_to_defaults may do it later, too)
798 
799      ### not used by loggy; no need to set MODIFY_FLAGS  */
800   {
801     const char *revision_str
802       = svn_hash_gets(atts, ENTRIES_ATTR_REVISION);
803 
804     if (revision_str)
805       entry->revision = SVN_STR_TO_REV(revision_str);
806     else
807       entry->revision = SVN_INVALID_REVNUM;
808   }
809 
810   /* Attempt to set up url path (again, see resolve_to_defaults).
811 
812      ### not used by loggy; no need to set MODIFY_FLAGS  */
813   entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool);
814   if (entry->url)
815     entry->url = svn_uri_canonicalize(entry->url, pool);
816 
817   /* Set up repository root.  Make sure it is a prefix of url.
818 
819      ### not used by loggy; no need to set MODIFY_FLAGS  */
820   entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool);
821   if (entry->repos)
822     entry->repos = svn_uri_canonicalize(entry->repos, pool);
823 
824   if (entry->url && entry->repos
825       && !svn_uri__is_ancestor(entry->repos, entry->url))
826     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
827                              _("Entry for '%s' has invalid repository "
828                                "root"),
829                              name ? name : SVN_WC_ENTRY_THIS_DIR);
830 
831   /* Set up kind. */
832   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
833   {
834     const char *kindstr
835       = svn_hash_gets(atts, ENTRIES_ATTR_KIND);
836 
837     entry->kind = svn_node_none;
838     if (kindstr)
839       {
840         if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
841           entry->kind = svn_node_file;
842         else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
843           entry->kind = svn_node_dir;
844         else
845           return svn_error_createf
846             (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
847              _("Entry '%s' has invalid node kind"),
848              (name ? name : SVN_WC_ENTRY_THIS_DIR));
849       }
850   }
851 
852   /* Look for a schedule attribute on this entry. */
853   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
854   {
855     const char *schedulestr
856       = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE);
857 
858     entry->schedule = svn_wc_schedule_normal;
859     if (schedulestr)
860       {
861         if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
862           entry->schedule = svn_wc_schedule_add;
863         else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
864           entry->schedule = svn_wc_schedule_delete;
865         else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
866           entry->schedule = svn_wc_schedule_replace;
867         else if (strcmp(schedulestr, "") == 0)
868           entry->schedule = svn_wc_schedule_normal;
869         else
870           return svn_error_createf(
871                    SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
872                    _("Entry '%s' has invalid 'schedule' value"),
873                    (name ? name : SVN_WC_ENTRY_THIS_DIR));
874       }
875   }
876 
877   /* Is this entry in a state of mental torment (conflict)? */
878   entry->prejfile = extract_string_normalize(atts,
879                                              ENTRIES_ATTR_PREJFILE,
880                                              pool);
881   entry->conflict_old = extract_string_normalize(atts,
882                                                  ENTRIES_ATTR_CONFLICT_OLD,
883                                                  pool);
884   entry->conflict_new = extract_string_normalize(atts,
885                                                  ENTRIES_ATTR_CONFLICT_NEW,
886                                                  pool);
887   entry->conflict_wrk = extract_string_normalize(atts,
888                                                  ENTRIES_ATTR_CONFLICT_WRK,
889                                                  pool);
890 
891   /* Is this entry copied? */
892   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
893   SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name));
894 
895   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
896   entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool);
897 
898   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
899   {
900     const char *revstr;
901 
902     revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV);
903     if (revstr)
904       entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
905   }
906 
907   /* Is this entry deleted?
908 
909      ### not used by loggy; no need to set MODIFY_FLAGS  */
910   SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name));
911 
912   /* Is this entry absent?
913 
914      ### not used by loggy; no need to set MODIFY_FLAGS  */
915   SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name));
916 
917   /* Is this entry incomplete?
918 
919      ### not used by loggy; no need to set MODIFY_FLAGS  */
920   SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE,
921                        name));
922 
923   /* Attempt to set up timestamps. */
924   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
925   {
926     const char *text_timestr;
927 
928     text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME);
929     if (text_timestr)
930       SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool));
931 
932     /* Note: we do not persist prop_time, so there is no need to attempt
933        to parse a new prop_time value from the log. Certainly, on any
934        recent working copy, there will not be a log record to alter
935        the prop_time value. */
936   }
937 
938   /* Checksum. */
939   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
940   entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool);
941 
942   /* UUID.
943 
944      ### not used by loggy; no need to set MODIFY_FLAGS  */
945   entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool);
946 
947   /* Setup last-committed values. */
948   {
949     const char *cmt_datestr, *cmt_revstr;
950 
951     cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE);
952     if (cmt_datestr)
953       {
954         SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
955       }
956     else
957       entry->cmt_date = 0;
958 
959     cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV);
960     if (cmt_revstr)
961       {
962         entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
963       }
964     else
965       entry->cmt_rev = SVN_INVALID_REVNUM;
966 
967     entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool);
968   }
969 
970   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
971   entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool);
972   entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool);
973   entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool);
974   {
975     const char *cdate_str =
976       svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE);
977     if (cdate_str)
978       {
979         SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
980                                       cdate_str, pool));
981       }
982   }
983   /* ----- end of lock handling.  */
984 
985   /* Note: if there are attributes for the (deprecated) has_props,
986      has_prop_mods, cachable_props, or present_props, then we're just
987      going to ignore them. */
988 
989   /* Translated size */
990   /* ### not used by loggy; no need to set MODIFY_FLAGS  */
991   {
992     const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE);
993     if (val)
994       {
995         /* Cast to off_t; it's safe: we put in an off_t to start with... */
996         entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
997       }
998   }
999 
1000   *new_entry = entry;
1001   return SVN_NO_ERROR;
1002 }
1003 
1004 /* Used when reading an entries file in XML format. */
1005 struct entries_accumulator
1006 {
1007   /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
1008   apr_hash_t *entries;
1009 
1010   /* The parser that's parsing it, for signal_expat_bailout(). */
1011   svn_xml_parser_t *parser;
1012 
1013   /* Don't leave home without one. */
1014   apr_pool_t *pool;
1015 
1016   /* Cleared before handling each entry. */
1017   apr_pool_t *scratch_pool;
1018 };
1019 
1020 
1021 
1022 /* Called whenever we find an <open> tag of some kind. */
1023 static void
handle_start_tag(void * userData,const char * tagname,const char ** atts)1024 handle_start_tag(void *userData, const char *tagname, const char **atts)
1025 {
1026   struct entries_accumulator *accum = userData;
1027   apr_hash_t *attributes;
1028   svn_wc_entry_t *entry;
1029   svn_error_t *err;
1030 
1031   /* We only care about the `entry' tag; all other tags, such as `xml'
1032      and `wc-entries', are ignored. */
1033   if (strcmp(tagname, ENTRIES_TAG_ENTRY))
1034     return;
1035 
1036   svn_pool_clear(accum->scratch_pool);
1037   /* Make an entry from the attributes. */
1038   attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
1039   err = atts_to_entry(&entry, attributes, accum->pool);
1040   if (err)
1041     {
1042       svn_xml_signal_bailout(err, accum->parser);
1043       return;
1044     }
1045 
1046   /* Find the name and set up the entry under that name.  This
1047      should *NOT* be NULL, since svn_wc__atts_to_entry() should
1048      have made it into SVN_WC_ENTRY_THIS_DIR. */
1049   svn_hash_sets(accum->entries, entry->name, entry);
1050 }
1051 
1052 /* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
1053    entries in ENTRIES.  Use SCRATCH_POOL for temporary allocations and
1054    RESULT_POOL for the returned entries.  */
1055 static svn_error_t *
parse_entries_xml(const char * dir_abspath,apr_hash_t * entries,const char * buf,apr_size_t size,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1056 parse_entries_xml(const char *dir_abspath,
1057                   apr_hash_t *entries,
1058                   const char *buf,
1059                   apr_size_t size,
1060                   apr_pool_t *result_pool,
1061                   apr_pool_t *scratch_pool)
1062 {
1063   svn_xml_parser_t *svn_parser;
1064   struct entries_accumulator accum;
1065 
1066   /* Set up userData for the XML parser. */
1067   accum.entries = entries;
1068   accum.pool = result_pool;
1069   accum.scratch_pool = svn_pool_create(scratch_pool);
1070 
1071   /* Create the XML parser */
1072   svn_parser = svn_xml_make_parser(&accum,
1073                                    handle_start_tag,
1074                                    NULL,
1075                                    NULL,
1076                                    scratch_pool);
1077 
1078   /* Store parser in its own userdata, so callbacks can call
1079      svn_xml_signal_bailout() */
1080   accum.parser = svn_parser;
1081 
1082   /* Parse. */
1083   SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
1084             apr_psprintf(scratch_pool,
1085                          _("XML parser failed in '%s'"),
1086                          svn_dirent_local_style(dir_abspath, scratch_pool)));
1087 
1088   svn_pool_destroy(accum.scratch_pool);
1089 
1090   /* Clean up the XML parser */
1091   svn_xml_free_parser(svn_parser);
1092 
1093   return SVN_NO_ERROR;
1094 }
1095 
1096 
1097 
1098 /* Use entry SRC to fill in blank portions of entry DST.  SRC itself
1099    may not have any blanks, of course.
1100    Typically, SRC is a parent directory's own entry, and DST is some
1101    child in that directory. */
1102 static void
take_from_entry(const svn_wc_entry_t * src,svn_wc_entry_t * dst,apr_pool_t * pool)1103 take_from_entry(const svn_wc_entry_t *src,
1104                 svn_wc_entry_t *dst,
1105                 apr_pool_t *pool)
1106 {
1107   /* Inherits parent's revision if doesn't have a revision of one's
1108      own, unless this is a subdirectory. */
1109   if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
1110     dst->revision = src->revision;
1111 
1112   /* Inherits parent's url if doesn't have a url of one's own. */
1113   if (! dst->url)
1114     dst->url = svn_path_url_add_component2(src->url, dst->name, pool);
1115 
1116   if (! dst->repos)
1117     dst->repos = src->repos;
1118 
1119   if ((! dst->uuid)
1120       && (! ((dst->schedule == svn_wc_schedule_add)
1121              || (dst->schedule == svn_wc_schedule_replace))))
1122     {
1123       dst->uuid = src->uuid;
1124     }
1125 }
1126 
1127 /* Resolve any missing information in ENTRIES by deducing from the
1128    directory's own entry (which must already be present in ENTRIES). */
1129 static svn_error_t *
resolve_to_defaults(apr_hash_t * entries,apr_pool_t * pool)1130 resolve_to_defaults(apr_hash_t *entries,
1131                     apr_pool_t *pool)
1132 {
1133   apr_hash_index_t *hi;
1134   svn_wc_entry_t *default_entry
1135     = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
1136 
1137   /* First check the dir's own entry for consistency. */
1138   if (! default_entry)
1139     return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
1140                             NULL,
1141                             _("Missing default entry"));
1142 
1143   if (default_entry->revision == SVN_INVALID_REVNUM)
1144     return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
1145                             NULL,
1146                             _("Default entry has no revision number"));
1147 
1148   if (! default_entry->url)
1149     return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
1150                             NULL,
1151                             _("Default entry is missing URL"));
1152 
1153 
1154   /* Then use it to fill in missing information in other entries. */
1155   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1156     {
1157       svn_wc_entry_t *this_entry = apr_hash_this_val(hi);
1158 
1159       if (this_entry == default_entry)
1160         /* THIS_DIR already has all the information it can possibly
1161            have.  */
1162         continue;
1163 
1164       if (this_entry->kind == svn_node_dir)
1165         /* Entries that are directories have everything but their
1166            name, kind, and state stored in the THIS_DIR entry of the
1167            directory itself.  However, we are disallowing the perusing
1168            of any entries outside of the current entries file.  If a
1169            caller wants more info about a directory, it should look in
1170            the entries file in the directory.  */
1171         continue;
1172 
1173       if (this_entry->kind == svn_node_file)
1174         /* For file nodes that do not explicitly have their ancestry
1175            stated, this can be derived from the default entry of the
1176            directory in which those files reside.  */
1177         take_from_entry(default_entry, this_entry, pool);
1178     }
1179 
1180   return SVN_NO_ERROR;
1181 }
1182 
1183 
1184 
1185 /* Read and parse an old-style 'entries' file in the administrative area
1186    of PATH, filling in ENTRIES with the contents. The results will be
1187    allocated in RESULT_POOL, and temporary allocations will be made in
1188    SCRATCH_POOL.  */
1189 svn_error_t *
svn_wc__read_entries_old(apr_hash_t ** entries,const char * dir_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1190 svn_wc__read_entries_old(apr_hash_t **entries,
1191                          const char *dir_abspath,
1192                          apr_pool_t *result_pool,
1193                          apr_pool_t *scratch_pool)
1194 {
1195   char *curp;
1196   const char *endp;
1197   svn_wc_entry_t *entry;
1198   svn_stream_t *stream;
1199   svn_string_t *buf;
1200 
1201   *entries = apr_hash_make(result_pool);
1202 
1203   /* Open the entries file. */
1204   SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES,
1205                                   scratch_pool, scratch_pool));
1206   SVN_ERR(svn_string_from_stream2(&buf, stream, SVN__STREAM_CHUNK_SIZE,
1207                                   scratch_pool));
1208 
1209   /* We own the returned data; it is modifiable, so cast away... */
1210   curp = (char *)buf->data;
1211   endp = buf->data + buf->len;
1212 
1213   /* If the first byte of the file is not a digit, then it is probably in XML
1214      format. */
1215   if (curp != endp && !svn_ctype_isdigit(*curp))
1216     SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len,
1217                               result_pool, scratch_pool));
1218   else
1219     {
1220       int entryno, entries_format;
1221       const char *val;
1222 
1223       /* Read the format line from the entries file. In case we're in the
1224          middle of upgrading a working copy, this line will contain the
1225          original format pre-upgrade. */
1226       SVN_ERR(read_val(&val, &curp, endp));
1227       if (val)
1228         entries_format = (int)apr_strtoi64(val, NULL, 0);
1229       else
1230         return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
1231                                  _("Invalid version line in entries file "
1232                                    "of '%s'"),
1233                                  svn_dirent_local_style(dir_abspath,
1234                                                         scratch_pool));
1235       entryno = 1;
1236 
1237       while (curp != endp)
1238         {
1239           svn_error_t *err = read_entry(&entry, &curp, endp,
1240                                         entries_format, result_pool);
1241           if (! err)
1242             {
1243               /* We allow extra fields at the end of the line, for
1244                  extensibility. */
1245               curp = memchr(curp, '\f', endp - curp);
1246               if (! curp)
1247                 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1248                                        _("Missing entry terminator"));
1249               if (! err && (curp == endp || *(++curp) != '\n'))
1250                 err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
1251                                        _("Invalid entry terminator"));
1252             }
1253           if (err)
1254             return svn_error_createf(err->apr_err, err,
1255                                      _("Error at entry %d in entries file for "
1256                                        "'%s':"),
1257                                      entryno,
1258                                      svn_dirent_local_style(dir_abspath,
1259                                                             scratch_pool));
1260 
1261           ++curp;
1262           ++entryno;
1263 
1264           svn_hash_sets(*entries, entry->name, entry);
1265         }
1266     }
1267 
1268   /* Fill in any implied fields. */
1269   return svn_error_trace(resolve_to_defaults(*entries, result_pool));
1270 }
1271 
1272 
1273 /* For non-directory PATHs full entry information is obtained by reading
1274  * the entries for the parent directory of PATH and then extracting PATH's
1275  * entry.  If PATH is a directory then only abrieviated information is
1276  * available in the parent directory, more complete information is
1277  * available by reading the entries for PATH itself.
1278  *
1279  * Note: There is one bit of information about directories that is only
1280  * available in the parent directory, that is the "deleted" state.  If PATH
1281  * is a versioned directory then the "deleted" state information will not
1282  * be returned in ENTRY.  This means some bits of the code (e.g. revert)
1283  * need to obtain it by directly extracting the directory entry from the
1284  * parent directory's entries.  I wonder if this function should handle
1285  * that?
1286  */
1287 svn_error_t *
svn_wc_entry(const svn_wc_entry_t ** entry,const char * path,svn_wc_adm_access_t * adm_access,svn_boolean_t show_hidden,apr_pool_t * pool)1288 svn_wc_entry(const svn_wc_entry_t **entry,
1289              const char *path,
1290              svn_wc_adm_access_t *adm_access,
1291              svn_boolean_t show_hidden,
1292              apr_pool_t *pool)
1293 {
1294   svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
1295   const char *local_abspath;
1296   svn_wc_adm_access_t *dir_access;
1297   const char *entry_name;
1298   apr_hash_t *entries;
1299 
1300   SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
1301 
1302   /* Does the provided path refer to a directory with an associated
1303      access baton?  */
1304   dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool);
1305   if (dir_access == NULL)
1306     {
1307       /* Damn. Okay. Assume the path is to a child, and let's look for
1308          a baton associated with its parent.  */
1309 
1310       const char *dir_abspath;
1311 
1312       svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool);
1313 
1314       dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool);
1315     }
1316   else
1317     {
1318       /* Woo! Got one. Look for "this dir" in the entries hash.  */
1319       entry_name = "";
1320     }
1321 
1322   if (dir_access == NULL)
1323     {
1324       /* Early exit.  */
1325       *entry = NULL;
1326       return SVN_NO_ERROR;
1327     }
1328 
1329   /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
1330      fetch all entries here (optimization) since we know how to filter
1331      out a "hidden" node.  */
1332   SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
1333   *entry = svn_hash_gets(entries, entry_name);
1334 
1335   if (!show_hidden && *entry != NULL)
1336     {
1337       svn_boolean_t hidden;
1338 
1339       SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry));
1340       if (hidden)
1341         *entry = NULL;
1342     }
1343 
1344   return SVN_NO_ERROR;
1345 }
1346