1
/* Licensed to the Apache Software Foundation (ASF) under one or more
2
* contributor license agreements. See the NOTICE file distributed with
3
* this work for additional information regarding copyright ownership.
4
* The ASF licenses this file to You under the Apache License, Version 2.0
5
* (the "License"); you may not use this file except in compliance with
6
* the License. You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
18
** DAV filesystem-based repository provider
22
#include "apr_file_io.h"
23
#include "apr_strings.h"
24
#include "apr_buckets.h"
27
#include <stdio.h> /* for sprintf() */
32
#include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */
33
#include "http_request.h" /* for ap_update_mtime() */
39
/* to assist in debugging mod_dav's GET handling */
40
#define DEBUG_GET_HANDLER 0
42
#define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */
44
/* context needed to identify a resource */
45
struct dav_resource_private {
46
apr_pool_t *pool; /* memory storage pool associated with request */
47
const char *pathname; /* full pathname to resource */
48
apr_finfo_t finfo; /* filesystem info */
51
/* private context for doing a filesystem walk */
53
/* the input walk parameters */
54
const dav_walk_params *params;
56
/* reused as we walk */
57
dav_walk_resource wres;
60
dav_resource_private info1;
64
/* MOVE/COPY need a secondary path */
66
dav_resource_private info2;
69
dav_buffer locknull_buf;
71
} dav_fs_walker_context;
74
int is_move; /* is this a MOVE? */
75
dav_buffer work_buf; /* handy buffer for copymove_file() */
77
/* CALLBACK: this is a secondary resource managed specially for us */
78
const dav_resource *res_dst;
80
/* copied from dav_walk_params (they are invariant across the walk) */
81
const dav_resource *root;
84
} dav_fs_copymove_walk_ctx;
86
/* an internal WALKTYPE to walk hidden files (the .DAV directory) */
87
#define DAV_WALKTYPE_HIDDEN 0x4000
89
/* an internal WALKTYPE to call collections (again) after their contents */
90
#define DAV_WALKTYPE_POSTFIX 0x8000
92
#define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */
95
/* pull this in from the other source file */
96
extern const dav_hooks_locks dav_hooks_locks_fs;
98
/* forward-declare the hook structures */
99
static const dav_hooks_repository dav_hooks_repository_fs;
100
static const dav_hooks_liveprop dav_hooks_liveprop_fs;
103
** The namespace URIs that we use. This list and the enumeration must
106
static const char * const dav_fs_namespace_uris[] =
109
"http://apache.org/dav/props/",
114
DAV_FS_URI_DAV, /* the DAV: namespace URI */
115
DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */
119
** Does this platform support an executable flag?
121
** ### need a way to portably abstract this query
124
#define DAV_FS_HAS_EXECUTABLE
128
** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
130
#define DAV_PROPID_FS_executable 1
132
static const dav_liveprop_spec dav_fs_props[] =
134
/* standard DAV properties */
138
DAV_PROPID_creationdate,
144
DAV_PROPID_getcontentlength,
156
DAV_PROPID_getlastmodified,
160
/* our custom properties */
164
DAV_PROPID_FS_executable,
165
0 /* handled special in dav_fs_is_writable */
171
static const dav_liveprop_group dav_fs_liveprop_group =
174
dav_fs_namespace_uris,
175
&dav_hooks_liveprop_fs
179
/* define the dav_stream structure for our use */
183
const char *pathname; /* we may need to remove it at close time */
186
/* returns an appropriate HTTP status code given an APR status code for a
187
* failed I/O operation. ### use something besides 500? */
188
#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
189
HTTP_INTERNAL_SERVER_ERROR)
191
/* forward declaration for internal treewalkers */
192
static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
193
dav_response **response);
194
static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
195
int depth, int is_move,
196
const dav_resource *root_dst,
197
dav_response **response);
199
/* --------------------------------------------------------------------
201
** PRIVATE REPOSITORY FUNCTIONS
203
apr_pool_t *dav_fs_pool(const dav_resource *resource)
205
return resource->info->pool;
208
const char *dav_fs_pathname(const dav_resource *resource)
210
return resource->info->pathname;
213
dav_error * dav_fs_dir_file_name(
214
const dav_resource *resource,
215
const char **dirpath_p,
216
const char **fname_p)
218
dav_resource_private *ctx = resource->info;
220
if (resource->collection) {
221
*dirpath_p = ctx->pathname;
226
const char *testpath, *rootpath;
227
char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
228
apr_size_t dirlen = strlen(dirpath);
229
apr_status_t rv = APR_SUCCESS;
233
rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
236
/* remove trailing slash from dirpath, unless it's a root path
238
if ((rv == APR_SUCCESS && testpath && *testpath)
239
|| rv == APR_ERELATIVE) {
240
if (dirpath[dirlen - 1] == '/') {
241
dirpath[dirlen - 1] = '\0';
245
/* ###: Looks like a response could be appropriate
247
* APR_SUCCESS here tells us the dir is a root
248
* APR_ERELATIVE told us we had no root (ok)
249
* APR_EINCOMPLETE an incomplete testpath told us
250
* there was no -file- name here!
251
* APR_EBADPATH or other errors tell us this file
252
* path is undecipherable
255
if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
256
*dirpath_p = dirpath;
258
*fname_p = ctx->pathname + dirlen;
261
return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
262
"An incomplete/bad path was found in "
263
"dav_fs_dir_file_name.");
270
/* Note: picked up from ap_gm_timestr_822() */
271
/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
272
static void dav_format_time(int style, apr_time_t sec, char *buf)
276
/* ### what to do if fails? */
277
(void) apr_time_exp_gmt(&tms, sec);
279
if (style == DAV_STYLE_ISO8601) {
280
/* ### should we use "-00:00" instead of "Z" ?? */
282
/* 20 chars plus null term */
283
sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
284
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
285
tms.tm_hour, tms.tm_min, tms.tm_sec);
289
/* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
291
/* 29 chars plus null term */
293
"%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
294
apr_day_snames[tms.tm_wday],
295
tms.tm_mday, apr_month_snames[tms.tm_mon],
297
tms.tm_hour, tms.tm_min, tms.tm_sec);
300
/* Copy or move src to dst; src_finfo is used to propagate permissions
301
* bits across if non-NULL; dst_finfo must be non-NULL iff dst already
303
static dav_error * dav_fs_copymove_file(
308
const apr_finfo_t *src_finfo,
309
const apr_finfo_t *dst_finfo,
312
dav_buffer work_buf = { 0 };
313
apr_file_t *inf = NULL;
314
apr_file_t *outf = NULL;
316
apr_fileperms_t perms;
321
/* Determine permissions to use for destination */
322
if (src_finfo && src_finfo->valid & APR_FINFO_PROT
323
&& src_finfo->protection & APR_UEXECUTE) {
324
perms = src_finfo->protection;
326
if (dst_finfo != NULL) {
327
/* chmod it if it already exist */
328
if (apr_file_perms_set(dst, perms)) {
329
return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
330
"Could not set permissions on destination");
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 *)((long)(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
long value = context != NULL;
2010
apr_fileperms_t perms = resource->info->finfo.protection;
2011
long 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);