]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_base/lock.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_base / lock.c
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. */
46 static svn_error_t *
47 add_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. */
61 static svn_error_t *
62 delete_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
73 struct 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
86 static svn_error_t *
87 txn_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
217 svn_error_t *
218 svn_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
246 svn_error_t *
247 svn_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
261 struct unlock_args
262 {
263   const char *path;
264   const char *token;
265   svn_boolean_t break_lock;
266 };
267
268
269 static svn_error_t *
270 txn_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
309 svn_error_t *
310 svn_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
327 svn_error_t *
328 svn_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
368 struct lock_token_get_args
369 {
370   svn_lock_t **lock_p;
371   const char *path;
372 };
373
374
375 static svn_error_t *
376 txn_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
384 svn_error_t *
385 svn_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). */
408 static svn_error_t *
409 spool_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
431 struct locks_get_args
432 {
433   const char *path;
434   svn_depth_t depth;
435   svn_stream_t *stream;
436 };
437
438
439 static svn_error_t *
440 txn_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
449 svn_error_t *
450 svn_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  */
532 static svn_error_t *
533 verify_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. */
561 static svn_error_t *
562 get_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. */
571 svn_error_t *
572 svn_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 }