diff options
Diffstat (limited to '')
-rw-r--r-- | modules/dav/main/util_lock.c | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/modules/dav/main/util_lock.c b/modules/dav/main/util_lock.c new file mode 100644 index 0000000..1b3a647 --- /dev/null +++ b/modules/dav/main/util_lock.c @@ -0,0 +1,798 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +** DAV repository-independent lock functions +*/ + +#include "apr.h" +#include "apr_strings.h" + +#include "mod_dav.h" +#include "http_log.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_core.h" + +APLOG_USE_MODULE(dav); + +/* --------------------------------------------------------------- +** +** Property-related lock functions +** +*/ + +/* +** dav_lock_get_activelock: Returns a <lockdiscovery> containing +** an activelock element for every item in the lock_discovery tree +*/ +DAV_DECLARE(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); + apr_snprintf(tmp, sizeof(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); + + /* + ** Check if the timeout is not, for any reason, already elapsed. + ** (e.g., because of a large collection, or disk under heavy load...) + */ + if (now >= lock->timeout) { + dav_buffer_append(p, pbuf, "Second-0"); + } + else { + apr_snprintf(tmp, sizeof(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_DECLARE(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, 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, 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, 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_DECLARE(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, 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, 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, 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. +*/ +DAV_DECLARE(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, 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. +*/ +DAV_DECLARE(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, APLOGNO(00623) + "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_DECLARE(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; +} |