/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
** DAV repository-independent lock functions
*/

#include "apr.h"
#include "apr_strings.h"

#if APR_HAVE_STDIO_H
#include <stdio.h>              /* for sprintf() */
#endif

#include "mod_dav.h"
#include "http_log.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_core.h"


/* ---------------------------------------------------------------
**
** Property-related lock functions
**
*/

/*
** dav_lock_get_activelock:  Returns a <lockdiscovery> containing
**    an activelock element for every item in the lock_discovery tree
*/
const char *dav_lock_get_activelock(request_rec *r, dav_lock *lock,
				    dav_buffer *pbuf)
{
    dav_lock *lock_scan;
    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
    int count = 0;
    dav_buffer work_buf = { 0 };
    apr_pool_t *p = r->pool;

    /* If no locks or no lock provider, there are no locks */
    if (lock == NULL || hooks == NULL) {
	/*
	** Since resourcediscovery is defined with (activelock)*, 
	** <D:activelock/> shouldn't be necessary for an empty lock.
	*/
	return "";
    }

    /*
    ** Note: it could be interesting to sum the lengths of the owners
    **       and locktokens during this loop. However, the buffer
    **       mechanism provides some rough padding so that we don't
    **       really need to have an exact size. Further, constructing
    **       locktoken strings could be relatively expensive.
    */
    for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
	count++;

    /* if a buffer was not provided, then use an internal buffer */
    if (pbuf == NULL)
	pbuf = &work_buf;

    /* reset the length before we start appending stuff */
    pbuf->cur_len = 0;

    /* prep the buffer with a "good" size */
    dav_check_bufsize(p, pbuf, count * 300);

    for (; lock != NULL; lock = lock->next) {
	char tmp[100];

#if DAV_DEBUG
	if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
	    /* ### crap. design error */
	    dav_buffer_append(p, pbuf,
			      "DESIGN ERROR: attempted to product an "
			      "activelock element from a partial, indirect "
			      "lock record. Creating an XML parsing error "
			      "to ease detection of this situation: <");
	}
#endif

	dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
	switch (lock->type) {
	case DAV_LOCKTYPE_WRITE:
	    dav_buffer_append(p, pbuf, "<D:write/>");
	    break;
	default:
	    /* ### internal error. log something? */
	    break;
	}
	dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
	switch (lock->scope) {
	case DAV_LOCKSCOPE_EXCLUSIVE:
	    dav_buffer_append(p, pbuf, "<D:exclusive/>");
	    break;
	case DAV_LOCKSCOPE_SHARED:
	    dav_buffer_append(p, pbuf, "<D:shared/>");
	    break;
	default:
	    /* ### internal error. log something? */
	    break;
	}
	dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
	sprintf(tmp, "<D:depth>%s</D:depth>" DEBUG_CR,
		lock->depth == DAV_INFINITY ? "infinity" : "0");
	dav_buffer_append(p, pbuf, tmp);

	if (lock->owner) {
	    /*
	    ** This contains a complete, self-contained <DAV:owner> element,
	    ** with namespace declarations and xml:lang handling. Just drop
	    ** it in.
	    */
	    dav_buffer_append(p, pbuf, lock->owner);
	}
		
	dav_buffer_append(p, pbuf, "<D:timeout>");
	if (lock->timeout == DAV_TIMEOUT_INFINITE) {
	    dav_buffer_append(p, pbuf, "Infinite");
	}
	else {
	    time_t now = time(NULL);
	    sprintf(tmp, "Second-%lu", (long unsigned int)(lock->timeout - now));
	    dav_buffer_append(p, pbuf, tmp);
	}

	dav_buffer_append(p, pbuf,
			  "</D:timeout>" DEBUG_CR
			  "<D:locktoken>" DEBUG_CR
			  "<D:href>");
	dav_buffer_append(p, pbuf,
			  (*hooks->format_locktoken)(p, lock->locktoken));
	dav_buffer_append(p, pbuf,
			  "</D:href>" DEBUG_CR
			  "</D:locktoken>" DEBUG_CR
			  "</D:activelock>" DEBUG_CR);
    }

    return pbuf->buf;
}

