1251881Speter/* lock.c :  functions for manipulating filesystem locks.
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#include "svn_pools.h"
24251881Speter#include "svn_error.h"
25251881Speter#include "svn_dirent_uri.h"
26251881Speter#include "svn_path.h"
27251881Speter#include "svn_fs.h"
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_time.h"
30251881Speter#include "svn_utf.h"
31251881Speter
32251881Speter#include <apr_uuid.h>
33251881Speter#include <apr_file_io.h>
34251881Speter#include <apr_file_info.h>
35251881Speter
36251881Speter#include "lock.h"
37251881Speter#include "tree.h"
38251881Speter#include "fs_fs.h"
39299742Sdim#include "util.h"
40251881Speter#include "../libsvn_fs/fs-loader.h"
41251881Speter
42251881Speter#include "private/svn_fs_util.h"
43251881Speter#include "private/svn_fspath.h"
44299742Sdim#include "private/svn_sorts_private.h"
45251881Speter#include "svn_private_config.h"
46251881Speter
47251881Speter/* Names of hash keys used to store a lock for writing to disk. */
48251881Speter#define PATH_KEY "path"
49251881Speter#define TOKEN_KEY "token"
50251881Speter#define OWNER_KEY "owner"
51251881Speter#define CREATION_DATE_KEY "creation_date"
52251881Speter#define EXPIRATION_DATE_KEY "expiration_date"
53251881Speter#define COMMENT_KEY "comment"
54251881Speter#define IS_DAV_COMMENT_KEY "is_dav_comment"
55251881Speter#define CHILDREN_KEY "children"
56251881Speter
57251881Speter/* Number of characters from the head of a digest file name used to
58251881Speter   calculate a subdirectory in which to drop that file. */
59251881Speter#define DIGEST_SUBDIR_LEN 3
60251881Speter
61251881Speter
62251881Speter
63251881Speter/*** Generic helper functions. ***/
64251881Speter
65251881Speter/* Set *DIGEST to the MD5 hash of STR. */
66251881Speterstatic svn_error_t *
67251881Spetermake_digest(const char **digest,
68251881Speter            const char *str,
69251881Speter            apr_pool_t *pool)
70251881Speter{
71251881Speter  svn_checksum_t *checksum;
72251881Speter
73251881Speter  SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
74251881Speter
75251881Speter  *digest = svn_checksum_to_cstring_display(checksum, pool);
76251881Speter  return SVN_NO_ERROR;
77251881Speter}
78251881Speter
79251881Speter
80251881Speter/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
81251881Speter   if unknown) to an svn_string_t-ized version of VALUE (whose size is
82251881Speter   VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
83251881Speter   will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
84251881Speter   is NULL, this function will do nothing. */
85251881Speterstatic void
86251881Speterhash_store(apr_hash_t *hash,
87251881Speter           const char *key,
88251881Speter           apr_ssize_t key_len,
89251881Speter           const char *value,
90251881Speter           apr_ssize_t value_len,
91251881Speter           apr_pool_t *pool)
92251881Speter{
93251881Speter  if (! (key && value))
94251881Speter    return;
95251881Speter  if (value_len == APR_HASH_KEY_STRING)
96251881Speter    value_len = strlen(value);
97251881Speter  apr_hash_set(hash, key, key_len,
98251881Speter               svn_string_ncreate(value, value_len, pool));
99251881Speter}
100251881Speter
101251881Speter
102251881Speter/* Fetch the value of KEY from HASH, returning only the cstring data
103251881Speter   of that value (if it exists). */
104251881Speterstatic const char *
105251881Speterhash_fetch(apr_hash_t *hash,
106299742Sdim           const char *key)
107251881Speter{
108251881Speter  svn_string_t *str = svn_hash_gets(hash, key);
109251881Speter  return str ? str->data : NULL;
110251881Speter}
111251881Speter
112251881Speter
113251881Speter/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt.  */
114251881Speterstatic svn_error_t *
115251881Spetererr_corrupt_lockfile(const char *fs_path, const char *path)
116251881Speter{
117251881Speter  return
118251881Speter    svn_error_createf(
119251881Speter     SVN_ERR_FS_CORRUPT, 0,
120251881Speter     _("Corrupt lockfile for path '%s' in filesystem '%s'"),
121251881Speter     path, fs_path);
122251881Speter}
123251881Speter
124251881Speter
125251881Speter/*** Digest file handling functions. ***/
126251881Speter
127251881Speter/* Return the path of the lock/entries file for which DIGEST is the
128251881Speter   hashed repository relative path. */
129251881Speterstatic const char *
130251881Speterdigest_path_from_digest(const char *fs_path,
131251881Speter                        const char *digest,
132251881Speter                        apr_pool_t *pool)
133251881Speter{
134251881Speter  return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
135251881Speter                              apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
136299742Sdim                              digest, SVN_VA_NULL);
137251881Speter}
138251881Speter
139251881Speter
140251881Speter/* Set *DIGEST_PATH to the path to the lock/entries digest file associate
141251881Speter   with PATH, where PATH is the path to the lock file or lock entries file
142251881Speter   in FS. */
143251881Speterstatic svn_error_t *
144251881Speterdigest_path_from_path(const char **digest_path,
145251881Speter                      const char *fs_path,
146251881Speter                      const char *path,
147251881Speter                      apr_pool_t *pool)
148251881Speter{
149251881Speter  const char *digest;
150251881Speter  SVN_ERR(make_digest(&digest, path, pool));
151251881Speter  *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
152251881Speter                                      apr_pstrmemdup(pool, digest,
153251881Speter                                                     DIGEST_SUBDIR_LEN),
154299742Sdim                                      digest, SVN_VA_NULL);
155251881Speter  return SVN_NO_ERROR;
156251881Speter}
157251881Speter
158251881Speter
159251881Speter/* Write to DIGEST_PATH a representation of CHILDREN (which may be
160251881Speter   empty, if the versioned path in FS represented by DIGEST_PATH has
161251881Speter   no children) and LOCK (which may be NULL if that versioned path is
162251881Speter   lock itself locked).  Set the permissions of DIGEST_PATH to those of
163251881Speter   PERMS_REFERENCE.  Use POOL for all allocations.
164251881Speter */
165251881Speterstatic svn_error_t *
166251881Speterwrite_digest_file(apr_hash_t *children,
167251881Speter                  svn_lock_t *lock,
168251881Speter                  const char *fs_path,
169251881Speter                  const char *digest_path,
170251881Speter                  const char *perms_reference,
171251881Speter                  apr_pool_t *pool)
172251881Speter{
173251881Speter  svn_error_t *err = SVN_NO_ERROR;
174251881Speter  svn_stream_t *stream;
175251881Speter  apr_hash_index_t *hi;
176251881Speter  apr_hash_t *hash = apr_hash_make(pool);
177251881Speter  const char *tmp_path;
178251881Speter
179251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
180251881Speter                                                       pool), fs_path, pool));
181251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
182251881Speter                                       fs_path, pool));
183251881Speter
184251881Speter  if (lock)
185251881Speter    {
186251881Speter      const char *creation_date = NULL, *expiration_date = NULL;
187251881Speter      if (lock->creation_date)
188251881Speter        creation_date = svn_time_to_cstring(lock->creation_date, pool);
189251881Speter      if (lock->expiration_date)
190251881Speter        expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
191251881Speter      hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
192251881Speter                 lock->path, APR_HASH_KEY_STRING, pool);
193251881Speter      hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
194251881Speter                 lock->token, APR_HASH_KEY_STRING, pool);
195251881Speter      hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
196251881Speter                 lock->owner, APR_HASH_KEY_STRING, pool);
197251881Speter      hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
198251881Speter                 lock->comment, APR_HASH_KEY_STRING, pool);
199251881Speter      hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
200251881Speter                 lock->is_dav_comment ? "1" : "0", 1, pool);
201251881Speter      hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
202251881Speter                 creation_date, APR_HASH_KEY_STRING, pool);
203251881Speter      hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
204251881Speter                 expiration_date, APR_HASH_KEY_STRING, pool);
205251881Speter    }
206251881Speter  if (apr_hash_count(children))
207251881Speter    {
208251881Speter      svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
209251881Speter      for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
210251881Speter        {
211251881Speter          svn_stringbuf_appendbytes(children_list,
212299742Sdim                                    apr_hash_this_key(hi),
213299742Sdim                                    apr_hash_this_key_len(hi));
214251881Speter          svn_stringbuf_appendbyte(children_list, '\n');
215251881Speter        }
216251881Speter      hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
217251881Speter                 children_list->data, children_list->len, pool);
218251881Speter    }
219251881Speter
220251881Speter  SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
221251881Speter                                 svn_dirent_dirname(digest_path, pool),
222251881Speter                                 svn_io_file_del_none, pool, pool));
223251881Speter  if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
224251881Speter    {
225251881Speter      svn_error_clear(svn_stream_close(stream));
226251881Speter      return svn_error_createf(err->apr_err,
227251881Speter                               err,
228251881Speter                               _("Cannot write lock/entries hashfile '%s'"),
229251881Speter                               svn_dirent_local_style(tmp_path, pool));
230251881Speter    }
231251881Speter
232251881Speter  SVN_ERR(svn_stream_close(stream));
233251881Speter  SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
234251881Speter  SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
235251881Speter  return SVN_NO_ERROR;
236251881Speter}
237251881Speter
238251881Speter
239251881Speter/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
240251881Speter   file (if it exists, and if *LOCK_P is non-NULL) and the hash of
241251881Speter   CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
242251881Speter   for all allocations.  */
243251881Speterstatic svn_error_t *
244251881Speterread_digest_file(apr_hash_t **children_p,
245251881Speter                 svn_lock_t **lock_p,
246251881Speter                 const char *fs_path,
247251881Speter                 const char *digest_path,
248251881Speter                 apr_pool_t *pool)
249251881Speter{
250251881Speter  svn_error_t *err = SVN_NO_ERROR;
251251881Speter  svn_lock_t *lock;
252251881Speter  apr_hash_t *hash;
253251881Speter  svn_stream_t *stream;
254251881Speter  const char *val;
255299742Sdim  svn_node_kind_t kind;
256251881Speter
257251881Speter  if (lock_p)
258251881Speter    *lock_p = NULL;
259251881Speter  if (children_p)
260251881Speter    *children_p = apr_hash_make(pool);
261251881Speter
262299742Sdim  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
263299742Sdim  if (kind == svn_node_none)
264299742Sdim    return SVN_NO_ERROR;
265251881Speter
266251881Speter  /* If our caller doesn't care about anything but the presence of the
267251881Speter     file... whatever. */
268299742Sdim  if (kind == svn_node_file && !lock_p && !children_p)
269299742Sdim    return SVN_NO_ERROR;
270251881Speter
271299742Sdim  SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
272299742Sdim
273251881Speter  hash = apr_hash_make(pool);
274251881Speter  if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
275251881Speter    {
276251881Speter      svn_error_clear(svn_stream_close(stream));
277251881Speter      return svn_error_createf(err->apr_err,
278251881Speter                               err,
279251881Speter                               _("Can't parse lock/entries hashfile '%s'"),
280251881Speter                               svn_dirent_local_style(digest_path, pool));
281251881Speter    }
282251881Speter  SVN_ERR(svn_stream_close(stream));
283251881Speter
284251881Speter  /* If our caller cares, see if we have a lock path in our hash. If
285251881Speter     so, we'll assume we have a lock here. */
286299742Sdim  val = hash_fetch(hash, PATH_KEY);
287251881Speter  if (val && lock_p)
288251881Speter    {
289251881Speter      const char *path = val;
290251881Speter
291251881Speter      /* Create our lock and load it up. */
292251881Speter      lock = svn_lock_create(pool);
293251881Speter      lock->path = path;
294251881Speter
295299742Sdim      if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
296251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
297251881Speter
298299742Sdim      if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
299251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
300251881Speter
301299742Sdim      if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY))))
302251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
303251881Speter      lock->is_dav_comment = (val[0] == '1');
304251881Speter
305299742Sdim      if (! ((val = hash_fetch(hash, CREATION_DATE_KEY))))
306251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
307251881Speter      SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
308251881Speter
309299742Sdim      if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
310251881Speter        SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
311251881Speter
312299742Sdim      lock->comment = hash_fetch(hash, COMMENT_KEY);
313251881Speter
314251881Speter      *lock_p = lock;
315251881Speter    }
316251881Speter
317251881Speter  /* If our caller cares, see if we have any children for this path. */
318299742Sdim  val = hash_fetch(hash, CHILDREN_KEY);
319251881Speter  if (val && children_p)
320251881Speter    {
321251881Speter      apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
322251881Speter      int i;
323251881Speter
324251881Speter      for (i = 0; i < kiddos->nelts; i++)
325251881Speter        {
326251881Speter          svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
327251881Speter                        (void *)1);
328251881Speter        }
329251881Speter    }
330251881Speter  return SVN_NO_ERROR;
331251881Speter}
332251881Speter
333251881Speter
334251881Speter
335251881Speter/*** Lock helper functions (path here are still FS paths, not on-disk
336251881Speter     schema-supporting paths) ***/
337251881Speter
338251881Speter
339251881Speter/* Write LOCK in FS to the actual OS filesystem.
340251881Speter
341251881Speter   Use PERMS_REFERENCE for the permissions of any digest files.
342251881Speter */
343251881Speterstatic svn_error_t *
344251881Speterset_lock(const char *fs_path,
345251881Speter         svn_lock_t *lock,
346251881Speter         const char *perms_reference,
347251881Speter         apr_pool_t *pool)
348251881Speter{
349299742Sdim  const char *digest_path;
350299742Sdim  apr_hash_t *children;
351251881Speter
352299742Sdim  SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
353251881Speter
354299742Sdim  /* We could get away without reading the file as children should
355299742Sdim     always come back empty. */
356299742Sdim  SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
357251881Speter
358299742Sdim  SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
359299742Sdim                            perms_reference, pool));
360251881Speter
361299742Sdim  return SVN_NO_ERROR;
362299742Sdim}
363251881Speter
364299742Sdimstatic svn_error_t *
365299742Sdimdelete_lock(const char *fs_path,
366299742Sdim            const char *path,
367299742Sdim            apr_pool_t *pool)
368299742Sdim{
369299742Sdim  const char *digest_path;
370251881Speter
371299742Sdim  SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
372251881Speter
373299742Sdim  SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
374251881Speter
375251881Speter  return SVN_NO_ERROR;
376251881Speter}
377251881Speter
378251881Speterstatic svn_error_t *
379299742Sdimadd_to_digest(const char *fs_path,
380299742Sdim              apr_array_header_t *paths,
381299742Sdim              const char *index_path,
382299742Sdim              const char *perms_reference,
383299742Sdim              apr_pool_t *pool)
384251881Speter{
385299742Sdim  const char *index_digest_path;
386299742Sdim  apr_hash_t *children;
387299742Sdim  svn_lock_t *lock;
388299742Sdim  int i;
389299742Sdim  unsigned int original_count;
390251881Speter
391299742Sdim  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
392251881Speter
393299742Sdim  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
394299742Sdim
395299742Sdim  original_count = apr_hash_count(children);
396299742Sdim
397299742Sdim  for (i = 0; i < paths->nelts; ++i)
398251881Speter    {
399299742Sdim      const char *path = APR_ARRAY_IDX(paths, i, const char *);
400251881Speter      const char *digest_path, *digest_file;
401251881Speter
402299742Sdim      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
403299742Sdim      digest_file = svn_dirent_basename(digest_path, NULL);
404299742Sdim      svn_hash_sets(children, digest_file, (void *)1);
405299742Sdim    }
406251881Speter
407299742Sdim  if (apr_hash_count(children) != original_count)
408299742Sdim    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
409299742Sdim                              perms_reference, pool));
410251881Speter
411299742Sdim  return SVN_NO_ERROR;
412299742Sdim}
413251881Speter
414299742Sdimstatic svn_error_t *
415299742Sdimdelete_from_digest(const char *fs_path,
416299742Sdim                   apr_array_header_t *paths,
417299742Sdim                   const char *index_path,
418299742Sdim                   const char *perms_reference,
419299742Sdim                   apr_pool_t *pool)
420299742Sdim{
421299742Sdim  const char *index_digest_path;
422299742Sdim  apr_hash_t *children;
423299742Sdim  svn_lock_t *lock;
424299742Sdim  int i;
425251881Speter
426299742Sdim  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
427251881Speter
428299742Sdim  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
429251881Speter
430299742Sdim  for (i = 0; i < paths->nelts; ++i)
431299742Sdim    {
432299742Sdim      const char *path = APR_ARRAY_IDX(paths, i, const char *);
433299742Sdim      const char *digest_path, *digest_file;
434299742Sdim
435299742Sdim      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
436299742Sdim      digest_file = svn_dirent_basename(digest_path, NULL);
437299742Sdim      svn_hash_sets(children, digest_file, NULL);
438251881Speter    }
439251881Speter
440299742Sdim  if (apr_hash_count(children) || lock)
441299742Sdim    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
442299742Sdim                              perms_reference, pool));
443299742Sdim  else
444299742Sdim    SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
445299742Sdim
446251881Speter  return SVN_NO_ERROR;
447251881Speter}
448251881Speter
449299742Sdimstatic svn_error_t *
450299742Sdimunlock_single(svn_fs_t *fs,
451299742Sdim              svn_lock_t *lock,
452299742Sdim              apr_pool_t *pool);
453299742Sdim
454299742Sdim/* Check if LOCK has been already expired. */
455299742Sdimstatic svn_boolean_t lock_expired(const svn_lock_t *lock)
456299742Sdim{
457299742Sdim  return lock->expiration_date && (apr_time_now() > lock->expiration_date);
458299742Sdim}
459299742Sdim
460251881Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
461251881Speter   TRUE if the caller (or one of its callers) has taken out the
462251881Speter   repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
463251881Speter   not set, the function will simply return NULL in *LOCK_P instead
464251881Speter   of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
465251881Speter   was not found (much faster).  Use POOL for allocations. */
466251881Speterstatic svn_error_t *
467251881Speterget_lock(svn_lock_t **lock_p,
468251881Speter         svn_fs_t *fs,
469251881Speter         const char *path,
470251881Speter         svn_boolean_t have_write_lock,
471251881Speter         svn_boolean_t must_exist,
472251881Speter         apr_pool_t *pool)
473251881Speter{
474251881Speter  svn_lock_t *lock = NULL;
475251881Speter  const char *digest_path;
476251881Speter  svn_node_kind_t kind;
477251881Speter
478251881Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
479251881Speter  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
480251881Speter
481251881Speter  *lock_p = NULL;
482251881Speter  if (kind != svn_node_none)
483251881Speter    SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
484251881Speter
485251881Speter  if (! lock)
486251881Speter    return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
487251881Speter
488251881Speter  /* Don't return an expired lock. */
489299742Sdim  if (lock_expired(lock))
490251881Speter    {
491251881Speter      /* Only remove the lock if we have the write lock.
492251881Speter         Read operations shouldn't change the filesystem. */
493251881Speter      if (have_write_lock)
494299742Sdim        SVN_ERR(unlock_single(fs, lock, pool));
495251881Speter      return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
496251881Speter    }
497251881Speter
498251881Speter  *lock_p = lock;
499251881Speter  return SVN_NO_ERROR;
500251881Speter}
501251881Speter
502251881Speter
503251881Speter/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
504251881Speter   TRUE if the caller (or one of its callers) has taken out the
505251881Speter   repository-wide write lock, FALSE otherwise.  Use POOL for
506251881Speter   allocations. */
507251881Speterstatic svn_error_t *
508251881Speterget_lock_helper(svn_fs_t *fs,
509251881Speter                svn_lock_t **lock_p,
510251881Speter                const char *path,
511251881Speter                svn_boolean_t have_write_lock,
512251881Speter                apr_pool_t *pool)
513251881Speter{
514251881Speter  svn_lock_t *lock;
515251881Speter  svn_error_t *err;
516251881Speter
517251881Speter  err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
518251881Speter
519251881Speter  /* We've deliberately decided that this function doesn't tell the
520251881Speter     caller *why* the lock is unavailable.  */
521251881Speter  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
522251881Speter              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
523251881Speter    {
524251881Speter      svn_error_clear(err);
525251881Speter      *lock_p = NULL;
526251881Speter      return SVN_NO_ERROR;
527251881Speter    }
528251881Speter  else
529251881Speter    SVN_ERR(err);
530251881Speter
531251881Speter  *lock_p = lock;
532251881Speter  return SVN_NO_ERROR;
533251881Speter}
534251881Speter
535251881Speter
536299742Sdim/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
537299742Sdim   all locks in and under PATH in FS.
538251881Speter   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
539251881Speter   has the FS write lock. */
540251881Speterstatic svn_error_t *
541299742Sdimwalk_locks(svn_fs_t *fs,
542299742Sdim           const char *digest_path,
543299742Sdim           svn_fs_get_locks_callback_t get_locks_func,
544299742Sdim           void *get_locks_baton,
545299742Sdim           svn_boolean_t have_write_lock,
546299742Sdim           apr_pool_t *pool)
547251881Speter{
548251881Speter  apr_hash_index_t *hi;
549251881Speter  apr_hash_t *children;
550251881Speter  apr_pool_t *subpool;
551251881Speter  svn_lock_t *lock;
552251881Speter
553251881Speter  /* First, send up any locks in the current digest file. */
554299742Sdim  SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
555251881Speter
556299742Sdim  if (lock && lock_expired(lock))
557299742Sdim    {
558299742Sdim      /* Only remove the lock if we have the write lock.
559299742Sdim         Read operations shouldn't change the filesystem. */
560299742Sdim      if (have_write_lock)
561299742Sdim        SVN_ERR(unlock_single(fs, lock, pool));
562299742Sdim    }
563299742Sdim  else if (lock)
564299742Sdim    {
565299742Sdim      SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
566299742Sdim    }
567251881Speter
568299742Sdim  /* Now, report all the child entries (if any; bail otherwise). */
569251881Speter  if (! apr_hash_count(children))
570251881Speter    return SVN_NO_ERROR;
571251881Speter  subpool = svn_pool_create(pool);
572251881Speter  for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
573251881Speter    {
574299742Sdim      const char *digest = apr_hash_this_key(hi);
575251881Speter      svn_pool_clear(subpool);
576299742Sdim
577299742Sdim      SVN_ERR(read_digest_file
578299742Sdim              (NULL, &lock, fs->path,
579299742Sdim               digest_path_from_digest(fs->path, digest, subpool), subpool));
580299742Sdim
581299742Sdim      if (lock && lock_expired(lock))
582299742Sdim        {
583299742Sdim          /* Only remove the lock if we have the write lock.
584299742Sdim             Read operations shouldn't change the filesystem. */
585299742Sdim          if (have_write_lock)
586299742Sdim            SVN_ERR(unlock_single(fs, lock, pool));
587299742Sdim        }
588299742Sdim      else if (lock)
589299742Sdim        {
590299742Sdim          SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
591299742Sdim        }
592251881Speter    }
593251881Speter  svn_pool_destroy(subpool);
594251881Speter  return SVN_NO_ERROR;
595251881Speter}
596251881Speter
597251881Speter
598251881Speter/* Utility function:  verify that a lock can be used.  Interesting
599251881Speter   errors returned from this function:
600251881Speter
601251881Speter      SVN_ERR_FS_NO_USER: No username attached to FS.
602251881Speter      SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
603251881Speter      SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
604251881Speter */
605251881Speterstatic svn_error_t *
606251881Speterverify_lock(svn_fs_t *fs,
607251881Speter            svn_lock_t *lock,
608251881Speter            apr_pool_t *pool)
609251881Speter{
610251881Speter  if ((! fs->access_ctx) || (! fs->access_ctx->username))
611251881Speter    return svn_error_createf
612251881Speter      (SVN_ERR_FS_NO_USER, NULL,
613251881Speter       _("Cannot verify lock on path '%s'; no username available"),
614251881Speter       lock->path);
615251881Speter
616251881Speter  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
617251881Speter    return svn_error_createf
618251881Speter      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
619251881Speter       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
620251881Speter       fs->access_ctx->username, lock->path, lock->owner);
621251881Speter
622251881Speter  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
623251881Speter    return svn_error_createf
624251881Speter      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
625251881Speter       _("Cannot verify lock on path '%s'; no matching lock-token available"),
626251881Speter       lock->path);
627251881Speter
628251881Speter  return SVN_NO_ERROR;
629251881Speter}
630251881Speter
631251881Speter
632251881Speter/* This implements the svn_fs_get_locks_callback_t interface, where
633251881Speter   BATON is just an svn_fs_t object. */
634251881Speterstatic svn_error_t *
635251881Speterget_locks_callback(void *baton,
636251881Speter                   svn_lock_t *lock,
637251881Speter                   apr_pool_t *pool)
638251881Speter{
639251881Speter  return verify_lock(baton, lock, pool);
640251881Speter}
641251881Speter
642251881Speter
643251881Speter/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
644251881Spetersvn_error_t *
645251881Spetersvn_fs_fs__allow_locked_operation(const char *path,
646251881Speter                                  svn_fs_t *fs,
647251881Speter                                  svn_boolean_t recurse,
648251881Speter                                  svn_boolean_t have_write_lock,
649251881Speter                                  apr_pool_t *pool)
650251881Speter{
651251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
652251881Speter  if (recurse)
653251881Speter    {
654251881Speter      /* Discover all locks at or below the path. */
655251881Speter      const char *digest_path;
656251881Speter      SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
657251881Speter      SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
658251881Speter                         fs, have_write_lock, pool));
659251881Speter    }
660251881Speter  else
661251881Speter    {
662251881Speter      /* Discover and verify any lock attached to the path. */
663251881Speter      svn_lock_t *lock;
664251881Speter      SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
665251881Speter      if (lock)
666251881Speter        SVN_ERR(verify_lock(fs, lock, pool));
667251881Speter    }
668251881Speter  return SVN_NO_ERROR;
669251881Speter}
670251881Speter
671299742Sdim/* Helper function called from the lock and unlock code.
672299742Sdim   UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
673299742Sdim   arrays of child paths.  For all of the parent paths of PATH this function
674299742Sdim   adds PATH to the corresponding array of child paths. */
675299742Sdimstatic void
676299742Sdimschedule_index_update(apr_hash_t *updates,
677299742Sdim                      const char *path,
678299742Sdim                      apr_pool_t *scratch_pool)
679299742Sdim{
680299742Sdim  apr_pool_t *hashpool = apr_hash_pool_get(updates);
681299742Sdim  const char *parent_path = path;
682299742Sdim
683299742Sdim  while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
684299742Sdim    {
685299742Sdim      apr_array_header_t *children;
686299742Sdim
687299742Sdim      parent_path = svn_fspath__dirname(parent_path, scratch_pool);
688299742Sdim      children = svn_hash_gets(updates, parent_path);
689299742Sdim
690299742Sdim      if (! children)
691299742Sdim        {
692299742Sdim          children = apr_array_make(hashpool, 8, sizeof(const char *));
693299742Sdim          svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
694299742Sdim        }
695299742Sdim
696299742Sdim      APR_ARRAY_PUSH(children, const char *) = path;
697299742Sdim    }
698299742Sdim}
699299742Sdim
700299742Sdim/* The effective arguments for lock_body() below. */
701251881Speterstruct lock_baton {
702251881Speter  svn_fs_t *fs;
703299742Sdim  apr_array_header_t *targets;
704299742Sdim  apr_array_header_t *infos;
705251881Speter  const char *comment;
706251881Speter  svn_boolean_t is_dav_comment;
707251881Speter  apr_time_t expiration_date;
708251881Speter  svn_boolean_t steal_lock;
709299742Sdim  apr_pool_t *result_pool;
710251881Speter};
711251881Speter
712251881Speterstatic svn_error_t *
713299742Sdimcheck_lock(svn_error_t **fs_err,
714299742Sdim           const char *path,
715299742Sdim           const svn_fs_lock_target_t *target,
716299742Sdim           struct lock_baton *lb,
717299742Sdim           svn_fs_root_t *root,
718299742Sdim           svn_revnum_t youngest_rev,
719299742Sdim           apr_pool_t *pool)
720251881Speter{
721251881Speter  svn_node_kind_t kind;
722251881Speter  svn_lock_t *existing_lock;
723251881Speter
724299742Sdim  *fs_err = SVN_NO_ERROR;
725299742Sdim
726299742Sdim  SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
727251881Speter  if (kind == svn_node_dir)
728299742Sdim    {
729299742Sdim      *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
730299742Sdim      return SVN_NO_ERROR;
731299742Sdim    }
732251881Speter
733251881Speter  /* While our locking implementation easily supports the locking of
734251881Speter     nonexistent paths, we deliberately choose not to allow such madness. */
735251881Speter  if (kind == svn_node_none)
736251881Speter    {
737299742Sdim      if (SVN_IS_VALID_REVNUM(target->current_rev))
738299742Sdim        *fs_err = svn_error_createf(
739251881Speter          SVN_ERR_FS_OUT_OF_DATE, NULL,
740251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
741299742Sdim          path);
742251881Speter      else
743299742Sdim        *fs_err = svn_error_createf(
744251881Speter          SVN_ERR_FS_NOT_FOUND, NULL,
745251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
746299742Sdim          path);
747299742Sdim
748299742Sdim      return SVN_NO_ERROR;
749251881Speter    }
750251881Speter
751251881Speter  /* Is the caller attempting to lock an out-of-date working file? */
752299742Sdim  if (SVN_IS_VALID_REVNUM(target->current_rev))
753251881Speter    {
754251881Speter      svn_revnum_t created_rev;
755299742Sdim
756299742Sdim      if (target->current_rev > youngest_rev)
757299742Sdim        {
758299742Sdim          *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
759299742Sdim                                      _("No such revision %ld"),
760299742Sdim                                      target->current_rev);
761299742Sdim          return SVN_NO_ERROR;
762299742Sdim        }
763299742Sdim
764299742Sdim      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
765251881Speter                                          pool));
766251881Speter
767251881Speter      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
768251881Speter         apparently somebody is trying to lock something in their
769251881Speter         working copy, but somebody else has deleted the thing
770251881Speter         from HEAD.  That counts as being 'out of date'. */
771251881Speter      if (! SVN_IS_VALID_REVNUM(created_rev))
772299742Sdim        {
773299742Sdim          *fs_err = svn_error_createf
774299742Sdim            (SVN_ERR_FS_OUT_OF_DATE, NULL,
775299742Sdim             _("Path '%s' doesn't exist in HEAD revision"), path);
776251881Speter
777299742Sdim          return SVN_NO_ERROR;
778299742Sdim        }
779299742Sdim
780299742Sdim      if (target->current_rev < created_rev)
781299742Sdim        {
782299742Sdim          *fs_err = svn_error_createf
783299742Sdim            (SVN_ERR_FS_OUT_OF_DATE, NULL,
784299742Sdim             _("Lock failed: newer version of '%s' exists"), path);
785299742Sdim
786299742Sdim          return SVN_NO_ERROR;
787299742Sdim        }
788251881Speter    }
789251881Speter
790251881Speter  /* If the caller provided a TOKEN, we *really* need to see
791251881Speter     if a lock already exists with that token, and if so, verify that
792251881Speter     the lock's path matches PATH.  Otherwise we run the risk of
793251881Speter     breaking the 1-to-1 mapping of lock tokens to locked paths. */
794251881Speter  /* ### TODO:  actually do this check.  This is tough, because the
795251881Speter     schema doesn't supply a lookup-by-token mechanism. */
796251881Speter
797251881Speter  /* Is the path already locked?
798251881Speter
799251881Speter     Note that this next function call will automatically ignore any
800251881Speter     errors about {the path not existing as a key, the path's token
801251881Speter     not existing as a key, the lock just having been expired}.  And
802251881Speter     that's totally fine.  Any of these three errors are perfectly
803251881Speter     acceptable to ignore; it means that the path is now free and
804251881Speter     clear for locking, because the fsfs funcs just cleared out both
805251881Speter     of the tables for us.   */
806299742Sdim  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
807251881Speter  if (existing_lock)
808251881Speter    {
809251881Speter      if (! lb->steal_lock)
810251881Speter        {
811251881Speter          /* Sorry, the path is already locked. */
812299742Sdim          *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
813299742Sdim          return SVN_NO_ERROR;
814251881Speter        }
815299742Sdim    }
816299742Sdim
817299742Sdim  return SVN_NO_ERROR;
818299742Sdim}
819299742Sdim
820299742Sdimstruct lock_info_t {
821299742Sdim  const char *path;
822299742Sdim  svn_lock_t *lock;
823299742Sdim  svn_error_t *fs_err;
824299742Sdim};
825299742Sdim
826299742Sdim/* The body of svn_fs_fs__lock(), which see.
827299742Sdim
828299742Sdim   BATON is a 'struct lock_baton *' holding the effective arguments.
829299742Sdim   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
830299742Sdim   path, mapping canonical path to 'svn_fs_lock_target_t'.  Set
831299742Sdim   BATON->infos to an array of 'lock_info_t' holding the results.  For
832299742Sdim   the other arguments, see svn_fs_lock_many().
833299742Sdim
834299742Sdim   This implements the svn_fs_fs__with_write_lock() 'body' callback
835299742Sdim   type, and assumes that the write lock is held.
836299742Sdim */
837299742Sdimstatic svn_error_t *
838299742Sdimlock_body(void *baton, apr_pool_t *pool)
839299742Sdim{
840299742Sdim  struct lock_baton *lb = baton;
841299742Sdim  svn_fs_root_t *root;
842299742Sdim  svn_revnum_t youngest;
843299742Sdim  const char *rev_0_path;
844299742Sdim  int i;
845299742Sdim  apr_hash_t *index_updates = apr_hash_make(pool);
846299742Sdim  apr_hash_index_t *hi;
847299742Sdim  apr_pool_t *iterpool = svn_pool_create(pool);
848299742Sdim
849299742Sdim  /* Until we implement directory locks someday, we only allow locks
850299742Sdim     on files or non-existent paths. */
851299742Sdim  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
852299742Sdim     library dependencies, which are not portable. */
853299742Sdim  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
854299742Sdim  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
855299742Sdim
856299742Sdim  for (i = 0; i < lb->targets->nelts; ++i)
857299742Sdim    {
858299742Sdim      const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
859299742Sdim                                                    svn_sort__item_t);
860299742Sdim      struct lock_info_t info;
861299742Sdim
862299742Sdim      svn_pool_clear(iterpool);
863299742Sdim
864299742Sdim      info.path = item->key;
865299742Sdim      info.lock = NULL;
866299742Sdim      info.fs_err = SVN_NO_ERROR;
867299742Sdim
868299742Sdim      SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
869299742Sdim                         youngest, iterpool));
870299742Sdim
871299742Sdim      /* If no error occurred while pre-checking, schedule the index updates for
872299742Sdim         this path. */
873299742Sdim      if (!info.fs_err)
874299742Sdim        schedule_index_update(index_updates, info.path, iterpool);
875299742Sdim
876299742Sdim      APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
877299742Sdim    }
878299742Sdim
879299742Sdim  rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
880299742Sdim
881299742Sdim  /* We apply the scheduled index updates before writing the actual locks.
882299742Sdim
883299742Sdim     Writing indices before locks is correct: if interrupted it leaves
884299742Sdim     indices without locks rather than locks without indices.  An
885299742Sdim     index without a lock is consistent in that it always shows up as
886299742Sdim     unlocked in svn_fs_fs__allow_locked_operation.  A lock without an
887299742Sdim     index is inconsistent, svn_fs_fs__allow_locked_operation will
888299742Sdim     show locked on the file but unlocked on the parent. */
889299742Sdim
890299742Sdim  for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
891299742Sdim    {
892299742Sdim      const char *path = apr_hash_this_key(hi);
893299742Sdim      apr_array_header_t *children = apr_hash_this_val(hi);
894299742Sdim
895299742Sdim      svn_pool_clear(iterpool);
896299742Sdim      SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
897299742Sdim                            iterpool));
898299742Sdim    }
899299742Sdim
900299742Sdim  for (i = 0; i < lb->infos->nelts; ++i)
901299742Sdim    {
902299742Sdim      struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
903299742Sdim                                                struct lock_info_t);
904299742Sdim      svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
905299742Sdim      svn_fs_lock_target_t *target = item->value;
906299742Sdim
907299742Sdim      svn_pool_clear(iterpool);
908299742Sdim
909299742Sdim      if (! info->fs_err)
910251881Speter        {
911299742Sdim          info->lock = svn_lock_create(lb->result_pool);
912299742Sdim          if (target->token)
913299742Sdim            info->lock->token = apr_pstrdup(lb->result_pool, target->token);
914299742Sdim          else
915299742Sdim            SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
916299742Sdim                                                   lb->result_pool));
917299742Sdim
918299742Sdim          /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
919299742Sdim             of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
920299742Sdim          info->lock->path = info->path;
921299742Sdim          info->lock->owner = apr_pstrdup(lb->result_pool,
922299742Sdim                                          lb->fs->access_ctx->username);
923299742Sdim          info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
924299742Sdim          info->lock->is_dav_comment = lb->is_dav_comment;
925299742Sdim          info->lock->creation_date = apr_time_now();
926299742Sdim          info->lock->expiration_date = lb->expiration_date;
927299742Sdim
928299742Sdim          info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
929299742Sdim                                  iterpool);
930251881Speter        }
931251881Speter    }
932251881Speter
933299742Sdim  svn_pool_destroy(iterpool);
934251881Speter  return SVN_NO_ERROR;
935251881Speter}
936251881Speter
937299742Sdim/* The effective arguments for unlock_body() below. */
938251881Speterstruct unlock_baton {
939251881Speter  svn_fs_t *fs;
940299742Sdim  apr_array_header_t *targets;
941299742Sdim  apr_array_header_t *infos;
942299742Sdim  /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
943299742Sdim  svn_boolean_t skip_check;
944251881Speter  svn_boolean_t break_lock;
945299742Sdim  apr_pool_t *result_pool;
946251881Speter};
947251881Speter
948299742Sdimstatic svn_error_t *
949299742Sdimcheck_unlock(svn_error_t **fs_err,
950299742Sdim             const char *path,
951299742Sdim             const char *token,
952299742Sdim             struct unlock_baton *ub,
953299742Sdim             svn_fs_root_t *root,
954299742Sdim             apr_pool_t *pool)
955299742Sdim{
956299742Sdim  svn_lock_t *lock;
957299742Sdim
958299742Sdim  *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
959299742Sdim  if (!*fs_err && !ub->break_lock)
960299742Sdim    {
961299742Sdim      if (strcmp(token, lock->token) != 0)
962299742Sdim        *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
963299742Sdim      else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
964299742Sdim        *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
965299742Sdim                                                  ub->fs->access_ctx->username,
966299742Sdim                                                  lock->owner);
967299742Sdim    }
968299742Sdim
969299742Sdim  return SVN_NO_ERROR;
970299742Sdim}
971299742Sdim
972299742Sdimstruct unlock_info_t {
973299742Sdim  const char *path;
974299742Sdim  svn_error_t *fs_err;
975299742Sdim  svn_boolean_t done;
976299742Sdim};
977299742Sdim
978299742Sdim/* The body of svn_fs_fs__unlock(), which see.
979299742Sdim
980299742Sdim   BATON is a 'struct unlock_baton *' holding the effective arguments.
981299742Sdim   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
982299742Sdim   path, mapping canonical path to (const char *) token.  Set
983299742Sdim   BATON->infos to an array of 'unlock_info_t' results.  For the other
984299742Sdim   arguments, see svn_fs_unlock_many().
985299742Sdim
986299742Sdim   This implements the svn_fs_fs__with_write_lock() 'body' callback
987251881Speter   type, and assumes that the write lock is held.
988299742Sdim */
989251881Speterstatic svn_error_t *
990251881Speterunlock_body(void *baton, apr_pool_t *pool)
991251881Speter{
992251881Speter  struct unlock_baton *ub = baton;
993299742Sdim  svn_fs_root_t *root;
994299742Sdim  svn_revnum_t youngest;
995299742Sdim  const char *rev_0_path;
996299742Sdim  int i;
997299742Sdim  apr_hash_t *indices_updates = apr_hash_make(pool);
998299742Sdim  apr_hash_index_t *hi;
999299742Sdim  apr_pool_t *iterpool = svn_pool_create(pool);
1000251881Speter
1001299742Sdim  SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1002299742Sdim  SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1003251881Speter
1004299742Sdim  for (i = 0; i < ub->targets->nelts; ++i)
1005251881Speter    {
1006299742Sdim      const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1007299742Sdim                                                    svn_sort__item_t);
1008299742Sdim      const char *token = item->value;
1009299742Sdim      struct unlock_info_t info;
1010251881Speter
1011299742Sdim      svn_pool_clear(iterpool);
1012251881Speter
1013299742Sdim      info.path = item->key;
1014299742Sdim      info.fs_err = SVN_NO_ERROR;
1015299742Sdim      info.done = FALSE;
1016299742Sdim
1017299742Sdim      if (!ub->skip_check)
1018299742Sdim        SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1019299742Sdim                             iterpool));
1020299742Sdim
1021299742Sdim      /* If no error occurred while pre-checking, schedule the index updates for
1022299742Sdim         this path. */
1023299742Sdim      if (!info.fs_err)
1024299742Sdim        schedule_index_update(indices_updates, info.path, iterpool);
1025299742Sdim
1026299742Sdim      APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
1027251881Speter    }
1028251881Speter
1029299742Sdim  rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
1030299742Sdim
1031299742Sdim  /* Unlike the lock_body(), we need to delete locks *before* we start to
1032299742Sdim     update indices. */
1033299742Sdim
1034299742Sdim  for (i = 0; i < ub->infos->nelts; ++i)
1035299742Sdim    {
1036299742Sdim      struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1037299742Sdim                                                  struct unlock_info_t);
1038299742Sdim
1039299742Sdim      svn_pool_clear(iterpool);
1040299742Sdim
1041299742Sdim      if (! info->fs_err)
1042299742Sdim        {
1043299742Sdim          SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1044299742Sdim          info->done = TRUE;
1045299742Sdim        }
1046299742Sdim    }
1047299742Sdim
1048299742Sdim  for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1049299742Sdim    {
1050299742Sdim      const char *path = apr_hash_this_key(hi);
1051299742Sdim      apr_array_header_t *children = apr_hash_this_val(hi);
1052299742Sdim
1053299742Sdim      svn_pool_clear(iterpool);
1054299742Sdim      SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1055299742Sdim                                 iterpool));
1056299742Sdim    }
1057299742Sdim
1058299742Sdim  svn_pool_destroy(iterpool);
1059299742Sdim  return SVN_NO_ERROR;
1060251881Speter}
1061251881Speter
1062299742Sdim/* Unlock the lock described by LOCK->path and LOCK->token in FS.
1063299742Sdim
1064299742Sdim   This assumes that the write lock is held.
1065299742Sdim */
1066299742Sdimstatic svn_error_t *
1067299742Sdimunlock_single(svn_fs_t *fs,
1068299742Sdim              svn_lock_t *lock,
1069299742Sdim              apr_pool_t *pool)
1070299742Sdim{
1071299742Sdim  struct unlock_baton ub;
1072299742Sdim  svn_sort__item_t item;
1073299742Sdim  apr_array_header_t *targets = apr_array_make(pool, 1,
1074299742Sdim                                               sizeof(svn_sort__item_t));
1075299742Sdim  item.key = lock->path;
1076299742Sdim  item.klen = strlen(item.key);
1077299742Sdim  item.value = (char*)lock->token;
1078299742Sdim  APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1079299742Sdim
1080299742Sdim  ub.fs = fs;
1081299742Sdim  ub.targets = targets;
1082299742Sdim  ub.infos = apr_array_make(pool, targets->nelts,
1083299742Sdim                            sizeof(struct unlock_info_t));
1084299742Sdim  ub.skip_check = TRUE;
1085299742Sdim  ub.result_pool = pool;
1086299742Sdim
1087299742Sdim  /* No ub.infos[].fs_err error because skip_check is TRUE. */
1088299742Sdim  SVN_ERR(unlock_body(&ub, pool));
1089299742Sdim
1090299742Sdim  return SVN_NO_ERROR;
1091299742Sdim}
1092299742Sdim
1093251881Speter
1094251881Speter/*** Public API implementations ***/
1095251881Speter
1096251881Spetersvn_error_t *
1097299742Sdimsvn_fs_fs__lock(svn_fs_t *fs,
1098299742Sdim                apr_hash_t *targets,
1099251881Speter                const char *comment,
1100251881Speter                svn_boolean_t is_dav_comment,
1101251881Speter                apr_time_t expiration_date,
1102251881Speter                svn_boolean_t steal_lock,
1103299742Sdim                svn_fs_lock_callback_t lock_callback,
1104299742Sdim                void *lock_baton,
1105299742Sdim                apr_pool_t *result_pool,
1106299742Sdim                apr_pool_t *scratch_pool)
1107251881Speter{
1108251881Speter  struct lock_baton lb;
1109299742Sdim  apr_array_header_t *sorted_targets;
1110299742Sdim  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1111299742Sdim  apr_hash_index_t *hi;
1112299742Sdim  apr_pool_t *iterpool;
1113299742Sdim  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1114299742Sdim  int i;
1115251881Speter
1116251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1117251881Speter
1118299742Sdim  /* We need to have a username attached to the fs. */
1119299742Sdim  if (!fs->access_ctx || !fs->access_ctx->username)
1120299742Sdim    return SVN_FS__ERR_NO_USER(fs);
1121299742Sdim
1122299742Sdim  /* The FS locking API allows both canonical and non-canonical
1123299742Sdim     paths which means that the same canonical path could be
1124299742Sdim     represented more than once in the TARGETS hash.  We just keep
1125299742Sdim     one, choosing one with a token if possible. */
1126299742Sdim  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1127299742Sdim    {
1128299742Sdim      const char *path = apr_hash_this_key(hi);
1129299742Sdim      const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1130299742Sdim      const svn_fs_lock_target_t *other;
1131299742Sdim
1132299742Sdim      path = svn_fspath__canonicalize(path, result_pool);
1133299742Sdim      other = svn_hash_gets(canonical_targets, path);
1134299742Sdim
1135299742Sdim      if (!other || (!other->token && target->token))
1136299742Sdim        svn_hash_sets(canonical_targets, path, target);
1137299742Sdim    }
1138299742Sdim
1139299742Sdim  sorted_targets = svn_sort__hash(canonical_targets,
1140299742Sdim                                  svn_sort_compare_items_as_paths,
1141299742Sdim                                  scratch_pool);
1142299742Sdim
1143251881Speter  lb.fs = fs;
1144299742Sdim  lb.targets = sorted_targets;
1145299742Sdim  lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1146299742Sdim                            sizeof(struct lock_info_t));
1147251881Speter  lb.comment = comment;
1148251881Speter  lb.is_dav_comment = is_dav_comment;
1149251881Speter  lb.expiration_date = expiration_date;
1150251881Speter  lb.steal_lock = steal_lock;
1151299742Sdim  lb.result_pool = result_pool;
1152251881Speter
1153299742Sdim  iterpool = svn_pool_create(scratch_pool);
1154299742Sdim  err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool);
1155299742Sdim  for (i = 0; i < lb.infos->nelts; ++i)
1156299742Sdim    {
1157299742Sdim      struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1158299742Sdim                                                struct lock_info_t);
1159299742Sdim      svn_pool_clear(iterpool);
1160299742Sdim      if (!cb_err && lock_callback)
1161299742Sdim        {
1162299742Sdim          if (!info->lock && !info->fs_err)
1163299742Sdim            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1164299742Sdim                                             0, _("Failed to lock '%s'"),
1165299742Sdim                                             info->path);
1166299742Sdim
1167299742Sdim          cb_err = lock_callback(lock_baton, info->path, info->lock,
1168299742Sdim                                 info->fs_err, iterpool);
1169299742Sdim        }
1170299742Sdim      svn_error_clear(info->fs_err);
1171299742Sdim    }
1172299742Sdim  svn_pool_destroy(iterpool);
1173299742Sdim
1174299742Sdim  if (err && cb_err)
1175299742Sdim    svn_error_compose(err, cb_err);
1176299742Sdim  else if (!err)
1177299742Sdim    err = cb_err;
1178299742Sdim
1179299742Sdim  return svn_error_trace(err);
1180251881Speter}
1181251881Speter
1182251881Speter
1183251881Spetersvn_error_t *
1184251881Spetersvn_fs_fs__generate_lock_token(const char **token,
1185251881Speter                               svn_fs_t *fs,
1186251881Speter                               apr_pool_t *pool)
1187251881Speter{
1188251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1189251881Speter
1190251881Speter  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
1191251881Speter     want to use the fs UUID + some incremented number?  For now, we
1192251881Speter     generate a URI that matches the DAV RFC.  We could change this to
1193251881Speter     some other URI scheme someday, if we wish. */
1194251881Speter  *token = apr_pstrcat(pool, "opaquelocktoken:",
1195299742Sdim                       svn_uuid_generate(pool), SVN_VA_NULL);
1196251881Speter  return SVN_NO_ERROR;
1197251881Speter}
1198251881Speter
1199251881Spetersvn_error_t *
1200251881Spetersvn_fs_fs__unlock(svn_fs_t *fs,
1201299742Sdim                  apr_hash_t *targets,
1202251881Speter                  svn_boolean_t break_lock,
1203299742Sdim                  svn_fs_lock_callback_t lock_callback,
1204299742Sdim                  void *lock_baton,
1205299742Sdim                  apr_pool_t *result_pool,
1206299742Sdim                  apr_pool_t *scratch_pool)
1207251881Speter{
1208251881Speter  struct unlock_baton ub;
1209299742Sdim  apr_array_header_t *sorted_targets;
1210299742Sdim  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1211299742Sdim  apr_hash_index_t *hi;
1212299742Sdim  apr_pool_t *iterpool;
1213299742Sdim  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1214299742Sdim  int i;
1215251881Speter
1216251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1217251881Speter
1218299742Sdim  /* We need to have a username attached to the fs. */
1219299742Sdim  if (!fs->access_ctx || !fs->access_ctx->username)
1220299742Sdim    return SVN_FS__ERR_NO_USER(fs);
1221299742Sdim
1222299742Sdim  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1223299742Sdim    {
1224299742Sdim      const char *path = apr_hash_this_key(hi);
1225299742Sdim      const char *token = apr_hash_this_val(hi);
1226299742Sdim      const char *other;
1227299742Sdim
1228299742Sdim      path = svn_fspath__canonicalize(path, result_pool);
1229299742Sdim      other = svn_hash_gets(canonical_targets, path);
1230299742Sdim
1231299742Sdim      if (!other)
1232299742Sdim        svn_hash_sets(canonical_targets, path, token);
1233299742Sdim    }
1234299742Sdim
1235299742Sdim  sorted_targets = svn_sort__hash(canonical_targets,
1236299742Sdim                                  svn_sort_compare_items_as_paths,
1237299742Sdim                                  scratch_pool);
1238299742Sdim
1239251881Speter  ub.fs = fs;
1240299742Sdim  ub.targets = sorted_targets;
1241299742Sdim  ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1242299742Sdim                            sizeof(struct unlock_info_t));
1243299742Sdim  ub.skip_check = FALSE;
1244251881Speter  ub.break_lock = break_lock;
1245299742Sdim  ub.result_pool = result_pool;
1246251881Speter
1247299742Sdim  iterpool = svn_pool_create(scratch_pool);
1248299742Sdim  err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
1249299742Sdim  for (i = 0; i < ub.infos->nelts; ++i)
1250299742Sdim    {
1251299742Sdim      struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
1252299742Sdim                                                  struct unlock_info_t);
1253299742Sdim      svn_pool_clear(iterpool);
1254299742Sdim      if (!cb_err && lock_callback)
1255299742Sdim        {
1256299742Sdim          if (!info->done && !info->fs_err)
1257299742Sdim            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1258299742Sdim                                             0, _("Failed to unlock '%s'"),
1259299742Sdim                                             info->path);
1260299742Sdim          cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1261299742Sdim                                 iterpool);
1262299742Sdim        }
1263299742Sdim      svn_error_clear(info->fs_err);
1264299742Sdim    }
1265299742Sdim  svn_pool_destroy(iterpool);
1266299742Sdim
1267299742Sdim  if (err && cb_err)
1268299742Sdim    svn_error_compose(err, cb_err);
1269299742Sdim  else if (!err)
1270299742Sdim    err = cb_err;
1271299742Sdim
1272299742Sdim  return svn_error_trace(err);
1273251881Speter}
1274251881Speter
1275251881Speter
1276251881Spetersvn_error_t *
1277251881Spetersvn_fs_fs__get_lock(svn_lock_t **lock_p,
1278251881Speter                    svn_fs_t *fs,
1279251881Speter                    const char *path,
1280251881Speter                    apr_pool_t *pool)
1281251881Speter{
1282251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1283251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
1284251881Speter  return get_lock_helper(fs, lock_p, path, FALSE, pool);
1285251881Speter}
1286251881Speter
1287251881Speter
1288251881Speter/* Baton for get_locks_filter_func(). */
1289251881Spetertypedef struct get_locks_filter_baton_t
1290251881Speter{
1291251881Speter  const char *path;
1292251881Speter  svn_depth_t requested_depth;
1293251881Speter  svn_fs_get_locks_callback_t get_locks_func;
1294251881Speter  void *get_locks_baton;
1295251881Speter
1296251881Speter} get_locks_filter_baton_t;
1297251881Speter
1298251881Speter
1299251881Speter/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
1300251881Speter   which filters out locks on paths that aren't within
1301251881Speter   BATON->requested_depth of BATON->path before called
1302251881Speter   BATON->get_locks_func() with BATON->get_locks_baton.
1303251881Speter
1304251881Speter   NOTE: See issue #3660 for details about how the FSFS lock
1305251881Speter   management code is inconsistent.  Until that inconsistency is
1306251881Speter   resolved, we take this filtering approach rather than honoring
1307251881Speter   depth requests closer to the crawling code.  In other words, once
1308251881Speter   we decide how to resolve issue #3660, there might be a more
1309251881Speter   performant way to honor the depth passed to svn_fs_fs__get_locks().  */
1310251881Speterstatic svn_error_t *
1311251881Speterget_locks_filter_func(void *baton,
1312251881Speter                      svn_lock_t *lock,
1313251881Speter                      apr_pool_t *pool)
1314251881Speter{
1315251881Speter  get_locks_filter_baton_t *b = baton;
1316251881Speter
1317251881Speter  /* Filter out unwanted paths.  Since Subversion only allows
1318251881Speter     locks on files, we can treat depth=immediates the same as
1319251881Speter     depth=files for filtering purposes.  Meaning, we'll keep
1320251881Speter     this lock if:
1321251881Speter
1322251881Speter     a) its path is the very path we queried, or
1323251881Speter     b) we've asked for a fully recursive answer, or
1324251881Speter     c) we've asked for depth=files or depth=immediates, and this
1325251881Speter        lock is on an immediate child of our query path.
1326251881Speter  */
1327251881Speter  if ((strcmp(b->path, lock->path) == 0)
1328251881Speter      || (b->requested_depth == svn_depth_infinity))
1329251881Speter    {
1330251881Speter      SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1331251881Speter    }
1332251881Speter  else if ((b->requested_depth == svn_depth_files) ||
1333251881Speter           (b->requested_depth == svn_depth_immediates))
1334251881Speter    {
1335251881Speter      const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
1336251881Speter      if (rel_uri && (svn_path_component_count(rel_uri) == 1))
1337251881Speter        SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
1338251881Speter    }
1339251881Speter
1340251881Speter  return SVN_NO_ERROR;
1341251881Speter}
1342251881Speter
1343251881Spetersvn_error_t *
1344251881Spetersvn_fs_fs__get_locks(svn_fs_t *fs,
1345251881Speter                     const char *path,
1346251881Speter                     svn_depth_t depth,
1347251881Speter                     svn_fs_get_locks_callback_t get_locks_func,
1348251881Speter                     void *get_locks_baton,
1349251881Speter                     apr_pool_t *pool)
1350251881Speter{
1351251881Speter  const char *digest_path;
1352251881Speter  get_locks_filter_baton_t glfb;
1353251881Speter
1354251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1355251881Speter  path = svn_fs__canonicalize_abspath(path, pool);
1356251881Speter
1357251881Speter  glfb.path = path;
1358251881Speter  glfb.requested_depth = depth;
1359251881Speter  glfb.get_locks_func = get_locks_func;
1360251881Speter  glfb.get_locks_baton = get_locks_baton;
1361251881Speter
1362251881Speter  /* Get the top digest path in our tree of interest, and then walk it. */
1363251881Speter  SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
1364251881Speter  SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
1365251881Speter                     FALSE, pool));
1366251881Speter  return SVN_NO_ERROR;
1367251881Speter}
1368