1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
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
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.
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.
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.
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
47 * ====================================================================
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/>.
56 ** DAV filesystem lock implementation
60 #include "apr_strings.h"
61 #include "apr_file_io.h"
64 #define APR_WANT_MEMFUNC
74 /* ---------------------------------------------------------------
76 ** Lock database primitives
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.
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).
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),
101 ** apr_uuid_t locktoken,
105 ** INDIRECT LOCK: [char (DAV_LOCK_INDIRECT),
106 ** apr_uuid_t locktoken,
110 ** The key is to the collection lock that resulted in this indirect lock
116 #define DAV_CREATE_LIST 23
117 #define DAV_APPEND_LIST 24
119 /* Stored lock_discovery prefix */
120 #define DAV_LOCK_DIRECT 1
121 #define DAV_LOCK_INDIRECT 2
123 #define DAV_TYPE_INODE 10
124 #define DAV_TYPE_FNAME 11
127 /* ack. forward declare. */
128 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
129 const char *filename,
133 ** Use the opaquelock scheme for locktokens
135 struct dav_locktoken {
138 #define dav_compare_locktoken(plt1, plt2) \
139 memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
142 /* #################################################################
143 ** ### keep these structures (internal) or move fully to dav_lock?
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.
151 typedef struct dav_lock_discovery_fixed
157 } dav_lock_discovery_fixed;
159 typedef struct dav_lock_discovery
161 struct dav_lock_discovery_fixed f;
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;
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
175 typedef struct dav_lock_indirect
177 dav_locktoken *locktoken;
179 struct dav_lock_indirect *next;
183 /* ################################################################# */
187 ** Stored direct lock info - full lock_discovery length:
188 ** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
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) \
196 /* Stored indirect lock info - lock token and apr_datum_t */
197 #define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
199 + sizeof(int) + (a)->key.dsize)
202 ** The lockdb structure.
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.
209 ** The delayed opening (determined by <opened>) makes creating a lockdb
210 ** quick, while deferring the underlying I/O until it is actually required.
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.
216 struct dav_lockdb_private
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? */
222 int opened; /* we opened the database */
223 dav_db *db; /* if non-NULL, the lock database */
228 dav_lockdb_private priv;
229 } dav_lockdb_combined;
232 ** The private part of the lock structure.
234 struct dav_lock_private
236 apr_datum_t key; /* key into the lock database */
241 dav_lock_private priv;
246 ** This must be forward-declared so the open_lockdb function can use it.
248 extern const dav_hooks_locks dav_hooks_locks_fs;
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)
255 dav_lock_combined *comb;
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;
262 if (locktoken == NULL) {
263 comb->pub.locktoken = &comb->token;
264 apr_uuid_get(&comb->token.uuid);
267 comb->pub.locktoken = locktoken;
274 ** dav_fs_parse_locktoken
276 ** Parse an opaquelocktoken URI into a locktoken.
278 static dav_error * dav_fs_parse_locktoken(
280 const char *char_token,
281 dav_locktoken **locktoken_p)
283 dav_locktoken *locktoken;
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.");
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.");
300 *locktoken_p = locktoken;
305 ** dav_fs_format_locktoken
307 ** Generate the URI for a locktoken
309 static const char *dav_fs_format_locktoken(
311 const dav_locktoken *locktoken)
313 char buf[APR_UUID_FORMATTED_LENGTH + 1];
315 apr_uuid_format(buf, &locktoken->uuid);
316 return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
320 ** dav_fs_compare_locktoken
322 ** Determine whether two locktokens are the same
324 static int dav_fs_compare_locktoken(
325 const dav_locktoken *lt1,
326 const dav_locktoken *lt2)
328 return dav_compare_locktoken(lt1, lt2);
332 ** dav_fs_really_open_lockdb:
334 ** If the database hasn't been opened yet, then open the thing.
336 static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
340 if (lockdb->info->opened)
343 err = dav_dbm_open_direct(lockdb->info->pool,
344 lockdb->info->lockdb_path,
348 return dav_push_error(lockdb->info->pool,
349 HTTP_INTERNAL_SERVER_ERROR,
351 "Could not open the lock database.",
355 /* all right. it is opened now. */
356 lockdb->info->opened = 1;
362 ** dav_fs_open_lockdb:
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.
367 ** Note that only one can be open read/write.
369 static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
372 dav_lockdb_combined *comb;
374 comb = apr_pcalloc(r->pool, sizeof(*comb));
375 comb->pub.hooks = &dav_hooks_locks_fs;
377 comb->pub.info = &comb->priv;
379 comb->priv.pool = r->pool;
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,
385 "A lock database was not specified with the "
386 "DAVLockDB directive. One must be specified "
387 "to use the locking functionality.");
390 /* done initializing. return it. */
391 *lockdb = &comb->pub;
394 /* ### add a higher-level comment? */
395 return dav_fs_really_open_lockdb(*lockdb);
402 ** dav_fs_close_lockdb:
406 static void dav_fs_close_lockdb(dav_lockdb *lockdb)
408 if (lockdb->info->db != NULL)
409 dav_dbm_close(lockdb->info->db);
413 ** dav_fs_build_fname_key
415 ** Given a pathname, build a DAV_TYPE_FNAME lock database key.
417 static apr_datum_t dav_fs_build_fname_key(apr_pool_t *p, const char *pathname)
421 /* ### does this allocation have a proper lifetime? need to check */
422 /* ### can we use a buffer for this? */
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';
435 ** dav_fs_build_key: Given a resource, return a apr_datum_t key
436 ** to look up lock information for this file.
438 ** (inode/dev not supported or file is lock-null):
439 ** apr_datum_t->dvalue = full path
441 ** (inode/dev supported and file exists ):
442 ** apr_datum_t->dvalue = inode, dev
444 static apr_datum_t dav_fs_build_key(apr_pool_t *p,
445 const dav_resource *resource)
447 const char *file = dav_fs_pathname(resource);
452 /* ### use lstat() ?? */
454 * XXX: What for platforms with no IDENT (dev/inode)?
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))
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));
471 return dav_fs_build_fname_key(p, file);
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).
479 static int dav_fs_lock_expired(time_t expires)
481 return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
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.
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)
494 apr_datum_t val = { 0 };
496 dav_lock_discovery *dp = direct;
497 dav_lock_indirect *ip = indirect;
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 "
509 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
510 /* ### add a higher-level error? */
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);
523 val.dsize += dav_size_direct(dp);
527 val.dsize += dav_size_indirect(ip);
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);
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) {
547 memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
548 ptr += strlen(dp->owner) + 1;
550 if (dp->auth_user == NULL) {
554 memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
555 ptr += strlen(dp->auth_user) + 1;
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;
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.",
587 ** dav_load_lock_record: Reads lock information about key from lock db;
588 ** creates linked lists of the direct and indirect locks.
590 ** If add_method = DAV_APPEND_LIST, the result will be appended to the
591 ** head of the direct and indirect lists supplied.
593 ** Passive lock removal: If lock has timed out, it will not be returned.
594 ** ### How much "logging" does RFC 2518 require?
596 static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
598 dav_lock_discovery **direct,
599 dav_lock_indirect **indirect)
601 apr_pool_t *p = lockdb->info->pool;
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 };
610 if (add_method != DAV_APPEND_LIST) {
615 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
616 /* ### add a higher-level error? */
621 ** If we opened readonly and the db wasn't there, then there are no
622 ** locks for this resource. Just exit.
624 if (lockdb->info->db == NULL)
627 if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
633 while (offset < val.dsize) {
634 switch (*(val.dptr + offset++)) {
635 case DAV_LOCK_DIRECT:
636 /* Create and fill a dav_lock_discovery structure */
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') {
648 dp->owner = apr_pstrdup(p, val.dptr + offset);
649 offset += strlen(dp->owner) + 1;
652 if (*(val.dptr + offset) == '\0') {
656 dp->auth_user = apr_pstrdup(p, val.dptr + offset);
657 offset += strlen(dp->auth_user) + 1;
660 if (!dav_fs_lock_expired(dp->f.timeout)) {
665 need_save = DAV_TRUE;
667 /* Remove timed-out locknull fm .locknull list */
668 if (*key.dptr == DAV_TYPE_FNAME) {
669 const char *fname = key.dptr + 1;
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? */
685 case DAV_LOCK_INDIRECT:
686 /* Create and fill a dav_lock_indirect structure */
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;
700 if (!dav_fs_lock_expired(ip->timeout)) {
701 ip->next = *indirect;
705 need_save = DAV_TRUE;
706 /* A locknull resource will never be locked indirectly */
712 dav_dbm_freedatum(lockdb->info->db, val);
714 /* ### should use a computed_desc and insert corrupt token data */
716 return dav_new_error(p,
717 HTTP_INTERNAL_SERVER_ERROR,
718 DAV_ERR_LOCK_CORRUPT_DB,
720 "The lock database was found to "
721 "be corrupt. offset %"
722 APR_SIZE_T_FMT ", c=%02x",
723 offset, val.dptr[offset]));
727 dav_dbm_freedatum(lockdb->info->db, val);
729 /* Clean up this record if we found expired locks */
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
734 if (need_save == DAV_TRUE) {
735 return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
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)
749 dav_lock_discovery *dir;
750 dav_lock_indirect *ind;
752 if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
754 &dir, &ind)) != NULL) {
755 /* ### insert a higher-level description? */
758 if (ref_dp != NULL) {
763 for (; dir != NULL; dir = dir->next) {
764 if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
770 /* No match found (but we should have found one!) */
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 "
781 /* ---------------------------------------------------------------
783 ** Property-related lock functions
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.
792 static const char *dav_fs_get_supportedlock(const dav_resource *resource)
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;
807 /* ---------------------------------------------------------------
809 ** General lock functions
813 /* ---------------------------------------------------------------
815 ** Functions dealing with lock-null resources
820 ** dav_fs_load_locknull_list: Returns a dav_buffer dump of the locknull file
821 ** for the given directory.
823 static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
827 apr_file_t *file = NULL;
828 dav_error *err = NULL;
832 dav_buffer_init(p, pbuf, dirpath);
834 if (pbuf->buf[pbuf->cur_len - 1] == '/')
835 pbuf->buf[--pbuf->cur_len] = '\0';
837 dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
839 /* reset this in case we leave w/o reading into the buffer */
842 if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
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,
851 "Opened but could not stat file %s",
856 if (finfo.size != (apr_size_t)finfo.size) {
857 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
859 "Opened but rejected huge file %s",
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,
870 "Failure reading locknull file "
873 /* just in case the caller disregards the returned error */
879 apr_file_close(file);
884 ** dav_fs_save_locknull_list: Saves contents of pbuf into the
885 ** locknull file for dirpath.
887 static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
890 const char *pathname;
891 apr_file_t *file = NULL;
892 dav_error *err = NULL;
895 if (pbuf->buf == NULL)
898 dav_fs_ensure_state_dir(p, dirpath);
899 pathname = apr_pstrcat(p,
901 dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
902 DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
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,
910 "Error removing %s", pathname));
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,
920 "Error opening %s for writing",
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,
929 "Error writing %" APR_SIZE_T_FMT
931 pbuf->cur_len, pathname));
934 apr_file_close(file);
939 ** dav_fs_remove_locknull_member: Removes filename from the locknull list
940 ** for directory path.
942 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
943 const char *filename,
951 char *dirpath = apr_pstrdup(p, filename);
952 char *fname = strrchr(dirpath, '/');
959 len = strlen(fname) + 1;
961 if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
962 /* ### add a higher level description? */
966 for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
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));
979 if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
980 /* ### add a higher level description? */
988 /* Note: used by dav_fs_repos.c */
989 dav_error * dav_fs_get_locknull_members(
990 const dav_resource *resource,
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);
1000 /* ### fold into append_lock? */
1001 /* ### take an optional buf parameter? */
1002 static dav_error * dav_fs_add_locknull_state(
1004 const dav_resource *resource)
1006 dav_buffer buf = { 0 };
1007 apr_pool_t *p = lockdb->info->pool;
1008 const char *dirpath;
1012 /* ### should test this result value... */
1013 (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
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);
1020 dav_buffer_append(p, &buf, fname);
1021 buf.cur_len++; /* we want the null-term here */
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);
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.
1035 ** ### this function is broken... it doesn't check!
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.
1041 static dav_error * dav_fs_remove_locknull_state(
1043 const dav_resource *resource)
1045 dav_buffer buf = { 0 };
1047 apr_pool_t *p = lockdb->info->pool;
1048 const char *pathname = dav_fs_pathname(resource);
1050 if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
1051 /* ### add a higher-level description? */
1056 dav_lock_discovery *ld;
1057 dav_lock_indirect *id;
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.
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 */
1072 if ((err = dav_fs_save_lock_record(lockdb, key, NULL, NULL)) != NULL) {
1073 /* ### insert a higher-level error description */
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 */
1087 static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
1088 const dav_resource *resource,
1093 key = dav_fs_build_key(lockdb->info->pool, resource);
1095 *lock = dav_fs_alloc_lock(lockdb,
1099 (*lock)->is_locknull = !resource->exists;
1104 static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
1105 const dav_resource *resource,
1109 apr_pool_t *p = lockdb->info->pool;
1112 dav_lock *lock = NULL;
1114 dav_lock_discovery *dp;
1115 dav_lock_indirect *ip;
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");
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? */
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;
1144 /* hook into the result list */
1145 newlock->next = lock;
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;
1154 if (calltype == DAV_GETLOCKS_RESOLVED) {
1155 if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
1156 /* ### push a higher-level desc? */
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;
1168 /* DAV_GETLOCKS_PARTIAL */
1169 newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
1172 /* hook into the result list */
1173 newlock->next = lock;
1181 static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
1182 const dav_resource *resource,
1183 const dav_locktoken *locktoken,
1189 dav_lock_discovery *dp;
1190 dav_lock_indirect *ip;
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? */
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;
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;
1220 /* ### nobody uses the resolving right now! */
1222 (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
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? */
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;
1245 static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
1246 const dav_resource *resource,
1254 if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
1255 /* ### insert a higher-level error description */
1260 ** If we opened readonly and the db wasn't there, then there are no
1261 ** locks for this resource. Just exit.
1263 if (lockdb->info->db == NULL)
1266 key = dav_fs_build_key(lockdb->info->pool, resource);
1268 *locks_present = dav_dbm_exists(lockdb->info->db, key);
1273 static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
1274 const dav_resource *resource,
1276 const dav_lock *lock)
1278 apr_pool_t *p = lockdb->info->pool;
1280 dav_lock_indirect *ip;
1281 dav_lock_discovery *dp;
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 */
1291 ** ### when we store the lock more directly, we need to update
1292 ** ### lock->rectype and lock->is_locknull
1295 if (make_indirect) {
1296 for (; lock != NULL; lock = lock->next) {
1298 /* ### this works for any <lock> rectype */
1299 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
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;
1310 for (; lock != NULL; lock = lock->next) {
1311 /* create and link in the right kind of lock */
1313 if (lock->rectype == DAV_LOCKREC_DIRECT) {
1314 dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
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;
1328 /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
1330 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
1332 /* ### shut off the const warning for now */
1333 newi->locktoken = (dav_locktoken *)lock->locktoken;
1334 newi->key = lock->info->key;
1341 if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1342 /* ### maybe add a higher-level description */
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 */
1357 static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
1358 const dav_resource *resource,
1359 const dav_locktoken *locktoken)
1362 dav_buffer buf = { 0 };
1363 dav_lock_discovery *dh = NULL;
1364 dav_lock_indirect *ih = NULL;
1367 key = dav_fs_build_key(lockdb->info->pool, resource);
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;
1375 if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1376 &dh, &ih)) != NULL) {
1377 /* ### maybe add a higher-level description */
1381 for (dp = dh; dp != NULL; dp = dp->next) {
1382 if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
1384 dprev->next = dp->next;
1391 for (ip = ih; ip != NULL; ip = ip->next) {
1392 if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
1394 iprev->next = ip->next;
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 */
1410 ** If this resource is a locknull resource AND no more locks exist,
1411 ** then remove the locknull member.
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).
1419 if (!resource->exists && dh == NULL && ih == NULL
1420 && (err = dav_fs_remove_locknull_member(lockdb->info->pool,
1421 dav_fs_pathname(resource),
1423 /* ### maybe add a higher-level description */
1430 static int dav_fs_do_refresh(dav_lock_discovery *dp,
1431 const dav_locktoken_list *ltl,
1436 for (; ltl != NULL; ltl = ltl->next) {
1437 if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
1439 dp->f.timeout = new_time;
1447 static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
1448 const dav_resource *resource,
1449 const dav_locktoken_list *ltl,
1455 dav_lock_discovery *dp;
1456 dav_lock_discovery *dp_scan;
1457 dav_lock_indirect *ip;
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 */
1470 /* ### we should be refreshing direct AND (resolved) indirect locks! */
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;
1485 newlock->next = *locks;
1492 /* if we refreshed any locks, then save them back. */
1494 && (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1495 /* ### maybe add in a higher-level description */
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;
1504 if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
1505 &ref_dp, &ref_ip)) != NULL) {
1506 /* ### push a higher-level desc? */
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;
1520 newlock->next = *locks;
1523 /* save the (resolved) direct lock back */
1524 if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
1526 /* ### push a higher-level desc? */
1536 const dav_hooks_locks dav_hooks_locks_fs =
1538 dav_fs_get_supportedlock,
1539 dav_fs_parse_locktoken,
1540 dav_fs_format_locktoken,
1541 dav_fs_compare_locktoken,
1543 dav_fs_close_lockdb,
1544 dav_fs_remove_locknull_state,
1549 dav_fs_append_locks,
1551 dav_fs_refresh_locks,
1552 NULL, /* get_resource */