/*
** dav_lock_parse_lockinfo:  Validates the given xml_doc to contain a
**    lockinfo XML element, then populates a dav_lock structure
**    with its contents.
*/
dav_error * dav_lock_parse_lockinfo(request_rec *r,
				    const dav_resource *resource,
				    dav_lockdb *lockdb,
				    const apr_xml_doc *doc,
				    dav_lock **lock_request)
{
    apr_pool_t *p = r->pool;
    dav_error *err;
    apr_xml_elem *child;
    dav_lock *lock;

    if (!dav_validate_root(doc, "lockinfo")) {
	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
			     "The request body contains an unexpected "
			     "XML root element.");
    }

    if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
                                             &lock)) != NULL) {
	return dav_push_error(p, err->status, 0,
			      "Could not parse the lockinfo due to an "
			      "internal problem creating a lock structure.",
			      err);
    }

    lock->depth = dav_get_depth(r, DAV_INFINITY);
    if (lock->depth == -1) {
	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
			     "An invalid Depth header was specified.");
    }
    lock->timeout = dav_get_timeout(r);

    /* Parse elements in the XML body */
    for (child = doc->root->first_child; child; child = child->next) {
	if (strcmp(child->name, "locktype") == 0
	    && child->first_child
	    && lock->type == DAV_LOCKTYPE_UNKNOWN) {
	    if (strcmp(child->first_child->name, "write") == 0) {
		lock->type = DAV_LOCKTYPE_WRITE;
		continue;
	    }
	}
	if (strcmp(child->name, "lockscope") == 0
	    && child->first_child
	    && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
	    if (strcmp(child->first_child->name, "exclusive") == 0)
		lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
	    else if (strcmp(child->first_child->name, "shared") == 0)
		lock->scope = DAV_LOCKSCOPE_SHARED;
	    if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
		continue;
	}

	if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
	    const char *text;

	    /* quote all the values in the <DAV:owner> element */
	    apr_xml_quote_elem(p, child);

	    /*
	    ** Store a full <DAV:owner> element with namespace definitions
	    ** and an xml:lang definition, if applicable.
	    */
	    apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces, 
			    NULL, &text, NULL);
	    lock->owner = text;

	    continue;
	}

	return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
			     apr_psprintf(p,
					 "The server cannot satisfy the "
					 "LOCK request due to an unknown XML "
					 "element (\"%s\") within the "
					 "DAV:lockinfo element.",
					 child->name));
    }

    *lock_request = lock;
    return NULL;
}

/* ---------------------------------------------------------------
**
** General lock functions
**
*/

/* dav_lock_walker:  Walker callback function to record indirect locks */
static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
{
    dav_walker_ctx *ctx = wres->walk_ctx;
    dav_error *err;

    /* We don't want to set indirects on the target */
    if ((*wres->resource->hooks->is_same_resource)(wres->resource,
                                                   ctx->w.root))
	return NULL;

    if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
                                                     wres->resource, 1,
                                                     ctx->lock)) != NULL) {
	if (ap_is_HTTP_SERVER_ERROR(err->status)) {
	    /* ### add a higher-level description? */
	    return err;
	}

	/* add to the multistatus response */
	dav_add_response(wres, err->status, NULL);

	/*
	** ### actually, this is probably wrong: we want to fail the whole
	** ### LOCK process if something goes bad. maybe the caller should
	** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
	*/
    }

    return NULL;
}

/*
** dav_add_lock:  Add a direct lock for resource, and indirect locks for
**    all children, bounded by depth.
**    ### assume request only contains one lock
*/
dav_error * dav_add_lock(request_rec *r, const dav_resource *resource,
			 dav_lockdb *lockdb, dav_lock *lock,
			 dav_response **response)
{
    dav_error *err;
    int depth = lock->depth;

    *response = NULL;

    /* Requested lock can be:
     *   Depth: 0   for null resource, existing resource, or existing collection
     *   Depth: Inf for existing collection
     */

    /*
    ** 2518 9.2 says to ignore depth if target is not a collection (it has
    **   no internal children); pretend the client gave the correct depth.
    */
    if (!resource->collection) {
	depth = 0;
    }

    /* In all cases, first add direct entry in lockdb */

    /*
    ** Append the new (direct) lock to the resource's existing locks.
    **
    ** Note: this also handles locknull resources
    */
    if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
                                              lock)) != NULL) {
	/* ### maybe add a higher-level description */
	return err;
    }

    if (depth > 0) {
	/* Walk existing collection and set indirect locks */
        dav_walker_ctx ctx = { { 0 } };
        dav_response *multi_status;

	ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
	ctx.w.func = dav_lock_walker;
        ctx.w.walk_ctx = &ctx;
	ctx.w.pool = r->pool;
        ctx.w.root = resource;
	ctx.w.lockdb = lockdb;

	ctx.r = r;
	ctx.lock = lock;

	err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
	if (err != NULL) {
	    /* implies a 5xx status code occurred. screw the multistatus */
	    return err;
	}

	if (multi_status != NULL) {
	    /* manufacture a 207 error for the multistatus response */
	    *response = multi_status;
	    return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
				 "Error(s) occurred on resources during the "
				 "addition of a depth lock.");
	}
    }

    return NULL;
}

