1/* lock.c :  functions for manipulating filesystem locks.
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24#include "svn_hash.h"
25#include "svn_pools.h"
26#include "svn_error.h"
27#include "svn_fs.h"
28#include "svn_private_config.h"
29
30#include <apr_uuid.h>
31
32#include "lock.h"
33#include "tree.h"
34#include "err.h"
35#include "bdb/locks-table.h"
36#include "bdb/lock-tokens-table.h"
37#include "util/fs_skels.h"
38#include "../libsvn_fs/fs-loader.h"
39#include "private/svn_fs_util.h"
40#include "private/svn_subr_private.h"
41#include "private/svn_dep_compat.h"
42
43
44/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
45   part of TRAIL. */
46static svn_error_t *
47add_lock_and_token(svn_lock_t *lock,
48                   const char *lock_token,
49                   const char *path,
50                   trail_t *trail)
51{
52  SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
53                               trail, trail->pool));
54  return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
55                                    trail, trail->pool);
56}
57
58
59/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
60   whose KIND is supplied), as part of TRAIL. */
61static svn_error_t *
62delete_lock_and_token(const char *lock_token,
63                      const char *path,
64                      trail_t *trail)
65{
66  SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
67                                  trail, trail->pool));
68  return svn_fs_bdb__lock_token_delete(trail->fs, path,
69                                       trail, trail->pool);
70}
71
72
73struct lock_args
74{
75  svn_lock_t **lock_p;
76  const char *path;
77  const char *token;
78  const char *comment;
79  svn_boolean_t is_dav_comment;
80  svn_boolean_t steal_lock;
81  apr_time_t expiration_date;
82  svn_revnum_t current_rev;
83};
84
85
86static svn_error_t *
87txn_body_lock(void *baton, trail_t *trail)
88{
89  struct lock_args *args = baton;
90  svn_node_kind_t kind = svn_node_file;
91  svn_lock_t *existing_lock;
92  svn_lock_t *lock;
93
94  SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
95
96  /* Until we implement directory locks someday, we only allow locks
97     on files or non-existent paths. */
98  if (kind == svn_node_dir)
99    return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
100
101  /* While our locking implementation easily supports the locking of
102     nonexistent paths, we deliberately choose not to allow such madness. */
103  if (kind == svn_node_none)
104    {
105      if (SVN_IS_VALID_REVNUM(args->current_rev))
106        return svn_error_createf(
107          SVN_ERR_FS_OUT_OF_DATE, NULL,
108          _("Path '%s' doesn't exist in HEAD revision"),
109          args->path);
110      else
111        return svn_error_createf(
112          SVN_ERR_FS_NOT_FOUND, NULL,
113          _("Path '%s' doesn't exist in HEAD revision"),
114          args->path);
115    }
116
117  /* There better be a username attached to the fs. */
118  if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
119    return SVN_FS__ERR_NO_USER(trail->fs);
120
121  /* Is the caller attempting to lock an out-of-date working file? */
122  if (SVN_IS_VALID_REVNUM(args->current_rev))
123    {
124      svn_revnum_t created_rev;
125      SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
126                                                trail, trail->pool));
127
128      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
129         apparently somebody is trying to lock something in their
130         working copy, but somebody else has deleted the thing
131         from HEAD.  That counts as being 'out of date'. */
132      if (! SVN_IS_VALID_REVNUM(created_rev))
133        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
134                                 "Path '%s' doesn't exist in HEAD revision",
135                                 args->path);
136
137      if (args->current_rev < created_rev)
138        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
139                                 "Lock failed: newer version of '%s' exists",
140                                 args->path);
141    }
142
143  /* If the caller provided a TOKEN, we *really* need to see
144     if a lock already exists with that token, and if so, verify that
145     the lock's path matches PATH.  Otherwise we run the risk of
146     breaking the 1-to-1 mapping of lock tokens to locked paths. */
147  if (args->token)
148    {
149      svn_lock_t *lock_from_token;
150      svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
151                                              args->token, trail,
152                                              trail->pool);
153      if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
154                  || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
155        {
156          svn_error_clear(err);
157        }
158      else
159        {
160          SVN_ERR(err);
161          if (strcmp(lock_from_token->path, args->path) != 0)
162            return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
163                                    "Lock failed: token refers to existing "
164                                    "lock with non-matching path.");
165        }
166    }
167
168  /* Is the path already locked?
169
170     Note that this next function call will automatically ignore any
171     errors about {the path not existing as a key, the path's token
172     not existing as a key, the lock just having been expired}.  And
173     that's totally fine.  Any of these three errors are perfectly
174     acceptable to ignore; it means that the path is now free and
175     clear for locking, because the bdb funcs just cleared out both
176     of the tables for us.   */
177  SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
178                                       trail, trail->pool));
179  if (existing_lock)
180    {
181      if (! args->steal_lock)
182        {
183          /* Sorry, the path is already locked. */
184          return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
185                                                 existing_lock);
186        }
187      else
188        {
189          /* ARGS->steal_lock is set, so fs_username is "stealing" the
190             lock from lock->owner.  Destroy the existing lock. */
191          SVN_ERR(delete_lock_and_token(existing_lock->token,
192                                        existing_lock->path, trail));
193        }
194    }
195
196  /* Create a new lock, and add it to the tables. */
197  lock = svn_lock_create(trail->pool);
198  if (args->token)
199    lock->token = apr_pstrdup(trail->pool, args->token);
200  else
201    SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
202                                             trail->pool));
203  lock->path = apr_pstrdup(trail->pool, args->path);
204  lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
205  lock->comment = apr_pstrdup(trail->pool, args->comment);
206  lock->is_dav_comment = args->is_dav_comment;
207  lock->creation_date = apr_time_now();
208  lock->expiration_date = args->expiration_date;
209  SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
210  *(args->lock_p) = lock;
211
212  return SVN_NO_ERROR;
213}
214
215
216
217svn_error_t *
218svn_fs_base__lock(svn_lock_t **lock,
219                  svn_fs_t *fs,
220                  const char *path,
221                  const char *token,
222                  const char *comment,
223                  svn_boolean_t is_dav_comment,
224                  apr_time_t expiration_date,
225                  svn_revnum_t current_rev,
226                  svn_boolean_t steal_lock,
227                  apr_pool_t *pool)
228{
229  struct lock_args args;
230
231  SVN_ERR(svn_fs__check_fs(fs, TRUE));
232
233  args.lock_p = lock;
234  args.path = svn_fs__canonicalize_abspath(path, pool);
235  args.token = token;
236  args.comment = comment;
237  args.is_dav_comment = is_dav_comment;
238  args.steal_lock = steal_lock;
239  args.expiration_date = expiration_date;
240  args.current_rev = current_rev;
241
242  return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
243}
244
245
246svn_error_t *
247svn_fs_base__generate_lock_token(const char **token,
248                                 svn_fs_t *fs,
249                                 apr_pool_t *pool)
250{
251  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
252     want to use the fs UUID + some incremented number?  For now, we
253     generate a URI that matches the DAV RFC.  We could change this to
254     some other URI scheme someday, if we wish. */
255  *token = apr_pstrcat(pool, "opaquelocktoken:",
256                       svn_uuid_generate(pool), (char *)NULL);
257  return SVN_NO_ERROR;
258}
259
260
261struct unlock_args
262{
263  const char *path;
264  const char *token;
265  svn_boolean_t break_lock;
266};
267
268
269static svn_error_t *
270txn_body_unlock(void *baton, trail_t *trail)
271{
272  struct unlock_args *args = baton;
273  const char *lock_token;
274  svn_lock_t *lock;
275
276  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
277  SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
278                                     trail, trail->pool));
279
280  /* If not breaking the lock, we need to do some more checking. */
281  if (!args->break_lock)
282    {
283      /* Sanity check: The lock token must exist, and must match. */
284      if (args->token == NULL)
285        return svn_fs_base__err_no_lock_token(trail->fs, args->path);
286      else if (strcmp(lock_token, args->token) != 0)
287        return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
288
289      SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
290                                   trail, trail->pool));
291
292      /* There better be a username attached to the fs. */
293      if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
294        return SVN_FS__ERR_NO_USER(trail->fs);
295
296      /* And that username better be the same as the lock's owner. */
297      if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
298        return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
299           trail->fs,
300           trail->fs->access_ctx->username,
301           lock->owner);
302    }
303
304  /* Remove a row from each of the locking tables. */
305  return delete_lock_and_token(lock_token, args->path, trail);
306}
307
308
309svn_error_t *
310svn_fs_base__unlock(svn_fs_t *fs,
311                    const char *path,
312                    const char *token,
313                    svn_boolean_t break_lock,
314                    apr_pool_t *pool)
315{
316  struct unlock_args args;
317
318  SVN_ERR(svn_fs__check_fs(fs, TRUE));
319
320  args.path = svn_fs__canonicalize_abspath(path, pool);
321  args.token = token;
322  args.break_lock = break_lock;
323  return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
324}
325
326
327svn_error_t *
328svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
329                             const char *path,
330                             trail_t *trail,
331                             apr_pool_t *pool)
332{
333  const char *lock_token;
334  svn_error_t *err;
335
336  err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
337                                   trail, pool);
338
339  /* We've deliberately decided that this function doesn't tell the
340     caller *why* the lock is unavailable.  */
341  if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
342              || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
343              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
344    {
345      svn_error_clear(err);
346      *lock_p = NULL;
347      return SVN_NO_ERROR;
348    }
349  else
350    SVN_ERR(err);
351
352  /* Same situation here.  */
353  err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
354  if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
355              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
356    {
357      svn_error_clear(err);
358      *lock_p = NULL;
359      return SVN_NO_ERROR;
360    }
361  else
362    SVN_ERR(err);
363
364  return svn_error_trace(err);
365}
366
367
368struct lock_token_get_args
369{
370  svn_lock_t **lock_p;
371  const char *path;
372};
373
374
375static svn_error_t *
376txn_body_get_lock(void *baton, trail_t *trail)
377{
378  struct lock_token_get_args *args = baton;
379  return svn_fs_base__get_lock_helper(args->lock_p, args->path,
380                                      trail, trail->pool);
381}
382
383
384svn_error_t *
385svn_fs_base__get_lock(svn_lock_t **lock,
386                      svn_fs_t *fs,
387                      const char *path,
388                      apr_pool_t *pool)
389{
390  struct lock_token_get_args args;
391
392  SVN_ERR(svn_fs__check_fs(fs, TRUE));
393
394  args.path = svn_fs__canonicalize_abspath(path, pool);
395  args.lock_p = lock;
396  return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
397}
398
399/* Implements `svn_fs_get_locks_callback_t', spooling lock information
400   to a stream as the filesystem provides it.  BATON is an 'svn_stream_t *'
401   object pointing to the stream.  We'll write the spool stream with a
402   format like so:
403
404      SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
405
406   where each skel is a lock skel (the same format we use to store
407   locks in the `locks' table). */
408static svn_error_t *
409spool_locks_info(void *baton,
410                 svn_lock_t *lock,
411                 apr_pool_t *pool)
412{
413  svn_skel_t *lock_skel;
414  svn_stream_t *stream = baton;
415  const char *skel_len;
416  svn_stringbuf_t *skel_buf;
417  apr_size_t len;
418
419  SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
420  skel_buf = svn_skel__unparse(lock_skel, pool);
421  skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
422  len = strlen(skel_len);
423  SVN_ERR(svn_stream_write(stream, skel_len, &len));
424  len = skel_buf->len;
425  SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
426  len = 1;
427  return svn_stream_write(stream, "\n", &len);
428}
429
430
431struct locks_get_args
432{
433  const char *path;
434  svn_depth_t depth;
435  svn_stream_t *stream;
436};
437
438
439static svn_error_t *
440txn_body_get_locks(void *baton, trail_t *trail)
441{
442  struct locks_get_args *args = baton;
443  return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
444                               spool_locks_info, args->stream,
445                               trail, trail->pool);
446}
447
448
449svn_error_t *
450svn_fs_base__get_locks(svn_fs_t *fs,
451                       const char *path,
452                       svn_depth_t depth,
453                       svn_fs_get_locks_callback_t get_locks_func,
454                       void *get_locks_baton,
455                       apr_pool_t *pool)
456{
457  struct locks_get_args args;
458  svn_stream_t *stream;
459  svn_stringbuf_t *buf;
460  svn_boolean_t eof;
461  apr_pool_t *iterpool = svn_pool_create(pool);
462
463  SVN_ERR(svn_fs__check_fs(fs, TRUE));
464
465  args.path = svn_fs__canonicalize_abspath(path, pool);
466  args.depth = depth;
467  /* Enough for 100+ locks if the comments are small. */
468  args.stream = svn_stream__from_spillbuf(4 * 1024  /* blocksize */,
469                                          64 * 1024 /* maxsize */,
470                                          pool);
471  SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
472
473  /* Read the stream calling GET_LOCKS_FUNC(). */
474  stream = args.stream;
475
476  while (1)
477    {
478      apr_size_t len, skel_len;
479      char c, *skel_buf;
480      svn_skel_t *lock_skel;
481      svn_lock_t *lock;
482      apr_uint64_t ui64;
483      svn_error_t *err;
484
485      svn_pool_clear(iterpool);
486
487      /* Read a skel length line and parse it for the skel's length.  */
488      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
489      if (eof)
490        break;
491      err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
492      if (err)
493        return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
494      skel_len = (apr_size_t)ui64;
495
496      /* Now read that much into a buffer. */
497      skel_buf = apr_palloc(pool, skel_len + 1);
498      SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len));
499      skel_buf[skel_len] = '\0';
500
501      /* Read the extra newline that follows the skel. */
502      len = 1;
503      SVN_ERR(svn_stream_read(stream, &c, &len));
504      if (c != '\n')
505        return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
506
507      /* Parse the skel into a lock, and notify the caller. */
508      lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
509      SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
510      SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
511    }
512
513  SVN_ERR(svn_stream_close(stream));
514  svn_pool_destroy(iterpool);
515  return SVN_NO_ERROR;
516}
517
518
519
520/* Utility function:  verify that a lock can be used.
521
522   If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
523
524   If the FS username doesn't match LOCK's owner, return
525   SVN_ERR_FS_LOCK_OWNER_MISMATCH.
526
527   If FS hasn't been supplied with a matching lock-token for LOCK,
528   return SVN_ERR_FS_BAD_LOCK_TOKEN.
529
530   Otherwise return SVN_NO_ERROR.
531 */
532static svn_error_t *
533verify_lock(svn_fs_t *fs,
534            svn_lock_t *lock,
535            apr_pool_t *pool)
536{
537  if ((! fs->access_ctx) || (! fs->access_ctx->username))
538    return svn_error_createf
539      (SVN_ERR_FS_NO_USER, NULL,
540       _("Cannot verify lock on path '%s'; no username available"),
541       lock->path);
542
543  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
544    return svn_error_createf
545      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
546       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
547       fs->access_ctx->username, lock->path, lock->owner);
548
549  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
550    return svn_error_createf
551      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
552       _("Cannot verify lock on path '%s'; no matching lock-token available"),
553       lock->path);
554
555  return SVN_NO_ERROR;
556}
557
558
559/* This implements the svn_fs_get_locks_callback_t interface, where
560   BATON is just an svn_fs_t object. */
561static svn_error_t *
562get_locks_callback(void *baton,
563                   svn_lock_t *lock,
564                   apr_pool_t *pool)
565{
566  return verify_lock(baton, lock, pool);
567}
568
569
570/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
571svn_error_t *
572svn_fs_base__allow_locked_operation(const char *path,
573                                    svn_boolean_t recurse,
574                                    trail_t *trail,
575                                    apr_pool_t *pool)
576{
577  if (recurse)
578    {
579      /* Discover all locks at or below the path. */
580      SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
581                                    get_locks_callback,
582                                    trail->fs, trail, pool));
583    }
584  else
585    {
586      svn_lock_t *lock;
587
588      /* Discover any lock attached to the path. */
589      SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
590      if (lock)
591        SVN_ERR(verify_lock(trail->fs, lock, pool));
592    }
593  return SVN_NO_ERROR;
594}
595