]> granicus.if.org Git - apache/blob - modules/dav/fs/lock.c
1d1c88c7b403d0ab0882824f28ed1fb65eeafd9d
[apache] / modules / dav / fs / lock.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56 ** DAV filesystem lock implementation
57 */
58
59 #include "apr.h"
60 #include "apr_strings.h"
61 #include "apr_file_io.h"
62 #include "apr_uuid.h"
63
64 #define APR_WANT_MEMFUNC
65 #include "apr_want.h"
66
67 #include "httpd.h"
68 #include "http_log.h"
69
70 #include "mod_dav.h"
71 #include "repos.h"
72
73
74 /* ---------------------------------------------------------------
75 **
76 ** Lock database primitives
77 **
78 */
79
80 /*
81 ** LOCK DATABASES
82 ** 
83 ** Lockdiscovery information is stored in the single lock database specified
84 ** by the DAVLockDB directive.  Information about this db is stored in the
85 ** global server configuration.
86 **
87 ** KEY
88 **
89 ** The database is keyed by a key_type unsigned char (DAV_TYPE_INODE or
90 ** DAV_TYPE_FNAME) followed by inode and device number if possible,
91 ** otherwise full path (in the case of Win32 or lock-null resources).
92 **
93 ** VALUE
94 **
95 ** The value consists of a list of elements.
96 **    DIRECT LOCK:     [char      (DAV_LOCK_DIRECT),
97 **                      char      (dav_lock_scope),
98 **                      char      (dav_lock_type),
99 **                      int        depth,
100 **                      time_t     expires,
101 **                      apr_uuid_t locktoken,
102 **                      char[]     owner,
103 **                      char[]     auth_user]
104 **
105 **    INDIRECT LOCK:   [char      (DAV_LOCK_INDIRECT),
106 **                      apr_uuid_t locktoken,
107 **                      time_t     expires,
108 **                      int        key_size,
109 **                      char[]     key]
110 **       The key is to the collection lock that resulted in this indirect lock
111 */
112
113 #define DAV_TRUE                1
114 #define DAV_FALSE               0
115
116 #define DAV_CREATE_LIST         23
117 #define DAV_APPEND_LIST         24
118
119 /* Stored lock_discovery prefix */
120 #define DAV_LOCK_DIRECT         1
121 #define DAV_LOCK_INDIRECT       2
122
123 #define DAV_TYPE_INODE          10
124 #define DAV_TYPE_FNAME          11
125
126
127 /* ack. forward declare. */
128 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
129                                                  const char *filename,
130                                                  dav_buffer *pbuf);
131
132 /*
133 ** Use the opaquelock scheme for locktokens
134 */
135 struct dav_locktoken {
136     apr_uuid_t uuid;
137 };
138 #define dav_compare_locktoken(plt1, plt2) \
139                 memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
140
141
142 /* #################################################################
143 ** ### keep these structures (internal) or move fully to dav_lock?
144 */
145
146 /*
147 ** We need to reliably size the fixed-length portion of
148 ** dav_lock_discovery; best to separate it into another 
149 ** struct for a convenient sizeof, unless we pack lock_discovery.
150 */
151 typedef struct dav_lock_discovery_fixed
152 {
153     char scope;
154     char type;
155     int depth;
156     time_t timeout;
157 } dav_lock_discovery_fixed;
158
159 typedef struct dav_lock_discovery
160 {
161     struct dav_lock_discovery_fixed f;
162
163     dav_locktoken *locktoken;
164     const char *owner;          /* owner field from activelock */
165     const char *auth_user;      /* authenticated user who created the lock */
166     struct dav_lock_discovery *next;
167 } dav_lock_discovery;
168
169 /* Indirect locks represent locks inherited from containing collections.
170  * They reference the lock token for the collection the lock is
171  * inherited from. A lock provider may also define a key to the
172  * inherited lock, for fast datbase lookup. The key is opaque outside
173  * the lock provider.
174  */
175 typedef struct dav_lock_indirect
176 {
177     dav_locktoken *locktoken;
178     apr_datum_t key;
179     struct dav_lock_indirect *next;
180     time_t timeout;
181 } dav_lock_indirect;
182
183 /* ################################################################# */
184
185
186 /*
187 ** Stored direct lock info - full lock_discovery length:  
188 ** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
189 */
190 #define dav_size_direct(a)      (1 + sizeof(dav_lock_discovery_fixed) \
191                                  + sizeof(apr_uuid_t) \
192                                  + ((a)->owner ? strlen((a)->owner) : 0) \
193                                  + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
194                                  + 2)
195
196 /* Stored indirect lock info - lock token and apr_datum_t */
197 #define dav_size_indirect(a)    (1 + sizeof(apr_uuid_t) \
198                                  + sizeof(time_t) \
199                                  + sizeof(int) + (a)->key.dsize)
200
201 /*
202 ** The lockdb structure.
203 **
204 ** The <db> field may be NULL, meaning one of two things:
205 ** 1) That we have not actually opened the underlying database (yet). The
206 **    <opened> field should be false.
207 ** 2) We opened it readonly and it wasn't present.
208 **
209 ** The delayed opening (determined by <opened>) makes creating a lockdb
210 ** quick, while deferring the underlying I/O until it is actually required.
211 **
212 ** We export the notion of a lockdb, but hide the details of it. Most
213 ** implementations will use a database of some kind, but it is certainly
214 ** possible that alternatives could be used.
215 */
216 struct dav_lockdb_private
217 {
218     request_rec *r;                     /* for accessing the uuid state */
219     apr_pool_t *pool;                   /* a pool to use */
220     const char *lockdb_path;            /* where is the lock database? */
221
222     int opened;                         /* we opened the database */
223     dav_db *db;                         /* if non-NULL, the lock database */
224 };
225 typedef struct
226 {
227     dav_lockdb pub;
228     dav_lockdb_private priv;
229 } dav_lockdb_combined;
230
231 /*
232 ** The private part of the lock structure.
233 */
234 struct dav_lock_private
235 {
236     apr_datum_t key;    /* key into the lock database */
237 };
238 typedef struct
239 {
240     dav_lock pub;
241     dav_lock_private priv;
242     dav_locktoken token;
243 } dav_lock_combined;
244
245 /*
246 ** This must be forward-declared so the open_lockdb function can use it.
247 */
248 extern const dav_hooks_locks dav_hooks_locks_fs;
249
250
251 /* internal function for creating locks */
252 static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
253                                    const dav_locktoken *locktoken)
254 {
255     dav_lock_combined *comb;
256
257     comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
258     comb->pub.rectype = DAV_LOCKREC_DIRECT;
259     comb->pub.info = &comb->priv;
260     comb->priv.key = key;
261
262     if (locktoken == NULL) {
263         comb->pub.locktoken = &comb->token;
264         apr_uuid_get(&comb->token.uuid);
265     }
266     else {
267         comb->pub.locktoken = locktoken;
268     }
269
270     return &comb->pub;
271 }
272
273 /*
274 ** dav_fs_parse_locktoken
275 **
276 ** Parse an opaquelocktoken URI into a locktoken.
277 */
278 static dav_error * dav_fs_parse_locktoken(
279     apr_pool_t *p,
280     const char *char_token,
281     dav_locktoken **locktoken_p)
282 {
283     dav_locktoken *locktoken;
284
285     if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
286         return dav_new_error(p,
287                              HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN,
288                              "The lock token uses an unknown State-token "
289                              "format and could not be parsed.");
290     }
291     char_token += 16;
292
293     locktoken = apr_pcalloc(p, sizeof(*locktoken));
294     if (apr_uuid_parse(&locktoken->uuid, char_token)) {
295         return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN,
296                              "The opaquelocktoken has an incorrect format "
297                              "and could not be parsed.");
298     }
299     
300     *locktoken_p = locktoken;
301     return NULL;
302 }
303
304 /*
305 ** dav_fs_format_locktoken
306 **
307 ** Generate the URI for a locktoken
308 */
309 static const char *dav_fs_format_locktoken(
310     apr_pool_t *p,
311     const dav_locktoken *locktoken)
312 {
313     char buf[APR_UUID_FORMATTED_LENGTH + 1];
314
315     apr_uuid_format(buf, &locktoken->uuid);
316     return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
317 }
318
319 /*
320 ** dav_fs_compare_locktoken
321 **
322 ** Determine whether two locktokens are the same
323 */
324 static int dav_fs_compare_locktoken(
325     const dav_locktoken *lt1,
326     const dav_locktoken *lt2)
327 {
328     return dav_compare_locktoken(lt1, lt2);
329 }
330
331 /*
332 ** dav_fs_really_open_lockdb:
333 **
334 ** If the database hasn't been opened yet, then open the thing.
335 */
336 static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
337 {
338     dav_error *err;
339
340     if (lockdb->info->opened)
341         return NULL;
342
343     err = dav_dbm_open_direct(lockdb->info->pool,
344                               lockdb->info->lockdb_path,
345                               lockdb->ro,
346                               &lockdb->info->db);
347     if (err != NULL) {
348         return dav_push_error(lockdb->info->pool,
349                               HTTP_INTERNAL_SERVER_ERROR,
350                               DAV_ERR_LOCK_OPENDB,
351                               "Could not open the lock database.",
352                               err);
353     }
354
355     /* all right. it is opened now. */
356     lockdb->info->opened = 1;
357
358     return NULL;
359 }
360
361 /*
362 ** dav_fs_open_lockdb:
363 **
364 ** "open" the lock database, as specified in the global server configuration.
365 ** If force is TRUE, then the database is opened now, rather than lazily.
366 **
367 ** Note that only one can be open read/write.
368 */
369 static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
370                                       dav_lockdb **lockdb)
371 {
372     dav_lockdb_combined *comb;
373
374     comb = apr_pcalloc(r->pool, sizeof(*comb));
375     comb->pub.hooks = &dav_hooks_locks_fs;
376     comb->pub.ro = ro;
377     comb->pub.info = &comb->priv;
378     comb->priv.r = r;
379     comb->priv.pool = r->pool;
380
381     comb->priv.lockdb_path = dav_get_lockdb_path(r);
382     if (comb->priv.lockdb_path == NULL) {
383         return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
384                              DAV_ERR_LOCK_NO_DB,
385                              "A lock database was not specified with the "
386                              "DAVLockDB directive. One must be specified "
387                              "to use the locking functionality.");
388     }
389
390     /* done initializing. return it. */
391     *lockdb = &comb->pub;
392
393     if (force) {
394         /* ### add a higher-level comment? */
395         return dav_fs_really_open_lockdb(*lockdb);
396     }
397
398     return NULL;
399 }
400
401 /*
402 ** dav_fs_close_lockdb:
403 **
404 ** Close it. Duh.
405 */
406 static void dav_fs_close_lockdb(dav_lockdb *lockdb)
407 {
408     if (lockdb->info->db != NULL)
409         dav_dbm_close(lockdb->info->db);
410 }
411
412 /*
413 ** dav_fs_build_fname_key
414 **
415 ** Given a pathname, build a DAV_TYPE_FNAME lock database key.
416 */
417 static apr_datum_t dav_fs_build_fname_key(apr_pool_t *p, const char *pathname)
418 {
419     apr_datum_t key;
420
421     /* ### does this allocation have a proper lifetime? need to check */
422     /* ### can we use a buffer for this? */
423
424     /* size is TYPE + pathname + null */
425     key.dsize = strlen(pathname) + 2;
426     key.dptr = apr_palloc(p, key.dsize);
427     *key.dptr = DAV_TYPE_FNAME;
428     memcpy(key.dptr + 1, pathname, key.dsize - 1);
429     if (key.dptr[key.dsize - 2] == '/')
430         key.dptr[--key.dsize - 1] = '\0';
431     return key;
432 }
433
434 /*
435 ** dav_fs_build_key:  Given a resource, return a apr_datum_t key
436 **    to look up lock information for this file.
437 **
438 **    (inode/dev not supported or file is lock-null):
439 **       apr_datum_t->dvalue = full path
440 **
441 **    (inode/dev supported and file exists ):
442 **       apr_datum_t->dvalue = inode, dev
443 */
444 static apr_datum_t dav_fs_build_key(apr_pool_t *p,
445                                     const dav_resource *resource)
446 {
447     const char *file = dav_fs_pathname(resource);
448     apr_datum_t key;
449     apr_finfo_t finfo;
450     apr_status_t rv;
451
452     /* ### use lstat() ?? */
453     /*
454      * XXX: What for platforms with no IDENT (dev/inode)?
455      */
456     rv = apr_stat(&finfo, file, APR_FINFO_IDENT, p);
457     if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
458         && ((finfo.valid & APR_FINFO_IDENT) == APR_FINFO_IDENT))
459     {
460         /* ### can we use a buffer for this? */
461         key.dsize = 1 + sizeof(finfo.inode) + sizeof(finfo.device);
462         key.dptr = apr_palloc(p, key.dsize);
463         *key.dptr = DAV_TYPE_INODE;
464         memcpy(key.dptr + 1, &finfo.inode, sizeof(finfo.inode));
465         memcpy(key.dptr + 1 + sizeof(finfo.inode), &finfo.device,
466                sizeof(finfo.device));
467
468         return key;
469     }
470
471     return dav_fs_build_fname_key(p, file);
472 }
473
474 /*
475 ** dav_fs_lock_expired:  return 1 (true) if the given timeout is in the past
476 **    or present (the lock has expired), or 0 (false) if in the future
477 **    (the lock has not yet expired).
478 */
479 static int dav_fs_lock_expired(time_t expires)
480 {
481     return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
482 }
483
484 /*
485 ** dav_fs_save_lock_record:  Saves the lock information specified in the
486 **    direct and indirect lock lists about path into the lock database.
487 **    If direct and indirect == NULL, the key is removed.
488 */
489 static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key,
490                                            dav_lock_discovery *direct,
491                                            dav_lock_indirect *indirect)
492 {
493     dav_error *err;
494     apr_datum_t val = { 0 };
495     char *ptr;
496     dav_lock_discovery *dp = direct;
497     dav_lock_indirect *ip = indirect;
498
499 #if DAV_DEBUG
500     if (lockdb->ro) {
501         return dav_new_error(lockdb->info->pool,
502                              HTTP_INTERNAL_SERVER_ERROR, 0,
503                              "INTERNAL DESIGN ERROR: the lockdb was opened "
504                              "readonly, but an attempt to save locks was "
505                              "performed.");
506     }
507 #endif
508
509     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
510         /* ### add a higher-level error? */
511         return err;
512     }
513
514     /* If nothing to save, delete key */
515     if (dp == NULL && ip == NULL) {
516         /* don't fail if the key is not present */
517         /* ### but what about other errors? */
518         (void) dav_dbm_delete(lockdb->info->db, key);
519         return NULL;
520     }
521                 
522     while(dp) {
523         val.dsize += dav_size_direct(dp);
524         dp = dp->next;
525     }
526     while(ip) {
527         val.dsize += dav_size_indirect(ip);
528         ip = ip->next;
529     }
530
531     /* ### can this be apr_palloc() ? */
532     /* ### hmmm.... investigate the use of a buffer here */
533     ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
534     dp  = direct;
535     ip  = indirect;
536
537     while(dp) {
538         *ptr++ = DAV_LOCK_DIRECT;       /* Direct lock - lock_discovery struct follows */
539         memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
540         ptr += sizeof(dp->f);
541         memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
542         ptr += sizeof(*dp->locktoken);
543         if (dp->owner == NULL) {
544             *ptr++ = '\0';
545         }
546         else {
547             memcpy(ptr, dp->owner, strlen(dp->owner) + 1);      
548             ptr += strlen(dp->owner) + 1;
549         }
550         if (dp->auth_user == NULL) {
551             *ptr++ = '\0';
552         }
553         else {
554             memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
555             ptr += strlen(dp->auth_user) + 1;
556         }
557
558         dp = dp->next;
559     }
560
561     while(ip) {
562         *ptr++ = DAV_LOCK_INDIRECT;     /* Indirect lock prefix */
563         memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));     /* Locktoken */
564         ptr += sizeof(*ip->locktoken);
565         memcpy(ptr, &ip->timeout, sizeof(ip->timeout));         /* Expire time */
566         ptr += sizeof(ip->timeout);
567         memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));     /* Size of key */
568         ptr += sizeof(ip->key.dsize);
569         memcpy(ptr, ip->key.dptr, ip->key.dsize);       /* Key data */
570         ptr += ip->key.dsize;
571         ip = ip->next;
572     }
573
574     if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) {
575         /* ### more details? add an error_id? */
576         return dav_push_error(lockdb->info->pool,
577                               HTTP_INTERNAL_SERVER_ERROR,
578                               DAV_ERR_LOCK_SAVE_LOCK,
579                               "Could not save lock information.",
580                               err);
581     }
582
583     return NULL;
584 }
585
586 /*
587 ** dav_load_lock_record:  Reads lock information about key from lock db;
588 **    creates linked lists of the direct and indirect locks.
589 **
590 **    If add_method = DAV_APPEND_LIST, the result will be appended to the
591 **    head of the direct and indirect lists supplied.
592 **
593 **    Passive lock removal:  If lock has timed out, it will not be returned.
594 **    ### How much "logging" does RFC 2518 require?
595 */
596 static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
597                                            int add_method,
598                                            dav_lock_discovery **direct,
599                                            dav_lock_indirect **indirect)
600 {
601     apr_pool_t *p = lockdb->info->pool;
602     dav_error *err;
603     apr_size_t offset = 0;
604     int need_save = DAV_FALSE;
605     apr_datum_t val = { 0 };
606     dav_lock_discovery *dp;
607     dav_lock_indirect *ip;
608     dav_buffer buf = { 0 };
609
610     if (add_method != DAV_APPEND_LIST) {
611         *direct = NULL;
612         *indirect = NULL;
613     }
614
615     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
616         /* ### add a higher-level error? */
617         return err;
618     }
619
620     /*
621     ** If we opened readonly and the db wasn't there, then there are no
622     ** locks for this resource. Just exit.
623     */
624     if (lockdb->info->db == NULL)
625         return NULL;
626
627     if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
628         return err;
629         
630     if (!val.dsize)
631         return NULL;
632
633     while (offset < val.dsize) {
634         switch (*(val.dptr + offset++)) {
635         case DAV_LOCK_DIRECT:
636             /* Create and fill a dav_lock_discovery structure */
637
638             dp = apr_pcalloc(p, sizeof(*dp));
639             memcpy(dp, val.dptr + offset, sizeof(dp->f));
640             offset += sizeof(dp->f);
641             dp->locktoken = apr_palloc(p, sizeof(*dp->locktoken));
642             memcpy(dp->locktoken, val.dptr + offset, sizeof(*dp->locktoken));
643             offset += sizeof(*dp->locktoken);
644             if (*(val.dptr + offset) == '\0') {
645                 ++offset;
646             }
647             else {
648                 dp->owner = apr_pstrdup(p, val.dptr + offset);
649                 offset += strlen(dp->owner) + 1;
650             }
651
652             if (*(val.dptr + offset) == '\0') {
653                 ++offset;
654             } 
655             else {
656                 dp->auth_user = apr_pstrdup(p, val.dptr + offset);
657                 offset += strlen(dp->auth_user) + 1;
658             }
659
660             if (!dav_fs_lock_expired(dp->f.timeout)) {
661                 dp->next = *direct;
662                 *direct = dp;
663             }
664             else {
665                 need_save = DAV_TRUE;
666
667                 /* Remove timed-out locknull fm .locknull list */
668                 if (*key.dptr == DAV_TYPE_FNAME) {
669                     const char *fname = key.dptr + 1;
670                     apr_finfo_t finfo;
671                     apr_status_t rv;
672
673                     /* if we don't see the file, then it's a locknull */
674                     rv = apr_lstat(&finfo, fname, APR_FINFO_MIN, p);
675                     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
676                         if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) {
677                             /* ### push a higher-level description? */
678                             return err;
679                         }
680                     }
681                 }
682             }
683             break;
684
685         case DAV_LOCK_INDIRECT:
686             /* Create and fill a dav_lock_indirect structure */
687
688             ip = apr_pcalloc(p, sizeof(*ip));
689             ip->locktoken = apr_palloc(p, sizeof(*ip->locktoken));
690             memcpy(ip->locktoken, val.dptr + offset, sizeof(*ip->locktoken));
691             offset += sizeof(*ip->locktoken);
692             memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
693             offset += sizeof(ip->timeout);
694             ip->key.dsize = *((int *) (val.dptr + offset));     /* length of datum */
695             offset += sizeof(ip->key.dsize);
696             ip->key.dptr = apr_palloc(p, ip->key.dsize); 
697             memcpy(ip->key.dptr, val.dptr + offset, ip->key.dsize);
698             offset += ip->key.dsize;
699
700             if (!dav_fs_lock_expired(ip->timeout)) {
701                 ip->next = *indirect;
702                 *indirect = ip;
703             }
704             else {
705                 need_save = DAV_TRUE;
706                 /* A locknull resource will never be locked indirectly */
707             }
708
709             break;
710
711         default:
712             dav_dbm_freedatum(lockdb->info->db, val);
713
714             /* ### should use a computed_desc and insert corrupt token data */
715             --offset;
716             return dav_new_error(p,
717                                  HTTP_INTERNAL_SERVER_ERROR,
718                                  DAV_ERR_LOCK_CORRUPT_DB,
719                                  apr_psprintf(p,
720                                              "The lock database was found to "
721                                              "be corrupt. offset %"
722                                              APR_SIZE_T_FMT ", c=%02x",
723                                              offset, val.dptr[offset]));
724         }
725     }
726
727     dav_dbm_freedatum(lockdb->info->db, val);
728
729     /* Clean up this record if we found expired locks */
730     /*
731     ** ### shouldn't do this if we've been opened READONLY. elide the
732     ** ### timed-out locks from the response, but don't save that info back
733     */
734     if (need_save == DAV_TRUE) {
735         return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
736     }
737
738     return NULL;
739 }
740
741 /* resolve <indirect>, returning <*direct> */
742 static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
743                                   dav_lock_indirect *indirect,
744                                   dav_lock_discovery **direct,
745                                   dav_lock_discovery **ref_dp,
746                                   dav_lock_indirect **ref_ip)
747 {
748     dav_error *err;
749     dav_lock_discovery *dir;
750     dav_lock_indirect *ind;
751         
752     if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
753                                        DAV_CREATE_LIST,
754                                        &dir, &ind)) != NULL) {
755         /* ### insert a higher-level description? */
756         return err;
757     }
758     if (ref_dp != NULL) {
759         *ref_dp = dir;
760         *ref_ip = ind;
761     }
762                 
763     for (; dir != NULL; dir = dir->next) {
764         if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
765             *direct = dir;
766             return NULL;
767         }
768     }
769
770     /* No match found (but we should have found one!) */
771
772     /* ### use a different description and/or error ID? */
773     return dav_new_error(lockdb->info->pool,
774                          HTTP_INTERNAL_SERVER_ERROR,
775                          DAV_ERR_LOCK_CORRUPT_DB,
776                          "The lock database was found to be corrupt. "
777                          "An indirect lock's direct lock could not "
778                          "be found.");
779 }
780
781 /* ---------------------------------------------------------------
782 **
783 ** Property-related lock functions
784 **
785 */
786
787 /*
788 ** dav_fs_get_supportedlock:  Returns a static string for all supportedlock
789 **    properties. I think we save more returning a static string than
790 **    constructing it every time, though it might look cleaner.
791 */
792 static const char *dav_fs_get_supportedlock(const dav_resource *resource)
793 {
794     static const char supported[] = DEBUG_CR
795         "<D:lockentry>" DEBUG_CR
796         "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
797         "<D:locktype><D:write/></D:locktype>" DEBUG_CR
798         "</D:lockentry>" DEBUG_CR
799         "<D:lockentry>" DEBUG_CR
800         "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
801         "<D:locktype><D:write/></D:locktype>" DEBUG_CR
802         "</D:lockentry>" DEBUG_CR;
803
804     return supported;
805 }
806
807 /* ---------------------------------------------------------------
808 **
809 ** General lock functions
810 **
811 */
812
813 /* ---------------------------------------------------------------
814 **
815 ** Functions dealing with lock-null resources
816 **
817 */
818
819 /*
820 ** dav_fs_load_locknull_list:  Returns a dav_buffer dump of the locknull file
821 **    for the given directory.
822 */
823 static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
824                                              dav_buffer *pbuf) 
825 {
826     apr_finfo_t finfo;
827     apr_file_t *file = NULL;
828     dav_error *err = NULL;
829     apr_size_t amt;
830     apr_status_t rv;
831
832     dav_buffer_init(p, pbuf, dirpath);
833
834     if (pbuf->buf[pbuf->cur_len - 1] == '/')
835         pbuf->buf[--pbuf->cur_len] = '\0';
836
837     dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
838
839     /* reset this in case we leave w/o reading into the buffer */
840     pbuf->cur_len = 0;
841
842     if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
843                 p) != APR_SUCCESS) {
844         return NULL;
845     }
846
847     rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
848     if (rv != APR_SUCCESS) {
849         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
850                             apr_psprintf(p,
851                                         "Opened but could not stat file %s",
852                                         pbuf->buf));
853         goto loaderror;
854     }
855
856     if (finfo.size != (apr_size_t)finfo.size) {
857         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
858                             apr_psprintf(p,
859                                         "Opened but rejected huge file %s",
860                                         pbuf->buf));
861         goto loaderror;
862     }
863
864     amt = (apr_size_t)finfo.size;
865     dav_set_bufsize(p, pbuf, amt);
866     if (apr_file_read(file, pbuf->buf, &amt) != APR_SUCCESS
867         || amt != finfo.size) {
868         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
869                             apr_psprintf(p,
870                                         "Failure reading locknull file "
871                                         "for %s", dirpath));
872
873         /* just in case the caller disregards the returned error */
874         pbuf->cur_len = 0;
875         goto loaderror;
876     }
877
878   loaderror:
879     apr_file_close(file);
880     return err;
881 }
882
883 /*
884 ** dav_fs_save_locknull_list:  Saves contents of pbuf into the
885 **    locknull file for dirpath.
886 */
887 static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
888                                              dav_buffer *pbuf)
889 {
890     const char *pathname;
891     apr_file_t *file = NULL;
892     dav_error *err = NULL;
893     apr_size_t amt;
894
895     if (pbuf->buf == NULL)
896         return NULL;
897
898     dav_fs_ensure_state_dir(p, dirpath);
899     pathname = apr_pstrcat(p,
900                           dirpath,
901                           dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
902                           DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
903                           NULL);
904
905     if (pbuf->cur_len == 0) {
906         /* delete the file if cur_len == 0 */
907         if (apr_file_remove(pathname, p) != 0) {
908             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
909                                  apr_psprintf(p,
910                                              "Error removing %s", pathname));
911         }
912         return NULL;
913     }
914
915     if (apr_file_open(&file, pathname,
916                 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
917                 APR_OS_DEFAULT, p) != APR_SUCCESS) {
918         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
919                              apr_psprintf(p,
920                                          "Error opening %s for writing",
921                                          pathname));
922     }
923
924     amt = pbuf->cur_len;
925     if (apr_file_write(file, pbuf->buf, &amt) != APR_SUCCESS
926         || amt != pbuf->cur_len) {
927         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
928                             apr_psprintf(p,
929                                         "Error writing %" APR_SIZE_T_FMT 
930                                         " bytes to %s",
931                                         pbuf->cur_len, pathname));
932     }
933
934     apr_file_close(file);
935     return err;
936 }
937
938 /*
939 ** dav_fs_remove_locknull_member:  Removes filename from the locknull list
940 **    for directory path.
941 */
942 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
943                                                  const char *filename,
944                                                  dav_buffer *pbuf)
945 {
946     dav_error *err;
947     apr_size_t len;
948     apr_size_t scanlen;
949     char *scan;
950     const char *scanend;
951     char *dirpath = apr_pstrdup(p, filename);
952     char *fname = strrchr(dirpath, '/');
953     int dirty = 0;
954
955     if (fname != NULL)
956         *fname++ = '\0';
957     else
958         fname = dirpath;
959     len = strlen(fname) + 1;
960
961     if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
962         /* ### add a higher level description? */
963         return err;
964     }
965
966     for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
967          scan < scanend;
968          scan += scanlen) {
969         scanlen = strlen(scan) + 1;
970         if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
971             pbuf->cur_len -= scanlen;
972             memmove(scan, scan + scanlen, scanend - (scan + scanlen));
973             dirty = 1;
974             break;
975         }
976     }
977
978     if (dirty) {
979         if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
980             /* ### add a higher level description? */
981             return err;
982         }
983     }
984
985     return NULL;
986 }
987
988 /* Note: used by dav_fs_repos.c */
989 dav_error * dav_fs_get_locknull_members(
990     const dav_resource *resource,
991     dav_buffer *pbuf)
992 {
993     const char *dirpath;
994
995     /* ### should test this result value... */
996     (void) dav_fs_dir_file_name(resource, &dirpath, NULL);
997     return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
998 }
999
1000 /* ### fold into append_lock? */
1001 /* ### take an optional buf parameter? */
1002 static dav_error * dav_fs_add_locknull_state(
1003     dav_lockdb *lockdb,
1004     const dav_resource *resource)
1005 {
1006     dav_buffer buf = { 0 };
1007     apr_pool_t *p = lockdb->info->pool;
1008     const char *dirpath;
1009     const char *fname;
1010     dav_error *err;
1011
1012     /* ### should test this result value... */
1013     (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
1014
1015     if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
1016         return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
1017                               "Could not load .locknull file.", err);
1018     }
1019
1020     dav_buffer_append(p, &buf, fname);
1021     buf.cur_len++;      /* we want the null-term here */
1022
1023     if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
1024         return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
1025                               "Could not save .locknull file.", err);
1026     }
1027
1028     return NULL;
1029 }
1030
1031 /*
1032 ** dav_fs_remove_locknull_state:  Given a request, check to see if r->filename
1033 **    is/was a lock-null resource.  If so, return it to an existant state.
1034 **
1035 **    ### this function is broken... it doesn't check!
1036 **
1037 **    In this implementation, this involves two things:
1038 **    (a) remove it from the list in the appropriate .DAV/locknull file
1039 **    (b) on *nix, convert the key from a filename to an inode.
1040 */
1041 static dav_error * dav_fs_remove_locknull_state(
1042     dav_lockdb *lockdb,
1043     const dav_resource *resource)
1044 {
1045     dav_buffer buf = { 0 };
1046     dav_error *err;
1047     apr_pool_t *p = lockdb->info->pool;
1048     const char *pathname = dav_fs_pathname(resource);
1049
1050     if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
1051         /* ### add a higher-level description? */
1052         return err;
1053     }
1054
1055     {
1056         dav_lock_discovery *ld;
1057         dav_lock_indirect  *id;
1058         apr_datum_t key;
1059
1060         /*
1061         ** Fetch the lock(s) that made the resource lock-null. Remove
1062         ** them under the filename key. Obtain the new inode key, and
1063         ** save the same lock information under it.
1064         */
1065         key = dav_fs_build_fname_key(p, pathname);
1066         if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1067                                            &ld, &id)) != NULL) {
1068             /* ### insert a higher-level error description */
1069             return err;
1070         }
1071
1072         if ((err = dav_fs_save_lock_record(lockdb, key, NULL, NULL)) != NULL) {
1073             /* ### insert a higher-level error description */
1074             return err;
1075         }
1076
1077         key = dav_fs_build_key(p, resource);
1078         if ((err = dav_fs_save_lock_record(lockdb, key, ld, id)) != NULL) {
1079             /* ### insert a higher-level error description */
1080             return err;
1081         }
1082     }
1083
1084     return NULL;
1085 }
1086
1087 static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
1088                                       const dav_resource *resource,
1089                                       dav_lock **lock)
1090 {
1091     apr_datum_t key;
1092
1093     key = dav_fs_build_key(lockdb->info->pool, resource);
1094
1095     *lock = dav_fs_alloc_lock(lockdb,
1096                               key,
1097                               NULL);
1098
1099     (*lock)->is_locknull = !resource->exists;
1100
1101     return NULL;
1102 }
1103
1104 static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
1105                                     const dav_resource *resource,
1106                                     int calltype,
1107                                     dav_lock **locks)
1108 {
1109     apr_pool_t *p = lockdb->info->pool;
1110     apr_datum_t key;
1111     dav_error *err;
1112     dav_lock *lock = NULL;
1113     dav_lock *newlock;
1114     dav_lock_discovery *dp;
1115     dav_lock_indirect *ip;
1116
1117 #if DAV_DEBUG
1118     if (calltype == DAV_GETLOCKS_COMPLETE) {
1119         return dav_new_error(lockdb->info->pool,
1120                              HTTP_INTERNAL_SERVER_ERROR, 0,
1121                              "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
1122                              "is not yet supported");
1123     }
1124 #endif
1125
1126     key = dav_fs_build_key(p, resource);
1127     if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1128                                        &dp, &ip)) != NULL) {
1129         /* ### push a higher-level desc? */
1130         return err;
1131     }
1132
1133     /* copy all direct locks to the result list */
1134     for (; dp != NULL; dp = dp->next) {
1135         newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
1136         newlock->is_locknull = !resource->exists;
1137         newlock->scope = dp->f.scope;
1138         newlock->type = dp->f.type;
1139         newlock->depth = dp->f.depth;
1140         newlock->timeout = dp->f.timeout;
1141         newlock->owner = dp->owner;
1142         newlock->auth_user = dp->auth_user;
1143
1144         /* hook into the result list */
1145         newlock->next = lock;
1146         lock = newlock;
1147     }
1148
1149     /* copy all the indirect locks to the result list. resolve as needed. */
1150     for (; ip != NULL; ip = ip->next) {
1151         newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
1152         newlock->is_locknull = !resource->exists;
1153
1154         if (calltype == DAV_GETLOCKS_RESOLVED) {
1155             if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
1156                 /* ### push a higher-level desc? */
1157                 return err;
1158             }
1159
1160             newlock->scope = dp->f.scope;
1161             newlock->type = dp->f.type;
1162             newlock->depth = dp->f.depth;
1163             newlock->timeout = dp->f.timeout;
1164             newlock->owner = dp->owner;
1165             newlock->auth_user = dp->auth_user;
1166         }
1167         else {
1168             /* DAV_GETLOCKS_PARTIAL */
1169             newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
1170         }
1171
1172         /* hook into the result list */
1173         newlock->next = lock;
1174         lock = newlock;
1175     }
1176
1177     *locks = lock;
1178     return NULL;
1179 }
1180
1181 static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
1182                                     const dav_resource *resource,
1183                                     const dav_locktoken *locktoken,
1184                                     int partial_ok,
1185                                     dav_lock **lock)
1186 {
1187     dav_error *err;
1188     apr_datum_t key;
1189     dav_lock_discovery *dp;
1190     dav_lock_indirect *ip;
1191
1192     *lock = NULL;
1193
1194     key = dav_fs_build_key(lockdb->info->pool, resource);
1195     if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1196                                        &dp, &ip)) != NULL) {
1197         /* ### push a higher-level desc? */
1198         return err;
1199     }
1200
1201     for (; dp != NULL; dp = dp->next) {
1202         if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
1203             *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
1204             (*lock)->is_locknull = !resource->exists;
1205             (*lock)->scope = dp->f.scope;
1206             (*lock)->type = dp->f.type;
1207             (*lock)->depth = dp->f.depth;
1208             (*lock)->timeout = dp->f.timeout;
1209             (*lock)->owner = dp->owner;
1210             (*lock)->auth_user = dp->auth_user;
1211             return NULL;
1212         }
1213     }
1214
1215     for (; ip != NULL; ip = ip->next) {
1216         if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
1217             *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
1218             (*lock)->is_locknull = !resource->exists;
1219
1220             /* ### nobody uses the resolving right now! */
1221             if (partial_ok) {
1222                 (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
1223             }
1224             else {
1225                 (*lock)->rectype = DAV_LOCKREC_INDIRECT;
1226                 if ((err = dav_fs_resolve(lockdb, ip, &dp,
1227                                           NULL, NULL)) != NULL) {
1228                     /* ### push a higher-level desc? */
1229                     return err;
1230                 }
1231                 (*lock)->scope = dp->f.scope;
1232                 (*lock)->type = dp->f.type;
1233                 (*lock)->depth = dp->f.depth;
1234                 (*lock)->timeout = dp->f.timeout;
1235                 (*lock)->owner = dp->owner;
1236                 (*lock)->auth_user = dp->auth_user;
1237             }
1238             return NULL;
1239         }
1240     }
1241
1242     return NULL;
1243 }
1244
1245 static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
1246                                     const dav_resource *resource,
1247                                     int *locks_present)
1248 {
1249     dav_error *err;
1250     apr_datum_t key;
1251
1252     *locks_present = 0;
1253
1254     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
1255         /* ### insert a higher-level error description */
1256         return err;
1257     }
1258
1259     /*
1260     ** If we opened readonly and the db wasn't there, then there are no
1261     ** locks for this resource. Just exit.
1262     */
1263     if (lockdb->info->db == NULL)
1264         return NULL;
1265
1266     key = dav_fs_build_key(lockdb->info->pool, resource);
1267
1268     *locks_present = dav_dbm_exists(lockdb->info->db, key);
1269
1270     return NULL;
1271 }
1272
1273 static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
1274                                        const dav_resource *resource,
1275                                        int make_indirect,
1276                                        const dav_lock *lock)
1277 {
1278     apr_pool_t *p = lockdb->info->pool;
1279     dav_error *err;
1280     dav_lock_indirect *ip;
1281     dav_lock_discovery *dp;
1282     apr_datum_t key;
1283
1284     key = dav_fs_build_key(lockdb->info->pool, resource);
1285     if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
1286         /* ### maybe add in a higher-level description */
1287         return err;
1288     }
1289
1290     /*
1291     ** ### when we store the lock more directly, we need to update
1292     ** ### lock->rectype and lock->is_locknull
1293     */
1294
1295     if (make_indirect) {
1296         for (; lock != NULL; lock = lock->next) {
1297
1298             /* ### this works for any <lock> rectype */
1299             dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
1300
1301             /* ### shut off the const warning for now */
1302             newi->locktoken = (dav_locktoken *)lock->locktoken;
1303             newi->timeout   = lock->timeout;
1304             newi->key       = lock->info->key;
1305             newi->next      = ip;
1306             ip              = newi;
1307         }
1308     }
1309     else {
1310         for (; lock != NULL; lock = lock->next) {
1311             /* create and link in the right kind of lock */
1312
1313             if (lock->rectype == DAV_LOCKREC_DIRECT) {
1314                 dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
1315
1316                 newd->f.scope = lock->scope;
1317                 newd->f.type = lock->type;
1318                 newd->f.depth = lock->depth;
1319                 newd->f.timeout = lock->timeout;
1320                 /* ### shut off the const warning for now */
1321                 newd->locktoken = (dav_locktoken *)lock->locktoken;
1322                 newd->owner = lock->owner;
1323                 newd->auth_user = lock->auth_user;
1324                 newd->next = dp;
1325                 dp = newd;
1326             }
1327             else {
1328                 /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
1329
1330                 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
1331
1332                 /* ### shut off the const warning for now */
1333                 newi->locktoken = (dav_locktoken *)lock->locktoken;
1334                 newi->key       = lock->info->key;
1335                 newi->next      = ip;
1336                 ip              = newi;
1337             }
1338         }
1339     }
1340
1341     if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1342         /* ### maybe add a higher-level description */
1343         return err;
1344     }
1345
1346     /* we have a special list for recording locknull resources */
1347     /* ### ack! this can add two copies to the locknull list */
1348     if (!resource->exists
1349         && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
1350         /* ### maybe add a higher-level description */
1351         return err;
1352     }
1353
1354     return NULL;
1355 }
1356
1357 static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
1358                                       const dav_resource *resource,
1359                                       const dav_locktoken *locktoken)
1360 {
1361     dav_error *err;
1362     dav_buffer buf = { 0 };
1363     dav_lock_discovery *dh = NULL;
1364     dav_lock_indirect *ih = NULL;
1365     apr_datum_t key;
1366
1367     key = dav_fs_build_key(lockdb->info->pool, resource);
1368
1369     if (locktoken != NULL) {
1370         dav_lock_discovery *dp;
1371         dav_lock_discovery *dprev = NULL;
1372         dav_lock_indirect *ip;
1373         dav_lock_indirect *iprev = NULL;
1374
1375         if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1376                                            &dh, &ih)) != NULL) {
1377             /* ### maybe add a higher-level description */
1378             return err;
1379         }
1380
1381         for (dp = dh; dp != NULL; dp = dp->next) {
1382             if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
1383                 if (dprev)
1384                     dprev->next = dp->next;
1385                 else
1386                     dh = dh->next;
1387             }
1388             dprev = dp;
1389         }
1390
1391         for (ip = ih; ip != NULL; ip = ip->next) {
1392             if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
1393                 if (iprev)
1394                     iprev->next = ip->next;
1395                 else
1396                     ih = ih->next;
1397             }
1398             iprev = ip;
1399         }
1400
1401     }
1402
1403     /* save the modified locks, or remove all locks (dh=ih=NULL). */
1404     if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) {
1405         /* ### maybe add a higher-level description */
1406         return err;
1407     }
1408
1409     /*
1410     ** If this resource is a locknull resource AND no more locks exist,
1411     ** then remove the locknull member.
1412     **
1413     ** Note: remove_locknull_state() attempts to convert a locknull member
1414     **       to a real member. In this case, all locks are gone, so the
1415     **       locknull resource returns to the null state (ie. doesn't exist),
1416     **       so there is no need to update the lockdb (and it won't find
1417     **       any because a precondition is that none exist).
1418     */
1419     if (!resource->exists && dh == NULL && ih == NULL
1420         && (err = dav_fs_remove_locknull_member(lockdb->info->pool,
1421                                                 dav_fs_pathname(resource),
1422                                                 &buf)) != NULL) {
1423         /* ### maybe add a higher-level description */
1424         return err;
1425     }
1426
1427     return NULL;
1428 }
1429
1430 static int dav_fs_do_refresh(dav_lock_discovery *dp,
1431                              const dav_locktoken_list *ltl,
1432                              time_t new_time)
1433 {
1434     int dirty = 0;
1435
1436     for (; ltl != NULL; ltl = ltl->next) {
1437         if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
1438         {
1439             dp->f.timeout = new_time;
1440             dirty = 1;
1441         }
1442     }
1443
1444     return dirty;
1445 }
1446
1447 static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
1448                                         const dav_resource *resource,
1449                                         const dav_locktoken_list *ltl,
1450                                         time_t new_time,
1451                                         dav_lock **locks)
1452 {
1453     dav_error *err;
1454     apr_datum_t key;
1455     dav_lock_discovery *dp;
1456     dav_lock_discovery *dp_scan;
1457     dav_lock_indirect *ip;
1458     int dirty = 0;
1459     dav_lock *newlock;
1460
1461     *locks = NULL;
1462
1463     key = dav_fs_build_key(lockdb->info->pool, resource);
1464     if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1465                                        &dp, &ip)) != NULL) {
1466         /* ### maybe add in a higher-level description */
1467         return err;
1468     }
1469
1470     /* ### we should be refreshing direct AND (resolved) indirect locks! */
1471
1472     /* refresh all of the direct locks on this resource */
1473     for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
1474         if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
1475             /* the lock was refreshed. return the lock. */
1476             newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken);
1477             newlock->is_locknull = !resource->exists;
1478             newlock->scope = dp_scan->f.scope;
1479             newlock->type = dp_scan->f.type;
1480             newlock->depth = dp_scan->f.depth;
1481             newlock->timeout = dp_scan->f.timeout;
1482             newlock->owner = dp_scan->owner;
1483             newlock->auth_user = dp_scan->auth_user;
1484
1485             newlock->next = *locks;
1486             *locks = newlock;
1487
1488             dirty = 1;
1489         }
1490     }
1491
1492     /* if we refreshed any locks, then save them back. */
1493     if (dirty
1494         && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1495         /* ### maybe add in a higher-level description */
1496         return err;
1497     }
1498
1499     /* for each indirect lock, find its direct lock and refresh it. */
1500     for (; ip != NULL; ip = ip->next) {
1501         dav_lock_discovery *ref_dp;
1502         dav_lock_indirect *ref_ip;
1503
1504         if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
1505                                   &ref_dp, &ref_ip)) != NULL) {
1506             /* ### push a higher-level desc? */
1507             return err;
1508         }
1509         if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
1510             /* the lock was refreshed. return the lock. */
1511             newlock = dav_fs_alloc_lock(lockdb, ip->key, dp->locktoken);
1512             newlock->is_locknull = !resource->exists;
1513             newlock->scope = dp->f.scope;
1514             newlock->type = dp->f.type;
1515             newlock->depth = dp->f.depth;
1516             newlock->timeout = dp->f.timeout;
1517             newlock->owner = dp->owner;
1518             newlock->auth_user = dp_scan->auth_user;
1519
1520             newlock->next = *locks;
1521             *locks = newlock;
1522
1523             /* save the (resolved) direct lock back */
1524             if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
1525                                                ref_ip)) != NULL) {
1526                 /* ### push a higher-level desc? */
1527                 return err;
1528             }
1529         }
1530     }
1531
1532     return NULL;
1533 }
1534
1535
1536 const dav_hooks_locks dav_hooks_locks_fs =
1537 {
1538     dav_fs_get_supportedlock,
1539     dav_fs_parse_locktoken,
1540     dav_fs_format_locktoken,
1541     dav_fs_compare_locktoken,
1542     dav_fs_open_lockdb,
1543     dav_fs_close_lockdb,
1544     dav_fs_remove_locknull_state,
1545     dav_fs_create_lock,
1546     dav_fs_get_locks,
1547     dav_fs_find_lock,
1548     dav_fs_has_locks,
1549     dav_fs_append_locks,
1550     dav_fs_remove_lock,
1551     dav_fs_refresh_locks,
1552     NULL, /* get_resource */
1553 };