Main Page | Modules | Namespace List | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals | Related Pages | Examples

lock.c

Go to the documentation of this file.
00001 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*
00018 ** DAV filesystem lock implementation
00019 */
00020 
00021 #include "apr.h"
00022 #include "apr_strings.h"
00023 #include "apr_file_io.h"
00024 #include "apr_uuid.h"
00025 
00026 #define APR_WANT_MEMFUNC
00027 #include "apr_want.h"
00028 
00029 #include "httpd.h"
00030 #include "http_log.h"
00031 
00032 #include "mod_dav.h"
00033 #include "repos.h"
00034 
00035 
00036 /* ---------------------------------------------------------------
00037 **
00038 ** Lock database primitives
00039 **
00040 */
00041 
00042 /*
00043 ** LOCK DATABASES
00044 ** 
00045 ** Lockdiscovery information is stored in the single lock database specified
00046 ** by the DAVLockDB directive.  Information about this db is stored in the
00047 ** global server configuration.
00048 **
00049 ** KEY
00050 **
00051 ** The database is keyed by a key_type unsigned char (DAV_TYPE_INODE or
00052 ** DAV_TYPE_FNAME) followed by inode and device number if possible,
00053 ** otherwise full path (in the case of Win32 or lock-null resources).
00054 **
00055 ** VALUE
00056 **
00057 ** The value consists of a list of elements.
00058 **    DIRECT LOCK:     [char      (DAV_LOCK_DIRECT),
00059 **                      char      (dav_lock_scope),
00060 **                      char      (dav_lock_type),
00061 **                      int        depth,
00062 **                      time_t     expires,
00063 **                      apr_uuid_t locktoken,
00064 **                      char[]     owner,
00065 **                      char[]     auth_user]
00066 **
00067 **    INDIRECT LOCK:   [char      (DAV_LOCK_INDIRECT),
00068 **                      apr_uuid_t locktoken,
00069 **                      time_t     expires,
00070 **                      apr_size_t key_size,
00071 **                      char[]     key]
00072 **       The key is to the collection lock that resulted in this indirect lock
00073 */
00074 
00075 #define DAV_TRUE                1
00076 #define DAV_FALSE               0
00077 
00078 #define DAV_CREATE_LIST         23
00079 #define DAV_APPEND_LIST         24
00080 
00081 /* Stored lock_discovery prefix */
00082 #define DAV_LOCK_DIRECT         1
00083 #define DAV_LOCK_INDIRECT       2
00084 
00085 #define DAV_TYPE_INODE          10
00086 #define DAV_TYPE_FNAME          11
00087 
00088 
00089 /* ack. forward declare. */
00090 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
00091                                                  const char *filename,
00092                                                  dav_buffer *pbuf);
00093 
00094 /*
00095 ** Use the opaquelock scheme for locktokens
00096 */
00097 struct dav_locktoken {
00098     apr_uuid_t uuid;
00099 };
00100 #define dav_compare_locktoken(plt1, plt2) \
00101                 memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
00102 
00103 
00104 /* #################################################################
00105 ** ### keep these structures (internal) or move fully to dav_lock?
00106 */
00107 
00108 /*
00109 ** We need to reliably size the fixed-length portion of
00110 ** dav_lock_discovery; best to separate it into another 
00111 ** struct for a convenient sizeof, unless we pack lock_discovery.
00112 */
00113 typedef struct dav_lock_discovery_fixed
00114 {
00115     char scope;
00116     char type;
00117     int depth;
00118     time_t timeout;
00119 } dav_lock_discovery_fixed;
00120 
00121 typedef struct dav_lock_discovery
00122 {
00123     struct dav_lock_discovery_fixed f;
00124 
00125     dav_locktoken *locktoken;
00126     const char *owner;          /* owner field from activelock */
00127     const char *auth_user;      /* authenticated user who created the lock */
00128     struct dav_lock_discovery *next;
00129 } dav_lock_discovery;
00130 
00131 /* Indirect locks represent locks inherited from containing collections.
00132  * They reference the lock token for the collection the lock is
00133  * inherited from. A lock provider may also define a key to the
00134  * inherited lock, for fast datbase lookup. The key is opaque outside
00135  * the lock provider.
00136  */
00137 typedef struct dav_lock_indirect
00138 {
00139     dav_locktoken *locktoken;
00140     apr_datum_t key;
00141     struct dav_lock_indirect *next;
00142     time_t timeout;
00143 } dav_lock_indirect;
00144 
00145 /* ################################################################# */
00146 
00147 
00148 /*
00149 ** Stored direct lock info - full lock_discovery length:  
00150 ** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
00151 */
00152 #define dav_size_direct(a)      (1 + sizeof(dav_lock_discovery_fixed) \
00153                                  + sizeof(apr_uuid_t) \
00154                                  + ((a)->owner ? strlen((a)->owner) : 0) \
00155                                  + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
00156                                  + 2)
00157 
00158 /* Stored indirect lock info - lock token and apr_datum_t */
00159 #define dav_size_indirect(a)    (1 + sizeof(apr_uuid_t) \
00160                                  + sizeof(time_t) \
00161                                  + sizeof((a)->key.dsize) + (a)->key.dsize)
00162 
00163 /*
00164 ** The lockdb structure.
00165 **
00166 ** The <db> field may be NULL, meaning one of two things:
00167 ** 1) That we have not actually opened the underlying database (yet). The
00168 **    <opened> field should be false.
00169 ** 2) We opened it readonly and it wasn't present.
00170 **
00171 ** The delayed opening (determined by <opened>) makes creating a lockdb
00172 ** quick, while deferring the underlying I/O until it is actually required.
00173 **
00174 ** We export the notion of a lockdb, but hide the details of it. Most
00175 ** implementations will use a database of some kind, but it is certainly
00176 ** possible that alternatives could be used.
00177 */
00178 struct dav_lockdb_private
00179 {
00180     request_rec *r;                     /* for accessing the uuid state */
00181     apr_pool_t *pool;                   /* a pool to use */
00182     const char *lockdb_path;            /* where is the lock database? */
00183 
00184     int opened;                         /* we opened the database */
00185     dav_db *db;                         /* if non-NULL, the lock database */
00186 };
00187 typedef struct
00188 {
00189     dav_lockdb pub;
00190     dav_lockdb_private priv;
00191 } dav_lockdb_combined;
00192 
00193 /*
00194 ** The private part of the lock structure.
00195 */
00196 struct dav_lock_private
00197 {
00198     apr_datum_t key;    /* key into the lock database */
00199 };
00200 typedef struct
00201 {
00202     dav_lock pub;
00203     dav_lock_private priv;
00204     dav_locktoken token;
00205 } dav_lock_combined;
00206 
00207 /*
00208 ** This must be forward-declared so the open_lockdb function can use it.
00209 */
00210 extern const dav_hooks_locks dav_hooks_locks_fs;
00211 
00212 
00213 /* internal function for creating locks */
00214 static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
00215                                    const dav_locktoken *locktoken)
00216 {
00217     dav_lock_combined *comb;
00218 
00219     comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
00220     comb->pub.rectype = DAV_LOCKREC_DIRECT;
00221     comb->pub.info = &comb->priv;
00222     comb->priv.key = key;
00223 
00224     if (locktoken == NULL) {
00225         comb->pub.locktoken = &comb->token;
00226         apr_uuid_get(&comb->token.uuid);
00227     }
00228     else {
00229         comb->pub.locktoken = locktoken;
00230     }
00231 
00232     return &comb->pub;
00233 }
00234 
00235 /*
00236 ** dav_fs_parse_locktoken
00237 **
00238 ** Parse an opaquelocktoken URI into a locktoken.
00239 */
00240 static dav_error * dav_fs_parse_locktoken(
00241     apr_pool_t *p,
00242     const char *char_token,
00243     dav_locktoken **locktoken_p)
00244 {
00245     dav_locktoken *locktoken;
00246 
00247     if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
00248         return dav_new_error(p,
00249                              HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN,
00250                              "The lock token uses an unknown State-token "
00251                              "format and could not be parsed.");
00252     }
00253     char_token += 16;
00254 
00255     locktoken = apr_pcalloc(p, sizeof(*locktoken));
00256     if (apr_uuid_parse(&locktoken->uuid, char_token)) {
00257         return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN,
00258                              "The opaquelocktoken has an incorrect format "
00259                              "and could not be parsed.");
00260     }
00261     
00262     *locktoken_p = locktoken;
00263     return NULL;
00264 }
00265 
00266 /*
00267 ** dav_fs_format_locktoken
00268 **
00269 ** Generate the URI for a locktoken
00270 */
00271 static const char *dav_fs_format_locktoken(
00272     apr_pool_t *p,
00273     const dav_locktoken *locktoken)
00274 {
00275     char buf[APR_UUID_FORMATTED_LENGTH + 1];
00276 
00277     apr_uuid_format(buf, &locktoken->uuid);
00278     return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
00279 }
00280 
00281 /*
00282 ** dav_fs_compare_locktoken
00283 **
00284 ** Determine whether two locktokens are the same
00285 */
00286 static int dav_fs_compare_locktoken(
00287     const dav_locktoken *lt1,
00288     const dav_locktoken *lt2)
00289 {
00290     return dav_compare_locktoken(lt1, lt2);
00291 }
00292 
00293 /*
00294 ** dav_fs_really_open_lockdb:
00295 **
00296 ** If the database hasn't been opened yet, then open the thing.
00297 */
00298 static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
00299 {
00300     dav_error *err;
00301 
00302     if (lockdb->info->opened)
00303         return NULL;
00304 
00305     err = dav_dbm_open_direct(lockdb->info->pool,
00306                               lockdb->info->lockdb_path,
00307                               lockdb->ro,
00308                               &lockdb->info->db);
00309     if (err != NULL) {
00310         return dav_push_error(lockdb->info->pool,
00311                               HTTP_INTERNAL_SERVER_ERROR,
00312                               DAV_ERR_LOCK_OPENDB,
00313                               "Could not open the lock database.",
00314                               err);
00315     }
00316 
00317     /* all right. it is opened now. */
00318     lockdb->info->opened = 1;
00319 
00320     return NULL;
00321 }
00322 
00323 /*
00324 ** dav_fs_open_lockdb:
00325 **
00326 ** "open" the lock database, as specified in the global server configuration.
00327 ** If force is TRUE, then the database is opened now, rather than lazily.
00328 **
00329 ** Note that only one can be open read/write.
00330 */
00331 static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
00332                                       dav_lockdb **lockdb)
00333 {
00334     dav_lockdb_combined *comb;
00335 
00336     comb = apr_pcalloc(r->pool, sizeof(*comb));
00337     comb->pub.hooks = &dav_hooks_locks_fs;
00338     comb->pub.ro = ro;
00339     comb->pub.info = &comb->priv;
00340     comb->priv.r = r;
00341     comb->priv.pool = r->pool;
00342 
00343     comb->priv.lockdb_path = dav_get_lockdb_path(r);
00344     if (comb->priv.lockdb_path == NULL) {
00345         return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
00346                              DAV_ERR_LOCK_NO_DB,
00347                              "A lock database was not specified with the "
00348                              "DAVLockDB directive. One must be specified "
00349                              "to use the locking functionality.");
00350     }
00351 
00352     /* done initializing. return it. */
00353     *lockdb = &comb->pub;
00354 
00355     if (force) {
00356         /* ### add a higher-level comment? */
00357         return dav_fs_really_open_lockdb(*lockdb);
00358     }
00359 
00360     return NULL;
00361 }
00362 
00363 /*
00364 ** dav_fs_close_lockdb:
00365 **
00366 ** Close it. Duh.
00367 */
00368 static void dav_fs_close_lockdb(dav_lockdb *lockdb)
00369 {
00370     if (lockdb->info->db != NULL)
00371         dav_dbm_close(lockdb->info->db);
00372 }
00373 
00374 /*
00375 ** dav_fs_build_fname_key
00376 **
00377 ** Given a pathname, build a DAV_TYPE_FNAME lock database key.
00378 */
00379 static apr_datum_t dav_fs_build_fname_key(apr_pool_t *p, const char *pathname)
00380 {
00381     apr_datum_t key;
00382 
00383     /* ### does this allocation have a proper lifetime? need to check */
00384     /* ### can we use a buffer for this? */
00385 
00386     /* size is TYPE + pathname + null */
00387     key.dsize = strlen(pathname) + 2;
00388     key.dptr = apr_palloc(p, key.dsize);
00389     *key.dptr = DAV_TYPE_FNAME;
00390     memcpy(key.dptr + 1, pathname, key.dsize - 1);
00391     if (key.dptr[key.dsize - 2] == '/')
00392         key.dptr[--key.dsize - 1] = '\0';
00393     return key;
00394 }
00395 
00396 /*
00397 ** dav_fs_build_key:  Given a resource, return a apr_datum_t key
00398 **    to look up lock information for this file.
00399 **
00400 **    (inode/dev not supported or file is lock-null):
00401 **       apr_datum_t->dvalue = full path
00402 **
00403 **    (inode/dev supported and file exists ):
00404 **       apr_datum_t->dvalue = inode, dev
00405 */
00406 static apr_datum_t dav_fs_build_key(apr_pool_t *p,
00407                                     const dav_resource *resource)
00408 {
00409     const char *file = dav_fs_pathname(resource);
00410     apr_datum_t key;
00411     apr_finfo_t finfo;
00412     apr_status_t rv;
00413 
00414     /* ### use lstat() ?? */
00415     /*
00416      * XXX: What for platforms with no IDENT (dev/inode)?
00417      */
00418     rv = apr_stat(&finfo, file, APR_FINFO_IDENT, p);
00419     if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
00420         && ((finfo.valid & APR_FINFO_IDENT) == APR_FINFO_IDENT))
00421     {
00422         /* ### can we use a buffer for this? */
00423         key.dsize = 1 + sizeof(finfo.inode) + sizeof(finfo.device);
00424         key.dptr = apr_palloc(p, key.dsize);
00425         *key.dptr = DAV_TYPE_INODE;
00426         memcpy(key.dptr + 1, &finfo.inode, sizeof(finfo.inode));
00427         memcpy(key.dptr + 1 + sizeof(finfo.inode), &finfo.device,
00428                sizeof(finfo.device));
00429 
00430         return key;
00431     }
00432 
00433     return dav_fs_build_fname_key(p, file);
00434 }
00435 
00436 /*
00437 ** dav_fs_lock_expired:  return 1 (true) if the given timeout is in the past
00438 **    or present (the lock has expired), or 0 (false) if in the future
00439 **    (the lock has not yet expired).
00440 */
00441 static int dav_fs_lock_expired(time_t expires)
00442 {
00443     return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
00444 }
00445 
00446 /*
00447 ** dav_fs_save_lock_record:  Saves the lock information specified in the
00448 **    direct and indirect lock lists about path into the lock database.
00449 **    If direct and indirect == NULL, the key is removed.
00450 */
00451 static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, apr_datum_t key,
00452                                            dav_lock_discovery *direct,
00453                                            dav_lock_indirect *indirect)
00454 {
00455     dav_error *err;
00456     apr_datum_t val = { 0 };
00457     char *ptr;
00458     dav_lock_discovery *dp = direct;
00459     dav_lock_indirect *ip = indirect;
00460 
00461 #if DAV_DEBUG
00462     if (lockdb->ro) {
00463         return dav_new_error(lockdb->info->pool,
00464                              HTTP_INTERNAL_SERVER_ERROR, 0,
00465                              "INTERNAL DESIGN ERROR: the lockdb was opened "
00466                              "readonly, but an attempt to save locks was "
00467                              "performed.");
00468     }
00469 #endif
00470 
00471     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
00472         /* ### add a higher-level error? */
00473         return err;
00474     }
00475 
00476     /* If nothing to save, delete key */
00477     if (dp == NULL && ip == NULL) {
00478         /* don't fail if the key is not present */
00479         /* ### but what about other errors? */
00480         (void) dav_dbm_delete(lockdb->info->db, key);
00481         return NULL;
00482     }
00483                 
00484     while(dp) {
00485         val.dsize += dav_size_direct(dp);
00486         dp = dp->next;
00487     }
00488     while(ip) {
00489         val.dsize += dav_size_indirect(ip);
00490         ip = ip->next;
00491     }
00492 
00493     /* ### can this be apr_palloc() ? */
00494     /* ### hmmm.... investigate the use of a buffer here */
00495     ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
00496     dp  = direct;
00497     ip  = indirect;
00498 
00499     while(dp) {
00500         *ptr++ = DAV_LOCK_DIRECT;       /* Direct lock - lock_discovery struct follows */
00501         memcpy(ptr, dp, sizeof(dp->f)); /* Fixed portion of struct */
00502         ptr += sizeof(dp->f);
00503         memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
00504         ptr += sizeof(*dp->locktoken);
00505         if (dp->owner == NULL) {
00506             *ptr++ = '\0';
00507         }
00508         else {
00509             memcpy(ptr, dp->owner, strlen(dp->owner) + 1);      
00510             ptr += strlen(dp->owner) + 1;
00511         }
00512         if (dp->auth_user == NULL) {
00513             *ptr++ = '\0';
00514         }
00515         else {
00516             memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
00517             ptr += strlen(dp->auth_user) + 1;
00518         }
00519 
00520         dp = dp->next;
00521     }
00522 
00523     while(ip) {
00524         *ptr++ = DAV_LOCK_INDIRECT;     /* Indirect lock prefix */
00525         memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));     /* Locktoken */
00526         ptr += sizeof(*ip->locktoken);
00527         memcpy(ptr, &ip->timeout, sizeof(ip->timeout));         /* Expire time */
00528         ptr += sizeof(ip->timeout);
00529         memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));     /* Size of key */
00530         ptr += sizeof(ip->key.dsize);
00531         memcpy(ptr, ip->key.dptr, ip->key.dsize);       /* Key data */
00532         ptr += ip->key.dsize;
00533         ip = ip->next;
00534     }
00535 
00536     if ((err = dav_dbm_store(lockdb->info->db, key, val)) != NULL) {
00537         /* ### more details? add an error_id? */
00538         return dav_push_error(lockdb->info->pool,
00539                               HTTP_INTERNAL_SERVER_ERROR,
00540                               DAV_ERR_LOCK_SAVE_LOCK,
00541                               "Could not save lock information.",
00542                               err);
00543     }
00544 
00545     return NULL;
00546 }
00547 
00548 /*
00549 ** dav_load_lock_record:  Reads lock information about key from lock db;
00550 **    creates linked lists of the direct and indirect locks.
00551 **
00552 **    If add_method = DAV_APPEND_LIST, the result will be appended to the
00553 **    head of the direct and indirect lists supplied.
00554 **
00555 **    Passive lock removal:  If lock has timed out, it will not be returned.
00556 **    ### How much "logging" does RFC 2518 require?
00557 */
00558 static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, apr_datum_t key,
00559                                            int add_method,
00560                                            dav_lock_discovery **direct,
00561                                            dav_lock_indirect **indirect)
00562 {
00563     apr_pool_t *p = lockdb->info->pool;
00564     dav_error *err;
00565     apr_size_t offset = 0;
00566     int need_save = DAV_FALSE;
00567     apr_datum_t val = { 0 };
00568     dav_lock_discovery *dp;
00569     dav_lock_indirect *ip;
00570     dav_buffer buf = { 0 };
00571 
00572     if (add_method != DAV_APPEND_LIST) {
00573         *direct = NULL;
00574         *indirect = NULL;
00575     }
00576 
00577     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
00578         /* ### add a higher-level error? */
00579         return err;
00580     }
00581 
00582     /*
00583     ** If we opened readonly and the db wasn't there, then there are no
00584     ** locks for this resource. Just exit.
00585     */
00586     if (lockdb->info->db == NULL)
00587         return NULL;
00588 
00589     if ((err = dav_dbm_fetch(lockdb->info->db, key, &val)) != NULL)
00590         return err;
00591         
00592     if (!val.dsize)
00593         return NULL;
00594 
00595     while (offset < val.dsize) {
00596         switch (*(val.dptr + offset++)) {
00597         case DAV_LOCK_DIRECT:
00598             /* Create and fill a dav_lock_discovery structure */
00599 
00600             dp = apr_pcalloc(p, sizeof(*dp));
00601             memcpy(dp, val.dptr + offset, sizeof(dp->f));
00602             offset += sizeof(dp->f);
00603             dp->locktoken = apr_palloc(p, sizeof(*dp->locktoken));
00604             memcpy(dp->locktoken, val.dptr + offset, sizeof(*dp->locktoken));
00605             offset += sizeof(*dp->locktoken);
00606             if (*(val.dptr + offset) == '\0') {
00607                 ++offset;
00608             }
00609             else {
00610                 dp->owner = apr_pstrdup(p, val.dptr + offset);
00611                 offset += strlen(dp->owner) + 1;
00612             }
00613 
00614             if (*(val.dptr + offset) == '\0') {
00615                 ++offset;
00616             } 
00617             else {
00618                 dp->auth_user = apr_pstrdup(p, val.dptr + offset);
00619                 offset += strlen(dp->auth_user) + 1;
00620             }
00621 
00622             if (!dav_fs_lock_expired(dp->f.timeout)) {
00623                 dp->next = *direct;
00624                 *direct = dp;
00625             }
00626             else {
00627                 need_save = DAV_TRUE;
00628 
00629                 /* Remove timed-out locknull fm .locknull list */
00630                 if (*key.dptr == DAV_TYPE_FNAME) {
00631                     const char *fname = key.dptr + 1;
00632                     apr_finfo_t finfo;
00633                     apr_status_t rv;
00634 
00635                     /* if we don't see the file, then it's a locknull */
00636                     rv = apr_lstat(&finfo, fname, APR_FINFO_MIN, p);
00637                     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
00638                         if ((err = dav_fs_remove_locknull_member(p, fname, &buf)) != NULL) {
00639                             /* ### push a higher-level description? */
00640                             return err;
00641                         }
00642                     }
00643                 }
00644             }
00645             break;
00646 
00647         case DAV_LOCK_INDIRECT:
00648             /* Create and fill a dav_lock_indirect structure */
00649 
00650             ip = apr_pcalloc(p, sizeof(*ip));
00651             ip->locktoken = apr_palloc(p, sizeof(*ip->locktoken));
00652             memcpy(ip->locktoken, val.dptr + offset, sizeof(*ip->locktoken));
00653             offset += sizeof(*ip->locktoken);
00654             memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
00655             offset += sizeof(ip->timeout);
00656             memcpy(&ip->key.dsize, val.dptr + offset, sizeof(ip->key.dsize)); /* length of datum */
00657             offset += sizeof(ip->key.dsize);
00658             ip->key.dptr = apr_palloc(p, ip->key.dsize); 
00659             memcpy(ip->key.dptr, val.dptr + offset, ip->key.dsize);
00660             offset += ip->key.dsize;
00661 
00662             if (!dav_fs_lock_expired(ip->timeout)) {
00663                 ip->next = *indirect;
00664                 *indirect = ip;
00665             }
00666             else {
00667                 need_save = DAV_TRUE;
00668                 /* A locknull resource will never be locked indirectly */
00669             }
00670 
00671             break;
00672 
00673         default:
00674             dav_dbm_freedatum(lockdb->info->db, val);
00675 
00676             /* ### should use a computed_desc and insert corrupt token data */
00677             --offset;
00678             return dav_new_error(p,
00679                                  HTTP_INTERNAL_SERVER_ERROR,
00680                                  DAV_ERR_LOCK_CORRUPT_DB,
00681                                  apr_psprintf(p,
00682                                              "The lock database was found to "
00683                                              "be corrupt. offset %"
00684                                              APR_SIZE_T_FMT ", c=%02x",
00685                                              offset, val.dptr[offset]));
00686         }
00687     }
00688 
00689     dav_dbm_freedatum(lockdb->info->db, val);
00690 
00691     /* Clean up this record if we found expired locks */
00692     /*
00693     ** ### shouldn't do this if we've been opened READONLY. elide the
00694     ** ### timed-out locks from the response, but don't save that info back
00695     */
00696     if (need_save == DAV_TRUE) {
00697         return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
00698     }
00699 
00700     return NULL;
00701 }
00702 
00703 /* resolve <indirect>, returning <*direct> */
00704 static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
00705                                   dav_lock_indirect *indirect,
00706                                   dav_lock_discovery **direct,
00707                                   dav_lock_discovery **ref_dp,
00708                                   dav_lock_indirect **ref_ip)
00709 {
00710     dav_error *err;
00711     dav_lock_discovery *dir;
00712     dav_lock_indirect *ind;
00713         
00714     if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
00715                                        DAV_CREATE_LIST,
00716                                        &dir, &ind)) != NULL) {
00717         /* ### insert a higher-level description? */
00718         return err;
00719     }
00720     if (ref_dp != NULL) {
00721         *ref_dp = dir;
00722         *ref_ip = ind;
00723     }
00724                 
00725     for (; dir != NULL; dir = dir->next) {
00726         if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
00727             *direct = dir;
00728             return NULL;
00729         }
00730     }
00731 
00732     /* No match found (but we should have found one!) */
00733 
00734     /* ### use a different description and/or error ID? */
00735     return dav_new_error(lockdb->info->pool,
00736                          HTTP_INTERNAL_SERVER_ERROR,
00737                          DAV_ERR_LOCK_CORRUPT_DB,
00738                          "The lock database was found to be corrupt. "
00739                          "An indirect lock's direct lock could not "
00740                          "be found.");
00741 }
00742 
00743 /* ---------------------------------------------------------------
00744 **
00745 ** Property-related lock functions
00746 **
00747 */
00748 
00749 /*
00750 ** dav_fs_get_supportedlock:  Returns a static string for all supportedlock
00751 **    properties. I think we save more returning a static string than
00752 **    constructing it every time, though it might look cleaner.
00753 */
00754 static const char *dav_fs_get_supportedlock(const dav_resource *resource)
00755 {
00756     static const char supported[] = DEBUG_CR
00757         "<D:lockentry>" DEBUG_CR
00758         "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
00759         "<D:locktype><D:write/></D:locktype>" DEBUG_CR
00760         "</D:lockentry>" DEBUG_CR
00761         "<D:lockentry>" DEBUG_CR
00762         "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
00763         "<D:locktype><D:write/></D:locktype>" DEBUG_CR
00764         "</D:lockentry>" DEBUG_CR;
00765 
00766     return supported;
00767 }
00768 
00769 /* ---------------------------------------------------------------
00770 **
00771 ** General lock functions
00772 **
00773 */
00774 
00775 /* ---------------------------------------------------------------
00776 **
00777 ** Functions dealing with lock-null resources
00778 **
00779 */
00780 
00781 /*
00782 ** dav_fs_load_locknull_list:  Returns a dav_buffer dump of the locknull file
00783 **    for the given directory.
00784 */
00785 static dav_error * dav_fs_load_locknull_list(apr_pool_t *p, const char *dirpath,
00786                                              dav_buffer *pbuf) 
00787 {
00788     apr_finfo_t finfo;
00789     apr_file_t *file = NULL;
00790     dav_error *err = NULL;
00791     apr_size_t amt;
00792     apr_status_t rv;
00793 
00794     dav_buffer_init(p, pbuf, dirpath);
00795 
00796     if (pbuf->buf[pbuf->cur_len - 1] == '/')
00797         pbuf->buf[--pbuf->cur_len] = '\0';
00798 
00799     dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
00800 
00801     /* reset this in case we leave w/o reading into the buffer */
00802     pbuf->cur_len = 0;
00803 
00804     if (apr_file_open(&file, pbuf->buf, APR_READ | APR_BINARY, APR_OS_DEFAULT,
00805                 p) != APR_SUCCESS) {
00806         return NULL;
00807     }
00808 
00809     rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
00810     if (rv != APR_SUCCESS) {
00811         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00812                             apr_psprintf(p,
00813                                         "Opened but could not stat file %s",
00814                                         pbuf->buf));
00815         goto loaderror;
00816     }
00817 
00818     if (finfo.size != (apr_size_t)finfo.size) {
00819         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00820                             apr_psprintf(p,
00821                                         "Opened but rejected huge file %s",
00822                                         pbuf->buf));
00823         goto loaderror;
00824     }
00825 
00826     amt = (apr_size_t)finfo.size;
00827     dav_set_bufsize(p, pbuf, amt);
00828     if (apr_file_read(file, pbuf->buf, &amt) != APR_SUCCESS
00829         || amt != finfo.size) {
00830         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00831                             apr_psprintf(p,
00832                                         "Failure reading locknull file "
00833                                         "for %s", dirpath));
00834 
00835         /* just in case the caller disregards the returned error */
00836         pbuf->cur_len = 0;
00837         goto loaderror;
00838     }
00839 
00840   loaderror:
00841     apr_file_close(file);
00842     return err;
00843 }
00844 
00845 /*
00846 ** dav_fs_save_locknull_list:  Saves contents of pbuf into the
00847 **    locknull file for dirpath.
00848 */
00849 static dav_error * dav_fs_save_locknull_list(apr_pool_t *p, const char *dirpath,
00850                                              dav_buffer *pbuf)
00851 {
00852     const char *pathname;
00853     apr_file_t *file = NULL;
00854     dav_error *err = NULL;
00855     apr_size_t amt;
00856 
00857     if (pbuf->buf == NULL)
00858         return NULL;
00859 
00860     dav_fs_ensure_state_dir(p, dirpath);
00861     pathname = apr_pstrcat(p,
00862                           dirpath,
00863                           dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
00864                           DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
00865                           NULL);
00866 
00867     if (pbuf->cur_len == 0) {
00868         /* delete the file if cur_len == 0 */
00869         if (apr_file_remove(pathname, p) != 0) {
00870             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00871                                  apr_psprintf(p,
00872                                              "Error removing %s", pathname));
00873         }
00874         return NULL;
00875     }
00876 
00877     if (apr_file_open(&file, pathname,
00878                 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
00879                 APR_OS_DEFAULT, p) != APR_SUCCESS) {
00880         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00881                              apr_psprintf(p,
00882                                          "Error opening %s for writing",
00883                                          pathname));
00884     }
00885 
00886     amt = pbuf->cur_len;
00887     if (apr_file_write(file, pbuf->buf, &amt) != APR_SUCCESS
00888         || amt != pbuf->cur_len) {
00889         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00890                             apr_psprintf(p,
00891                                         "Error writing %" APR_SIZE_T_FMT 
00892                                         " bytes to %s",
00893                                         pbuf->cur_len, pathname));
00894     }
00895 
00896     apr_file_close(file);
00897     return err;
00898 }
00899 
00900 /*
00901 ** dav_fs_remove_locknull_member:  Removes filename from the locknull list
00902 **    for directory path.
00903 */
00904 static dav_error * dav_fs_remove_locknull_member(apr_pool_t *p,
00905                                                  const char *filename,
00906                                                  dav_buffer *pbuf)
00907 {
00908     dav_error *err;
00909     apr_size_t len;
00910     apr_size_t scanlen;
00911     char *scan;
00912     const char *scanend;
00913     char *dirpath = apr_pstrdup(p, filename);
00914     char *fname = strrchr(dirpath, '/');
00915     int dirty = 0;
00916 
00917     if (fname != NULL)
00918         *fname++ = '\0';
00919     else
00920         fname = dirpath;
00921     len = strlen(fname) + 1;
00922 
00923     if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
00924         /* ### add a higher level description? */
00925         return err;
00926     }
00927 
00928     for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
00929          scan < scanend;
00930          scan += scanlen) {
00931         scanlen = strlen(scan) + 1;
00932         if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
00933             pbuf->cur_len -= scanlen;
00934             memmove(scan, scan + scanlen, scanend - (scan + scanlen));
00935             dirty = 1;
00936             break;
00937         }
00938     }
00939 
00940     if (dirty) {
00941         if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
00942             /* ### add a higher level description? */
00943             return err;
00944         }
00945     }
00946 
00947     return NULL;
00948 }
00949 
00950 /* Note: used by dav_fs_repos.c */
00951 dav_error * dav_fs_get_locknull_members(
00952     const dav_resource *resource,
00953     dav_buffer *pbuf)
00954 {
00955     const char *dirpath;
00956 
00957     /* ### should test this result value... */
00958     (void) dav_fs_dir_file_name(resource, &dirpath, NULL);
00959     return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
00960 }
00961 
00962 /* ### fold into append_lock? */
00963 /* ### take an optional buf parameter? */
00964 static dav_error * dav_fs_add_locknull_state(
00965     dav_lockdb *lockdb,
00966     const dav_resource *resource)
00967 {
00968     dav_buffer buf = { 0 };
00969     apr_pool_t *p = lockdb->info->pool;
00970     const char *dirpath;
00971     const char *fname;
00972     dav_error *err;
00973 
00974     /* ### should test this result value... */
00975     (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
00976 
00977     if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
00978         return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00979                               "Could not load .locknull file.", err);
00980     }
00981 
00982     dav_buffer_append(p, &buf, fname);
00983     buf.cur_len++;      /* we want the null-term here */
00984 
00985     if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
00986         return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00987                               "Could not save .locknull file.", err);
00988     }
00989 
00990     return NULL;
00991 }
00992 
00993 /*
00994 ** dav_fs_remove_locknull_state:  Given a request, check to see if r->filename
00995 **    is/was a lock-null resource.  If so, return it to an existant state.
00996 **
00997 **    ### this function is broken... it doesn't check!
00998 **
00999 **    In this implementation, this involves two things:
01000 **    (a) remove it from the list in the appropriate .DAV/locknull file
01001 **    (b) on *nix, convert the key from a filename to an inode.
01002 */
01003 static dav_error * dav_fs_remove_locknull_state(
01004     dav_lockdb *lockdb,
01005     const dav_resource *resource)
01006 {
01007     dav_buffer buf = { 0 };
01008     dav_error *err;
01009     apr_pool_t *p = lockdb->info->pool;
01010     const char *pathname = dav_fs_pathname(resource);
01011 
01012     if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
01013         /* ### add a higher-level description? */
01014         return err;
01015     }
01016 
01017     {
01018         dav_lock_discovery *ld;
01019         dav_lock_indirect  *id;
01020         apr_datum_t key;
01021 
01022         /*
01023         ** Fetch the lock(s) that made the resource lock-null. Remove
01024         ** them under the filename key. Obtain the new inode key, and
01025         ** save the same lock information under it.
01026         */
01027         key = dav_fs_build_fname_key(p, pathname);
01028         if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
01029                                            &ld, &id)) != NULL) {
01030             /* ### insert a higher-level error description */
01031             return err;
01032         }
01033 
01034         if ((err = dav_fs_save_lock_record(lockdb, key, NULL, NULL)) != NULL) {
01035             /* ### insert a higher-level error description */
01036             return err;
01037         }
01038 
01039         key = dav_fs_build_key(p, resource);
01040         if ((err = dav_fs_save_lock_record(lockdb, key, ld, id)) != NULL) {
01041             /* ### insert a higher-level error description */
01042             return err;
01043         }
01044     }
01045 
01046     return NULL;
01047 }
01048 
01049 static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
01050                                       const dav_resource *resource,
01051                                       dav_lock **lock)
01052 {
01053     apr_datum_t key;
01054 
01055     key = dav_fs_build_key(lockdb->info->pool, resource);
01056 
01057     *lock = dav_fs_alloc_lock(lockdb,
01058                               key,
01059                               NULL);
01060 
01061     (*lock)->is_locknull = !resource->exists;
01062 
01063     return NULL;
01064 }
01065 
01066 static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
01067                                     const dav_resource *resource,
01068                                     int calltype,
01069                                     dav_lock **locks)
01070 {
01071     apr_pool_t *p = lockdb->info->pool;
01072     apr_datum_t key;
01073     dav_error *err;
01074     dav_lock *lock = NULL;
01075     dav_lock *newlock;
01076     dav_lock_discovery *dp;
01077     dav_lock_indirect *ip;
01078 
01079 #if DAV_DEBUG
01080     if (calltype == DAV_GETLOCKS_COMPLETE) {
01081         return dav_new_error(lockdb->info->pool,
01082                              HTTP_INTERNAL_SERVER_ERROR, 0,
01083                              "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
01084                              "is not yet supported");
01085     }
01086 #endif
01087 
01088     key = dav_fs_build_key(p, resource);
01089     if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
01090                                        &dp, &ip)) != NULL) {
01091         /* ### push a higher-level desc? */
01092         return err;
01093     }
01094 
01095     /* copy all direct locks to the result list */
01096     for (; dp != NULL; dp = dp->next) {
01097         newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
01098         newlock->is_locknull = !resource->exists;
01099         newlock->scope = dp->f.scope;
01100         newlock->type = dp->f.type;
01101         newlock->depth = dp->f.depth;
01102         newlock->timeout = dp->f.timeout;
01103         newlock->owner = dp->owner;
01104         newlock->auth_user = dp->auth_user;
01105 
01106         /* hook into the result list */
01107         newlock->next = lock;
01108         lock = newlock;
01109     }
01110 
01111     /* copy all the indirect locks to the result list. resolve as needed. */
01112     for (; ip != NULL; ip = ip->next) {
01113         newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
01114         newlock->is_locknull = !resource->exists;
01115 
01116         if (calltype == DAV_GETLOCKS_RESOLVED) {
01117             if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
01118                 /* ### push a higher-level desc? */
01119                 return err;
01120             }
01121 
01122             newlock->scope = dp->f.scope;
01123             newlock->type = dp->f.type;
01124             newlock->depth = dp->f.depth;
01125             newlock->timeout = dp->f.timeout;
01126             newlock->owner = dp->owner;
01127             newlock->auth_user = dp->auth_user;
01128         }
01129         else {
01130             /* DAV_GETLOCKS_PARTIAL */
01131             newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
01132         }
01133 
01134         /* hook into the result list */
01135         newlock->next = lock;
01136         lock = newlock;
01137     }
01138 
01139     *locks = lock;
01140     return NULL;
01141 }
01142 
01143 static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
01144                                     const dav_resource *resource,
01145                                     const dav_locktoken *locktoken,
01146                                     int partial_ok,
01147                                     dav_lock **lock)
01148 {
01149     dav_error *err;
01150     apr_datum_t key;
01151     dav_lock_discovery *dp;
01152     dav_lock_indirect *ip;
01153 
01154     *lock = NULL;
01155 
01156     key = dav_fs_build_key(lockdb->info->pool, resource);
01157     if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
01158                                        &dp, &ip)) != NULL) {
01159         /* ### push a higher-level desc? */
01160         return err;
01161     }
01162 
01163     for (; dp != NULL; dp = dp->next) {
01164         if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
01165             *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
01166             (*lock)->is_locknull = !resource->exists;
01167             (*lock)->scope = dp->f.scope;
01168             (*lock)->type = dp->f.type;
01169             (*lock)->depth = dp->f.depth;
01170             (*lock)->timeout = dp->f.timeout;
01171             (*lock)->owner = dp->owner;
01172             (*lock)->auth_user = dp->auth_user;
01173             return NULL;
01174         }
01175     }
01176 
01177     for (; ip != NULL; ip = ip->next) {
01178         if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
01179             *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
01180             (*lock)->is_locknull = !resource->exists;
01181 
01182             /* ### nobody uses the resolving right now! */
01183             if (partial_ok) {
01184                 (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
01185             }
01186             else {
01187                 (*lock)->rectype = DAV_LOCKREC_INDIRECT;
01188                 if ((err = dav_fs_resolve(lockdb, ip, &dp,
01189                                           NULL, NULL)) != NULL) {
01190                     /* ### push a higher-level desc? */
01191                     return err;
01192                 }
01193                 (*lock)->scope = dp->f.scope;
01194                 (*lock)->type = dp->f.type;
01195                 (*lock)->depth = dp->f.depth;
01196                 (*lock)->timeout = dp->f.timeout;
01197                 (*lock)->owner = dp->owner;
01198                 (*lock)->auth_user = dp->auth_user;
01199             }
01200             return NULL;
01201         }
01202     }
01203 
01204     return NULL;
01205 }
01206 
01207 static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
01208                                     const dav_resource *resource,
01209                                     int *locks_present)
01210 {
01211     dav_error *err;
01212     apr_datum_t key;
01213 
01214     *locks_present = 0;
01215 
01216     if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
01217         /* ### insert a higher-level error description */
01218         return err;
01219     }
01220 
01221     /*
01222     ** If we opened readonly and the db wasn't there, then there are no
01223     ** locks for this resource. Just exit.
01224     */
01225     if (lockdb->info->db == NULL)
01226         return NULL;
01227 
01228     key = dav_fs_build_key(lockdb->info->pool, resource);
01229 
01230     *locks_present = dav_dbm_exists(lockdb->info->db, key);
01231 
01232     return NULL;
01233 }
01234 
01235 static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
01236                                        const dav_resource *resource,
01237                                        int make_indirect,
01238                                        const dav_lock *lock)
01239 {
01240     apr_pool_t *p = lockdb->info->pool;
01241     dav_error *err;
01242     dav_lock_indirect *ip;
01243     dav_lock_discovery *dp;
01244     apr_datum_t key;
01245 
01246     key = dav_fs_build_key(lockdb->info->pool, resource);
01247     if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
01248         /* ### maybe add in a higher-level description */
01249         return err;
01250     }
01251 
01252     /*
01253     ** ### when we store the lock more directly, we need to update
01254     ** ### lock->rectype and lock->is_locknull
01255     */
01256 
01257     if (make_indirect) {
01258         for (; lock != NULL; lock = lock->next) {
01259 
01260             /* ### this works for any <lock> rectype */
01261             dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
01262 
01263             /* ### shut off the const warning for now */
01264             newi->locktoken = (dav_locktoken *)lock->locktoken;
01265             newi->timeout   = lock->timeout;
01266             newi->key       = lock->info->key;
01267             newi->next      = ip;
01268             ip              = newi;
01269         }
01270     }
01271     else {
01272         for (; lock != NULL; lock = lock->next) {
01273             /* create and link in the right kind of lock */
01274 
01275             if (lock->rectype == DAV_LOCKREC_DIRECT) {
01276                 dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
01277 
01278                 newd->f.scope = lock->scope;
01279                 newd->f.type = lock->type;
01280                 newd->f.depth = lock->depth;
01281                 newd->f.timeout = lock->timeout;
01282                 /* ### shut off the const warning for now */
01283                 newd->locktoken = (dav_locktoken *)lock->locktoken;
01284                 newd->owner = lock->owner;
01285                 newd->auth_user = lock->auth_user;
01286                 newd->next = dp;
01287                 dp = newd;
01288             }
01289             else {
01290                 /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
01291 
01292                 dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
01293 
01294                 /* ### shut off the const warning for now */
01295                 newi->locktoken = (dav_locktoken *)lock->locktoken;
01296                 newi->key       = lock->info->key;
01297                 newi->next      = ip;
01298                 ip              = newi;
01299             }
01300         }
01301     }
01302 
01303     if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
01304         /* ### maybe add a higher-level description */
01305         return err;
01306     }
01307 
01308     /* we have a special list for recording locknull resources */
01309     /* ### ack! this can add two copies to the locknull list */
01310     if (!resource->exists
01311         && (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
01312         /* ### maybe add a higher-level description */
01313         return err;
01314     }
01315 
01316     return NULL;
01317 }
01318 
01319 static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
01320                                       const dav_resource *resource,
01321                                       const dav_locktoken *locktoken)
01322 {
01323     dav_error *err;
01324     dav_buffer buf = { 0 };
01325     dav_lock_discovery *dh = NULL;
01326     dav_lock_indirect *ih =