/*
** dav_lock_query:  Opens the lock database. Returns a linked list of
**    dav_lock structures for all direct locks on path.
*/
DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb, 
                                       const dav_resource *resource,
                                       dav_lock **locks)
{
    /* If no lock database, return empty result */
    if (lockdb == NULL) {
        *locks = NULL;
        return NULL;
    }

    /* ### insert a higher-level description? */
    return (*lockdb->hooks->get_locks)(lockdb, resource,
				       DAV_GETLOCKS_RESOLVED,
				       locks);
}

/* dav_unlock_walker:  Walker callback function to remove indirect locks */
static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
{
    dav_walker_ctx *ctx = wres->walk_ctx;
    dav_error *err;

    /* Before removing the lock, do any auto-checkin required */
    if (wres->resource->working) {
        /* ### get rid of this typecast */
        if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
                                    0 /*undo*/, 1 /*unlock*/, NULL))
            != NULL) {
            return err;
        }
    }

    if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
                                                    wres->resource,
                                                    ctx->locktoken)) != NULL) {
	/* ### should we stop or return a multistatus? looks like STOP */
	/* ### add a higher-level description? */
	return err;
    }

    return NULL;
}

/*
** dav_get_direct_resource:
**
** Find a lock on the specified resource, then return the resource the
** lock was applied to (in other words, given a (possibly) indirect lock,
** return the direct lock's corresponding resource).
**
** If the lock is an indirect lock, this usually means traversing up the
** namespace [repository] hierarchy. Note that some lock providers may be
** able to return this information with a traversal.
*/
static dav_error * dav_get_direct_resource(apr_pool_t *p,
					   dav_lockdb *lockdb,
					   const dav_locktoken *locktoken,
					   const dav_resource *resource,
					   const dav_resource **direct_resource)
{
    if (lockdb->hooks->lookup_resource != NULL) {
	return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
						 resource, direct_resource);
    }

    *direct_resource = NULL;

    /* Find the top of this lock-
     * If r->filename's direct   locks include locktoken, use r->filename.
     * If r->filename's indirect locks include locktoken, retry r->filename/..
     * Else fail.
     */
    while (resource != NULL) {
	dav_error *err;
	dav_lock *lock;
        dav_resource *parent;

	/*
	** Find the lock specified by <locktoken> on <resource>. If it is
	** an indirect lock, then partial results are okay. We're just
	** trying to find the thing and know whether it is a direct or
	** an indirect lock.
	*/
	if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
					       1, &lock)) != NULL) {
	    /* ### add a higher-level desc? */
	    return err;
	}

	/* not found! that's an error. */
	if (lock == NULL) {
	    return dav_new_error(p, HTTP_BAD_REQUEST, 0,
				 "The specified locktoken does not correspond "
				 "to an existing lock on this resource.");
	}

	if (lock->rectype == DAV_LOCKREC_DIRECT) {
	    /* we found the direct lock. return this resource. */

	    *direct_resource = resource;
	    return NULL;
	}

	/* the lock was indirect. move up a level in the URL namespace */
	if ((err = (*resource->hooks->get_parent_resource)(resource,
                                                           &parent)) != NULL) {
            /* ### add a higher-level desc? */
            return err;
        }
        resource = parent;
    }

    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
			 "The lock database is corrupt. A direct lock could "
			 "not be found for the corresponding indirect lock "
			 "on this resource.");
}

