From: Greg Stein Date: Sat, 14 Apr 2001 13:10:23 +0000 (+0000) Subject: Fix up the auto-versioning stuff. The new scheme more closely matches the X-Git-Tag: 2.0.17~17 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2cc437cd200b4831ef4a261b5fda3c17d00c9f8b;p=apache Fix up the auto-versioning stuff. The new scheme more closely matches the intent of DeltaV draft 14, simplifying some previous assumptions. Includes some heavy fixes to MOVE/COPY in a versioning world. Fix to CHECKOUT when a working resource is not created (checkout in place) Submitted by: John Vasta git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@88856 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index 4e4a352cda..f1a32bd9c4 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -1001,9 +1001,9 @@ static int dav_method_put(request_rec *r) } /* make sure the resource can be modified (if versioning repository) */ - if ((err = dav_ensure_resource_writable(r, resource, - 0 /* not parent_only */, - &av_info)) != NULL) { + if ((err = dav_auto_checkout(r, resource, + 0 /* not parent_only */, + &av_info)) != NULL) { /* ### add a higher-level description? */ return dav_handle_err(r, err, NULL); } @@ -1086,8 +1086,8 @@ static int dav_method_put(request_rec *r) } /* restore modifiability of resources back to what they were */ - err2 = dav_revert_resource_writability(r, resource, err != NULL /* undo if error */, - &av_info); + err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */, + 0 /*unlock*/, &av_info); /* check for errors now */ if (err != NULL) { @@ -1097,7 +1097,7 @@ static int dav_method_put(request_rec *r) /* just log a warning */ err2 = dav_push_error(r->pool, err->status, 0, "The PUT was successful, but there " - "was a problem reverting the writability of " + "was a problem automatically checking in " "the resource or its parent collection.", err2); dav_log_err(r, err2, APLOG_WARNING); @@ -1232,8 +1232,8 @@ static int dav_method_delete(request_rec *r) } /* if versioned resource, make sure parent is checked out */ - if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */, - &av_info)) != NULL) { + if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, + &av_info)) != NULL) { /* ### add a higher-level description? */ return dav_handle_err(r, err, NULL); } @@ -1242,8 +1242,8 @@ static int dav_method_delete(request_rec *r) err = (*resource->hooks->remove_resource)(resource, &multi_response); /* restore writability of parent back to what it was */ - err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */, - &av_info); + err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, + 0 /*unlock*/, &av_info); /* check for errors now */ if (err != NULL) { @@ -1258,8 +1258,8 @@ static int dav_method_delete(request_rec *r) /* just log a warning */ err = dav_push_error(r->pool, err2->status, 0, "The DELETE was successful, but there " - "was a problem reverting the writability of " - "its parent collection.", + "was a problem automatically checking in " + "the parent collection.", err2); dav_log_err(r, err, APLOG_WARNING); } @@ -2146,6 +2146,7 @@ static int dav_method_proppatch(request_rec *r) ap_text *propstat_text; apr_array_header_t *ctx_list; dav_prop_ctx *ctx; + dav_auto_version_info av_info; /* Ask repository module to resolve the resource */ err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, @@ -2178,8 +2179,19 @@ static int dav_method_proppatch(request_rec *r) return dav_handle_err(r, err, NULL); } + /* make sure the resource can be modified (if versioning repository) */ + if ((err = dav_auto_checkout(r, resource, + 0 /* not parent_only */, + &av_info)) != NULL) { + /* ### add a higher-level description? */ + return dav_handle_err(r, err, NULL); + } + if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces, &propdb)) != NULL) { + /* undo any auto-checkout */ + dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); + err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, apr_psprintf(r->pool, "Could not open the property " @@ -2212,6 +2224,9 @@ static int dav_method_proppatch(request_rec *r) if ((prop_group = dav_find_child(child, "prop")) == NULL) { dav_close_propdb(propdb); + /* undo any auto-checkout */ + dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); + /* This supplies additional information for the default message. */ ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, "A \"prop\" element is missing inside " @@ -2257,6 +2272,9 @@ static int dav_method_proppatch(request_rec *r) /* make sure this gets closed! */ dav_close_propdb(propdb); + /* complete any auto-versioning */ + dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info); + /* log any errors that occurred */ (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0); @@ -2384,8 +2402,8 @@ static int dav_method_mkcol(request_rec *r) } /* if versioned resource, make sure parent is checked out */ - if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */, - &av_info)) != NULL) { + if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, + &av_info)) != NULL) { /* ### add a higher-level description? */ return dav_handle_err(r, err, NULL); } @@ -2395,8 +2413,8 @@ static int dav_method_mkcol(request_rec *r) err = (*resource->hooks->create_collection)(resource); /* restore modifiability of parent back to what it was */ - err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */, - &av_info); + err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, + 0 /*unlock*/, &av_info); /* check for errors now */ if (err != NULL) { @@ -2406,8 +2424,8 @@ static int dav_method_mkcol(request_rec *r) /* just log a warning */ err = dav_push_error(r->pool, err->status, 0, "The MKCOL was successful, but there " - "was a problem reverting the writability of " - "its parent collection.", + "was a problem automatically checking in " + "the parent collection.", err2); dav_log_err(r, err, APLOG_WARNING); } @@ -2450,9 +2468,9 @@ static int dav_method_mkcol(request_rec *r) static int dav_method_copymove(request_rec *r, int is_move) { dav_resource *resource; - dav_auto_version_info src_av_info = { 0 }; dav_resource *resnew; - dav_auto_version_info dst_av_info; + dav_auto_version_info src_av_info = { 0 }; + dav_auto_version_info dst_av_info = { 0 }; const char *body; const char *dest; dav_error *err; @@ -2465,8 +2483,8 @@ static int dav_method_copymove(request_rec *r, int is_move) int depth; int result; dav_lockdb *lockdb; - int replaced; - int resource_state; + int replace_dest; + int resnew_state; /* Ask repository module to resolve the resource */ err = dav_get_resource(r, !is_move /* label_allowed */, @@ -2479,6 +2497,7 @@ static int dav_method_copymove(request_rec *r, int is_move) } /* If not a file or collection resource, COPY/MOVE not allowed */ + /* ### allow COPY/MOVE of DeltaV resource types */ if (resource->type != DAV_RESOURCE_TYPE_REGULAR) { body = apr_psprintf(r->pool, "Cannot COPY/MOVE resource %s.", @@ -2686,13 +2705,10 @@ static int dav_method_copymove(request_rec *r, int is_move) (void)dav_unlock(r, resource, NULL); } - /* remember whether target resource existed */ - replaced = resnew->exists; - /* if this is a move, then the source parent collection will be modified */ if (is_move) { - if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */, - &src_av_info)) != NULL) { + if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, + &src_av_info)) != NULL) { if (lockdb != NULL) (*lockdb->hooks->close_lockdb)(lockdb); @@ -2701,44 +2717,76 @@ static int dav_method_copymove(request_rec *r, int is_move) } } - /* prepare the destination collection for modification */ - if ((err = dav_ensure_resource_writable(r, resnew, 1 /* parent_only */, - &dst_av_info)) != NULL) { - /* could not make destination writable: - * if move, restore state of source parent - */ - if (is_move) { - (void) dav_revert_resource_writability(r, NULL, 1 /* undo */, - &src_av_info); - } + /* + * Remember the initial state of the destination, so the lock system + * can be notified as to how it changed. + */ + resnew_state = dav_get_resource_state(lookup.rnew, resnew); - if (lockdb != NULL) - (*lockdb->hooks->close_lockdb)(lockdb); + /* If destination does not exist, initialize resource object + * to be same type as the source. + */ + if (!resnew->exists) { + resnew->type = resource->type; + resnew->collection = resource->collection; + } - /* ### add a higher-level description? */ - return dav_handle_err(r, err, NULL); + /* In a MOVE operation, the destination is replaced by the source. + * In a COPY operation, if the destination exists, is under version + * control, and is the same resource type as the source, + * then it should not be replaced, but modified to be a copy of + * the source. + */ + if (!resnew->exists) + replace_dest = 0; + else if (is_move || !resource->versioned) + replace_dest = 1; + else if (resource->type != resnew->type) + replace_dest = 1; + else if ((resource->collection == 0) != (resnew->collection == 0)) + replace_dest = 1; + else + replace_dest = 0; + + /* If the destination must be created or replaced, + * make sure the parent collection is writable + */ + if (!resnew->exists || replace_dest) { + if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/, + &dst_av_info)) != NULL) { + /* could not make destination writable: + * if move, restore state of source parent + */ + if (is_move) { + (void) dav_auto_checkin(r, NULL, 1 /* undo */, + 0 /*unlock*/, &src_av_info); + } + + if (lockdb != NULL) + (*lockdb->hooks->close_lockdb)(lockdb); + + /* ### add a higher-level description? */ + return dav_handle_err(r, err, NULL); + } } /* If source and destination parents are the same, then - * use the same object, so status updates to one are reflected - * in the other, when reverting their writable states. + * use the same resource object, so status updates to one are reflected + * in the other, when doing auto-versioning. Otherwise, + * we may try to checkin the parent twice. */ if (src_av_info.parent_resource != NULL + && dst_av_info.parent_resource != NULL && (*src_av_info.parent_resource->hooks->is_same_resource) (src_av_info.parent_resource, dst_av_info.parent_resource)) { dst_av_info.parent_resource = src_av_info.parent_resource; } - /* New resource will be same kind as source */ - resnew->collection = resource->collection; - - resource_state = dav_get_resource_state(lookup.rnew, resnew); - - /* If target exists, remove it first (we know Ovewrite must be TRUE). - * Then try to copy/move the resource. + /* If destination is being replaced, remove it first + * (we know Ovewrite must be TRUE). Then try to copy/move the resource. */ - if (resnew->exists) + if (replace_dest) err = (*resnew->hooks->remove_resource)(resnew, &multi_response); if (err == NULL) { @@ -2750,13 +2798,13 @@ static int dav_method_copymove(request_rec *r, int is_move) &multi_response); } - /* restore parent collection states */ - err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */, - &dst_av_info); + /* perform any auto-versioning cleanup */ + err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, + 0 /*unlock*/, &dst_av_info); if (is_move) { - err3 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */, - &src_av_info); + err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, + 0 /*unlock*/, &src_av_info); } else err3 = NULL; @@ -2774,12 +2822,12 @@ static int dav_method_copymove(request_rec *r, int is_move) return dav_handle_err(r, err, multi_response); } - /* check for errors from reverting writability */ + /* check for errors from auto-versioning */ if (err2 != NULL) { /* just log a warning */ err = dav_push_error(r->pool, err2->status, 0, "The MOVE/COPY was successful, but there was a " - "problem reverting the writability of the " + "problem automatically checking in the " "source parent collection.", err2); dav_log_err(r, err, APLOG_WARNING); @@ -2788,8 +2836,8 @@ static int dav_method_copymove(request_rec *r, int is_move) /* just log a warning */ err = dav_push_error(r->pool, err3->status, 0, "The MOVE/COPY was successful, but there was a " - "problem reverting the writability of the " - "destination parent collection.", + "problem automatically checking in the " + "destination or its parent collection.", err3); dav_log_err(r, err, APLOG_WARNING); } @@ -2798,7 +2846,7 @@ static int dav_method_copymove(request_rec *r, int is_move) if (lockdb != NULL) { /* notify lock system that we have created/replaced a resource */ - err = dav_notify_created(r, lockdb, resnew, resource_state, depth); + err = dav_notify_created(r, lockdb, resnew, resnew_state, depth); (*lockdb->hooks->close_lockdb)(lockdb); @@ -2814,7 +2862,8 @@ static int dav_method_copymove(request_rec *r, int is_move) } /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */ - return dav_created(r, lookup.rnew->uri, "Destination", replaced); + return dav_created(r, lookup.rnew->uri, "Destination", + resnew_state == DAV_RESOURCE_EXISTS); } /* dav_method_lock: Handler to implement the DAV LOCK method @@ -3193,14 +3242,14 @@ static int dav_method_vsn_control(request_rec *r) } /* if in versioned collection, make sure parent is checked out */ - if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */, - &av_info)) != NULL) { + if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, + &av_info)) != NULL) { return dav_handle_err(r, err, NULL); } /* attempt to version-control the resource */ if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) { - dav_revert_resource_writability(r, resource, 1 /*undo*/, &av_info); + dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); err = dav_push_error(r->pool, HTTP_CONFLICT, 0, apr_psprintf(r->pool, "Could not VERSION-CONTROL resource %s.", @@ -3210,12 +3259,12 @@ static int dav_method_vsn_control(request_rec *r) } /* revert writability of parent directory */ - err = dav_revert_resource_writability(r, resource, 0 /*undo*/, &av_info); + err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info); if (err != NULL) { /* just log a warning */ err = dav_push_error(r->pool, err->status, 0, "The VERSION-CONTROL was successful, but there " - "was a problem reverting the writability of " + "was a problem automatically checking in " "the parent collection.", err); dav_log_err(r, err, APLOG_WARNING); @@ -3375,7 +3424,8 @@ static int dav_method_checkout(request_rec *r) /* ### do lock checks, once behavior is defined */ /* Do the checkout */ - if ((err = (*vsn_hooks->checkout)(resource, is_unreserved, is_fork_ok, + if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/, + is_unreserved, is_fork_ok, create_activity, activities, &working_resource)) != NULL) { err = dav_push_error(r->pool, HTTP_CONFLICT, 0, @@ -3389,9 +3439,14 @@ static int dav_method_checkout(request_rec *r) /* set the Cache-Control header, per the spec */ apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); - /* use appropriate URI for Location header */ - if (working_resource == NULL) - working_resource = resource; + /* if no working resource created, return OK, + * else return CREATED with working resource URL in Location header + */ + if (working_resource == NULL) { + /* no body */ + ap_set_content_length(r, 0); + return DONE; + } return dav_created(r, working_resource->uri, "Checked-out resource", 0); } @@ -3894,7 +3949,7 @@ static int dav_method_report(request_rec *r) * First determine whether a Target-Selector header is allowed * for this report. */ - label_allowed = (*vsn_hooks->report_target_selector_allowed)(doc); + label_allowed = (*vsn_hooks->report_label_header_allowed)(doc); err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */, &resource); if (err != NULL) @@ -4331,8 +4386,8 @@ static int dav_method_bind(request_rec *r) } /* prepare the destination collection for modification */ - if ((err = dav_ensure_resource_writable(r, binding, 1 /* parent_only */, - &av_info)) != NULL) { + if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */, + &av_info)) != NULL) { /* could not make destination writable */ return dav_handle_err(r, err, NULL); } @@ -4348,9 +4403,9 @@ static int dav_method_bind(request_rec *r) } /* restore parent collection states */ - err2 = dav_revert_resource_writability(r, NULL, - err != NULL /* undo if error */, - &av_info); + err2 = dav_auto_checkin(r, NULL, + err != NULL /* undo if error */, + 0 /*unlock*/, &av_info); /* check for error from remove/bind operations */ if (err != NULL) { @@ -4367,7 +4422,7 @@ static int dav_method_bind(request_rec *r) /* just log a warning */ err = dav_push_error(r->pool, err2->status, 0, "The BIND was successful, but there was a " - "problem reverting the writability of the " + "problem automatically checking in the " "source parent collection.", err2); dav_log_err(r, err, APLOG_WARNING); diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h index 012cee1075..466cb6d711 100644 --- a/modules/dav/main/mod_dav.h +++ b/modules/dav/main/mod_dav.h @@ -1737,8 +1737,12 @@ struct dav_hooks_repository dav_resource *resource ); - /* Copy one resource to another. The destination must not exist. + /* Copy one resource to another. The destination may exist, if it is + * versioned. * Handles both files and collections. Properties are copied as well. + * If the destination exists and is versioned, the provider must update + * the destination to have identical content to the source, + * recursively for collections. * The depth argument is ignored for a file, and can be either 0 or * DAV_INFINITY for a collection. * If an error occurs in a child resource, then the return value is @@ -1813,15 +1817,42 @@ void dav_add_vary_header(request_rec *in_req, request_rec *out_req, const dav_resource *resource); +/* +** Flags specifying auto-versioning behavior, returned by +** the auto_versionable hook. The value returned depends +** on both the state of the resource and the value of the +** DAV:auto-versioning property for the resource. +** +** If the resource does not exist (null or lock-null), +** DAV_AUTO_VERSION_ALWAYS causes creation of a new version-controlled resource +** +** If the resource is checked in, +** DAV_AUTO_VERSION_ALWAYS causes it to be checked out always, +** DAV_AUTO_VERSION_LOCKED causes it to be checked out only when locked +** +** If the resource is checked out, +** DAV_AUTO_VERSION_ALWAYS causes it to be checked in always, +** DAV_AUTO_VERSION_LOCKED causes it to be checked in when unlocked +** (note: a provider should allow auto-checkin only for resources which +** were automatically checked out) +** +** In all cases, DAV_AUTO_VERSION_NEVER results in no auto-versioning behavior. +*/ +typedef enum { + DAV_AUTO_VERSION_NEVER, + DAV_AUTO_VERSION_ALWAYS, + DAV_AUTO_VERSION_LOCKED +} dav_auto_version; + /* ** This structure is used to record what auto-versioning operations ** were done to make a resource writable, so that they can be undone ** at the end of a request. */ typedef struct { - int resource_created; /* 0 => resource existed previously */ - int resource_checkedout; /* 0 => resource was checked out */ - int parent_checkedout; /* 0 => parent was checked out */ + int resource_versioned; /* 1 => resource was auto-version-controlled */ + int resource_checkedout; /* 1 => resource was auto-checked-out */ + int parent_checkedout; /* 1 => parent was auto-checked-out */ dav_resource *parent_resource; /* parent resource, if it was needed */ } dav_auto_version_info; @@ -1836,14 +1867,18 @@ typedef struct { * child does not exist, then a new versioned resource is created and * checked out. * + * If auto-versioning is not enabled for a versioned resource, then an error is + * returned, since the resource cannot be modified. + * * The dav_auto_version_info structure is filled in with enough information * to restore both parent and child resources to the state they were in * before the auto-versioning operations occurred. */ -dav_error *dav_ensure_resource_writable(request_rec *r, - dav_resource *resource, - int parent_only, - dav_auto_version_info *av_info); +dav_error *dav_auto_checkout( + request_rec *r, + dav_resource *resource, + int parent_only, + dav_auto_version_info *av_info); /* Revert the writability of resources back to what they were * before they were modified. If undo == 0, then the resource @@ -1851,15 +1886,21 @@ dav_error *dav_ensure_resource_writable(request_rec *r, * If undo != 0, then resource modifications are discarded * (i.e. they are unchecked out). * + * Set the unlock flag to indicate that the resource is about + * to be unlocked; it will be checked in if the resource + * auto-versioning property indicates it should be. In this case, + * av_info is ignored, so it can be NULL. + * * The resource argument may be NULL if only the parent resource - * was made writable (i.e. the parent_only was != 0 in the - * dav_ensure_resource_writable call). + * was checked out (i.e. the parent_only was != 0 in the + * dav_auto_checkout call). */ -dav_error *dav_revert_resource_writability( +dav_error *dav_auto_checkin( request_rec *r, dav_resource *resource, int undo, - const dav_auto_version_info *av_info); + int unlock, + dav_auto_version_info *av_info); /* ** This structure is used to describe available reports @@ -1899,18 +1940,34 @@ struct dav_hooks_vsn const ap_xml_elem *elem, ap_text_header *option); + /* Determine whether a non-versioned (or non-existent) resource + * is versionable. Returns != 0 if resource can be versioned. + */ + int (*versionable)(const dav_resource *resource); + + /* Determine whether auto-versioning is enabled for a resource + * (which may not exist, or may not be versioned). If the resource + * is a checked-out resource, the provider must only enable + * auto-checkin if the resource was automatically checked out. + * + * The value returned depends on both the state of the resource + * and the value of its DAV:auto-version property. See the description + * of the dav_auto_version enumeration above for the details. + */ + dav_auto_version (*auto_versionable)(const dav_resource *resource); + /* Put a resource under version control. If the resource already * exists unversioned, then it becomes the initial version of the * new version history, and it is replaced by a version selector * which targets the new version. * - * If the resource does not exist, then a new version selector - * is created which either targets an existing version (if the + * If the resource does not exist, then a new version-controlled + * resource is created which either targets an existing version (if the * "target" argument is not NULL), or the initial, empty version * in a new history resource (if the "target" argument is NULL). * * If successful, the resource object state is updated appropriately - * (that is, changed to refer to the new version selector resource). + * (that is, changed to refer to the new version-controlled resource). */ dav_error * (*vsn_control)(dav_resource *resource, const char *target); @@ -1918,6 +1975,13 @@ struct dav_hooks_vsn /* Checkout a resource. If successful, the resource * object state is updated appropriately. * + * The auto_checkout flag will be set if this checkout is being + * done automatically, as part of some method which modifies + * the resource. The provider must remember that the resource + * was automatically checked out, so it can determine whether it + * can be automatically checked in. (Auto-checkin should only be + * enabled for resources which were automatically checked out.) + * * If the working resource has a different URL from the * target resource, a dav_resource descriptor is returned * for the new working resource. Otherwise, the original @@ -1934,6 +1998,7 @@ struct dav_hooks_vsn * no DAV:activity-set was provided or when create_activity is set. */ dav_error * (*checkout)(dav_resource *resource, + int auto_checkout, int is_unreserved, int is_fork_ok, int create_activity, apr_array_header_t *activities, @@ -1959,17 +2024,6 @@ struct dav_hooks_vsn int keep_checked_out, dav_resource **version_resource); - /* Determine whether a non-versioned (or non-existent) resource - * is versionable. Returns != 0 if resource can be versioned. - */ - int (*versionable)(const dav_resource *resource); - - /* Determine whether auto-versioning is enabled for a resource - * (which may not exist, or may not be versioned). - * Returns != 0 if auto-versioning is enabled. - */ - int (*auto_version_enabled)(const dav_resource *resource); - /* ** Return the set of reports available at this resource. ** @@ -1982,12 +2036,12 @@ struct dav_hooks_vsn const dav_report_elem **reports); /* - ** Determine whether a Target-Selector header can be used + ** Determine whether a Label header can be used ** with a particular report. The dav_xml_doc structure ** contains the parsed report request body. - ** Returns 0 if Target-Selector is not allowed. + ** Returns 0 if the Label header is not allowed. */ - int (*report_target_selector_allowed)(const ap_xml_doc *doc); + int (*report_label_header_allowed)(const ap_xml_doc *doc); /* ** Generate a report on a resource. Since a provider is free diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index 12929f7e46..1d523fcb11 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -1609,183 +1609,313 @@ void dav_add_vary_header(request_rec *in_req, } } +/* dav_can_auto_checkout + * + * Determine whether auto-checkout is enabled for a resource. + * r - the request_rec + * resource - the resource + * auto_version - the value of the auto_versionable hook for the resource + * lockdb - pointer to lock database (opened if necessary) + * auto_checkout - set to 1 if auto-checkout enabled + */ +static dav_error * dav_can_auto_checkout( + request_rec *r, + dav_resource *resource, + dav_auto_version auto_version, + dav_lockdb **lockdb, + int *auto_checkout) +{ + dav_error *err; + dav_lock *lock_list; + + *auto_checkout = 0; + + if (auto_version == DAV_AUTO_VERSION_ALWAYS) { + *auto_checkout = 1; + } + else if (auto_version == DAV_AUTO_VERSION_LOCKED) { + if (*lockdb == NULL) { + const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); + + if (locks_hooks == NULL) { + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + "Auto-checkout is only enabled for locked resources, " + "but there is no lock provider."); + } + + if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) { + return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + "Cannot open lock database to determine " + "auto-versioning behavior.", + err); + } + } + + if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) { + return dav_push_error(r->pool, + HTTP_INTERNAL_SERVER_ERROR, 0, + "The locks could not be queried for " + "determining auto-versioning behavior.", + err); + } + + if (lock_list != NULL) + *auto_checkout = 1; + } + + return NULL; +} + /* see mod_dav.h for docco */ -dav_error *dav_ensure_resource_writable(request_rec *r, - dav_resource *resource, - int parent_only, - dav_auto_version_info *av_info) +dav_error *dav_auto_checkout( + request_rec *r, + dav_resource *resource, + int parent_only, + dav_auto_version_info *av_info) { const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); - const char *body; - dav_error *err; + dav_lockdb *lockdb = NULL; + dav_error *err = NULL; /* Initialize results */ memset(av_info, 0, sizeof(*av_info)); + /* if no versioning provider, just return */ + if (vsn_hooks == NULL) + return NULL; + /* check parent resource if requested or if resource must be created */ if (!resource->exists || parent_only) { dav_resource *parent; if ((err = (*resource->hooks->get_parent_resource)(resource, &parent)) != NULL) - return err; + goto done; if (parent == NULL || !parent->exists) { - body = apr_psprintf(r->pool, - "Missing one or more intermediate collections. " - "Cannot create resource %s.", - ap_escape_html(r->pool, resource->uri)); - return dav_new_error(r->pool, HTTP_CONFLICT, 0, body); + err = dav_new_error(r->pool, HTTP_CONFLICT, 0, + apr_psprintf(r->pool, + "Missing one or more intermediate " + "collections. Cannot create resource %s.", + ap_escape_html(r->pool, resource->uri))); + goto done; } av_info->parent_resource = parent; - /* if parent not versioned, assume child can be created */ - if (!parent->versioned) { - return NULL; - } + /* if parent versioned and not checked out, see if it can be */ + if (parent->versioned && !parent->working) { + int checkout_parent; - /* if no versioning provider, something is terribly wrong */ - if (vsn_hooks == NULL) { - return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - "INTERNAL ERROR: " - "versioned resource with no versioning " - "provider?"); - } + if ((err = dav_can_auto_checkout(r, parent, + (*vsn_hooks->auto_versionable)(parent), + &lockdb, &checkout_parent)) + != NULL) { + goto done; + } - /* parent must be checked out */ - if (!parent->working) { - /* if parent cannot be automatically checked out, fail */ - if (!(*vsn_hooks->auto_version_enabled)(parent)) { - body = apr_psprintf(r->pool, - "Parent collection must be checked out. " - "Cannot create resource %s.", - ap_escape_html(r->pool, resource->uri)); - return dav_new_error(r->pool, HTTP_CONFLICT, 0, body); + if (!checkout_parent) { + err = dav_new_error(r->pool, HTTP_CONFLICT, 0, + ""); + goto done; } /* Try to checkout the parent collection. * Note that auto-versioning can only be applied to a version selector, * so no separate working resource will be created. */ - if ((err = (*vsn_hooks->checkout)(parent, 0, 0, 0, NULL, NULL)) + if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/, + 0, 0, 0, NULL, NULL)) != NULL) { - body = apr_psprintf(r->pool, - "Unable to checkout parent collection. " - "Cannot create resource %s.", - ap_escape_html(r->pool, resource->uri)); - return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err); + err = dav_push_error(r->pool, HTTP_CONFLICT, 0, + apr_psprintf(r->pool, + "Unable to auto-checkout parent collection. " + "Cannot create resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); + goto done; } /* remember that parent was checked out */ av_info->parent_checkedout = 1; } + } - /* if not just checking parent, create new child resource */ - if (!parent_only) { - if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) { - body = apr_psprintf(r->pool, - "Unable to create versioned resource %s.", - ap_escape_html(r->pool, resource->uri)); - return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err); - } + /* if only checking parent, we're done */ + if (parent_only) + goto done; + + /* if creating a new resource, see if it should be version-controlled */ + if (!resource->exists + && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) { + + if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) { + err = dav_push_error(r->pool, HTTP_CONFLICT, 0, + apr_psprintf(r->pool, + "Unable to create versioned resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); + goto done; + } - /* remember that resource was created */ - av_info->resource_created = 1; - } - } - else if (!resource->versioned) { - /* resource exists and is not versioned; assume it is writable */ - return NULL; + /* remember that resource was created */ + av_info->resource_versioned = 1; } - /* if not just checking parent, make sure child resource is checked out */ - if (!parent_only && !resource->working) { + /* if resource is versioned, make sure it is checked out */ + if (resource->versioned && !resource->working) { + int checkout_resource; + + if ((err = dav_can_auto_checkout(r, resource, + (*vsn_hooks->auto_versionable)(resource), + &lockdb, &checkout_resource)) != NULL) { + goto done; + } + + if (!checkout_resource) { + err = dav_new_error(r->pool, HTTP_CONFLICT, 0, + ""); + goto done; + } + /* Auto-versioning can only be applied to version selectors, so * no separate working resource will be created. */ - if ((err = (*vsn_hooks->checkout)(resource, 0, 0, 0, NULL, NULL)) + if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/, + 0, 0, 0, NULL, NULL)) != NULL) { - body = apr_psprintf(r->pool, - "Unable to checkout resource %s.", - ap_escape_html(r->pool, resource->uri)); - return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err); + err = dav_push_error(r->pool, HTTP_CONFLICT, 0, + apr_psprintf(r->pool, + "Unable to checkout resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); + goto done; } /* remember that resource was checked out */ av_info->resource_checkedout = 1; } +done: + + /* make sure lock database is closed */ + if (lockdb != NULL) + (*lockdb->hooks->close_lockdb)(lockdb); + + /* if an error occurred, undo any auto-versioning operations already done */ + if (err != NULL) { + dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info); + return err; + } + return NULL; } /* see mod_dav.h for docco */ -dav_error *dav_revert_resource_writability( +dav_error *dav_auto_checkin( request_rec *r, dav_resource *resource, int undo, - const dav_auto_version_info *av_info) + int unlock, + dav_auto_version_info *av_info) { const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); - const char *body; - dav_error *err; + dav_error *err = NULL; + dav_auto_version auto_version; + + /* If no versioning provider, this is a no-op */ + if (vsn_hooks == NULL) + return NULL; + + /* If undoing auto-checkouts, then do uncheckouts */ + if (undo) { + if (resource != NULL) { + if (av_info->resource_checkedout) { + if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) { + return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + apr_psprintf(r->pool, + "Unable to undo auto-checkout " + "of resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); + } + } - /* If a resource was provided, restore its writable state. - * Otherwise, only the parent must have been modified */ - if (resource != NULL) { - if (av_info->resource_checkedout) { - - if (undo) - err = (*vsn_hooks->uncheckout)(resource); - else - err = (*vsn_hooks->checkin)(resource, - 0 /*keep_checked_out*/, NULL); - - if (err != NULL) { - body = apr_psprintf(r->pool, - "Unable to %s resource %s.", - undo ? "uncheckout" : "checkin", - ap_escape_html(r->pool, resource->uri)); - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - body, err); + if (av_info->resource_versioned) { + dav_response *response; + + /* ### should we do anything with the response? */ + if ((err = (*resource->hooks->remove_resource)(resource, + &response)) != NULL) { + return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + apr_psprintf(r->pool, + "Unable to undo auto-version-control " + "of resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); + } } } - /* If undoing because of an error, and the resource was created, - * then remove it */ - if (undo && av_info->resource_created) { - dav_response *response; - - /* ### should we do anything with the response? */ - if ((err = (*resource->hooks->remove_resource)(resource, - &response)) != NULL) { - body = apr_psprintf(r->pool, - "Unable to undo creation of resource %s.", - ap_escape_html(r->pool, resource->uri)); + if (av_info->parent_resource != NULL && av_info->parent_checkedout) { + if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) { + return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + apr_psprintf(r->pool, + "Unable to undo auto-checkout " + "of parent collection %s.", + ap_escape_html(r->pool, av_info->parent_resource->uri)), + err); + } + } + + return NULL; + } + + /* If the resource was checked out, and auto-checkin is enabled, + * then check it in. + */ + if (resource != NULL && resource->working + && (unlock || av_info->resource_checkedout)) { + + auto_version = (*vsn_hooks->auto_versionable)(resource); + + if (auto_version == DAV_AUTO_VERSION_ALWAYS || + (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) { + + if ((err = (*vsn_hooks->checkin)(resource, + 0 /*keep_checked_out*/, NULL)) + != NULL) { return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - body, err); + apr_psprintf(r->pool, + "Unable to auto-checkin resource %s.", + ap_escape_html(r->pool, resource->uri)), + err); } } } - /* If parent resource was made writable, restore its state */ - if (av_info->parent_resource != NULL && av_info->parent_checkedout) { - - if (undo) - err = (*vsn_hooks->uncheckout)(av_info->parent_resource); - else - err = (*vsn_hooks->checkin)(av_info->parent_resource, - 0 /*keep_checked_out*/, NULL); - - if (err != NULL) { - body = apr_psprintf(r->pool, - "Unable to %s parent collection %s.", - undo ? "uncheckout" : "checkin", - ap_escape_html(r->pool, av_info->parent_resource->uri)); - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - body, err); - } + /* If parent resource was checked out, and auto-checkin is enabled, + * then check it in. + */ + if (av_info->parent_resource != NULL && av_info->parent_resource->working + && (unlock || av_info->parent_checkedout)) { + + auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource); + + if (auto_version == DAV_AUTO_VERSION_ALWAYS || + (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) { + + if ((err = (*vsn_hooks->checkin)(av_info->parent_resource, + 0 /*keep_checked_out*/, NULL)) + != NULL) { + return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, + apr_psprintf(r->pool, + "Unable to auto-checkin parent collection %s.", + ap_escape_html(r->pool, av_info->parent_resource->uri)), + err); + } + } } return NULL; diff --git a/modules/dav/main/util_lock.c b/modules/dav/main/util_lock.c index e4c231aa71..ff59b58e4a 100644 --- a/modules/dav/main/util_lock.c +++ b/modules/dav/main/util_lock.c @@ -417,6 +417,16 @@ 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) { @@ -521,6 +531,8 @@ int dav_unlock(request_rec *r, const dav_resource *resource, 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. */ @@ -558,35 +570,21 @@ int dav_unlock(request_rec *r, const dav_resource *resource, /* At this point, lock_resource/locktoken refers to a direct lock (key), ie * the root of a depth > 0 lock, or locktoken is null. */ - if ((err = (*hooks->remove_lock)(lockdb, lock_resource, - locktoken)) != NULL) { - /* ### add a higher-level desc? */ - /* ### return err! */ - return HTTP_INTERNAL_SERVER_ERROR; - } - - if (lock_resource->collection) { - dav_walker_ctx ctx = { { 0 } }; - dav_response *multi_status; - - 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.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; + ctx.r = r; + ctx.locktoken = locktoken; - err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); + err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); - /* ### fix this! */ - /* ### do something with multi_status */ - result = err == NULL ? OK : err->status; - } - else - result = OK; + /* ### fix this! */ + /* ### do something with multi_status */ + result = err == NULL ? OK : err->status; (*hooks->close_lockdb)(lockdb);