props.c revision b99dbaab171d91e1b664397cc40e039d0c087c65
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 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
* 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
*/
/*
** DAV extension module for Apache 2.0.*
** - Property database handling (repository-independent)
**
** NOTES:
**
** PROPERTY DATABASE
**
** This version assumes that there is a per-resource database provider
** to record properties. The database provider decides how and where to
** store these databases.
**
** The DBM keys for the properties have the following form:
**
** namespace ":" propname
**
** For example: 5:author
**
** The namespace provides an integer index into the namespace table
** (see below). propname is simply the property name, without a namespace
** prefix.
**
** A special case exists for properties that had a prefix starting with
** "xml". The XML Specification reserves these for future use. mod_dav
** stores and retrieves them unchanged. The keys for these properties
** have the form:
**
** ":" propname
**
** The propname will contain the prefix and the property name. For
** example, a key might be ":xmlfoo:name"
**
** The ":name" style will also be used for properties that do not
** exist within a namespace.
**
** The DBM values consist of two null-terminated strings, appended
** together (the null-terms are retained and stored in the database).
** The first string is the xml:lang value for the property. An empty
** string signifies that a lang value was not in context for the value.
** The second string is the property value itself.
**
**
** NAMESPACE TABLE
**
** The namespace table is an array that lists each of the namespaces
** that are in use by the properties in the given propdb. Each entry
** in the array is a simple URI.
**
** For example: http://www.foo.bar/standards/props/
**
** The prefix used for the property is stripped and the URI for it
** is entered into the namespace table. Also, any namespaces used
** within the property value will be entered into the table (and
** stripped from the child elements).
**
** The namespaces are stored in the DBM database under the "METADATA" key.
**
**
** STRIPPING NAMESPACES
**
** Within the property values, the namespace declarations (xmlns...)
** are stripped. Each element and attribute will have its prefix removed
** and a new prefix inserted.
**
** This must be done so that we can return multiple properties in a
** PROPFIND which may have (originally) used conflicting prefixes. For
** that case, we must bind all property value elements to new namespace
** values.
**
** This implies that clients must NOT be sensitive to the namespace
** prefix used for their properties. It WILL change when the properties
** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
** property value can contain ONLY XML elements and CDATA. PI and comment
** elements will be stripped. CDATA whitespace will be preserved, but
** whitespace within element tags will be altered. Attribute ordering
** may be altered. Element and CDATA ordering will be preserved.
**
**
** ATTRIBUTES ON PROPERTY NAME ELEMENTS
**
**
** <prop>
** <propname1>value</propname1>
** <propname2>value</propname1>
** </prop>
**
** This implementation (mod_dav) DOES NOT save any attributes that are
** associated with the <propname1> element. The property value is deemed
** to be only the contents ("value" in the above example).
**
** We do store the xml:lang value (if any) that applies to the context
** of the <propname1> element. Whether the xml:lang attribute is on
** <propname1> itself, or from a higher level element, we will store it
** with the property value.
**
**
** VERSIONING
**
** The DBM db contains a key named "METADATA" that holds database-level
** information, such as the namespace table. The record also contains the
** db's version number as the very first 16-bit value. This first number
** is actually stored as two single bytes: the first byte is a "major"
** version number. The second byte is a "minor" number.
**
** If the major number is not what mod_dav expects, then the db is closed
** immediately and an error is returned. A minor number change is
** acceptable -- it is presumed that old/new dav_props.c can deal with
** the database format. For example, a newer dav_props might update the
** minor value and append information to the end of the metadata record
** (which would be ignored by previous versions).
**
**
** ISSUES:
**
** At the moment, for the dav_get_allprops() and dav_get_props() functions,
** we must return a set of xmlns: declarations for ALL known namespaces
** in the file. There isn't a way to filter this because we don't know
** which are going to be used or not. Examining property names is not
** sufficient because the property values could use entirely different
** namespaces.
**
** ==> we must devise a scheme where we can "garbage collect" the namespace
** entries from the property database.
*/
#include "apr.h"
#include "apr_strings.h"
#define APR_WANT_STDIO
#define APR_WANT_BYTEFUNC
#include "apr_want.h"
#include "mod_dav.h"
#include "http_log.h"
#include "http_request.h"
/*
** There is some rough support for writable DAV:getcontenttype and
** DAV:getcontentlanguage properties. If this #define is (1), then
** this support is disabled.
**
** We are disabling it because of a lack of support in GET and PUT
** operations. For GET, it would be "expensive" to look for a propdb,
** open it, and attempt to extract the Content-Type and Content-Language
** values for the response.
** (Handling the PUT would not be difficult, though)
*/
#define DAV_DISABLE_WRITABLE_PROPS 1
#define DAV_GDBM_NS_KEY "METADATA"
#define DAV_GDBM_NS_KEY_LEN 8
/* the namespace URI was not found; no ID is available */
typedef struct {
unsigned char major;
#define DAV_DBVSN_MAJOR 4
/*
** V4 -- 0.9.9 ..
** Prior versions could have keys or values with invalid
** namespace prefixes as a result of the xmlns="" form not
** resetting the default namespace to be "no namespace". The
** namespace would be set to "" which is invalid; it should
** be set to "no namespace".
**
** V3 -- 0.9.8
** Prior versions could have values with invalid namespace
** prefixes due to an incorrect mapping of input to propdb
** namespace indices. Version bumped to obsolete the old
** values.
**
** V2 -- 0.9.7
** This introduced the xml:lang value into the property value's
** record in the propdb.
**
** V1 -- .. 0.9.6
** Initial version.
*/
unsigned char minor;
#define DAV_DBVSN_MINOR 0
short ns_count;
struct dav_propdb {
int version; /* *minor* version of this db */
apr_pool_t *p; /* the pool we should use */
request_rec *r; /* the request record */
int deferred; /* open of db has been deferred */
short ns_count; /* number of entries in table */
int ns_table_dirty; /* ns_table was modified */
int *ns_map; /* map elem->ns to propdb ns values */
int incomplete_map; /* some mappings do not exist */
/* if we ever run a GET subreq, it will be stored here */
/* hooks we should use for processing (based on the target resource) */
const dav_hooks_db *db_hooks;
};
/* NOTE: dav_core_props[] and the following enum must stay in sync. */
/* ### move these into a "core" liveprop provider? */
static const char * const dav_core_props[] =
{
"getcontenttype",
"getcontentlanguage",
"lockdiscovery",
"supportedlock",
NULL /* sentinel */
};
enum {
};
/*
** This structure is used to track information needed for a rollback.
** If a SET was performed and no prior value existed, then value.dptr
** will be NULL.
*/
typedef struct dav_rollback_item {
/* or use the following (choice selected by dav_prop_ctx.is_liveprop) */
#if 0
/* ### unused */
{
while (ns--)
p += strlen(p) + 1;
return p;
}
#endif
const char *ns_uri,
const char *propname,
const dav_hooks_liveprop **provider)
{
int propid;
/* policy: liveprop providers cannot define no-namespace properties */
return DAV_PROPID_CORE_UNKNOWN;
}
const char * const *p = dav_core_props;
return propid;
}
/* didn't find it. fall thru. a provider can define DAV: props */
}
/* is there a liveprop provider for this property? */
provider);
if (propid != 0) {
return propid;
}
/* no provider for this property */
return DAV_PROPID_CORE_UNKNOWN;
}
{
const char *ns_uri;
const dav_hooks_liveprop *hooks;
ns_uri = "DAV:";
else
&hooks);
/* ### this test seems redundant... */
}
}
{
/*
** Check the liveprop provider (if this is a provider-defined prop)
*/
}
/* these are defined as read-only */
#endif
) {
return 0;
}
|| propid == DAV_PROPID_CORE_UNKNOWN) {
return 1;
}
/*
** We don't recognize the property, so it must be dead (and writable)
*/
return 1;
}
/* do a sub-request to fetch properties for the target resource's URI. */
{
/* perform a "GET" on the resource's URI (note that the resource
may not correspond to the current request!). */
NULL);
}
{
/* fast-path the common case */
if (propid == DAV_PROPID_CORE_UNKNOWN)
return NULL;
switch (propid) {
"DAV:lockdiscovery could not be "
"determined due to a problem fetching "
"the locks for this resource.",
err);
}
/* fast-path the no-locks case */
value = "";
}
else {
/*
** This may modify the buffer. value may point to
** wb_lock.pbuf or a string constant.
*/
/* make a copy to isolate it from changes to wb_lock */
}
}
break;
}
break;
}
}
break;
{
const char *lang;
}
"Content-Language")) != NULL) {
}
break;
}
default:
/* fall through to interpret as a dead property */
break;
}
/* if something was supplied, then insert it */
const char *s;
if (what == DAV_PROP_INSERT_SUPPORTED) {
/* use D: prefix to refer to the DAV: namespace URI,
* and let the namespace attribute default to "DAV:"
*/
s = apr_psprintf(propdb->p,
"<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR,
name);
}
/* use D: prefix to refer to the DAV: namespace URI */
}
else {
/* use D: prefix to refer to the DAV: namespace URI */
}
}
return NULL;
}
const ap_xml_elem *elem,
{
/* this is a "core" property that we define */
}
/* ask the provider (that defined this prop) to insert the prop */
return NULL;
}
{
const char *s;
/* skip past the xml:lang value */
if (*value == '\0') {
/* the property is an empty value */
if (*name == ':') {
/* "no namespace" case */
}
else {
}
}
else if (*lang != '\0') {
if (*name == ':') {
/* "no namespace" case */
}
else {
}
}
else if (*name == ':') {
/* "no namespace" case */
}
else {
}
}
/*
** Prepare the ns_map variable in the propdb structure. This entails copying
** all URIs from the "input" namespace list (in propdb->ns_xlate) into the
** propdb's list of namespaces. As each URI is copied (or pre-existing
** URI looked up), the index mapping is stored into the ns_map variable.
**
** Note: we must copy all declared namespaces because we cannot easily
** determine which input namespaces were actually used within the property
** values that are being stored within the propdb. Theoretically, we can
** determine this at the point where we serialize the property values
** back into strings. This would require a bit more work, and will be
** left to future optimizations.
**
** ### we should always initialize the propdb namespace array with "DAV:"
** ### since we know it will be entered anyhow (by virtue of it always
** ### occurring in the ns_xlate array). That will allow us to use
** ### AP_XML_NS_DAV_ID for propdb ns values, too.
*/
{
int i;
const char **puri;
int *pmap;
int updating = 0; /* are we updating an existing ns_map? */
/* we must revisit the map and insert new entries */
updating = 1;
propdb->incomplete_map = 0;
}
else {
/* nothing to do: we have a proper ns_map */
return;
}
}
else {
}
/* ### stupid O(n * orig_count) algorithm */
i-- > 0;
if (updating) {
/* updating an existing mapping... we can skip a lot of stuff */
if (*pmap != AP_XML_NS_ERROR_NOT_FOUND) {
/* This entry has been filled in, so we can skip it */
continue;
}
}
else {
int j;
const char *p;
/*
** GIVEN: uri (a namespace URI from the request input)
**
** FIND: an equivalent URI in the propdb namespace table
*/
/* only scan original entries (we may have added some in here) */
j = 0;
j < orig_count;
++j, p += len + 1) {
continue;
*pmap = j;
goto next_input_uri;
}
}
if (!add_ns) {
/*
** This flag indicates that we have an ns_map with missing
** entries. If dav_prep_ns_map() is called with add_ns==1 AND
** this flag is set, then we zip thru the array and add those
** URIs (effectively updating the ns_map as if add_ns=1 was
** passed when the initial prep was called).
*/
continue;
}
}
/*
** The input URI was not found in the propdb namespace table, and
** we are supposed to add it. Append it to the table and store
** the index into the ns_map.
*/
;
}
}
/* find the "DAV:" namespace in our table and return its ID. */
{
int ns;
return ns;
p += len + 1;
}
/* the "DAV:" namespace is not present */
return -1;
}
{
const char *s;
ap_text_append(p, phdr, s);
}
/* return all known namespaces (in this propdb) */
{
int i;
/* note: ns_count == 0 when we have no propdb file */
}
}
/* add a namespace decl from one of the namespace tables */
const char *pre_prefix,
{
return;
phdr);
}
/*
** Internal function to build a key
**
** WARNING: returns a pointer to a "static" buffer holding the key. The
** value must be copied or no longer used if this function is
** called again.
*/
{
int ns;
char nsbuf[20];
/*
* Convert namespace ID to a string. "no namespace" is an empty string,
* so the keys will have the form ":name". Otherwise, the keys will
* have the form "#:name".
*/
nsbuf[0] = '\0';
l_ns = 0;
}
else {
/*
* Note that we prep the map and do NOT add namespaces. If that
* is required, then the caller should have called prep
* beforehand, passing the correct values.
*/
dav_prep_ns_map(propdb, 0);
}
if (AP_XML_NS_IS_ERROR(ns))
return key; /* zeroed */
}
/* assemble: #:name */
/* build the database key */
return key;
}
{
/* we're trying to open the db; turn off the 'deferred' flag */
/* ask the DB provider to open the thing */
"Could not open the property database.",
err);
}
/*
** NOTE: propdb->db could be NULL if we attempted to open a readonly
** access, then a database was created and opened.
*/
/* ### push a higher-level description? */
return err;
}
}
dav_propdb_metadata m = {
};
/*
* If there is no METADATA key, then the database may be
* from versions 0.9.0 .. 0.9.4 (which would be incompatible).
* These can be identified by the presence of an NS_TABLE entry.
*/
/* call it a major version error */
"Prop database has the wrong major "
"version number and cannot be used.");
}
}
/* initialize a new metadata structure */
}
else {
if (m.major != DAV_DBVSN_MAJOR) {
"Prop database has the wrong major "
"version number and cannot be used.");
}
}
return NULL;
}
const dav_resource *resource,
int ro,
{
#if DAV_DEBUG
"INTERNAL DESIGN ERROR: resource must define "
"its URI.");
}
#endif
propdb->r = r;
/* always defer actual open, to avoid expense of accessing db
* when only live properties are involved
*/
/* ### what to do about closing the propdb on server failure? */
return NULL;
}
{
return;
if (propdb->ns_table_dirty) {
/* fill in the metadata that we store into the prop db. */
m.major = DAV_DBVSN_MAJOR;
/* ### what to do with the error? */
}
}
{
ap_text_header hdr = { 0 };
ap_text_header hdr_ns = { 0 };
dav_get_props_result result = { 0 };
int found_contenttype = 0;
int found_contentlang = 0;
/* if not just getting supported live properties,
* scan all properties in the dead prop database
*/
if (what != DAV_PROP_INSERT_SUPPORTED) {
/* ### what to do with db open error? */
}
/* generate all the namespaces that are in the propdb */
/* initialize the result with some start tags... */
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
/* if there ARE properties, then scan them */
/* any keys with leading capital letters should be skipped
(real keys start with a number or a colon) */
goto next_key;
/*
** We also look for <DAV:getcontenttype> and
** <DAV:getcontentlanguage>. If they are not stored as dead
** properties, then we need to perform a subrequest to get
** their values (if any).
*/
if (dav_id != -1
const char *colon;
/* find the colon */
}
else {
}
found_contenttype = 1;
}
found_contentlang = 1;
}
}
}
if (what == DAV_PROP_INSERT_VALUE) {
/* ### anything better to do? */
/* ### probably should enter a 500 error */
goto next_key;
}
/* put the prop name and value into the result */
}
else {
/* simple, empty element if a value isn't needed */
}
}
}
/* add namespaces for all the liveprop providers */
}
/* ask the liveprop providers to insert their properties */
/* insert the standard properties */
/* ### should be handling the return errors here */
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_supportedlock, "supportedlock",
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
/* if we didn't find these, then do the whole subreq thing. */
if (!found_contenttype) {
/* ### should be handling the return error here */
(void)dav_insert_coreprop(propdb,
"getcontenttype",
}
if (!found_contentlang) {
/* ### should be handling the return error here */
(void)dav_insert_coreprop(propdb,
"getcontentlanguage",
}
/* if not just reporting on supported live props,
* terminate the result */
if (what != DAV_PROP_INSERT_SUPPORTED) {
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR);
}
return result;
}
{
ap_text_header hdr_good = { 0 };
ap_text_header hdr_bad = { 0 };
ap_text_header hdr_ns = { 0 };
int have_good = 0;
int propdb_xmlns_done = 0;
dav_get_props_result result = { 0 };
char *marks_input;
char *marks_liveprop;
/* ### NOTE: we should pass in TWO buffers -- one for keys, one for
the marks */
/* we will ALWAYS provide a "good" result, even if it is EMPTY */
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
/* ### the marks should be in a buffer! */
/* allocate zeroed-memory for the marks. These marks indicate which
input namespaces we've generated into the output xmlns buffer */
/* same for the liveprops */
int is_liveprop = 0;
/*
** First try live property providers; if they don't handle
** the property, then try looking it up in the propdb.
*/
}
/* cache the propid; dav_get_props() could be called many times */
is_liveprop = 1;
/* insert the property. returns 1 if an insertion was done. */
/* ### need to propagate the error to the caller... */
/* ### skip it for now, as if nothing was inserted */
}
if (inserted == DAV_PROP_INSERT_VALUE) {
have_good = 1;
/*
** Add the liveprop's namespace URIs. Note that provider==NULL
** for core properties.
*/
const char * const * scan_ns_uri;
*scan_ns_uri != NULL;
++scan_ns_uri) {
int ns;
if (marks_liveprop[ns])
continue;
&hdr_ns);
}
}
continue;
}
else if (inserted == DAV_PROP_INSERT_NOTDEF) {
/* allow property to be handled as a dead property */
is_liveprop = 0;
}
}
/*
** If not handled as a live property, look in the dead property
** database.
*/
if (!is_liveprop) {
/* make sure propdb is really open */
/* ### what to do with db open error? */
}
/* if not done yet,
* generate all the namespaces that are in the propdb
*/
if (!propdb_xmlns_done) {
propdb_xmlns_done = 1;
}
/*
** Note: the key may be NULL if we have no properties that are in
** a namespace that matches the requested prop's namespace.
*/
/* fetch IF we have a db and a key. otherwise, value is NULL */
}
}
/* not found. add a record to the "bad" propstats */
/* make sure we've started our "bad" propstat */
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
}
/* note: key.dptr may be NULL if the propdb doesn't have an
equivalent namespace stored */
const char *s;
/*
* elem has a prefix already (xml...:name) or the elem
* simply has no namespace.
*/
}
else {
/* ensure that an xmlns is generated for the
input namespace */
}
}
else {
/* add in the bad prop using our namespace decl */
}
}
else {
/* found it. put the value into the "good" propstats */
have_good = 1;
}
}
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR);
/* default to start with the good */
/* we may not have any "bad" results */
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR);
/* if there are no good props, then just return the bad */
if (!have_good) {
}
else {
/* hook the bad propstat to the end of the good one */
}
}
return result;
}
const char *ns_uri,
const char *propname,
{
int propid;
const dav_hooks_liveprop *hooks;
if (propid != DAV_PROPID_CORE_UNKNOWN) {
/* this is a "core" property that we define */
}
else {
}
}
}
{
/*
** Check to see if this is a live property, and fill the fields
** in the XML elem, as appropriate.
**
** be SET or DELETEd.
*/
/* it's a liveprop if a provider was found */
/* ### actually the "core" props should really be liveprops, but
### there is no "provider" for those and the r/w props are
### treated as dead props anyhow */
}
"Property is read-only.");
return;
}
if (ctx->is_liveprop) {
int defer_to_dead = 0;
&ctx->liveprop_ctx,
return;
/* clear is_liveprop -- act as a dead prop now */
ctx->is_liveprop = 0;
}
/*
** The property is supposed to be stored into the dead-property
** database. Make sure the thing is truly open (and writable).
*/
return;
}
/*
** There should be an open, writable database in here!
**
** Note: the database would be NULL if it was opened readonly and it
** did not exist.
*/
"property database.");
return;
}
/*
** Prep the element => propdb namespace index mapping, inserting
** namespace URIs into the propdb that don't exist.
*/
}
/*
** There are no checks to perform here. If a property exists, then
** we will delete it. If it does not exist, then it does not matter
** (see S12.13.1).
**
** Note that if a property does not exist, that does not rule out
** that a SET will occur during this PROPPATCH (thusly creating it).
*/
}
}
{
if (ctx->is_liveprop) {
}
else {
/* we're going to need the key for all operations */
/* save the old value so that we can do a rollback. */
goto error;
/* Note: propdb->ns_map was set in dav_prop_validate() */
/* quote all the values in the element */
/* generate a text blob for the xml:lang plus the contents */
/*
** If an error occurred, then assume that we didn't change the
** value. Remove the rollback item so that we don't try to set
** its value during the rollback.
*/
}
/*
** Delete the property. Ignore errors -- the property is there, or
** we are deleting it for a second time.
*/
/* ### but what about other errors? */
}
}
/* push a more specific error here */
/*
** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
** any errors at this point.
*/
"Could not execute PROPPATCH.", err);
}
}
{
/*
** Note that a commit implies ctx->err is NULL. The caller should assume
** a status of HTTP_OK for this case.
*/
if (ctx->is_liveprop) {
}
}
{
/* do nothing if there is no rollback information. */
return;
/*
** ### if we have an error, and a rollback occurs, then the namespace
** ### mods should not happen at all. Basically, the namespace management
** ### is simply a bitch.
*/
if (ctx->is_liveprop) {
}
/* don't fail if the thing isn't really there */
/* ### but what about other errors? */
}
else {
}
else {
/* hook previous errors at the end of the rollback error */
}
}
}