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"
39289180Speter#include "util.h"
40251881Speter#include "../libsvn_fs/fs-loader.h"
41251881Speter
42251881Speter#include "private/svn_fs_util.h"
43251881Speter#include "private/svn_fspath.h"
44289180Speter#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,
106289180Speter           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),
136289180Speter                              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),
154289180Speter                                      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,
212289180Speter                                    apr_hash_this_key(hi),
213289180Speter                                    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    {
225362181Sdim      err = svn_error_compose_create(err, 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));
233362181Sdim  SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, 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;
255289180Speter  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
262289180Speter  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
263289180Speter  if (kind == svn_node_none)
264289180Speter    return SVN_NO_ERROR;
265251881Speter
266251881Speter  /* If our caller doesn't care about anything but the presence of the
267251881Speter     file... whatever. */
268289180Speter  if (kind == svn_node_file && !lock_p && !children_p)
269289180Speter    return SVN_NO_ERROR;
270251881Speter
271289180Speter  SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
272289180Speter
273251881Speter  hash = apr_hash_make(pool);
274251881Speter  if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
275251881Speter    {
276362181Sdim      err = svn_error_compose_create(err, 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. */
286289180Speter  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
295289180Speter      if (! ((lock->token = hash_fetch(hash, TOKEN_KEY))))
296251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
297251881Speter
298289180Speter      if (! ((lock->owner = hash_fetch(hash, OWNER_KEY))))
299251881Speter        return svn_error_trace(err_corrupt_lockfile(fs_path, path));
300251881Speter
301289180Speter      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
305289180Speter      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
309289180Speter      if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY)))
310251881Speter        SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
311251881Speter
312289180Speter      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. */
318289180Speter  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{
349289180Speter  const char *digest_path;
350289180Speter  apr_hash_t *children;
351251881Speter
352289180Speter  SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
353251881Speter
354289180Speter  /* We could get away without reading the file as children should
355289180Speter     always come back empty. */
356289180Speter  SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
357251881Speter
358289180Speter  SVN_ERR(write_digest_file(children, lock, fs_path, digest_path,
359289180Speter                            perms_reference, pool));
360251881Speter
361289180Speter  return SVN_NO_ERROR;
362289180Speter}
363251881Speter
364289180Speterstatic svn_error_t *
365289180Speterdelete_lock(const char *fs_path,
366289180Speter            const char *path,
367289180Speter            apr_pool_t *pool)
368289180Speter{
369289180Speter  const char *digest_path;
370251881Speter
371289180Speter  SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
372251881Speter
373289180Speter  SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
374251881Speter
375251881Speter  return SVN_NO_ERROR;
376251881Speter}
377251881Speter
378251881Speterstatic svn_error_t *
379289180Speteradd_to_digest(const char *fs_path,
380289180Speter              apr_array_header_t *paths,
381289180Speter              const char *index_path,
382289180Speter              const char *perms_reference,
383289180Speter              apr_pool_t *pool)
384251881Speter{
385289180Speter  const char *index_digest_path;
386289180Speter  apr_hash_t *children;
387289180Speter  svn_lock_t *lock;
388289180Speter  int i;
389289180Speter  unsigned int original_count;
390251881Speter
391289180Speter  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
392251881Speter
393289180Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
394289180Speter
395289180Speter  original_count = apr_hash_count(children);
396289180Speter
397289180Speter  for (i = 0; i < paths->nelts; ++i)
398251881Speter    {
399289180Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
400251881Speter      const char *digest_path, *digest_file;
401251881Speter
402289180Speter      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
403289180Speter      digest_file = svn_dirent_basename(digest_path, NULL);
404289180Speter      svn_hash_sets(children, digest_file, (void *)1);
405289180Speter    }
406251881Speter
407289180Speter  if (apr_hash_count(children) != original_count)
408289180Speter    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
409289180Speter                              perms_reference, pool));
410251881Speter
411289180Speter  return SVN_NO_ERROR;
412289180Speter}
413251881Speter
414289180Speterstatic svn_error_t *
415289180Speterdelete_from_digest(const char *fs_path,
416289180Speter                   apr_array_header_t *paths,
417289180Speter                   const char *index_path,
418289180Speter                   const char *perms_reference,
419289180Speter                   apr_pool_t *pool)
420289180Speter{
421289180Speter  const char *index_digest_path;
422289180Speter  apr_hash_t *children;
423289180Speter  svn_lock_t *lock;
424289180Speter  int i;
425251881Speter
426289180Speter  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
427251881Speter
428289180Speter  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
429251881Speter
430289180Speter  for (i = 0; i < paths->nelts; ++i)
431289180Speter    {
432289180Speter      const char *path = APR_ARRAY_IDX(paths, i, const char *);
433289180Speter      const char *digest_path, *digest_file;
434289180Speter
435289180Speter      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
436289180Speter      digest_file = svn_dirent_basename(digest_path, NULL);
437289180Speter      svn_hash_sets(children, digest_file, NULL);
438251881Speter    }
439251881Speter
440289180Speter  if (apr_hash_count(children) || lock)
441289180Speter    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path,
442289180Speter                              perms_reference, pool));
443289180Speter  else
444289180Speter    SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
445289180Speter
446251881Speter  return SVN_NO_ERROR;
447251881Speter}
448251881Speter
449289180Speterstatic svn_error_t *
450289180Speterunlock_single(svn_fs_t *fs,
451289180Speter              svn_lock_t *lock,
452289180Speter              apr_pool_t *pool);
453289180Speter
454289180Speter/* Check if LOCK has been already expired. */
455289180Speterstatic svn_boolean_t lock_expired(const svn_lock_t *lock)
456289180Speter{
457289180Speter  return lock->expiration_date && (apr_time_now() > lock->expiration_date);
458289180Speter}
459289180Speter
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. */
489289180Speter  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)
494289180Speter        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
536289180Speter/* A function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
537289180Speter   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 *
541289180Speterwalk_locks(svn_fs_t *fs,
542289180Speter           const char *digest_path,
543289180Speter           svn_fs_get_locks_callback_t get_locks_func,
544289180Speter           void *get_locks_baton,
545289180Speter           svn_boolean_t have_write_lock,
546289180Speter           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. */
554289180Speter  SVN_ERR(read_digest_file(&children, &lock, fs->path, digest_path, pool));
555251881Speter
556289180Speter  if (lock && lock_expired(lock))
557289180Speter    {
558289180Speter      /* Only remove the lock if we have the write lock.
559289180Speter         Read operations shouldn't change the filesystem. */
560289180Speter      if (have_write_lock)
561289180Speter        SVN_ERR(unlock_single(fs, lock, pool));
562289180Speter    }
563289180Speter  else if (lock)
564289180Speter    {
565289180Speter      SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
566289180Speter    }
567251881Speter
568289180Speter  /* 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    {
574289180Speter      const char *digest = apr_hash_this_key(hi);
575251881Speter      svn_pool_clear(subpool);
576289180Speter
577289180Speter      SVN_ERR(read_digest_file
578289180Speter              (NULL, &lock, fs->path,
579289180Speter               digest_path_from_digest(fs->path, digest, subpool), subpool));
580289180Speter
581289180Speter      if (lock && lock_expired(lock))
582289180Speter        {
583289180Speter          /* Only remove the lock if we have the write lock.
584289180Speter             Read operations shouldn't change the filesystem. */
585289180Speter          if (have_write_lock)
586289180Speter            SVN_ERR(unlock_single(fs, lock, pool));
587289180Speter        }
588289180Speter      else if (lock)
589289180Speter        {
590289180Speter          SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
591289180Speter        }
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
671289180Speter/* Helper function called from the lock and unlock code.
672289180Speter   UPDATES is a map from "const char *" parent paths to "apr_array_header_t *"
673289180Speter   arrays of child paths.  For all of the parent paths of PATH this function
674289180Speter   adds PATH to the corresponding array of child paths. */
675289180Speterstatic void
676289180Speterschedule_index_update(apr_hash_t *updates,
677289180Speter                      const char *path,
678289180Speter                      apr_pool_t *scratch_pool)
679289180Speter{
680289180Speter  apr_pool_t *hashpool = apr_hash_pool_get(updates);
681289180Speter  const char *parent_path = path;
682289180Speter
683289180Speter  while (! svn_fspath__is_root(parent_path, strlen(parent_path)))
684289180Speter    {
685289180Speter      apr_array_header_t *children;
686289180Speter
687289180Speter      parent_path = svn_fspath__dirname(parent_path, scratch_pool);
688289180Speter      children = svn_hash_gets(updates, parent_path);
689289180Speter
690289180Speter      if (! children)
691289180Speter        {
692289180Speter          children = apr_array_make(hashpool, 8, sizeof(const char *));
693289180Speter          svn_hash_sets(updates, apr_pstrdup(hashpool, parent_path), children);
694289180Speter        }
695289180Speter
696289180Speter      APR_ARRAY_PUSH(children, const char *) = path;
697289180Speter    }
698289180Speter}
699289180Speter
700289180Speter/* The effective arguments for lock_body() below. */
701251881Speterstruct lock_baton {
702251881Speter  svn_fs_t *fs;
703289180Speter  apr_array_header_t *targets;
704289180Speter  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;
709289180Speter  apr_pool_t *result_pool;
710251881Speter};
711251881Speter
712251881Speterstatic svn_error_t *
713289180Spetercheck_lock(svn_error_t **fs_err,
714289180Speter           const char *path,
715289180Speter           const svn_fs_lock_target_t *target,
716289180Speter           struct lock_baton *lb,
717289180Speter           svn_fs_root_t *root,
718289180Speter           svn_revnum_t youngest_rev,
719289180Speter           apr_pool_t *pool)
720251881Speter{
721251881Speter  svn_node_kind_t kind;
722251881Speter  svn_lock_t *existing_lock;
723251881Speter
724289180Speter  *fs_err = SVN_NO_ERROR;
725289180Speter
726289180Speter  SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
727251881Speter  if (kind == svn_node_dir)
728289180Speter    {
729289180Speter      *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
730289180Speter      return SVN_NO_ERROR;
731289180Speter    }
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    {
737289180Speter      if (SVN_IS_VALID_REVNUM(target->current_rev))
738289180Speter        *fs_err = svn_error_createf(
739251881Speter          SVN_ERR_FS_OUT_OF_DATE, NULL,
740251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
741289180Speter          path);
742251881Speter      else
743289180Speter        *fs_err = svn_error_createf(
744251881Speter          SVN_ERR_FS_NOT_FOUND, NULL,
745251881Speter          _("Path '%s' doesn't exist in HEAD revision"),
746289180Speter          path);
747289180Speter
748289180Speter      return SVN_NO_ERROR;
749251881Speter    }
750251881Speter
751251881Speter  /* Is the caller attempting to lock an out-of-date working file? */
752289180Speter  if (SVN_IS_VALID_REVNUM(target->current_rev))
753251881Speter    {
754251881Speter      svn_revnum_t created_rev;
755289180Speter
756289180Speter      if (target->current_rev > youngest_rev)
757289180Speter        {
758289180Speter          *fs_err = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
759289180Speter                                      _("No such revision %ld"),
760289180Speter                                      target->current_rev);
761289180Speter          return SVN_NO_ERROR;
762289180Speter        }
763289180Speter
764289180Speter      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))
772289180Speter        {
773289180Speter          *fs_err = svn_error_createf
774289180Speter            (SVN_ERR_FS_OUT_OF_DATE, NULL,
775289180Speter             _("Path '%s' doesn't exist in HEAD revision"), path);
776251881Speter
777289180Speter          return SVN_NO_ERROR;
778289180Speter        }
779289180Speter
780289180Speter      if (target->current_rev < created_rev)
781289180Speter        {
782289180Speter          *fs_err = svn_error_createf
783289180Speter            (SVN_ERR_FS_OUT_OF_DATE, NULL,
784289180Speter             _("Lock failed: newer version of '%s' exists"), path);
785289180Speter
786289180Speter          return SVN_NO_ERROR;
787289180Speter        }
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.   */
806289180Speter  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. */
812289180Speter          *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
813289180Speter          return SVN_NO_ERROR;
814251881Speter        }
815289180Speter    }
816289180Speter
817289180Speter  return SVN_NO_ERROR;
818289180Speter}
819289180Speter
820289180Speterstruct lock_info_t {
821289180Speter  const char *path;
822289180Speter  svn_lock_t *lock;
823289180Speter  svn_error_t *fs_err;
824289180Speter};
825289180Speter
826289180Speter/* The body of svn_fs_fs__lock(), which see.
827289180Speter
828289180Speter   BATON is a 'struct lock_baton *' holding the effective arguments.
829289180Speter   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
830289180Speter   path, mapping canonical path to 'svn_fs_lock_target_t'.  Set
831289180Speter   BATON->infos to an array of 'lock_info_t' holding the results.  For
832289180Speter   the other arguments, see svn_fs_lock_many().
833289180Speter
834289180Speter   This implements the svn_fs_fs__with_write_lock() 'body' callback
835289180Speter   type, and assumes that the write lock is held.
836289180Speter */
837289180Speterstatic svn_error_t *
838289180Speterlock_body(void *baton, apr_pool_t *pool)
839289180Speter{
840289180Speter  struct lock_baton *lb = baton;
841289180Speter  svn_fs_root_t *root;
842289180Speter  svn_revnum_t youngest;
843289180Speter  const char *rev_0_path;
844289180Speter  int i;
845289180Speter  apr_hash_t *index_updates = apr_hash_make(pool);
846289180Speter  apr_hash_index_t *hi;
847289180Speter  apr_pool_t *iterpool = svn_pool_create(pool);
848289180Speter
849289180Speter  /* Until we implement directory locks someday, we only allow locks
850362181Sdim     on files. */
851289180Speter  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
852289180Speter     library dependencies, which are not portable. */
853289180Speter  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
854289180Speter  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
855289180Speter
856289180Speter  for (i = 0; i < lb->targets->nelts; ++i)
857289180Speter    {
858289180Speter      const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
859289180Speter                                                    svn_sort__item_t);
860289180Speter      struct lock_info_t info;
861289180Speter
862289180Speter      svn_pool_clear(iterpool);
863289180Speter
864289180Speter      info.path = item->key;
865289180Speter      info.lock = NULL;
866289180Speter      info.fs_err = SVN_NO_ERROR;
867289180Speter
868289180Speter      SVN_ERR(check_lock(&info.fs_err, info.path, item->value, lb, root,
869289180Speter                         youngest, iterpool));
870289180Speter
871289180Speter      /* If no error occurred while pre-checking, schedule the index updates for
872289180Speter         this path. */
873289180Speter      if (!info.fs_err)
874289180Speter        schedule_index_update(index_updates, info.path, iterpool);
875289180Speter
876289180Speter      APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
877289180Speter    }
878289180Speter
879289180Speter  rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
880289180Speter
881289180Speter  /* We apply the scheduled index updates before writing the actual locks.
882289180Speter
883289180Speter     Writing indices before locks is correct: if interrupted it leaves
884289180Speter     indices without locks rather than locks without indices.  An
885289180Speter     index without a lock is consistent in that it always shows up as
886289180Speter     unlocked in svn_fs_fs__allow_locked_operation.  A lock without an
887289180Speter     index is inconsistent, svn_fs_fs__allow_locked_operation will
888289180Speter     show locked on the file but unlocked on the parent. */
889289180Speter
890289180Speter  for (hi = apr_hash_first(pool, index_updates); hi; hi = apr_hash_next(hi))
891289180Speter    {
892289180Speter      const char *path = apr_hash_this_key(hi);
893289180Speter      apr_array_header_t *children = apr_hash_this_val(hi);
894289180Speter
895289180Speter      svn_pool_clear(iterpool);
896289180Speter      SVN_ERR(add_to_digest(lb->fs->path, children, path, rev_0_path,
897289180Speter                            iterpool));
898289180Speter    }
899289180Speter
900289180Speter  for (i = 0; i < lb->infos->nelts; ++i)
901289180Speter    {
902289180Speter      struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
903289180Speter                                                struct lock_info_t);
904289180Speter      svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i, svn_sort__item_t);
905289180Speter      svn_fs_lock_target_t *target = item->value;
906289180Speter
907289180Speter      svn_pool_clear(iterpool);
908289180Speter
909289180Speter      if (! info->fs_err)
910251881Speter        {
911289180Speter          info->lock = svn_lock_create(lb->result_pool);
912289180Speter          if (target->token)
913289180Speter            info->lock->token = apr_pstrdup(lb->result_pool, target->token);
914289180Speter          else
915289180Speter            SVN_ERR(svn_fs_fs__generate_lock_token(&(info->lock->token), lb->fs,
916289180Speter                                                   lb->result_pool));
917289180Speter
918289180Speter          /* The INFO->PATH is already allocated in LB->RESULT_POOL as a result
919289180Speter             of svn_fspath__canonicalize() (see svn_fs_fs__lock()). */
920289180Speter          info->lock->path = info->path;
921289180Speter          info->lock->owner = apr_pstrdup(lb->result_pool,
922289180Speter                                          lb->fs->access_ctx->username);
923289180Speter          info->lock->comment = apr_pstrdup(lb->result_pool, lb->comment);
924289180Speter          info->lock->is_dav_comment = lb->is_dav_comment;
925289180Speter          info->lock->creation_date = apr_time_now();
926289180Speter          info->lock->expiration_date = lb->expiration_date;
927289180Speter
928289180Speter          info->fs_err = set_lock(lb->fs->path, info->lock, rev_0_path,
929289180Speter                                  iterpool);
930251881Speter        }
931251881Speter    }
932251881Speter
933289180Speter  svn_pool_destroy(iterpool);
934251881Speter  return SVN_NO_ERROR;
935251881Speter}
936251881Speter
937289180Speter/* The effective arguments for unlock_body() below. */
938251881Speterstruct unlock_baton {
939251881Speter  svn_fs_t *fs;
940289180Speter  apr_array_header_t *targets;
941289180Speter  apr_array_header_t *infos;
942289180Speter  /* Set skip_check TRUE to prevent the checks that set infos[].fs_err. */
943289180Speter  svn_boolean_t skip_check;
944251881Speter  svn_boolean_t break_lock;
945289180Speter  apr_pool_t *result_pool;
946251881Speter};
947251881Speter
948289180Speterstatic svn_error_t *
949289180Spetercheck_unlock(svn_error_t **fs_err,
950289180Speter             const char *path,
951289180Speter             const char *token,
952289180Speter             struct unlock_baton *ub,
953289180Speter             svn_fs_root_t *root,
954289180Speter             apr_pool_t *pool)
955289180Speter{
956289180Speter  svn_lock_t *lock;
957289180Speter
958289180Speter  *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
959289180Speter  if (!*fs_err && !ub->break_lock)
960289180Speter    {
961289180Speter      if (strcmp(token, lock->token) != 0)
962289180Speter        *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
963289180Speter      else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
964289180Speter        *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
965289180Speter                                                  ub->fs->access_ctx->username,
966289180Speter                                                  lock->owner);
967289180Speter    }
968289180Speter
969289180Speter  return SVN_NO_ERROR;
970289180Speter}
971289180Speter
972289180Speterstruct unlock_info_t {
973289180Speter  const char *path;
974289180Speter  svn_error_t *fs_err;
975289180Speter  svn_boolean_t done;
976289180Speter};
977289180Speter
978289180Speter/* The body of svn_fs_fs__unlock(), which see.
979289180Speter
980289180Speter   BATON is a 'struct unlock_baton *' holding the effective arguments.
981289180Speter   BATON->targets is an array of 'svn_sort__item_t' targets, sorted by
982289180Speter   path, mapping canonical path to (const char *) token.  Set
983289180Speter   BATON->infos to an array of 'unlock_info_t' results.  For the other
984289180Speter   arguments, see svn_fs_unlock_many().
985289180Speter
986289180Speter   This implements the svn_fs_fs__with_write_lock() 'body' callback
987251881Speter   type, and assumes that the write lock is held.
988289180Speter */
989251881Speterstatic svn_error_t *
990251881Speterunlock_body(void *baton, apr_pool_t *pool)
991251881Speter{
992251881Speter  struct unlock_baton *ub = baton;
993289180Speter  svn_fs_root_t *root;
994289180Speter  svn_revnum_t youngest;
995289180Speter  const char *rev_0_path;
996289180Speter  int i;
997289180Speter  apr_hash_t *indices_updates = apr_hash_make(pool);
998289180Speter  apr_hash_index_t *hi;
999289180Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1000251881Speter
1001289180Speter  SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
1002289180Speter  SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
1003251881Speter
1004289180Speter  for (i = 0; i < ub->targets->nelts; ++i)
1005251881Speter    {
1006289180Speter      const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
1007289180Speter                                                    svn_sort__item_t);
1008289180Speter      const char *token = item->value;
1009289180Speter      struct unlock_info_t info;
1010251881Speter
1011289180Speter      svn_pool_clear(iterpool);
1012251881Speter
1013289180Speter      info.path = item->key;
1014289180Speter      info.fs_err = SVN_NO_ERROR;
1015289180Speter      info.done = FALSE;
1016289180Speter
1017289180Speter      if (!ub->skip_check)
1018289180Speter        SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
1019289180Speter                             iterpool));
1020289180Speter
1021289180Speter      /* If no error occurred while pre-checking, schedule the index updates for
1022289180Speter         this path. */
1023289180Speter      if (!info.fs_err)
1024289180Speter        schedule_index_update(indices_updates, info.path, iterpool);
1025289180Speter
1026289180Speter      APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
1027251881Speter    }
1028251881Speter
1029289180Speter  rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
1030289180Speter
1031289180Speter  /* Unlike the lock_body(), we need to delete locks *before* we start to
1032289180Speter     update indices. */
1033289180Speter
1034289180Speter  for (i = 0; i < ub->infos->nelts; ++i)
1035289180Speter    {
1036289180Speter      struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, i,
1037289180Speter                                                  struct unlock_info_t);
1038289180Speter
1039289180Speter      svn_pool_clear(iterpool);
1040289180Speter
1041289180Speter      if (! info->fs_err)
1042289180Speter        {
1043289180Speter          SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
1044289180Speter          info->done = TRUE;
1045289180Speter        }
1046289180Speter    }
1047289180Speter
1048289180Speter  for (hi = apr_hash_first(pool, indices_updates); hi; hi = apr_hash_next(hi))
1049289180Speter    {
1050289180Speter      const char *path = apr_hash_this_key(hi);
1051289180Speter      apr_array_header_t *children = apr_hash_this_val(hi);
1052289180Speter
1053289180Speter      svn_pool_clear(iterpool);
1054289180Speter      SVN_ERR(delete_from_digest(ub->fs->path, children, path, rev_0_path,
1055289180Speter                                 iterpool));
1056289180Speter    }
1057289180Speter
1058289180Speter  svn_pool_destroy(iterpool);
1059289180Speter  return SVN_NO_ERROR;
1060251881Speter}
1061251881Speter
1062289180Speter/* Unlock the lock described by LOCK->path and LOCK->token in FS.
1063289180Speter
1064289180Speter   This assumes that the write lock is held.
1065289180Speter */
1066289180Speterstatic svn_error_t *
1067289180Speterunlock_single(svn_fs_t *fs,
1068289180Speter              svn_lock_t *lock,
1069289180Speter              apr_pool_t *pool)
1070289180Speter{
1071289180Speter  struct unlock_baton ub;
1072289180Speter  svn_sort__item_t item;
1073289180Speter  apr_array_header_t *targets = apr_array_make(pool, 1,
1074289180Speter                                               sizeof(svn_sort__item_t));
1075289180Speter  item.key = lock->path;
1076289180Speter  item.klen = strlen(item.key);
1077289180Speter  item.value = (char*)lock->token;
1078289180Speter  APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
1079289180Speter
1080289180Speter  ub.fs = fs;
1081289180Speter  ub.targets = targets;
1082289180Speter  ub.infos = apr_array_make(pool, targets->nelts,
1083289180Speter                            sizeof(struct unlock_info_t));
1084289180Speter  ub.skip_check = TRUE;
1085289180Speter  ub.result_pool = pool;
1086289180Speter
1087289180Speter  /* No ub.infos[].fs_err error because skip_check is TRUE. */
1088289180Speter  SVN_ERR(unlock_body(&ub, pool));
1089289180Speter
1090289180Speter  return SVN_NO_ERROR;
1091289180Speter}
1092289180Speter
1093251881Speter
1094251881Speter/*** Public API implementations ***/
1095251881Speter
1096251881Spetersvn_error_t *
1097289180Spetersvn_fs_fs__lock(svn_fs_t *fs,
1098289180Speter                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,
1103289180Speter                svn_fs_lock_callback_t lock_callback,
1104289180Speter                void *lock_baton,
1105289180Speter                apr_pool_t *result_pool,
1106289180Speter                apr_pool_t *scratch_pool)
1107251881Speter{
1108251881Speter  struct lock_baton lb;
1109289180Speter  apr_array_header_t *sorted_targets;
1110289180Speter  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1111289180Speter  apr_hash_index_t *hi;
1112289180Speter  apr_pool_t *iterpool;
1113289180Speter  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1114289180Speter  int i;
1115251881Speter
1116251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1117251881Speter
1118289180Speter  /* We need to have a username attached to the fs. */
1119289180Speter  if (!fs->access_ctx || !fs->access_ctx->username)
1120289180Speter    return SVN_FS__ERR_NO_USER(fs);
1121289180Speter
1122289180Speter  /* The FS locking API allows both canonical and non-canonical
1123289180Speter     paths which means that the same canonical path could be
1124289180Speter     represented more than once in the TARGETS hash.  We just keep
1125289180Speter     one, choosing one with a token if possible. */
1126289180Speter  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1127289180Speter    {
1128289180Speter      const char *path = apr_hash_this_key(hi);
1129289180Speter      const svn_fs_lock_target_t *target = apr_hash_this_val(hi);
1130289180Speter      const svn_fs_lock_target_t *other;
1131289180Speter
1132289180Speter      path = svn_fspath__canonicalize(path, result_pool);
1133289180Speter      other = svn_hash_gets(canonical_targets, path);
1134289180Speter
1135289180Speter      if (!other || (!other->token && target->token))
1136289180Speter        svn_hash_sets(canonical_targets, path, target);
1137289180Speter    }
1138289180Speter
1139289180Speter  sorted_targets = svn_sort__hash(canonical_targets,
1140289180Speter                                  svn_sort_compare_items_as_paths,
1141289180Speter                                  scratch_pool);
1142289180Speter
1143251881Speter  lb.fs = fs;
1144289180Speter  lb.targets = sorted_targets;
1145289180Speter  lb.infos = apr_array_make(result_pool, sorted_targets->nelts,
1146289180Speter                            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;
1151289180Speter  lb.result_pool = result_pool;
1152251881Speter
1153289180Speter  iterpool = svn_pool_create(scratch_pool);
1154289180Speter  err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, iterpool);
1155289180Speter  for (i = 0; i < lb.infos->nelts; ++i)
1156289180Speter    {
1157289180Speter      struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
1158289180Speter                                                struct lock_info_t);
1159289180Speter      svn_pool_clear(iterpool);
1160289180Speter      if (!cb_err && lock_callback)
1161289180Speter        {
1162289180Speter          if (!info->lock && !info->fs_err)
1163289180Speter            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1164289180Speter                                             0, _("Failed to lock '%s'"),
1165289180Speter                                             info->path);
1166289180Speter
1167289180Speter          cb_err = lock_callback(lock_baton, info->path, info->lock,
1168289180Speter                                 info->fs_err, iterpool);
1169289180Speter        }
1170289180Speter      svn_error_clear(info->fs_err);
1171289180Speter    }
1172289180Speter  svn_pool_destroy(iterpool);
1173289180Speter
1174289180Speter  if (err && cb_err)
1175289180Speter    svn_error_compose(err, cb_err);
1176289180Speter  else if (!err)
1177289180Speter    err = cb_err;
1178289180Speter
1179289180Speter  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:",
1195289180Speter                       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,
1201289180Speter                  apr_hash_t *targets,
1202251881Speter                  svn_boolean_t break_lock,
1203289180Speter                  svn_fs_lock_callback_t lock_callback,
1204289180Speter                  void *lock_baton,
1205289180Speter                  apr_pool_t *result_pool,
1206289180Speter                  apr_pool_t *scratch_pool)
1207251881Speter{
1208251881Speter  struct unlock_baton ub;
1209289180Speter  apr_array_header_t *sorted_targets;
1210289180Speter  apr_hash_t *canonical_targets = apr_hash_make(scratch_pool);
1211289180Speter  apr_hash_index_t *hi;
1212289180Speter  apr_pool_t *iterpool;
1213289180Speter  svn_error_t *err, *cb_err = SVN_NO_ERROR;
1214289180Speter  int i;
1215251881Speter
1216251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1217251881Speter
1218289180Speter  /* We need to have a username attached to the fs. */
1219289180Speter  if (!fs->access_ctx || !fs->access_ctx->username)
1220289180Speter    return SVN_FS__ERR_NO_USER(fs);
1221289180Speter
1222289180Speter  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
1223289180Speter    {
1224289180Speter      const char *path = apr_hash_this_key(hi);
1225289180Speter      const char *token = apr_hash_this_val(hi);
1226289180Speter      const char *other;
1227289180Speter
1228289180Speter      path = svn_fspath__canonicalize(path, result_pool);
1229289180Speter      other = svn_hash_gets(canonical_targets, path);
1230289180Speter
1231289180Speter      if (!other)
1232289180Speter        svn_hash_sets(canonical_targets, path, token);
1233289180Speter    }
1234289180Speter
1235289180Speter  sorted_targets = svn_sort__hash(canonical_targets,
1236289180Speter                                  svn_sort_compare_items_as_paths,
1237289180Speter                                  scratch_pool);
1238289180Speter
1239251881Speter  ub.fs = fs;
1240289180Speter  ub.targets = sorted_targets;
1241289180Speter  ub.infos = apr_array_make(result_pool, sorted_targets->nelts,
1242289180Speter                            sizeof(struct unlock_info_t));
1243289180Speter  ub.skip_check = FALSE;
1244251881Speter  ub.break_lock = break_lock;
1245289180Speter  ub.result_pool = result_pool;
1246251881Speter
1247289180Speter  iterpool = svn_pool_create(scratch_pool);
1248289180Speter  err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, iterpool);
1249289180Speter  for (i = 0; i < ub.infos->nelts; ++i)
1250289180Speter    {
1251289180Speter      struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
1252289180Speter                                                  struct unlock_info_t);
1253289180Speter      svn_pool_clear(iterpool);
1254289180Speter      if (!cb_err && lock_callback)
1255289180Speter        {
1256289180Speter          if (!info->done && !info->fs_err)
1257289180Speter            info->fs_err = svn_error_createf(SVN_ERR_FS_LOCK_OPERATION_FAILED,
1258289180Speter                                             0, _("Failed to unlock '%s'"),
1259289180Speter                                             info->path);
1260289180Speter          cb_err = lock_callback(lock_baton, info->path, NULL, info->fs_err,
1261289180Speter                                 iterpool);
1262289180Speter        }
1263289180Speter      svn_error_clear(info->fs_err);
1264289180Speter    }
1265289180Speter  svn_pool_destroy(iterpool);
1266289180Speter
1267289180Speter  if (err && cb_err)
1268289180Speter    svn_error_compose(err, cb_err);
1269289180Speter  else if (!err)
1270289180Speter    err = cb_err;
1271289180Speter
1272289180Speter  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