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