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