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