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

repos.c

Go to the documentation of this file.
00001 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
00002  * applicable.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 /*
00018 ** DAV filesystem-based repository provider
00019 */
00020 
00021 #include "apr.h"
00022 #include "apr_file_io.h"
00023 #include "apr_strings.h"
00024 #include "apr_buckets.h"
00025 
00026 #if APR_HAVE_STDIO_H
00027 #include <stdio.h>              /* for sprintf() */
00028 #endif
00029 
00030 #include "httpd.h"
00031 #include "http_log.h"
00032 #include "http_protocol.h"      /* for ap_set_* (in dav_fs_set_headers) */
00033 #include "http_request.h"       /* for ap_update_mtime() */
00034 
00035 #include "mod_dav.h"
00036 #include "repos.h"
00037 
00038 
00039 /* to assist in debugging mod_dav's GET handling */
00040 #define DEBUG_GET_HANDLER       0
00041 
00042 #define DAV_FS_COPY_BLOCKSIZE   16384   /* copy 16k at a time */
00043 
00044 /* context needed to identify a resource */
00045 struct dav_resource_private {
00046     apr_pool_t *pool;        /* memory storage pool associated with request */
00047     const char *pathname;   /* full pathname to resource */
00048     apr_finfo_t finfo;       /* filesystem info */
00049 };
00050 
00051 /* private context for doing a filesystem walk */
00052 typedef struct {
00053     /* the input walk parameters */
00054     const dav_walk_params *params;
00055 
00056     /* reused as we walk */
00057     dav_walk_resource wres;
00058 
00059     dav_resource res1;
00060     dav_resource_private info1;
00061     dav_buffer path1;
00062     dav_buffer uri_buf;
00063 
00064     /* MOVE/COPY need a secondary path */
00065     dav_resource res2;
00066     dav_resource_private info2;
00067     dav_buffer path2;
00068 
00069     dav_buffer locknull_buf;
00070 
00071 } dav_fs_walker_context;
00072 
00073 typedef struct {
00074     int is_move;                /* is this a MOVE? */
00075     dav_buffer work_buf;        /* handy buffer for copymove_file() */
00076 
00077     /* CALLBACK: this is a secondary resource managed specially for us */
00078     const dav_resource *res_dst;
00079 
00080     /* copied from dav_walk_params (they are invariant across the walk) */
00081     const dav_resource *root;
00082     apr_pool_t *pool;
00083 
00084 } dav_fs_copymove_walk_ctx;
00085 
00086 /* an internal WALKTYPE to walk hidden files (the .DAV directory) */
00087 #define DAV_WALKTYPE_HIDDEN     0x4000
00088 
00089 /* an internal WALKTYPE to call collections (again) after their contents */
00090 #define DAV_WALKTYPE_POSTFIX    0x8000
00091 
00092 #define DAV_CALLTYPE_POSTFIX    1000    /* a private call type */
00093 
00094 
00095 /* pull this in from the other source file */
00096 extern const dav_hooks_locks dav_hooks_locks_fs;
00097 
00098 /* forward-declare the hook structures */
00099 static const dav_hooks_repository dav_hooks_repository_fs;
00100 static const dav_hooks_liveprop dav_hooks_liveprop_fs;
00101 
00102 /*
00103 ** The namespace URIs that we use. This list and the enumeration must
00104 ** stay in sync.
00105 */
00106 static const char * const dav_fs_namespace_uris[] =
00107 {
00108     "DAV:",
00109     "http://apache.org/dav/props/",
00110 
00111     NULL        /* sentinel */
00112 };
00113 enum {
00114     DAV_FS_URI_DAV,            /* the DAV: namespace URI */
00115     DAV_FS_URI_MYPROPS         /* the namespace URI for our custom props */
00116 };
00117 
00118 /*
00119 ** Does this platform support an executable flag?
00120 **
00121 ** ### need a way to portably abstract this query
00122 */
00123 #ifndef WIN32
00124 #define DAV_FS_HAS_EXECUTABLE
00125 #endif
00126 
00127 /*
00128 ** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
00129 */
00130 #define DAV_PROPID_FS_executable        1
00131 
00132 static const dav_liveprop_spec dav_fs_props[] =
00133 {
00134     /* standard DAV properties */
00135     {
00136         DAV_FS_URI_DAV,
00137         "creationdate",
00138         DAV_PROPID_creationdate,
00139         0
00140     },
00141     {
00142         DAV_FS_URI_DAV,
00143         "getcontentlength",
00144         DAV_PROPID_getcontentlength,
00145         0
00146     },
00147     {
00148         DAV_FS_URI_DAV,
00149         "getetag",
00150         DAV_PROPID_getetag,
00151         0
00152     },
00153     {
00154         DAV_FS_URI_DAV,
00155         "getlastmodified",
00156         DAV_PROPID_getlastmodified,
00157         0
00158     },
00159 
00160     /* our custom properties */
00161     {
00162         DAV_FS_URI_MYPROPS,
00163         "executable",
00164         DAV_PROPID_FS_executable,
00165         0       /* handled special in dav_fs_is_writable */
00166     },
00167 
00168     { 0 }        /* sentinel */
00169 };
00170 
00171 static const dav_liveprop_group dav_fs_liveprop_group =
00172 {
00173     dav_fs_props,
00174     dav_fs_namespace_uris,
00175     &dav_hooks_liveprop_fs
00176 };
00177 
00178 
00179 /* define the dav_stream structure for our use */
00180 struct dav_stream {
00181     apr_pool_t *p;
00182     apr_file_t *f;
00183     const char *pathname;       /* we may need to remove it at close time */
00184 };
00185 
00186 /* returns an appropriate HTTP status code given an APR status code for a 
00187  * failed I/O operation.  ### use something besides 500? */
00188 #define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
00189                         HTTP_INTERNAL_SERVER_ERROR)
00190 
00191 /* forward declaration for internal treewalkers */
00192 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
00193                                dav_response **response);
00194 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
00195                                         int depth, int is_move,
00196                                         const dav_resource *root_dst,
00197                                         dav_response **response);
00198 
00199 /* --------------------------------------------------------------------
00200 **
00201 ** PRIVATE REPOSITORY FUNCTIONS
00202 */
00203 apr_pool_t *dav_fs_pool(const dav_resource *resource)
00204 {
00205     return resource->info->pool;
00206 }
00207 
00208 const char *dav_fs_pathname(const dav_resource *resource)
00209 {
00210     return resource->info->pathname;
00211 }
00212 
00213 dav_error * dav_fs_dir_file_name(
00214     const dav_resource *resource,
00215     const char **dirpath_p,
00216     const char **fname_p)
00217 {
00218     dav_resource_private *ctx = resource->info;
00219 
00220     if (resource->collection) {
00221         *dirpath_p = ctx->pathname;
00222         if (fname_p != NULL)
00223             *fname_p = NULL;
00224     }
00225     else {
00226         const char *testpath, *rootpath;
00227         char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
00228         apr_size_t dirlen = strlen(dirpath);
00229         apr_status_t rv = APR_SUCCESS;
00230 
00231         testpath = dirpath;
00232         if (dirlen > 0) {
00233             rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
00234         }
00235         
00236         /* remove trailing slash from dirpath, unless it's a root path
00237          */
00238         if ((rv == APR_SUCCESS && testpath && *testpath)
00239             || rv == APR_ERELATIVE) {
00240             if (dirpath[dirlen - 1] == '/') {
00241                 dirpath[dirlen - 1] = '\0';
00242             }
00243         }
00244         
00245         /* ###: Looks like a response could be appropriate
00246          *
00247          * APR_SUCCESS     here tells us the dir is a root
00248          * APR_ERELATIVE   told us we had no root (ok)
00249          * APR_EINCOMPLETE an incomplete testpath told us
00250          *                 there was no -file- name here!
00251          * APR_EBADPATH    or other errors tell us this file
00252          *                 path is undecipherable
00253          */
00254 
00255         if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
00256             *dirpath_p = dirpath;
00257             if (fname_p != NULL)
00258                 *fname_p = ctx->pathname + dirlen;
00259         }
00260         else {
00261             return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
00262                                  "An incomplete/bad path was found in "
00263                                  "dav_fs_dir_file_name.");
00264         }
00265     }
00266 
00267     return NULL;
00268 }
00269 
00270 /* Note: picked up from ap_gm_timestr_822() */
00271 /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
00272 static void dav_format_time(int style, apr_time_t sec, char *buf)
00273 {
00274     apr_time_exp_t tms;
00275     
00276     /* ### what to do if fails? */
00277     (void) apr_time_exp_gmt(&tms, sec);
00278 
00279     if (style == DAV_STYLE_ISO8601) {
00280         /* ### should we use "-00:00" instead of "Z" ?? */
00281 
00282         /* 20 chars plus null term */
00283         sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
00284                tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
00285                tms.tm_hour, tms.tm_min, tms.tm_sec);
00286         return;
00287     }
00288 
00289     /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
00290 
00291     /* 29 chars plus null term */
00292     sprintf(buf,
00293             "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
00294            apr_day_snames[tms.tm_wday],
00295            tms.tm_mday, apr_month_snames[tms.tm_mon],
00296            tms.tm_year + 1900,
00297            tms.tm_hour, tms.tm_min, tms.tm_sec);
00298 }
00299 
00300 static dav_error * dav_fs_copymove_file(
00301     int is_move,
00302     apr_pool_t * p,
00303     const char *src,
00304     const char *dst,
00305     dav_buffer *pbuf)
00306 {
00307     dav_buffer work_buf = { 0 };
00308     apr_file_t *inf = NULL;
00309     apr_file_t *outf = NULL;
00310     apr_status_t status;
00311 
00312     if (pbuf == NULL)
00313         pbuf = &work_buf;
00314 
00315     dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
00316 
00317     if ((apr_file_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p)) 
00318             != APR_SUCCESS) {
00319         /* ### use something besides 500? */
00320         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00321                              "Could not open file for reading");
00322     }
00323 
00324     /* ### do we need to deal with the umask? */
00325     status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE 
00326                            | APR_BINARY, APR_OS_DEFAULT, p);
00327     if (status != APR_SUCCESS) {
00328         apr_file_close(inf);
00329 
00330         return dav_new_error(p, MAP_IO2HTTP(status), 0,
00331                              "Could not open file for writing");
00332     }
00333 
00334     while (1) {
00335         apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
00336 
00337         status = apr_file_read(inf, pbuf->buf, &len);
00338         if (status != APR_SUCCESS && status != APR_EOF) {
00339             apr_file_close(inf);
00340             apr_file_close(outf);
00341             
00342             if (apr_file_remove(dst, p) != APR_SUCCESS) {
00343                 /* ### ACK! Inconsistent state... */
00344 
00345                 /* ### use something besides 500? */
00346                 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00347                                      "Could not delete output after read "
00348                                      "failure. Server is now in an "
00349                                      "inconsistent state.");
00350             }
00351 
00352             /* ### use something besides 500? */
00353             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00354                                  "Could not read input file");
00355         }
00356 
00357         if (status == APR_EOF)
00358             break;
00359 
00360         /* write any bytes that were read */
00361         status = apr_file_write_full(outf, pbuf->buf, len, NULL);
00362         if (status != APR_SUCCESS) {
00363             apr_file_close(inf);
00364             apr_file_close(outf);
00365 
00366             if (apr_file_remove(dst, p) != APR_SUCCESS) {
00367                 /* ### ACK! Inconsistent state... */
00368 
00369                 /* ### use something besides 500? */
00370                 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00371                                      "Could not delete output after write "
00372                                      "failure. Server is now in an "
00373                                      "inconsistent state.");
00374             }
00375 
00376             return dav_new_error(p, MAP_IO2HTTP(status), 0,
00377                                  "Could not write output file");
00378         }
00379     }
00380 
00381     apr_file_close(inf);
00382     apr_file_close(outf);
00383 
00384     if (is_move && apr_file_remove(src, p) != APR_SUCCESS) {
00385         dav_error *err;
00386         int save_errno = errno;   /* save the errno that got us here */
00387 
00388         if (apr_file_remove(dst, p) != APR_SUCCESS) {
00389             /* ### ACK. this creates an inconsistency. do more!? */
00390 
00391             /* ### use something besides 500? */
00392             /* Note that we use the latest errno */
00393             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00394                                  "Could not remove source or destination "
00395                                  "file. Server is now in an inconsistent "
00396                                  "state.");
00397         }
00398 
00399         /* ### use something besides 500? */
00400         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00401                             "Could not remove source file after move. "
00402                             "Destination was removed to ensure consistency.");
00403         err->save_errno = save_errno;
00404         return err;
00405     }
00406 
00407     return NULL;
00408 }
00409 
00410 /* copy/move a file from within a state dir to another state dir */
00411 /* ### need more buffers to replace the pool argument */
00412 static dav_error * dav_fs_copymove_state(
00413     int is_move,
00414     apr_pool_t * p,
00415     const char *src_dir, const char *src_file,
00416     const char *dst_dir, const char *dst_file,
00417     dav_buffer *pbuf)
00418 {
00419     apr_finfo_t src_finfo;        /* finfo for source file */
00420     apr_finfo_t dst_state_finfo;        /* finfo for STATE directory */
00421     apr_status_t rv;
00422     const char *src;
00423     const char *dst;
00424 
00425     /* build the propset pathname for the source file */
00426     src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
00427 
00428     /* the source file doesn't exist */
00429     rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
00430     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
00431         return NULL;
00432     }
00433 
00434     /* build the pathname for the destination state dir */
00435     dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
00436 
00437     /* ### do we need to deal with the umask? */
00438 
00439     /* ensure that it exists */
00440     rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
00441     if (rv != APR_SUCCESS) {
00442         if (!APR_STATUS_IS_EEXIST(rv)) {
00443             /* ### use something besides 500? */
00444             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00445                                  "Could not create internal state directory");
00446         }
00447     }
00448 
00449     /* get info about the state directory */
00450     rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
00451     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
00452         /* Ack! Where'd it go? */
00453         /* ### use something besides 500? */
00454         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00455                              "State directory disappeared");
00456     }
00457 
00458     /* The mkdir() may have failed because a *file* exists there already */
00459     if (dst_state_finfo.filetype != APR_DIR) {
00460         /* ### try to recover by deleting this file? (and mkdir again) */
00461         /* ### use something besides 500? */
00462         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00463                              "State directory is actually a file");
00464     }
00465 
00466     /* append the target file to the state directory pathname */
00467     dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
00468 
00469     /* copy/move the file now */
00470     if (is_move && src_finfo.device == dst_state_finfo.device) {
00471         /* simple rename is possible since it is on the same device */
00472         if (apr_file_rename(src, dst, p) != APR_SUCCESS) {
00473             /* ### use something besides 500? */
00474             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00475                                  "Could not move state file.");
00476         }
00477     }
00478     else
00479     {
00480         /* gotta copy (and delete) */
00481         return dav_fs_copymove_file(is_move, p, src, dst, pbuf);
00482     }
00483 
00484     return NULL;
00485 }
00486 
00487 static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
00488                                      const dav_resource *src,
00489                                      const dav_resource *dst,
00490                                      dav_buffer *pbuf)
00491 {
00492     const char *src_dir;
00493     const char *src_file;
00494     const char *src_state1;
00495     const char *src_state2;
00496     const char *dst_dir;
00497     const char *dst_file;
00498     const char *dst_state1;
00499     const char *dst_state2;
00500     dav_error *err;
00501 
00502     /* Get directory and filename for resources */
00503     /* ### should test these result values... */
00504     (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
00505     (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
00506 
00507     /* Get the corresponding state files for each resource */
00508     dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
00509     dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
00510 #if DAV_DEBUG
00511     if ((src_state2 != NULL && dst_state2 == NULL) ||
00512         (src_state2 == NULL && dst_state2 != NULL)) {
00513         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00514                              "DESIGN ERROR: dav_dbm_get_statefiles() "
00515                              "returned inconsistent results.");
00516     }
00517 #endif
00518 
00519     err = dav_fs_copymove_state(is_move, p,
00520                                 src_dir, src_state1,
00521                                 dst_dir, dst_state1,
00522                                 pbuf);
00523 
00524     if (err == NULL && src_state2 != NULL) {
00525         err = dav_fs_copymove_state(is_move, p,
00526                                     src_dir, src_state2,
00527                                     dst_dir, dst_state2,
00528                                     pbuf);
00529 
00530         if (err != NULL) {
00531             /* ### CRAP. inconsistency. */
00532             /* ### should perform some cleanup at the target if we still
00533                ### have the original files */
00534 
00535             /* Change the error to reflect the bad server state. */
00536             err->status = HTTP_INTERNAL_SERVER_ERROR;
00537             err->desc =
00538                 "Could not fully copy/move the properties. "
00539                 "The server is now in an inconsistent state.";
00540         }
00541     }
00542 
00543     return err;
00544 }
00545 
00546 static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
00547 {
00548     const char *dirpath;
00549     const char *fname;
00550     const char *state1;
00551     const char *state2;
00552     const char *pathname;
00553     apr_status_t status;
00554 
00555     /* Get directory, filename, and state-file names for the resource */
00556     /* ### should test this result value... */
00557     (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
00558     dav_dbm_get_statefiles(p, fname, &state1, &state2);
00559 
00560     /* build the propset pathname for the file */
00561     pathname = apr_pstrcat(p,
00562                           dirpath,
00563                           "/" DAV_FS_STATE_DIR "/",
00564                           state1,
00565                           NULL);
00566 
00567     /* note: we may get ENOENT if the state dir is not present */
00568     if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
00569         && !APR_STATUS_IS_ENOENT(status)) {
00570         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00571                              "Could not remove properties.");
00572     }
00573 
00574     if (state2 != NULL) {
00575         /* build the propset pathname for the file */
00576         pathname = apr_pstrcat(p,
00577                               dirpath,
00578                               "/" DAV_FS_STATE_DIR "/",
00579                               state2,
00580                               NULL);
00581 
00582         if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
00583             && !APR_STATUS_IS_ENOENT(status)) {
00584             /* ### CRAP. only removed half. */
00585             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
00586                                  "Could not fully remove properties. "
00587                                  "The server is now in an inconsistent "
00588                                  "state.");
00589         }
00590     }
00591 
00592     return NULL;
00593 }
00594 
00595 /* --------------------------------------------------------------------
00596 **
00597 ** REPOSITORY HOOK FUNCTIONS
00598 */
00599 
00600 static dav_error * dav_fs_get_resource(
00601     request_rec *r,
00602     const char *root_dir,
00603     const char *label,
00604     int use_checked_in,
00605     dav_resource **result_resource)
00606 {
00607     dav_resource_private *ctx;
00608     dav_resource *resource;
00609     char *s;
00610     char *filename;
00611     apr_size_t len;
00612 
00613     /* ### optimize this into a single allocation! */
00614 
00615     /* Create private resource context descriptor */
00616     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
00617     ctx->finfo = r->finfo;
00618 
00619     /* ### this should go away */
00620     ctx->pool = r->pool;
00621 
00622     /* Preserve case on OSes which fold canonical filenames */
00623 #if 0
00624     /* ### not available in Apache 2.0 yet */
00625     filename = r->case_preserved_filename;
00626 #else
00627     filename = r->filename;
00628 #endif
00629 
00630     /*
00631     ** If there is anything in the path_info, then this indicates that the
00632     ** entire path was not used to specify the file/dir. We want to append
00633     ** it onto the filename so that we get a "valid" pathname for null
00634     ** resources.
00635     */
00636     s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
00637 
00638     /* make sure the pathname does not have a trailing "/" */
00639     len = strlen(s);
00640     if (len > 1 && s[len - 1] == '/') {
00641         s[len - 1] = '\0';
00642     }
00643     ctx->pathname = s;
00644 
00645     /* Create resource descriptor */
00646     resource = apr_pcalloc(r->pool, sizeof(*resource));
00647     resource->type = DAV_RESOURCE_TYPE_REGULAR;
00648     resource->info = ctx;
00649     resource->hooks = &dav_hooks_repository_fs;
00650     resource->pool = r->pool;
00651 
00652     /* make sure the URI does not have a trailing "/" */
00653     len = strlen(r->uri);
00654     if (len > 1 && r->uri[len - 1] == '/') {
00655         s = apr_pstrdup(r->pool, r->uri);
00656         s[len - 1] = '\0';
00657         resource->uri = s;
00658     }
00659     else {
00660         resource->uri = r->uri;
00661     }
00662 
00663     if (r->finfo.filetype != 0) {
00664         resource->exists = 1;
00665         resource->collection = r->finfo.filetype == APR_DIR;
00666 
00667         /* unused info in the URL will indicate a null resource */
00668 
00669         if (r->path_info != NULL && *r->path_info != '\0') {
00670             if (resource->collection) {
00671                 /* only a trailing "/" is allowed */
00672                 if (*r->path_info != '/' || r->path_info[1] != '\0') {
00673 
00674                     /*
00675                     ** This URL/filename represents a locknull resource or
00676                     ** possibly a destination of a MOVE/COPY
00677                     */
00678                     resource->exists = 0;
00679                     resource->collection = 0;
00680                 }
00681             }
00682             else
00683             {
00684                 /*
00685                 ** The base of the path refers to a file -- nothing should
00686                 ** be in path_info. The resource is simply an error: it
00687                 ** can't be a null or a locknull resource.
00688                 */
00689                 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
00690                                      "The URL contains extraneous path "
00691                                      "components. The resource could not "
00692                                      "be identified.");
00693             }
00694 
00695             /* retain proper integrity across the structures */
00696             if (!resource->exists) {
00697                 ctx->finfo.filetype = 0;
00698             }
00699         }
00700     }
00701 
00702     *result_resource = resource;
00703     return NULL;
00704 }
00705 
00706 static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
00707                                               dav_resource **result_parent)
00708 {
00709     dav_resource_private *ctx = resource->info;
00710     dav_resource_private *parent_ctx;
00711     dav_resource *parent_resource;
00712     apr_status_t rv;
00713     char *dirpath;
00714     const char *testroot;
00715     const char *testpath;
00716 
00717     /* If we're at the root of the URL space, then there is no parent. */
00718     if (strcmp(resource->uri, "/") == 0) {
00719         *result_parent = NULL;
00720         return NULL;
00721     }
00722 
00723     /* If given resource is root, then there is no parent.
00724      * Unless we can retrieve the filepath root, this is
00725      * intendend to fail.  If we split the root and
00726      * no path info remains, then we also fail.
00727      */
00728     testpath = ctx->pathname;
00729     rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
00730     if ((rv != APR_SUCCESS && rv != APR_ERELATIVE) 
00731         || !testpath || !*testpath) {
00732         *result_parent = NULL;
00733         return NULL;
00734     }
00735 
00736     /* ### optimize this into a single allocation! */
00737 
00738     /* Create private resource context descriptor */
00739     parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
00740 
00741     /* ### this should go away */
00742     parent_ctx->pool = ctx->pool;
00743 
00744     dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
00745     if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/') 
00746         dirpath[strlen(dirpath) - 1] = '\0';
00747     parent_ctx->pathname = dirpath;
00748 
00749     parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
00750     parent_resource->info = parent_ctx;
00751     parent_resource->collection = 1;
00752     parent_resource->hooks = &dav_hooks_repository_fs;
00753     parent_resource->pool = resource->pool;
00754 
00755     if (resource->uri != NULL) {
00756         char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
00757         if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
00758             uri[strlen(uri) - 1] = '\0';
00759         parent_resource->uri = uri;
00760     }
00761 
00762     rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname, 
00763                   APR_FINFO_NORM, ctx->pool);
00764     if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
00765         parent_resource->exists = 1;
00766     }
00767 
00768     *result_parent = parent_resource;
00769     return NULL;
00770 }
00771 
00772 static int dav_fs_is_same_resource(
00773     const dav_resource *res1,
00774     const dav_resource *res2)
00775 {
00776     dav_resource_private *ctx1 = res1->info;
00777     dav_resource_private *ctx2 = res2->info;
00778 
00779     if (res1->hooks != res2->hooks)
00780         return 0;
00781 
00782     if ((ctx1->finfo.filetype != 0) && (ctx2->finfo.filetype != 0)
00783         && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
00784         return ctx1->finfo.inode == ctx2->finfo.inode;
00785     }
00786     else {
00787         return strcmp(ctx1->pathname, ctx2->pathname) == 0;
00788     }
00789 }
00790 
00791 static int dav_fs_is_parent_resource(
00792     const dav_resource *res1,
00793     const dav_resource *res2)
00794 {
00795     dav_resource_private *ctx1 = res1->info;
00796     dav_resource_private *ctx2 = res2->info;
00797     apr_size_t len1 = strlen(ctx1->pathname);
00798     apr_size_t len2;
00799 
00800     if (res1->hooks != res2->hooks)
00801         return 0;
00802 
00803     /* it is safe to use ctx2 now */
00804     len2 = strlen(ctx2->pathname);
00805 
00806     return (len2 > len1
00807             && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
00808             && ctx2->pathname[len1] == '/');
00809 }
00810 
00811 static dav_error * dav_fs_open_stream(const dav_resource *resource,
00812                                       dav_stream_mode mode,
00813                                       dav_stream **stream)
00814 {
00815     apr_pool_t *p = resource->info->pool;
00816     dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
00817     apr_int32_t flags;
00818     apr_status_t rv;
00819 
00820     switch (mode) {
00821     default:
00822         flags = APR_READ | APR_BINARY;
00823         break;
00824 
00825     case DAV_MODE_WRITE_TRUNC:
00826         flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
00827         break;
00828     case DAV_MODE_WRITE_SEEKABLE:
00829         flags = APR_WRITE | APR_CREATE | APR_BINARY;
00830         break;
00831     }
00832 
00833     ds->p = p;
00834     ds->pathname = resource->info->pathname;
00835     rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
00836     if (rv != APR_SUCCESS) {
00837         return dav_new_error(p, MAP_IO2HTTP(rv), 0,
00838                              "An error occurred while opening a resource.");
00839     }
00840 
00841     /* (APR registers cleanups for the fd with the pool) */
00842 
00843     *stream = ds;
00844     return NULL;
00845 }
00846 
00847 static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
00848 {
00849     apr_file_close(stream->f);
00850 
00851     if (!commit) {
00852         if (apr_file_remove(stream->pathname, stream->p) != APR_SUCCESS) {
00853             /* ### use a better description? */
00854             return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
00855                                  "There was a problem removing (rolling "
00856                                  "back) the resource "
00857                                  "when it was being closed.");
00858         }
00859     }
00860 
00861     return NULL;
00862 }
00863 
00864 static dav_error * dav_fs_write_stream(dav_stream *stream,
00865                                        const void *buf, apr_size_t bufsize)
00866 {
00867     apr_status_t status;
00868 
00869     status = apr_file_write_full(stream->f, buf, bufsize, NULL);
00870     if (APR_STATUS_IS_ENOSPC(status)) {
00871         return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
00872                              "There is not enough storage to write to "
00873                              "this resource.");
00874     }
00875     else if (status != APR_SUCCESS) {
00876         /* ### use something besides 500? */
00877         return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
00878                              "An error occurred while writing to a "
00879                              "resource.");
00880     }
00881     return NULL;
00882 }
00883 
00884 static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
00885 {
00886     if (apr_file_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
00887         /* ### should check whether apr_file_seek set abs_pos was set to the
00888          * correct position? */
00889         /* ### use something besides 500? */
00890         return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
00891                              "Could not seek to specified position in the "
00892                              "resource.");
00893     }
00894     return NULL;
00895 }
00896 
00897 
00898 #if DEBUG_GET_HANDLER
00899 
00900 /* only define set_headers() and deliver() for debug purposes */
00901 
00902 
00903 static dav_error * dav_fs_set_headers(request_rec *r,
00904                                       const dav_resource *resource)
00905 {
00906     /* ### this function isn't really used since we have a get_pathname */
00907     if (!resource->exists)
00908         return NULL;
00909 
00910     /* make sure the proper mtime is in the request record */
00911     ap_update_mtime(r, resource->info->finfo.mtime);
00912 
00913     /* ### note that these use r->filename rather than <resource> */
00914     ap_set_last_modified(r);
00915     ap_set_etag(r);
00916 
00917     /* we accept byte-ranges */
00918     apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
00919 
00920     /* set up the Content-Length header */
00921     ap_set_content_length(r, resource->info->finfo.size);
00922 
00923     /* ### how to set the content type? */
00924     /* ### until this is resolved, the Content-Type header is busted */
00925 
00926     return NULL;
00927 }
00928 
00929 static dav_error * dav_fs_deliver(const dav_resource *resource,
00930                                   ap_filter_t *output)
00931 {
00932     apr_pool_t *pool = resource->pool;
00933     apr_bucket_brigade *bb;
00934     apr_file_t *fd;
00935     apr_status_t status;
00936     apr_bucket *bkt;
00937 
00938     /* Check resource type */
00939     if (resource->type != DAV_RESOURCE_TYPE_REGULAR
00940         && resource->type != DAV_RESOURCE_TYPE_VERSION
00941         && resource->type != DAV_RESOURCE_TYPE_WORKING) {
00942         return dav_new_error(pool, HTTP_CONFLICT, 0,
00943                              "Cannot GET this type of resource.");
00944     }
00945     if (resource->collection) {
00946         return dav_new_error(pool, HTTP_CONFLICT, 0,
00947                              "There is no default response to GET for a "
00948                              "collection.");
00949     }
00950 
00951     if ((status = apr_file_open(&fd, resource->info->pathname,
00952                                 APR_READ | APR_BINARY, 0,
00953                                 pool)) != APR_SUCCESS) {
00954         return dav_new_error(pool, HTTP_FORBIDDEN, 0,
00955                              "File permissions deny server access.");
00956     }
00957 
00958     bb = apr_brigade_create(pool, output->c->bucket_alloc);
00959 
00960     /* ### this does not handle large files. but this is test code anyway */
00961     bkt = apr_bucket_file_create(fd, 0,
00962                                  (apr_size_t)resource->info->finfo.size,
00963                                  pool, output->c->bucket_alloc);
00964     APR_BRIGADE_INSERT_TAIL(bb, bkt);
00965 
00966     bkt = apr_bucket_eos_create(output->c->bucket_alloc);
00967     APR_BRIGADE_INSERT_TAIL(bb, bkt);
00968 
00969     if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
00970         return dav_new_error(pool, HTTP_FORBIDDEN, 0,
00971                              "Could not write contents to filter.");
00972     }
00973 
00974     return NULL;
00975 }
00976 
00977 #endif /* DEBUG_GET_HANDLER */
00978 
00979 
00980 static dav_error * dav_fs_create_collection(dav_resource *resource)
00981 {
00982     dav_resource_private *ctx = resource->info;
00983     apr_status_t status;
00984 
00985     status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
00986     if (APR_STATUS_IS_ENOSPC(status)) {
00987         return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
00988                              "There is not enough storage to create "
00989                              "this collection.");
00990     }
00991     else if (APR_STATUS_IS_ENOENT(status)) {
00992         return dav_new_error(ctx->pool, HTTP_CONFLICT, 0,
00993                              "Cannot create collection; intermediate "
00994                              "collection does not exist.");
00995     }
00996     else if (status != APR_SUCCESS) {
00997         /* ### refine this error message? */
00998         return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
00999                              "Unable to create collection.");
01000     }
01001 
01002     /* update resource state to show it exists as a collection */
01003     resource->exists = 1;
01004     resource->collection = 1;
01005 
01006     return NULL;
01007 }
01008 
01009 static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
01010                                           int calltype)
01011 {
01012     dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
01013     dav_resource_private *srcinfo = wres->resource->info;
01014     dav_resource_private *dstinfo = ctx->res_dst->info;
01015     dav_error *err = NULL;
01016 
01017     if (wres->resource->collection) {
01018         if (calltype == DAV_CALLTYPE_POSTFIX) {
01019             /* Postfix call for MOVE. delete the source dir.
01020              * Note: when copying, we do not enable the postfix-traversal.
01021              */
01022             /* ### we are ignoring any error here; what should we do? */
01023             (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
01024         }
01025         else {
01026             /* copy/move of a collection. Create the new, target collection */
01027             if (apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
01028                              ctx->pool) != APR_SUCCESS) {
01029                 /* ### assume it was a permissions problem */
01030                 /* ### need a description here */
01031                 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
01032             }
01033         }
01034     }
01035     else {
01036         err = dav_fs_copymove_file(ctx->is_move, ctx->pool, 
01037                                    srcinfo->pathname, dstinfo->pathname, 
01038                                    &ctx->work_buf);
01039         /* ### push a higher-level description? */
01040     }
01041 
01042     /*
01043     ** If we have a "not so bad" error, then it might need to go into a
01044     ** multistatus response.
01045     **
01046     ** For a MOVE, it will always go into the multistatus. It could be
01047     ** that everything has been moved *except* for the root. Using a
01048     ** multistatus (with no errors for the other resources) will signify
01049     ** this condition.
01050     **
01051     ** For a COPY, we are traversing in a prefix fashion. If the root fails,
01052     ** then we can just bail out now.
01053     */
01054     if (err != NULL
01055         && !ap_is_HTTP_SERVER_ERROR(err->status)
01056         && (ctx->is_move
01057             || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
01058         /* ### use errno to generate DAV:responsedescription? */
01059         dav_add_response(wres, err->status, NULL);
01060 
01061         /* the error is in the multistatus now. do not stop the traversal. */
01062         return NULL;
01063     }
01064 
01065     return err;
01066 }
01067 
01068 static dav_error *dav_fs_copymove_resource(
01069     int is_move,
01070     const dav_resource *src,
01071     const dav_resource *dst,
01072     int depth,
01073     dav_response **response)
01074 {
01075     dav_error *err = NULL;
01076     dav_buffer work_buf = { 0 };
01077 
01078     *response = NULL;
01079 
01080     /* if a collection, recursively copy/move it and its children,
01081      * including the state dirs
01082      */
01083     if (src->collection) {
01084         dav_walk_params params = { 0 };
01085         dav_response *multi_status;
01086 
01087         params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
01088         params.func = dav_fs_copymove_walker;
01089         params.pool = src->info->pool;
01090         params.root = src;
01091 
01092         /* params.walk_ctx is managed by dav_fs_internal_walk() */
01093 
01094         /* postfix is needed for MOVE to delete source dirs */
01095         if (is_move)
01096             params.walk_type |= DAV_WALKTYPE_POSTFIX;
01097 
01098         /* note that we return the error OR the multistatus. never both */
01099 
01100         if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
01101                                         &multi_status)) != NULL) {
01102             /* on a "real" error, then just punt. nothing else to do. */
01103             return err;
01104         }
01105 
01106         if ((*response = multi_status) != NULL) {
01107             /* some multistatus responses exist. wrap them in a 207 */
01108             return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
01109                                  "Error(s) occurred on some resources during "
01110                                  "the COPY/MOVE process.");
01111         }
01112 
01113         return NULL;
01114     }
01115 
01116     /* not a collection */
01117     if ((err = dav_fs_copymove_file(is_move, src->info->pool,
01118                                     src->info->pathname, dst->info->pathname,
01119                                     &work_buf)) != NULL) {
01120         /* ### push a higher-level description? */
01121         return err;
01122     }
01123         
01124     /* copy/move properties as well */
01125     return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
01126 }
01127 
01128 static dav_error * dav_fs_copy_resource(
01129     const dav_resource *src,
01130     dav_resource *dst,
01131     int depth,
01132     dav_response **response)
01133 {
01134     dav_error *err;
01135 
01136 #if DAV_DEBUG
01137     if (src->hooks != dst->hooks) {
01138         /*
01139         ** ### strictly speaking, this is a design error; we should not
01140         ** ### have reached this point.
01141         */
01142         return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
01143                              "DESIGN ERROR: a mix of repositories "
01144                              "was passed to copy_resource.");
01145     }
01146 #endif
01147 
01148     if ((err = dav_fs_copymove_resource(0, src, dst, depth,
01149                                         response)) == NULL) {
01150 
01151         /* update state of destination resource to show it exists */
01152         dst->exists = 1;
01153         dst->collection = src->collection;
01154     }
01155 
01156     return err;
01157 }
01158 
01159 static dav_error * dav_fs_move_resource(
01160     dav_resource *src,
01161     dav_resource *dst,
01162     dav_response **response)
01163 {
01164     dav_resource_private *srcinfo = src->info;
01165     dav_resource_private *dstinfo = dst->info;
01166     dav_error *err;
01167     int can_rename = 0;
01168 
01169 #if DAV_DEBUG
01170     if (src->hooks != dst->hooks) {
01171         /*
01172         ** ### strictly speaking, this is a design error; we should not
01173         ** ### have reached this point.
01174         */
01175         return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
01176                              "DESIGN ERROR: a mix of repositories "
01177                              "was passed to move_resource.");
01178     }
01179 #endif
01180 
01181     /* determine whether a simple rename will work.
01182      * Assume source exists, else we wouldn't get called.
01183      */
01184     if (dstinfo->finfo.filetype != 0) {
01185         if (dstinfo->finfo.device == srcinfo->finfo.device) {
01186             /* target exists and is on the same device. */
01187             can_rename = 1;
01188         }
01189     }
01190     else {
01191         const char *dirpath;
01192         apr_finfo_t finfo;
01193         apr_status_t rv;
01194 
01195         /* destination does not exist, but the parent directory should,
01196          * so try it
01197          */
01198         dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
01199         /* 
01200          * XXX: If missing dev ... then what test?
01201          * Really need a try and failover for those platforms.
01202          * 
01203          */
01204         rv = apr_stat(&finfo, dirpath, APR_FINFO_DEV, dstinfo->pool);
01205         if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
01206             && (finfo.valid & srcinfo->finfo.valid & APR_FINFO_DEV)
01207             && (finfo.device == srcinfo->finfo.device)) {
01208             can_rename = 1;
01209         }
01210     }
01211 
01212     /* if we can't simply rename, then do it the hard way... */
01213     if (!can_rename) {
01214         if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
01215                                             response)) == NULL) {
01216             /* update resource states */
01217             dst->exists = 1;
01218             dst->collection = src->collection;
01219             src->exists = 0;
01220             src->collection = 0;
01221         }
01222 
01223         return err;
01224     }
01225 
01226     /* a rename should work. do it, and move properties as well */
01227 
01228     /* no multistatus response */
01229     *response = NULL;
01230 
01231     /* ### APR has no rename? */
01232     if (apr_file_rename(srcinfo->pathname, dstinfo->pathname,
01233                        srcinfo->pool) != APR_SUCCESS) {
01234         /* ### should have a better error than this. */
01235         return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
01236                              "Could not rename resource.");
01237     }
01238 
01239     /* update resource states */
01240     dst->exists = 1;
01241     dst->collection = src->collection;
01242     src->exists = 0;
01243     src->collection = 0;
01244 
01245     if ((err = dav_fs_copymoveset(1, src->info->pool,
01246                                   src, dst, NULL)) == NULL) {
01247         /* no error. we're done. go ahead and return now. */
01248         return NULL;
01249     }
01250 
01251     /* error occurred during properties move; try to put resource back */
01252     if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
01253                        srcinfo->pool) != APR_SUCCESS) {
01254         /* couldn't put it back! */
01255         return dav_push_error(srcinfo->pool,
01256                               HTTP_INTERNAL_SERVER_ERROR, 0,
01257                               "The resource was moved, but a failure "
01258                               "occurred during the move of its "
01259                               "properties. The resource could not be "
01260                               "restored to its original location. The "
01261                               "server is now in an inconsistent state.",
01262                               err);
01263     }
01264 
01265     /* update resource states again */
01266     src->exists = 1;
01267     src->collection = dst->collection;
01268     dst->exists = 0;
01269     dst->collection = 0;
01270 
01271     /* resource moved back, but properties may be inconsistent */
01272     return dav_push_error(srcinfo->pool,
01273                           HTTP_INTERNAL_SERVER_ERROR, 0,
01274                           "The resource was moved, but a failure "
01275                           "occurred during the move of its properties. "
01276                           "The resource was moved back to its original "
01277                           "location, but its properties may have been "
01278                           "partially moved. The server may be in an "
01279                           "inconsistent state.",
01280                           err);
01281 }
01282 
01283 static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
01284 {
01285     dav_resource_private *info = wres->resource->info;
01286 
01287     /* do not attempt to remove a null resource,
01288      * or a collection with children
01289      */
01290     if (wres->resource->exists &&
01291         (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
01292         /* try to remove the resource */
01293         apr_status_t result;
01294 
01295         result = wres->resource->collection
01296             ? apr_dir_remove(info->pathname, wres->pool)
01297             : apr_file_remove(info->pathname, wres->pool);
01298 
01299         /*
01300         ** If an error occurred, then add it to multistatus response.
01301         ** Note that we add it for the root resource, too. It is quite
01302         ** possible to delete the whole darn tree, yet fail on the root.
01303         **
01304         ** (also: remember we are deleting via a postfix traversal)
01305         */
01306         if (result != APR_SUCCESS) {
01307             /* ### assume there is a permissions problem */
01308 
01309             /* ### use errno to generate DAV:responsedescription? */
01310             dav_add_response(wres, HTTP_FORBIDDEN, NULL);
01311         }
01312     }
01313 
01314     return NULL;
01315 }
01316 
01317 static dav_error * dav_fs_remove_resource(dav_resource *resource,
01318                                           dav_response **response)
01319 {
01320     dav_resource_private *info = resource->info;
01321 
01322     *response = NULL;
01323 
01324     /* if a collection, recursively remove it and its children,
01325      * including the state dirs
01326      */
01327     if (resource->collection) {
01328         dav_walk_params params = { 0 };
01329         dav_error *err = NULL;
01330         dav_response *multi_status;
01331 
01332         params.walk_type = (DAV_WALKTYPE_NORMAL
01333                             | DAV_WALKTYPE_HIDDEN
01334                             | DAV_WALKTYPE_POSTFIX);
01335         params.func = dav_fs_delete_walker;
01336         params.pool = info->pool;
01337         params.root = resource;
01338 
01339         if ((err = dav_fs_walk(&params, DAV_INFINITY,
01340                                &multi_status)) != NULL) {
01341             /* on a "real" error, then just punt. nothing else to do. */
01342             return err;
01343         }
01344 
01345         if ((*response = multi_status) != NULL) {
01346             /* some multistatus responses exist. wrap them in a 207 */
01347             return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
0