]> granicus.if.org Git - apache/blob - modules/dav/fs/repos.c
Add DASL(SEARCH) support to mod_dav.
[apache] / modules / dav / fs / repos.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56 ** DAV filesystem-based repository provider
57 */
58
59 #include "apr.h"
60 #include "apr_file_io.h"
61 #include "apr_strings.h"
62 #include "apr_buckets.h"
63
64 #if APR_HAVE_STDIO_H
65 #include <stdio.h>              /* for sprintf() */
66 #endif
67
68 #include "httpd.h"
69 #include "http_log.h"
70 #include "http_protocol.h"      /* for ap_set_* (in dav_fs_set_headers) */
71 #include "http_request.h"       /* for ap_update_mtime() */
72
73 #include "mod_dav.h"
74 #include "repos.h"
75
76
77 /* to assist in debugging mod_dav's GET handling */
78 #define DEBUG_GET_HANDLER       0
79
80 #define DAV_FS_COPY_BLOCKSIZE   16384   /* copy 16k at a time */
81
82 /* context needed to identify a resource */
83 struct dav_resource_private {
84     apr_pool_t *pool;        /* memory storage pool associated with request */
85     const char *pathname;   /* full pathname to resource */
86     apr_finfo_t finfo;       /* filesystem info */
87 };
88
89 /* private context for doing a filesystem walk */
90 typedef struct {
91     /* the input walk parameters */
92     const dav_walk_params *params;
93
94     /* reused as we walk */
95     dav_walk_resource wres;
96
97     dav_resource res1;
98     dav_resource_private info1;
99     dav_buffer path1;
100     dav_buffer uri_buf;
101
102     /* MOVE/COPY need a secondary path */
103     dav_resource res2;
104     dav_resource_private info2;
105     dav_buffer path2;
106
107     dav_buffer locknull_buf;
108
109 } dav_fs_walker_context;
110
111 typedef struct {
112     int is_move;                /* is this a MOVE? */
113     dav_buffer work_buf;        /* handy buffer for copymove_file() */
114
115     /* CALLBACK: this is a secondary resource managed specially for us */
116     const dav_resource *res_dst;
117
118     /* copied from dav_walk_params (they are invariant across the walk) */
119     const dav_resource *root;
120     apr_pool_t *pool;
121
122 } dav_fs_copymove_walk_ctx;
123
124 /* an internal WALKTYPE to walk hidden files (the .DAV directory) */
125 #define DAV_WALKTYPE_HIDDEN     0x4000
126
127 /* an internal WALKTYPE to call collections (again) after their contents */
128 #define DAV_WALKTYPE_POSTFIX    0x8000
129
130 #define DAV_CALLTYPE_POSTFIX    1000    /* a private call type */
131
132
133 /* pull this in from the other source file */
134 extern const dav_hooks_locks dav_hooks_locks_fs;
135
136 /* forward-declare the hook structures */
137 static const dav_hooks_repository dav_hooks_repository_fs;
138 static const dav_hooks_liveprop dav_hooks_liveprop_fs;
139
140 /*
141 ** The namespace URIs that we use. This list and the enumeration must
142 ** stay in sync.
143 */
144 static const char * const dav_fs_namespace_uris[] =
145 {
146     "DAV:",
147     "http://apache.org/dav/props/",
148
149     NULL        /* sentinel */
150 };
151 enum {
152     DAV_FS_URI_DAV,             /* the DAV: namespace URI */
153     DAV_FS_URI_MYPROPS          /* the namespace URI for our custom props */
154 };
155
156 /*
157 ** Does this platform support an executable flag?
158 **
159 ** ### need a way to portably abstract this query
160 */
161 #ifndef WIN32
162 #define DAV_FS_HAS_EXECUTABLE
163 #endif
164
165 /*
166 ** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
167 */
168 #define DAV_PROPID_FS_executable        1
169
170 static const dav_liveprop_spec dav_fs_props[] =
171 {
172     /* standard DAV properties */
173     {
174         DAV_FS_URI_DAV,
175         "creationdate",
176         DAV_PROPID_creationdate,
177         0
178     },
179     {
180         DAV_FS_URI_DAV,
181         "getcontentlength",
182         DAV_PROPID_getcontentlength,
183         0
184     },
185     {
186         DAV_FS_URI_DAV,
187         "getetag",
188         DAV_PROPID_getetag,
189         0
190     },
191     {
192         DAV_FS_URI_DAV,
193         "getlastmodified",
194         DAV_PROPID_getlastmodified,
195         0
196     },
197
198     /* our custom properties */
199     {
200         DAV_FS_URI_MYPROPS,
201         "executable",
202         DAV_PROPID_FS_executable,
203         0       /* handled special in dav_fs_is_writable */
204     },
205
206     { 0 }       /* sentinel */
207 };
208
209 static const dav_liveprop_group dav_fs_liveprop_group =
210 {
211     dav_fs_props,
212     dav_fs_namespace_uris,
213     &dav_hooks_liveprop_fs
214 };
215
216
217 /* define the dav_stream structure for our use */
218 struct dav_stream {
219     apr_pool_t *p;
220     apr_file_t *f;
221     const char *pathname;       /* we may need to remove it at close time */
222 };
223
224 /* forward declaration for internal treewalkers */
225 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
226                                dav_response **response);
227 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
228                                         int depth, int is_move,
229                                         const dav_resource *root_dst,
230                                         dav_response **response);
231
232 /* --------------------------------------------------------------------
233 **
234 ** PRIVATE REPOSITORY FUNCTIONS
235 */
236 apr_pool_t *dav_fs_pool(const dav_resource *resource)
237 {
238     return resource->info->pool;
239 }
240
241 const char *dav_fs_pathname(const dav_resource *resource)
242 {
243     return resource->info->pathname;
244 }
245
246 dav_error * dav_fs_dir_file_name(
247     const dav_resource *resource,
248     const char **dirpath_p,
249     const char **fname_p)
250 {
251     dav_resource_private *ctx = resource->info;
252
253     if (resource->collection) {
254         *dirpath_p = ctx->pathname;
255         if (fname_p != NULL)
256             *fname_p = NULL;
257     }
258     else {
259         const char *testpath, *rootpath;
260         char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
261         apr_size_t dirlen = strlen(dirpath);
262         apr_status_t rv = APR_SUCCESS;
263
264         testpath = dirpath;
265         if (dirlen > 0) {
266             rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
267         }
268         
269         /* remove trailing slash from dirpath, unless it's a root path
270          */
271         if ((rv == APR_SUCCESS && testpath && *testpath)
272             || rv == APR_ERELATIVE) {
273             if (dirpath[dirlen - 1] == '/') {
274                 dirpath[dirlen - 1] = '\0';
275             }
276         }
277         
278         /* ###: Looks like a response could be appropriate
279          *
280          * APR_SUCCESS     here tells us the dir is a root
281          * APR_ERELATIVE   told us we had no root (ok)
282          * APR_EINCOMPLETE an incomplete testpath told us
283          *                 there was no -file- name here!
284          * APR_EBADPATH    or other errors tell us this file
285          *                 path is undecipherable
286          */
287
288         if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
289             *dirpath_p = dirpath;
290             if (fname_p != NULL)
291                 *fname_p = ctx->pathname + dirlen;
292         }
293         else {
294             return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
295                                  "An incomplete/bad path was found in "
296                                  "dav_fs_dir_file_name.");
297         }
298     }
299
300     return NULL;
301 }
302
303 /* Note: picked up from ap_gm_timestr_822() */
304 /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
305 static void dav_format_time(int style, apr_time_t sec, char *buf)
306 {
307     apr_time_exp_t tms;
308     
309     /* ### what to do if fails? */
310     (void) apr_time_exp_gmt(&tms, sec);
311
312     if (style == DAV_STYLE_ISO8601) {
313         /* ### should we use "-00:00" instead of "Z" ?? */
314
315         /* 20 chars plus null term */
316         sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
317                tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
318                tms.tm_hour, tms.tm_min, tms.tm_sec);
319         return;
320     }
321
322     /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
323
324     /* 29 chars plus null term */
325     sprintf(buf,
326             "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
327            apr_day_snames[tms.tm_wday],
328            tms.tm_mday, apr_month_snames[tms.tm_mon],
329            tms.tm_year + 1900,
330            tms.tm_hour, tms.tm_min, tms.tm_sec);
331 }
332
333 static dav_error * dav_fs_copymove_file(
334     int is_move,
335     apr_pool_t * p,
336     const char *src,
337     const char *dst,
338     dav_buffer *pbuf)
339 {
340     dav_buffer work_buf = { 0 };
341     apr_file_t *inf = NULL;
342     apr_file_t *outf = NULL;
343
344     if (pbuf == NULL)
345         pbuf = &work_buf;
346
347     dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
348
349     if ((apr_file_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p)) 
350         != APR_SUCCESS) {
351         /* ### use something besides 500? */
352         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
353                              "Could not open file for reading");
354     }
355
356     /* ### do we need to deal with the umask? */
357     if ((apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
358                  APR_OS_DEFAULT, p)) != APR_SUCCESS) {
359         apr_file_close(inf);
360
361         /* ### use something besides 500? */
362         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
363                              "Could not open file for writing");
364     }
365
366     while (1) {
367         apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
368         apr_status_t status;
369
370         status = apr_file_read(inf, pbuf->buf, &len);
371         if (status != APR_SUCCESS && status != APR_EOF) {
372             apr_file_close(inf);
373             apr_file_close(outf);
374             
375             if (apr_file_remove(dst, p) != APR_SUCCESS) {
376                 /* ### ACK! Inconsistent state... */
377
378                 /* ### use something besides 500? */
379                 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
380                                      "Could not delete output after read "
381                                      "failure. Server is now in an "
382                                      "inconsistent state.");
383             }
384
385             /* ### use something besides 500? */
386             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
387                                  "Could not read input file");
388         }
389
390         /* write any bytes that were read (applies to APR_EOF, too) */
391         if (apr_file_write_full(outf, pbuf->buf, len, NULL) != APR_SUCCESS) {
392             int save_errno = errno;
393
394             apr_file_close(inf);
395             apr_file_close(outf);
396
397             if (apr_file_remove(dst, p) != APR_SUCCESS) {
398                 /* ### ACK! Inconsistent state... */
399
400                 /* ### use something besides 500? */
401                 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
402                                      "Could not delete output after write "
403                                      "failure. Server is now in an "
404                                      "inconsistent state.");
405             }
406
407             if (save_errno == ENOSPC) {
408                 return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0,
409                                      "There is not enough storage to write to "
410                                      "this resource.");
411             }
412
413             /* ### use something besides 500? */
414             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
415                                  "Could not write output file");
416         }
417
418         if (status == APR_EOF)
419             break;
420     }
421
422     apr_file_close(inf);
423     apr_file_close(outf);
424
425     if (is_move && apr_file_remove(src, p) != APR_SUCCESS) {
426         dav_error *err;
427         int save_errno = errno; /* save the errno that got us here */
428
429         if (apr_file_remove(dst, p) != APR_SUCCESS) {
430             /* ### ACK. this creates an inconsistency. do more!? */
431
432             /* ### use something besides 500? */
433             /* Note that we use the latest errno */
434             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
435                                  "Could not remove source or destination "
436                                  "file. Server is now in an inconsistent "
437                                  "state.");
438         }
439
440         /* ### use something besides 500? */
441         err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
442                             "Could not remove source file after move. "
443                             "Destination was removed to ensure consistency.");
444         err->save_errno = save_errno;
445         return err;
446     }
447
448     return NULL;
449 }
450
451 /* copy/move a file from within a state dir to another state dir */
452 /* ### need more buffers to replace the pool argument */
453 static dav_error * dav_fs_copymove_state(
454     int is_move,
455     apr_pool_t * p,
456     const char *src_dir, const char *src_file,
457     const char *dst_dir, const char *dst_file,
458     dav_buffer *pbuf)
459 {
460     apr_finfo_t src_finfo;      /* finfo for source file */
461     apr_finfo_t dst_state_finfo;        /* finfo for STATE directory */
462     apr_status_t rv;
463     const char *src;
464     const char *dst;
465
466     /* build the propset pathname for the source file */
467     src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
468
469     /* the source file doesn't exist */
470     rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
471     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
472         return NULL;
473     }
474
475     /* build the pathname for the destination state dir */
476     dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
477
478     /* ### do we need to deal with the umask? */
479
480     /* ensure that it exists */
481     rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
482     if (rv != APR_SUCCESS) {
483         if (!APR_STATUS_IS_EEXIST(rv)) {
484             /* ### use something besides 500? */
485             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
486                                  "Could not create internal state directory");
487         }
488     }
489
490     /* get info about the state directory */
491     rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
492     if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
493         /* Ack! Where'd it go? */
494         /* ### use something besides 500? */
495         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
496                              "State directory disappeared");
497     }
498
499     /* The mkdir() may have failed because a *file* exists there already */
500     if (dst_state_finfo.filetype != APR_DIR) {
501         /* ### try to recover by deleting this file? (and mkdir again) */
502         /* ### use something besides 500? */
503         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
504                              "State directory is actually a file");
505     }
506
507     /* append the target file to the state directory pathname */
508     dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
509
510     /* copy/move the file now */
511     if (is_move && src_finfo.device == dst_state_finfo.device) {
512         /* simple rename is possible since it is on the same device */
513         if (apr_file_rename(src, dst, p) != APR_SUCCESS) {
514             /* ### use something besides 500? */
515             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
516                                  "Could not move state file.");
517         }
518     }
519     else
520     {
521         /* gotta copy (and delete) */
522         return dav_fs_copymove_file(is_move, p, src, dst, pbuf);
523     }
524
525     return NULL;
526 }
527
528 static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
529                                      const dav_resource *src,
530                                      const dav_resource *dst,
531                                      dav_buffer *pbuf)
532 {
533     const char *src_dir;
534     const char *src_file;
535     const char *src_state1;
536     const char *src_state2;
537     const char *dst_dir;
538     const char *dst_file;
539     const char *dst_state1;
540     const char *dst_state2;
541     dav_error *err;
542
543     /* Get directory and filename for resources */
544     /* ### should test these result values... */
545     (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
546     (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
547
548     /* Get the corresponding state files for each resource */
549     dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
550     dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
551 #if DAV_DEBUG
552     if ((src_state2 != NULL && dst_state2 == NULL) ||
553         (src_state2 == NULL && dst_state2 != NULL)) {
554         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
555                              "DESIGN ERROR: dav_dbm_get_statefiles() "
556                              "returned inconsistent results.");
557     }
558 #endif
559
560     err = dav_fs_copymove_state(is_move, p,
561                                 src_dir, src_state1,
562                                 dst_dir, dst_state1,
563                                 pbuf);
564
565     if (err == NULL && src_state2 != NULL) {
566         err = dav_fs_copymove_state(is_move, p,
567                                     src_dir, src_state2,
568                                     dst_dir, dst_state2,
569                                     pbuf);
570
571         if (err != NULL) {
572             /* ### CRAP. inconsistency. */
573             /* ### should perform some cleanup at the target if we still
574                ### have the original files */
575
576             /* Change the error to reflect the bad server state. */
577             err->status = HTTP_INTERNAL_SERVER_ERROR;
578             err->desc =
579                 "Could not fully copy/move the properties. "
580                 "The server is now in an inconsistent state.";
581         }
582     }
583
584     return err;
585 }
586
587 static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
588 {
589     const char *dirpath;
590     const char *fname;
591     const char *state1;
592     const char *state2;
593     const char *pathname;
594     apr_status_t status;
595
596     /* Get directory, filename, and state-file names for the resource */
597     /* ### should test this result value... */
598     (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
599     dav_dbm_get_statefiles(p, fname, &state1, &state2);
600
601     /* build the propset pathname for the file */
602     pathname = apr_pstrcat(p,
603                           dirpath,
604                           "/" DAV_FS_STATE_DIR "/",
605                           state1,
606                           NULL);
607
608     /* note: we may get ENOENT if the state dir is not present */
609     if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
610         && !APR_STATUS_IS_ENOENT(status)) {
611         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
612                              "Could not remove properties.");
613     }
614
615     if (state2 != NULL) {
616         /* build the propset pathname for the file */
617         pathname = apr_pstrcat(p,
618                               dirpath,
619                               "/" DAV_FS_STATE_DIR "/",
620                               state2,
621                               NULL);
622
623         if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
624             && !APR_STATUS_IS_ENOENT(status)) {
625             /* ### CRAP. only removed half. */
626             return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
627                                  "Could not fully remove properties. "
628                                  "The server is now in an inconsistent "
629                                  "state.");
630         }
631     }
632
633     return NULL;
634 }
635
636 /* --------------------------------------------------------------------
637 **
638 ** REPOSITORY HOOK FUNCTIONS
639 */
640
641 static dav_error * dav_fs_get_resource(
642     request_rec *r,
643     const char *root_dir,
644     const char *label,
645     int use_checked_in,
646     dav_resource **result_resource)
647 {
648     dav_resource_private *ctx;
649     dav_resource *resource;
650     char *s;
651     char *filename;
652     apr_size_t len;
653
654     /* ### optimize this into a single allocation! */
655
656     /* Create private resource context descriptor */
657     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
658     ctx->finfo = r->finfo;
659
660     /* ### this should go away */
661     ctx->pool = r->pool;
662
663     /* Preserve case on OSes which fold canonical filenames */
664 #if 0
665     /* ### not available in Apache 2.0 yet */
666     filename = r->case_preserved_filename;
667 #else
668     filename = r->filename;
669 #endif
670
671     /*
672     ** If there is anything in the path_info, then this indicates that the
673     ** entire path was not used to specify the file/dir. We want to append
674     ** it onto the filename so that we get a "valid" pathname for null
675     ** resources.
676     */
677     s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
678
679     /* make sure the pathname does not have a trailing "/" */
680     len = strlen(s);
681     if (len > 1 && s[len - 1] == '/') {
682         s[len - 1] = '\0';
683     }
684     ctx->pathname = s;
685
686     /* Create resource descriptor */
687     resource = apr_pcalloc(r->pool, sizeof(*resource));
688     resource->type = DAV_RESOURCE_TYPE_REGULAR;
689     resource->info = ctx;
690     resource->hooks = &dav_hooks_repository_fs;
691     resource->pool = r->pool;
692
693     /* make sure the URI does not have a trailing "/" */
694     len = strlen(r->uri);
695     if (len > 1 && r->uri[len - 1] == '/') {
696         s = apr_pstrdup(r->pool, r->uri);
697         s[len - 1] = '\0';
698         resource->uri = s;
699     }
700     else {
701         resource->uri = r->uri;
702     }
703
704     if (r->finfo.filetype != 0) {
705         resource->exists = 1;
706         resource->collection = r->finfo.filetype == APR_DIR;
707
708         /* unused info in the URL will indicate a null resource */
709
710         if (r->path_info != NULL && *r->path_info != '\0') {
711             if (resource->collection) {
712                 /* only a trailing "/" is allowed */
713                 if (*r->path_info != '/' || r->path_info[1] != '\0') {
714
715                     /*
716                     ** This URL/filename represents a locknull resource or
717                     ** possibly a destination of a MOVE/COPY
718                     */
719                     resource->exists = 0;
720                     resource->collection = 0;
721                 }
722             }
723             else
724             {
725                 /*
726                 ** The base of the path refers to a file -- nothing should
727                 ** be in path_info. The resource is simply an error: it
728                 ** can't be a null or a locknull resource.
729                 */
730                 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
731                                      "The URL contains extraneous path "
732                                      "components. The resource could not "
733                                      "be identified.");
734             }
735
736             /* retain proper integrity across the structures */
737             if (!resource->exists) {
738                 ctx->finfo.filetype = 0;
739             }
740         }
741     }
742
743     *result_resource = resource;
744     return NULL;
745 }
746
747 static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
748                                               dav_resource **result_parent)
749 {
750     dav_resource_private *ctx = resource->info;
751     dav_resource_private *parent_ctx;
752     dav_resource *parent_resource;
753     apr_status_t rv;
754     char *dirpath;
755     const char *testroot;
756     const char *testpath;
757
758     /* If we're at the root of the URL space, then there is no parent. */
759     if (strcmp(resource->uri, "/") == 0) {
760         *result_parent = NULL;
761         return NULL;
762     }
763
764     /* If given resource is root, then there is no parent.
765      * Unless we can retrieve the filepath root, this is
766      * intendend to fail.  If we split the root and
767      * no path info remains, then we also fail.
768      */
769     testpath = ctx->pathname;
770     rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
771     if ((rv != APR_SUCCESS && rv != APR_ERELATIVE) 
772         || !testpath || !*testpath) {
773         *result_parent = NULL;
774         return NULL;
775     }
776
777     /* ### optimize this into a single allocation! */
778
779     /* Create private resource context descriptor */
780     parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
781
782     /* ### this should go away */
783     parent_ctx->pool = ctx->pool;
784
785     dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
786     if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/') 
787         dirpath[strlen(dirpath) - 1] = '\0';
788     parent_ctx->pathname = dirpath;
789
790     parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
791     parent_resource->info = parent_ctx;
792     parent_resource->collection = 1;
793     parent_resource->hooks = &dav_hooks_repository_fs;
794     parent_resource->pool = resource->pool;
795
796     if (resource->uri != NULL) {
797         char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
798         if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
799             uri[strlen(uri) - 1] = '\0';
800         parent_resource->uri = uri;
801     }
802
803     rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname, 
804                   APR_FINFO_NORM, ctx->pool);
805     if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
806         parent_resource->exists = 1;
807     }
808
809     *result_parent = parent_resource;
810     return NULL;
811 }
812
813 static int dav_fs_is_same_resource(
814     const dav_resource *res1,
815     const dav_resource *res2)
816 {
817     dav_resource_private *ctx1 = res1->info;
818     dav_resource_private *ctx2 = res2->info;
819
820     if (res1->hooks != res2->hooks)
821         return 0;
822
823     if ((ctx1->finfo.filetype != 0) && (ctx2->finfo.filetype != 0)
824         && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
825         return ctx1->finfo.inode == ctx2->finfo.inode;
826     }
827     else {
828         return strcmp(ctx1->pathname, ctx2->pathname) == 0;
829     }
830 }
831
832 static int dav_fs_is_parent_resource(
833     const dav_resource *res1,
834     const dav_resource *res2)
835 {
836     dav_resource_private *ctx1 = res1->info;
837     dav_resource_private *ctx2 = res2->info;
838     apr_size_t len1 = strlen(ctx1->pathname);
839     apr_size_t len2;
840
841     if (res1->hooks != res2->hooks)
842         return 0;
843
844     /* it is safe to use ctx2 now */
845     len2 = strlen(ctx2->pathname);
846
847     return (len2 > len1
848             && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
849             && ctx2->pathname[len1] == '/');
850 }
851
852 static dav_error * dav_fs_open_stream(const dav_resource *resource,
853                                       dav_stream_mode mode,
854                                       dav_stream **stream)
855 {
856     apr_pool_t *p = resource->info->pool;
857     dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
858     apr_int32_t flags;
859
860     switch (mode) {
861     default:
862         flags = APR_READ | APR_BINARY;
863         break;
864
865     case DAV_MODE_WRITE_TRUNC:
866         flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
867         break;
868     case DAV_MODE_WRITE_SEEKABLE:
869         flags = APR_WRITE | APR_CREATE | APR_BINARY;
870         break;
871     }
872
873     ds->p = p;
874     ds->pathname = resource->info->pathname;
875     if (apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, 
876                 ds->p) != APR_SUCCESS) {
877         /* ### use something besides 500? */
878         return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
879                              "An error occurred while opening a resource.");
880     }
881
882     /* (APR registers cleanups for the fd with the pool) */
883
884     *stream = ds;
885     return NULL;
886 }
887
888 static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
889 {
890     apr_file_close(stream->f);
891
892     if (!commit) {
893         if (apr_file_remove(stream->pathname, stream->p) != APR_SUCCESS) {
894             /* ### use a better description? */
895             return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
896                                  "There was a problem removing (rolling "
897                                  "back) the resource "
898                                  "when it was being closed.");
899         }
900     }
901
902     return NULL;
903 }
904
905 static dav_error * dav_fs_write_stream(dav_stream *stream,
906                                        const void *buf, apr_size_t bufsize)
907 {
908     apr_status_t status;
909
910     status = apr_file_write_full(stream->f, buf, bufsize, NULL);
911     if (APR_STATUS_IS_ENOSPC(status)) {
912         return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
913                              "There is not enough storage to write to "
914                              "this resource.");
915     }
916     else if (status != APR_SUCCESS) {
917         /* ### use something besides 500? */
918         return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
919                              "An error occurred while writing to a "
920                              "resource.");
921     }
922     return NULL;
923 }
924
925 static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
926 {
927     if (apr_file_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
928         /* ### should check whether apr_file_seek set abs_pos was set to the
929          * correct position? */
930         /* ### use something besides 500? */
931         return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
932                              "Could not seek to specified position in the "
933                              "resource.");
934     }
935     return NULL;
936 }
937
938
939 #if DEBUG_GET_HANDLER
940
941 /* only define set_headers() and deliver() for debug purposes */
942
943
944 static dav_error * dav_fs_set_headers(request_rec *r,
945                                       const dav_resource *resource)
946 {
947     /* ### this function isn't really used since we have a get_pathname */
948     if (!resource->exists)
949         return NULL;
950
951     /* make sure the proper mtime is in the request record */
952     ap_update_mtime(r, resource->info->finfo.mtime);
953
954     /* ### note that these use r->filename rather than <resource> */
955     ap_set_last_modified(r);
956     ap_set_etag(r);
957
958     /* we accept byte-ranges */
959     apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
960
961     /* set up the Content-Length header */
962     ap_set_content_length(r, resource->info->finfo.size);
963
964     /* ### how to set the content type? */
965     /* ### until this is resolved, the Content-Type header is busted */
966
967     return NULL;
968 }
969
970 static dav_error * dav_fs_deliver(const dav_resource *resource,
971                                   ap_filter_t *output)
972 {
973     apr_pool_t *pool = resource->pool;
974     apr_bucket_brigade *bb;
975     apr_file_t *fd;
976     apr_status_t status;
977     apr_bucket *bkt;
978
979     /* Check resource type */
980     if (resource->type != DAV_RESOURCE_TYPE_REGULAR
981         && resource->type != DAV_RESOURCE_TYPE_VERSION
982         && resource->type != DAV_RESOURCE_TYPE_WORKING) {
983         return dav_new_error(pool, HTTP_CONFLICT, 0,
984                              "Cannot GET this type of resource.");
985     }
986     if (resource->collection) {
987         return dav_new_error(pool, HTTP_CONFLICT, 0,
988                              "There is no default response to GET for a "
989                              "collection.");
990     }
991
992     if ((status = apr_file_open(&fd, resource->info->pathname,
993                                 APR_READ | APR_BINARY, 0,
994                                 pool)) != APR_SUCCESS) {
995         return dav_new_error(pool, HTTP_FORBIDDEN, 0,
996                              "File permissions deny server access.");
997     }
998
999     bb = apr_brigade_create(pool, output->c->bucket_alloc);
1000
1001     /* ### this does not handle large files. but this is test code anyway */
1002     bkt = apr_bucket_file_create(fd, 0,
1003                                  (apr_size_t)resource->info->finfo.size,
1004                                  pool, output->c->bucket_alloc);
1005     APR_BRIGADE_INSERT_TAIL(bb, bkt);
1006
1007     bkt = apr_bucket_eos_create(output->c->bucket_alloc);
1008     APR_BRIGADE_INSERT_TAIL(bb, bkt);
1009
1010     if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
1011         return dav_new_error(pool, HTTP_FORBIDDEN, 0,
1012                              "Could not write contents to filter.");
1013     }
1014
1015     return NULL;
1016 }
1017
1018 #endif /* DEBUG_GET_HANDLER */
1019
1020
1021 static dav_error * dav_fs_create_collection(dav_resource *resource)
1022 {
1023     dav_resource_private *ctx = resource->info;
1024     apr_status_t status;
1025
1026     status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
1027     if (status == ENOSPC) {
1028         return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
1029                              "There is not enough storage to create "
1030                              "this collection.");
1031     }
1032     else if (status != APR_SUCCESS) {
1033         /* ### refine this error message? */
1034         return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
1035                              "Unable to create collection.");
1036     }
1037
1038     /* update resource state to show it exists as a collection */
1039     resource->exists = 1;
1040     resource->collection = 1;
1041
1042     return NULL;
1043 }
1044
1045 static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
1046                                           int calltype)
1047 {
1048     dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
1049     dav_resource_private *srcinfo = wres->resource->info;
1050     dav_resource_private *dstinfo = ctx->res_dst->info;
1051     dav_error *err = NULL;
1052
1053     if (wres->resource->collection) {
1054         if (calltype == DAV_CALLTYPE_POSTFIX) {
1055             /* Postfix call for MOVE. delete the source dir.
1056              * Note: when copying, we do not enable the postfix-traversal.
1057              */
1058             /* ### we are ignoring any error here; what should we do? */
1059             (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
1060         }
1061         else {
1062             /* copy/move of a collection. Create the new, target collection */
1063             if (apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
1064                              ctx->pool) != APR_SUCCESS) {
1065                 /* ### assume it was a permissions problem */
1066                 /* ### need a description here */
1067                 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
1068             }
1069         }
1070     }
1071     else {
1072         err = dav_fs_copymove_file(ctx->is_move, ctx->pool, 
1073                                    srcinfo->pathname, dstinfo->pathname, 
1074                                    &ctx->work_buf);
1075         /* ### push a higher-level description? */
1076     }
1077
1078     /*
1079     ** If we have a "not so bad" error, then it might need to go into a
1080     ** multistatus response.
1081     **
1082     ** For a MOVE, it will always go into the multistatus. It could be
1083     ** that everything has been moved *except* for the root. Using a
1084     ** multistatus (with no errors for the other resources) will signify
1085     ** this condition.
1086     **
1087     ** For a COPY, we are traversing in a prefix fashion. If the root fails,
1088     ** then we can just bail out now.
1089     */
1090     if (err != NULL
1091         && !ap_is_HTTP_SERVER_ERROR(err->status)
1092         && (ctx->is_move
1093             || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
1094         /* ### use errno to generate DAV:responsedescription? */
1095         dav_add_response(wres, err->status, NULL);
1096
1097         /* the error is in the multistatus now. do not stop the traversal. */
1098         return NULL;
1099     }
1100
1101     return err;
1102 }
1103
1104 static dav_error *dav_fs_copymove_resource(
1105     int is_move,
1106     const dav_resource *src,
1107     const dav_resource *dst,
1108     int depth,
1109     dav_response **response)
1110 {
1111     dav_error *err = NULL;
1112     dav_buffer work_buf = { 0 };
1113
1114     *response = NULL;
1115
1116     /* if a collection, recursively copy/move it and its children,
1117      * including the state dirs
1118      */
1119     if (src->collection) {
1120         dav_walk_params params = { 0 };
1121         dav_response *multi_status;
1122
1123         params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
1124         params.func = dav_fs_copymove_walker;
1125         params.pool = src->info->pool;
1126         params.root = src;
1127
1128         /* params.walk_ctx is managed by dav_fs_internal_walk() */
1129
1130         /* postfix is needed for MOVE to delete source dirs */
1131         if (is_move)
1132             params.walk_type |= DAV_WALKTYPE_POSTFIX;
1133
1134         /* note that we return the error OR the multistatus. never both */
1135
1136         if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
1137                                         &multi_status)) != NULL) {
1138             /* on a "real" error, then just punt. nothing else to do. */
1139             return err;
1140         }
1141
1142         if ((*response = multi_status) != NULL) {
1143             /* some multistatus responses exist. wrap them in a 207 */
1144             return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
1145                                  "Error(s) occurred on some resources during "
1146                                  "the COPY/MOVE process.");
1147         }
1148
1149         return NULL;
1150     }
1151
1152     /* not a collection */
1153     if ((err = dav_fs_copymove_file(is_move, src->info->pool,
1154                                     src->info->pathname, dst->info->pathname,
1155                                     &work_buf)) != NULL) {
1156         /* ### push a higher-level description? */
1157         return err;
1158     }
1159         
1160     /* copy/move properties as well */
1161     return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
1162 }
1163
1164 static dav_error * dav_fs_copy_resource(
1165     const dav_resource *src,
1166     dav_resource *dst,
1167     int depth,
1168     dav_response **response)
1169 {
1170     dav_error *err;
1171
1172 #if DAV_DEBUG
1173     if (src->hooks != dst->hooks) {
1174         /*
1175         ** ### strictly speaking, this is a design error; we should not
1176         ** ### have reached this point.
1177         */
1178         return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1179                              "DESIGN ERROR: a mix of repositories "
1180                              "was passed to copy_resource.");
1181     }
1182 #endif
1183
1184     if ((err = dav_fs_copymove_resource(0, src, dst, depth,
1185                                         response)) == NULL) {
1186
1187         /* update state of destination resource to show it exists */
1188         dst->exists = 1;
1189         dst->collection = src->collection;
1190     }
1191
1192     return err;
1193 }
1194
1195 static dav_error * dav_fs_move_resource(
1196     dav_resource *src,
1197     dav_resource *dst,
1198     dav_response **response)
1199 {
1200     dav_resource_private *srcinfo = src->info;
1201     dav_resource_private *dstinfo = dst->info;
1202     dav_error *err;
1203     int can_rename = 0;
1204
1205 #if DAV_DEBUG
1206     if (src->hooks != dst->hooks) {
1207         /*
1208         ** ### strictly speaking, this is a design error; we should not
1209         ** ### have reached this point.
1210         */
1211         return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1212                              "DESIGN ERROR: a mix of repositories "
1213                              "was passed to move_resource.");
1214     }
1215 #endif
1216
1217     /* determine whether a simple rename will work.
1218      * Assume source exists, else we wouldn't get called.
1219      */
1220     if (dstinfo->finfo.filetype != 0) {
1221         if (dstinfo->finfo.device == srcinfo->finfo.device) {
1222             /* target exists and is on the same device. */
1223             can_rename = 1;
1224         }
1225     }
1226     else {
1227         const char *dirpath;
1228         apr_finfo_t finfo;
1229         apr_status_t rv;
1230
1231         /* destination does not exist, but the parent directory should,
1232          * so try it
1233          */
1234         dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
1235         /* 
1236          * XXX: If missing dev ... then what test?
1237          * Really need a try and failover for those platforms.
1238          * 
1239          */
1240         rv = apr_stat(&finfo, dirpath, APR_FINFO_DEV, dstinfo->pool);
1241         if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
1242             && (finfo.valid & srcinfo->finfo.valid & APR_FINFO_DEV)
1243             && (finfo.device == srcinfo->finfo.device)) {
1244             can_rename = 1;
1245         }
1246     }
1247
1248     /* if we can't simply rename, then do it the hard way... */
1249     if (!can_rename) {
1250         if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
1251                                             response)) == NULL) {
1252             /* update resource states */
1253             dst->exists = 1;
1254             dst->collection = src->collection;
1255             src->exists = 0;
1256             src->collection = 0;
1257         }
1258
1259         return err;
1260     }
1261
1262     /* a rename should work. do it, and move properties as well */
1263
1264     /* no multistatus response */
1265     *response = NULL;
1266
1267     /* ### APR has no rename? */
1268     if (apr_file_rename(srcinfo->pathname, dstinfo->pathname,
1269                        srcinfo->pool) != APR_SUCCESS) {
1270         /* ### should have a better error than this. */
1271         return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1272                              "Could not rename resource.");
1273     }
1274
1275     /* update resource states */
1276     dst->exists = 1;
1277     dst->collection = src->collection;
1278     src->exists = 0;
1279     src->collection = 0;
1280
1281     if ((err = dav_fs_copymoveset(1, src->info->pool,
1282                                   src, dst, NULL)) == NULL) {
1283         /* no error. we're done. go ahead and return now. */
1284         return NULL;
1285     }
1286
1287     /* error occurred during properties move; try to put resource back */
1288     if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
1289                        srcinfo->pool) != APR_SUCCESS) {
1290         /* couldn't put it back! */
1291         return dav_push_error(srcinfo->pool,
1292                               HTTP_INTERNAL_SERVER_ERROR, 0,
1293                               "The resource was moved, but a failure "
1294                               "occurred during the move of its "
1295                               "properties. The resource could not be "
1296                               "restored to its original location. The "
1297                               "server is now in an inconsistent state.",
1298                               err);
1299     }
1300
1301     /* update resource states again */
1302     src->exists = 1;
1303     src->collection = dst->collection;
1304     dst->exists = 0;
1305     dst->collection = 0;
1306
1307     /* resource moved back, but properties may be inconsistent */
1308     return dav_push_error(srcinfo->pool,
1309                           HTTP_INTERNAL_SERVER_ERROR, 0,
1310                           "The resource was moved, but a failure "
1311                           "occurred during the move of its properties. "
1312                           "The resource was moved back to its original "
1313                           "location, but its properties may have been "
1314                           "partially moved. The server may be in an "
1315                           "inconsistent state.",
1316                           err);
1317 }
1318
1319 static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
1320 {
1321     dav_resource_private *info = wres->resource->info;
1322
1323     /* do not attempt to remove a null resource,
1324      * or a collection with children
1325      */
1326     if (wres->resource->exists &&
1327         (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
1328         /* try to remove the resource */
1329         apr_status_t result;
1330
1331         result = wres->resource->collection
1332             ? apr_dir_remove(info->pathname, wres->pool)
1333             : apr_file_remove(info->pathname, wres->pool);
1334
1335         /*
1336         ** If an error occurred, then add it to multistatus response.
1337         ** Note that we add it for the root resource, too. It is quite
1338         ** possible to delete the whole darn tree, yet fail on the root.
1339         **
1340         ** (also: remember we are deleting via a postfix traversal)
1341         */
1342         if (result != APR_SUCCESS) {
1343             /* ### assume there is a permissions problem */
1344
1345             /* ### use errno to generate DAV:responsedescription? */
1346             dav_add_response(wres, HTTP_FORBIDDEN, NULL);
1347         }
1348     }
1349
1350     return NULL;
1351 }
1352
1353 static dav_error * dav_fs_remove_resource(dav_resource *resource,
1354                                           dav_response **response)
1355 {
1356     dav_resource_private *info = resource->info;
1357
1358     *response = NULL;
1359
1360     /* if a collection, recursively remove it and its children,
1361      * including the state dirs
1362      */
1363     if (resource->collection) {
1364         dav_walk_params params = { 0 };
1365         dav_error *err = NULL;
1366         dav_response *multi_status;
1367
1368         params.walk_type = (DAV_WALKTYPE_NORMAL
1369                             | DAV_WALKTYPE_HIDDEN
1370                             | DAV_WALKTYPE_POSTFIX);
1371         params.func = dav_fs_delete_walker;
1372         params.pool = info->pool;
1373         params.root = resource;
1374
1375         if ((err = dav_fs_walk(&params, DAV_INFINITY,
1376                                &multi_status)) != NULL) {
1377             /* on a "real" error, then just punt. nothing else to do. */
1378             return err;
1379         }
1380
1381         if ((*response = multi_status) != NULL) {
1382             /* some multistatus responses exist. wrap them in a 207 */
1383             return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
1384                                  "Error(s) occurred on some resources during "
1385                                  "the deletion process.");
1386         }
1387
1388         /* no errors... update resource state */
1389         resource->exists = 0;
1390         resource->collection = 0;
1391
1392         return NULL;
1393     }
1394
1395     /* not a collection; remove the file and its properties */
1396     if (apr_file_remove(info->pathname, info->pool) != APR_SUCCESS) {
1397         /* ### put a description in here */
1398         return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
1399     }
1400
1401     /* update resource state */
1402     resource->exists = 0;
1403     resource->collection = 0;
1404
1405     /* remove properties and return its result */
1406     return dav_fs_deleteset(info->pool, resource);
1407 }
1408
1409 /* ### move this to dav_util? */
1410 /* Walk recursively down through directories, *
1411  * including lock-null resources as we go.    */
1412 static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
1413 {
1414     const dav_walk_params *params = fsctx->params;
1415     apr_pool_t *pool = params->pool;
1416     dav_error *err = NULL;
1417     int isdir = fsctx->res1.collection;
1418     apr_finfo_t dirent;
1419     apr_dir_t *dirp;
1420
1421     /* ensure the context is prepared properly, then call the func */
1422     err = (*params->func)(&fsctx->wres,
1423                           isdir
1424                           ? DAV_CALLTYPE_COLLECTION
1425                           : DAV_CALLTYPE_MEMBER);
1426     if (err != NULL) {
1427         return err;
1428     }
1429
1430     if (depth == 0 || !isdir) {
1431         return NULL;
1432     }
1433
1434     /* put a trailing slash onto the directory, in preparation for appending
1435      * files to it as we discovery them within the directory */
1436     dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
1437     fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
1438     fsctx->path1.buf[fsctx->path1.cur_len] = '\0';      /* in pad area */
1439
1440     /* if a secondary path is present, then do that, too */
1441     if (fsctx->path2.buf != NULL) {
1442         dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
1443         fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
1444         fsctx->path2.buf[fsctx->path2.cur_len] = '\0';  /* in pad area */
1445     }
1446
1447     /* Note: the URI should ALREADY have a trailing "/" */
1448
1449     /* for this first pass of files, all resources exist */
1450     fsctx->res1.exists = 1;
1451
1452     /* a file is the default; we'll adjust if we hit a directory */
1453     fsctx->res1.collection = 0;
1454     fsctx->res2.collection = 0;
1455
1456     /* open and scan the directory */
1457     if ((apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
1458         /* ### need a better error */
1459         return dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1460     }
1461     while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
1462         apr_size_t len;
1463         apr_status_t status;
1464
1465         len = strlen(dirent.name);
1466
1467         /* avoid recursing into our current, parent, or state directories */
1468         if (dirent.name[0] == '.' 
1469               && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
1470             continue;
1471         }
1472
1473         if (params->walk_type & DAV_WALKTYPE_AUTH) {
1474             /* ### need to authorize each file */
1475             /* ### example: .htaccess is normally configured to fail auth */
1476
1477             /* stuff in the state directory is never authorized! */
1478             if (!strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1479                 continue;
1480             }
1481         }
1482         /* skip the state dir unless a HIDDEN is performed */
1483         if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
1484             && !strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1485             continue;
1486         }
1487
1488         /* append this file onto the path buffer (copy null term) */
1489         dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
1490
1491
1492         /* ### Optimize me, dirent can give us what we need! */
1493         status = apr_lstat(&fsctx->info1.finfo, fsctx->path1.buf, 
1494                            APR_FINFO_NORM, pool);
1495         if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
1496             /* woah! where'd it go? */
1497             /* ### should have a better error here */
1498             err = dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1499             break;
1500         }
1501
1502         /* copy the file to the URI, too. NOTE: we will pad an extra byte
1503            for the trailing slash later. */
1504         dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
1505
1506         /* if there is a secondary path, then do that, too */
1507         if (fsctx->path2.buf != NULL) {
1508             dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
1509         }
1510
1511         /* set up the (internal) pathnames for the two resources */
1512         fsctx->info1.pathname = fsctx->path1.buf;
1513         fsctx->info2.pathname = fsctx->path2.buf;
1514
1515         /* set up the URI for the current resource */
1516         fsctx->res1.uri = fsctx->uri_buf.buf;
1517
1518         /* ### for now, only process regular files (e.g. skip symlinks) */
1519         if (fsctx->info1.finfo.filetype == APR_REG) {
1520             /* call the function for the specified dir + file */
1521             if ((err = (*params->func)(&fsctx->wres,
1522                                        DAV_CALLTYPE_MEMBER)) != NULL) {
1523                 /* ### maybe add a higher-level description? */
1524                 break;
1525             }
1526         }
1527         else if (fsctx->info1.finfo.filetype == APR_DIR) {
1528             apr_size_t save_path_len = fsctx->path1.cur_len;
1529             apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
1530             apr_size_t save_path2_len = fsctx->path2.cur_len;
1531
1532             /* adjust length to incorporate the subdir name */
1533             fsctx->path1.cur_len += len;
1534             fsctx->path2.cur_len += len;
1535
1536             /* adjust URI length to incorporate subdir and a slash */
1537             fsctx->uri_buf.cur_len += len + 1;
1538             fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
1539             fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
1540
1541             /* switch over to a collection */
1542             fsctx->res1.collection = 1;
1543             fsctx->res2.collection = 1;
1544
1545             /* recurse on the subdir */
1546             /* ### don't always want to quit on error from single child */
1547             if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
1548                 /* ### maybe add a higher-level description? */
1549                 break;
1550             }
1551
1552             /* put the various information back */
1553             fsctx->path1.cur_len = save_path_len;
1554             fsctx->path2.cur_len = save_path2_len;
1555             fsctx->uri_buf.cur_len = save_uri_len;
1556
1557             fsctx->res1.collection = 0;
1558             fsctx->res2.collection = 0;
1559
1560             /* assert: res1.exists == 1 */
1561         }
1562     }
1563
1564     /* ### check the return value of this? */
1565     apr_dir_close(dirp);
1566
1567     if (err != NULL)
1568         return err;
1569
1570     if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
1571         apr_size_t offset = 0;
1572
1573         /* null terminate the directory name */
1574         fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
1575
1576         /* Include any lock null resources found in this collection */
1577         fsctx->res1.collection = 1;
1578         if ((err = dav_fs_get_locknull_members(&fsctx->res1,
1579                                                &fsctx->locknull_buf)) != NULL) {
1580             /* ### maybe add a higher-level description? */
1581             return err;
1582         }
1583
1584         /* put a slash back on the end of the directory */
1585         fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
1586
1587         /* these are all non-existant (files) */
1588         fsctx->res1.exists = 0;
1589         fsctx->res1.collection = 0;
1590         memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
1591
1592         while (offset < fsctx->locknull_buf.cur_len) {
1593             apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
1594             dav_lock *locks = NULL;
1595
1596             /*
1597             ** Append the locknull file to the paths and the URI. Note that
1598             ** we don't have to pad the URI for a slash since a locknull
1599             ** resource is not a collection.
1600             */
1601             dav_buffer_place_mem(pool, &fsctx->path1,
1602                                  fsctx->locknull_buf.buf + offset, len + 1, 0);
1603             dav_buffer_place_mem(pool, &fsctx->uri_buf,
1604                                  fsctx->locknull_buf.buf + offset, len + 1, 0);
1605             if (fsctx->path2.buf != NULL) {
1606                 dav_buffer_place_mem(pool, &fsctx->path2,
1607                                      fsctx->locknull_buf.buf + offset,
1608                                      len + 1, 0);
1609             }
1610
1611             /* set up the (internal) pathnames for the two resources */
1612             fsctx->info1.pathname = fsctx->path1.buf;
1613             fsctx->info2.pathname = fsctx->path2.buf;
1614
1615             /* set up the URI for the current resource */
1616             fsctx->res1.uri = fsctx->uri_buf.buf;
1617
1618             /*
1619             ** To prevent a PROPFIND showing an expired locknull
1620             ** resource, query the lock database to force removal
1621             ** of both the lock entry and .locknull, if necessary..
1622             ** Sure, the query in PROPFIND would do this.. after
1623             ** the locknull resource was already included in the 
1624             ** return.
1625             **
1626             ** NOTE: we assume the caller has opened the lock database
1627             **       if they have provided DAV_WALKTYPE_LOCKNULL.
1628             */
1629             /* ### we should also look into opening it read-only and
1630                ### eliding timed-out items from the walk, yet leaving
1631                ### them in the locknull database until somebody opens
1632                ### the thing writable.
1633                */
1634             /* ### probably ought to use has_locks. note the problem
1635                ### mentioned above, though... we would traverse this as
1636                ### a locknull, but then a PROPFIND would load the lock
1637                ### info, causing a timeout and the locks would not be
1638                ### reported. Therefore, a null resource would be returned
1639                ### in the PROPFIND.
1640                ###
1641                ### alternative: just load unresolved locks. any direct
1642                ### locks will be timed out (correct). any indirect will
1643                ### not (correct; consider if a parent timed out -- the
1644                ### timeout routines do not walk and remove indirects;
1645                ### even the resolve func would probably fail when it
1646                ### tried to find a timed-out direct lock).
1647             */
1648             if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
1649                                       &locks)) != NULL) {
1650                 /* ### maybe add a higher-level description? */
1651                 return err;
1652             }
1653
1654             /* call the function for the specified dir + file */
1655             if (locks != NULL &&
1656                 (err = (*params->func)(&fsctx->wres,
1657                                        DAV_CALLTYPE_LOCKNULL)) != NULL) {
1658                 /* ### maybe add a higher-level description? */
1659                 return err;
1660             }
1661
1662             offset += len + 1;
1663         }
1664
1665         /* reset the exists flag */
1666         fsctx->res1.exists = 1;
1667     }
1668
1669     if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
1670         /* replace the dirs' trailing slashes with null terms */
1671         fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
1672         fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
1673         if (fsctx->path2.buf != NULL) {
1674             fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
1675         }
1676
1677         /* this is a collection which exists */
1678         fsctx->res1.collection = 1;
1679
1680         return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
1681     }
1682
1683     return NULL;
1684 }
1685
1686 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
1687                                         int depth, int is_move,
1688                                         const dav_resource *root_dst,
1689                                         dav_response **response)
1690 {
1691     dav_fs_walker_context fsctx = { 0 };
1692     dav_error *err;
1693     dav_fs_copymove_walk_ctx cm_ctx = { 0 };
1694
1695 #if DAV_DEBUG
1696     if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
1697         && params->lockdb == NULL) {
1698         return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1699                              "DESIGN ERROR: walker called to walk locknull "
1700                              "resources, but a lockdb was not provided.");
1701     }
1702 #endif
1703
1704     fsctx.params = params;
1705     fsctx.wres.walk_ctx = params->walk_ctx;
1706     fsctx.wres.pool = params->pool;
1707
1708     /* ### zero out versioned, working, baselined? */
1709
1710     fsctx.res1 = *params->root;
1711     fsctx.res1.pool = params->pool;
1712
1713     fsctx.res1.info = &fsctx.info1;
1714     fsctx.info1 = *params->root->info;
1715
1716     /* the pathname is stored in the path1 buffer */
1717     dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
1718     fsctx.info1.pathname = fsctx.path1.buf;
1719
1720     if (root_dst != NULL) {
1721         /* internal call from the COPY/MOVE code. set it up. */
1722
1723         fsctx.wres.walk_ctx = &cm_ctx;
1724         cm_ctx.is_move = is_move;
1725         cm_ctx.res_dst = &fsctx.res2;
1726         cm_ctx.root = params->root;
1727         cm_ctx.pool = params->pool;
1728
1729         fsctx.res2 = *root_dst;
1730         fsctx.res2.exists = 0;
1731         fsctx.res2.collection = 0;
1732         fsctx.res2.uri = NULL;          /* we don't track this */
1733         fsctx.res2.pool = params->pool;
1734
1735         fsctx.res2.info = &fsctx.info2;
1736         fsctx.info2 = *root_dst->info;
1737
1738         /* res2 does not exist -- clear its finfo structure */
1739         memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
1740
1741         /* the pathname is stored in the path2 buffer */
1742         dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
1743         fsctx.info2.pathname = fsctx.path2.buf;
1744     }
1745
1746     /* prep the URI buffer */
1747     dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
1748
1749     /* if we have a directory, then ensure the URI has a trailing "/" */
1750     if (fsctx.res1.collection
1751         && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
1752
1753         /* this will fall into the pad area */
1754         fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
1755         fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
1756     }
1757
1758     /* the current resource's URI is stored in the uri_buf buffer */
1759     fsctx.res1.uri = fsctx.uri_buf.buf;
1760
1761     /* point the callback's resource at our structure */
1762     fsctx.wres.resource = &fsctx.res1;
1763
1764     /* always return the error, and any/all multistatus responses */
1765     err = dav_fs_walker(&fsctx, depth);
1766     *response = fsctx.wres.response;
1767     return err;
1768 }
1769
1770 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
1771                                dav_response **response)
1772 {
1773     /* always return the error, and any/all multistatus responses */
1774     return dav_fs_internal_walk(params, depth, 0, NULL, response);
1775 }
1776
1777 /* dav_fs_etag:  Stolen from ap_make_etag.  Creates a strong etag
1778  *    for file path.
1779  * ### do we need to return weak tags sometimes?
1780  */
1781 static const char *dav_fs_getetag(const dav_resource *resource)
1782 {
1783     dav_resource_private *ctx = resource->info;
1784
1785     if (!resource->exists) 
1786         return apr_pstrdup(ctx->pool, "");
1787
1788     if (ctx->finfo.filetype != 0) {
1789         return apr_psprintf(ctx->pool, "\"%lx-%lx-%lx\"",
1790                            (unsigned long) ctx->finfo.inode,
1791                            (unsigned long) ctx->finfo.size,
1792                            (unsigned long) ctx->finfo.mtime);
1793     }
1794
1795     return apr_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.mtime);
1796 }
1797
1798 static const dav_hooks_repository dav_hooks_repository_fs =
1799 {
1800     DEBUG_GET_HANDLER,   /* normally: special GET handling not required */
1801     dav_fs_get_resource,
1802     dav_fs_get_parent_resource,
1803     dav_fs_is_same_resource,
1804     dav_fs_is_parent_resource,
1805     dav_fs_open_stream,
1806     dav_fs_close_stream,
1807     dav_fs_write_stream,
1808     dav_fs_seek_stream,
1809 #if DEBUG_GET_HANDLER
1810     dav_fs_set_headers,
1811     dav_fs_deliver,
1812 #else
1813     NULL,
1814     NULL,
1815 #endif
1816     dav_fs_create_collection,
1817     dav_fs_copy_resource,
1818     dav_fs_move_resource,
1819     dav_fs_remove_resource,
1820     dav_fs_walk,
1821     dav_fs_getetag,
1822 };
1823
1824 static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
1825                                           int propid, dav_prop_insert what,
1826                                           ap_text_header *phdr)
1827 {
1828     const char *value;
1829     const char *s;
1830     apr_pool_t *p = resource->info->pool;
1831     const dav_liveprop_spec *info;
1832     int global_ns;
1833
1834     /* an HTTP-date can be 29 chars plus a null term */
1835     /* a 64-bit size can be 20 chars plus a null term */
1836     char buf[DAV_TIMEBUF_SIZE];
1837
1838     /*
1839     ** None of FS provider properties are defined if the resource does not
1840     ** exist. Just bail for this case.
1841     **
1842     ** Even though we state that the FS properties are not defined, the
1843     ** client cannot store dead values -- we deny that thru the is_writable
1844     ** hook function.
1845     */
1846     if (!resource->exists)
1847         return DAV_PROP_INSERT_NOTDEF;
1848
1849     switch (propid) {
1850     case DAV_PROPID_creationdate:
1851         /*
1852         ** Closest thing to a creation date. since we don't actually
1853         ** perform the operations that would modify ctime (after we
1854         ** create the file), then we should be pretty safe here.
1855         */
1856         dav_format_time(DAV_STYLE_ISO8601,
1857                         resource->info->finfo.ctime,
1858                         buf);
1859         value = buf;
1860         break;
1861
1862     case DAV_PROPID_getcontentlength:
1863         /* our property, but not defined on collection resources */
1864         if (resource->collection)
1865             return DAV_PROP_INSERT_NOTDEF;
1866
1867         (void) sprintf(buf, "%" APR_OFF_T_FMT, resource->info->finfo.size);
1868         value = buf;
1869         break;
1870
1871     case DAV_PROPID_getetag:
1872         value = dav_fs_getetag(resource);
1873         break;
1874
1875     case DAV_PROPID_getlastmodified:
1876         dav_format_time(DAV_STYLE_RFC822,
1877                         resource->info->finfo.mtime,
1878                         buf);
1879         value = buf;
1880         break;
1881
1882     case DAV_PROPID_FS_executable:
1883         /* our property, but not defined on collection resources */
1884         if (resource->collection)
1885             return DAV_PROP_INSERT_NOTDEF;
1886
1887         /* our property, but not defined on this platform */
1888         if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
1889             return DAV_PROP_INSERT_NOTDEF;
1890
1891         /* the files are "ours" so we only need to check owner exec privs */
1892         if (resource->info->finfo.protection & APR_UEXECUTE)
1893             value = "T";
1894         else
1895             value = "F";
1896         break;
1897
1898     default:
1899         /* ### what the heck was this property? */
1900         return DAV_PROP_INSERT_NOTDEF;
1901     }
1902
1903     /* assert: value != NULL */
1904
1905     /* get the information and global NS index for the property */
1906     global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1907
1908     /* assert: info != NULL && info->name != NULL */
1909
1910     /* DBG3("FS: inserting lp%d:%s  (local %d)", ns, scan->name, scan->ns); */
1911
1912     if (what == DAV_PROP_INSERT_VALUE) {
1913         s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
1914                          global_ns, info->name, value, global_ns, info->name);
1915     }
1916     else if (what == DAV_PROP_INSERT_NAME) {
1917         s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
1918     }
1919     else {
1920         /* assert: what == DAV_PROP_INSERT_SUPPORTED */
1921         s = apr_psprintf(p,
1922                          "<D:supported-live-property D:name=\"%s\" "
1923                          "D:namespace=\"%s\"/>" DEBUG_CR,
1924                          info->name, dav_fs_namespace_uris[info->ns]);
1925     }
1926     ap_text_append(p, phdr, s);
1927
1928     /* we inserted what was asked for */
1929     return what;
1930 }
1931
1932 static int dav_fs_is_writable(const dav_resource *resource, int propid)
1933 {
1934     const dav_liveprop_spec *info;
1935
1936 #ifdef DAV_FS_HAS_EXECUTABLE
1937     /* if we have the executable property, and this isn't a collection,
1938        then the property is writable. */
1939     if (propid == DAV_PROPID_FS_executable && !resource->collection)
1940         return 1;
1941 #endif
1942
1943     (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1944     return info->is_writable;
1945 }
1946
1947 static dav_error *dav_fs_patch_validate(const dav_resource *resource,
1948                                         const ap_xml_elem *elem,
1949                                         int operation,
1950                                         void **context,
1951                                         int *defer_to_dead)
1952 {
1953     const ap_text *cdata;
1954     const ap_text *f_cdata;
1955     char value;
1956     dav_elem_private *priv = elem->priv;
1957
1958     if (priv->propid != DAV_PROPID_FS_executable) {
1959         *defer_to_dead = 1;
1960         return NULL;
1961     }
1962
1963     if (operation == DAV_PROP_OP_DELETE) {
1964         return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1965                              "The 'executable' property cannot be removed.");
1966     }
1967
1968     cdata = elem->first_cdata.first;
1969
1970     /* ### hmm. this isn't actually looking at all the possible text items */
1971     f_cdata = elem->first_child == NULL
1972         ? NULL
1973         : elem->first_child->following_cdata.first;
1974
1975     /* DBG3("name=%s  cdata=%s  f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
1976
1977     if (cdata == NULL) {
1978         if (f_cdata == NULL) {
1979             return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1980                                  "The 'executable' property expects a single "
1981                                  "character, valued 'T' or 'F'. There was no "
1982                                  "value submitted.");
1983         }
1984         cdata = f_cdata;
1985     }
1986     else if (f_cdata != NULL)
1987         goto too_long;
1988
1989     if (cdata->next != NULL || strlen(cdata->text) != 1)
1990         goto too_long;
1991
1992     value = cdata->text[0];
1993     if (value != 'T' && value != 'F') {
1994         return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1995                              "The 'executable' property expects a single "
1996                              "character, valued 'T' or 'F'. The value "
1997                              "submitted is invalid.");
1998     }
1999
2000     *context = (void *)(value == 'T');
2001
2002     return NULL;
2003
2004   too_long:
2005     return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
2006                          "The 'executable' property expects a single "
2007                          "character, valued 'T' or 'F'. The value submitted "
2008                          "has too many characters.");
2009
2010 }
2011
2012 static dav_error *dav_fs_patch_exec(const dav_resource *resource,
2013                                     const ap_xml_elem *elem,
2014                                     int operation,
2015                                     void *context,
2016                                     dav_liveprop_rollback **rollback_ctx)
2017 {
2018     int value = context != NULL;
2019     apr_fileperms_t perms = resource->info->finfo.protection;
2020     int old_value = (perms & APR_UEXECUTE) != 0;
2021
2022     /* assert: prop == executable. operation == SET. */
2023
2024     /* don't do anything if there is no change. no rollback info either. */
2025     /* DBG2("new value=%d  (old=%d)", value, old_value); */
2026     if (value == old_value)
2027         return NULL;
2028
2029     perms &= ~APR_UEXECUTE;
2030     if (value)
2031         perms |= APR_UEXECUTE;
2032
2033     if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2034         return dav_new_error(resource->info->pool,
2035                              HTTP_INTERNAL_SERVER_ERROR, 0,
2036                              "Could not set the executable flag of the "
2037                              "target resource.");
2038     }
2039
2040     /* update the resource and set up the rollback context */
2041     resource->info->finfo.protection = perms;
2042     *rollback_ctx = (dav_liveprop_rollback *)old_value;
2043
2044     return NULL;
2045 }
2046
2047 static void dav_fs_patch_commit(const dav_resource *resource,
2048                                 int operation,
2049                                 void *context,
2050                                 dav_liveprop_rollback *rollback_ctx)
2051 {
2052     /* nothing to do */
2053 }
2054
2055 static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
2056                                         int operation,
2057                                         void *context,
2058                                         dav_liveprop_rollback *rollback_ctx)
2059 {
2060     apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
2061     int value = rollback_ctx != NULL;
2062
2063     /* assert: prop == executable. operation == SET. */
2064
2065     /* restore the executable bit */
2066     if (value)
2067         perms |= APR_UEXECUTE;
2068
2069     if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2070         return dav_new_error(resource->info->pool,
2071                              HTTP_INTERNAL_SERVER_ERROR, 0,
2072                              "After a failure occurred, the resource's "
2073                              "executable flag could not be restored.");
2074     }
2075
2076     /* restore the resource's state */
2077     resource->info->finfo.protection = perms;
2078
2079     return NULL;
2080 }
2081
2082
2083 static const dav_hooks_liveprop dav_hooks_liveprop_fs =
2084 {
2085     dav_fs_insert_prop,
2086     dav_fs_is_writable,
2087     dav_fs_namespace_uris,
2088     dav_fs_patch_validate,
2089     dav_fs_patch_exec,
2090     dav_fs_patch_commit,
2091     dav_fs_patch_rollback
2092 };
2093
2094 static const dav_provider dav_fs_provider =
2095 {
2096     &dav_hooks_repository_fs,
2097     &dav_hooks_db_dbm,
2098     &dav_hooks_locks_fs,
2099     NULL,               /* vsn */
2100     NULL,               /* binding */
2101     NULL                /* search */
2102 };
2103
2104 void dav_fs_gather_propsets(apr_array_header_t *uris)
2105 {
2106 #ifdef DAV_FS_HAS_EXECUTABLE
2107     *(const char **)apr_array_push(uris) =
2108         "<http://apache.org/dav/propset/fs/1>";
2109 #endif
2110 }
2111
2112 int dav_fs_find_liveprop(const dav_resource *resource,
2113                          const char *ns_uri, const char *name,
2114                          const dav_hooks_liveprop **hooks)
2115 {
2116     /* don't try to find any liveprops if this isn't "our" resource */
2117     if (resource->hooks != &dav_hooks_repository_fs)
2118         return 0;
2119     return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
2120 }
2121
2122 void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
2123                                  dav_prop_insert what, ap_text_header *phdr)
2124 {
2125     /* don't insert any liveprops if this isn't "our" resource */
2126     if (resource->hooks != &dav_hooks_repository_fs)
2127         return;
2128
2129     if (!resource->exists) {
2130         /* a lock-null resource */
2131         /*
2132         ** ### technically, we should insert empty properties. dunno offhand
2133         ** ### what part of the spec said this, but it was essentially thus:
2134         ** ### "the properties should be defined, but may have no value".
2135         */
2136         return;
2137     }
2138
2139     (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
2140                               what, phdr);
2141     (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
2142                               what, phdr);
2143     (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
2144                               what, phdr);
2145     (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
2146                               what, phdr);
2147
2148 #ifdef DAV_FS_HAS_EXECUTABLE
2149     /* Only insert this property if it is defined for this platform. */
2150     (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
2151                               what, phdr);
2152 #endif
2153
2154     /* ### we know the others aren't defined as liveprops */
2155 }
2156
2157 void dav_fs_register(apr_pool_t *p)
2158 {
2159     /* register the namespace URIs */
2160     dav_register_liveprop_group(p, &dav_fs_liveprop_group);
2161
2162     /* register the repository provider */
2163     dav_register_provider(p, "filesystem", &dav_fs_provider);
2164 }