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