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