1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
56 ** DAV filesystem-based repository provider
60 #include "apr_file_io.h"
61 #include "apr_strings.h"
64 #include <stdio.h> /* for sprintf() */
69 #include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
70 #include "http_request.h" /* for ap_update_mtime() */
76 /* to assist in debugging mod_dav's GET handling */
77 #define DEBUG_GET_HANDLER 0
78 #define DEBUG_PATHNAME_STYLE 0
80 #define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
82 /* context needed to identify a resource */
83 struct dav_resource_private {
84 apr_pool_t *pool; /* memory storage pool associated with request */
85 const char *pathname; /* full pathname to resource */
86 apr_finfo_t finfo; /* filesystem info */
89 /* private context for doing a filesystem walk */
91 /* the input walk parameters */
92 const dav_walk_params *params;
94 /* reused as we walk */
95 dav_walk_resource wres;
98 dav_resource_private info1;
102 /* MOVE/COPY need a secondary path */
104 dav_resource_private info2;
107 dav_buffer locknull_buf;
109 } dav_fs_walker_context;
112 int is_move; /* is this a MOVE? */
113 dav_buffer work_buf; /* handy buffer for copymove_file() */
115 /* CALLBACK: this is a secondary resource managed specially for us */
116 const dav_resource *res_dst;
118 /* copied from dav_walk_params (they are invariant across the walk) */
119 const dav_resource *root;
122 } dav_fs_copymove_walk_ctx;
124 /* an internal WALKTYPE to walk hidden files (the .DAV directory) */
125 #define DAV_WALKTYPE_HIDDEN 0x4000
127 /* an internal WALKTYPE to call collections (again) after their contents */
128 #define DAV_WALKTYPE_POSTFIX 0x8000
130 #define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
133 /* pull this in from the other source file */
134 extern const dav_hooks_locks dav_hooks_locks_fs;
136 /* forward-declare the hook structures */
137 static const dav_hooks_repository dav_hooks_repository_fs;
138 static const dav_hooks_liveprop dav_hooks_liveprop_fs;
141 ** The namespace URIs that we use. This list and the enumeration must
144 static const char * const dav_fs_namespace_uris[] =
147 "http://apache.org/dav/props/",
152 DAV_FS_URI_DAV, /* the DAV: namespace URI */
153 DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
157 ** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
159 #define DAV_PROPID_FS_executable 1
161 static const dav_liveprop_spec dav_fs_props[] =
166 DAV_PROPID_creationdate,
172 DAV_PROPID_getcontentlength,
184 DAV_PROPID_getlastmodified,
191 DAV_PROPID_FS_executable,
192 0 /* handled special in dav_fs_is_writable */
198 static const dav_liveprop_group dav_fs_liveprop_group =
201 dav_fs_namespace_uris,
202 &dav_hooks_liveprop_fs
206 /* define the dav_stream structure for our use */
210 const char *pathname; /* we may need to remove it at close time */
213 /* forward declaration for internal treewalkers */
214 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
215 dav_response **response);
216 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
217 int depth, int is_move,
218 const dav_resource *root_dst,
219 dav_response **response);
221 /* --------------------------------------------------------------------
223 ** PRIVATE REPOSITORY FUNCTIONS
225 apr_pool_t *dav_fs_pool(const dav_resource *resource)
227 return resource->info->pool;
230 const char *dav_fs_pathname(const dav_resource *resource)
232 return resource->info->pathname;
235 void dav_fs_dir_file_name(
236 const dav_resource *resource,
237 const char **dirpath_p,
238 const char **fname_p)
240 dav_resource_private *ctx = resource->info;
242 if (resource->collection) {
243 *dirpath_p = ctx->pathname;
248 char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
249 apr_size_t dirlen = strlen(dirpath);
252 *fname_p = ctx->pathname + dirlen;
253 *dirpath_p = dirpath;
255 /* remove trailing slash from dirpath, unless it's the root dir */
256 /* ### Win32 check */
257 if (dirlen > 1 && dirpath[dirlen - 1] == '/') {
258 dirpath[dirlen - 1] = '\0';
263 /* Note: picked up from ap_gm_timestr_822() */
264 /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
265 static void dav_format_time(int style, apr_time_t sec, char *buf)
267 apr_exploded_time_t tms;
269 /* ### what to do if fails? */
270 (void) apr_explode_gmt(&tms, sec);
272 if (style == DAV_STYLE_ISO8601) {
273 /* ### should we use "-00:00" instead of "Z" ?? */
275 /* 20 chars plus null term */
276 sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
277 tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
278 tms.tm_hour, tms.tm_min, tms.tm_sec);
282 /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
284 /* 29 chars plus null term */
286 "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
287 apr_day_snames[tms.tm_wday],
288 tms.tm_mday, apr_month_snames[tms.tm_mon],
290 tms.tm_hour, tms.tm_min, tms.tm_sec);
293 static dav_error * dav_fs_copymove_file(
300 dav_buffer work_buf = { 0 };
301 apr_file_t *inf = NULL;
302 apr_file_t *outf = NULL;
307 dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
309 if ((apr_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p))
311 /* ### use something besides 500? */
312 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
313 "Could not open file for reading");
316 /* ### do we need to deal with the umask? */
317 if ((apr_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY,
318 APR_OS_DEFAULT, p)) != APR_SUCCESS) {
321 /* ### use something besides 500? */
322 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
323 "Could not open file for writing");
327 apr_ssize_t len = DAV_FS_COPY_BLOCKSIZE;
330 status = apr_read(inf, pbuf->buf, &len);
331 if (status != APR_SUCCESS && status != APR_EOF) {
335 if (apr_remove_file(dst, p) != APR_SUCCESS) {
336 /* ### ACK! Inconsistent state... */
338 /* ### use something besides 500? */
339 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
340 "Could not delete output after read "
341 "failure. Server is now in an "
342 "inconsistent state.");
345 /* ### use something besides 500? */
346 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
347 "Could not read input file");
350 /* write any bytes that were read (applies to APR_EOF, too) */
351 if (apr_full_write(outf, pbuf->buf, len, NULL) != APR_SUCCESS) {
352 int save_errno = errno;
357 if (apr_remove_file(dst, p) != 0) {
358 /* ### ACK! Inconsistent state... */
360 /* ### use something besides 500? */
361 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
362 "Could not delete output after write "
363 "failure. Server is now in an "
364 "inconsistent state.");
367 if (save_errno == ENOSPC) {
368 return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0,
369 "There is not enough storage to write to "
373 /* ### use something besides 500? */
374 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
375 "Could not write output file");
378 if (status == APR_EOF)
385 if (is_move && remove(src) != 0) {
387 int save_errno = errno; /* save the errno that got us here */
389 if (remove(dst) != 0) {
390 /* ### ACK. this creates an inconsistency. do more!? */
392 /* ### use something besides 500? */
393 /* Note that we use the latest errno */
394 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
395 "Could not remove source or destination "
396 "file. Server is now in an inconsistent "
400 /* ### use something besides 500? */
401 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
402 "Could not remove source file after move. "
403 "Destination was removed to ensure consistency.");
404 err->save_errno = save_errno;
411 /* copy/move a file from within a state dir to another state dir */
412 /* ### need more buffers to replace the pool argument */
413 static dav_error * dav_fs_copymove_state(
416 const char *src_dir, const char *src_file,
417 const char *dst_dir, const char *dst_file,
420 apr_finfo_t src_finfo; /* finfo for source file */
421 apr_finfo_t dst_state_finfo; /* finfo for STATE directory */
426 /* build the propset pathname for the source file */
427 src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
429 /* the source file doesn't exist */
430 if (apr_stat(&src_finfo, src, APR_FINFO_NORM, p) != APR_SUCCESS) {
434 /* build the pathname for the destination state dir */
435 dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
437 /* ### do we need to deal with the umask? */
439 /* ensure that it exists */
440 rv = apr_make_dir(dst, APR_OS_DEFAULT, p);
441 if (rv != APR_SUCCESS) {
442 if (!APR_STATUS_IS_EEXIST(rv)) {
443 /* ### use something besides 500? */
444 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
445 "Could not create internal state directory");
449 /* get info about the state directory */
450 if (apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p) != APR_SUCCESS) {
451 /* Ack! Where'd it go? */
452 /* ### use something besides 500? */
453 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
454 "State directory disappeared");
457 /* The mkdir() may have failed because a *file* exists there already */
458 if (dst_state_finfo.filetype != APR_DIR) {
459 /* ### try to recover by deleting this file? (and mkdir again) */
460 /* ### use something besides 500? */
461 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
462 "State directory is actually a file");
465 /* append the target file to the state directory pathname */
466 dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
468 /* copy/move the file now */
469 if (is_move && src_finfo.device == dst_state_finfo.device) {
470 /* simple rename is possible since it is on the same device */
471 if (apr_rename_file(src, dst, p) != APR_SUCCESS) {
472 /* ### use something besides 500? */
473 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
474 "Could not move state file.");
479 /* gotta copy (and delete) */
480 return dav_fs_copymove_file(is_move, p, src, dst, pbuf);
486 static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
487 const dav_resource *src,
488 const dav_resource *dst,
492 const char *src_file;
493 const char *src_state1;
494 const char *src_state2;
496 const char *dst_file;
497 const char *dst_state1;
498 const char *dst_state2;
501 /* Get directory and filename for resources */
502 dav_fs_dir_file_name(src, &src_dir, &src_file);
503 dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
505 /* Get the corresponding state files for each resource */
506 dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
507 dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
509 if ((src_state2 != NULL && dst_state2 == NULL) ||
510 (src_state2 == NULL && dst_state2 != NULL)) {
511 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
512 "DESIGN ERROR: dav_dbm_get_statefiles() "
513 "returned inconsistent results.");
517 err = dav_fs_copymove_state(is_move, p,
522 if (err == NULL && src_state2 != NULL) {
523 err = dav_fs_copymove_state(is_move, p,
529 /* ### CRAP. inconsistency. */
530 /* ### should perform some cleanup at the target if we still
531 ### have the original files */
533 /* Change the error to reflect the bad server state. */
534 err->status = HTTP_INTERNAL_SERVER_ERROR;
536 "Could not fully copy/move the properties. "
537 "The server is now in an inconsistent state.";
544 static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
550 const char *pathname;
552 /* Get directory, filename, and state-file names for the resource */
553 dav_fs_dir_file_name(resource, &dirpath, &fname);
554 dav_dbm_get_statefiles(p, fname, &state1, &state2);
556 /* build the propset pathname for the file */
557 pathname = apr_pstrcat(p,
559 "/" DAV_FS_STATE_DIR "/",
563 /* note: we may get ENOENT if the state dir is not present */
564 if (remove(pathname) != 0 && errno != ENOENT) {
565 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
566 "Could not remove properties.");
569 if (state2 != NULL) {
570 /* build the propset pathname for the file */
571 pathname = apr_pstrcat(p,
573 "/" DAV_FS_STATE_DIR "/",
577 if (remove(pathname) != 0 && errno != ENOENT) {
578 /* ### CRAP. only removed half. */
579 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
580 "Could not fully remove properties. "
581 "The server is now in an inconsistent "
589 /* --------------------------------------------------------------------
591 ** REPOSITORY HOOK FUNCTIONS
594 static dav_error * dav_fs_get_resource(
596 const char *root_dir,
599 dav_resource **result_resource)
601 dav_resource_private *ctx;
602 dav_resource *resource;
607 /* ### optimize this into a single allocation! */
609 /* Create private resource context descriptor */
610 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
611 ctx->finfo = r->finfo;
613 /* ### this should go away */
616 /* Preserve case on OSes which fold canonical filenames */
618 /* ### not available in Apache 2.0 yet */
619 filename = r->case_preserved_filename;
621 filename = r->filename;
625 ** If there is anything in the path_info, then this indicates that the
626 ** entire path was not used to specify the file/dir. We want to append
627 ** it onto the filename so that we get a "valid" pathname for null
630 s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
632 /* make sure the pathname does not have a trailing "/" */
634 if (len > 1 && s[len - 1] == '/') {
639 /* Create resource descriptor */
640 resource = apr_pcalloc(r->pool, sizeof(*resource));
641 resource->type = DAV_RESOURCE_TYPE_REGULAR;
642 resource->info = ctx;
643 resource->hooks = &dav_hooks_repository_fs;
644 resource->pool = r->pool;
646 /* make sure the URI does not have a trailing "/" */
647 len = strlen(r->uri);
648 if (len > 1 && r->uri[len - 1] == '/') {
649 s = apr_pstrdup(r->pool, r->uri);
654 resource->uri = r->uri;
657 if (r->finfo.protection != 0) {
658 resource->exists = 1;
659 resource->collection = r->finfo.filetype == APR_DIR;
661 /* unused info in the URL will indicate a null resource */
663 if (r->path_info != NULL && *r->path_info != '\0') {
664 if (resource->collection) {
665 /* only a trailing "/" is allowed */
666 if (*r->path_info != '/' || r->path_info[1] != '\0') {
669 ** This URL/filename represents a locknull resource or
670 ** possibly a destination of a MOVE/COPY
672 resource->exists = 0;
673 resource->collection = 0;
679 ** The base of the path refers to a file -- nothing should
680 ** be in path_info. The resource is simply an error: it
681 ** can't be a null or a locknull resource.
683 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
684 "The URL contains extraneous path "
685 "components. The resource could not "
689 /* retain proper integrity across the structures */
690 if (!resource->exists) {
691 ctx->finfo.protection = 0;
696 *result_resource = resource;
700 static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
701 dav_resource **result_parent)
703 dav_resource_private *ctx = resource->info;
704 dav_resource_private *parent_ctx;
705 dav_resource *parent_resource;
708 /* If given resource is root, then there is no parent */
709 if (strcmp(resource->uri, "/") == 0 ||
711 (strlen(ctx->pathname) == 3 && ctx->pathname[1] == ':' && ctx->pathname[2] == '/')
713 strcmp(ctx->pathname, "/") == 0
716 *result_parent = NULL;
720 /* ### optimize this into a single allocation! */
722 /* Create private resource context descriptor */
723 parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
725 /* ### this should go away */
726 parent_ctx->pool = ctx->pool;
728 dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
729 if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
730 dirpath[strlen(dirpath) - 1] = '\0';
731 parent_ctx->pathname = dirpath;
733 parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
734 parent_resource->info = parent_ctx;
735 parent_resource->collection = 1;
736 parent_resource->hooks = &dav_hooks_repository_fs;
737 parent_resource->pool = resource->pool;
739 if (resource->uri != NULL) {
740 char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
741 if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
742 uri[strlen(uri) - 1] = '\0';
743 parent_resource->uri = uri;
746 if (apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
747 APR_FINFO_NORM, ctx->pool) == APR_SUCCESS) {
748 parent_resource->exists = 1;
751 *result_parent = parent_resource;
755 static int dav_fs_is_same_resource(
756 const dav_resource *res1,
757 const dav_resource *res2)
759 dav_resource_private *ctx1 = res1->info;
760 dav_resource_private *ctx2 = res2->info;
762 if (res1->hooks != res2->hooks)
766 return stricmp(ctx1->pathname, ctx2->pathname) == 0;
768 if (ctx1->finfo.protection != 0)
769 return ctx1->finfo.inode == ctx2->finfo.inode;
771 return strcmp(ctx1->pathname, ctx2->pathname) == 0;
775 static int dav_fs_is_parent_resource(
776 const dav_resource *res1,
777 const dav_resource *res2)
779 dav_resource_private *ctx1 = res1->info;
780 dav_resource_private *ctx2 = res2->info;
781 apr_size_t len1 = strlen(ctx1->pathname);
784 if (res1->hooks != res2->hooks)
787 /* it is safe to use ctx2 now */
788 len2 = strlen(ctx2->pathname);
791 && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
792 && ctx2->pathname[len1] == '/');
795 static dav_error * dav_fs_open_stream(const dav_resource *resource,
796 dav_stream_mode mode,
799 apr_pool_t *p = resource->info->pool;
800 dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
805 case DAV_MODE_READ_SEEKABLE:
807 flags = APR_READ | APR_BINARY;
810 case DAV_MODE_WRITE_TRUNC:
811 flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
813 case DAV_MODE_WRITE_SEEKABLE:
814 flags = APR_WRITE | APR_CREATE | APR_BINARY;
819 ds->pathname = resource->info->pathname;
820 if (apr_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT,
821 ds->p) != APR_SUCCESS) {
822 /* ### use something besides 500? */
823 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
824 "An error occurred while opening a resource.");
827 /* (APR registers cleanups for the fd with the pool) */
833 static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
835 apr_close(stream->f);
838 if (apr_remove_file(stream->pathname, stream->p) != 0) {
839 /* ### use a better description? */
840 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
841 "There was a problem removing (rolling "
842 "back) the resource "
843 "when it was being closed.");
850 static dav_error * dav_fs_read_stream(dav_stream *stream,
851 void *buf, apr_size_t *bufsize)
853 if (apr_read(stream->f, buf, (apr_ssize_t *)bufsize) != APR_SUCCESS) {
854 /* ### use something besides 500? */
855 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
856 "An error occurred while reading from a "
862 static dav_error * dav_fs_write_stream(dav_stream *stream,
863 const void *buf, apr_size_t bufsize)
867 status = apr_full_write(stream->f, buf, bufsize, NULL);
868 if (status == APR_ENOSPC) {
869 return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
870 "There is not enough storage to write to "
873 else if (status != APR_SUCCESS) {
874 /* ### use something besides 500? */
875 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
876 "An error occurred while writing to a "
882 static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
884 if (apr_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
885 /* ### should check whether apr_seek set abs_pos was set to the
886 * correct position? */
887 /* ### use something besides 500? */
888 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
889 "Could not seek to specified position in the "
895 static dav_error * dav_fs_set_headers(request_rec *r,
896 const dav_resource *resource)
898 /* ### this function isn't really used since we have a get_pathname */
899 #if DEBUG_GET_HANDLER
900 if (!resource->exists)
903 /* make sure the proper mtime is in the request record */
904 ap_update_mtime(r, resource->info->finfo.mtime);
906 /* ### note that these use r->filename rather than <resource> */
907 ap_set_last_modified(r);
910 /* we accept byte-ranges */
911 apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
913 /* set up the Content-Length header */
914 ap_set_content_length(r, resource->info->finfo.size);
916 /* ### how to set the content type? */
917 /* ### until this is resolved, the Content-Type header is busted */
924 #if DEBUG_PATHNAME_STYLE
925 static const char * dav_fs_get_pathname(
926 const dav_resource *resource,
927 void **free_handle_p)
929 return resource->info->pathname;
933 static void dav_fs_free_file(void *free_handle)
935 /* nothing to free ... */
938 static dav_error * dav_fs_create_collection(dav_resource *resource)
940 dav_resource_private *ctx = resource->info;
943 status = apr_make_dir(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
944 if (status == ENOSPC) {
945 return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
946 "There is not enough storage to create "
949 else if (status != APR_SUCCESS) {
950 /* ### refine this error message? */
951 return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
952 "Unable to create collection.");
955 /* update resource state to show it exists as a collection */
956 resource->exists = 1;
957 resource->collection = 1;
962 static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
965 dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
966 dav_resource_private *srcinfo = wres->resource->info;
967 dav_resource_private *dstinfo = ctx->res_dst->info;
968 dav_error *err = NULL;
970 if (wres->resource->collection) {
971 if (calltype == DAV_CALLTYPE_POSTFIX) {
972 /* Postfix call for MOVE. delete the source dir.
973 * Note: when copying, we do not enable the postfix-traversal.
975 /* ### we are ignoring any error here; what should we do? */
976 (void) rmdir(srcinfo->pathname);
979 /* copy/move of a collection. Create the new, target collection */
980 if (apr_make_dir(dstinfo->pathname, APR_OS_DEFAULT,
981 ctx->pool) != APR_SUCCESS) {
982 /* ### assume it was a permissions problem */
983 /* ### need a description here */
984 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
989 err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
990 srcinfo->pathname, dstinfo->pathname,
992 /* ### push a higher-level description? */
996 ** If we have a "not so bad" error, then it might need to go into a
997 ** multistatus response.
999 ** For a MOVE, it will always go into the multistatus. It could be
1000 ** that everything has been moved *except* for the root. Using a
1001 ** multistatus (with no errors for the other resources) will signify
1004 ** For a COPY, we are traversing in a prefix fashion. If the root fails,
1005 ** then we can just bail out now.
1008 && !ap_is_HTTP_SERVER_ERROR(err->status)
1010 || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
1011 /* ### use errno to generate DAV:responsedescription? */
1012 dav_add_response(wres, err->status, NULL);
1014 /* the error is in the multistatus now. do not stop the traversal. */
1021 static dav_error *dav_fs_copymove_resource(
1023 const dav_resource *src,
1024 const dav_resource *dst,
1026 dav_response **response)
1028 dav_error *err = NULL;
1029 dav_buffer work_buf = { 0 };
1033 /* if a collection, recursively copy/move it and its children,
1034 * including the state dirs
1036 if (src->collection) {
1037 dav_walk_params params = { 0 };
1038 dav_response *multi_status;
1040 params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
1041 params.func = dav_fs_copymove_walker;
1042 params.pool = src->info->pool;
1045 /* params.walk_ctx is managed by dav_fs_internal_walk() */
1047 /* postfix is needed for MOVE to delete source dirs */
1049 params.walk_type |= DAV_WALKTYPE_POSTFIX;
1051 /* note that we return the error OR the multistatus. never both */
1053 if ((err = dav_fs_internal_walk(¶ms, depth, is_move, dst,
1054 &multi_status)) != NULL) {
1055 /* on a "real" error, then just punt. nothing else to do. */
1059 if ((*response = multi_status) != NULL) {
1060 /* some multistatus responses exist. wrap them in a 207 */
1061 return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
1062 "Error(s) occurred on some resources during "
1063 "the COPY/MOVE process.");
1069 /* not a collection */
1070 if ((err = dav_fs_copymove_file(is_move, src->info->pool,
1071 src->info->pathname, dst->info->pathname,
1072 &work_buf)) != NULL) {
1073 /* ### push a higher-level description? */
1077 /* copy/move properties as well */
1078 return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
1081 static dav_error * dav_fs_copy_resource(
1082 const dav_resource *src,
1085 dav_response **response)
1090 if (src->hooks != dst->hooks) {
1092 ** ### strictly speaking, this is a design error; we should not
1093 ** ### have reached this point.
1095 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1096 "DESIGN ERROR: a mix of repositories "
1097 "was passed to copy_resource.");
1101 if ((err = dav_fs_copymove_resource(0, src, dst, depth,
1102 response)) == NULL) {
1104 /* update state of destination resource to show it exists */
1106 dst->collection = src->collection;
1112 static dav_error * dav_fs_move_resource(
1115 dav_response **response)
1117 dav_resource_private *srcinfo = src->info;
1118 dav_resource_private *dstinfo = dst->info;
1123 if (src->hooks != dst->hooks) {
1125 ** ### strictly speaking, this is a design error; we should not
1126 ** ### have reached this point.
1128 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1129 "DESIGN ERROR: a mix of repositories "
1130 "was passed to move_resource.");
1134 /* determine whether a simple rename will work.
1135 * Assume source exists, else we wouldn't get called.
1137 if (dstinfo->finfo.protection != 0) {
1138 if (dstinfo->finfo.device == srcinfo->finfo.device) {
1139 /* target exists and is on the same device. */
1144 const char *dirpath;
1147 /* destination does not exist, but the parent directory should,
1150 dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
1151 if (apr_stat(&finfo, dirpath, APR_FINFO_NORM, dstinfo->pool) == 0
1152 && finfo.device == srcinfo->finfo.device) {
1157 /* if we can't simply rename, then do it the hard way... */
1159 if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
1160 response)) == NULL) {
1161 /* update resource states */
1163 dst->collection = src->collection;
1165 src->collection = 0;
1171 /* a rename should work. do it, and move properties as well */
1173 /* no multistatus response */
1176 /* ### APR has no rename? */
1177 if (apr_rename_file(srcinfo->pathname, dstinfo->pathname,
1178 srcinfo->pool) != APR_SUCCESS) {
1179 /* ### should have a better error than this. */
1180 return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1181 "Could not rename resource.");
1184 /* update resource states */
1186 dst->collection = src->collection;
1188 src->collection = 0;
1190 if ((err = dav_fs_copymoveset(1, src->info->pool,
1191 src, dst, NULL)) == NULL) {
1192 /* no error. we're done. go ahead and return now. */
1196 /* error occurred during properties move; try to put resource back */
1197 if (apr_rename_file(dstinfo->pathname, srcinfo->pathname,
1198 srcinfo->pool) != APR_SUCCESS) {
1199 /* couldn't put it back! */
1200 return dav_push_error(srcinfo->pool,
1201 HTTP_INTERNAL_SERVER_ERROR, 0,
1202 "The resource was moved, but a failure "
1203 "occurred during the move of its "
1204 "properties. The resource could not be "
1205 "restored to its original location. The "
1206 "server is now in an inconsistent state.",
1210 /* update resource states again */
1212 src->collection = dst->collection;
1214 dst->collection = 0;
1216 /* resource moved back, but properties may be inconsistent */
1217 return dav_push_error(srcinfo->pool,
1218 HTTP_INTERNAL_SERVER_ERROR, 0,
1219 "The resource was moved, but a failure "
1220 "occurred during the move of its properties. "
1221 "The resource was moved back to its original "
1222 "location, but its properties may have been "
1223 "partially moved. The server may be in an "
1224 "inconsistent state.",
1228 static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
1230 dav_resource_private *info = wres->resource->info;
1232 /* do not attempt to remove a null resource,
1233 * or a collection with children
1235 if (wres->resource->exists &&
1236 (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
1237 /* try to remove the resource */
1240 result = wres->resource->collection
1241 ? rmdir(info->pathname)
1242 : remove(info->pathname);
1245 ** If an error occurred, then add it to multistatus response.
1246 ** Note that we add it for the root resource, too. It is quite
1247 ** possible to delete the whole darn tree, yet fail on the root.
1249 ** (also: remember we are deleting via a postfix traversal)
1252 /* ### assume there is a permissions problem */
1254 /* ### use errno to generate DAV:responsedescription? */
1255 dav_add_response(wres, HTTP_FORBIDDEN, NULL);
1262 static dav_error * dav_fs_remove_resource(dav_resource *resource,
1263 dav_response **response)
1265 dav_resource_private *info = resource->info;
1269 /* if a collection, recursively remove it and its children,
1270 * including the state dirs
1272 if (resource->collection) {
1273 dav_walk_params params = { 0 };
1274 dav_error *err = NULL;
1275 dav_response *multi_status;
1277 params.walk_type = (DAV_WALKTYPE_NORMAL
1278 | DAV_WALKTYPE_HIDDEN
1279 | DAV_WALKTYPE_POSTFIX);
1280 params.func = dav_fs_delete_walker;
1281 params.pool = info->pool;
1282 params.root = resource;
1284 if ((err = dav_fs_walk(¶ms, DAV_INFINITY,
1285 &multi_status)) != NULL) {
1286 /* on a "real" error, then just punt. nothing else to do. */
1290 if ((*response = multi_status) != NULL) {
1291 /* some multistatus responses exist. wrap them in a 207 */
1292 return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
1293 "Error(s) occurred on some resources during "
1294 "the deletion process.");
1297 /* no errors... update resource state */
1298 resource->exists = 0;
1299 resource->collection = 0;
1304 /* not a collection; remove the file and its properties */
1305 if (remove(info->pathname) != 0) {
1306 /* ### put a description in here */
1307 return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
1310 /* update resource state */
1311 resource->exists = 0;
1312 resource->collection = 0;
1314 /* remove properties and return its result */
1315 return dav_fs_deleteset(info->pool, resource);
1318 /* ### move this to dav_util? */
1319 /* Walk recursively down through directories, *
1320 * including lock-null resources as we go. */
1321 static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
1323 const dav_walk_params *params = fsctx->params;
1324 apr_pool_t *pool = params->pool;
1325 dav_error *err = NULL;
1326 int isdir = fsctx->res1.collection;
1330 /* ensure the context is prepared properly, then call the func */
1331 err = (*params->func)(&fsctx->wres,
1333 ? DAV_CALLTYPE_COLLECTION
1334 : DAV_CALLTYPE_MEMBER);
1339 if (depth == 0 || !isdir) {
1343 /* put a trailing slash onto the directory, in preparation for appending
1344 * files to it as we discovery them within the directory */
1345 dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
1346 fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
1347 fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */
1349 /* if a secondary path is present, then do that, too */
1350 if (fsctx->path2.buf != NULL) {
1351 dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
1352 fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
1353 fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */
1356 /* Note: the URI should ALREADY have a trailing "/" */
1358 /* for this first pass of files, all resources exist */
1359 fsctx->res1.exists = 1;
1361 /* a file is the default; we'll adjust if we hit a directory */
1362 fsctx->res1.collection = 0;
1363 fsctx->res2.collection = 0;
1365 /* open and scan the directory */
1366 if ((apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
1367 /* ### need a better error */
1368 return dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1370 while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
1373 len = strlen(dirent.name);
1375 /* avoid recursing into our current, parent, or state directories */
1376 if (dirent.name[0] == '.'
1377 && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
1381 if (params->walk_type & DAV_WALKTYPE_AUTH) {
1382 /* ### need to authorize each file */
1383 /* ### example: .htaccess is normally configured to fail auth */
1385 /* stuff in the state directory is never authorized! */
1386 if (!strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1390 /* skip the state dir unless a HIDDEN is performed */
1391 if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
1392 && !strcmp(dirent.name, DAV_FS_STATE_DIR)) {
1396 /* append this file onto the path buffer (copy null term) */
1397 dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
1400 /* ### Optimize me, dirent can give us what we need! */
1401 if (apr_lstat(&fsctx->info1.finfo, fsctx->path1.buf,
1402 APR_FINFO_NORM, pool) != APR_SUCCESS) {
1403 /* woah! where'd it go? */
1404 /* ### should have a better error here */
1405 err = dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
1409 /* copy the file to the URI, too. NOTE: we will pad an extra byte
1410 for the trailing slash later. */
1411 dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
1413 /* if there is a secondary path, then do that, too */
1414 if (fsctx->path2.buf != NULL) {
1415 dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
1418 /* set up the (internal) pathnames for the two resources */
1419 fsctx->info1.pathname = fsctx->path1.buf;
1420 fsctx->info2.pathname = fsctx->path2.buf;
1422 /* set up the URI for the current resource */
1423 fsctx->res1.uri = fsctx->uri_buf.buf;
1425 /* ### for now, only process regular files (e.g. skip symlinks) */
1426 if (fsctx->info1.finfo.filetype == APR_REG) {
1427 /* call the function for the specified dir + file */
1428 if ((err = (*params->func)(&fsctx->wres,
1429 DAV_CALLTYPE_MEMBER)) != NULL) {
1430 /* ### maybe add a higher-level description? */
1434 else if (fsctx->info1.finfo.filetype == APR_DIR) {
1435 apr_size_t save_path_len = fsctx->path1.cur_len;
1436 apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
1437 apr_size_t save_path2_len = fsctx->path2.cur_len;
1439 /* adjust length to incorporate the subdir name */
1440 fsctx->path1.cur_len += len;
1441 fsctx->path2.cur_len += len;
1443 /* adjust URI length to incorporate subdir and a slash */
1444 fsctx->uri_buf.cur_len += len + 1;
1445 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
1446 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
1448 /* switch over to a collection */
1449 fsctx->res1.collection = 1;
1450 fsctx->res2.collection = 1;
1452 /* recurse on the subdir */
1453 /* ### don't always want to quit on error from single child */
1454 if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
1455 /* ### maybe add a higher-level description? */
1459 /* put the various information back */
1460 fsctx->path1.cur_len = save_path_len;
1461 fsctx->path2.cur_len = save_path2_len;
1462 fsctx->uri_buf.cur_len = save_uri_len;
1464 fsctx->res1.collection = 0;
1465 fsctx->res2.collection = 0;
1467 /* assert: res1.exists == 1 */
1471 /* ### check the return value of this? */
1472 apr_dir_close(dirp);
1477 if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
1478 apr_size_t offset = 0;
1480 /* null terminate the directory name */
1481 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
1483 /* Include any lock null resources found in this collection */
1484 fsctx->res1.collection = 1;
1485 if ((err = dav_fs_get_locknull_members(&fsctx->res1,
1486 &fsctx->locknull_buf)) != NULL) {
1487 /* ### maybe add a higher-level description? */
1491 /* put a slash back on the end of the directory */
1492 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
1494 /* these are all non-existant (files) */
1495 fsctx->res1.exists = 0;
1496 fsctx->res1.collection = 0;
1497 memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
1499 while (offset < fsctx->locknull_buf.cur_len) {
1500 apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
1501 dav_lock *locks = NULL;
1504 ** Append the locknull file to the paths and the URI. Note that
1505 ** we don't have to pad the URI for a slash since a locknull
1506 ** resource is not a collection.
1508 dav_buffer_place_mem(pool, &fsctx->path1,
1509 fsctx->locknull_buf.buf + offset, len + 1, 0);
1510 dav_buffer_place_mem(pool, &fsctx->uri_buf,
1511 fsctx->locknull_buf.buf + offset, len + 1, 0);
1512 if (fsctx->path2.buf != NULL) {
1513 dav_buffer_place_mem(pool, &fsctx->path2,
1514 fsctx->locknull_buf.buf + offset,
1518 /* set up the (internal) pathnames for the two resources */
1519 fsctx->info1.pathname = fsctx->path1.buf;
1520 fsctx->info2.pathname = fsctx->path2.buf;
1522 /* set up the URI for the current resource */
1523 fsctx->res1.uri = fsctx->uri_buf.buf;
1526 ** To prevent a PROPFIND showing an expired locknull
1527 ** resource, query the lock database to force removal
1528 ** of both the lock entry and .locknull, if necessary..
1529 ** Sure, the query in PROPFIND would do this.. after
1530 ** the locknull resource was already included in the
1533 ** NOTE: we assume the caller has opened the lock database
1534 ** if they have provided DAV_WALKTYPE_LOCKNULL.
1536 /* ### we should also look into opening it read-only and
1537 ### eliding timed-out items from the walk, yet leaving
1538 ### them in the locknull database until somebody opens
1539 ### the thing writable.
1541 /* ### probably ought to use has_locks. note the problem
1542 ### mentioned above, though... we would traverse this as
1543 ### a locknull, but then a PROPFIND would load the lock
1544 ### info, causing a timeout and the locks would not be
1545 ### reported. Therefore, a null resource would be returned
1546 ### in the PROPFIND.
1548 ### alternative: just load unresolved locks. any direct
1549 ### locks will be timed out (correct). any indirect will
1550 ### not (correct; consider if a parent timed out -- the
1551 ### timeout routines do not walk and remove indirects;
1552 ### even the resolve func would probably fail when it
1553 ### tried to find a timed-out direct lock).
1555 if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
1557 /* ### maybe add a higher-level description? */
1561 /* call the function for the specified dir + file */
1562 if (locks != NULL &&
1563 (err = (*params->func)(&fsctx->wres,
1564 DAV_CALLTYPE_LOCKNULL)) != NULL) {
1565 /* ### maybe add a higher-level description? */
1572 /* reset the exists flag */
1573 fsctx->res1.exists = 1;
1576 if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
1577 /* replace the dirs' trailing slashes with null terms */
1578 fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
1579 fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
1580 if (fsctx->path2.buf != NULL) {
1581 fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
1584 /* this is a collection which exists */
1585 fsctx->res1.collection = 1;
1587 return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
1593 static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
1594 int depth, int is_move,
1595 const dav_resource *root_dst,
1596 dav_response **response)
1598 dav_fs_walker_context fsctx = { 0 };
1600 dav_fs_copymove_walk_ctx cm_ctx = { 0 };
1603 if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
1604 && params->lockdb == NULL) {
1605 return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1606 "DESIGN ERROR: walker called to walk locknull "
1607 "resources, but a lockdb was not provided.");
1611 fsctx.params = params;
1612 fsctx.wres.walk_ctx = params->walk_ctx;
1613 fsctx.wres.pool = params->pool;
1615 /* ### zero out versioned, working, baselined? */
1617 fsctx.res1 = *params->root;
1618 fsctx.res1.pool = params->pool;
1620 fsctx.res1.info = &fsctx.info1;
1621 fsctx.info1 = *params->root->info;
1623 /* the pathname is stored in the path1 buffer */
1624 dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
1625 fsctx.info1.pathname = fsctx.path1.buf;
1627 if (root_dst != NULL) {
1628 /* internal call from the COPY/MOVE code. set it up. */
1630 fsctx.wres.walk_ctx = &cm_ctx;
1631 cm_ctx.is_move = is_move;
1632 cm_ctx.res_dst = &fsctx.res2;
1633 cm_ctx.root = params->root;
1634 cm_ctx.pool = params->pool;
1636 fsctx.res2 = *root_dst;
1637 fsctx.res2.exists = 0;
1638 fsctx.res2.collection = 0;
1639 fsctx.res2.uri = NULL; /* we don't track this */
1640 fsctx.res2.pool = params->pool;
1642 fsctx.res2.info = &fsctx.info2;
1643 fsctx.info2 = *root_dst->info;
1645 /* res2 does not exist -- clear its finfo structure */
1646 memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
1648 /* the pathname is stored in the path2 buffer */
1649 dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
1650 fsctx.info2.pathname = fsctx.path2.buf;
1653 /* prep the URI buffer */
1654 dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
1656 /* if we have a directory, then ensure the URI has a trailing "/" */
1657 if (fsctx.res1.collection
1658 && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
1660 /* this will fall into the pad area */
1661 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
1662 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
1665 /* the current resource's URI is stored in the uri_buf buffer */
1666 fsctx.res1.uri = fsctx.uri_buf.buf;
1668 /* point the callback's resource at our structure */
1669 fsctx.wres.resource = &fsctx.res1;
1671 /* always return the error, and any/all multistatus responses */
1672 err = dav_fs_walker(&fsctx, depth);
1673 *response = fsctx.wres.response;
1677 static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
1678 dav_response **response)
1680 /* always return the error, and any/all multistatus responses */
1681 return dav_fs_internal_walk(params, depth, 0, NULL, response);
1684 /* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag
1686 * ### do we need to return weak tags sometimes?
1688 static const char *dav_fs_getetag(const dav_resource *resource)
1690 dav_resource_private *ctx = resource->info;
1692 if (!resource->exists)
1693 return apr_pstrdup(ctx->pool, "");
1695 if (ctx->finfo.protection != 0) {
1696 return apr_psprintf(ctx->pool, "\"%lx-%lx-%lx\"",
1697 (unsigned long) ctx->finfo.inode,
1698 (unsigned long) ctx->finfo.size,
1699 (unsigned long) ctx->finfo.mtime);
1702 return apr_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.mtime);
1705 static const dav_hooks_repository dav_hooks_repository_fs =
1707 DEBUG_GET_HANDLER, /* normally: special GET handling not required */
1708 dav_fs_get_resource,
1709 dav_fs_get_parent_resource,
1710 dav_fs_is_same_resource,
1711 dav_fs_is_parent_resource,
1713 dav_fs_close_stream,
1715 dav_fs_write_stream,
1718 #if DEBUG_PATHNAME_STYLE
1719 dav_fs_get_pathname,
1724 dav_fs_create_collection,
1725 dav_fs_copy_resource,
1726 dav_fs_move_resource,
1727 dav_fs_remove_resource,
1732 static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
1733 int propid, dav_prop_insert what,
1734 ap_text_header *phdr)
1738 apr_pool_t *p = resource->info->pool;
1739 const dav_liveprop_spec *info;
1742 /* an HTTP-date can be 29 chars plus a null term */
1743 /* a 64-bit size can be 20 chars plus a null term */
1744 char buf[DAV_TIMEBUF_SIZE];
1747 ** None of FS provider properties are defined if the resource does not
1748 ** exist. Just bail for this case.
1750 ** Even though we state that the FS properties are not defined, the
1751 ** client cannot store dead values -- we deny that thru the is_writable
1754 if (!resource->exists)
1755 return DAV_PROP_INSERT_NOTDEF;
1758 case DAV_PROPID_creationdate:
1760 ** Closest thing to a creation date. since we don't actually
1761 ** perform the operations that would modify ctime (after we
1762 ** create the file), then we should be pretty safe here.
1764 dav_format_time(DAV_STYLE_ISO8601,
1765 resource->info->finfo.ctime,
1770 case DAV_PROPID_getcontentlength:
1771 /* our property, but not defined on collection resources */
1772 if (resource->collection)
1773 return DAV_PROP_INSERT_NOTDEF;
1775 (void) sprintf(buf, "%ld", resource->info->finfo.size);
1779 case DAV_PROPID_getetag:
1780 value = dav_fs_getetag(resource);
1783 case DAV_PROPID_getlastmodified:
1784 dav_format_time(DAV_STYLE_RFC822,
1785 resource->info->finfo.mtime,
1790 case DAV_PROPID_FS_executable:
1792 /* our property, but not defined on the Win32 platform */
1793 return DAV_PROP_INSERT_NOTDEF;
1795 /* our property, but not defined on collection resources */
1796 if (resource->collection)
1797 return DAV_PROP_INSERT_NOTDEF;
1799 /* the files are "ours" so we only need to check owner exec privs */
1800 if (resource->info->finfo.protection & APR_UEXECUTE)
1808 /* ### what the heck was this property? */
1809 return DAV_PROP_INSERT_NOTDEF;
1812 /* assert: value != NULL */
1814 /* get the information and global NS index for the property */
1815 global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1817 /* assert: info != NULL && info->name != NULL */
1819 /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */
1821 if (what == DAV_PROP_INSERT_VALUE) {
1822 s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
1823 global_ns, info->name, value, global_ns, info->name);
1825 else if (what == DAV_PROP_INSERT_NAME) {
1826 s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
1829 /* assert: what == DAV_PROP_INSERT_SUPPORTED */
1831 "<D:supported-live-property D:name=\"%s\" "
1832 "D:namespace=\"%s\"/>" DEBUG_CR,
1833 info->name, dav_fs_namespace_uris[info->ns]);
1835 ap_text_append(p, phdr, s);
1837 /* we inserted what was asked for */
1841 static int dav_fs_is_writable(const dav_resource *resource, int propid)
1843 const dav_liveprop_spec *info;
1846 /* this property is not usable (writable) on the Win32 platform */
1847 if (propid == DAV_PROPID_FS_executable && !resource->collection)
1851 (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
1852 return info->is_writable;
1855 static dav_error *dav_fs_patch_validate(const dav_resource *resource,
1856 const ap_xml_elem *elem,
1861 const ap_text *cdata;
1862 const ap_text *f_cdata;
1864 dav_elem_private *priv = elem->private;
1866 if (priv->propid != DAV_PROPID_FS_executable) {
1871 if (operation == DAV_PROP_OP_DELETE) {
1872 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1873 "The 'executable' property cannot be removed.");
1876 cdata = elem->first_cdata.first;
1878 /* ### hmm. this isn't actually looking at all the possible text items */
1879 f_cdata = elem->first_child == NULL
1881 : elem->first_child->following_cdata.first;
1883 /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
1885 if (cdata == NULL) {
1886 if (f_cdata == NULL) {
1887 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1888 "The 'executable' property expects a single "
1889 "character, valued 'T' or 'F'. There was no "
1890 "value submitted.");
1894 else if (f_cdata != NULL)
1897 if (cdata->next != NULL || strlen(cdata->text) != 1)
1900 value = cdata->text[0];
1901 if (value != 'T' && value != 'F') {
1902 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1903 "The 'executable' property expects a single "
1904 "character, valued 'T' or 'F'. The value "
1905 "submitted is invalid.");
1908 *context = (void *)(value == 'T');
1913 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
1914 "The 'executable' property expects a single "
1915 "character, valued 'T' or 'F'. The value submitted "
1916 "has too many characters.");
1920 static dav_error *dav_fs_patch_exec(const dav_resource *resource,
1921 const ap_xml_elem *elem,
1924 dav_liveprop_rollback **rollback_ctx)
1926 int value = context != NULL;
1927 apr_fileperms_t perms = resource->info->finfo.protection;
1928 int old_value = (perms & APR_UEXECUTE) != 0;
1930 /* assert: prop == executable. operation == SET. */
1932 /* don't do anything if there is no change. no rollback info either. */
1933 /* DBG2("new value=%d (old=%d)", value, old_value); */
1934 if (value == old_value)
1937 perms &= ~APR_UEXECUTE;
1939 perms |= APR_UEXECUTE;
1941 if (apr_setfileperms(resource->info->pathname, perms) != APR_SUCCESS) {
1942 return dav_new_error(resource->info->pool,
1943 HTTP_INTERNAL_SERVER_ERROR, 0,
1944 "Could not set the executable flag of the "
1945 "target resource.");
1948 /* update the resource and set up the rollback context */
1949 resource->info->finfo.protection = perms;
1950 *rollback_ctx = (dav_liveprop_rollback *)old_value;
1955 static void dav_fs_patch_commit(const dav_resource *resource,
1958 dav_liveprop_rollback *rollback_ctx)
1963 static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
1966 dav_liveprop_rollback *rollback_ctx)
1968 apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
1969 int value = rollback_ctx != NULL;
1971 /* assert: prop == executable. operation == SET. */
1973 /* restore the executable bit */
1975 perms |= APR_UEXECUTE;
1977 if (apr_setfileperms(resource->info->pathname, perms) != APR_SUCCESS) {
1978 return dav_new_error(resource->info->pool,
1979 HTTP_INTERNAL_SERVER_ERROR, 0,
1980 "After a failure occurred, the resource's "
1981 "executable flag could not be restored.");
1984 /* restore the resource's state */
1985 resource->info->finfo.protection = perms;
1991 static const dav_hooks_liveprop dav_hooks_liveprop_fs =
1995 dav_fs_namespace_uris,
1996 dav_fs_patch_validate,
1998 dav_fs_patch_commit,
1999 dav_fs_patch_rollback
2002 static const dav_provider dav_fs_provider =
2004 &dav_hooks_repository_fs,
2006 &dav_hooks_locks_fs,
2011 void dav_fs_gather_propsets(apr_array_header_t *uris)
2014 *(const char **)apr_push_array(uris) =
2015 "<http://apache.org/dav/propset/fs/1>";
2019 int dav_fs_find_liveprop(const dav_resource *resource,
2020 const char *ns_uri, const char *name,
2021 const dav_hooks_liveprop **hooks)
2023 /* don't try to find any liveprops if this isn't "our" resource */
2024 if (resource->hooks != &dav_hooks_repository_fs)
2026 return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
2029 void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
2030 dav_prop_insert what, ap_text_header *phdr)
2032 /* don't insert any liveprops if this isn't "our" resource */
2033 if (resource->hooks != &dav_hooks_repository_fs)
2036 if (!resource->exists) {
2037 /* a lock-null resource */
2039 ** ### technically, we should insert empty properties. dunno offhand
2040 ** ### what part of the spec said this, but it was essentially thus:
2041 ** ### "the properties should be defined, but may have no value".
2046 (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
2048 (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
2050 (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
2052 (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
2057 ** Note: this property is not defined on the Win32 platform.
2058 ** dav_fs_insert_prop() won't insert it, but we may as
2059 ** well not even call it.
2061 (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
2065 /* ### we know the others aren't defined as liveprops */
2068 void dav_fs_register(apr_pool_t *p)
2070 /* register the namespace URIs */
2071 dav_register_liveprop_group(p, &dav_fs_liveprop_group);
2073 /* register the repository provider */
2074 dav_register_provider(p, "filesystem", &dav_fs_provider);