switch to a 60 bit hash
[httpd-crcsyncproxy.git] / modules / dav / fs / repos.c
blob8364ab686d1b538c36c95ba682a977dcb140f58a
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 ** DAV filesystem-based repository provider
21 #include "apr.h"
22 #include "apr_file_io.h"
23 #include "apr_strings.h"
24 #include "apr_buckets.h"
26 #if APR_HAVE_STDIO_H
27 #include <stdio.h> /* for sprintf() */
28 #endif
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() */
35 #include "mod_dav.h"
36 #include "repos.h"
39 /* to assist in debugging mod_dav's GET handling */
40 #define DEBUG_GET_HANDLER 0
42 #define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
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 */
51 /* private context for doing a filesystem walk */
52 typedef struct {
53 /* the input walk parameters */
54 const dav_walk_params *params;
56 /* reused as we walk */
57 dav_walk_resource wres;
59 dav_resource res1;
60 dav_resource_private info1;
61 dav_buffer path1;
62 dav_buffer uri_buf;
64 /* MOVE/COPY need a secondary path */
65 dav_resource res2;
66 dav_resource_private info2;
67 dav_buffer path2;
69 dav_buffer locknull_buf;
71 } dav_fs_walker_context;
73 typedef struct {
74 int is_move; /* is this a MOVE? */
75 dav_buffer work_buf; /* handy buffer for copymove_file() */
77 /* CALLBACK: this is a secondary resource managed specially for us */
78 const dav_resource *res_dst;
80 /* copied from dav_walk_params (they are invariant across the walk) */
81 const dav_resource *root;
82 apr_pool_t *pool;
84 } dav_fs_copymove_walk_ctx;
86 /* an internal WALKTYPE to walk hidden files (the .DAV directory) */
87 #define DAV_WALKTYPE_HIDDEN 0x4000
89 /* an internal WALKTYPE to call collections (again) after their contents */
90 #define DAV_WALKTYPE_POSTFIX 0x8000
92 #define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
95 /* pull this in from the other source file */
96 extern const dav_hooks_locks dav_hooks_locks_fs;
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;
103 ** The namespace URIs that we use. This list and the enumeration must
104 ** stay in sync.
106 static const char * const dav_fs_namespace_uris[] =
108 "DAV:",
109 "http://apache.org/dav/props/",
111 NULL /* sentinel */
113 enum {
114 DAV_FS_URI_DAV, /* the DAV: namespace URI */
115 DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
119 ** Does this platform support an executable flag?
121 ** ### need a way to portably abstract this query
123 ** DAV_FINFO_MASK gives the appropriate mask to use for the stat call
124 ** used to get file attributes.
126 #ifndef WIN32
127 #define DAV_FS_HAS_EXECUTABLE
128 #define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
129 APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME | \
130 APR_FINFO_PROT)
131 #else
132 /* as above, but without APR_FINFO_PROT */
133 #define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \
134 APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME)
135 #endif
138 ** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
140 #define DAV_PROPID_FS_executable 1
142 static const dav_liveprop_spec dav_fs_props[] =
144 /* standard DAV properties */
146 DAV_FS_URI_DAV,
147 "creationdate",
148 DAV_PROPID_creationdate,
152 DAV_FS_URI_DAV,
153 "getcontentlength",
154 DAV_PROPID_getcontentlength,
158 DAV_FS_URI_DAV,
159 "getetag",
160 DAV_PROPID_getetag,
164 DAV_FS_URI_DAV,
165 "getlastmodified",
166 DAV_PROPID_getlastmodified,
170 /* our custom properties */
172 DAV_FS_URI_MYPROPS,
173 "executable",
174 DAV_PROPID_FS_executable,
175 0 /* handled special in dav_fs_is_writable */
178 { 0 } /* sentinel */
181 static const dav_liveprop_group dav_fs_liveprop_group =
183 dav_fs_props,
184 dav_fs_namespace_uris,
185 &dav_hooks_liveprop_fs
189 /* define the dav_stream structure for our use */
190 struct dav_stream {
191 apr_pool_t *p;
192 apr_file_t *f;
193 const char *pathname; /* we may need to remove it at close time */
196 /* returns an appropriate HTTP status code given an APR status code for a
197 * failed I/O operation. ### use something besides 500? */
198 #define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
199 HTTP_INTERNAL_SERVER_ERROR)
201 /* forward declaration for internal treewalkers */
202 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
203 dav_response **response);
204 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
205 int depth, int is_move,
206 const dav_resource *root_dst,
207 dav_response **response);
209 /* --------------------------------------------------------------------
211 ** PRIVATE REPOSITORY FUNCTIONS
213 apr_pool_t *dav_fs_pool(const dav_resource *resource)
215 return resource->info->pool;
218 const char *dav_fs_pathname(const dav_resource *resource)
220 return resource->info->pathname;
223 dav_error * dav_fs_dir_file_name(
224 const dav_resource *resource,
225 const char **dirpath_p,
226 const char **fname_p)
228 dav_resource_private *ctx = resource->info;
230 if (resource->collection) {
231 *dirpath_p = ctx->pathname;
232 if (fname_p != NULL)
233 *fname_p = NULL;
235 else {
236 const char *testpath, *rootpath;
237 char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
238 apr_size_t dirlen = strlen(dirpath);
239 apr_status_t rv = APR_SUCCESS;
241 testpath = dirpath;
242 if (dirlen > 0) {
243 rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
246 /* remove trailing slash from dirpath, unless it's a root path
248 if ((rv == APR_SUCCESS && testpath && *testpath)
249 || rv == APR_ERELATIVE) {
250 if (dirpath[dirlen - 1] == '/') {
251 dirpath[dirlen - 1] = '\0';
255 /* ###: Looks like a response could be appropriate
257 * APR_SUCCESS here tells us the dir is a root
258 * APR_ERELATIVE told us we had no root (ok)
259 * APR_EINCOMPLETE an incomplete testpath told us
260 * there was no -file- name here!
261 * APR_EBADPATH or other errors tell us this file
262 * path is undecipherable
265 if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
266 *dirpath_p = dirpath;
267 if (fname_p != NULL)
268 *fname_p = ctx->pathname + dirlen;
270 else {
271 return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
272 "An incomplete/bad path was found in "
273 "dav_fs_dir_file_name.");
277 return NULL;
280 /* Note: picked up from ap_gm_timestr_822() */
281 /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
282 static void dav_format_time(int style, apr_time_t sec, char *buf)
284 apr_time_exp_t tms;
286 /* ### what to do if fails? */
287 (void) apr_time_exp_gmt(&tms, sec);
289 if (style == DAV_STYLE_ISO8601) {
290 /* ### should we use "-00:00" instead of "Z" ?? */
292 /* 20 chars plus null term */
293 sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
294 tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
295 tms.tm_hour, tms.tm_min, tms.tm_sec);
296 return;
299 /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
301 /* 29 chars plus null term */
302 sprintf(buf,
303 "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
304 apr_day_snames[tms.tm_wday],
305 tms.tm_mday, apr_month_snames[tms.tm_mon],
306 tms.tm_year + 1900,
307 tms.tm_hour, tms.tm_min, tms.tm_sec);
310 /* Copy or move src to dst; src_finfo is used to propagate permissions
311 * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
312 * exists. */
313 static dav_error * dav_fs_copymove_file(
314 int is_move,
315 apr_pool_t * p,
316 const char *src,
317 const char *dst,
318 const apr_finfo_t *src_finfo,
319 const apr_finfo_t *dst_finfo,
320 dav_buffer *pbuf)
322 dav_buffer work_buf = { 0 };
323 apr_file_t *inf = NULL;
324 apr_file_t *outf = NULL;
325 apr_status_t status;
326 apr_fileperms_t perms;
328 if (pbuf == NULL)
329 pbuf = &work_buf;
331 /* Determine permissions to use for destination */
332 if (src_finfo && src_finfo->valid & APR_FINFO_PROT
333 && src_finfo->protection & APR_UEXECUTE) {
334 perms = src_finfo->protection;
336 if (dst_finfo != NULL) {
337 /* chmod it if it already exist */
338 if (apr_file_perms_set(dst, perms)) {
339 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
340 "Could not set permissions on destination");
344 else {
345 perms = APR_OS_DEFAULT;
348 dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
350 if ((apr_file_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p))
351 != APR_SUCCESS) {
352 /* ### use something besides 500? */
353 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
354 "Could not open file for reading");
357 /* ### do we need to deal with the umask? */
358 status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
359 | APR_BINARY, perms, p);
360 if (status != APR_SUCCESS) {
361 apr_file_close(inf);
363 return dav_new_error(p, MAP_IO2HTTP(status), 0,
364 "Could not open file for writing");
367 while (1) {
368 apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
370 status = apr_file_read(inf, pbuf->buf, &len);
371 if (status != APR_SUCCESS && status != APR_EOF) {
372 apr_file_close(inf);
373 apr_file_close(outf);
375 if (apr_file_remove(dst, p) != APR_SUCCESS) {
376 /* ### ACK! Inconsistent state... */
378 /* ### use something besides 500? */
379 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
380 "Could not delete output after read "
381 "failure. Server is now in an "
382 "inconsistent state.");
385 /* ### use something besides 500? */
386 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
387 "Could not read input file");
390 if (status == APR_EOF)
391 break;
393 /* write any bytes that were read */
394 status = apr_file_write_full(outf, pbuf->buf, len, NULL);
395 if (status != APR_SUCCESS) {
396 apr_file_close(inf);
397 apr_file_close(outf);
399 if (apr_file_remove(dst, p) != APR_SUCCESS) {
400 /* ### ACK! Inconsistent state... */
402 /* ### use something besides 500? */
403 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
404 "Could not delete output after write "
405 "failure. Server is now in an "
406 "inconsistent state.");
409 return dav_new_error(p, MAP_IO2HTTP(status), 0,
410 "Could not write output file");
414 apr_file_close(inf);
415 apr_file_close(outf);
417 if (is_move && apr_file_remove(src, p) != APR_SUCCESS) {
418 dav_error *err;
419 int save_errno = errno; /* save the errno that got us here */
421 if (apr_file_remove(dst, p) != APR_SUCCESS) {
422 /* ### ACK. this creates an inconsistency. do more!? */
424 /* ### use something besides 500? */
425 /* Note that we use the latest errno */
426 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
427 "Could not remove source or destination "
428 "file. Server is now in an inconsistent "
429 "state.");
432 /* ### use something besides 500? */
433 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
434 "Could not remove source file after move. "
435 "Destination was removed to ensure consistency.");
436 err->save_errno = save_errno;
437 return err;
440 return NULL;
443 /* copy/move a file from within a state dir to another state dir */
444 /* ### need more buffers to replace the pool argument */
445 static dav_error * dav_fs_copymove_state(
446 int is_move,
447 apr_pool_t * p,
448 const char *src_dir, const char *src_file,
449 const char *dst_dir, const char *dst_file,
450 dav_buffer *pbuf)
452 apr_finfo_t src_finfo; /* finfo for source file */
453 apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
454 apr_status_t rv;
455 const char *src;
456 const char *dst;
458 /* build the propset pathname for the source file */
459 src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
461 /* the source file doesn't exist */
462 rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
463 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
464 return NULL;
467 /* build the pathname for the destination state dir */
468 dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
470 /* ### do we need to deal with the umask? */
472 /* ensure that it exists */
473 rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
474 if (rv != APR_SUCCESS) {
475 if (!APR_STATUS_IS_EEXIST(rv)) {
476 /* ### use something besides 500? */
477 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
478 "Could not create internal state directory");
482 /* get info about the state directory */
483 rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
484 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
485 /* Ack! Where'd it go? */
486 /* ### use something besides 500? */
487 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
488 "State directory disappeared");
491 /* The mkdir() may have failed because a *file* exists there already */
492 if (dst_state_finfo.filetype != APR_DIR) {
493 /* ### try to recover by deleting this file? (and mkdir again) */
494 /* ### use something besides 500? */
495 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
496 "State directory is actually a file");
499 /* append the target file to the state directory pathname */
500 dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
502 /* copy/move the file now */
503 if (is_move && src_finfo.device == dst_state_finfo.device) {
504 /* simple rename is possible since it is on the same device */
505 if (apr_file_rename(src, dst, p) != APR_SUCCESS) {
506 /* ### use something besides 500? */
507 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
508 "Could not move state file.");
511 else
513 /* gotta copy (and delete) */
514 return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
517 return NULL;
520 static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
521 const dav_resource *src,
522 const dav_resource *dst,
523 dav_buffer *pbuf)
525 const char *src_dir;
526 const char *src_file;
527 const char *src_state1;
528 const char *src_state2;
529 const char *dst_dir;
530 const char *dst_file;
531 const char *dst_state1;
532 const char *dst_state2;
533 dav_error *err;
535 /* Get directory and filename for resources */
536 /* ### should test these result values... */
537 (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
538 (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
540 /* Get the corresponding state files for each resource */
541 dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
542 dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
543 #if DAV_DEBUG
544 if ((src_state2 != NULL && dst_state2 == NULL) ||
545 (src_state2 == NULL && dst_state2 != NULL)) {
546 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
547 "DESIGN ERROR: dav_dbm_get_statefiles() "
548 "returned inconsistent results.");
550 #endif
552 err = dav_fs_copymove_state(is_move, p,
553 src_dir, src_state1,
554 dst_dir, dst_state1,
555 pbuf);
557 if (err == NULL && src_state2 != NULL) {
558 err = dav_fs_copymove_state(is_move, p,
559 src_dir, src_state2,
560 dst_dir, dst_state2,
561 pbuf);
563 if (err != NULL) {
564 /* ### CRAP. inconsistency. */
565 /* ### should perform some cleanup at the target if we still
566 ### have the original files */
568 /* Change the error to reflect the bad server state. */
569 err->status = HTTP_INTERNAL_SERVER_ERROR;
570 err->desc =
571 "Could not fully copy/move the properties. "
572 "The server is now in an inconsistent state.";
576 return err;
579 static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
581 const char *dirpath;
582 const char *fname;
583 const char *state1;
584 const char *state2;
585 const char *pathname;
586 apr_status_t status;
588 /* Get directory, filename, and state-file names for the resource */
589 /* ### should test this result value... */
590 (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
591 dav_dbm_get_statefiles(p, fname, &state1, &state2);
593 /* build the propset pathname for the file */
594 pathname = apr_pstrcat(p,
595 dirpath,
596 "/" DAV_FS_STATE_DIR "/",
597 state1,
598 NULL);
600 /* note: we may get ENOENT if the state dir is not present */
601 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
602 && !APR_STATUS_IS_ENOENT(status)) {
603 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
604 "Could not remove properties.");
607 if (state2 != NULL) {
608 /* build the propset pathname for the file */
609 pathname = apr_pstrcat(p,
610 dirpath,
611 "/" DAV_FS_STATE_DIR "/",
612 state2,
613 NULL);
615 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
616 && !APR_STATUS_IS_ENOENT(status)) {
617 /* ### CRAP. only removed half. */
618 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
619 "Could not fully remove properties. "
620 "The server is now in an inconsistent "
621 "state.");
625 return NULL;
628 /* --------------------------------------------------------------------
630 ** REPOSITORY HOOK FUNCTIONS
633 static dav_error * dav_fs_get_resource(
634 request_rec *r,
635 const char *root_dir,
636 const char *label,
637 int use_checked_in,
638 dav_resource **result_resource)
640 dav_resource_private *ctx;
641 dav_resource *resource;
642 char *s;
643 char *filename;
644 apr_size_t len;
646 /* ### optimize this into a single allocation! */
648 /* Create private resource context descriptor */
649 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
650 ctx->finfo = r->finfo;
652 /* ### this should go away */
653 ctx->pool = r->pool;
655 /* Preserve case on OSes which fold canonical filenames */
656 #if 0
657 /* ### not available in Apache 2.0 yet */
658 filename = r->case_preserved_filename;
659 #else
660 filename = r->filename;
661 #endif
664 ** If there is anything in the path_info, then this indicates that the
665 ** entire path was not used to specify the file/dir. We want to append
666 ** it onto the filename so that we get a "valid" pathname for null
667 ** resources.
669 s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
671 /* make sure the pathname does not have a trailing "/" */
672 len = strlen(s);
673 if (len > 1 && s[len - 1] == '/') {
674 s[len - 1] = '\0';
676 ctx->pathname = s;
678 /* Create resource descriptor */
679 resource = apr_pcalloc(r->pool, sizeof(*resource));
680 resource->type = DAV_RESOURCE_TYPE_REGULAR;
681 resource->info = ctx;
682 resource->hooks = &dav_hooks_repository_fs;
683 resource->pool = r->pool;
685 /* make sure the URI does not have a trailing "/" */
686 len = strlen(r->uri);
687 if (len > 1 && r->uri[len - 1] == '/') {
688 s = apr_pstrdup(r->pool, r->uri);
689 s[len - 1] = '\0';
690 resource->uri = s;
692 else {
693 resource->uri = r->uri;
696 if (r->finfo.filetype != 0) {
697 resource->exists = 1;
698 resource->collection = r->finfo.filetype == APR_DIR;
700 /* unused info in the URL will indicate a null resource */
702 if (r->path_info != NULL && *r->path_info != '\0') {
703 if (resource->collection) {
704 /* only a trailing "/" is allowed */
705 if (*r->path_info != '/' || r->path_info[1] != '\0') {
708 ** This URL/filename represents a locknull resource or
709 ** possibly a destination of a MOVE/COPY
711 resource->exists = 0;
712 resource->collection = 0;
715 else
718 ** The base of the path refers to a file -- nothing should
719 ** be in path_info. The resource is simply an error: it
720 ** can't be a null or a locknull resource.
722 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
723 "The URL contains extraneous path "
724 "components. The resource could not "
725 "be identified.");
728 /* retain proper integrity across the structures */
729 if (!resource->exists) {
730 ctx->finfo.filetype = 0;
735 *result_resource = resource;
736 return NULL;
739 static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
740 dav_resource **result_parent)
742 dav_resource_private *ctx = resource->info;
743 dav_resource_private *parent_ctx;
744 dav_resource *parent_resource;
745 apr_status_t rv;
746 char *dirpath;
747 const char *testroot;
748 const char *testpath;
750 /* If we're at the root of the URL space, then there is no parent. */
751 if (strcmp(resource->uri, "/") == 0) {
752 *result_parent = NULL;
753 return NULL;
756 /* If given resource is root, then there is no parent.
757 * Unless we can retrieve the filepath root, this is
758 * intendend to fail. If we split the root and
759 * no path info remains, then we also fail.
761 testpath = ctx->pathname;
762 rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
763 if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
764 || !testpath || !*testpath) {
765 *result_parent = NULL;
766 return NULL;
769 /* ### optimize this into a single allocation! */
771 /* Create private resource context descriptor */
772 parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
774 /* ### this should go away */
775 parent_ctx->pool = ctx->pool;
777 dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
778 if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
779 dirpath[strlen(dirpath) - 1] = '\0';
780 parent_ctx->pathname = dirpath;
782 parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
783 parent_resource->info = parent_ctx;
784 parent_resource->collection = 1;
785 parent_resource->hooks = &dav_hooks_repository_fs;
786 parent_resource->pool = resource->pool;
788 if (resource->uri != NULL) {
789 char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
790 if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
791 uri[strlen(uri) - 1] = '\0';
792 parent_resource->uri = uri;
795 rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
796 APR_FINFO_NORM, ctx->pool);
797 if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
798 parent_resource->exists = 1;
801 *result_parent = parent_resource;
802 return NULL;
805 static int dav_fs_is_same_resource(
806 const dav_resource *res1,
807 const dav_resource *res2)
809 dav_resource_private *ctx1 = res1->info;
810 dav_resource_private *ctx2 = res2->info;
812 if (res1->hooks != res2->hooks)
813 return 0;
815 if ((ctx1->finfo.filetype != 0) && (ctx2->finfo.filetype != 0)
816 && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
817 return ctx1->finfo.inode == ctx2->finfo.inode;
819 else {
820 return strcmp(ctx1->pathname, ctx2->pathname) == 0;
824 static int dav_fs_is_parent_resource(
825 const dav_resource *res1,
826 const dav_resource *res2)
828 dav_resource_private *ctx1 = res1->info;
829 dav_resource_private *ctx2 = res2->info;
830 apr_size_t len1 = strlen(ctx1->pathname);
831 apr_size_t len2;
833 if (res1->hooks != res2->hooks)
834 return 0;
836 /* it is safe to use ctx2 now */
837 len2 = strlen(ctx2->pathname);
839 return (len2 > len1
840 && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
841 && ctx2->pathname[len1] == '/');
844 static dav_error * dav_fs_open_stream(const dav_resource *resource,
845 dav_stream_mode mode,
846 dav_stream **stream)
848 apr_pool_t *p = resource->info->pool;
849 dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
850 apr_int32_t flags;
851 apr_status_t rv;
853 switch (mode) {
854 default:
855 flags = APR_READ | APR_BINARY;
856 break;
858 case DAV_MODE_WRITE_TRUNC:
859 flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
860 break;
861 case DAV_MODE_WRITE_SEEKABLE:
862 flags = APR_WRITE | APR_CREATE | APR_BINARY;
863 break;
866 ds->p = p;
867 ds->pathname = resource->info->pathname;
868 rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
869 if (rv != APR_SUCCESS) {
870 return dav_new_error(p, MAP_IO2HTTP(rv), 0,
871 "An error occurred while opening a resource.");
874 /* (APR registers cleanups for the fd with the pool) */
876 *stream = ds;
877 return NULL;
880 static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
882 apr_file_close(stream->f);
884 if (!commit) {
885 if (apr_file_remove(stream->pathname, stream->p) != APR_SUCCESS) {
886 /* ### use a better description? */
887 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
888 "There was a problem removing (rolling "
889 "back) the resource "
890 "when it was being closed.");
894 return NULL;
897 static dav_error * dav_fs_write_stream(dav_stream *stream,
898 const void *buf, apr_size_t bufsize)
900 apr_status_t status;
902 status = apr_file_write_full(stream->f, buf, bufsize, NULL);
903 if (APR_STATUS_IS_ENOSPC(status)) {
904 return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
905 "There is not enough storage to write to "
906 "this resource.");
908 else if (status != APR_SUCCESS) {
909 /* ### use something besides 500? */
910 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
911 "An error occurred while writing to a "
912 "resource.");
914 return NULL;
917 static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
919 if (apr_file_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
920 /* ### should check whether apr_file_seek set abs_pos was set to the
921 * correct position? */
922 /* ### use something besides 500? */
923 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
924 "Could not seek to specified position in the "
925 "resource.");
927 return NULL;
931 #if DEBUG_GET_HANDLER
933 /* only define set_headers() and deliver() for debug purposes */
936 static dav_error * dav_fs_set_headers(request_rec *r,
937 const dav_resource *resource)
939 /* ### this function isn't really used since we have a get_pathname */
940 if (!resource->exists)
941 return NULL;
943 /* make sure the proper mtime is in the request record */
944 ap_update_mtime(r, resource->info->finfo.mtime);
946 /* ### note that these use r->filename rather than <resource> */
947 ap_set_last_modified(r);
948 ap_set_etag(r);
950 /* we accept byte-ranges */
951 apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
953 /* set up the Content-Length header */
954 ap_set_content_length(r, resource->info->finfo.size);
956 /* ### how to set the content type? */
957 /* ### until this is resolved, the Content-Type header is busted */
959 return NULL;
962 static dav_error * dav_fs_deliver(const dav_resource *resource,
963 ap_filter_t *output)
965 apr_pool_t *pool = resource->pool;
966 apr_bucket_brigade *bb;
967 apr_file_t *fd;
968 apr_status_t status;
969 apr_bucket *bkt;
971 /* Check resource type */
972 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
973 && resource->type != DAV_RESOURCE_TYPE_VERSION
974 && resource->type != DAV_RESOURCE_TYPE_WORKING) {
975 return dav_new_error(pool, HTTP_CONFLICT, 0,
976 "Cannot GET this type of resource.");
978 if (resource->collection) {
979 return dav_new_error(pool, HTTP_CONFLICT, 0,
980 "There is no default response to GET for a "
981 "collection.");
984 if ((status = apr_file_open(&fd, resource->info->pathname,
985 APR_READ | APR_BINARY, 0,
986 pool)) != APR_SUCCESS) {
987 return dav_new_error(pool, HTTP_FORBIDDEN, 0,
988 "File permissions deny server access.");
991 bb = apr_brigade_create(pool, output->c->bucket_alloc);
993 apr_brigade_insert_file(bb, fd, 0, resource->info->finfo.size, pool);
995 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
996 APR_BRIGADE_INSERT_TAIL(bb, bkt);
998 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
999 return dav_new_error(pool, HTTP_FORBIDDEN, 0,
1000 "Could not write contents to filter.");
1003 return NULL;
1006 #endif /* DEBUG_GET_HANDLER */
1009 static dav_error * dav_fs_create_collection(dav_resource *resource)
1011 dav_resource_private *ctx = resource->info;
1012 apr_status_t status;
1014 status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
1015 if (APR_STATUS_IS_ENOSPC(status)) {
1016 return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
1017 "There is not enough storage to create "
1018 "this collection.");
1020 else if (APR_STATUS_IS_ENOENT(status)) {
1021 return dav_new_error(ctx->pool, HTTP_CONFLICT, 0,
1022 "Cannot create collection; intermediate "
1023 "collection does not exist.");
1025 else if (status != APR_SUCCESS) {
1026 /* ### refine this error message? */
1027 return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
1028 "Unable to create collection.");
1031 /* update resource state to show it exists as a collection */
1032 resource->exists = 1;
1033 resource->collection = 1;
1035 return NULL;
1038 static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
1039 int calltype)
1041 dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
1042 dav_resource_private *srcinfo = wres->resource->info;
1043 dav_resource_private *dstinfo = ctx->res_dst->info;
1044 dav_error *err = NULL;
1046 if (wres->resource->collection) {
1047 if (calltype == DAV_CALLTYPE_POSTFIX) {
1048 /* Postfix call for MOVE. delete the source dir.
1049 * Note: when copying, we do not enable the postfix-traversal.
1051 /* ### we are ignoring any error here; what should we do? */
1052 (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
1054 else {
1055 /* copy/move of a collection. Create the new, target collection */
1056 if (apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
1057 ctx->pool) != APR_SUCCESS) {
1058 /* ### assume it was a permissions problem */
1059 /* ### need a description here */
1060 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
1064 else {
1065 err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
1066 srcinfo->pathname, dstinfo->pathname,
1067 &srcinfo->finfo,
1068 ctx->res_dst->exists ? &dstinfo->finfo : NULL,
1069 &ctx->work_buf);
1070 /* ### push a higher-level description? */
1074 ** If we have a "not so bad" error, then it might need to go into a
1075 ** multistatus response.
1077 ** For a MOVE, it will always go into the multistatus. It could be
1078 ** that everything has been moved *except* for the root. Using a
1079 ** multistatus (with no errors for the other resources) will signify
1080 ** this condition.
1082 ** For a COPY, we are traversing in a prefix fashion. If the root fails,
1083 ** then we can just bail out now.
1085 if (err != NULL
1086 && !ap_is_HTTP_SERVER_ERROR(err->status)
1087 && (ctx->is_move
1088 || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
1089 /* ### use errno to generate DAV:responsedescription? */
1090 dav_add_response(wres, err->status, NULL);
1092 /* the error is in the multistatus now. do not stop the traversal. */
1093 return NULL;
1096 return err;
1099 static dav_error *dav_fs_copymove_resource(
1100 int is_move,
1101 const dav_resource *src,
1102 const dav_resource *dst,
1103 int depth,
1104 dav_response **response)
1106 dav_error *err = NULL;
1107 dav_buffer work_buf = { 0 };
1109 *response = NULL;
1111 /* if a collection, recursively copy/move it and its children,
1112 * including the state dirs
1114 if (src->collection) {
1115 dav_walk_params params = { 0 };
1116 dav_response *multi_status;
1118 params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
1119 params.func = dav_fs_copymove_walker;
1120 params.pool = src->info->pool;
1121 params.root = src;
1123 /* params.walk_ctx is managed by dav_fs_internal_walk() */
1125 /* postfix is needed for MOVE to delete source dirs */
1126 if (is_move)
1127 params.walk_type |= DAV_WALKTYPE_POSTFIX;
1129 /* note that we return the error OR the multistatus. never both */
1131 if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
1132 &multi_status)) != NULL) {
1133 /* on a "real" error, then just punt. nothing else to do. */
1134 return err;
1137 if ((*response = multi_status) != NULL) {
1138 /* some multistatus responses exist. wrap them in a 207 */
1139 return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
1140 "Error(s) occurred on some resources during "
1141 "the COPY/MOVE process.");
1144 return NULL;
1147 /* not a collection */
1148 if ((err = dav_fs_copymove_file(is_move, src->info->pool,
1149 src->info->pathname, dst->info->pathname,
1150 &src->info->finfo,
1151 dst->exists ? &dst->info->finfo : NULL,
1152 &work_buf)) != NULL) {
1153 /* ### push a higher-level description? */
1154 return err;
1157 /* copy/move properties as well */
1158 return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
1161 static dav_error * dav_fs_copy_resource(
1162 const dav_resource *src,
1163 dav_resource *dst,
1164 int depth,
1165 dav_response **response)
1167 dav_error *err;
1169 #if DAV_DEBUG
1170 if (src->hooks != dst->hooks) {
1172 ** ### strictly speaking, this is a design error; we should not
1173 ** ### have reached this point.
1175 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1176 "DESIGN ERROR: a mix of repositories "
1177 "was passed to copy_resource.");
1179 #endif
1181 if ((err = dav_fs_copymove_resource(0, src, dst, depth,
1182 response)) == NULL) {
1184 /* update state of destination resource to show it exists */
1185 dst->exists = 1;
1186 dst->collection = src->collection;
1189 return err;
1192 static dav_error * dav_fs_move_resource(
1193 dav_resource *src,
1194 dav_resource *dst,
1195 dav_response **response)
1197 dav_resource_private *srcinfo = src->info;
1198 dav_resource_private *dstinfo = dst->info;
1199 dav_error *err;
1200 int can_rename = 0;
1202 #if DAV_DEBUG
1203 if (src->hooks != dst->hooks) {
1205 ** ### strictly speaking, this is a design error; we should not
1206 ** ### have reached this point.
1208 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1209 "DESIGN ERROR: a mix of repositories "
1210 "was passed to move_resource.");
1212 #endif
1214 /* determine whether a simple rename will work.
1215 * Assume source exists, else we wouldn't get called.
1217 if (dstinfo->finfo.filetype != 0) {
1218 if (dstinfo->finfo.device == srcinfo->finfo.device) {
1219 /* target exists and is on the same device. */
1220 can_rename = 1;
1223 else {
1224 const char *dirpath;
1225 apr_finfo_t finfo;
1226 apr_status_t rv;
1228 /* destination does not exist, but the parent directory should,
1229 * so try it
1231 dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
1233 * XXX: If missing dev ... then what test?
1234 * Really need a try and failover for those platforms.
1237 rv = apr_stat(&finfo, dirpath, APR_FINFO_DEV, dstinfo->pool);
1238 if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
1239 && (finfo.valid & srcinfo->finfo.valid & APR_FINFO_DEV)
1240 && (finfo.device == srcinfo->finfo.device)) {
1241 can_rename = 1;
1245 /* if we can't simply rename, then do it the hard way... */
1246 if (!can_rename) {
1247 if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
1248 response)) == NULL) {
1249 /* update resource states */
1250 dst->exists = 1;
1251 dst->collection = src->collection;
1252 src->exists = 0;
1253 src->collection = 0;
1256 return err;
1259 /* a rename should work. do it, and move properties as well */
1261 /* no multistatus response */
1262 *response = NULL;
1264 /* ### APR has no rename? */
1265 if (apr_file_rename(srcinfo->pathname, dstinfo->pathname,
1266 srcinfo->pool) != APR_SUCCESS) {
1267 /* ### should have a better error than this. */
1268 return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1269 "Could not rename resource.");
1272 /* update resource states */
1273 dst->exists = 1;
1274 dst->collection = src->collection;
1275 src->exists = 0;
1276 src->collection = 0;
1278 if ((err = dav_fs_copymoveset(1, src->info->pool,
1279 src, dst, NULL)) == NULL) {
1280 /* no error. we're done. go ahead and return now. */
1281 return NULL;
1284 /* error occurred during properties move; try to put resource back */
1285 if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
1286 srcinfo->pool) != APR_SUCCESS) {
1287 /* couldn't put it back! */
1288 return dav_push_error(srcinfo->pool,
1289 HTTP_INTERNAL_SERVER_ERROR, 0,
1290 "The resource was moved, but a failure "
1291 "occurred during the move of its "
1292 "properties. The resource could not be "
1293 "restored to its original location. The "
1294 "server is now in an inconsistent state.",
1295 err);
1298 /* update resource states again */
1299 src->exists = 1;
1300 src->collection = dst->collection;
1301 dst->exists = 0;
1302 dst->collection = 0;
1304 /* resource moved back, but properties may be inconsistent */
1305 return dav_push_error(srcinfo->pool,
1306 HTTP_INTERNAL_SERVER_ERROR, 0,
1307 "The resource was moved, but a failure "
1308 "occurred during the move of its properties. "
1309 "The resource was moved back to its original "
1310 "location, but its properties may have been "
1311 "partially moved. The server may be in an "
1312 "inconsistent state.",
1313 err);
1316 static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
1318 dav_resource_private *info = wres->resource->info;
1320 /* do not attempt to remove a null resource,
1321 * or a collection with children
1323 if (wres->resource->exists &&
1324 (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
1325 /* try to remove the resource */
1326 apr_status_t result;
1328 result = wres->resource->collection
1329 ? apr_dir_remove(info->pathname, wres->pool)
1330 : apr_file_remove(info->pathname, wres->pool);
1333 ** If an error occurred, then add it to multistatus response.
1334 ** Note that we add it for the root resource, too. It is quite
1335 ** possible to delete the whole darn tree, yet fail on the root.
1337 ** (also: remember we are deleting via a postfix traversal)
1339 if (result != APR_SUCCESS) {
1340 /* ### assume there is a permissions problem */
1342 /* ### use errno to generate DAV:responsedescription? */
1343 dav_add_response(wres, HTTP_FORBIDDEN, NULL);
1347 return NULL;
1350 static dav_error * dav_fs_remove_resource(dav_resource *resource,
1351 dav_response **response)
1353 dav_resource_private *info = resource->info;
1355 *response = NULL;
1357 /* if a collection, recursively remove it and its children,
1358 * including the state dirs
1360 if (resource->collection) {
1361 dav_walk_params params = { 0 };
1362 dav_error *err = NULL;
1363 dav_response *multi_status;
1365 params.walk_type = (DAV_WALKTYPE_NORMAL
1366 | DAV_WALKTYPE_HIDDEN
1367 | DAV_WALKTYPE_POSTFIX);
1368 params.func = dav_fs_delete_walker;
1369 params.pool = info->pool;
1370 params.root = resource;
1372 if ((err = dav_fs_walk(&params, DAV_INFINITY,
1373 &multi_status)) != NULL) {
1374 /* on a "real" error, then just punt. nothing else to do. */
1375 return err;
1378 if ((*response = multi_status) != NULL) {
1379 /* some multistatus responses exist. wrap them in a 207 */
1380 return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
1381 "Error(s) occurred on some resources during "
1382 "the deletion process.");
1385 /* no errors... update resource state */
1386 resource->exists = 0;
1387 resource->collection = 0;
1389 return NULL;
1392 /* not a collection; remove the file and its properties */
1393 if (apr_file_remove(info->pathname, info->pool) != APR_SUCCESS) {
1394 /* ### put a description in here */
1395 return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
1398 /* update resource state */
1399 resource->exists = 0;
1400 resource->collection = 0;
1402 /* remove properties and return its result */
1403 return dav_fs_deleteset(info->pool, resource);
1406 /* ### move this to dav_util? */
1407 /* Walk recursively down through directories, *
1408 * including lock-null resources as we go. */
1409 static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
1411 const dav_walk_params *params = fsctx->params;
1412 apr_pool_t *pool = params->pool;
1413 dav_error *err = NULL;
1414 int isdir = fsctx->res1.collection;
1415 apr_finfo_t dirent;
1416 apr_dir_t *dirp;
1418 /* ensure the context is prepared properly, then call the func */
1419 err = (*params->func)(&fsctx->wres,
1420 isdir
1421 ? DAV_CALLTYPE_COLLECTION
1422 : DAV_CALLTYPE_MEMBER);
1423 if (err != NULL) {
1424 return err;
1427 if (depth == 0 || !isdir) {
1428 return NULL;
1431 /* put a trailing slash onto the directory, in preparation for appending
1432 * files to it as we discovery them within the directory */
1433 dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
1434 fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
1435 fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
1437 /* if a secondary path is present, then do that, too */
1438 if (fsctx->path2.buf != NULL) {
1439 dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
1440 fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
1441 fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
1444 /* Note: the URI should ALREADY have a trailing "/" */
1446 /* for this first pass of files, all resources exist */
1447 fsctx->res1.exists = 1;
1449 /* a file is the default; we'll adjust if we hit a directory */
1450 fsctx->res1.collection = 0;
1451 fsctx->res2.collection = 0;
1453 /* open and scan the directory */
1454 if ((apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
1455 /* ### need a better error */
1456 return dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1458 while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
1459 apr_size_t len;
1460 apr_status_t status;
1462 len = strlen(dirent.name);
1464 /* avoid recursing into our current, parent, or state directories */
1465 if (dirent.name[0] == '.'
1466 && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
1467 continue;
1470 if (params->walk_type & DAV_WALKTYPE_AUTH) {
1471 /* ### need to authorize each file */
1472 /* ### example: .htaccess is normally configured to fail auth */
1474 /* stuff in the state directory is never authorized! */
1475 if (!strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1476 continue;
1479 /* skip the state dir unless a HIDDEN is performed */
1480 if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
1481 && !strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1482 continue;
1485 /* append this file onto the path buffer (copy null term) */
1486 dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
1488 status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
1489 DAV_FINFO_MASK, pool);
1490 if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
1491 /* woah! where'd it go? */
1492 /* ### should have a better error here */
1493 err = dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1494 break;
1497 /* copy the file to the URI, too. NOTE: we will pad an extra byte
1498 for the trailing slash later. */
1499 dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
1501 /* if there is a secondary path, then do that, too */
1502 if (fsctx->path2.buf != NULL) {
1503 dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
1506 /* set up the (internal) pathnames for the two resources */
1507 fsctx->info1.pathname = fsctx->path1.buf;
1508 fsctx->info2.pathname = fsctx->path2.buf;
1510 /* set up the URI for the current resource */
1511 fsctx->res1.uri = fsctx->uri_buf.buf;
1513 /* ### for now, only process regular files (e.g. skip symlinks) */
1514 if (fsctx->info1.finfo.filetype == APR_REG) {
1515 /* call the function for the specified dir + file */
1516 if ((err = (*params->func)(&fsctx->wres,
1517 DAV_CALLTYPE_MEMBER)) != NULL) {
1518 /* ### maybe add a higher-level description? */
1519 break;
1522 else if (fsctx->info1.finfo.filetype == APR_DIR) {
1523 apr_size_t save_path_len = fsctx->path1.cur_len;
1524 apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
1525 apr_size_t save_path2_len = fsctx->path2.cur_len;
1527 /* adjust length to incorporate the subdir name */
1528 fsctx->path1.cur_len += len;
1529 fsctx->path2.cur_len += len;
1531 /* adjust URI length to incorporate subdir and a slash */
1532 fsctx->uri_buf.cur_len += len + 1;
1533 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
1534 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
1536 /* switch over to a collection */
1537 fsctx->res1.collection = 1;
1538 fsctx->res2.collection = 1;
1540 /* recurse on the subdir */
1541 /* ### don't always want to quit on error from single child */
1542 if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
1543 /* ### maybe add a higher-level description? */
1544 break;
1547 /* put the various information back */
1548 fsctx->path1.cur_len = save_path_len;
1549 fsctx->path2.cur_len = save_path2_len;
1550 fsctx->uri_buf.cur_len = save_uri_len;
1552 fsctx->res1.collection = 0;
1553 fsctx->res2.collection = 0;
1555 /* assert: res1.exists == 1 */
1559 /* ### check the return value of this? */
1560 apr_dir_close(dirp);
1562 if (err != NULL)
1563 return err;
1565 if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
1566 apr_size_t offset = 0;
1568 /* null terminate the directory name */
1569 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
1571 /* Include any lock null resources found in this collection */
1572 fsctx->res1.collection = 1;
1573 if ((err = dav_fs_get_locknull_members(&fsctx->res1,
1574 &fsctx->locknull_buf)) != NULL) {
1575 /* ### maybe add a higher-level description? */
1576 return err;
1579 /* put a slash back on the end of the directory */
1580 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
1582 /* these are all non-existant (files) */
1583 fsctx->res1.exists = 0;
1584 fsctx->res1.collection = 0;
1585 memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
1587 while (offset < fsctx->locknull_buf.cur_len) {
1588 apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
1589 dav_lock *locks = NULL;
1592 ** Append the locknull file to the paths and the URI. Note that
1593 ** we don't have to pad the URI for a slash since a locknull
1594 ** resource is not a collection.
1596 dav_buffer_place_mem(pool, &fsctx->path1,
1597 fsctx->locknull_buf.buf + offset, len + 1, 0);
1598 dav_buffer_place_mem(pool, &fsctx->uri_buf,
1599 fsctx->locknull_buf.buf + offset, len + 1, 0);
1600 if (fsctx->path2.buf != NULL) {
1601 dav_buffer_place_mem(pool, &fsctx->path2,
1602 fsctx->locknull_buf.buf + offset,
1603 len + 1, 0);
1606 /* set up the (internal) pathnames for the two resources */
1607 fsctx->info1.pathname = fsctx->path1.buf;
1608 fsctx->info2.pathname = fsctx->path2.buf;
1610 /* set up the URI for the current resource */
1611 fsctx->res1.uri = fsctx->uri_buf.buf;
1614 ** To prevent a PROPFIND showing an expired locknull
1615 ** resource, query the lock database to force removal
1616 ** of both the lock entry and .locknull, if necessary..
1617 ** Sure, the query in PROPFIND would do this.. after
1618 ** the locknull resource was already included in the
1619 ** return.
1621 ** NOTE: we assume the caller has opened the lock database
1622 ** if they have provided DAV_WALKTYPE_LOCKNULL.
1624 /* ### we should also look into opening it read-only and
1625 ### eliding timed-out items from the walk, yet leaving
1626 ### them in the locknull database until somebody opens
1627 ### the thing writable.
1629 /* ### probably ought to use has_locks. note the problem
1630 ### mentioned above, though... we would traverse this as
1631 ### a locknull, but then a PROPFIND would load the lock
1632 ### info, causing a timeout and the locks would not be
1633 ### reported. Therefore, a null resource would be returned
1634 ### in the PROPFIND.
1636 ### alternative: just load unresolved locks. any direct
1637 ### locks will be timed out (correct). any indirect will
1638 ### not (correct; consider if a parent timed out -- the
1639 ### timeout routines do not walk and remove indirects;
1640 ### even the resolve func would probably fail when it
1641 ### tried to find a timed-out direct lock).
1643 if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
1644 &locks)) != NULL) {
1645 /* ### maybe add a higher-level description? */
1646 return err;
1649 /* call the function for the specified dir + file */
1650 if (locks != NULL &&
1651 (err = (*params->func)(&fsctx->wres,
1652 DAV_CALLTYPE_LOCKNULL)) != NULL) {
1653 /* ### maybe add a higher-level description? */
1654 return err;
1657 offset += len + 1;
1660 /* reset the exists flag */
1661 fsctx->res1.exists = 1;
1664 if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
1665 /* replace the dirs' trailing slashes with null terms */
1666 fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
1667 fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
1668 if (fsctx->path2.buf != NULL) {
1669 fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
1672 /* this is a collection which exists */
1673 fsctx->res1.collection = 1;
1675 return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
1678 return NULL;
1681 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
1682 int depth, int is_move,
1683 const dav_resource *root_dst,
1684 dav_response **response)
1686 dav_fs_walker_context fsctx = { 0 };
1687 dav_error *err;
1688 dav_fs_copymove_walk_ctx cm_ctx = { 0 };
1690 #if DAV_DEBUG
1691 if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
1692 && params->lockdb == NULL) {
1693 return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1694 "DESIGN ERROR: walker called to walk locknull "
1695 "resources, but a lockdb was not provided.");
1697 #endif
1699 fsctx.params = params;
1700 fsctx.wres.walk_ctx = params->walk_ctx;
1701 fsctx.wres.pool = params->pool;
1703 /* ### zero out versioned, working, baselined? */
1705 fsctx.res1 = *params->root;
1706 fsctx.res1.pool = params->pool;
1708 fsctx.res1.info = &fsctx.info1;
1709 fsctx.info1 = *params->root->info;
1711 /* the pathname is stored in the path1 buffer */
1712 dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
1713 fsctx.info1.pathname = fsctx.path1.buf;
1715 if (root_dst != NULL) {
1716 /* internal call from the COPY/MOVE code. set it up. */
1718 fsctx.wres.walk_ctx = &cm_ctx;
1719 cm_ctx.is_move = is_move;
1720 cm_ctx.res_dst = &fsctx.res2;
1721 cm_ctx.root = params->root;
1722 cm_ctx.pool = params->pool;
1724 fsctx.res2 = *root_dst;
1725 fsctx.res2.exists = 0;
1726 fsctx.res2.collection = 0;
1727 fsctx.res2.uri = NULL; /* we don't track this */
1728 fsctx.res2.pool = params->pool;
1730 fsctx.res2.info = &fsctx.info2;
1731 fsctx.info2 = *root_dst->info;
1733 /* res2 does not exist -- clear its finfo structure */
1734 memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
1736 /* the pathname is stored in the path2 buffer */
1737 dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
1738 fsctx.info2.pathname = fsctx.path2.buf;
1741 /* prep the URI buffer */
1742 dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
1744 /* if we have a directory, then ensure the URI has a trailing "/" */
1745 if (fsctx.res1.collection
1746 && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
1748 /* this will fall into the pad area */
1749 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
1750 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
1753 /* the current resource's URI is stored in the uri_buf buffer */
1754 fsctx.res1.uri = fsctx.uri_buf.buf;
1756 /* point the callback's resource at our structure */
1757 fsctx.wres.resource = &fsctx.res1;
1759 /* always return the error, and any/all multistatus responses */
1760 err = dav_fs_walker(&fsctx, depth);
1761 *response = fsctx.wres.response;
1762 return err;
1765 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
1766 dav_response **response)
1768 /* always return the error, and any/all multistatus responses */
1769 return dav_fs_internal_walk(params, depth, 0, NULL, response);
1772 /* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
1773 * for file path.
1774 * ### do we need to return weak tags sometimes?
1776 static const char *dav_fs_getetag(const dav_resource *resource)
1778 dav_resource_private *ctx = resource->info;
1780 if (!resource->exists)
1781 return apr_pstrdup(ctx->pool, "");
1783 if (ctx->finfo.filetype != 0) {
1784 return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%"
1785 APR_UINT64_T_HEX_FMT "-%" APR_UINT64_T_HEX_FMT "\"",
1786 (apr_uint64_t) ctx->finfo.inode,
1787 (apr_uint64_t) ctx->finfo.size,
1788 (apr_uint64_t) ctx->finfo.mtime);
1791 return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"",
1792 (apr_uint64_t) ctx->finfo.mtime);
1795 static const dav_hooks_repository dav_hooks_repository_fs =
1797 DEBUG_GET_HANDLER, /* normally: special GET handling not required */
1798 dav_fs_get_resource,
1799 dav_fs_get_parent_resource,
1800 dav_fs_is_same_resource,
1801 dav_fs_is_parent_resource,
1802 dav_fs_open_stream,
1803 dav_fs_close_stream,
1804 dav_fs_write_stream,
1805 dav_fs_seek_stream,
1806 #if DEBUG_GET_HANDLER
1807 dav_fs_set_headers,
1808 dav_fs_deliver,
1809 #else
1810 NULL,
1811 NULL,
1812 #endif
1813 dav_fs_create_collection,
1814 dav_fs_copy_resource,
1815 dav_fs_move_resource,
1816 dav_fs_remove_resource,
1817 dav_fs_walk,
1818 dav_fs_getetag,
1821 static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
1822 int propid, dav_prop_insert what,
1823 apr_text_header *phdr)
1825 const char *value;
1826 const char *s;
1827 apr_pool_t *p = resource->info->pool;
1828 const dav_liveprop_spec *info;
1829 int global_ns;
1831 /* an HTTP-date can be 29 chars plus a null term */
1832 /* a 64-bit size can be 20 chars plus a null term */
1833 char buf[DAV_TIMEBUF_SIZE];
1836 ** None of FS provider properties are defined if the resource does not
1837 ** exist. Just bail for this case.
1839 ** Even though we state that the FS properties are not defined, the
1840 ** client cannot store dead values -- we deny that thru the is_writable
1841 ** hook function.
1843 if (!resource->exists)
1844 return DAV_PROP_INSERT_NOTDEF;
1846 switch (propid) {
1847 case DAV_PROPID_creationdate:
1849 ** Closest thing to a creation date. since we don't actually
1850 ** perform the operations that would modify ctime (after we
1851 ** create the file), then we should be pretty safe here.
1853 dav_format_time(DAV_STYLE_ISO8601,
1854 resource->info->finfo.ctime,
1855 buf);
1856 value = buf;
1857 break;
1859 case DAV_PROPID_getcontentlength:
1860 /* our property, but not defined on collection resources */
1861 if (resource->collection)
1862 return DAV_PROP_INSERT_NOTDEF;
1864 (void) sprintf(buf, "%" APR_OFF_T_FMT, resource->info->finfo.size);
1865 value = buf;
1866 break;
1868 case DAV_PROPID_getetag:
1869 value = dav_fs_getetag(resource);
1870 break;
1872 case DAV_PROPID_getlastmodified:
1873 dav_format_time(DAV_STYLE_RFC822,
1874 resource->info->finfo.mtime,
1875 buf);
1876 value = buf;
1877 break;
1879 case DAV_PROPID_FS_executable:
1880 /* our property, but not defined on collection resources */
1881 if (resource->collection)
1882 return DAV_PROP_INSERT_NOTDEF;
1884 /* our property, but not defined on this platform */
1885 if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
1886 return DAV_PROP_INSERT_NOTDEF;
1888 /* the files are "ours" so we only need to check owner exec privs */
1889 if (resource->info->finfo.protection & APR_UEXECUTE)
1890 value = "T";
1891 else
1892 value = "F";
1893 break;
1895 default:
1896 /* ### what the heck was this property? */
1897 return DAV_PROP_INSERT_NOTDEF;
1900 /* assert: value != NULL */
1902 /* get the information and global NS index for the property */
1903 global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1905 /* assert: info != NULL && info->name != NULL */
1907 /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
1909 if (what == DAV_PROP_INSERT_VALUE) {
1910 s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
1911 global_ns, info->name, value, global_ns, info->name);
1913 else if (what == DAV_PROP_INSERT_NAME) {
1914 s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
1916 else {
1917 /* assert: what == DAV_PROP_INSERT_SUPPORTED */
1918 s = apr_psprintf(p,
1919 "<D:supported-live-property D:name=\"%s\" "
1920 "D:namespace=\"%s\"/>" DEBUG_CR,
1921 info->name, dav_fs_namespace_uris[info->ns]);
1923 apr_text_append(p, phdr, s);
1925 /* we inserted what was asked for */
1926 return what;
1929 static int dav_fs_is_writable(const dav_resource *resource, int propid)
1931 const dav_liveprop_spec *info;
1933 #ifdef DAV_FS_HAS_EXECUTABLE
1934 /* if we have the executable property, and this isn't a collection,
1935 then the property is writable. */
1936 if (propid == DAV_PROPID_FS_executable && !resource->collection)
1937 return 1;
1938 #endif
1940 (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1941 return info->is_writable;
1944 static dav_error *dav_fs_patch_validate(const dav_resource *resource,
1945 const apr_xml_elem *elem,
1946 int operation,
1947 void **context,
1948 int *defer_to_dead)
1950 const apr_text *cdata;
1951 const apr_text *f_cdata;
1952 char value;
1953 dav_elem_private *priv = elem->priv;
1955 if (priv->propid != DAV_PROPID_FS_executable) {
1956 *defer_to_dead = 1;
1957 return NULL;
1960 if (operation == DAV_PROP_OP_DELETE) {
1961 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1962 "The 'executable' property cannot be removed.");
1965 cdata = elem->first_cdata.first;
1967 /* ### hmm. this isn't actually looking at all the possible text items */
1968 f_cdata = elem->first_child == NULL
1969 ? NULL
1970 : elem->first_child->following_cdata.first;
1972 /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
1974 if (cdata == NULL) {
1975 if (f_cdata == NULL) {
1976 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1977 "The 'executable' property expects a single "
1978 "character, valued 'T' or 'F'. There was no "
1979 "value submitted.");
1981 cdata = f_cdata;
1983 else if (f_cdata != NULL)
1984 goto too_long;
1986 if (cdata->next != NULL || strlen(cdata->text) != 1)
1987 goto too_long;
1989 value = cdata->text[0];
1990 if (value != 'T' && value != 'F') {
1991 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1992 "The 'executable' property expects a single "
1993 "character, valued 'T' or 'F'. The value "
1994 "submitted is invalid.");
1997 *context = (void *)((long)(value == 'T'));
1999 return NULL;
2001 too_long:
2002 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
2003 "The 'executable' property expects a single "
2004 "character, valued 'T' or 'F'. The value submitted "
2005 "has too many characters.");
2009 static dav_error *dav_fs_patch_exec(const dav_resource *resource,
2010 const apr_xml_elem *elem,
2011 int operation,
2012 void *context,
2013 dav_liveprop_rollback **rollback_ctx)
2015 long value = context != NULL;
2016 apr_fileperms_t perms = resource->info->finfo.protection;
2017 long old_value = (perms & APR_UEXECUTE) != 0;
2019 /* assert: prop == executable. operation == SET. */
2021 /* don't do anything if there is no change. no rollback info either. */
2022 /* DBG2("new value=%d (old=%d)", value, old_value); */
2023 if (value == old_value)
2024 return NULL;
2026 perms &= ~APR_UEXECUTE;
2027 if (value)
2028 perms |= APR_UEXECUTE;
2030 if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2031 return dav_new_error(resource->info->pool,
2032 HTTP_INTERNAL_SERVER_ERROR, 0,
2033 "Could not set the executable flag of the "
2034 "target resource.");
2037 /* update the resource and set up the rollback context */
2038 resource->info->finfo.protection = perms;
2039 *rollback_ctx = (dav_liveprop_rollback *)old_value;
2041 return NULL;
2044 static void dav_fs_patch_commit(const dav_resource *resource,
2045 int operation,
2046 void *context,
2047 dav_liveprop_rollback *rollback_ctx)
2049 /* nothing to do */
2052 static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
2053 int operation,
2054 void *context,
2055 dav_liveprop_rollback *rollback_ctx)
2057 apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
2058 int value = rollback_ctx != NULL;
2060 /* assert: prop == executable. operation == SET. */
2062 /* restore the executable bit */
2063 if (value)
2064 perms |= APR_UEXECUTE;
2066 if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2067 return dav_new_error(resource->info->pool,
2068 HTTP_INTERNAL_SERVER_ERROR, 0,
2069 "After a failure occurred, the resource's "
2070 "executable flag could not be restored.");
2073 /* restore the resource's state */
2074 resource->info->finfo.protection = perms;
2076 return NULL;
2080 static const dav_hooks_liveprop dav_hooks_liveprop_fs =
2082 dav_fs_insert_prop,
2083 dav_fs_is_writable,
2084 dav_fs_namespace_uris,
2085 dav_fs_patch_validate,
2086 dav_fs_patch_exec,
2087 dav_fs_patch_commit,
2088 dav_fs_patch_rollback
2091 static const dav_provider dav_fs_provider =
2093 &dav_hooks_repository_fs,
2094 &dav_hooks_db_dbm,
2095 &dav_hooks_locks_fs,
2096 NULL, /* vsn */
2097 NULL, /* binding */
2098 NULL, /* search */
2100 NULL /* ctx */
2103 void dav_fs_gather_propsets(apr_array_header_t *uris)
2105 #ifdef DAV_FS_HAS_EXECUTABLE
2106 *(const char **)apr_array_push(uris) =
2107 "<http://apache.org/dav/propset/fs/1>";
2108 #endif
2111 int dav_fs_find_liveprop(const dav_resource *resource,
2112 const char *ns_uri, const char *name,
2113 const dav_hooks_liveprop **hooks)
2115 /* don't try to find any liveprops if this isn't "our" resource */
2116 if (resource->hooks != &dav_hooks_repository_fs)
2117 return 0;
2118 return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
2121 void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
2122 dav_prop_insert what, apr_text_header *phdr)
2124 /* don't insert any liveprops if this isn't "our" resource */
2125 if (resource->hooks != &dav_hooks_repository_fs)
2126 return;
2128 if (!resource->exists) {
2129 /* a lock-null resource */
2131 ** ### technically, we should insert empty properties. dunno offhand
2132 ** ### what part of the spec said this, but it was essentially thus:
2133 ** ### "the properties should be defined, but may have no value".
2135 return;
2138 (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
2139 what, phdr);
2140 (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
2141 what, phdr);
2142 (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
2143 what, phdr);
2144 (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
2145 what, phdr);
2147 #ifdef DAV_FS_HAS_EXECUTABLE
2148 /* Only insert this property if it is defined for this platform. */
2149 (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
2150 what, phdr);
2151 #endif
2153 /* ### we know the others aren't defined as liveprops */
2156 void dav_fs_register(apr_pool_t *p)
2158 /* register the namespace URIs */
2159 dav_register_liveprop_group(p, &dav_fs_liveprop_group);
2161 /* register the repository provider */
2162 dav_register_provider(p, "filesystem", &dav_fs_provider);