wc_db_wcroot.c revision 289166
1/*
2 * wc_db_wcroot.c :  supporting datastructures for the administrative database
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#define SVN_WC__I_AM_WC_DB
25
26#include <assert.h>
27
28#include "svn_dirent_uri.h"
29#include "svn_hash.h"
30#include "svn_path.h"
31#include "svn_version.h"
32
33#include "wc.h"
34#include "adm_files.h"
35#include "wc_db_private.h"
36#include "wc-queries.h"
37
38#include "svn_private_config.h"
39
40/* ### Same values as wc_db.c */
41#define SDB_FILE  "wc.db"
42#define UNKNOWN_WC_ID ((apr_int64_t) -1)
43#define FORMAT_FROM_SDB (-1)
44
45
46
47/* Get the format version from a wc-1 directory. If it is not a working copy
48   directory, then it sets VERSION to zero and returns no error.  */
49static svn_error_t *
50get_old_version(int *version,
51                const char *abspath,
52                apr_pool_t *scratch_pool)
53{
54  svn_error_t *err;
55  const char *format_file_path;
56  svn_node_kind_t kind;
57
58  /* Try reading the format number from the entries file.  */
59  format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES,
60                                       scratch_pool);
61
62  /* Since trying to open a non-existent file is quite expensive, try a
63     quick stat call first. In wc-ng w/cs, this will be an early exit. */
64  SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool));
65  if (kind == svn_node_none)
66    {
67      *version = 0;
68      return SVN_NO_ERROR;
69    }
70
71  err = svn_io_read_version_file(version, format_file_path, scratch_pool);
72  if (err == NULL)
73    return SVN_NO_ERROR;
74  if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT
75      && !APR_STATUS_IS_ENOENT(err->apr_err)
76      && !APR_STATUS_IS_ENOTDIR(err->apr_err))
77    return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
78                             svn_dirent_local_style(abspath, scratch_pool));
79  svn_error_clear(err);
80
81  /* This must be a really old working copy!  Fall back to reading the
82     format file.
83
84     Note that the format file might not exist in newer working copies
85     (format 7 and higher), but in that case, the entries file should
86     have contained the format number. */
87  format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT,
88                                       scratch_pool);
89  err = svn_io_read_version_file(version, format_file_path, scratch_pool);
90  if (err == NULL)
91    return SVN_NO_ERROR;
92
93  /* Whatever error may have occurred... we can just ignore. This is not
94     a working copy directory. Signal the caller.  */
95  svn_error_clear(err);
96
97  *version = 0;
98  return SVN_NO_ERROR;
99}
100
101
102/* A helper function to parse_local_abspath() which returns the on-disk KIND
103   of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed.
104
105   This function may do strange things, but at long as it comes up with the
106   Right Answer, we should be happy. */
107static svn_error_t *
108get_path_kind(svn_node_kind_t *kind,
109              svn_wc__db_t *db,
110              const char *local_abspath,
111              apr_pool_t *scratch_pool)
112{
113  svn_boolean_t special;
114  svn_node_kind_t node_kind;
115
116  /* This implements a *really* simple LRU cache, where "simple" is defined
117     as "only one element".  In other words, we remember the most recently
118     queried path, and nothing else.  This gives >80% cache hits. */
119
120  if (db->parse_cache.abspath
121        && strcmp(db->parse_cache.abspath->data, local_abspath) == 0)
122    {
123      /* Cache hit! */
124      *kind = db->parse_cache.kind;
125      return SVN_NO_ERROR;
126    }
127
128  if (!db->parse_cache.abspath)
129    {
130      db->parse_cache.abspath = svn_stringbuf_create(local_abspath,
131                                                     db->state_pool);
132    }
133  else
134    {
135      svn_stringbuf_set(db->parse_cache.abspath, local_abspath);
136    }
137
138  SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind,
139                                    &special, scratch_pool));
140
141  db->parse_cache.kind = (special ? svn_node_symlink : node_kind);
142  *kind = db->parse_cache.kind;
143
144  return SVN_NO_ERROR;
145}
146
147
148/* Return an error if the work queue in SDB is non-empty. */
149static svn_error_t *
150verify_no_work(svn_sqlite__db_t *sdb)
151{
152  svn_sqlite__stmt_t *stmt;
153  svn_boolean_t have_row;
154
155  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK));
156  SVN_ERR(svn_sqlite__step(&have_row, stmt));
157  SVN_ERR(svn_sqlite__reset(stmt));
158
159  if (have_row)
160    return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL,
161                            NULL /* nothing to add.  */);
162
163  return SVN_NO_ERROR;
164}
165
166
167/* */
168static apr_status_t
169close_wcroot(void *data)
170{
171  svn_wc__db_wcroot_t *wcroot = data;
172  svn_error_t *err;
173
174  SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL);
175
176  err = svn_sqlite__close(wcroot->sdb);
177  wcroot->sdb = NULL;
178  if (err)
179    {
180      apr_status_t result = err->apr_err;
181      svn_error_clear(err);
182      return result;
183    }
184
185  return APR_SUCCESS;
186}
187
188
189svn_error_t *
190svn_wc__db_open(svn_wc__db_t **db,
191                svn_config_t *config,
192                svn_boolean_t open_without_upgrade,
193                svn_boolean_t enforce_empty_wq,
194                apr_pool_t *result_pool,
195                apr_pool_t *scratch_pool)
196{
197  *db = apr_pcalloc(result_pool, sizeof(**db));
198  (*db)->config = config;
199  (*db)->verify_format = !open_without_upgrade;
200  (*db)->enforce_empty_wq = enforce_empty_wq;
201  (*db)->dir_data = apr_hash_make(result_pool);
202
203  (*db)->state_pool = result_pool;
204
205  /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
206  if (config)
207    {
208      svn_error_t *err;
209      svn_boolean_t sqlite_exclusive = FALSE;
210
211      err = svn_config_get_bool(config, &sqlite_exclusive,
212                                SVN_CONFIG_SECTION_WORKING_COPY,
213                                SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
214                                FALSE);
215      if (err)
216        {
217          svn_error_clear(err);
218        }
219      else
220        (*db)->exclusive = sqlite_exclusive;
221    }
222
223  return SVN_NO_ERROR;
224}
225
226
227svn_error_t *
228svn_wc__db_close(svn_wc__db_t *db)
229{
230  apr_pool_t *scratch_pool = db->state_pool;
231  apr_hash_t *roots = apr_hash_make(scratch_pool);
232  apr_hash_index_t *hi;
233
234  /* Collect all the unique WCROOT structures, and empty out DIR_DATA.  */
235  for (hi = apr_hash_first(scratch_pool, db->dir_data);
236       hi;
237       hi = apr_hash_next(hi))
238    {
239      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
240      const char *local_abspath = svn__apr_hash_index_key(hi);
241
242      if (wcroot->sdb)
243        svn_hash_sets(roots, wcroot->abspath, wcroot);
244
245      svn_hash_sets(db->dir_data, local_abspath, NULL);
246    }
247
248  /* Run the cleanup for each WCROOT.  */
249  return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
250                                                       scratch_pool));
251}
252
253
254svn_error_t *
255svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
256                             const char *wcroot_abspath,
257                             svn_sqlite__db_t *sdb,
258                             apr_int64_t wc_id,
259                             int format,
260                             svn_boolean_t verify_format,
261                             svn_boolean_t enforce_empty_wq,
262                             apr_pool_t *result_pool,
263                             apr_pool_t *scratch_pool)
264{
265  if (sdb && format == FORMAT_FROM_SDB)
266    SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
267
268  /* If we construct a wcroot, then we better have a format.  */
269  SVN_ERR_ASSERT(format >= 1);
270
271  /* If this working copy is PRE-1.0, then simply bail out.  */
272  if (format < 4)
273    {
274      return svn_error_createf(
275        SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
276        _("Working copy format of '%s' is too old (%d); "
277          "please check out your working copy again"),
278        svn_dirent_local_style(wcroot_abspath, scratch_pool), format);
279    }
280
281  /* If this working copy is from a future version, then bail out.  */
282  if (format > SVN_WC__VERSION)
283    {
284      return svn_error_createf(
285        SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
286        _("This client is too old to work with the working copy at\n"
287          "'%s' (format %d).\n"
288          "You need to get a newer Subversion client. For more details, see\n"
289          "  http://subversion.apache.org/faq.html#working-copy-format-change\n"
290          ),
291        svn_dirent_local_style(wcroot_abspath, scratch_pool),
292        format);
293    }
294
295  /* Verify that no work items exists. If they do, then our integrity is
296     suspect and, thus, we cannot use this database.  */
297  if (format >= SVN_WC__HAS_WORK_QUEUE
298      && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format)))
299    {
300      svn_error_t *err = verify_no_work(sdb);
301      if (err)
302        {
303          /* Special message for attempts to upgrade a 1.7-dev wc with
304             outstanding workqueue items. */
305          if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED
306              && format < SVN_WC__VERSION && verify_format)
307            err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 "
308                                              "client before upgrading with "
309                                              "this client"));
310          return svn_error_trace(err);
311        }
312    }
313
314  /* Auto-upgrade the SDB if possible.  */
315  if (format < SVN_WC__VERSION && verify_format)
316    {
317      return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
318                               _("The working copy at '%s'\nis too old "
319                                 "(format %d) to work with client version "
320                                 "'%s' (expects format %d). You need to "
321                                 "upgrade the working copy first.\n"),
322                               svn_dirent_local_style(wcroot_abspath,
323                                                      scratch_pool),
324                               format, SVN_VERSION, SVN_WC__VERSION);
325    }
326
327  *wcroot = apr_palloc(result_pool, sizeof(**wcroot));
328
329  (*wcroot)->abspath = wcroot_abspath;
330  (*wcroot)->sdb = sdb;
331  (*wcroot)->wc_id = wc_id;
332  (*wcroot)->format = format;
333  /* 8 concurrent locks is probably more than a typical wc_ng based svn client
334     uses. */
335  (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
336                                          sizeof(svn_wc__db_wclock_t));
337  (*wcroot)->access_cache = apr_hash_make(result_pool);
338
339  /* SDB will be NULL for pre-NG working copies. We only need to run a
340     cleanup when the SDB is present.  */
341  if (sdb != NULL)
342    apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
343                              apr_pool_cleanup_null);
344  return SVN_NO_ERROR;
345}
346
347
348svn_error_t *
349svn_wc__db_close_many_wcroots(apr_hash_t *roots,
350                              apr_pool_t *state_pool,
351                              apr_pool_t *scratch_pool)
352{
353  apr_hash_index_t *hi;
354
355  for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi))
356    {
357      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
358      apr_status_t result;
359
360      result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot);
361      if (result != APR_SUCCESS)
362        return svn_error_wrap_apr(result, NULL);
363    }
364
365  return SVN_NO_ERROR;
366}
367
368
369/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient.  */
370static const char *
371compute_relpath(const svn_wc__db_wcroot_t *wcroot,
372                const char *local_abspath,
373                apr_pool_t *result_pool)
374{
375  const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath,
376                                            result_pool);
377  if (relpath == NULL)
378    return "";
379  return relpath;
380}
381
382
383/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
384 * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
385static svn_error_t *
386read_link_target(const char **link_target_abspath,
387                 const char *local_abspath,
388                 apr_pool_t *pool)
389{
390  svn_string_t *link_target;
391  const char *canon_link_target;
392
393  SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
394  if (link_target->len == 0)
395    return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
396                             _("The symlink at '%s' points nowhere"),
397                             svn_dirent_local_style(local_abspath, pool));
398
399  canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
400
401  /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
402  if (!svn_dirent_is_absolute(canon_link_target))
403    canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
404                                                           pool),
405                                        canon_link_target, pool);
406
407  /* Collapse any .. in the symlink part of the path. */
408  if (svn_path_is_backpath_present(canon_link_target))
409    SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
410                                    pool));
411  else
412    *link_target_abspath = canon_link_target;
413
414  return SVN_NO_ERROR;
415}
416
417/* Verify if the sqlite_stat1 table exists and if not tries to add
418   this table (but ignores errors on adding the schema) */
419static svn_error_t *
420verify_stats_table(svn_sqlite__db_t *sdb,
421                   int format,
422                   apr_pool_t *scratch_pool)
423{
424  svn_sqlite__stmt_t *stmt;
425  svn_boolean_t have_row;
426
427  if (format != SVN_WC__ENSURE_STAT1_TABLE)
428    return SVN_NO_ERROR;
429
430  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
431                                    STMT_HAVE_STAT1_TABLE));
432  SVN_ERR(svn_sqlite__step(&have_row, stmt));
433  SVN_ERR(svn_sqlite__reset(stmt));
434
435  if (!have_row)
436    {
437      svn_error_clear(
438          svn_wc__db_install_schema_statistics(sdb, scratch_pool));
439    }
440
441  return SVN_NO_ERROR;
442}
443
444/* Sqlite transaction helper for opening the db in
445   svn_wc__db_wcroot_parse_local_abspath() to avoid multiple
446   db operations that each obtain and release a lock */
447static svn_error_t *
448fetch_sdb_info(apr_int64_t *wc_id,
449               int *format,
450               svn_sqlite__db_t *sdb,
451               apr_pool_t *scratch_pool)
452{
453  *wc_id = -1;
454  *format = -1;
455
456  SVN_SQLITE__WITH_LOCK4(
457        svn_wc__db_util_fetch_wc_id(wc_id, sdb, scratch_pool),
458        svn_sqlite__read_schema_version(format, sdb, scratch_pool),
459        verify_stats_table(sdb, *format, scratch_pool),
460        SVN_NO_ERROR,
461        sdb);
462
463  return SVN_NO_ERROR;
464}
465
466
467svn_error_t *
468svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
469                                      const char **local_relpath,
470                                      svn_wc__db_t *db,
471                                      const char *local_abspath,
472                                      apr_pool_t *result_pool,
473                                      apr_pool_t *scratch_pool)
474{
475  const char *local_dir_abspath;
476  const char *original_abspath = local_abspath;
477  svn_node_kind_t kind;
478  const char *build_relpath;
479  svn_wc__db_wcroot_t *probe_wcroot;
480  svn_wc__db_wcroot_t *found_wcroot = NULL;
481  const char *scan_abspath;
482  svn_sqlite__db_t *sdb = NULL;
483  svn_boolean_t moved_upwards = FALSE;
484  svn_boolean_t always_check = FALSE;
485  int wc_format = 0;
486  const char *adm_relpath;
487  /* Non-NULL if WCROOT is found through a symlink: */
488  const char *symlink_wcroot_abspath = NULL;
489
490  /* ### we need more logic for finding the database (if it is located
491     ### outside of the wcroot) and then managing all of that within DB.
492     ### for now: play quick & dirty. */
493
494  probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
495  if (probe_wcroot != NULL)
496    {
497      *wcroot = probe_wcroot;
498
499      /* We got lucky. Just return the thing BEFORE performing any I/O.  */
500      /* ### validate SMODE against how we opened wcroot->sdb? and against
501         ### DB->mode? (will we record per-dir mode?)  */
502
503      /* ### for most callers, we could pass NULL for result_pool.  */
504      *local_relpath = compute_relpath(probe_wcroot, local_abspath,
505                                       result_pool);
506
507      return SVN_NO_ERROR;
508    }
509
510  /* ### at some point in the future, we may need to find a way to get
511     ### rid of this stat() call. it is going to happen for EVERY call
512     ### into wc_db which references a file. calls for directories could
513     ### get an early-exit in the hash lookup just above.  */
514  SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
515  if (kind != svn_node_dir)
516    {
517      /* If the node specified by the path is NOT present, then it cannot
518         possibly be a directory containing ".svn/wc.db".
519
520         If it is a file, then it cannot contain ".svn/wc.db".
521
522         For both of these cases, strip the basename off of the path and
523         move up one level. Keep record of what we strip, though, since
524         we'll need it later to construct local_relpath.  */
525      svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
526                       scratch_pool);
527
528      /* Is this directory in our hash?  */
529      probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
530      if (probe_wcroot != NULL)
531        {
532          const char *dir_relpath;
533
534          *wcroot = probe_wcroot;
535
536          /* Stashed directory's local_relpath + basename. */
537          dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
538                                        NULL);
539          *local_relpath = svn_relpath_join(dir_relpath,
540                                            build_relpath,
541                                            result_pool);
542          return SVN_NO_ERROR;
543        }
544
545      /* If the requested path is not on the disk, then we don't know how
546         many ancestors need to be scanned until we start hitting content
547         on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
548         rather than bailing out after the first check.  */
549      if (kind == svn_node_none)
550        always_check = TRUE;
551
552      /* Start the scanning at LOCAL_DIR_ABSPATH.  */
553      local_abspath = local_dir_abspath;
554    }
555  else
556    {
557      /* Start the local_relpath empty. If *this* directory contains the
558         wc.db, then relpath will be the empty string.  */
559      build_relpath = "";
560
561      /* Remember the dir containing LOCAL_ABSPATH (they're the same).  */
562      local_dir_abspath = local_abspath;
563    }
564
565  /* LOCAL_ABSPATH refers to a directory at this point. At this point,
566     we've determined that an associated WCROOT is NOT in the DB's hash
567     table for this directory. Let's find an existing one in the ancestors,
568     or create one when we find the actual wcroot.  */
569
570  /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
571     database in the right place. If we find it... great! If not, then
572     peel off some components, and try again. */
573
574  adm_relpath = svn_wc_get_adm_dir(scratch_pool);
575  while (TRUE)
576    {
577      svn_error_t *err;
578      svn_node_kind_t adm_subdir_kind;
579
580      const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath,
581                                               scratch_pool);
582
583      SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool));
584
585      if (adm_subdir_kind == svn_node_dir)
586        {
587          /* We always open the database in read/write mode.  If the database
588             isn't writable in the filesystem, SQLite will internally open
589             it as read-only, and we'll get an error if we try to do a write
590             operation.
591
592             We could decide what to do on a per-operation basis, but since
593             we're caching database handles, it make sense to be as permissive
594             as the filesystem allows. */
595          err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
596                                        svn_sqlite__mode_readwrite,
597                                        db->exclusive, NULL,
598                                        db->state_pool, scratch_pool);
599          if (err == NULL)
600            {
601#ifdef SVN_DEBUG
602              /* Install self-verification trigger statements. */
603              err = svn_sqlite__exec_statements(sdb,
604                                                STMT_VERIFICATION_TRIGGERS);
605              if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
606                {
607                  /* Verification triggers can fail to install on old 1.7-dev
608                   * formats which didn't have a NODES table yet. Ignore sqlite
609                   * errors so such working copies can be upgraded. */
610                  svn_error_clear(err);
611                }
612              else
613                SVN_ERR(err);
614#endif
615              break;
616            }
617          if (err->apr_err != SVN_ERR_SQLITE_ERROR
618              && !APR_STATUS_IS_ENOENT(err->apr_err))
619            return svn_error_trace(err);
620          svn_error_clear(err);
621
622          /* If we have not moved upwards, then check for a wc-1 working copy.
623             Since wc-1 has a .svn in every directory, and we didn't find one
624             in the original directory, then we aren't looking at a wc-1.
625
626             If the original path is not present, then we have to check on every
627             iteration. The content may be the immediate parent, or possibly
628             five ancetors higher. We don't test for directory presence (just
629             for the presence of subdirs/files), so we don't know when we can
630             stop checking ... so just check always.  */
631          if (!moved_upwards || always_check)
632            {
633              SVN_ERR(get_old_version(&wc_format, local_abspath,
634                                      scratch_pool));
635              if (wc_format != 0)
636                break;
637            }
638        }
639
640      /* We couldn't open the SDB within the specified directory, so
641         move up one more directory. */
642      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
643        {
644          /* Hit the root without finding a wcroot. */
645
646          /* The wcroot could be a symlink to a directory.
647           * (Issue #2557, #3987). If so, try again, this time scanning
648           * for a db within the directory the symlink points to,
649           * rather than within the symlink's parent directory. */
650          if (kind == svn_node_symlink)
651            {
652              svn_node_kind_t resolved_kind;
653
654              local_abspath = original_abspath;
655
656              SVN_ERR(svn_io_check_resolved_path(local_abspath,
657                                                 &resolved_kind,
658                                                 scratch_pool));
659              if (resolved_kind == svn_node_dir)
660                {
661                  /* Is this directory recorded in our hash?  */
662                  found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
663                  if (found_wcroot)
664                    break;
665
666                  symlink_wcroot_abspath = local_abspath;
667                  SVN_ERR(read_link_target(&local_abspath, local_abspath,
668                                           scratch_pool));
669try_symlink_as_dir:
670                  kind = svn_node_dir;
671                  moved_upwards = FALSE;
672                  local_dir_abspath = local_abspath;
673                  build_relpath = "";
674
675                  continue;
676                }
677            }
678
679          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
680                                   _("'%s' is not a working copy"),
681                                   svn_dirent_local_style(original_abspath,
682                                                          scratch_pool));
683        }
684
685      local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
686
687      moved_upwards = TRUE;
688      symlink_wcroot_abspath = NULL;
689
690      /* Is the parent directory recorded in our hash?  */
691      found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
692      if (found_wcroot != NULL)
693        break;
694    }
695
696  if (found_wcroot != NULL)
697    {
698      /* We found a hash table entry for an ancestor, so we stopped scanning
699         since all subdirectories use the same WCROOT.  */
700      *wcroot = found_wcroot;
701    }
702  else if (wc_format == 0)
703    {
704      /* We finally found the database. Construct a wcroot_t for it.  */
705
706      apr_int64_t wc_id;
707      int format;
708      svn_error_t *err;
709
710      err = fetch_sdb_info(&wc_id, &format, sdb, scratch_pool);
711      if (err)
712        {
713          if (err->apr_err == SVN_ERR_WC_CORRUPT)
714            return svn_error_quick_wrap(
715              err, apr_psprintf(scratch_pool,
716                                _("Missing a row in WCROOT for '%s'."),
717                                svn_dirent_local_style(original_abspath,
718                                                       scratch_pool)));
719          return svn_error_trace(err);
720        }
721
722      /* WCROOT.local_abspath may be NULL when the database is stored
723         inside the wcroot, but we know the abspath is this directory
724         (ie. where we found it).  */
725
726      err = svn_wc__db_pdh_create_wcroot(wcroot,
727                            apr_pstrdup(db->state_pool,
728                                        symlink_wcroot_abspath
729                                          ? symlink_wcroot_abspath
730                                          : local_abspath),
731                            sdb, wc_id, format,
732                            db->verify_format, db->enforce_empty_wq,
733                            db->state_pool, scratch_pool);
734      if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
735                  err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
736          kind == svn_node_symlink)
737        {
738          /* We found an unsupported WC after traversing upwards from a
739           * symlink. Fall through to code below to check if the symlink
740           * points at a supported WC. */
741          svn_error_clear(err);
742          *wcroot = NULL;
743        }
744      else if (err)
745        {
746          /* Close handle if we are not going to use it to support
747             upgrading with exclusive wc locking. */
748          return svn_error_compose_create(err, svn_sqlite__close(sdb));
749        }
750    }
751  else
752    {
753      /* We found something that looks like a wc-1 working copy directory.
754         However, if the format version is 12 and the .svn/entries file
755         is only 3 bytes long, then it's a breadcrumb in a wc-ng working
756         copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
757      if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
758        {
759          apr_finfo_t info;
760
761          /* Check attributes of .svn/entries */
762          const char *admin_abspath = svn_wc__adm_child(
763              local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
764          svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
765                                         scratch_pool);
766
767          /* If the former does not succeed, something is seriously wrong. */
768          if (err)
769            return svn_error_createf(
770                SVN_ERR_WC_CORRUPT, err,
771                _("The working copy at '%s' is corrupt."),
772                svn_dirent_local_style(local_abspath, scratch_pool));
773          svn_error_clear(err);
774
775          if (3 == info.size)
776            {
777              /* Check existence of .svn/wc.db */
778              admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
779                                                scratch_pool);
780              err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
781                                scratch_pool);
782              if (err && APR_STATUS_IS_ENOENT(err->apr_err))
783                {
784                  svn_error_clear(err);
785                  return svn_error_createf(
786                      SVN_ERR_WC_CORRUPT, NULL,
787                      _("The working copy database at '%s' is missing."),
788                      svn_dirent_local_style(local_abspath, scratch_pool));
789                }
790              else
791                /* We should never have reached this point in the code
792                   if .svn/wc.db exists; therefore it's best to assume
793                   it's corrupt. */
794                return svn_error_createf(
795                    SVN_ERR_WC_CORRUPT, err,
796                    _("The working copy database at '%s' is corrupt."),
797                    svn_dirent_local_style(local_abspath, scratch_pool));
798            }
799        }
800
801      SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
802                            apr_pstrdup(db->state_pool,
803                                        symlink_wcroot_abspath
804                                          ? symlink_wcroot_abspath
805                                          : local_abspath),
806                            NULL, UNKNOWN_WC_ID, wc_format,
807                            db->verify_format, db->enforce_empty_wq,
808                            db->state_pool, scratch_pool));
809    }
810
811  if (*wcroot)
812    {
813      const char *dir_relpath;
814
815      if (symlink_wcroot_abspath)
816        {
817          /* The WCROOT was found through a symlink pointing at the root of
818           * the WC. Cache the WCROOT under the symlink's path. */
819          local_dir_abspath = symlink_wcroot_abspath;
820        }
821
822      /* The subdirectory's relpath is easily computed relative to the
823         wcroot that we just found.  */
824      dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
825
826      /* And the result local_relpath may include a filename.  */
827      *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
828    }
829
830  if (kind == svn_node_symlink)
831    {
832      svn_boolean_t retry_if_dir = FALSE;
833      svn_wc__db_status_t status;
834      svn_boolean_t conflicted;
835      svn_error_t *err;
836
837      /* Check if the symlink is versioned or obstructs a versioned node
838       * in this DB -- in that case, use this wcroot. Else, if the symlink
839       * points to a directory, try to find a wcroot in that directory
840       * instead. */
841
842      if (*wcroot)
843        {
844          err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
845                                              NULL, NULL, NULL, NULL, NULL,
846                                              NULL, NULL, NULL, NULL, NULL,
847                                              NULL, NULL, NULL, &conflicted,
848                                              NULL, NULL, NULL, NULL, NULL,
849                                              NULL, *wcroot, *local_relpath,
850                                              scratch_pool, scratch_pool);
851          if (err)
852            {
853              if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
854                  && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
855                return svn_error_trace(err);
856
857              svn_error_clear(err);
858              retry_if_dir = TRUE; /* The symlink is unversioned. */
859            }
860          else
861            {
862              /* The symlink is versioned, or obstructs a versioned node.
863               * Ignore non-conflicted not-present/excluded nodes.
864               * This allows the symlink to redirect the wcroot query to a
865               * directory, regardless of 'invisible' nodes in this WC. */
866              retry_if_dir = ((status == svn_wc__db_status_not_present ||
867                               status == svn_wc__db_status_excluded ||
868                               status == svn_wc__db_status_server_excluded)
869                              && !conflicted);
870            }
871        }
872      else
873        retry_if_dir = TRUE;
874
875      if (retry_if_dir)
876        {
877          svn_node_kind_t resolved_kind;
878
879          SVN_ERR(svn_io_check_resolved_path(original_abspath,
880                                             &resolved_kind,
881                                             scratch_pool));
882          if (resolved_kind == svn_node_dir)
883            {
884              symlink_wcroot_abspath = original_abspath;
885              SVN_ERR(read_link_target(&local_abspath, original_abspath,
886                                       scratch_pool));
887              /* This handle was opened in this function but is not going
888                 to be used further so close it. */
889              if (sdb)
890                SVN_ERR(svn_sqlite__close(sdb));
891              goto try_symlink_as_dir;
892            }
893        }
894    }
895
896  /* We've found the appropriate WCROOT for the requested path. Stash
897     it into that path's directory.  */
898  svn_hash_sets(db->dir_data,
899                apr_pstrdup(db->state_pool, local_dir_abspath),
900                *wcroot);
901
902  /* Did we traverse up to parent directories?  */
903  if (!moved_upwards)
904    {
905      /* We did NOT move to a parent of the original requested directory.
906         We've constructed and filled in a WCROOT for the request, so we
907         are done.  */
908      return SVN_NO_ERROR;
909    }
910
911  /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
912     passed into this function. We stepped *at least* one directory above that.
913     We should now associate the WROOT for each parent directory that does
914     not (yet) have one.  */
915
916  scan_abspath = local_dir_abspath;
917
918  do
919    {
920      const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
921      svn_wc__db_wcroot_t *parent_wcroot;
922
923      parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
924      if (parent_wcroot == NULL)
925        {
926          svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
927                        *wcroot);
928        }
929
930      /* Move up a directory, stopping when we reach the directory where
931         we found/built the WCROOT.  */
932      scan_abspath = parent_dir;
933    }
934  while (strcmp(scan_abspath, local_abspath) != 0);
935
936  return SVN_NO_ERROR;
937}
938
939
940svn_error_t *
941svn_wc__db_drop_root(svn_wc__db_t *db,
942                     const char *local_abspath,
943                     apr_pool_t *scratch_pool)
944{
945  svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath);
946  apr_hash_index_t *hi;
947  apr_status_t result;
948
949  if (!root_wcroot)
950    return SVN_NO_ERROR;
951
952  if (strcmp(root_wcroot->abspath, local_abspath) != 0)
953    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
954                             _("'%s' is not a working copy root"),
955                             svn_dirent_local_style(local_abspath,
956                                                    scratch_pool));
957
958  for (hi = apr_hash_first(scratch_pool, db->dir_data);
959       hi;
960       hi = apr_hash_next(hi))
961    {
962      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
963
964      if (wcroot == root_wcroot)
965        svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL);
966    }
967
968  result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
969  if (result != APR_SUCCESS)
970    return svn_error_wrap_apr(result, NULL);
971
972  return SVN_NO_ERROR;
973}
974