1 /* Copyright 2000-2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 ** DAV filesystem-based repository provider
21 #include "apr_file_io.h"
22 #include "apr_strings.h"
23 #include "apr_buckets.h"
26 #include <stdio.h> /* for sprintf() */
31 #include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
32 #include "http_request.h" /* for ap_update_mtime() */
38 /* to assist in debugging mod_dav's GET handling */
39 #define DEBUG_GET_HANDLER 0
41 #define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
43 /* context needed to identify a resource */
44 struct dav_resource_private {
45 apr_pool_t *pool; /* memory storage pool associated with request */
46 const char *pathname; /* full pathname to resource */
47 apr_finfo_t finfo; /* filesystem info */
50 /* private context for doing a filesystem walk */
52 /* the input walk parameters */
53 const dav_walk_params *params;
55 /* reused as we walk */
56 dav_walk_resource wres;
59 dav_resource_private info1;
63 /* MOVE/COPY need a secondary path */
65 dav_resource_private info2;
68 dav_buffer locknull_buf;
70 } dav_fs_walker_context;
73 int is_move; /* is this a MOVE? */
74 dav_buffer work_buf; /* handy buffer for copymove_file() */
76 /* CALLBACK: this is a secondary resource managed specially for us */
77 const dav_resource *res_dst;
79 /* copied from dav_walk_params (they are invariant across the walk) */
80 const dav_resource *root;
83 } dav_fs_copymove_walk_ctx;
85 /* an internal WALKTYPE to walk hidden files (the .DAV directory) */
86 #define DAV_WALKTYPE_HIDDEN 0x4000
88 /* an internal WALKTYPE to call collections (again) after their contents */
89 #define DAV_WALKTYPE_POSTFIX 0x8000
91 #define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
94 /* pull this in from the other source file */
95 extern const dav_hooks_locks dav_hooks_locks_fs;
97 /* forward-declare the hook structures */
98 static const dav_hooks_repository dav_hooks_repository_fs;
99 static const dav_hooks_liveprop dav_hooks_liveprop_fs;
102 ** The namespace URIs that we use. This list and the enumeration must
105 static const char * const dav_fs_namespace_uris[] =
108 "http://apache.org/dav/props/",
113 DAV_FS_URI_DAV, /* the DAV: namespace URI */
114 DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
118 ** Does this platform support an executable flag?
120 ** ### need a way to portably abstract this query
123 #define DAV_FS_HAS_EXECUTABLE
127 ** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
129 #define DAV_PROPID_FS_executable 1
131 static const dav_liveprop_spec dav_fs_props[] =
133 /* standard DAV properties */
137 DAV_PROPID_creationdate,
143 DAV_PROPID_getcontentlength,
155 DAV_PROPID_getlastmodified,
159 /* our custom properties */
163 DAV_PROPID_FS_executable,
164 0 /* handled special in dav_fs_is_writable */
170 static const dav_liveprop_group dav_fs_liveprop_group =
173 dav_fs_namespace_uris,
174 &dav_hooks_liveprop_fs
178 /* define the dav_stream structure for our use */
182 const char *pathname; /* we may need to remove it at close time */
185 /* returns an appropriate HTTP status code given an APR status code for a
186 * failed I/O operation. ### use something besides 500? */
187 #define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
188 HTTP_INTERNAL_SERVER_ERROR)
190 /* forward declaration for internal treewalkers */
191 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
192 dav_response **response);
193 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
194 int depth, int is_move,
195 const dav_resource *root_dst,
196 dav_response **response);
198 /* --------------------------------------------------------------------
200 ** PRIVATE REPOSITORY FUNCTIONS
202 apr_pool_t *dav_fs_pool(const dav_resource *resource)
204 return resource->info->pool;
207 const char *dav_fs_pathname(const dav_resource *resource)
209 return resource->info->pathname;
212 dav_error * dav_fs_dir_file_name(
213 const dav_resource *resource,
214 const char **dirpath_p,
215 const char **fname_p)
217 dav_resource_private *ctx = resource->info;
219 if (resource->collection) {
220 *dirpath_p = ctx->pathname;
225 const char *testpath, *rootpath;
226 char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
227 apr_size_t dirlen = strlen(dirpath);
228 apr_status_t rv = APR_SUCCESS;
232 rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
235 /* remove trailing slash from dirpath, unless it's a root path
237 if ((rv == APR_SUCCESS && testpath && *testpath)
238 || rv == APR_ERELATIVE) {
239 if (dirpath[dirlen - 1] == '/') {
240 dirpath[dirlen - 1] = '\0';
244 /* ###: Looks like a response could be appropriate
246 * APR_SUCCESS here tells us the dir is a root
247 * APR_ERELATIVE told us we had no root (ok)
248 * APR_EINCOMPLETE an incomplete testpath told us
249 * there was no -file- name here!
250 * APR_EBADPATH or other errors tell us this file
251 * path is undecipherable
254 if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
255 *dirpath_p = dirpath;
257 *fname_p = ctx->pathname + dirlen;
260 return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
261 "An incomplete/bad path was found in "
262 "dav_fs_dir_file_name.");
269 /* Note: picked up from ap_gm_timestr_822() */
270 /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
271 static void dav_format_time(int style, apr_time_t sec, char *buf)
275 /* ### what to do if fails? */
276 (void) apr_time_exp_gmt(&tms, sec);
278 if (style == DAV_STYLE_ISO8601) {
279 /* ### should we use "-00:00" instead of "Z" ?? */
281 /* 20 chars plus null term */
282 sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
283 tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
284 tms.tm_hour, tms.tm_min, tms.tm_sec);
288 /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
290 /* 29 chars plus null term */
292 "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
293 apr_day_snames[tms.tm_wday],
294 tms.tm_mday, apr_month_snames[tms.tm_mon],
296 tms.tm_hour, tms.tm_min, tms.tm_sec);
299 /* Copy or move src to dst; src_finfo is used to propagate permissions
300 * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
302 static dav_error * dav_fs_copymove_file(
307 const apr_finfo_t *src_finfo,
308 const apr_finfo_t *dst_finfo,
311 dav_buffer work_buf = { 0 };
312 apr_file_t *inf = NULL;
313 apr_file_t *outf = NULL;
315 apr_fileperms_t perms;
320 /* Determine permissions to use for destination */
321 if (src_finfo && src_finfo->valid & APR_FINFO_PROT
322 && src_finfo->protection & APR_UEXECUTE) {
323 if (dst_finfo != NULL) {
324 /* chmod it if it already exist */
325 if (apr_file_perms_set(dst, src_finfo->protection)) {
326 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
327 "Could not set permissions on destination");
331 perms = src_finfo->protection;
335 perms = APR_OS_DEFAULT;
338 dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
340 if ((apr_file_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p))
342 /* ### use something besides 500? */
343 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
344 "Could not open file for reading");
347 /* ### do we need to deal with the umask? */
348 status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
349 | APR_BINARY, perms, p);
350 if (status != APR_SUCCESS) {
353 return dav_new_error(p, MAP_IO2HTTP(status), 0,
354 "Could not open file for writing");
358 apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
360 status = apr_file_read(inf, pbuf->buf, &len);
361 if (status != APR_SUCCESS && status != APR_EOF) {
363 apr_file_close(outf);
365 if (apr_file_remove(dst, p) != APR_SUCCESS) {
366 /* ### ACK! Inconsistent state... */
368 /* ### use something besides 500? */
369 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
370 "Could not delete output after read "
371 "failure. Server is now in an "
372 "inconsistent state.");
375 /* ### use something besides 500? */
376 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
377 "Could not read input file");
380 if (status == APR_EOF)
383 /* write any bytes that were read */
384 status = apr_file_write_full(outf, pbuf->buf, len, NULL);
385 if (status != APR_SUCCESS) {
387 apr_file_close(outf);
389 if (apr_file_remove(dst, p) != APR_SUCCESS) {
390 /* ### ACK! Inconsistent state... */
392 /* ### use something besides 500? */
393 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
394 "Could not delete output after write "
395 "failure. Server is now in an "
396 "inconsistent state.");
399 return dav_new_error(p, MAP_IO2HTTP(status), 0,
400 "Could not write output file");
405 apr_file_close(outf);
407 if (is_move && apr_file_remove(src, p) != APR_SUCCESS) {
409 int save_errno = errno; /* save the errno that got us here */
411 if (apr_file_remove(dst, p) != APR_SUCCESS) {
412 /* ### ACK. this creates an inconsistency. do more!? */
414 /* ### use something besides 500? */
415 /* Note that we use the latest errno */
416 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
417 "Could not remove source or destination "
418 "file. Server is now in an inconsistent "
422 /* ### use something besides 500? */
423 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
424 "Could not remove source file after move. "
425 "Destination was removed to ensure consistency.");
426 err->save_errno = save_errno;
433 /* copy/move a file from within a state dir to another state dir */
434 /* ### need more buffers to replace the pool argument */
435 static dav_error * dav_fs_copymove_state(
438 const char *src_dir, const char *src_file,
439 const char *dst_dir, const char *dst_file,
442 apr_finfo_t src_finfo; /* finfo for source file */
443 apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
448 /* build the propset pathname for the source file */
449 src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
451 /* the source file doesn't exist */
452 rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
453 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
457 /* build the pathname for the destination state dir */
458 dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
460 /* ### do we need to deal with the umask? */
462 /* ensure that it exists */
463 rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
464 if (rv != APR_SUCCESS) {
465 if (!APR_STATUS_IS_EEXIST(rv)) {
466 /* ### use something besides 500? */
467 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
468 "Could not create internal state directory");
472 /* get info about the state directory */
473 rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
474 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
475 /* Ack! Where'd it go? */
476 /* ### use something besides 500? */
477 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
478 "State directory disappeared");
481 /* The mkdir() may have failed because a *file* exists there already */
482 if (dst_state_finfo.filetype != APR_DIR) {
483 /* ### try to recover by deleting this file? (and mkdir again) */
484 /* ### use something besides 500? */
485 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
486 "State directory is actually a file");
489 /* append the target file to the state directory pathname */
490 dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
492 /* copy/move the file now */
493 if (is_move && src_finfo.device == dst_state_finfo.device) {
494 /* simple rename is possible since it is on the same device */
495 if (apr_file_rename(src, dst, p) != APR_SUCCESS) {
496 /* ### use something besides 500? */
497 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
498 "Could not move state file.");
503 /* gotta copy (and delete) */
504 return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
510 static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
511 const dav_resource *src,
512 const dav_resource *dst,
516 const char *src_file;
517 const char *src_state1;
518 const char *src_state2;
520 const char *dst_file;
521 const char *dst_state1;
522 const char *dst_state2;
525 /* Get directory and filename for resources */
526 /* ### should test these result values... */
527 (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
528 (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
530 /* Get the corresponding state files for each resource */
531 dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
532 dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
534 if ((src_state2 != NULL && dst_state2 == NULL) ||
535 (src_state2 == NULL && dst_state2 != NULL)) {
536 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
537 "DESIGN ERROR: dav_dbm_get_statefiles() "
538 "returned inconsistent results.");
542 err = dav_fs_copymove_state(is_move, p,
547 if (err == NULL && src_state2 != NULL) {
548 err = dav_fs_copymove_state(is_move, p,
554 /* ### CRAP. inconsistency. */
555 /* ### should perform some cleanup at the target if we still
556 ### have the original files */
558 /* Change the error to reflect the bad server state. */
559 err->status = HTTP_INTERNAL_SERVER_ERROR;
561 "Could not fully copy/move the properties. "
562 "The server is now in an inconsistent state.";
569 static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
575 const char *pathname;
578 /* Get directory, filename, and state-file names for the resource */
579 /* ### should test this result value... */
580 (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
581 dav_dbm_get_statefiles(p, fname, &state1, &state2);
583 /* build the propset pathname for the file */
584 pathname = apr_pstrcat(p,
586 "/" DAV_FS_STATE_DIR "/",
590 /* note: we may get ENOENT if the state dir is not present */
591 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
592 && !APR_STATUS_IS_ENOENT(status)) {
593 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
594 "Could not remove properties.");
597 if (state2 != NULL) {
598 /* build the propset pathname for the file */
599 pathname = apr_pstrcat(p,
601 "/" DAV_FS_STATE_DIR "/",
605 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
606 && !APR_STATUS_IS_ENOENT(status)) {
607 /* ### CRAP. only removed half. */
608 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
609 "Could not fully remove properties. "
610 "The server is now in an inconsistent "
618 /* --------------------------------------------------------------------
620 ** REPOSITORY HOOK FUNCTIONS
623 static dav_error * dav_fs_get_resource(
625 const char *root_dir,
628 dav_resource **result_resource)
630 dav_resource_private *ctx;
631 dav_resource *resource;
636 /* ### optimize this into a single allocation! */
638 /* Create private resource context descriptor */
639 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
640 ctx->finfo = r->finfo;
642 /* ### this should go away */
645 /* Preserve case on OSes which fold canonical filenames */
647 /* ### not available in Apache 2.0 yet */
648 filename = r->case_preserved_filename;
650 filename = r->filename;
654 ** If there is anything in the path_info, then this indicates that the
655 ** entire path was not used to specify the file/dir. We want to append
656 ** it onto the filename so that we get a "valid" pathname for null
659 s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
661 /* make sure the pathname does not have a trailing "/" */
663 if (len > 1 && s[len - 1] == '/') {
668 /* Create resource descriptor */
669 resource = apr_pcalloc(r->pool, sizeof(*resource));
670 resource->type = DAV_RESOURCE_TYPE_REGULAR;
671 resource->info = ctx;
672 resource->hooks = &dav_hooks_repository_fs;
673 resource->pool = r->pool;
675 /* make sure the URI does not have a trailing "/" */
676 len = strlen(r->uri);
677 if (len > 1 && r->uri[len - 1] == '/') {
678 s = apr_pstrdup(r->pool, r->uri);
683 resource->uri = r->uri;
686 if (r->finfo.filetype != 0) {
687 resource->exists = 1;
688 resource->collection = r->finfo.filetype == APR_DIR;
690 /* unused info in the URL will indicate a null resource */
692 if (r->path_info != NULL && *r->path_info != '\0') {
693 if (resource->collection) {
694 /* only a trailing "/" is allowed */
695 if (*r->path_info != '/' || r->path_info[1] != '\0') {
698 ** This URL/filename represents a locknull resource or
699 ** possibly a destination of a MOVE/COPY
701 resource->exists = 0;
702 resource->collection = 0;
708 ** The base of the path refers to a file -- nothing should
709 ** be in path_info. The resource is simply an error: it
710 ** can't be a null or a locknull resource.
712 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
713 "The URL contains extraneous path "
714 "components. The resource could not "
718 /* retain proper integrity across the structures */
719 if (!resource->exists) {
720 ctx->finfo.filetype = 0;
725 *result_resource = resource;
729 static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
730 dav_resource **result_parent)
732 dav_resource_private *ctx = resource->info;
733 dav_resource_private *parent_ctx;
734 dav_resource *parent_resource;
737 const char *testroot;
738 const char *testpath;
740 /* If we're at the root of the URL space, then there is no parent. */
741 if (strcmp(resource->uri, "/") == 0) {
742 *result_parent = NULL;
746 /* If given resource is root, then there is no parent.
747 * Unless we can retrieve the filepath root, this is
748 * intendend to fail. If we split the root and
749 * no path info remains, then we also fail.
751 testpath = ctx->pathname;
752 rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
753 if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
754 || !testpath || !*testpath) {
755 *result_parent = NULL;
759 /* ### optimize this into a single allocation! */
761 /* Create private resource context descriptor */
762 parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
764 /* ### this should go away */
765 parent_ctx->pool = ctx->pool;
767 dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
768 if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
769 dirpath[strlen(dirpath) - 1] = '\0';
770 parent_ctx->pathname = dirpath;
772 parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
773 parent_resource->info = parent_ctx;
774 parent_resource->collection = 1;
775 parent_resource->hooks = &dav_hooks_repository_fs;
776 parent_resource->pool = resource->pool;
778 if (resource->uri != NULL) {
779 char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
780 if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
781 uri[strlen(uri) - 1] = '\0';
782 parent_resource->uri = uri;
785 rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
786 APR_FINFO_NORM, ctx->pool);
787 if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
788 parent_resource->exists = 1;
791 *result_parent = parent_resource;
795 static int dav_fs_is_same_resource(
796 const dav_resource *res1,
797 const dav_resource *res2)
799 dav_resource_private *ctx1 = res1->info;
800 dav_resource_private *ctx2 = res2->info;
802 if (res1->hooks != res2->hooks)
805 if ((ctx1->finfo.filetype != 0) && (ctx2->finfo.filetype != 0)
806 && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
807 return ctx1->finfo.inode == ctx2->finfo.inode;
810 return strcmp(ctx1->pathname, ctx2->pathname) == 0;
814 static int dav_fs_is_parent_resource(
815 const dav_resource *res1,
816 const dav_resource *res2)
818 dav_resource_private *ctx1 = res1->info;
819 dav_resource_private *ctx2 = res2->info;
820 apr_size_t len1 = strlen(ctx1->pathname);
823 if (res1->hooks != res2->hooks)
826 /* it is safe to use ctx2 now */
827 len2 = strlen(ctx2->pathname);
830 && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
831 && ctx2->pathname[len1] == '/');
834 static dav_error * dav_fs_open_stream(const dav_resource *resource,
835 dav_stream_mode mode,
838 apr_pool_t *p = resource->info->pool;
839 dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
845 flags = APR_READ | APR_BINARY;
848 case DAV_MODE_WRITE_TRUNC:
849 flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
851 case DAV_MODE_WRITE_SEEKABLE:
852 flags = APR_WRITE | APR_CREATE | APR_BINARY;
857 ds->pathname = resource->info->pathname;
858 rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
859 if (rv != APR_SUCCESS) {
860 return dav_new_error(p, MAP_IO2HTTP(rv), 0,
861 "An error occurred while opening a resource.");
864 /* (APR registers cleanups for the fd with the pool) */
870 static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
872 apr_file_close(stream->f);
875 if (apr_file_remove(stream->pathname, stream->p) != APR_SUCCESS) {
876 /* ### use a better description? */
877 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
878 "There was a problem removing (rolling "
879 "back) the resource "
880 "when it was being closed.");
887 static dav_error * dav_fs_write_stream(dav_stream *stream,
888 const void *buf, apr_size_t bufsize)
892 status = apr_file_write_full(stream->f, buf, bufsize, NULL);
893 if (APR_STATUS_IS_ENOSPC(status)) {
894 return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
895 "There is not enough storage to write to "
898 else if (status != APR_SUCCESS) {
899 /* ### use something besides 500? */
900 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
901 "An error occurred while writing to a "
907 static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
909 if (apr_file_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
910 /* ### should check whether apr_file_seek set abs_pos was set to the
911 * correct position? */
912 /* ### use something besides 500? */
913 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
914 "Could not seek to specified position in the "
921 #if DEBUG_GET_HANDLER
923 /* only define set_headers() and deliver() for debug purposes */
926 static dav_error * dav_fs_set_headers(request_rec *r,
927 const dav_resource *resource)
929 /* ### this function isn't really used since we have a get_pathname */
930 if (!resource->exists)
933 /* make sure the proper mtime is in the request record */
934 ap_update_mtime(r, resource->info->finfo.mtime);
936 /* ### note that these use r->filename rather than <resource> */
937 ap_set_last_modified(r);
940 /* we accept byte-ranges */
941 apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
943 /* set up the Content-Length header */
944 ap_set_content_length(r, resource->info->finfo.size);
946 /* ### how to set the content type? */
947 /* ### until this is resolved, the Content-Type header is busted */
952 static dav_error * dav_fs_deliver(const dav_resource *resource,
955 apr_pool_t *pool = resource->pool;
956 apr_bucket_brigade *bb;
961 /* Check resource type */
962 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
963 && resource->type != DAV_RESOURCE_TYPE_VERSION
964 && resource->type != DAV_RESOURCE_TYPE_WORKING) {
965 return dav_new_error(pool, HTTP_CONFLICT, 0,
966 "Cannot GET this type of resource.");
968 if (resource->collection) {
969 return dav_new_error(pool, HTTP_CONFLICT, 0,
970 "There is no default response to GET for a "
974 if ((status = apr_file_open(&fd, resource->info->pathname,
975 APR_READ | APR_BINARY, 0,
976 pool)) != APR_SUCCESS) {
977 return dav_new_error(pool, HTTP_FORBIDDEN, 0,
978 "File permissions deny server access.");
981 bb = apr_brigade_create(pool, output->c->bucket_alloc);
983 /* ### this does not handle large files. but this is test code anyway */
984 bkt = apr_bucket_file_create(fd, 0,
985 (apr_size_t)resource->info->finfo.size,
986 pool, output->c->bucket_alloc);
987 APR_BRIGADE_INSERT_TAIL(bb, bkt);
989 bkt = apr_bucket_eos_create(output->c->bucket_alloc);
990 APR_BRIGADE_INSERT_TAIL(bb, bkt);
992 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
993 return dav_new_error(pool, HTTP_FORBIDDEN, 0,
994 "Could not write contents to filter.");
1000 #endif /* DEBUG_GET_HANDLER */
1003 static dav_error * dav_fs_create_collection(dav_resource *resource)
1005 dav_resource_private *ctx = resource->info;
1006 apr_status_t status;
1008 status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
1009 if (APR_STATUS_IS_ENOSPC(status)) {
1010 return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
1011 "There is not enough storage to create "
1012 "this collection.");
1014 else if (APR_STATUS_IS_ENOENT(status)) {
1015 return dav_new_error(ctx->pool, HTTP_CONFLICT, 0,
1016 "Cannot create collection; intermediate "
1017 "collection does not exist.");
1019 else if (status != APR_SUCCESS) {
1020 /* ### refine this error message? */
1021 return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
1022 "Unable to create collection.");
1025 /* update resource state to show it exists as a collection */
1026 resource->exists = 1;
1027 resource->collection = 1;
1032 static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
1035 dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
1036 dav_resource_private *srcinfo = wres->resource->info;
1037 dav_resource_private *dstinfo = ctx->res_dst->info;
1038 dav_error *err = NULL;
1040 if (wres->resource->collection) {
1041 if (calltype == DAV_CALLTYPE_POSTFIX) {
1042 /* Postfix call for MOVE. delete the source dir.
1043 * Note: when copying, we do not enable the postfix-traversal.
1045 /* ### we are ignoring any error here; what should we do? */
1046 (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
1049 /* copy/move of a collection. Create the new, target collection */
1050 if (apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
1051 ctx->pool) != APR_SUCCESS) {
1052 /* ### assume it was a permissions problem */
1053 /* ### need a description here */
1054 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
1059 err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
1060 srcinfo->pathname, dstinfo->pathname,
1062 ctx->res_dst->exists ? &dstinfo->finfo : NULL,
1064 /* ### push a higher-level description? */
1068 ** If we have a "not so bad" error, then it might need to go into a
1069 ** multistatus response.
1071 ** For a MOVE, it will always go into the multistatus. It could be
1072 ** that everything has been moved *except* for the root. Using a
1073 ** multistatus (with no errors for the other resources) will signify
1076 ** For a COPY, we are traversing in a prefix fashion. If the root fails,
1077 ** then we can just bail out now.
1080 && !ap_is_HTTP_SERVER_ERROR(err->status)
1082 || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
1083 /* ### use errno to generate DAV:responsedescription? */
1084 dav_add_response(wres, err->status, NULL);
1086 /* the error is in the multistatus now. do not stop the traversal. */
1093 static dav_error *dav_fs_copymove_resource(
1095 const dav_resource *src,
1096 const dav_resource *dst,
1098 dav_response **response)
1100 dav_error *err = NULL;
1101 dav_buffer work_buf = { 0 };
1105 /* if a collection, recursively copy/move it and its children,
1106 * including the state dirs
1108 if (src->collection) {
1109 dav_walk_params params = { 0 };
1110 dav_response *multi_status;
1112 params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
1113 params.func = dav_fs_copymove_walker;
1114 params.pool = src->info->pool;
1117 /* params.walk_ctx is managed by dav_fs_internal_walk() */
1119 /* postfix is needed for MOVE to delete source dirs */
1121 params.walk_type |= DAV_WALKTYPE_POSTFIX;
1123 /* note that we return the error OR the multistatus. never both */
1125 if ((err = dav_fs_internal_walk(¶ms, depth, is_move, dst,
1126 &multi_status)) != NULL) {
1127 /* on a "real" error, then just punt. nothing else to do. */
1131 if ((*response = multi_status) != NULL) {
1132 /* some multistatus responses exist. wrap them in a 207 */
1133 return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
1134 "Error(s) occurred on some resources during "
1135 "the COPY/MOVE process.");
1141 /* not a collection */
1142 if ((err = dav_fs_copymove_file(is_move, src->info->pool,
1143 src->info->pathname, dst->info->pathname,
1145 dst->exists ? &dst->info->finfo : NULL,
1146 &work_buf)) != NULL) {
1147 /* ### push a higher-level description? */
1151 /* copy/move properties as well */
1152 return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
1155 static dav_error * dav_fs_copy_resource(
1156 const dav_resource *src,
1159 dav_response **response)
1164 if (src->hooks != dst->hooks) {
1166 ** ### strictly speaking, this is a design error; we should not
1167 ** ### have reached this point.
1169 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1170 "DESIGN ERROR: a mix of repositories "
1171 "was passed to copy_resource.");
1175 if ((err = dav_fs_copymove_resource(0, src, dst, depth,
1176 response)) == NULL) {
1178 /* update state of destination resource to show it exists */
1180 dst->collection = src->collection;
1186 static dav_error * dav_fs_move_resource(
1189 dav_response **response)
1191 dav_resource_private *srcinfo = src->info;
1192 dav_resource_private *dstinfo = dst->info;
1197 if (src->hooks != dst->hooks) {
1199 ** ### strictly speaking, this is a design error; we should not
1200 ** ### have reached this point.
1202 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1203 "DESIGN ERROR: a mix of repositories "
1204 "was passed to move_resource.");
1208 /* determine whether a simple rename will work.
1209 * Assume source exists, else we wouldn't get called.
1211 if (dstinfo->finfo.filetype != 0) {
1212 if (dstinfo->finfo.device == srcinfo->finfo.device) {
1213 /* target exists and is on the same device. */
1218 const char *dirpath;
1222 /* destination does not exist, but the parent directory should,
1225 dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
1227 * XXX: If missing dev ... then what test?
1228 * Really need a try and failover for those platforms.
1231 rv = apr_stat(&finfo, dirpath, APR_FINFO_DEV, dstinfo->pool);
1232 if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
1233 && (finfo.valid & srcinfo->finfo.valid & APR_FINFO_DEV)
1234 && (finfo.device == srcinfo->finfo.device)) {
1239 /* if we can't simply rename, then do it the hard way... */
1241 if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
1242 response)) == NULL) {
1243 /* update resource states */
1245 dst->collection = src->collection;
1247 src->collection = 0;
1253 /* a rename should work. do it, and move properties as well */
1255 /* no multistatus response */
1258 /* ### APR has no rename? */
1259 if (apr_file_rename(srcinfo->pathname, dstinfo->pathname,
1260 srcinfo->pool) != APR_SUCCESS) {
1261 /* ### should have a better error than this. */
1262 return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1263 "Could not rename resource.");
1266 /* update resource states */
1268 dst->collection = src->collection;
1270 src->collection = 0;
1272 if ((err = dav_fs_copymoveset(1, src->info->pool,
1273 src, dst, NULL)) == NULL) {
1274 /* no error. we're done. go ahead and return now. */
1278 /* error occurred during properties move; try to put resource back */
1279 if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
1280 srcinfo->pool) != APR_SUCCESS) {
1281 /* couldn't put it back! */
1282 return dav_push_error(srcinfo->pool,
1283 HTTP_INTERNAL_SERVER_ERROR, 0,
1284 "The resource was moved, but a failure "
1285 "occurred during the move of its "
1286 "properties. The resource could not be "
1287 "restored to its original location. The "
1288 "server is now in an inconsistent state.",
1292 /* update resource states again */
1294 src->collection = dst->collection;
1296 dst->collection = 0;
1298 /* resource moved back, but properties may be inconsistent */
1299 return dav_push_error(srcinfo->pool,
1300 HTTP_INTERNAL_SERVER_ERROR, 0,
1301 "The resource was moved, but a failure "
1302 "occurred during the move of its properties. "
1303 "The resource was moved back to its original "
1304 "location, but its properties may have been "
1305 "partially moved. The server may be in an "
1306 "inconsistent state.",
1310 static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
1312 dav_resource_private *info = wres->resource->info;
1314 /* do not attempt to remove a null resource,
1315 * or a collection with children
1317 if (wres->resource->exists &&
1318 (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
1319 /* try to remove the resource */
1320 apr_status_t result;
1322 result = wres->resource->collection
1323 ? apr_dir_remove(info->pathname, wres->pool)
1324 : apr_file_remove(info->pathname, wres->pool);
1327 ** If an error occurred, then add it to multistatus response.
1328 ** Note that we add it for the root resource, too. It is quite
1329 ** possible to delete the whole darn tree, yet fail on the root.
1331 ** (also: remember we are deleting via a postfix traversal)
1333 if (result != APR_SUCCESS) {
1334 /* ### assume there is a permissions problem */
1336 /* ### use errno to generate DAV:responsedescription? */
1337 dav_add_response(wres, HTTP_FORBIDDEN, NULL);
1344 static dav_error * dav_fs_remove_resource(dav_resource *resource,
1345 dav_response **response)
1347 dav_resource_private *info = resource->info;
1351 /* if a collection, recursively remove it and its children,
1352 * including the state dirs
1354 if (resource->collection) {
1355 dav_walk_params params = { 0 };
1356 dav_error *err = NULL;
1357 dav_response *multi_status;
1359 params.walk_type = (DAV_WALKTYPE_NORMAL
1360 | DAV_WALKTYPE_HIDDEN
1361 | DAV_WALKTYPE_POSTFIX);
1362 params.func = dav_fs_delete_walker;
1363 params.pool = info->pool;
1364 params.root = resource;
1366 if ((err = dav_fs_walk(¶ms, DAV_INFINITY,
1367 &multi_status)) != NULL) {
1368 /* on a "real" error, then just punt. nothing else to do. */
1372 if ((*response = multi_status) != NULL) {
1373 /* some multistatus responses exist. wrap them in a 207 */
1374 return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
1375 "Error(s) occurred on some resources during "
1376 "the deletion process.");
1379 /* no errors... update resource state */
1380 resource->exists = 0;
1381 resource->collection = 0;
1386 /* not a collection; remove the file and its properties */
1387 if (apr_file_remove(info->pathname, info->pool) != APR_SUCCESS) {
1388 /* ### put a description in here */
1389 return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
1392 /* update resource state */
1393 resource->exists = 0;
1394 resource->collection = 0;
1396 /* remove properties and return its result */
1397 return dav_fs_deleteset(info->pool, resource);
1400 /* ### move this to dav_util? */
1401 /* Walk recursively down through directories, *
1402 * including lock-null resources as we go. */
1403 static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
1405 const dav_walk_params *params = fsctx->params;
1406 apr_pool_t *pool = params->pool;
1407 dav_error *err = NULL;
1408 int isdir = fsctx->res1.collection;
1412 /* ensure the context is prepared properly, then call the func */
1413 err = (*params->func)(&fsctx->wres,
1415 ? DAV_CALLTYPE_COLLECTION
1416 : DAV_CALLTYPE_MEMBER);
1421 if (depth == 0 || !isdir) {
1425 /* put a trailing slash onto the directory, in preparation for appending
1426 * files to it as we discovery them within the directory */
1427 dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
1428 fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
1429 fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
1431 /* if a secondary path is present, then do that, too */
1432 if (fsctx->path2.buf != NULL) {
1433 dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
1434 fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
1435 fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
1438 /* Note: the URI should ALREADY have a trailing "/" */
1440 /* for this first pass of files, all resources exist */
1441 fsctx->res1.exists = 1;
1443 /* a file is the default; we'll adjust if we hit a directory */
1444 fsctx->res1.collection = 0;
1445 fsctx->res2.collection = 0;
1447 /* open and scan the directory */
1448 if ((apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
1449 /* ### need a better error */
1450 return dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1452 while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
1454 apr_status_t status;
1456 len = strlen(dirent.name);
1458 /* avoid recursing into our current, parent, or state directories */
1459 if (dirent.name[0] == '.'
1460 && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
1464 if (params->walk_type & DAV_WALKTYPE_AUTH) {
1465 /* ### need to authorize each file */
1466 /* ### example: .htaccess is normally configured to fail auth */
1468 /* stuff in the state directory is never authorized! */
1469 if (!strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1473 /* skip the state dir unless a HIDDEN is performed */
1474 if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
1475 && !strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1479 /* append this file onto the path buffer (copy null term) */
1480 dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
1483 /* ### Optimize me, dirent can give us what we need! */
1484 status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
1485 APR_FINFO_NORM | APR_FINFO_LINK, pool);
1486 if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
1487 /* woah! where'd it go? */
1488 /* ### should have a better error here */
1489 err = dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1493 /* copy the file to the URI, too. NOTE: we will pad an extra byte
1494 for the trailing slash later. */
1495 dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
1497 /* if there is a secondary path, then do that, too */
1498 if (fsctx->path2.buf != NULL) {
1499 dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
1502 /* set up the (internal) pathnames for the two resources */
1503 fsctx->info1.pathname = fsctx->path1.buf;
1504 fsctx->info2.pathname = fsctx->path2.buf;
1506 /* set up the URI for the current resource */
1507 fsctx->res1.uri = fsctx->uri_buf.buf;
1509 /* ### for now, only process regular files (e.g. skip symlinks) */
1510 if (fsctx->info1.finfo.filetype == APR_REG) {
1511 /* call the function for the specified dir + file */
1512 if ((err = (*params->func)(&fsctx->wres,
1513 DAV_CALLTYPE_MEMBER)) != NULL) {
1514 /* ### maybe add a higher-level description? */
1518 else if (fsctx->info1.finfo.filetype == APR_DIR) {
1519 apr_size_t save_path_len = fsctx->path1.cur_len;
1520 apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
1521 apr_size_t save_path2_len = fsctx->path2.cur_len;
1523 /* adjust length to incorporate the subdir name */
1524 fsctx->path1.cur_len += len;
1525 fsctx->path2.cur_len += len;
1527 /* adjust URI length to incorporate subdir and a slash */
1528 fsctx->uri_buf.cur_len += len + 1;
1529 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
1530 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
1532 /* switch over to a collection */
1533 fsctx->res1.collection = 1;
1534 fsctx->res2.collection = 1;
1536 /* recurse on the subdir */
1537 /* ### don't always want to quit on error from single child */
1538 if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
1539 /* ### maybe add a higher-level description? */
1543 /* put the various information back */
1544 fsctx->path1.cur_len = save_path_len;
1545 fsctx->path2.cur_len = save_path2_len;
1546 fsctx->uri_buf.cur_len = save_uri_len;
1548 fsctx->res1.collection = 0;
1549 fsctx->res2.collection = 0;
1551 /* assert: res1.exists == 1 */
1555 /* ### check the return value of this? */
1556 apr_dir_close(dirp);
1561 if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
1562 apr_size_t offset = 0;
1564 /* null terminate the directory name */
1565 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
1567 /* Include any lock null resources found in this collection */
1568 fsctx->res1.collection = 1;
1569 if ((err = dav_fs_get_locknull_members(&fsctx->res1,
1570 &fsctx->locknull_buf)) != NULL) {
1571 /* ### maybe add a higher-level description? */
1575 /* put a slash back on the end of the directory */
1576 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
1578 /* these are all non-existant (files) */
1579 fsctx->res1.exists = 0;
1580 fsctx->res1.collection = 0;
1581 memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
1583 while (offset < fsctx->locknull_buf.cur_len) {
1584 apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
1585 dav_lock *locks = NULL;
1588 ** Append the locknull file to the paths and the URI. Note that
1589 ** we don't have to pad the URI for a slash since a locknull
1590 ** resource is not a collection.
1592 dav_buffer_place_mem(pool, &fsctx->path1,
1593 fsctx->locknull_buf.buf + offset, len + 1, 0);
1594 dav_buffer_place_mem(pool, &fsctx->uri_buf,
1595 fsctx->locknull_buf.buf + offset, len + 1, 0);
1596 if (fsctx->path2.buf != NULL) {
1597 dav_buffer_place_mem(pool, &fsctx->path2,
1598 fsctx->locknull_buf.buf + offset,
1602 /* set up the (internal) pathnames for the two resources */
1603 fsctx->info1.pathname = fsctx->path1.buf;
1604 fsctx->info2.pathname = fsctx->path2.buf;
1606 /* set up the URI for the current resource */
1607 fsctx->res1.uri = fsctx->uri_buf.buf;
1610 ** To prevent a PROPFIND showing an expired locknull
1611 ** resource, query the lock database to force removal
1612 ** of both the lock entry and .locknull, if necessary..
1613 ** Sure, the query in PROPFIND would do this.. after
1614 ** the locknull resource was already included in the
1617 ** NOTE: we assume the caller has opened the lock database
1618 ** if they have provided DAV_WALKTYPE_LOCKNULL.
1620 /* ### we should also look into opening it read-only and
1621 ### eliding timed-out items from the walk, yet leaving
1622 ### them in the locknull database until somebody opens
1623 ### the thing writable.
1625 /* ### probably ought to use has_locks. note the problem
1626 ### mentioned above, though... we would traverse this as
1627 ### a locknull, but then a PROPFIND would load the lock
1628 ### info, causing a timeout and the locks would not be
1629 ### reported. Therefore, a null resource would be returned
1630 ### in the PROPFIND.
1632 ### alternative: just load unresolved locks. any direct
1633 ### locks will be timed out (correct). any indirect will
1634 ### not (correct; consider if a parent timed out -- the
1635 ### timeout routines do not walk and remove indirects;
1636 ### even the resolve func would probably fail when it
1637 ### tried to find a timed-out direct lock).
1639 if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
1641 /* ### maybe add a higher-level description? */
1645 /* call the function for the specified dir + file */
1646 if (locks != NULL &&
1647 (err = (*params->func)(&fsctx->wres,
1648 DAV_CALLTYPE_LOCKNULL)) != NULL) {
1649 /* ### maybe add a higher-level description? */
1656 /* reset the exists flag */
1657 fsctx->res1.exists = 1;
1660 if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
1661 /* replace the dirs' trailing slashes with null terms */
1662 fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
1663 fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
1664 if (fsctx->path2.buf != NULL) {
1665 fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
1668 /* this is a collection which exists */
1669 fsctx->res1.collection = 1;
1671 return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
1677 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
1678 int depth, int is_move,
1679 const dav_resource *root_dst,
1680 dav_response **response)
1682 dav_fs_walker_context fsctx = { 0 };
1684 dav_fs_copymove_walk_ctx cm_ctx = { 0 };
1687 if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
1688 && params->lockdb == NULL) {
1689 return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1690 "DESIGN ERROR: walker called to walk locknull "
1691 "resources, but a lockdb was not provided.");
1695 fsctx.params = params;
1696 fsctx.wres.walk_ctx = params->walk_ctx;
1697 fsctx.wres.pool = params->pool;
1699 /* ### zero out versioned, working, baselined? */
1701 fsctx.res1 = *params->root;
1702 fsctx.res1.pool = params->pool;
1704 fsctx.res1.info = &fsctx.info1;
1705 fsctx.info1 = *params->root->info;
1707 /* the pathname is stored in the path1 buffer */
1708 dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
1709 fsctx.info1.pathname = fsctx.path1.buf;
1711 if (root_dst != NULL) {
1712 /* internal call from the COPY/MOVE code. set it up. */
1714 fsctx.wres.walk_ctx = &cm_ctx;
1715 cm_ctx.is_move = is_move;
1716 cm_ctx.res_dst = &fsctx.res2;
1717 cm_ctx.root = params->root;
1718 cm_ctx.pool = params->pool;
1720 fsctx.res2 = *root_dst;
1721 fsctx.res2.exists = 0;
1722 fsctx.res2.collection = 0;
1723 fsctx.res2.uri = NULL; /* we don't track this */
1724 fsctx.res2.pool = params->pool;
1726 fsctx.res2.info = &fsctx.info2;
1727 fsctx.info2 = *root_dst->info;
1729 /* res2 does not exist -- clear its finfo structure */
1730 memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
1732 /* the pathname is stored in the path2 buffer */
1733 dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
1734 fsctx.info2.pathname = fsctx.path2.buf;
1737 /* prep the URI buffer */
1738 dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
1740 /* if we have a directory, then ensure the URI has a trailing "/" */
1741 if (fsctx.res1.collection
1742 && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
1744 /* this will fall into the pad area */
1745 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
1746 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
1749 /* the current resource's URI is stored in the uri_buf buffer */
1750 fsctx.res1.uri = fsctx.uri_buf.buf;
1752 /* point the callback's resource at our structure */
1753 fsctx.wres.resource = &fsctx.res1;
1755 /* always return the error, and any/all multistatus responses */
1756 err = dav_fs_walker(&fsctx, depth);
1757 *response = fsctx.wres.response;
1761 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
1762 dav_response **response)
1764 /* always return the error, and any/all multistatus responses */
1765 return dav_fs_internal_walk(params, depth, 0, NULL, response);
1768 /* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
1770 * ### do we need to return weak tags sometimes?
1772 static const char *dav_fs_getetag(const dav_resource *resource)
1774 dav_resource_private *ctx = resource->info;
1776 if (!resource->exists)
1777 return apr_pstrdup(ctx->pool, "");
1779 if (ctx->finfo.filetype != 0) {
1780 return apr_psprintf(ctx->pool, "\"%lx-%lx-%lx\"",
1781 (unsigned long) ctx->finfo.inode,
1782 (unsigned long) ctx->finfo.size,
1783 (unsigned long) ctx->finfo.mtime);
1786 return apr_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.mtime);
1789 static const dav_hooks_repository dav_hooks_repository_fs =
1791 DEBUG_GET_HANDLER, /* normally: special GET handling not required */
1792 dav_fs_get_resource,
1793 dav_fs_get_parent_resource,
1794 dav_fs_is_same_resource,
1795 dav_fs_is_parent_resource,
1797 dav_fs_close_stream,
1798 dav_fs_write_stream,
1800 #if DEBUG_GET_HANDLER
1807 dav_fs_create_collection,
1808 dav_fs_copy_resource,
1809 dav_fs_move_resource,
1810 dav_fs_remove_resource,
1815 static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
1816 int propid, dav_prop_insert what,
1817 apr_text_header *phdr)
1821 apr_pool_t *p = resource->info->pool;
1822 const dav_liveprop_spec *info;
1825 /* an HTTP-date can be 29 chars plus a null term */
1826 /* a 64-bit size can be 20 chars plus a null term */
1827 char buf[DAV_TIMEBUF_SIZE];
1830 ** None of FS provider properties are defined if the resource does not
1831 ** exist. Just bail for this case.
1833 ** Even though we state that the FS properties are not defined, the
1834 ** client cannot store dead values -- we deny that thru the is_writable
1837 if (!resource->exists)
1838 return DAV_PROP_INSERT_NOTDEF;
1841 case DAV_PROPID_creationdate:
1843 ** Closest thing to a creation date. since we don't actually
1844 ** perform the operations that would modify ctime (after we
1845 ** create the file), then we should be pretty safe here.
1847 dav_format_time(DAV_STYLE_ISO8601,
1848 resource->info->finfo.ctime,
1853 case DAV_PROPID_getcontentlength:
1854 /* our property, but not defined on collection resources */
1855 if (resource->collection)
1856 return DAV_PROP_INSERT_NOTDEF;
1858 (void) sprintf(buf, "%" APR_OFF_T_FMT, resource->info->finfo.size);
1862 case DAV_PROPID_getetag:
1863 value = dav_fs_getetag(resource);
1866 case DAV_PROPID_getlastmodified:
1867 dav_format_time(DAV_STYLE_RFC822,
1868 resource->info->finfo.mtime,
1873 case DAV_PROPID_FS_executable:
1874 /* our property, but not defined on collection resources */
1875 if (resource->collection)
1876 return DAV_PROP_INSERT_NOTDEF;
1878 /* our property, but not defined on this platform */
1879 if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
1880 return DAV_PROP_INSERT_NOTDEF;
1882 /* the files are "ours" so we only need to check owner exec privs */
1883 if (resource->info->finfo.protection & APR_UEXECUTE)
1890 /* ### what the heck was this property? */
1891 return DAV_PROP_INSERT_NOTDEF;
1894 /* assert: value != NULL */
1896 /* get the information and global NS index for the property */
1897 global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1899 /* assert: info != NULL && info->name != NULL */
1901 /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
1903 if (what == DAV_PROP_INSERT_VALUE) {
1904 s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
1905 global_ns, info->name, value, global_ns, info->name);
1907 else if (what == DAV_PROP_INSERT_NAME) {
1908 s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
1911 /* assert: what == DAV_PROP_INSERT_SUPPORTED */
1913 "<D:supported-live-property D:name=\"%s\" "
1914 "D:namespace=\"%s\"/>" DEBUG_CR,
1915 info->name, dav_fs_namespace_uris[info->ns]);
1917 apr_text_append(p, phdr, s);
1919 /* we inserted what was asked for */
1923 static int dav_fs_is_writable(const dav_resource *resource, int propid)
1925 const dav_liveprop_spec *info;
1927 #ifdef DAV_FS_HAS_EXECUTABLE
1928 /* if we have the executable property, and this isn't a collection,
1929 then the property is writable. */
1930 if (propid == DAV_PROPID_FS_executable && !resource->collection)
1934 (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1935 return info->is_writable;
1938 static dav_error *dav_fs_patch_validate(const dav_resource *resource,
1939 const apr_xml_elem *elem,
1944 const apr_text *cdata;
1945 const apr_text *f_cdata;
1947 dav_elem_private *priv = elem->priv;
1949 if (priv->propid != DAV_PROPID_FS_executable) {
1954 if (operation == DAV_PROP_OP_DELETE) {
1955 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1956 "The 'executable' property cannot be removed.");
1959 cdata = elem->first_cdata.first;
1961 /* ### hmm. this isn't actually looking at all the possible text items */
1962 f_cdata = elem->first_child == NULL
1964 : elem->first_child->following_cdata.first;
1966 /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
1968 if (cdata == NULL) {
1969 if (f_cdata == NULL) {
1970 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1971 "The 'executable' property expects a single "
1972 "character, valued 'T' or 'F'. There was no "
1973 "value submitted.");
1977 else if (f_cdata != NULL)
1980 if (cdata->next != NULL || strlen(cdata->text) != 1)
1983 value = cdata->text[0];
1984 if (value != 'T' && value != 'F') {
1985 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1986 "The 'executable' property expects a single "
1987 "character, valued 'T' or 'F'. The value "
1988 "submitted is invalid.");
1991 *context = (void *)(value == 'T');
1996 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1997 "The 'executable' property expects a single "
1998 "character, valued 'T' or 'F'. The value submitted "
1999 "has too many characters.");
2003 static dav_error *dav_fs_patch_exec(const dav_resource *resource,
2004 const apr_xml_elem *elem,
2007 dav_liveprop_rollback **rollback_ctx)
2009 int value = context != NULL;
2010 apr_fileperms_t perms = resource->info->finfo.protection;
2011 int old_value = (perms & APR_UEXECUTE) != 0;
2013 /* assert: prop == executable. operation == SET. */
2015 /* don't do anything if there is no change. no rollback info either. */
2016 /* DBG2("new value=%d (old=%d)", value, old_value); */
2017 if (value == old_value)
2020 perms &= ~APR_UEXECUTE;
2022 perms |= APR_UEXECUTE;
2024 if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2025 return dav_new_error(resource->info->pool,
2026 HTTP_INTERNAL_SERVER_ERROR, 0,
2027 "Could not set the executable flag of the "
2028 "target resource.");
2031 /* update the resource and set up the rollback context */
2032 resource->info->finfo.protection = perms;
2033 *rollback_ctx = (dav_liveprop_rollback *)old_value;
2038 static void dav_fs_patch_commit(const dav_resource *resource,
2041 dav_liveprop_rollback *rollback_ctx)
2046 static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
2049 dav_liveprop_rollback *rollback_ctx)
2051 apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
2052 int value = rollback_ctx != NULL;
2054 /* assert: prop == executable. operation == SET. */
2056 /* restore the executable bit */
2058 perms |= APR_UEXECUTE;
2060 if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
2061 return dav_new_error(resource->info->pool,
2062 HTTP_INTERNAL_SERVER_ERROR, 0,
2063 "After a failure occurred, the resource's "
2064 "executable flag could not be restored.");
2067 /* restore the resource's state */
2068 resource->info->finfo.protection = perms;
2074 static const dav_hooks_liveprop dav_hooks_liveprop_fs =
2078 dav_fs_namespace_uris,
2079 dav_fs_patch_validate,
2081 dav_fs_patch_commit,
2082 dav_fs_patch_rollback
2085 static const dav_provider dav_fs_provider =
2087 &dav_hooks_repository_fs,
2089 &dav_hooks_locks_fs,
2097 void dav_fs_gather_propsets(apr_array_header_t *uris)
2099 #ifdef DAV_FS_HAS_EXECUTABLE
2100 *(const char **)apr_array_push(uris) =
2101 "<http://apache.org/dav/propset/fs/1>";
2105 int dav_fs_find_liveprop(const dav_resource *resource,
2106 const char *ns_uri, const char *name,
2107 const dav_hooks_liveprop **hooks)
2109 /* don't try to find any liveprops if this isn't "our" resource */
2110 if (resource->hooks != &dav_hooks_repository_fs)
2112 return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
2115 void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
2116 dav_prop_insert what, apr_text_header *phdr)
2118 /* don't insert any liveprops if this isn't "our" resource */
2119 if (resource->hooks != &dav_hooks_repository_fs)
2122 if (!resource->exists) {
2123 /* a lock-null resource */
2125 ** ### technically, we should insert empty properties. dunno offhand
2126 ** ### what part of the spec said this, but it was essentially thus:
2127 ** ### "the properties should be defined, but may have no value".
2132 (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
2134 (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
2136 (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
2138 (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
2141 #ifdef DAV_FS_HAS_EXECUTABLE
2142 /* Only insert this property if it is defined for this platform. */
2143 (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
2147 /* ### we know the others aren't defined as liveprops */
2150 void dav_fs_register(apr_pool_t *p)
2152 /* register the namespace URIs */
2153 dav_register_liveprop_group(p, &dav_fs_liveprop_group);
2155 /* register the repository provider */
2156 dav_register_provider(p, "filesystem", &dav_fs_provider);