/*
** dav_unlock:  Removes all direct and indirect locks for r->filename,
**    with given locktoken.  If locktoken == null_locktoken, all locks
**    are removed.  If r->filename represents an indirect lock,
**    we must unlock the appropriate direct lock.
**    Returns OK or appropriate HTTP_* response and logs any errors.
**
** ### We've already crawled the tree to ensure everything was locked
**     by us; there should be no need to incorporate a rollback.
*/
int dav_unlock(request_rec *r, const dav_resource *resource,
	       const dav_locktoken *locktoken)
{
    int result;
    dav_lockdb *lockdb;
    const dav_resource *lock_resource = resource;
    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
    const dav_hooks_repository *repos_hooks = resource->hooks;
    dav_walker_ctx ctx = { { 0 } };
    dav_response *multi_status;
    dav_error *err;

    /* If no locks provider, then there is nothing to unlock. */
    if (hooks == NULL) {
        return OK;
    }

    /* 2518 requires the entire lock to be removed if resource/locktoken
     * point to an indirect lock.  We need resource of the _direct_
     * lock in order to walk down the tree and remove the locks.  So,
     * If locktoken != null_locktoken, 
     *    Walk up the resource hierarchy until we see a direct lock.
     *    Or, we could get the direct lock's db/key, pick out the URL
     *    and do a subrequest.  I think walking up is faster and will work
     *    all the time.
     * Else
     *    Just start removing all locks at and below resource.
     */

    if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
	/* ### return err! maybe add a higher-level desc */
	/* ### map result to something nice; log an error */
	return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (locktoken != NULL
	&& (err = dav_get_direct_resource(r->pool, lockdb,
					  locktoken, resource,
					  &lock_resource)) != NULL) {
	/* ### add a higher-level desc? */
	/* ### should return err! */
	return err->status;
    }

    /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
     * the root of a depth > 0 lock, or locktoken is null.
     */
    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
    ctx.w.func = dav_unlock_walker;
    ctx.w.walk_ctx = &ctx;
    ctx.w.pool = r->pool;
    ctx.w.root = lock_resource;
    ctx.w.lockdb = lockdb;

    ctx.r = r;
    ctx.locktoken = locktoken;

    err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);

    /* ### fix this! */
    /* ### do something with multi_status */
    result = err == NULL ? OK : err->status;

    (*hooks->close_lockdb)(lockdb);

    return result;
}

/* dav_inherit_walker:  Walker callback function to inherit locks */
static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
{
    dav_walker_ctx *ctx = wres->walk_ctx;

    if (ctx->skip_root
	&& (*wres->resource->hooks->is_same_resource)(wres->resource,
                                                      ctx->w.root)) {
	return NULL;
    }

    /* ### maybe add a higher-level desc */
    return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
                                                 wres->resource, 1,
                                                 ctx->lock);
}

/*
** dav_inherit_locks:  When a resource or collection is added to a collection,
**    locks on the collection should be inherited to the resource/collection.
**    (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
**    parent of resource to resource and below.
*/
static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
				     const dav_resource *resource,
				     int use_parent)
{
    dav_error *err;
    const dav_resource *which_resource;
    dav_lock *locks;
    dav_lock *scan;
    dav_lock *prev;
    dav_walker_ctx ctx = { { 0 } };
    const dav_hooks_repository *repos_hooks = resource->hooks;
    dav_response *multi_status;

    if (use_parent) {
        dav_resource *parent;
	if ((err = (*repos_hooks->get_parent_resource)(resource,
                                                       &parent)) != NULL) {
            /* ### add a higher-level desc? */
            return err;
        }
	if (parent == NULL) {
	    /* ### map result to something nice; log an error */
	    return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
				 "Could not fetch parent resource. Unable to "
				 "inherit locks from the parent and apply "
				 "them to this resource.");
	}
        which_resource = parent;
    }
    else {
	which_resource = resource;
    }

    if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
					   DAV_GETLOCKS_PARTIAL,
					   &locks)) != NULL) {
	/* ### maybe add a higher-level desc */
	return err;
    }

    if (locks == NULL) {
	/* No locks to propagate, just return */
	return NULL;
    }

    /*
    ** (1) Copy all indirect locks from our parent;
    ** (2) Create indirect locks for the depth infinity, direct locks
    **     in our parent.
    **
    ** The append_locks call in the walker callback will do the indirect
    ** conversion, but we need to remove any direct locks that are NOT
    ** depth "infinity".
    */
    for (scan = locks, prev = NULL;
	 scan != NULL;
	 prev = scan, scan = scan->next) {

	if (scan->rectype == DAV_LOCKREC_DIRECT
	    && scan->depth != DAV_INFINITY) {

	    if (prev == NULL)
		locks = scan->next;
	    else
		prev->next = scan->next;
	}
    }

    /* <locks> has all our new locks.  Walk down and propagate them. */

    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
    ctx.w.func = dav_inherit_walker;
    ctx.w.walk_ctx = &ctx;
    ctx.w.pool = r->pool;
    ctx.w.root = resource;
    ctx.w.lockdb = lockdb;

    ctx.r = r;
    ctx.lock = locks;
    ctx.skip_root = !use_parent;

    /* ### do something with multi_status */
    return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
}

