1251881Speter/*
2251881Speter * wc_db_wcroot.c :  supporting datastructures for the administrative database
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#define SVN_WC__I_AM_WC_DB
25251881Speter
26251881Speter#include <assert.h>
27251881Speter
28251881Speter#include "svn_dirent_uri.h"
29251881Speter#include "svn_hash.h"
30251881Speter#include "svn_path.h"
31251881Speter#include "svn_version.h"
32251881Speter
33251881Speter#include "wc.h"
34251881Speter#include "adm_files.h"
35251881Speter#include "wc_db_private.h"
36251881Speter#include "wc-queries.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter
40251881Speter/* ### Same values as wc_db.c */
41251881Speter#define SDB_FILE  "wc.db"
42251881Speter#define UNKNOWN_WC_ID ((apr_int64_t) -1)
43251881Speter#define FORMAT_FROM_SDB (-1)
44251881Speter
45251881Speter
46251881Speter
47251881Speter/* Get the format version from a wc-1 directory. If it is not a working copy
48251881Speter   directory, then it sets VERSION to zero and returns no error.  */
49251881Speterstatic svn_error_t *
50251881Speterget_old_version(int *version,
51251881Speter                const char *abspath,
52251881Speter                apr_pool_t *scratch_pool)
53251881Speter{
54251881Speter  svn_error_t *err;
55251881Speter  const char *format_file_path;
56251881Speter  svn_node_kind_t kind;
57251881Speter
58251881Speter  /* Try reading the format number from the entries file.  */
59251881Speter  format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES,
60251881Speter                                       scratch_pool);
61251881Speter
62251881Speter  /* Since trying to open a non-existent file is quite expensive, try a
63251881Speter     quick stat call first. In wc-ng w/cs, this will be an early exit. */
64251881Speter  SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool));
65251881Speter  if (kind == svn_node_none)
66251881Speter    {
67251881Speter      *version = 0;
68251881Speter      return SVN_NO_ERROR;
69251881Speter    }
70251881Speter
71251881Speter  err = svn_io_read_version_file(version, format_file_path, scratch_pool);
72251881Speter  if (err == NULL)
73251881Speter    return SVN_NO_ERROR;
74251881Speter  if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT
75251881Speter      && !APR_STATUS_IS_ENOENT(err->apr_err)
76251881Speter      && !APR_STATUS_IS_ENOTDIR(err->apr_err))
77251881Speter    return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
78251881Speter                             svn_dirent_local_style(abspath, scratch_pool));
79251881Speter  svn_error_clear(err);
80251881Speter
81251881Speter  /* This must be a really old working copy!  Fall back to reading the
82251881Speter     format file.
83251881Speter
84251881Speter     Note that the format file might not exist in newer working copies
85251881Speter     (format 7 and higher), but in that case, the entries file should
86251881Speter     have contained the format number. */
87251881Speter  format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT,
88251881Speter                                       scratch_pool);
89251881Speter  err = svn_io_read_version_file(version, format_file_path, scratch_pool);
90251881Speter  if (err == NULL)
91251881Speter    return SVN_NO_ERROR;
92251881Speter
93251881Speter  /* Whatever error may have occurred... we can just ignore. This is not
94251881Speter     a working copy directory. Signal the caller.  */
95251881Speter  svn_error_clear(err);
96251881Speter
97251881Speter  *version = 0;
98251881Speter  return SVN_NO_ERROR;
99251881Speter}
100251881Speter
101251881Speter
102251881Speter/* A helper function to parse_local_abspath() which returns the on-disk KIND
103251881Speter   of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed.
104251881Speter
105251881Speter   This function may do strange things, but at long as it comes up with the
106251881Speter   Right Answer, we should be happy. */
107251881Speterstatic svn_error_t *
108251881Speterget_path_kind(svn_node_kind_t *kind,
109251881Speter              svn_wc__db_t *db,
110251881Speter              const char *local_abspath,
111251881Speter              apr_pool_t *scratch_pool)
112251881Speter{
113251881Speter  svn_boolean_t special;
114251881Speter  svn_node_kind_t node_kind;
115251881Speter
116251881Speter  /* This implements a *really* simple LRU cache, where "simple" is defined
117251881Speter     as "only one element".  In other words, we remember the most recently
118251881Speter     queried path, and nothing else.  This gives >80% cache hits. */
119251881Speter
120251881Speter  if (db->parse_cache.abspath
121251881Speter        && strcmp(db->parse_cache.abspath->data, local_abspath) == 0)
122251881Speter    {
123251881Speter      /* Cache hit! */
124251881Speter      *kind = db->parse_cache.kind;
125251881Speter      return SVN_NO_ERROR;
126251881Speter    }
127251881Speter
128251881Speter  if (!db->parse_cache.abspath)
129251881Speter    {
130251881Speter      db->parse_cache.abspath = svn_stringbuf_create(local_abspath,
131251881Speter                                                     db->state_pool);
132251881Speter    }
133251881Speter  else
134251881Speter    {
135251881Speter      svn_stringbuf_set(db->parse_cache.abspath, local_abspath);
136251881Speter    }
137251881Speter
138251881Speter  SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind,
139251881Speter                                    &special, scratch_pool));
140251881Speter
141251881Speter  db->parse_cache.kind = (special ? svn_node_symlink : node_kind);
142251881Speter  *kind = db->parse_cache.kind;
143251881Speter
144251881Speter  return SVN_NO_ERROR;
145251881Speter}
146251881Speter
147251881Speter
148251881Speter/* Return an error if the work queue in SDB is non-empty. */
149251881Speterstatic svn_error_t *
150251881Speterverify_no_work(svn_sqlite__db_t *sdb)
151251881Speter{
152251881Speter  svn_sqlite__stmt_t *stmt;
153251881Speter  svn_boolean_t have_row;
154251881Speter
155251881Speter  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK));
156251881Speter  SVN_ERR(svn_sqlite__step(&have_row, stmt));
157251881Speter  SVN_ERR(svn_sqlite__reset(stmt));
158251881Speter
159251881Speter  if (have_row)
160251881Speter    return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL,
161251881Speter                            NULL /* nothing to add.  */);
162251881Speter
163251881Speter  return SVN_NO_ERROR;
164251881Speter}
165251881Speter
166251881Speter
167251881Speter/* */
168251881Speterstatic apr_status_t
169251881Speterclose_wcroot(void *data)
170251881Speter{
171251881Speter  svn_wc__db_wcroot_t *wcroot = data;
172251881Speter  svn_error_t *err;
173251881Speter
174251881Speter  SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL);
175251881Speter
176251881Speter  err = svn_sqlite__close(wcroot->sdb);
177251881Speter  wcroot->sdb = NULL;
178251881Speter  if (err)
179251881Speter    {
180251881Speter      apr_status_t result = err->apr_err;
181251881Speter      svn_error_clear(err);
182251881Speter      return result;
183251881Speter    }
184251881Speter
185251881Speter  return APR_SUCCESS;
186251881Speter}
187251881Speter
188251881Speter
189251881Spetersvn_error_t *
190251881Spetersvn_wc__db_open(svn_wc__db_t **db,
191251881Speter                svn_config_t *config,
192251881Speter                svn_boolean_t open_without_upgrade,
193251881Speter                svn_boolean_t enforce_empty_wq,
194251881Speter                apr_pool_t *result_pool,
195251881Speter                apr_pool_t *scratch_pool)
196251881Speter{
197251881Speter  *db = apr_pcalloc(result_pool, sizeof(**db));
198251881Speter  (*db)->config = config;
199251881Speter  (*db)->verify_format = !open_without_upgrade;
200251881Speter  (*db)->enforce_empty_wq = enforce_empty_wq;
201251881Speter  (*db)->dir_data = apr_hash_make(result_pool);
202251881Speter
203251881Speter  (*db)->state_pool = result_pool;
204251881Speter
205251881Speter  /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
206251881Speter  if (config)
207251881Speter    {
208251881Speter      svn_error_t *err;
209251881Speter      svn_boolean_t sqlite_exclusive = FALSE;
210251881Speter
211251881Speter      err = svn_config_get_bool(config, &sqlite_exclusive,
212251881Speter                                SVN_CONFIG_SECTION_WORKING_COPY,
213251881Speter                                SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
214251881Speter                                FALSE);
215251881Speter      if (err)
216251881Speter        {
217251881Speter          svn_error_clear(err);
218251881Speter        }
219251881Speter      else
220251881Speter        (*db)->exclusive = sqlite_exclusive;
221251881Speter    }
222251881Speter
223251881Speter  return SVN_NO_ERROR;
224251881Speter}
225251881Speter
226251881Speter
227251881Spetersvn_error_t *
228251881Spetersvn_wc__db_close(svn_wc__db_t *db)
229251881Speter{
230251881Speter  apr_pool_t *scratch_pool = db->state_pool;
231251881Speter  apr_hash_t *roots = apr_hash_make(scratch_pool);
232251881Speter  apr_hash_index_t *hi;
233251881Speter
234251881Speter  /* Collect all the unique WCROOT structures, and empty out DIR_DATA.  */
235251881Speter  for (hi = apr_hash_first(scratch_pool, db->dir_data);
236251881Speter       hi;
237251881Speter       hi = apr_hash_next(hi))
238251881Speter    {
239251881Speter      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
240251881Speter      const char *local_abspath = svn__apr_hash_index_key(hi);
241251881Speter
242251881Speter      if (wcroot->sdb)
243251881Speter        svn_hash_sets(roots, wcroot->abspath, wcroot);
244251881Speter
245251881Speter      svn_hash_sets(db->dir_data, local_abspath, NULL);
246251881Speter    }
247251881Speter
248251881Speter  /* Run the cleanup for each WCROOT.  */
249251881Speter  return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
250251881Speter                                                       scratch_pool));
251251881Speter}
252251881Speter
253251881Speter
254251881Spetersvn_error_t *
255251881Spetersvn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
256251881Speter                             const char *wcroot_abspath,
257251881Speter                             svn_sqlite__db_t *sdb,
258251881Speter                             apr_int64_t wc_id,
259251881Speter                             int format,
260251881Speter                             svn_boolean_t verify_format,
261251881Speter                             svn_boolean_t enforce_empty_wq,
262251881Speter                             apr_pool_t *result_pool,
263251881Speter                             apr_pool_t *scratch_pool)
264251881Speter{
265251881Speter  if (sdb != NULL)
266251881Speter    SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
267251881Speter
268251881Speter  /* If we construct a wcroot, then we better have a format.  */
269251881Speter  SVN_ERR_ASSERT(format >= 1);
270251881Speter
271251881Speter  /* If this working copy is PRE-1.0, then simply bail out.  */
272251881Speter  if (format < 4)
273251881Speter    {
274251881Speter      return svn_error_createf(
275251881Speter        SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
276251881Speter        _("Working copy format of '%s' is too old (%d); "
277251881Speter          "please check out your working copy again"),
278251881Speter        svn_dirent_local_style(wcroot_abspath, scratch_pool), format);
279251881Speter    }
280251881Speter
281251881Speter  /* If this working copy is from a future version, then bail out.  */
282251881Speter  if (format > SVN_WC__VERSION)
283251881Speter    {
284251881Speter      return svn_error_createf(
285251881Speter        SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
286251881Speter        _("This client is too old to work with the working copy at\n"
287251881Speter          "'%s' (format %d).\n"
288251881Speter          "You need to get a newer Subversion client. For more details, see\n"
289251881Speter          "  http://subversion.apache.org/faq.html#working-copy-format-change\n"
290251881Speter          ),
291251881Speter        svn_dirent_local_style(wcroot_abspath, scratch_pool),
292251881Speter        format);
293251881Speter    }
294251881Speter
295251881Speter  /* Verify that no work items exists. If they do, then our integrity is
296251881Speter     suspect and, thus, we cannot use this database.  */
297251881Speter  if (format >= SVN_WC__HAS_WORK_QUEUE
298251881Speter      && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format)))
299251881Speter    {
300251881Speter      svn_error_t *err = verify_no_work(sdb);
301251881Speter      if (err)
302251881Speter        {
303251881Speter          /* Special message for attempts to upgrade a 1.7-dev wc with
304251881Speter             outstanding workqueue items. */
305251881Speter          if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED
306251881Speter              && format < SVN_WC__VERSION && verify_format)
307251881Speter            err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 "
308251881Speter                                              "client before upgrading with "
309251881Speter                                              "this client"));
310251881Speter          return svn_error_trace(err);
311251881Speter        }
312251881Speter    }
313251881Speter
314251881Speter  /* Auto-upgrade the SDB if possible.  */
315251881Speter  if (format < SVN_WC__VERSION && verify_format)
316251881Speter    {
317251881Speter      return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
318251881Speter                               _("The working copy at '%s'\nis too old "
319251881Speter                                 "(format %d) to work with client version "
320251881Speter                                 "'%s' (expects format %d). You need to "
321251881Speter                                 "upgrade the working copy first.\n"),
322251881Speter                               svn_dirent_local_style(wcroot_abspath,
323251881Speter                                                      scratch_pool),
324251881Speter                               format, SVN_VERSION, SVN_WC__VERSION);
325251881Speter    }
326251881Speter
327251881Speter  *wcroot = apr_palloc(result_pool, sizeof(**wcroot));
328251881Speter
329251881Speter  (*wcroot)->abspath = wcroot_abspath;
330251881Speter  (*wcroot)->sdb = sdb;
331251881Speter  (*wcroot)->wc_id = wc_id;
332251881Speter  (*wcroot)->format = format;
333251881Speter  /* 8 concurrent locks is probably more than a typical wc_ng based svn client
334251881Speter     uses. */
335251881Speter  (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
336251881Speter                                          sizeof(svn_wc__db_wclock_t));
337251881Speter  (*wcroot)->access_cache = apr_hash_make(result_pool);
338251881Speter
339251881Speter  /* SDB will be NULL for pre-NG working copies. We only need to run a
340251881Speter     cleanup when the SDB is present.  */
341251881Speter  if (sdb != NULL)
342251881Speter    apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
343251881Speter                              apr_pool_cleanup_null);
344251881Speter  return SVN_NO_ERROR;
345251881Speter}
346251881Speter
347251881Speter
348251881Spetersvn_error_t *
349251881Spetersvn_wc__db_close_many_wcroots(apr_hash_t *roots,
350251881Speter                              apr_pool_t *state_pool,
351251881Speter                              apr_pool_t *scratch_pool)
352251881Speter{
353251881Speter  apr_hash_index_t *hi;
354251881Speter
355251881Speter  for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi))
356251881Speter    {
357251881Speter      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
358251881Speter      apr_status_t result;
359251881Speter
360251881Speter      result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot);
361251881Speter      if (result != APR_SUCCESS)
362251881Speter        return svn_error_wrap_apr(result, NULL);
363251881Speter    }
364251881Speter
365251881Speter  return SVN_NO_ERROR;
366251881Speter}
367251881Speter
368251881Speter
369251881Speter/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient.  */
370251881Speterstatic const char *
371251881Spetercompute_relpath(const svn_wc__db_wcroot_t *wcroot,
372251881Speter                const char *local_abspath,
373251881Speter                apr_pool_t *result_pool)
374251881Speter{
375251881Speter  const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath,
376251881Speter                                            result_pool);
377251881Speter  if (relpath == NULL)
378251881Speter    return "";
379251881Speter  return relpath;
380251881Speter}
381251881Speter
382251881Speter
383251881Speter/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
384251881Speter * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
385251881Speterstatic svn_error_t *
386251881Speterread_link_target(const char **link_target_abspath,
387251881Speter                 const char *local_abspath,
388251881Speter                 apr_pool_t *pool)
389251881Speter{
390251881Speter  svn_string_t *link_target;
391251881Speter  const char *canon_link_target;
392251881Speter
393251881Speter  SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
394251881Speter  if (link_target->len == 0)
395251881Speter    return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
396251881Speter                             _("The symlink at '%s' points nowhere"),
397251881Speter                             svn_dirent_local_style(local_abspath, pool));
398251881Speter
399251881Speter  canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
400251881Speter
401251881Speter  /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
402251881Speter  if (!svn_dirent_is_absolute(canon_link_target))
403251881Speter    canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
404251881Speter                                                           pool),
405251881Speter                                        canon_link_target, pool);
406251881Speter
407251881Speter  /* Collapse any .. in the symlink part of the path. */
408251881Speter  if (svn_path_is_backpath_present(canon_link_target))
409251881Speter    SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
410251881Speter                                    pool));
411251881Speter  else
412251881Speter    *link_target_abspath = canon_link_target;
413251881Speter
414251881Speter  return SVN_NO_ERROR;
415251881Speter}
416251881Speter
417251881Spetersvn_error_t *
418251881Spetersvn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
419251881Speter                                      const char **local_relpath,
420251881Speter                                      svn_wc__db_t *db,
421251881Speter                                      const char *local_abspath,
422251881Speter                                      apr_pool_t *result_pool,
423251881Speter                                      apr_pool_t *scratch_pool)
424251881Speter{
425251881Speter  const char *local_dir_abspath;
426251881Speter  const char *original_abspath = local_abspath;
427251881Speter  svn_node_kind_t kind;
428251881Speter  const char *build_relpath;
429251881Speter  svn_wc__db_wcroot_t *probe_wcroot;
430251881Speter  svn_wc__db_wcroot_t *found_wcroot = NULL;
431251881Speter  const char *scan_abspath;
432251881Speter  svn_sqlite__db_t *sdb = NULL;
433251881Speter  svn_boolean_t moved_upwards = FALSE;
434251881Speter  svn_boolean_t always_check = FALSE;
435251881Speter  int wc_format = 0;
436251881Speter  const char *adm_relpath;
437253734Speter  /* Non-NULL if WCROOT is found through a symlink: */
438253734Speter  const char *symlink_wcroot_abspath = NULL;
439251881Speter
440251881Speter  /* ### we need more logic for finding the database (if it is located
441251881Speter     ### outside of the wcroot) and then managing all of that within DB.
442251881Speter     ### for now: play quick & dirty. */
443251881Speter
444251881Speter  probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
445251881Speter  if (probe_wcroot != NULL)
446251881Speter    {
447251881Speter      *wcroot = probe_wcroot;
448251881Speter
449251881Speter      /* We got lucky. Just return the thing BEFORE performing any I/O.  */
450251881Speter      /* ### validate SMODE against how we opened wcroot->sdb? and against
451251881Speter         ### DB->mode? (will we record per-dir mode?)  */
452251881Speter
453251881Speter      /* ### for most callers, we could pass NULL for result_pool.  */
454251881Speter      *local_relpath = compute_relpath(probe_wcroot, local_abspath,
455251881Speter                                       result_pool);
456251881Speter
457251881Speter      return SVN_NO_ERROR;
458251881Speter    }
459251881Speter
460251881Speter  /* ### at some point in the future, we may need to find a way to get
461251881Speter     ### rid of this stat() call. it is going to happen for EVERY call
462251881Speter     ### into wc_db which references a file. calls for directories could
463251881Speter     ### get an early-exit in the hash lookup just above.  */
464251881Speter  SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
465251881Speter  if (kind != svn_node_dir)
466251881Speter    {
467251881Speter      /* If the node specified by the path is NOT present, then it cannot
468251881Speter         possibly be a directory containing ".svn/wc.db".
469251881Speter
470251881Speter         If it is a file, then it cannot contain ".svn/wc.db".
471251881Speter
472251881Speter         For both of these cases, strip the basename off of the path and
473251881Speter         move up one level. Keep record of what we strip, though, since
474251881Speter         we'll need it later to construct local_relpath.  */
475251881Speter      svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
476251881Speter                       scratch_pool);
477251881Speter
478251881Speter      /* Is this directory in our hash?  */
479251881Speter      probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
480251881Speter      if (probe_wcroot != NULL)
481251881Speter        {
482251881Speter          const char *dir_relpath;
483251881Speter
484251881Speter          *wcroot = probe_wcroot;
485251881Speter
486251881Speter          /* Stashed directory's local_relpath + basename. */
487251881Speter          dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
488251881Speter                                        NULL);
489251881Speter          *local_relpath = svn_relpath_join(dir_relpath,
490251881Speter                                            build_relpath,
491251881Speter                                            result_pool);
492251881Speter          return SVN_NO_ERROR;
493251881Speter        }
494251881Speter
495251881Speter      /* If the requested path is not on the disk, then we don't know how
496251881Speter         many ancestors need to be scanned until we start hitting content
497251881Speter         on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
498251881Speter         rather than bailing out after the first check.  */
499251881Speter      if (kind == svn_node_none)
500251881Speter        always_check = TRUE;
501251881Speter
502251881Speter      /* Start the scanning at LOCAL_DIR_ABSPATH.  */
503251881Speter      local_abspath = local_dir_abspath;
504251881Speter    }
505251881Speter  else
506251881Speter    {
507251881Speter      /* Start the local_relpath empty. If *this* directory contains the
508251881Speter         wc.db, then relpath will be the empty string.  */
509251881Speter      build_relpath = "";
510251881Speter
511251881Speter      /* Remember the dir containing LOCAL_ABSPATH (they're the same).  */
512251881Speter      local_dir_abspath = local_abspath;
513251881Speter    }
514251881Speter
515251881Speter  /* LOCAL_ABSPATH refers to a directory at this point. At this point,
516251881Speter     we've determined that an associated WCROOT is NOT in the DB's hash
517251881Speter     table for this directory. Let's find an existing one in the ancestors,
518251881Speter     or create one when we find the actual wcroot.  */
519251881Speter
520251881Speter  /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
521251881Speter     database in the right place. If we find it... great! If not, then
522251881Speter     peel off some components, and try again. */
523251881Speter
524251881Speter  adm_relpath = svn_wc_get_adm_dir(scratch_pool);
525251881Speter  while (TRUE)
526251881Speter    {
527251881Speter      svn_error_t *err;
528251881Speter      svn_node_kind_t adm_subdir_kind;
529251881Speter
530251881Speter      const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath,
531251881Speter                                               scratch_pool);
532251881Speter
533251881Speter      SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool));
534251881Speter
535251881Speter      if (adm_subdir_kind == svn_node_dir)
536251881Speter        {
537251881Speter          /* We always open the database in read/write mode.  If the database
538251881Speter             isn't writable in the filesystem, SQLite will internally open
539251881Speter             it as read-only, and we'll get an error if we try to do a write
540251881Speter             operation.
541251881Speter
542251881Speter             We could decide what to do on a per-operation basis, but since
543251881Speter             we're caching database handles, it make sense to be as permissive
544251881Speter             as the filesystem allows. */
545251881Speter          err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
546251881Speter                                        svn_sqlite__mode_readwrite,
547251881Speter                                        db->exclusive, NULL,
548251881Speter                                        db->state_pool, scratch_pool);
549251881Speter          if (err == NULL)
550251881Speter            {
551251881Speter#ifdef SVN_DEBUG
552251881Speter              /* Install self-verification trigger statements. */
553251881Speter              err = svn_sqlite__exec_statements(sdb,
554251881Speter                                                STMT_VERIFICATION_TRIGGERS);
555251881Speter              if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
556251881Speter                {
557251881Speter                  /* Verification triggers can fail to install on old 1.7-dev
558251881Speter                   * formats which didn't have a NODES table yet. Ignore sqlite
559251881Speter                   * errors so such working copies can be upgraded. */
560251881Speter                  svn_error_clear(err);
561251881Speter                }
562251881Speter              else
563251881Speter                SVN_ERR(err);
564251881Speter#endif
565251881Speter              break;
566251881Speter            }
567251881Speter          if (err->apr_err != SVN_ERR_SQLITE_ERROR
568251881Speter              && !APR_STATUS_IS_ENOENT(err->apr_err))
569251881Speter            return svn_error_trace(err);
570251881Speter          svn_error_clear(err);
571251881Speter
572251881Speter          /* If we have not moved upwards, then check for a wc-1 working copy.
573251881Speter             Since wc-1 has a .svn in every directory, and we didn't find one
574251881Speter             in the original directory, then we aren't looking at a wc-1.
575251881Speter
576251881Speter             If the original path is not present, then we have to check on every
577251881Speter             iteration. The content may be the immediate parent, or possibly
578251881Speter             five ancetors higher. We don't test for directory presence (just
579251881Speter             for the presence of subdirs/files), so we don't know when we can
580251881Speter             stop checking ... so just check always.  */
581251881Speter          if (!moved_upwards || always_check)
582251881Speter            {
583251881Speter              SVN_ERR(get_old_version(&wc_format, local_abspath,
584251881Speter                                      scratch_pool));
585251881Speter              if (wc_format != 0)
586251881Speter                break;
587251881Speter            }
588251881Speter        }
589251881Speter
590251881Speter      /* We couldn't open the SDB within the specified directory, so
591251881Speter         move up one more directory. */
592251881Speter      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
593251881Speter        {
594251881Speter          /* Hit the root without finding a wcroot. */
595251881Speter
596251881Speter          /* The wcroot could be a symlink to a directory.
597251881Speter           * (Issue #2557, #3987). If so, try again, this time scanning
598251881Speter           * for a db within the directory the symlink points to,
599251881Speter           * rather than within the symlink's parent directory. */
600251881Speter          if (kind == svn_node_symlink)
601251881Speter            {
602251881Speter              svn_node_kind_t resolved_kind;
603251881Speter
604251881Speter              local_abspath = original_abspath;
605251881Speter
606251881Speter              SVN_ERR(svn_io_check_resolved_path(local_abspath,
607251881Speter                                                 &resolved_kind,
608251881Speter                                                 scratch_pool));
609251881Speter              if (resolved_kind == svn_node_dir)
610251881Speter                {
611251881Speter                  /* Is this directory recorded in our hash?  */
612251881Speter                  found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
613251881Speter                  if (found_wcroot)
614251881Speter                    break;
615251881Speter
616253734Speter                  symlink_wcroot_abspath = local_abspath;
617251881Speter                  SVN_ERR(read_link_target(&local_abspath, local_abspath,
618251881Speter                                           scratch_pool));
619251881Spetertry_symlink_as_dir:
620251881Speter                  kind = svn_node_dir;
621251881Speter                  moved_upwards = FALSE;
622251881Speter                  local_dir_abspath = local_abspath;
623251881Speter                  build_relpath = "";
624251881Speter
625251881Speter                  continue;
626251881Speter                }
627251881Speter            }
628251881Speter
629251881Speter          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
630251881Speter                                   _("'%s' is not a working copy"),
631251881Speter                                   svn_dirent_local_style(original_abspath,
632251881Speter                                                          scratch_pool));
633251881Speter        }
634251881Speter
635251881Speter      local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
636251881Speter
637251881Speter      moved_upwards = TRUE;
638253734Speter      symlink_wcroot_abspath = NULL;
639251881Speter
640251881Speter      /* Is the parent directory recorded in our hash?  */
641251881Speter      found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
642251881Speter      if (found_wcroot != NULL)
643251881Speter        break;
644251881Speter    }
645251881Speter
646251881Speter  if (found_wcroot != NULL)
647251881Speter    {
648251881Speter      /* We found a hash table entry for an ancestor, so we stopped scanning
649251881Speter         since all subdirectories use the same WCROOT.  */
650251881Speter      *wcroot = found_wcroot;
651251881Speter    }
652251881Speter  else if (wc_format == 0)
653251881Speter    {
654251881Speter      /* We finally found the database. Construct a wcroot_t for it.  */
655251881Speter
656251881Speter      apr_int64_t wc_id;
657251881Speter      svn_error_t *err;
658251881Speter
659251881Speter      err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool);
660251881Speter      if (err)
661251881Speter        {
662251881Speter          if (err->apr_err == SVN_ERR_WC_CORRUPT)
663251881Speter            return svn_error_quick_wrap(
664251881Speter              err, apr_psprintf(scratch_pool,
665251881Speter                                _("Missing a row in WCROOT for '%s'."),
666251881Speter                                svn_dirent_local_style(original_abspath,
667251881Speter                                                       scratch_pool)));
668251881Speter          return svn_error_trace(err);
669251881Speter        }
670251881Speter
671251881Speter      /* WCROOT.local_abspath may be NULL when the database is stored
672251881Speter         inside the wcroot, but we know the abspath is this directory
673251881Speter         (ie. where we found it).  */
674251881Speter
675251881Speter      err = svn_wc__db_pdh_create_wcroot(wcroot,
676253734Speter                            apr_pstrdup(db->state_pool,
677253734Speter                                        symlink_wcroot_abspath
678253734Speter                                          ? symlink_wcroot_abspath
679253734Speter                                          : local_abspath),
680251881Speter                            sdb, wc_id, FORMAT_FROM_SDB,
681251881Speter                            db->verify_format, db->enforce_empty_wq,
682251881Speter                            db->state_pool, scratch_pool);
683251881Speter      if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
684251881Speter                  err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
685251881Speter          kind == svn_node_symlink)
686251881Speter        {
687251881Speter          /* We found an unsupported WC after traversing upwards from a
688251881Speter           * symlink. Fall through to code below to check if the symlink
689251881Speter           * points at a supported WC. */
690251881Speter          svn_error_clear(err);
691251881Speter          *wcroot = NULL;
692251881Speter        }
693269847Speter      else if (err)
694269847Speter        {
695269847Speter          /* Close handle if we are not going to use it to support
696269847Speter             upgrading with exclusive wc locking. */
697269847Speter          return svn_error_compose_create(err, svn_sqlite__close(sdb));
698269847Speter        }
699251881Speter    }
700251881Speter  else
701251881Speter    {
702251881Speter      /* We found something that looks like a wc-1 working copy directory.
703251881Speter         However, if the format version is 12 and the .svn/entries file
704251881Speter         is only 3 bytes long, then it's a breadcrumb in a wc-ng working
705251881Speter         copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
706251881Speter      if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
707251881Speter        {
708251881Speter          apr_finfo_t info;
709251881Speter
710251881Speter          /* Check attributes of .svn/entries */
711251881Speter          const char *admin_abspath = svn_wc__adm_child(
712251881Speter              local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
713251881Speter          svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
714251881Speter                                         scratch_pool);
715251881Speter
716251881Speter          /* If the former does not succeed, something is seriously wrong. */
717251881Speter          if (err)
718251881Speter            return svn_error_createf(
719251881Speter                SVN_ERR_WC_CORRUPT, err,
720251881Speter                _("The working copy at '%s' is corrupt."),
721251881Speter                svn_dirent_local_style(local_abspath, scratch_pool));
722251881Speter          svn_error_clear(err);
723251881Speter
724251881Speter          if (3 == info.size)
725251881Speter            {
726251881Speter              /* Check existence of .svn/wc.db */
727251881Speter              admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
728251881Speter                                                scratch_pool);
729251881Speter              err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
730251881Speter                                scratch_pool);
731251881Speter              if (err && APR_STATUS_IS_ENOENT(err->apr_err))
732251881Speter                {
733251881Speter                  svn_error_clear(err);
734251881Speter                  return svn_error_createf(
735251881Speter                      SVN_ERR_WC_CORRUPT, NULL,
736251881Speter                      _("The working copy database at '%s' is missing."),
737251881Speter                      svn_dirent_local_style(local_abspath, scratch_pool));
738251881Speter                }
739251881Speter              else
740251881Speter                /* We should never have reached this point in the code
741251881Speter                   if .svn/wc.db exists; therefore it's best to assume
742251881Speter                   it's corrupt. */
743251881Speter                return svn_error_createf(
744251881Speter                    SVN_ERR_WC_CORRUPT, err,
745251881Speter                    _("The working copy database at '%s' is corrupt."),
746251881Speter                    svn_dirent_local_style(local_abspath, scratch_pool));
747251881Speter            }
748251881Speter        }
749251881Speter
750251881Speter      SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
751253734Speter                            apr_pstrdup(db->state_pool,
752253734Speter                                        symlink_wcroot_abspath
753253734Speter                                          ? symlink_wcroot_abspath
754253734Speter                                          : local_abspath),
755251881Speter                            NULL, UNKNOWN_WC_ID, wc_format,
756251881Speter                            db->verify_format, db->enforce_empty_wq,
757251881Speter                            db->state_pool, scratch_pool));
758251881Speter    }
759251881Speter
760251881Speter  if (*wcroot)
761251881Speter    {
762251881Speter      const char *dir_relpath;
763251881Speter
764253734Speter      if (symlink_wcroot_abspath)
765253734Speter        {
766253734Speter          /* The WCROOT was found through a symlink pointing at the root of
767253734Speter           * the WC. Cache the WCROOT under the symlink's path. */
768253734Speter          local_dir_abspath = symlink_wcroot_abspath;
769253734Speter        }
770253734Speter
771251881Speter      /* The subdirectory's relpath is easily computed relative to the
772251881Speter         wcroot that we just found.  */
773251881Speter      dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
774251881Speter
775251881Speter      /* And the result local_relpath may include a filename.  */
776251881Speter      *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
777251881Speter    }
778251881Speter
779251881Speter  if (kind == svn_node_symlink)
780251881Speter    {
781251881Speter      svn_boolean_t retry_if_dir = FALSE;
782251881Speter      svn_wc__db_status_t status;
783251881Speter      svn_boolean_t conflicted;
784251881Speter      svn_error_t *err;
785251881Speter
786251881Speter      /* Check if the symlink is versioned or obstructs a versioned node
787251881Speter       * in this DB -- in that case, use this wcroot. Else, if the symlink
788251881Speter       * points to a directory, try to find a wcroot in that directory
789251881Speter       * instead. */
790251881Speter
791251881Speter      if (*wcroot)
792251881Speter        {
793251881Speter          err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
794251881Speter                                              NULL, NULL, NULL, NULL, NULL,
795251881Speter                                              NULL, NULL, NULL, NULL, NULL,
796251881Speter                                              NULL, NULL, NULL, &conflicted,
797251881Speter                                              NULL, NULL, NULL, NULL, NULL,
798251881Speter                                              NULL, *wcroot, *local_relpath,
799251881Speter                                              scratch_pool, scratch_pool);
800251881Speter          if (err)
801251881Speter            {
802251881Speter              if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
803251881Speter                  && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
804251881Speter                return svn_error_trace(err);
805251881Speter
806251881Speter              svn_error_clear(err);
807251881Speter              retry_if_dir = TRUE; /* The symlink is unversioned. */
808251881Speter            }
809251881Speter          else
810251881Speter            {
811251881Speter              /* The symlink is versioned, or obstructs a versioned node.
812251881Speter               * Ignore non-conflicted not-present/excluded nodes.
813251881Speter               * This allows the symlink to redirect the wcroot query to a
814251881Speter               * directory, regardless of 'invisible' nodes in this WC. */
815251881Speter              retry_if_dir = ((status == svn_wc__db_status_not_present ||
816251881Speter                               status == svn_wc__db_status_excluded ||
817251881Speter                               status == svn_wc__db_status_server_excluded)
818251881Speter                              && !conflicted);
819251881Speter            }
820251881Speter        }
821251881Speter      else
822251881Speter        retry_if_dir = TRUE;
823251881Speter
824251881Speter      if (retry_if_dir)
825251881Speter        {
826251881Speter          svn_node_kind_t resolved_kind;
827251881Speter
828251881Speter          SVN_ERR(svn_io_check_resolved_path(original_abspath,
829251881Speter                                             &resolved_kind,
830251881Speter                                             scratch_pool));
831251881Speter          if (resolved_kind == svn_node_dir)
832251881Speter            {
833253734Speter              symlink_wcroot_abspath = original_abspath;
834251881Speter              SVN_ERR(read_link_target(&local_abspath, original_abspath,
835251881Speter                                       scratch_pool));
836251881Speter              /* This handle was opened in this function but is not going
837251881Speter                 to be used further so close it. */
838251881Speter              if (sdb)
839251881Speter                SVN_ERR(svn_sqlite__close(sdb));
840251881Speter              goto try_symlink_as_dir;
841251881Speter            }
842251881Speter        }
843251881Speter    }
844251881Speter
845251881Speter  /* We've found the appropriate WCROOT for the requested path. Stash
846251881Speter     it into that path's directory.  */
847251881Speter  svn_hash_sets(db->dir_data,
848251881Speter                apr_pstrdup(db->state_pool, local_dir_abspath),
849251881Speter                *wcroot);
850251881Speter
851251881Speter  /* Did we traverse up to parent directories?  */
852251881Speter  if (!moved_upwards)
853251881Speter    {
854251881Speter      /* We did NOT move to a parent of the original requested directory.
855251881Speter         We've constructed and filled in a WCROOT for the request, so we
856251881Speter         are done.  */
857251881Speter      return SVN_NO_ERROR;
858251881Speter    }
859251881Speter
860251881Speter  /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
861251881Speter     passed into this function. We stepped *at least* one directory above that.
862251881Speter     We should now associate the WROOT for each parent directory that does
863251881Speter     not (yet) have one.  */
864251881Speter
865251881Speter  scan_abspath = local_dir_abspath;
866251881Speter
867251881Speter  do
868251881Speter    {
869251881Speter      const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
870251881Speter      svn_wc__db_wcroot_t *parent_wcroot;
871251881Speter
872251881Speter      parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
873251881Speter      if (parent_wcroot == NULL)
874251881Speter        {
875251881Speter          svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
876251881Speter                        *wcroot);
877251881Speter        }
878251881Speter
879251881Speter      /* Move up a directory, stopping when we reach the directory where
880251881Speter         we found/built the WCROOT.  */
881251881Speter      scan_abspath = parent_dir;
882251881Speter    }
883251881Speter  while (strcmp(scan_abspath, local_abspath) != 0);
884251881Speter
885251881Speter  return SVN_NO_ERROR;
886251881Speter}
887251881Speter
888251881Speter
889251881Spetersvn_error_t *
890251881Spetersvn_wc__db_drop_root(svn_wc__db_t *db,
891251881Speter                     const char *local_abspath,
892251881Speter                     apr_pool_t *scratch_pool)
893251881Speter{
894251881Speter  svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath);
895251881Speter  apr_hash_index_t *hi;
896251881Speter  apr_status_t result;
897251881Speter
898251881Speter  if (!root_wcroot)
899251881Speter    return SVN_NO_ERROR;
900251881Speter
901251881Speter  if (strcmp(root_wcroot->abspath, local_abspath) != 0)
902251881Speter    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
903251881Speter                             _("'%s' is not a working copy root"),
904251881Speter                             svn_dirent_local_style(local_abspath,
905251881Speter                                                    scratch_pool));
906251881Speter
907251881Speter  for (hi = apr_hash_first(scratch_pool, db->dir_data);
908251881Speter       hi;
909251881Speter       hi = apr_hash_next(hi))
910251881Speter    {
911251881Speter      svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
912251881Speter
913251881Speter      if (wcroot == root_wcroot)
914251881Speter        svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL);
915251881Speter    }
916251881Speter
917251881Speter  result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
918251881Speter  if (result != APR_SUCCESS)
919251881Speter    return svn_error_wrap_apr(result, NULL);
920251881Speter
921251881Speter  return SVN_NO_ERROR;
922251881Speter}
923