lock.c revision 299742
1/*
2 * lock.c:  routines for locking working copy subdirectories.
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_DEPRECATED
25
26#include <apr_pools.h>
27#include <apr_time.h>
28
29#include "svn_pools.h"
30#include "svn_dirent_uri.h"
31#include "svn_path.h"
32#include "svn_sorts.h"
33#include "svn_hash.h"
34#include "svn_types.h"
35
36#include "wc.h"
37#include "adm_files.h"
38#include "lock.h"
39#include "props.h"
40#include "wc_db.h"
41
42#include "svn_private_config.h"
43#include "private/svn_wc_private.h"
44
45
46
47
48struct svn_wc_adm_access_t
49{
50  /* PATH to directory which contains the administrative area */
51  const char *path;
52
53  /* And the absolute form of the path.  */
54  const char *abspath;
55
56  /* Indicates that the baton has been closed. */
57  svn_boolean_t closed;
58
59  /* Handle to the administrative database. */
60  svn_wc__db_t *db;
61
62  /* Was the DB provided to us? If so, then we'll never close it.  */
63  svn_boolean_t db_provided;
64
65  /* ENTRIES_HIDDEN is all cached entries including those in
66     state deleted or state absent. It may be NULL. */
67  apr_hash_t *entries_all;
68
69  /* POOL is used to allocate cached items, they need to persist for the
70     lifetime of this access baton */
71  apr_pool_t *pool;
72
73};
74
75
76/* This is a placeholder used in the set hash to represent missing
77   directories.  Only its address is important, it contains no useful
78   data. */
79static const svn_wc_adm_access_t missing = { 0 };
80#define IS_MISSING(lock) ((lock) == &missing)
81
82/* ### hack for now. future functionality coming in a future revision.  */
83#define svn_wc__db_is_closed(db) FALSE
84
85
86svn_error_t *
87svn_wc__internal_check_wc(int *wc_format,
88                          svn_wc__db_t *db,
89                          const char *local_abspath,
90                          svn_boolean_t check_path,
91                          apr_pool_t *scratch_pool)
92{
93  svn_error_t *err;
94
95  err = svn_wc__db_temp_get_format(wc_format, db, local_abspath, scratch_pool);
96  if (err)
97    {
98      svn_node_kind_t kind;
99
100      if (err->apr_err != SVN_ERR_WC_MISSING &&
101          err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT &&
102          err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
103        return svn_error_trace(err);
104      svn_error_clear(err);
105
106      /* ### the stuff below seems to be redundant. get_format() probably
107         ### does all this.
108         ###
109         ### investigate all callers. DEFINITELY keep in mind the
110         ### svn_wc_check_wc() entrypoint.
111      */
112
113      /* If the format file does not exist or path not directory, then for
114         our purposes this is not a working copy, so return 0. */
115      *wc_format = 0;
116
117      /* Check path itself exists. */
118      SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
119      if (kind == svn_node_none)
120        {
121          return svn_error_createf(APR_ENOENT, NULL, _("'%s' does not exist"),
122                                   svn_dirent_local_style(local_abspath,
123                                                          scratch_pool));
124        }
125    }
126
127  if (*wc_format >= SVN_WC__WC_NG_VERSION)
128    {
129      svn_wc__db_status_t db_status;
130      svn_node_kind_t db_kind;
131
132      if (check_path)
133        {
134          /* If a node is not a directory, it is not a working copy
135             directory.  This allows creating new working copies as
136             a path below an existing working copy. */
137          svn_node_kind_t wc_kind;
138
139          SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool));
140          if (wc_kind != svn_node_dir)
141            {
142              *wc_format = 0; /* Not a directory, so not a wc-directory */
143              return SVN_NO_ERROR;
144            }
145        }
146
147      err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL,
148                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
149                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
150                                 NULL, NULL, NULL, NULL, NULL,
151                                 NULL, NULL, NULL,
152                                 db, local_abspath,
153                                 scratch_pool, scratch_pool);
154
155      if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
156        {
157          svn_error_clear(err);
158          *wc_format = 0;
159          return SVN_NO_ERROR;
160        }
161      else
162        SVN_ERR(err);
163
164      if (db_kind != svn_node_dir)
165        {
166          /* The WC thinks there must be a file, so this is not
167             a wc-directory */
168          *wc_format = 0;
169          return SVN_NO_ERROR;
170        }
171
172      switch (db_status)
173        {
174          case svn_wc__db_status_not_present:
175          case svn_wc__db_status_server_excluded:
176          case svn_wc__db_status_excluded:
177            /* If there is a directory here, it is not related to the parent
178               working copy: Obstruction */
179            *wc_format = 0;
180            return SVN_NO_ERROR;
181          default:
182            break;
183        }
184    }
185
186  return SVN_NO_ERROR;
187}
188
189
190svn_error_t *
191svn_wc_check_wc2(int *wc_format,
192                 svn_wc_context_t *wc_ctx,
193                 const char *local_abspath,
194                 apr_pool_t *scratch_pool)
195{
196  /* ### Should we pass TRUE for check_path to find obstructions and
197         missing directories? */
198  return svn_error_trace(
199    svn_wc__internal_check_wc(wc_format, wc_ctx->db, local_abspath, FALSE,
200                              scratch_pool));
201}
202
203
204/* */
205static svn_error_t *
206add_to_shared(svn_wc_adm_access_t *lock, apr_pool_t *scratch_pool)
207{
208  /* ### sometimes we replace &missing with a now-valid lock.  */
209  {
210    svn_wc_adm_access_t *prior = svn_wc__db_temp_get_access(lock->db,
211                                                            lock->abspath,
212                                                            scratch_pool);
213    if (IS_MISSING(prior))
214      SVN_ERR(svn_wc__db_temp_close_access(lock->db, lock->abspath,
215                                           prior, scratch_pool));
216  }
217
218  svn_wc__db_temp_set_access(lock->db, lock->abspath, lock,
219                             scratch_pool);
220
221  return SVN_NO_ERROR;
222}
223
224
225/* */
226static svn_wc_adm_access_t *
227get_from_shared(const char *abspath,
228                svn_wc__db_t *db,
229                apr_pool_t *scratch_pool)
230{
231  /* We closed the DB when it became empty. ABSPATH is not present.  */
232  if (db == NULL)
233    return NULL;
234  return svn_wc__db_temp_get_access(db, abspath, scratch_pool);
235}
236
237
238/* */
239static svn_error_t *
240close_single(svn_wc_adm_access_t *adm_access,
241             svn_boolean_t preserve_lock,
242             apr_pool_t *scratch_pool)
243{
244  svn_boolean_t locked;
245
246  if (adm_access->closed)
247    return SVN_NO_ERROR;
248
249  /* Physically unlock if required */
250  SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
251                                      adm_access->abspath, TRUE,
252                                      scratch_pool));
253  if (locked)
254    {
255      if (!preserve_lock)
256        {
257          /* Remove the physical lock in the admin directory for
258             PATH. It is acceptable for the administrative area to
259             have disappeared, such as when the directory is removed
260             from the working copy.  It is an error for the lock to
261             have disappeared if the administrative area still exists. */
262
263          svn_error_t *err = svn_wc__db_wclock_release(adm_access->db,
264                                                       adm_access->abspath,
265                                                       scratch_pool);
266          if (err)
267            {
268              if (svn_wc__adm_area_exists(adm_access->abspath, scratch_pool))
269                return err;
270              svn_error_clear(err);
271            }
272        }
273    }
274
275  /* Reset to prevent further use of the lock. */
276  adm_access->closed = TRUE;
277
278  /* Detach from set */
279  SVN_ERR(svn_wc__db_temp_close_access(adm_access->db, adm_access->abspath,
280                                       adm_access, scratch_pool));
281
282  /* Possibly close the underlying wc_db. */
283  if (!adm_access->db_provided)
284    {
285      apr_hash_t *opened = svn_wc__db_temp_get_all_access(adm_access->db,
286                                                          scratch_pool);
287      if (apr_hash_count(opened) == 0)
288        {
289          SVN_ERR(svn_wc__db_close(adm_access->db));
290          adm_access->db = NULL;
291        }
292    }
293
294  return SVN_NO_ERROR;
295}
296
297
298/* Cleanup for a locked access baton.
299
300   This handles closing access batons when their pool gets destroyed.
301   The physical locks associated with such batons remain in the working
302   copy if they are protecting work items in the workqueue.  */
303static apr_status_t
304pool_cleanup_locked(void *p)
305{
306  svn_wc_adm_access_t *lock = p;
307  apr_uint64_t id;
308  svn_skel_t *work_item;
309  svn_error_t *err;
310
311  if (lock->closed)
312    return APR_SUCCESS;
313
314  /* If the DB is closed, then we have a bunch of extra work to do.  */
315  if (svn_wc__db_is_closed(lock->db))
316    {
317      apr_pool_t *scratch_pool;
318      svn_wc__db_t *db;
319
320      lock->closed = TRUE;
321
322      /* If there is no ADM area, then we definitely have no work items
323         or physical locks to worry about. Bail out.  */
324      if (!svn_wc__adm_area_exists(lock->abspath, lock->pool))
325        return APR_SUCCESS;
326
327      /* Creating a subpool is safe within a pool cleanup, as long as
328         we're absolutely sure to destroy it before we exit this function.
329
330         We avoid using LOCK->POOL to keep the following functions from
331         hanging cleanups or subpools from it. (the cleanups *might* get
332         run, but the subpools will NOT be destroyed)  */
333      scratch_pool = svn_pool_create(lock->pool);
334
335      err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
336                            scratch_pool, scratch_pool);
337      if (!err)
338        {
339          err = svn_wc__db_wq_fetch_next(&id, &work_item, db, lock->abspath, 0,
340                                         scratch_pool, scratch_pool);
341          if (!err && work_item == NULL)
342            {
343              /* There is no remaining work, so we're good to remove any
344                 potential "physical" lock.  */
345              err = svn_wc__db_wclock_release(db, lock->abspath, scratch_pool);
346            }
347        }
348      svn_error_clear(err);
349
350      /* Closes the DB, too.  */
351      svn_pool_destroy(scratch_pool);
352
353      return APR_SUCCESS;
354    }
355
356  /* ### should we create an API that just looks, but doesn't return?  */
357  err = svn_wc__db_wq_fetch_next(&id, &work_item, lock->db, lock->abspath, 0,
358                                 lock->pool, lock->pool);
359
360  /* Close just this access baton. The pool cleanup will close the rest.  */
361  if (!err)
362    err = close_single(lock,
363                       work_item != NULL /* preserve_lock */,
364                       lock->pool);
365
366  if (err)
367    {
368      apr_status_t apr_err = err->apr_err;
369      svn_error_clear(err);
370      return apr_err;
371    }
372
373  return APR_SUCCESS;
374}
375
376
377/* Cleanup for a readonly access baton.  */
378static apr_status_t
379pool_cleanup_readonly(void *data)
380{
381  svn_wc_adm_access_t *lock = data;
382  svn_error_t *err;
383
384  if (lock->closed)
385    return APR_SUCCESS;
386
387  /* If the DB is closed, then we have nothing to do. There are no
388     "physical" locks to remove, and we don't care whether this baton
389     is registered with the DB.  */
390  if (svn_wc__db_is_closed(lock->db))
391    return APR_SUCCESS;
392
393  /* Close this baton. No lock to preserve. Since this is part of the
394     pool cleanup, we don't need to close children -- the cleanup process
395     will close all children.  */
396  err = close_single(lock, FALSE /* preserve_lock */, lock->pool);
397  if (err)
398    {
399      apr_status_t result = err->apr_err;
400      svn_error_clear(err);
401      return result;
402    }
403
404  return APR_SUCCESS;
405}
406
407
408/* An APR pool cleanup handler.  This is a child handler, it removes the
409   main pool handler. */
410static apr_status_t
411pool_cleanup_child(void *p)
412{
413  svn_wc_adm_access_t *lock = p;
414
415  apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_locked);
416  apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_readonly);
417
418  return APR_SUCCESS;
419}
420
421
422/* Allocate from POOL, initialise and return an access baton. TYPE and PATH
423   are used to initialise the baton.  If STEAL_LOCK, steal the lock if path
424   is already locked */
425static svn_error_t *
426adm_access_alloc(svn_wc_adm_access_t **adm_access,
427                 const char *path,
428                 svn_wc__db_t *db,
429                 svn_boolean_t db_provided,
430                 svn_boolean_t write_lock,
431                 apr_pool_t *result_pool,
432                 apr_pool_t *scratch_pool)
433{
434  svn_error_t *err;
435  svn_wc_adm_access_t *lock = apr_palloc(result_pool, sizeof(*lock));
436
437  lock->closed = FALSE;
438  lock->entries_all = NULL;
439  lock->db = db;
440  lock->db_provided = db_provided;
441  lock->path = apr_pstrdup(result_pool, path);
442  lock->pool = result_pool;
443
444  SVN_ERR(svn_dirent_get_absolute(&lock->abspath, path, result_pool));
445
446  *adm_access = lock;
447
448  if (write_lock)
449    {
450      svn_boolean_t owns_lock;
451
452      /* If the db already owns a lock, we can't add an extra lock record */
453      SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, path, FALSE,
454                                          scratch_pool));
455
456      /* If DB owns the lock, but when there is no access baton open for this
457         directory, old access baton based code is trying to access data that
458         was previously locked by new code. Just hand them the lock, or
459         important code paths like svn_wc_add3() will start failing */
460      if (!owns_lock
461          || svn_wc__adm_retrieve_internal2(db, lock->abspath, scratch_pool))
462        {
463          SVN_ERR(svn_wc__db_wclock_obtain(db, lock->abspath, 0, FALSE,
464                                           scratch_pool));
465        }
466    }
467
468  err = add_to_shared(lock, scratch_pool);
469
470  if (err)
471    return svn_error_compose_create(
472                err,
473                svn_wc__db_wclock_release(db, lock->abspath, scratch_pool));
474
475  /* ### does this utf8 thing really/still apply??  */
476  /* It's important that the cleanup handler is registered *after* at least
477     one UTF8 conversion has been done, since such a conversion may create
478     the apr_xlate_t object in the pool, and that object must be around
479     when the cleanup handler runs.  If the apr_xlate_t cleanup handler
480     were to run *before* the access baton cleanup handler, then the access
481     baton's handler won't work. */
482
483  /* Register an appropriate cleanup handler, based on the whether this
484     access baton is locked or not.  */
485  apr_pool_cleanup_register(lock->pool, lock,
486                            write_lock
487                              ? pool_cleanup_locked
488                              : pool_cleanup_readonly,
489                            pool_cleanup_child);
490
491  return SVN_NO_ERROR;
492}
493
494
495/* */
496static svn_error_t *
497probe(svn_wc__db_t *db,
498      const char **dir,
499      const char *path,
500      apr_pool_t *pool)
501{
502  svn_node_kind_t kind;
503  int wc_format = 0;
504
505  SVN_ERR(svn_io_check_path(path, &kind, pool));
506  if (kind == svn_node_dir)
507    {
508      const char *local_abspath;
509
510      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
511      SVN_ERR(svn_wc__internal_check_wc(&wc_format, db, local_abspath,
512                                        FALSE, pool));
513    }
514
515  /* a "version" of 0 means a non-wc directory */
516  if (kind != svn_node_dir || wc_format == 0)
517    {
518      /* Passing a path ending in "." or ".." to svn_dirent_dirname() is
519         probably always a bad idea; certainly it is in this case.
520         Unfortunately, svn_dirent_dirname()'s current signature can't
521         return an error, so we have to insert the protection in this
522         caller, ideally the API needs a change.  See issue #1617. */
523      const char *base_name = svn_dirent_basename(path, pool);
524      if ((strcmp(base_name, "..") == 0)
525          || (strcmp(base_name, ".") == 0))
526        {
527          return svn_error_createf
528            (SVN_ERR_WC_BAD_PATH, NULL,
529             _("Path '%s' ends in '%s', "
530               "which is unsupported for this operation"),
531             svn_dirent_local_style(path, pool), base_name);
532        }
533
534      *dir = svn_dirent_dirname(path, pool);
535    }
536  else
537    *dir = path;
538
539  return SVN_NO_ERROR;
540}
541
542
543/* */
544static svn_error_t *
545open_single(svn_wc_adm_access_t **adm_access,
546            const char *path,
547            svn_boolean_t write_lock,
548            svn_wc__db_t *db,
549            svn_boolean_t db_provided,
550            apr_pool_t *result_pool,
551            apr_pool_t *scratch_pool)
552{
553  const char *local_abspath;
554  int wc_format = 0;
555  svn_error_t *err;
556  svn_wc_adm_access_t *lock;
557
558  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
559  err = svn_wc__internal_check_wc(&wc_format, db, local_abspath, FALSE,
560                                  scratch_pool);
561  if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err)))
562    {
563      return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, err,
564                               _("'%s' is not a working copy"),
565                               svn_dirent_local_style(path, scratch_pool));
566    }
567  SVN_ERR(err);
568
569  /* The format version must match exactly. Note that wc_db will perform
570     an auto-upgrade if allowed. If it does *not*, then it has decided a
571     manual upgrade is required and it should have raised an error.  */
572  SVN_ERR_ASSERT(wc_format == SVN_WC__VERSION);
573
574  /* Need to create a new lock */
575  SVN_ERR(adm_access_alloc(&lock, path, db, db_provided, write_lock,
576                           result_pool, scratch_pool));
577
578  /* ### recurse was here */
579  *adm_access = lock;
580
581  return SVN_NO_ERROR;
582}
583
584
585/* Retrieves the KIND of LOCAL_ABSPATH and whether its administrative data is
586   available in the working copy.
587
588   *AVAILABLE is set to TRUE when the node and its metadata are available,
589   otherwise to FALSE (due to obstruction, missing, absence, exclusion,
590   or a "not-present" child).
591
592   KIND can be NULL.
593
594   ### note: this function should go away when we move to a single
595   ### adminstrative area.  */
596static svn_error_t *
597adm_available(svn_boolean_t *available,
598              svn_node_kind_t *kind,
599              svn_wc__db_t *db,
600              const char *local_abspath,
601              apr_pool_t *scratch_pool)
602{
603  svn_wc__db_status_t status;
604
605  if (kind)
606    *kind = svn_node_unknown;
607
608  SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL,
609                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
610                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
611                               NULL, NULL, NULL, NULL, NULL, NULL,
612                               db, local_abspath, scratch_pool, scratch_pool));
613
614  *available = !(status == svn_wc__db_status_server_excluded
615                 || status == svn_wc__db_status_excluded
616                 || status == svn_wc__db_status_not_present);
617
618  return SVN_NO_ERROR;
619}
620/* This is essentially the guts of svn_wc_adm_open3.
621 *
622 * If the working copy is already locked, return SVN_ERR_WC_LOCKED; if
623 * it is not a versioned directory, return SVN_ERR_WC_NOT_WORKING_COPY.
624 */
625static svn_error_t *
626do_open(svn_wc_adm_access_t **adm_access,
627        const char *path,
628        svn_wc__db_t *db,
629        svn_boolean_t db_provided,
630        apr_array_header_t *rollback,
631        svn_boolean_t write_lock,
632        int levels_to_lock,
633        svn_cancel_func_t cancel_func,
634        void *cancel_baton,
635        apr_pool_t *result_pool,
636        apr_pool_t *scratch_pool)
637{
638  svn_wc_adm_access_t *lock;
639  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
640
641  SVN_ERR(open_single(&lock, path, write_lock, db, db_provided,
642                      result_pool, iterpool));
643
644  /* Add self to the rollback list in case of error.  */
645  APR_ARRAY_PUSH(rollback, svn_wc_adm_access_t *) = lock;
646
647  if (levels_to_lock != 0)
648    {
649      const apr_array_header_t *children;
650      const char *local_abspath = svn_wc__adm_access_abspath(lock);
651      int i;
652
653      /* Reduce levels_to_lock since we are about to recurse */
654      if (levels_to_lock > 0)
655        levels_to_lock--;
656
657      SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath,
658                                       scratch_pool, iterpool));
659
660      /* Open the tree */
661      for (i = 0; i < children->nelts; i++)
662        {
663          const char *node_abspath;
664          svn_node_kind_t kind;
665          svn_boolean_t available;
666          const char *name = APR_ARRAY_IDX(children, i, const char *);
667
668          svn_pool_clear(iterpool);
669
670          /* See if someone wants to cancel this operation. */
671          if (cancel_func)
672            SVN_ERR(cancel_func(cancel_baton));
673
674          node_abspath = svn_dirent_join(local_abspath, name, iterpool);
675
676          SVN_ERR(adm_available(&available,
677                                &kind,
678                                db,
679                                node_abspath,
680                                scratch_pool));
681
682          if (kind != svn_node_dir)
683            continue;
684
685          if (available)
686            {
687              const char *node_path = svn_dirent_join(path, name, iterpool);
688              svn_wc_adm_access_t *node_access;
689
690              SVN_ERR(do_open(&node_access, node_path, db, db_provided,
691                              rollback, write_lock, levels_to_lock,
692                              cancel_func, cancel_baton,
693                              lock->pool, iterpool));
694              /* node_access has been registered in DB, so we don't need
695                 to do anything with it.  */
696            }
697        }
698    }
699  svn_pool_destroy(iterpool);
700
701  *adm_access = lock;
702
703  return SVN_NO_ERROR;
704}
705
706
707/* */
708static svn_error_t *
709open_all(svn_wc_adm_access_t **adm_access,
710         const char *path,
711         svn_wc__db_t *db,
712         svn_boolean_t db_provided,
713         svn_boolean_t write_lock,
714         int levels_to_lock,
715         svn_cancel_func_t cancel_func,
716         void *cancel_baton,
717         apr_pool_t *pool)
718{
719  apr_array_header_t *rollback;
720  svn_error_t *err;
721
722  rollback = apr_array_make(pool, 10, sizeof(svn_wc_adm_access_t *));
723
724  err = do_open(adm_access, path, db, db_provided, rollback,
725                write_lock, levels_to_lock,
726                cancel_func, cancel_baton, pool, pool);
727  if (err)
728    {
729      int i;
730
731      for (i = rollback->nelts; i--; )
732        {
733          svn_wc_adm_access_t *lock = APR_ARRAY_IDX(rollback, i,
734                                                    svn_wc_adm_access_t *);
735          SVN_ERR_ASSERT(!IS_MISSING(lock));
736
737          svn_error_clear(close_single(lock, FALSE /* preserve_lock */, pool));
738        }
739    }
740
741  return svn_error_trace(err);
742}
743
744
745svn_error_t *
746svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
747                 svn_wc_adm_access_t *associated,
748                 const char *path,
749                 svn_boolean_t write_lock,
750                 int levels_to_lock,
751                 svn_cancel_func_t cancel_func,
752                 void *cancel_baton,
753                 apr_pool_t *pool)
754{
755  svn_wc__db_t *db;
756  svn_boolean_t db_provided;
757
758  /* Make sure that ASSOCIATED has a set of access batons, so that we can
759     glom a reference to self into it. */
760  if (associated)
761    {
762      const char *abspath;
763      svn_wc_adm_access_t *lock;
764
765      SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
766      lock = get_from_shared(abspath, associated->db, pool);
767      if (lock && !IS_MISSING(lock))
768        /* Already locked.  The reason we don't return the existing baton
769           here is that the user is supposed to know whether a directory is
770           locked: if it's not locked call svn_wc_adm_open, if it is locked
771           call svn_wc_adm_retrieve.  */
772        return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
773                                 _("Working copy '%s' locked"),
774                                 svn_dirent_local_style(path, pool));
775      db = associated->db;
776      db_provided = associated->db_provided;
777    }
778  else
779    {
780      /* Any baton creation is going to need a shared structure for holding
781         data across the entire set. The caller isn't providing one, so we
782         do it here.  */
783      /* ### we could optimize around levels_to_lock==0, but much of this
784         ### is going to be simplified soon anyways.  */
785      SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
786                              pool, pool));
787      db_provided = FALSE;
788    }
789
790  return svn_error_trace(open_all(adm_access, path, db, db_provided,
791                                  write_lock, levels_to_lock,
792                                  cancel_func, cancel_baton, pool));
793}
794
795
796svn_error_t *
797svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
798                       svn_wc_adm_access_t *associated,
799                       const char *path,
800                       svn_boolean_t write_lock,
801                       int levels_to_lock,
802                       svn_cancel_func_t cancel_func,
803                       void *cancel_baton,
804                       apr_pool_t *pool)
805{
806  svn_error_t *err;
807  const char *dir;
808
809  if (associated == NULL)
810    {
811      svn_wc__db_t *db;
812
813      /* Ugh. Too bad about having to open a DB.  */
814      SVN_ERR(svn_wc__db_open(&db,
815                              NULL /* ### config */, FALSE, TRUE, pool, pool));
816      err = probe(db, &dir, path, pool);
817      svn_error_clear(svn_wc__db_close(db));
818      SVN_ERR(err);
819    }
820  else
821    {
822      SVN_ERR(probe(associated->db, &dir, path, pool));
823    }
824
825  /* If we moved up a directory, then the path is not a directory, or it
826     is not under version control. In either case, the notion of
827     levels_to_lock does not apply to the provided path.  Disable it so
828     that we don't end up trying to lock more than we need.  */
829  if (dir != path)
830    levels_to_lock = 0;
831
832  err = svn_wc_adm_open3(adm_access, associated, dir, write_lock,
833                         levels_to_lock, cancel_func, cancel_baton, pool);
834  if (err)
835    {
836      svn_error_t *err2;
837
838      /* If we got an error on the parent dir, that means we failed to
839         get an access baton for the child in the first place.  And if
840         the reason we couldn't get the child access baton is that the
841         child is not a versioned directory, then return an error
842         about the child, not the parent. */
843      svn_node_kind_t child_kind;
844      if ((err2 = svn_io_check_path(path, &child_kind, pool)))
845        {
846          svn_error_compose(err, err2);
847          return err;
848        }
849
850      if ((dir != path)
851          && (child_kind == svn_node_dir)
852          && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
853        {
854          svn_error_clear(err);
855          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
856                                   _("'%s' is not a working copy"),
857                                   svn_dirent_local_style(path, pool));
858        }
859
860      return err;
861    }
862
863  return SVN_NO_ERROR;
864}
865
866
867svn_wc_adm_access_t *
868svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
869                               const char *abspath,
870                               apr_pool_t *scratch_pool)
871{
872  svn_wc_adm_access_t *adm_access = get_from_shared(abspath, db, scratch_pool);
873
874  /* If the entry is marked as "missing", then return nothing.  */
875  if (IS_MISSING(adm_access))
876    adm_access = NULL;
877
878  return adm_access;
879}
880
881
882/* SVN_DEPRECATED */
883svn_error_t *
884svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
885                    svn_wc_adm_access_t *associated,
886                    const char *path,
887                    apr_pool_t *pool)
888{
889  const char *local_abspath;
890  svn_node_kind_t kind = svn_node_unknown;
891  svn_node_kind_t wckind;
892  svn_error_t *err;
893
894  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
895
896  if (strcmp(associated->path, path) == 0)
897    *adm_access = associated;
898  else
899    *adm_access = svn_wc__adm_retrieve_internal2(associated->db, local_abspath,
900                                                 pool);
901
902  /* We found what we're looking for, so bail. */
903  if (*adm_access)
904    return SVN_NO_ERROR;
905
906  /* Most of the code expects access batons to exist, so returning an error
907     generally makes the calling code simpler as it doesn't need to check
908     for NULL batons. */
909  /* We are going to send a SVN_ERR_WC_NOT_LOCKED, but let's provide
910     a bit more information to our caller */
911
912  err = svn_io_check_path(path, &wckind, pool);
913
914  /* If we can't check the path, we can't make a good error message.  */
915  if (err)
916    {
917      return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err,
918                               _("Unable to check path existence for '%s'"),
919                               svn_dirent_local_style(path, pool));
920    }
921
922  if (associated)
923    {
924      err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated),
925                                 local_abspath,
926                                 TRUE /* allow_missing */,
927                                 TRUE /* show_deleted */,
928                                 FALSE /* show_hidden */, pool);
929
930      if (err)
931        {
932          kind = svn_node_unknown;
933          svn_error_clear(err);
934        }
935    }
936
937  if (kind == svn_node_dir && wckind == svn_node_file)
938    {
939      err = svn_error_createf(
940               SVN_ERR_WC_NOT_WORKING_COPY, NULL,
941               _("Expected '%s' to be a directory but found a file"),
942               svn_dirent_local_style(path, pool));
943
944      return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
945    }
946
947  if (kind != svn_node_dir && kind != svn_node_unknown)
948    {
949      err = svn_error_createf(
950               SVN_ERR_WC_NOT_WORKING_COPY, NULL,
951               _("Can't retrieve an access baton for non-directory '%s'"),
952               svn_dirent_local_style(path, pool));
953
954      return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
955    }
956
957  if (kind == svn_node_unknown || wckind == svn_node_none)
958    {
959      err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
960                              _("Directory '%s' is missing"),
961                              svn_dirent_local_style(path, pool));
962
963      return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
964    }
965
966  /* If all else fails, return our useless generic error.  */
967  return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
968                           _("Working copy '%s' is not locked"),
969                           svn_dirent_local_style(path, pool));
970}
971
972
973/* SVN_DEPRECATED */
974svn_error_t *
975svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
976                          svn_wc_adm_access_t *associated,
977                          const char *path,
978                          apr_pool_t *pool)
979{
980  const char *dir;
981  const char *local_abspath;
982  svn_node_kind_t kind;
983  svn_error_t *err;
984
985  SVN_ERR_ASSERT(associated != NULL);
986
987  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
988  SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath,
989                               TRUE /* allow_missing */,
990                               TRUE /* show_deleted */,
991                               FALSE /* show_hidden*/,
992                               pool));
993
994  if (kind == svn_node_dir)
995    dir = path;
996  else if (kind != svn_node_unknown)
997    dir = svn_dirent_dirname(path, pool);
998  else
999    /* Not a versioned item, probe it */
1000    SVN_ERR(probe(associated->db, &dir, path, pool));
1001
1002  err = svn_wc_adm_retrieve(adm_access, associated, dir, pool);
1003  if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
1004    {
1005      /* We'll receive a NOT LOCKED error for various reasons,
1006         including the reason we'll actually want to test for:
1007         The path is a versioned directory, but missing, in which case
1008         we want its parent's adm_access (which holds minimal data
1009         on the child) */
1010      svn_error_clear(err);
1011      SVN_ERR(probe(associated->db, &dir, path, pool));
1012      SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool));
1013    }
1014  else
1015    return svn_error_trace(err);
1016
1017  return SVN_NO_ERROR;
1018}
1019
1020
1021/* SVN_DEPRECATED */
1022svn_error_t *
1023svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
1024                      svn_wc_adm_access_t *associated,
1025                      const char *path,
1026                      svn_boolean_t write_lock,
1027                      int levels_to_lock,
1028                      svn_cancel_func_t cancel_func,
1029                      void *cancel_baton,
1030                      apr_pool_t *pool)
1031{
1032  svn_error_t *err;
1033
1034  err = svn_wc_adm_probe_retrieve(adm_access, associated, path, pool);
1035
1036  /* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for
1037     path in associated, in which case we want to open an access
1038     baton and add it to associated. */
1039  if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
1040    {
1041      svn_error_clear(err);
1042      err = svn_wc_adm_probe_open3(adm_access, associated,
1043                                   path, write_lock, levels_to_lock,
1044                                   cancel_func, cancel_baton,
1045                                   svn_wc_adm_access_pool(associated));
1046
1047      /* If the path is not a versioned directory, we just return a
1048         null access baton with no error.  Note that of the errors we
1049         do report, the most important (and probably most likely) is
1050         SVN_ERR_WC_LOCKED.  That error would mean that someone else
1051         has this area locked, and we definitely want to bail in that
1052         case. */
1053      if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
1054        {
1055          svn_error_clear(err);
1056          *adm_access = NULL;
1057          err = NULL;
1058        }
1059    }
1060
1061  return err;
1062}
1063
1064
1065/* */
1066static svn_error_t *
1067child_is_disjoint(svn_boolean_t *disjoint,
1068                  svn_wc__db_t *db,
1069                  const char *local_abspath,
1070                  apr_pool_t *scratch_pool)
1071{
1072  svn_boolean_t is_switched;
1073
1074  /* Check if the parent directory knows about this node */
1075  SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL,
1076                                 db, local_abspath, scratch_pool));
1077
1078  if (*disjoint)
1079    return SVN_NO_ERROR;
1080
1081  if (is_switched)
1082    *disjoint = TRUE;
1083
1084  return SVN_NO_ERROR;
1085}
1086
1087/* */
1088static svn_error_t *
1089open_anchor(svn_wc_adm_access_t **anchor_access,
1090            svn_wc_adm_access_t **target_access,
1091            const char **target,
1092            svn_wc__db_t *db,
1093            svn_boolean_t db_provided,
1094            const char *path,
1095            svn_boolean_t write_lock,
1096            int levels_to_lock,
1097            svn_cancel_func_t cancel_func,
1098            void *cancel_baton,
1099            apr_pool_t *pool)
1100{
1101  const char *base_name = svn_dirent_basename(path, pool);
1102
1103  /* Any baton creation is going to need a shared structure for holding
1104     data across the entire set. The caller isn't providing one, so we
1105     do it here.  */
1106  /* ### we could maybe skip the shared struct for levels_to_lock==0, but
1107     ### given that we need DB for format detection, may as well keep this.
1108     ### in any case, much of this is going to be simplified soon anyways.  */
1109  if (!db_provided)
1110    SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE,
1111                            pool, pool));
1112
1113  if (svn_path_is_empty(path)
1114      || svn_dirent_is_root(path, strlen(path))
1115      || ! strcmp(base_name, ".."))
1116    {
1117      SVN_ERR(open_all(anchor_access, path, db, db_provided,
1118                       write_lock, levels_to_lock,
1119                       cancel_func, cancel_baton, pool));
1120      *target_access = *anchor_access;
1121      *target = "";
1122    }
1123  else
1124    {
1125      svn_error_t *err;
1126      svn_wc_adm_access_t *p_access = NULL;
1127      svn_wc_adm_access_t *t_access = NULL;
1128      const char *parent = svn_dirent_dirname(path, pool);
1129      const char *local_abspath;
1130      svn_error_t *p_access_err = SVN_NO_ERROR;
1131
1132      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
1133
1134      /* Try to open parent of PATH to setup P_ACCESS */
1135      err = open_single(&p_access, parent, write_lock, db, db_provided,
1136                        pool, pool);
1137      if (err)
1138        {
1139          const char *abspath = svn_dirent_dirname(local_abspath, pool);
1140          svn_wc_adm_access_t *existing_adm = svn_wc__db_temp_get_access(db, abspath, pool);
1141
1142          if (IS_MISSING(existing_adm))
1143            svn_wc__db_temp_clear_access(db, abspath, pool);
1144          else
1145            SVN_ERR_ASSERT(existing_adm == NULL);
1146
1147          if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
1148            {
1149              svn_error_clear(err);
1150              p_access = NULL;
1151            }
1152          else if (write_lock && (err->apr_err == SVN_ERR_WC_LOCKED
1153                                  || APR_STATUS_IS_EACCES(err->apr_err)))
1154            {
1155              /* If P_ACCESS isn't to be returned then a read-only baton
1156                 will do for now, but keep the error in case we need it. */
1157              svn_error_t *err2 = open_single(&p_access, parent, FALSE,
1158                                              db, db_provided, pool, pool);
1159              if (err2)
1160                {
1161                  svn_error_clear(err2);
1162                  return err;
1163                }
1164              p_access_err = err;
1165            }
1166          else
1167            return err;
1168        }
1169
1170      /* Try to open PATH to setup T_ACCESS */
1171      err = open_all(&t_access, path, db, db_provided, write_lock,
1172                     levels_to_lock, cancel_func, cancel_baton, pool);
1173      if (err)
1174        {
1175          if (p_access == NULL)
1176            {
1177              /* Couldn't open the parent or the target. Bail out.  */
1178              svn_error_clear(p_access_err);
1179              return svn_error_trace(err);
1180            }
1181
1182          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
1183            {
1184              if (p_access)
1185                svn_error_clear(svn_wc_adm_close2(p_access, pool));
1186              svn_error_clear(p_access_err);
1187              return svn_error_trace(err);
1188            }
1189
1190          /* This directory is not under version control. Ignore it.  */
1191          svn_error_clear(err);
1192          t_access = NULL;
1193        }
1194
1195      /* At this stage might have P_ACCESS, T_ACCESS or both */
1196
1197      /* Check for switched or disjoint P_ACCESS and T_ACCESS */
1198      if (p_access && t_access)
1199        {
1200          svn_boolean_t disjoint;
1201
1202          err = child_is_disjoint(&disjoint, db, local_abspath, pool);
1203          if (err)
1204            {
1205              svn_error_clear(p_access_err);
1206              svn_error_clear(svn_wc_adm_close2(p_access, pool));
1207              svn_error_clear(svn_wc_adm_close2(t_access, pool));
1208              return svn_error_trace(err);
1209            }
1210
1211          if (disjoint)
1212            {
1213              /* Switched or disjoint, so drop P_ACCESS. Don't close any
1214                 descendants, or we might blast the child.  */
1215              err = close_single(p_access, FALSE /* preserve_lock */, pool);
1216              if (err)
1217                {
1218                  svn_error_clear(p_access_err);
1219                  svn_error_clear(svn_wc_adm_close2(t_access, pool));
1220                  return svn_error_trace(err);
1221                }
1222              p_access = NULL;
1223            }
1224        }
1225
1226      /* We have a parent baton *and* we have an error related to opening
1227         the baton. That means we have a readonly baton, but that isn't
1228         going to work for us. (p_access would have been set to NULL if
1229         a writable parent baton is not required)  */
1230      if (p_access && p_access_err)
1231        {
1232          if (t_access)
1233            svn_error_clear(svn_wc_adm_close2(t_access, pool));
1234          svn_error_clear(svn_wc_adm_close2(p_access, pool));
1235          return svn_error_trace(p_access_err);
1236        }
1237      svn_error_clear(p_access_err);
1238
1239      if (! t_access)
1240        {
1241          svn_boolean_t available;
1242          svn_node_kind_t kind;
1243
1244          err = adm_available(&available, &kind, db, local_abspath, pool);
1245
1246          if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1247            svn_error_clear(err);
1248          else if (err)
1249            {
1250              svn_error_clear(svn_wc_adm_close2(p_access, pool));
1251              return svn_error_trace(err);
1252            }
1253        }
1254
1255      *anchor_access = p_access ? p_access : t_access;
1256      *target_access = t_access ? t_access : p_access;
1257
1258      if (! p_access)
1259        *target = "";
1260      else
1261        *target = base_name;
1262    }
1263
1264  return SVN_NO_ERROR;
1265}
1266
1267
1268svn_error_t *
1269svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access,
1270                       svn_wc_adm_access_t **target_access,
1271                       const char **target,
1272                       const char *path,
1273                       svn_boolean_t write_lock,
1274                       int levels_to_lock,
1275                       svn_cancel_func_t cancel_func,
1276                       void *cancel_baton,
1277                       apr_pool_t *pool)
1278{
1279  return svn_error_trace(open_anchor(anchor_access, target_access, target,
1280                                     NULL, FALSE, path, write_lock,
1281                                     levels_to_lock, cancel_func,
1282                                     cancel_baton, pool));
1283}
1284
1285
1286/* Does the work of closing the access baton ADM_ACCESS.  Any physical
1287   locks are removed from the working copy if PRESERVE_LOCK is FALSE, or
1288   are left if PRESERVE_LOCK is TRUE.  Any associated access batons that
1289   are direct descendants will also be closed.
1290 */
1291static svn_error_t *
1292do_close(svn_wc_adm_access_t *adm_access,
1293         svn_boolean_t preserve_lock,
1294         apr_pool_t *scratch_pool)
1295{
1296  svn_wc_adm_access_t *look;
1297
1298  if (adm_access->closed)
1299    return SVN_NO_ERROR;
1300
1301  /* If we are part of the shared set, then close descendant batons.  */
1302  look = get_from_shared(adm_access->abspath, adm_access->db, scratch_pool);
1303  if (look != NULL)
1304    {
1305      apr_hash_t *opened;
1306      apr_hash_index_t *hi;
1307
1308      /* Gather all the opened access batons from the DB.  */
1309      opened = svn_wc__db_temp_get_all_access(adm_access->db, scratch_pool);
1310
1311      /* Close any that are descendants of this baton.  */
1312      for (hi = apr_hash_first(scratch_pool, opened);
1313           hi;
1314           hi = apr_hash_next(hi))
1315        {
1316          const char *abspath = apr_hash_this_key(hi);
1317          svn_wc_adm_access_t *child = apr_hash_this_val(hi);
1318          const char *path = child->path;
1319
1320          if (IS_MISSING(child))
1321            {
1322              /* We don't close the missing entry, but get rid of it from
1323                 the set. */
1324              svn_wc__db_temp_clear_access(adm_access->db, abspath,
1325                                           scratch_pool);
1326              continue;
1327            }
1328
1329          if (! svn_dirent_is_ancestor(adm_access->path, path)
1330              || strcmp(adm_access->path, path) == 0)
1331            continue;
1332
1333          SVN_ERR(close_single(child, preserve_lock, scratch_pool));
1334        }
1335    }
1336
1337  return svn_error_trace(close_single(adm_access, preserve_lock,
1338                                      scratch_pool));
1339}
1340
1341
1342/* SVN_DEPRECATED */
1343svn_error_t *
1344svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool)
1345{
1346  return svn_error_trace(do_close(adm_access, FALSE, scratch_pool));
1347}
1348
1349
1350/* SVN_DEPRECATED */
1351svn_boolean_t
1352svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access)
1353{
1354  svn_boolean_t locked;
1355  apr_pool_t *subpool = svn_pool_create(adm_access->pool);
1356  svn_error_t *err = svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
1357                                                 adm_access->abspath, TRUE,
1358                                                 subpool);
1359  svn_pool_destroy(subpool);
1360
1361  if (err)
1362    {
1363      svn_error_clear(err);
1364      /* ### is this right? */
1365      return FALSE;
1366    }
1367
1368  return locked;
1369}
1370
1371svn_error_t *
1372svn_wc__write_check(svn_wc__db_t *db,
1373                    const char *local_abspath,
1374                    apr_pool_t *scratch_pool)
1375{
1376  svn_boolean_t locked;
1377
1378  SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, db, local_abspath, FALSE,
1379                                      scratch_pool));
1380  if (!locked)
1381    return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
1382                             _("No write-lock in '%s'"),
1383                             svn_dirent_local_style(local_abspath,
1384                                                    scratch_pool));
1385
1386  return SVN_NO_ERROR;
1387}
1388
1389svn_error_t *
1390svn_wc_locked2(svn_boolean_t *locked_here,
1391               svn_boolean_t *locked,
1392               svn_wc_context_t *wc_ctx,
1393               const char *local_abspath,
1394               apr_pool_t *scratch_pool)
1395{
1396  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1397
1398  if (locked_here != NULL)
1399    SVN_ERR(svn_wc__db_wclock_owns_lock(locked_here, wc_ctx->db, local_abspath,
1400                                        FALSE, scratch_pool));
1401  if (locked != NULL)
1402    SVN_ERR(svn_wc__db_wclocked(locked, wc_ctx->db, local_abspath,
1403                                scratch_pool));
1404
1405  return SVN_NO_ERROR;
1406}
1407
1408
1409/* SVN_DEPRECATED */
1410const char *
1411svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access)
1412{
1413  return adm_access->path;
1414}
1415
1416
1417const char *
1418svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access)
1419{
1420  return adm_access->abspath;
1421}
1422
1423
1424/* SVN_DEPRECATED */
1425apr_pool_t *
1426svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access)
1427{
1428  return adm_access->pool;
1429}
1430
1431apr_pool_t *
1432svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access)
1433{
1434  return adm_access->pool;
1435}
1436
1437void
1438svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
1439                               apr_hash_t *entries)
1440{
1441  adm_access->entries_all = entries;
1442}
1443
1444
1445apr_hash_t *
1446svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access)
1447{
1448  /* Compile with -DSVN_DISABLE_ENTRY_CACHE to disable the in-memory
1449     entry caching. As of 2010-03-18 (r924708) merge_tests 34 and 134
1450     fail during "make check".  */
1451#ifdef SVN_DISABLE_ENTRY_CACHE
1452  return NULL;
1453#else
1454  return adm_access->entries_all;
1455#endif
1456}
1457
1458
1459svn_wc__db_t *
1460svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access)
1461{
1462  return adm_access->db;
1463}
1464
1465svn_error_t *
1466svn_wc__acquire_write_lock(const char **lock_root_abspath,
1467                           svn_wc_context_t *wc_ctx,
1468                           const char *local_abspath,
1469                           svn_boolean_t lock_anchor,
1470                           apr_pool_t *result_pool,
1471                           apr_pool_t *scratch_pool)
1472{
1473  svn_wc__db_t *db = wc_ctx->db;
1474  svn_boolean_t is_wcroot;
1475  svn_boolean_t is_switched;
1476  svn_node_kind_t kind;
1477  svn_error_t *err;
1478
1479  err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind,
1480                               db, local_abspath, scratch_pool);
1481
1482  if (err)
1483    {
1484      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1485        return svn_error_trace(err);
1486
1487      svn_error_clear(err);
1488
1489      kind = svn_node_none;
1490      is_wcroot = FALSE;
1491      is_switched = FALSE;
1492    }
1493
1494  if (!lock_root_abspath && kind != svn_node_dir)
1495    return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
1496                             _("Can't obtain lock on non-directory '%s'."),
1497                             svn_dirent_local_style(local_abspath,
1498                                                    scratch_pool));
1499
1500  if (lock_anchor && kind == svn_node_dir)
1501    {
1502      if (is_wcroot)
1503        lock_anchor = FALSE;
1504    }
1505
1506  if (lock_anchor)
1507    {
1508      const char *parent_abspath;
1509      SVN_ERR_ASSERT(lock_root_abspath != NULL);
1510
1511      parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
1512
1513      if (kind == svn_node_dir)
1514        {
1515          if (! is_switched)
1516            local_abspath = parent_abspath;
1517        }
1518      else if (kind != svn_node_none && kind != svn_node_unknown)
1519        {
1520          /* In the single-DB world we know parent exists */
1521          local_abspath = parent_abspath;
1522        }
1523      else
1524        {
1525          /* Can't lock parents that don't exist */
1526          svn_node_kind_t parent_kind;
1527          err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath,
1528                                     TRUE /* allow_missing */,
1529                                     TRUE /* show_deleted */,
1530                                     FALSE /* show_hidden */,
1531                                     scratch_pool);
1532          if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
1533            {
1534              svn_error_clear(err);
1535              parent_kind = svn_node_unknown;
1536            }
1537          else
1538            SVN_ERR(err);
1539
1540          if (parent_kind != svn_node_dir)
1541            return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1542                                     _("'%s' is not a working copy"),
1543                                     svn_dirent_local_style(local_abspath,
1544                                                            scratch_pool));
1545
1546          local_abspath = parent_abspath;
1547        }
1548    }
1549  else if (kind != svn_node_dir)
1550    {
1551      local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
1552    }
1553
1554  if (lock_root_abspath)
1555    *lock_root_abspath = apr_pstrdup(result_pool, local_abspath);
1556
1557  SVN_ERR(svn_wc__db_wclock_obtain(wc_ctx->db, local_abspath,
1558                                   -1 /* levels_to_lock (infinite) */,
1559                                   FALSE /* steal_lock */,
1560                                   scratch_pool));
1561
1562  return SVN_NO_ERROR;
1563}
1564
1565
1566svn_error_t *
1567svn_wc__release_write_lock(svn_wc_context_t *wc_ctx,
1568                           const char *local_abspath,
1569                           apr_pool_t *scratch_pool)
1570{
1571  apr_uint64_t id;
1572  svn_skel_t *work_item;
1573
1574  SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, wc_ctx->db, local_abspath,
1575                                   0, scratch_pool, scratch_pool));
1576  if (work_item)
1577    {
1578      /* Do not release locks (here or below) if there is work to do.  */
1579      return SVN_NO_ERROR;
1580    }
1581
1582  SVN_ERR(svn_wc__db_wclock_release(wc_ctx->db, local_abspath, scratch_pool));
1583
1584  return SVN_NO_ERROR;
1585}
1586
1587svn_error_t *
1588svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func,
1589                             void *baton,
1590                             svn_wc_context_t *wc_ctx,
1591                             const char *local_abspath,
1592                             svn_boolean_t lock_anchor,
1593                             apr_pool_t *result_pool,
1594                             apr_pool_t *scratch_pool)
1595{
1596  svn_error_t *err1, *err2;
1597  const char *lock_root_abspath;
1598
1599  SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, wc_ctx, local_abspath,
1600                                     lock_anchor, scratch_pool, scratch_pool));
1601  err1 = svn_error_trace(func(baton, result_pool, scratch_pool));
1602  err2 = svn_wc__release_write_lock(wc_ctx, lock_root_abspath, scratch_pool);
1603  return svn_error_compose_create(err1, err2);
1604}
1605
1606
1607svn_error_t *
1608svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath,
1609                                       svn_wc_context_t *wc_ctx,
1610                                       const char *local_abspath,
1611                                       apr_pool_t *result_pool,
1612                                       apr_pool_t *scratch_pool)
1613{
1614  svn_boolean_t locked = FALSE;
1615  const char *obtained_abspath;
1616  const char *requested_abspath = local_abspath;
1617
1618  while (!locked)
1619    {
1620      const char *required_abspath;
1621      const char *child;
1622
1623      SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx,
1624                                         requested_abspath, FALSE,
1625                                         scratch_pool, scratch_pool));
1626      locked = TRUE;
1627
1628      SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath,
1629                                                wc_ctx->db, local_abspath,
1630                                                scratch_pool, scratch_pool));
1631
1632      /* It's possible for the required lock path to be an ancestor
1633         of, a descendant of, or equal to, the obtained lock path. If
1634         it's an ancestor we have to try again, otherwise the obtained
1635         lock will do. */
1636      child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath);
1637      if (child && child[0])
1638        {
1639          SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath,
1640                                             scratch_pool));
1641          locked = FALSE;
1642          requested_abspath = required_abspath;
1643        }
1644      else
1645        {
1646          /* required should be a descendant of, or equal to, obtained */
1647          SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath)
1648                         || svn_dirent_skip_ancestor(obtained_abspath,
1649                                                     required_abspath));
1650        }
1651    }
1652
1653  *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath);
1654
1655  return SVN_NO_ERROR;
1656}
1657