/* ---------------------------------------------------------------
**
** Functions dealing with lock-null resources
**
*/

/*
** dav_get_resource_state:  Returns the state of the resource
**    r->filename:  DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
**    or DAV_RESOURCE_EXIST.
**
**    Returns DAV_RESOURCE_ERROR if an error occurs.
*/
int dav_get_resource_state(request_rec *r, const dav_resource *resource)
{
    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);

    if (resource->exists)
	return DAV_RESOURCE_EXISTS;

    if (hooks != NULL) {
	dav_error *err;
	dav_lockdb *lockdb;
	int locks_present;

	/*
	** A locknull resource has the form:
	**
	**   known-dir "/" locknull-file
	**
	** It would be nice to look into <resource> to verify this form,
	** but it does not have enough information for us. Instead, we
	** can look at the path_info. If the form does not match, then
	** there is no way we could have a locknull resource -- it must
	** be a plain, null resource.
	**
	** Apache sets r->filename to known-dir/unknown-file and r->path_info
	** to "" for the "proper" case. If anything is in path_info, then
	** it can't be a locknull resource.
	**
	** ### I bet this path_info hack doesn't work for repositories.
	** ### Need input from repository implementors! What kind of
	** ### restructure do we need? New provider APIs?
	*/
	if (r->path_info != NULL && *r->path_info != '\0') {
	    return DAV_RESOURCE_NULL;
	}
	
        if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
	    /* note that we might see some expired locks... *shrug* */
	    err = (*hooks->has_locks)(lockdb, resource, &locks_present);
	    (*hooks->close_lockdb)(lockdb);
        }

        if (err != NULL) {
	    /* ### don't log an error. return err. add higher-level desc. */

	    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
		          "Failed to query lock-null status for %s",
			  r->filename);

	    return DAV_RESOURCE_ERROR;
        }

	if (locks_present)
	    return DAV_RESOURCE_LOCK_NULL;
    }

    return DAV_RESOURCE_NULL;
}

dav_error * dav_notify_created(request_rec *r,
			       dav_lockdb *lockdb,
			       const dav_resource *resource,
			       int resource_state,
			       int depth)
{
    dav_error *err;

    if (resource_state == DAV_RESOURCE_LOCK_NULL) {

	/*
	** The resource is no longer a locknull resource. This will remove
	** the special marker.
	**
	** Note that a locknull resource has already inherited all of the
	** locks from the parent. We do not need to call dav_inherit_locks.
	**
	** NOTE: some lock providers record locks for locknull resources using
	**       a different key than for regular resources. this will shift
	**       the lock information between the two key types.
	*/
	(void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);

	/*
	** There are resources under this one, which are new. We must
	** propagate the locks down to the new resources.
	*/
	if (depth > 0 &&
	    (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
	    /* ### add a higher level desc? */
	    return err;
	}
    }
    else if (resource_state == DAV_RESOURCE_NULL) {

	/* ### should pass depth to dav_inherit_locks so that it can
	** ### optimize for the depth==0 case.
	*/

	/* this resource should inherit locks from its parent */
	if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {

	    err = dav_push_error(r->pool, err->status, 0,
				 "The resource was created successfully, but "
				 "there was a problem inheriting locks from "
				 "the parent resource.",
				 err);
	    return err;
	}
    }
    /* else the resource already exists and its locks are correct. */

    return NULL;
}
