source: valtobtest/subversion-1.6.2/subversion/libsvn_fs_fs/lock.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 29.6 KB
Line 
1/* lock.c :  functions for manipulating filesystem locks.
2 *
3 * ====================================================================
4 * Copyright (c) 2000-2008 CollabNet.  All rights reserved.
5 *
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution.  The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
11 *
12 * This software consists of voluntary contributions made by many
13 * individuals.  For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
16 */
17
18
19#include "svn_pools.h"
20#include "svn_error.h"
21#include "svn_path.h"
22#include "svn_fs.h"
23#include "svn_hash.h"
24#include "svn_time.h"
25#include "svn_utf.h"
26
27#include <apr_uuid.h>
28#include <apr_file_io.h>
29#include <apr_file_info.h>
30
31#include "lock.h"
32#include "tree.h"
33#include "err.h"
34#include "fs_fs.h"
35#include "../libsvn_fs/fs-loader.h"
36
37#include "private/svn_fs_util.h"
38#include "svn_private_config.h"
39
40/* Names of hash keys used to store a lock for writing to disk. */
41#define PATH_KEY "path"
42#define TOKEN_KEY "token"
43#define OWNER_KEY "owner"
44#define CREATION_DATE_KEY "creation_date"
45#define EXPIRATION_DATE_KEY "expiration_date"
46#define COMMENT_KEY "comment"
47#define IS_DAV_COMMENT_KEY "is_dav_comment"
48#define CHILDREN_KEY "children"
49
50/* Number of characters from the head of a digest file name used to
51   calculate a subdirectory in which to drop that file. */
52#define DIGEST_SUBDIR_LEN 3
53
54
55
56/*** Generic helper functions. ***/
57
58/* Return the MD5 hash of STR. */
59static const char *
60make_digest(const char *str,
61            apr_pool_t *pool)
62{
63  svn_checksum_t *checksum;
64
65  svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool);
66
67  return svn_checksum_to_cstring_display(checksum, pool);
68}
69
70
71/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
72   if unknown) to an svn_string_t-ized version of VALUE (whose size is
73   VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH.  The value
74   will be allocated in POOL; KEY will not be duped.  If either KEY or VALUE
75   is NULL, this function will do nothing. */
76static void
77hash_store(apr_hash_t *hash,
78           const char *key,
79           apr_ssize_t key_len,
80           const char *value,
81           apr_ssize_t value_len,
82           apr_pool_t *pool)
83{
84  if (! (key && value))
85    return;
86  if (value_len == APR_HASH_KEY_STRING)
87    value_len = strlen(value);
88  apr_hash_set(hash, key, key_len,
89               svn_string_ncreate(value, value_len, pool));
90}
91
92
93/* Fetch the value of KEY from HASH, returning only the cstring data
94   of that value (if it exists). */
95static const char *
96hash_fetch(apr_hash_t *hash,
97           const char *key,
98           apr_pool_t *pool)
99{
100  svn_string_t *str = apr_hash_get(hash, key, APR_HASH_KEY_STRING);
101  return str ? str->data : NULL;
102}
103
104
105
106/*** Digest file handling functions. ***/
107
108/* Return the path of the lock/entries file for which DIGEST is the
109   hashed repository relative path. */
110static const char *
111digest_path_from_digest(svn_fs_t *fs,
112                        const char *digest,
113                        apr_pool_t *pool)
114{
115  return svn_path_join_many(pool, fs->path, PATH_LOCKS_DIR,
116                            apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
117                            digest, NULL);
118}
119
120
121/* Return the path to the lock/entries digest file associate with
122   PATH, where PATH is the path to the lock file or lock entries file
123   in FS. */
124static const char *
125digest_path_from_path(svn_fs_t *fs,
126                      const char *path,
127                      apr_pool_t *pool)
128{
129  const char *digest = make_digest(path, pool);
130  return svn_path_join_many(pool, fs->path, PATH_LOCKS_DIR,
131                            apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
132                            digest, NULL);
133}
134
135
136/* Write to DIGEST_PATH a representation of CHILDREN (which may be
137   empty, if the versioned path in FS represented by DIGEST_PATH has
138   no children) and LOCK (which may be NULL if that versioned path is
139   lock itself locked).  Use POOL for all allocations. */
140static svn_error_t *
141write_digest_file(apr_hash_t *children,
142                  svn_lock_t *lock,
143                  svn_fs_t *fs,
144                  const char *digest_path,
145                  apr_pool_t *pool)
146{
147  svn_error_t *err = SVN_NO_ERROR;
148  svn_stream_t *stream;
149  apr_hash_index_t *hi;
150  apr_hash_t *hash = apr_hash_make(pool);
151  const char *tmp_path;
152  const char *rev_0_path;
153
154  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_join(fs->path, PATH_LOCKS_DIR,
155                                                     pool), fs, pool));
156  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_path_dirname(digest_path, pool), fs,
157                                       pool));
158
159  if (lock)
160    {
161      const char *creation_date = NULL, *expiration_date = NULL;
162      if (lock->creation_date)
163        creation_date = svn_time_to_cstring(lock->creation_date, pool);
164      if (lock->expiration_date)
165        expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
166      hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
167                 lock->path, APR_HASH_KEY_STRING, pool);
168      hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
169                 lock->token, APR_HASH_KEY_STRING, pool);
170      hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
171                 lock->owner, APR_HASH_KEY_STRING, pool);
172      hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
173                 lock->comment, APR_HASH_KEY_STRING, pool);
174      hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
175                 lock->is_dav_comment ? "1" : "0", 1, pool);
176      hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
177                 creation_date, APR_HASH_KEY_STRING, pool);
178      hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
179                 expiration_date, APR_HASH_KEY_STRING, pool);
180    }
181  if (apr_hash_count(children))
182    {
183      svn_stringbuf_t *children_list = svn_stringbuf_create("", pool);
184      for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
185        {
186          const void *key;
187          apr_ssize_t klen;
188          apr_hash_this(hi, &key, &klen, NULL);
189          svn_stringbuf_appendbytes(children_list, key, klen);
190          svn_stringbuf_appendbytes(children_list, "\n", 1);
191        }
192      hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
193                 children_list->data, children_list->len, pool);
194    }
195
196  SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
197                                 svn_path_dirname(digest_path, pool),
198                                 svn_io_file_del_none, pool, pool));
199  if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
200    {
201      svn_error_clear(svn_stream_close(stream));
202      return svn_error_createf(err->apr_err,
203                               err,
204                               _("Cannot write lock/entries hashfile '%s'"),
205                               svn_path_local_style(tmp_path, pool));
206    }
207
208  SVN_ERR(svn_stream_close(stream));
209  SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
210  SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
211  return svn_fs_fs__dup_perms(digest_path, rev_0_path, pool);
212}
213
214
215/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
216   file (if it exists, and if *LOCK_P is non-NULL) and the hash of
217   CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL).  Use POOL
218   for all allocations.  */
219static svn_error_t *
220read_digest_file(apr_hash_t **children_p,
221                 svn_lock_t **lock_p,
222                 svn_fs_t *fs,
223                 const char *digest_path,
224                 apr_pool_t *pool)
225{
226  svn_error_t *err = SVN_NO_ERROR;
227  svn_lock_t *lock;
228  apr_hash_t *hash;
229  svn_stream_t *stream;
230  const char *val;
231
232  if (lock_p)
233    *lock_p = NULL;
234  if (children_p)
235    *children_p = apr_hash_make(pool);
236
237  err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
238  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
239    {
240      svn_error_clear(err);
241      return SVN_NO_ERROR;
242    }
243  SVN_ERR(err);
244
245  /* If our caller doesn't care about anything but the presence of the
246     file... whatever. */
247  if (! (lock_p || children_p))
248    return svn_stream_close(stream);
249
250  hash = apr_hash_make(pool);
251  if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
252    {
253      svn_error_clear(svn_stream_close(stream));
254      return svn_error_createf(err->apr_err,
255                               err,
256                               _("Can't parse lock/entries hashfile '%s'"),
257                               svn_path_local_style(digest_path, pool));
258    }
259  SVN_ERR(svn_stream_close(stream));
260
261  /* If our caller cares, see if we have a lock path in our hash. If
262     so, we'll assume we have a lock here. */
263  val = hash_fetch(hash, PATH_KEY, pool);
264  if (val && lock_p)
265    {
266      const char *path = val;
267
268      /* Create our lock and load it up. */
269      lock = svn_lock_create(pool);
270      lock->path = path;
271
272      if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
273        return svn_fs_fs__err_corrupt_lockfile(fs, path);
274
275      if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
276        return svn_fs_fs__err_corrupt_lockfile(fs, path);
277
278      if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
279        return svn_fs_fs__err_corrupt_lockfile(fs, path);
280      lock->is_dav_comment = (val[0] == '1');
281
282      if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
283        return svn_fs_fs__err_corrupt_lockfile(fs, path);
284      SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
285
286      if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
287        SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
288
289      lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
290
291      *lock_p = lock;
292    }
293
294  /* If our caller cares, see if we have any children for this path. */
295  val = hash_fetch(hash, CHILDREN_KEY, pool);
296  if (val && children_p)
297    {
298      apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
299      int i;
300
301      for (i = 0; i < kiddos->nelts; i++)
302        {
303          apr_hash_set(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
304                       APR_HASH_KEY_STRING, (void *)1);
305        }
306    }
307  return SVN_NO_ERROR;
308}
309
310
311
312/*** Lock helper functions (path here are still FS paths, not on-disk
313     schema-supporting paths) ***/
314
315
316/* Write LOCK in FS to the actual OS filesystem. */
317static svn_error_t *
318set_lock(svn_fs_t *fs,
319         svn_lock_t *lock,
320         apr_pool_t *pool)
321{
322  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
323  svn_stringbuf_t *last_child = svn_stringbuf_create("", pool);
324  apr_pool_t *subpool;
325
326  SVN_ERR_ASSERT(lock);
327
328  /* Iterate in reverse, creating the lock for LOCK->path, and then
329     just adding entries for its parent, until we reach a parent
330     that's already listed in *its* parent. */
331  subpool = svn_pool_create(pool);
332  while (1729)
333    {
334      const char *digest_path, *parent_dir, *digest_file;
335      apr_hash_t *this_children;
336      svn_lock_t *this_lock;
337
338      svn_pool_clear(subpool);
339
340      /* Calculate the DIGEST_PATH for the currently FS path, and then
341         split it into a PARENT_DIR and DIGEST_FILE basename. */
342      digest_path = digest_path_from_path(fs, this_path->data, subpool);
343      svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
344
345      SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
346                               digest_path, subpool));
347
348      /* We're either writing a new lock (first time through only) or
349         a new entry (every time but the first). */
350      if (lock)
351        {
352          this_lock = lock;
353          lock = NULL;
354          svn_stringbuf_set(last_child, digest_file);
355        }
356      else
357        {
358          /* If we already have an entry for this path, we're done. */
359          if (apr_hash_get(this_children, last_child->data, last_child->len))
360            break;
361          apr_hash_set(this_children, last_child->data,
362                       last_child->len, (void *)1);
363        }
364      SVN_ERR(write_digest_file(this_children, this_lock, fs,
365                                digest_path, subpool));
366
367      /* Prep for next iteration, or bail if we're done. */
368      if ((this_path->len == 1) && (this_path->data[0] == '/'))
369        break;
370      svn_stringbuf_set(this_path,
371                        svn_path_dirname(this_path->data, subpool));
372    }
373
374  svn_pool_destroy(subpool);
375  return SVN_NO_ERROR;
376}
377
378/* Delete LOCK from FS in the actual OS filesystem. */
379static svn_error_t *
380delete_lock(svn_fs_t *fs,
381            svn_lock_t *lock,
382            apr_pool_t *pool)
383{
384  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
385  svn_stringbuf_t *child_to_kill = svn_stringbuf_create("", pool);
386  apr_pool_t *subpool;
387
388  SVN_ERR_ASSERT(lock);
389
390  /* Iterate in reverse, deleting the lock for LOCK->path, and then
391     pruning entries from its parents. */
392  subpool = svn_pool_create(pool);
393  while (1729)
394    {
395      const char *digest_path, *parent_dir, *digest_file;
396      apr_hash_t *this_children;
397      svn_lock_t *this_lock;
398
399      svn_pool_clear(subpool);
400
401      /* Calculate the DIGEST_PATH for the currently FS path, and then
402         split it into a PARENT_DIR and DIGEST_FILE basename. */
403      digest_path = digest_path_from_path(fs, this_path->data, subpool);
404      svn_path_split(digest_path, &parent_dir, &digest_file, subpool);
405
406      SVN_ERR(read_digest_file(&this_children, &this_lock, fs,
407                               digest_path, subpool));
408
409      /* If we are supposed to drop the last entry from this path's
410         children list, do so. */
411      if (child_to_kill->len)
412        apr_hash_set(this_children, child_to_kill->data,
413                     child_to_kill->len, NULL);
414
415      /* Delete the lock (first time through only). */
416      if (lock)
417        {
418          this_lock = NULL;
419          lock = NULL;
420        }
421
422      if (! (this_lock || apr_hash_count(this_children) != 0))
423        {
424          /* Special case:  no goodz, no file.  And remember to nix
425             the entry for it in its parent. */
426          svn_stringbuf_set(child_to_kill,
427                            svn_path_basename(digest_path, subpool));
428          SVN_ERR(svn_io_remove_file(digest_path, subpool));
429        }
430      else
431        {
432          SVN_ERR(write_digest_file(this_children, this_lock, fs,
433                                    digest_path, subpool));
434          svn_stringbuf_setempty(child_to_kill);
435        }
436
437      /* Prep for next iteration, or bail if we're done. */
438      if ((this_path->len == 1) && (this_path->data[0] == '/'))
439        break;
440      svn_stringbuf_set(this_path,
441                        svn_path_dirname(this_path->data, subpool));
442    }
443
444  svn_pool_destroy(subpool);
445  return SVN_NO_ERROR;
446}
447
448/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
449   TRUE if the caller (or one of its callers) has taken out the
450   repository-wide write lock, FALSE otherwise.  Use POOL for
451   allocations. */
452static svn_error_t *
453get_lock(svn_lock_t **lock_p,
454         svn_fs_t *fs,
455         const char *path,
456         svn_boolean_t have_write_lock,
457         apr_pool_t *pool)
458{
459  svn_lock_t *lock;
460  const char *digest_path = digest_path_from_path(fs, path, pool);
461
462  SVN_ERR(read_digest_file(NULL, &lock, fs, digest_path, pool));
463  if (! lock)
464    return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
465
466  /* Don't return an expired lock. */
467  if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
468    {
469      /* Only remove the lock if we have the write lock.
470         Read operations shouldn't change the filesystem. */
471      if (have_write_lock)
472        SVN_ERR(delete_lock(fs, lock, pool));
473      *lock_p = NULL;
474      return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
475    }
476
477  *lock_p = lock;
478  return SVN_NO_ERROR;
479}
480
481
482/* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
483   TRUE if the caller (or one of its callers) has taken out the
484   repository-wide write lock, FALSE otherwise.  Use POOL for
485   allocations. */
486static svn_error_t *
487get_lock_helper(svn_fs_t *fs,
488                svn_lock_t **lock_p,
489                const char *path,
490                svn_boolean_t have_write_lock,
491                apr_pool_t *pool)
492{
493  svn_lock_t *lock;
494  svn_error_t *err;
495
496  err = get_lock(&lock, fs, path, have_write_lock, pool);
497
498  /* We've deliberately decided that this function doesn't tell the
499     caller *why* the lock is unavailable.  */
500  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
501              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
502    {
503      svn_error_clear(err);
504      *lock_p = NULL;
505      return SVN_NO_ERROR;
506    }
507  else
508    SVN_ERR(err);
509
510  *lock_p = lock;
511  return SVN_NO_ERROR;
512}
513
514
515/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
516   all locks in and under PATH in FS.
517   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
518   has the FS write lock. */
519static svn_error_t *
520walk_digest_files(svn_fs_t *fs,
521                  const char *digest_path,
522                  svn_fs_get_locks_callback_t get_locks_func,
523                  void *get_locks_baton,
524                  svn_boolean_t have_write_lock,
525                  apr_pool_t *pool)
526{
527  apr_hash_t *children;
528  svn_lock_t *lock;
529  apr_hash_index_t *hi;
530  apr_pool_t *subpool;
531
532  /* First, send up any locks in the current digest file. */
533  SVN_ERR(read_digest_file(&children, &lock, fs, digest_path, pool));
534  if (lock)
535    {
536      /* Don't report an expired lock. */
537      if (lock->expiration_date == 0
538          || (apr_time_now() <= lock->expiration_date))
539        {
540          if (get_locks_func)
541            SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
542        }
543      else
544        {
545          /* Only remove the lock if we have the write lock.
546             Read operations shouldn't change the filesystem. */
547          if (have_write_lock)
548            SVN_ERR(delete_lock(fs, lock, pool));
549        }
550    }
551
552  /* Now, recurse on this thing's child entries (if any; bail otherwise). */
553  if (! apr_hash_count(children))
554    return SVN_NO_ERROR;
555  subpool = svn_pool_create(pool);
556  for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
557    {
558      const void *key;
559      svn_pool_clear(subpool);
560      apr_hash_this(hi, &key, NULL, NULL);
561      SVN_ERR(walk_digest_files
562              (fs, digest_path_from_digest(fs, key, subpool),
563               get_locks_func, get_locks_baton, have_write_lock, subpool));
564    }
565  svn_pool_destroy(subpool);
566  return SVN_NO_ERROR;
567}
568
569
570/* Utility function:  verify that a lock can be used.  Interesting
571   errors returned from this function:
572
573      SVN_ERR_FS_NO_USER: No username attached to FS.
574      SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
575      SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
576 */
577static svn_error_t *
578verify_lock(svn_fs_t *fs,
579            svn_lock_t *lock,
580            apr_pool_t *pool)
581{
582  if ((! fs->access_ctx) || (! fs->access_ctx->username))
583    return svn_error_createf
584      (SVN_ERR_FS_NO_USER, NULL,
585       _("Cannot verify lock on path '%s'; no username available"),
586       lock->path);
587
588  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
589    return svn_error_createf
590      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
591       _("User %s does not own lock on path '%s' (currently locked by %s)"),
592       fs->access_ctx->username, lock->path, lock->owner);
593
594  else if (apr_hash_get(fs->access_ctx->lock_tokens, lock->token,
595                        APR_HASH_KEY_STRING) == NULL)
596    return svn_error_createf
597      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
598       _("Cannot verify lock on path '%s'; no matching lock-token available"),
599       lock->path);
600
601  return SVN_NO_ERROR;
602}
603
604
605/* This implements the svn_fs_get_locks_callback_t interface, where
606   BATON is just an svn_fs_t object. */
607static svn_error_t *
608get_locks_callback(void *baton,
609                   svn_lock_t *lock,
610                   apr_pool_t *pool)
611{
612  return verify_lock(baton, lock, pool);
613}
614
615
616/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
617svn_error_t *
618svn_fs_fs__allow_locked_operation(const char *path,
619                                  svn_fs_t *fs,
620                                  svn_boolean_t recurse,
621                                  svn_boolean_t have_write_lock,
622                                  apr_pool_t *pool)
623{
624  path = svn_fs__canonicalize_abspath(path, pool);
625  if (recurse)
626    {
627      /* Discover all locks at or below the path. */
628      const char *digest_path = digest_path_from_path(fs, path, pool);
629      SVN_ERR(walk_digest_files(fs, digest_path, get_locks_callback,
630                                fs, have_write_lock, pool));
631    }
632  else
633    {
634      /* Discover and verify any lock attached to the path. */
635      svn_lock_t *lock;
636      SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
637      if (lock)
638        SVN_ERR(verify_lock(fs, lock, pool));
639    }
640  return SVN_NO_ERROR;
641}
642
643/* Baton used for lock_body below. */
644struct lock_baton {
645  svn_lock_t **lock_p;
646  svn_fs_t *fs;
647  const char *path;
648  const char *token;
649  const char *comment;
650  svn_boolean_t is_dav_comment;
651  apr_time_t expiration_date;
652  svn_revnum_t current_rev;
653  svn_boolean_t steal_lock;
654  apr_pool_t *pool;
655};
656
657
658/* This implements the svn_fs_fs__with_write_lock() 'body' callback
659   type, and assumes that the write lock is held.
660   BATON is a 'struct lock_baton *'. */
661static svn_error_t *
662lock_body(void *baton, apr_pool_t *pool)
663{
664  struct lock_baton *lb = baton;
665  svn_node_kind_t kind;
666  svn_lock_t *existing_lock;
667  svn_lock_t *lock;
668  svn_fs_root_t *root;
669  svn_revnum_t youngest;
670
671  /* Until we implement directory locks someday, we only allow locks
672     on files or non-existent paths. */
673  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
674     library dependencies, which are not portable. */
675  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
676  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
677  SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
678  if (kind == svn_node_dir)
679    return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
680
681  /* While our locking implementation easily supports the locking of
682     nonexistent paths, we deliberately choose not to allow such madness. */
683  if (kind == svn_node_none)
684    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
685                             _("Path '%s' doesn't exist in HEAD revision"),
686                             lb->path);
687
688  /* We need to have a username attached to the fs. */
689  if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
690    return SVN_FS__ERR_NO_USER(lb->fs);
691
692  /* Is the caller attempting to lock an out-of-date working file? */
693  if (SVN_IS_VALID_REVNUM(lb->current_rev))
694    {
695      svn_revnum_t created_rev;
696      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
697                                          pool));
698
699      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
700         apparently somebody is trying to lock something in their
701         working copy, but somebody else has deleted the thing
702         from HEAD.  That counts as being 'out of date'. */
703      if (! SVN_IS_VALID_REVNUM(created_rev))
704        return svn_error_createf
705          (SVN_ERR_FS_OUT_OF_DATE, NULL,
706           _("Path '%s' doesn't exist in HEAD revision"), lb->path);
707
708      if (lb->current_rev < created_rev)
709        return svn_error_createf
710          (SVN_ERR_FS_OUT_OF_DATE, NULL,
711           _("Lock failed: newer version of '%s' exists"), lb->path);
712    }
713
714  /* If the caller provided a TOKEN, we *really* need to see
715     if a lock already exists with that token, and if so, verify that
716     the lock's path matches PATH.  Otherwise we run the risk of
717     breaking the 1-to-1 mapping of lock tokens to locked paths. */
718  /* ### TODO:  actually do this check.  This is tough, because the
719     schema doesn't supply a lookup-by-token mechanism. */
720
721  /* Is the path already locked?
722
723     Note that this next function call will automatically ignore any
724     errors about {the path not existing as a key, the path's token
725     not existing as a key, the lock just having been expired}.  And
726     that's totally fine.  Any of these three errors are perfectly
727     acceptable to ignore; it means that the path is now free and
728     clear for locking, because the fsfs funcs just cleared out both
729     of the tables for us.   */
730  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
731  if (existing_lock)
732    {
733      if (! lb->steal_lock)
734        {
735          /* Sorry, the path is already locked. */
736          return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
737        }
738      else
739        {
740          /* STEAL_LOCK was passed, so fs_username is "stealing" the
741             lock from lock->owner.  Destroy the existing lock. */
742          SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
743        }
744    }
745
746  /* Create our new lock, and add it to the tables.
747     Ensure that the lock is created in the correct pool. */
748  lock = svn_lock_create(lb->pool);
749  if (lb->token)
750    lock->token = apr_pstrdup(lb->pool, lb->token);
751  else
752    SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
753                                           lb->pool));
754  lock->path = apr_pstrdup(lb->pool, lb->path);
755  lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
756  lock->comment = apr_pstrdup(lb->pool, lb->comment);
757  lock->is_dav_comment = lb->is_dav_comment;
758  lock->creation_date = apr_time_now();
759  lock->expiration_date = lb->expiration_date;
760  SVN_ERR(set_lock(lb->fs, lock, pool));
761  *lb->lock_p = lock;
762
763  return SVN_NO_ERROR;
764}
765
766/* Baton used for unlock_body below. */
767struct unlock_baton {
768  svn_fs_t *fs;
769  const char *path;
770  const char *token;
771  svn_boolean_t break_lock;
772};
773
774/* This implements the svn_fs_fs__with_write_lock() 'body' callback
775   type, and assumes that the write lock is held.
776   BATON is a 'struct unlock_baton *'. */
777static svn_error_t *
778unlock_body(void *baton, apr_pool_t *pool)
779{
780  struct unlock_baton *ub = baton;
781  svn_lock_t *lock;
782
783  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
784  SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, pool));
785
786  /* Unless breaking the lock, we do some checks. */
787  if (! ub->break_lock)
788    {
789      /* Sanity check:  the incoming token should match lock->token. */
790      if (strcmp(ub->token, lock->token) != 0)
791        return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
792
793      /* There better be a username attached to the fs. */
794      if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
795        return SVN_FS__ERR_NO_USER(ub->fs);
796
797      /* And that username better be the same as the lock's owner. */
798      if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
799        return SVN_FS__ERR_LOCK_OWNER_MISMATCH
800          (ub->fs, ub->fs->access_ctx->username, lock->owner);
801    }
802
803  /* Remove lock and lock token files. */
804  return delete_lock(ub->fs, lock, pool);
805}
806
807
808/*** Public API implementations ***/
809
810svn_error_t *
811svn_fs_fs__lock(svn_lock_t **lock_p,
812                svn_fs_t *fs,
813                const char *path,
814                const char *token,
815                const char *comment,
816                svn_boolean_t is_dav_comment,
817                apr_time_t expiration_date,
818                svn_revnum_t current_rev,
819                svn_boolean_t steal_lock,
820                apr_pool_t *pool)
821{
822  struct lock_baton lb;
823
824  SVN_ERR(svn_fs__check_fs(fs, TRUE));
825  path = svn_fs__canonicalize_abspath(path, pool);
826
827  lb.lock_p = lock_p;
828  lb.fs = fs;
829  lb.path = path;
830  lb.token = token;
831  lb.comment = comment;
832  lb.is_dav_comment = is_dav_comment;
833  lb.expiration_date = expiration_date;
834  lb.current_rev = current_rev;
835  lb.steal_lock = steal_lock;
836  lb.pool = pool;
837
838  return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
839}
840
841
842svn_error_t *
843svn_fs_fs__generate_lock_token(const char **token,
844                               svn_fs_t *fs,
845                               apr_pool_t *pool)
846{
847  SVN_ERR(svn_fs__check_fs(fs, TRUE));
848
849  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
850     want to use the fs UUID + some incremented number?  For now, we
851     generate a URI that matches the DAV RFC.  We could change this to
852     some other URI scheme someday, if we wish. */
853  *token = apr_pstrcat(pool, "opaquelocktoken:",
854                       svn_uuid_generate(pool), NULL);
855  return SVN_NO_ERROR;
856}
857
858
859svn_error_t *
860svn_fs_fs__unlock(svn_fs_t *fs,
861                  const char *path,
862                  const char *token,
863                  svn_boolean_t break_lock,
864                  apr_pool_t *pool)
865{
866  struct unlock_baton ub;
867
868  SVN_ERR(svn_fs__check_fs(fs, TRUE));
869  path = svn_fs__canonicalize_abspath(path, pool);
870
871  ub.fs = fs;
872  ub.path = path;
873  ub.token = token;
874  ub.break_lock = break_lock;
875
876  return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
877}
878
879
880svn_error_t *
881svn_fs_fs__get_lock(svn_lock_t **lock_p,
882                    svn_fs_t *fs,
883                    const char *path,
884                    apr_pool_t *pool)
885{
886  SVN_ERR(svn_fs__check_fs(fs, TRUE));
887  path = svn_fs__canonicalize_abspath(path, pool);
888  return get_lock_helper(fs, lock_p, path, FALSE, pool);
889}
890
891
892svn_error_t *
893svn_fs_fs__get_locks(svn_fs_t *fs,
894                     const char *path,
895                     svn_fs_get_locks_callback_t get_locks_func,
896                     void *get_locks_baton,
897                     apr_pool_t *pool)
898{
899  const char *digest_path;
900
901  SVN_ERR(svn_fs__check_fs(fs, TRUE));
902  path = svn_fs__canonicalize_abspath(path, pool);
903
904  /* Get the top digest path in our tree of interest, and then walk it. */
905  digest_path = digest_path_from_path(fs, path, pool);
906  return walk_digest_files(fs, digest_path, get_locks_func,
907                           get_locks_baton, FALSE, pool);
908}
Note: See TracBrowser for help on using the repository browser.