/* ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved. ** ** By using this file, you agree to the terms and conditions set forth in ** the LICENSE.html file which can be found at the top level of the mod_dav ** distribution or at http://www.webdav.org/mod_dav/license-1.html. ** ** Contact information: ** Greg Stein, PO Box 760, Palo Alto, CA, 94302 ** gstein@lyra.org, http://www.webdav.org/mod_dav/ ** */ /* ** DAV filesystem-based repository provider ** ** Written 08/99 by John Vasta, vasta@rational.com, by separating ** mod_dav into repository-independent and provider modules. */ #include #include "httpd.h" #include "http_log.h" #include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */ #include "http_request.h" /* for ap_update_mtime() */ #include "mod_dav.h" #include "dav_fs_repos.h" /* to assist in debugging mod_dav's GET handling */ #define DEBUG_GET_HANDLER 0 #define DEBUG_PATHNAME_STYLE 0 #define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */ /* context needed to identify a resource */ struct dav_resource_private { pool *pool; /* memory storage pool associated with request */ const char *pathname; /* full pathname to resource */ struct stat finfo; /* filesystem info */ }; /* private context for doing a filesystem walk */ typedef struct { dav_walker_ctx *wctx; dav_resource res1; dav_resource res2; dav_resource_private info1; dav_resource_private info2; dav_buffer path1; dav_buffer path2; dav_buffer locknull_buf; } dav_fs_walker_context; /* pull this in from the other source file */ extern const dav_hooks_locks dav_hooks_locks_fs; /* forward-declare this sucker */ static const dav_hooks_repository dav_hooks_repository_fs; /* ** The Provider ID is used to differentiate "logical" providers that use ** the same set of hook functions. Essentially, the ID is an instance ** handle and the hooks are a vtable. ** ** In this module, we only have a single provider for each type, so we ** actually ignore the Provider ID. */ #define DAV_FS_PROVIDER_ID 0 /* ** The namespace URIs that we use. This list and the enumeration must ** stay in sync. */ static const char * const dav_fs_namespace_uris[] = { "DAV:", "http://apache.org/dav/props/", NULL /* sentinel */ }; enum { DAV_FS_URI_DAV, /* the DAV: namespace URI */ DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */ }; /* ** The properties that we define. */ enum { /* using DAV_FS_URI_DAV */ DAV_PROPID_FS_creationdate = DAV_PROPID_FS, DAV_PROPID_FS_displayname, DAV_PROPID_FS_getcontentlength, DAV_PROPID_FS_getetag, DAV_PROPID_FS_getlastmodified, DAV_PROPID_FS_source, /* using DAV_FS_URI_MYPROPS */ DAV_PROPID_FS_executable }; /* NOTE: the magic "200" is derived from the ranges in mod_dav.h */ #define DAV_PROPID_FS_OURS(id) (DAV_PROPID_FS <= (id) && \ (id) < DAV_PROPID_FS + 200) typedef struct { int ns; const char * name; int propid; } dav_fs_liveprop_name; static const dav_fs_liveprop_name dav_fs_props[] = { { DAV_FS_URI_DAV, "creationdate", DAV_PROPID_FS_creationdate }, { DAV_FS_URI_DAV, "getcontentlength", DAV_PROPID_FS_getcontentlength }, { DAV_FS_URI_DAV, "getetag", DAV_PROPID_FS_getetag }, { DAV_FS_URI_DAV, "getlastmodified", DAV_PROPID_FS_getlastmodified }, { DAV_FS_URI_MYPROPS, "executable", DAV_PROPID_FS_executable }, /* ### these aren't FS specific */ { DAV_FS_URI_DAV, "displayname", DAV_PROPID_FS_displayname }, { DAV_FS_URI_DAV, "source", DAV_PROPID_FS_source }, { 0 } /* sentinel */ }; /* define the dav_stream structure for our use */ struct dav_stream { pool *p; int fd; const char *pathname; /* we may need to remove it at close time */ }; /* forward declaration for internal treewalkers */ static dav_error * dav_fs_walk(dav_walker_ctx *wctx, int depth); /* -------------------------------------------------------------------- ** ** PRIVATE REPOSITORY FUNCTIONS */ pool *dav_fs_pool(const dav_resource *resource) { return resource->info->pool; } const char *dav_fs_pathname(const dav_resource *resource) { return resource->info->pathname; } void dav_fs_dir_file_name( const dav_resource *resource, const char **dirpath_p, const char **fname_p) { dav_resource_private *ctx = resource->info; if (resource->collection) { *dirpath_p = ctx->pathname; if (fname_p != NULL) *fname_p = NULL; } else { char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname); size_t dirlen = strlen(dirpath); if (fname_p != NULL) *fname_p = ctx->pathname + dirlen; *dirpath_p = dirpath; /* remove trailing slash from dirpath, unless it's the root dir */ /* ### Win32 check */ if (dirlen > 1 && dirpath[dirlen - 1] == '/') { dirpath[dirlen - 1] = '\0'; } } } /* Note: picked up from ap_gm_timestr_822() */ /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */ static void dav_format_time(int style, time_t sec, char *buf) { struct tm *tms; tms = gmtime(&sec); if (style == DAV_STYLE_ISO8601) { /* ### should we use "-00:00" instead of "Z" ?? */ /* 20 chars plus null term */ sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ", tms->tm_year + 1900, tms->tm_mon + 1, tms->tm_mday, tms->tm_hour, tms->tm_min, tms->tm_sec); return; } /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */ /* 29 chars plus null term */ sprintf(buf, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", ap_day_snames[tms->tm_wday], tms->tm_mday, ap_month_snames[tms->tm_mon], tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); } static int dav_sync_write(int fd, const char *buf, ssize_t bufsize) { ssize_t amt; do { amt = write(fd, buf, bufsize); if (amt > 0) { bufsize -= amt; buf += amt; } } while (amt > 0 && bufsize > 0); return amt < 0 ? -1 : 0; } static dav_error * dav_fs_copymove_file( int is_move, pool * p, const char *src, const char *dst, const struct stat *src_finfo, const struct stat *dst_finfo, dav_buffer *pbuf) { dav_buffer work_buf = { 0 }; int fdi; int fdo; mode_t mode; if (pbuf == NULL) pbuf = &work_buf; mode = src_finfo->st_mode; /* chmod() the destination if the source is executable, and the * destination already exists. */ if ((mode & DAV_FS_MODE_XUSR) && (dst_finfo != NULL) && (dst_finfo->st_mode != 0)) { if (chmod(dst, mode) == -1) { return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not set permissions on destination"); } } dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE); if ((fdi = open(src, O_RDONLY | O_BINARY)) == -1) { /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not open file for reading"); } /* ### do we need to deal with the umask? */ if ((fdo = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode )) == -1) { close(fdi); /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not open file for writing"); } while (1) { ssize_t len = read(fdi, pbuf->buf, DAV_FS_COPY_BLOCKSIZE); if (len == -1) { close(fdi); close(fdo); if (remove(dst) != 0) { /* ### ACK! Inconsistent state... */ /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not delete output after read " "failure. Server is now in an " "inconsistent state."); } /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not read input file"); } if (len == 0) break; if (dav_sync_write(fdo, pbuf->buf, len) != 0) { int save_errno = errno; close(fdi); close(fdo); if (remove(dst) != 0) { /* ### ACK! Inconsistent state... */ /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not delete output after write " "failure. Server is now in an " "inconsistent state."); } if (save_errno == ENOSPC) { return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0, "There is not enough storage to write to " "this resource."); } /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not write output file"); } } close(fdi); close(fdo); if (is_move && remove(src) != 0) { dav_error *err; int save_errno = errno; /* save the errno that got us here */ if (remove(dst) != 0) { /* ### ACK. this creates an inconsistency. do more!? */ /* ### use something besides 500? */ /* Note that we use the latest errno */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not remove source or destination " "file. Server is now in an inconsistent " "state."); } /* ### use something besides 500? */ err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not remove source file after move. " "Destination was removed to ensure consistency."); err->save_errno = save_errno; return err; } return NULL; } /* copy/move a file from within a state dir to another state dir */ /* ### need more buffers to replace the pool argument */ static dav_error * dav_fs_copymove_state( int is_move, pool * p, const char *src_dir, const char *src_file, const char *dst_dir, const char *dst_file, dav_buffer *pbuf) { struct stat src_finfo; /* finfo for source file */ struct stat dst_state_finfo; /* finfo for STATE directory */ const char *src; const char *dst; /* build the propset pathname for the source file */ src = ap_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL); /* the source file doesn't exist */ if (stat(src, &src_finfo) != 0) { return NULL; } /* build the pathname for the destination state dir */ dst = ap_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL); /* ### do we need to deal with the umask? */ /* ensure that it exists */ if (mkdir(dst, DAV_FS_MODE_DIR) != 0) { if (errno != EEXIST) { /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not create internal state directory"); } } /* get info about the state directory */ if (stat(dst, &dst_state_finfo) != 0) { /* Ack! Where'd it go? */ /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "State directory disappeared"); } /* The mkdir() may have failed because a *file* exists there already */ if (!S_ISDIR(dst_state_finfo.st_mode)) { /* ### try to recover by deleting this file? (and mkdir again) */ /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "State directory is actually a file"); } /* append the target file to the state directory pathname */ dst = ap_pstrcat(p, dst, "/", dst_file, NULL); /* copy/move the file now */ if (is_move && src_finfo.st_dev == dst_state_finfo.st_dev) { /* simple rename is possible since it is on the same device */ if (rename(src, dst) != 0) { /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not move state file."); } } else { /* gotta copy (and delete). * We don't have the finfo of the destination *file*, * only the destination *directory*, so pass NULL. */ return dav_fs_copymove_file(is_move, p, src, dst, &src_finfo, NULL, pbuf); } return NULL; } static dav_error *dav_fs_copymoveset(int is_move, pool *p, const dav_resource *src, const dav_resource *dst, dav_buffer *pbuf) { const char *src_dir; const char *src_file; const char *src_state1; const char *src_state2; const char *dst_dir; const char *dst_file; const char *dst_state1; const char *dst_state2; dav_error *err; /* Get directory and filename for resources */ dav_fs_dir_file_name(src, &src_dir, &src_file); dav_fs_dir_file_name(dst, &dst_dir, &dst_file); /* Get the corresponding state files for each resource */ dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2); dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2); #if DAV_DEBUG if ((src_state2 != NULL && dst_state2 == NULL) || (src_state2 == NULL && dst_state2 != NULL)) { return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "DESIGN ERROR: dav_dbm_get_statefiles() " "returned inconsistent results."); } #endif err = dav_fs_copymove_state(is_move, p, src_dir, src_state1, dst_dir, dst_state1, pbuf); if (err == NULL && src_state2 != NULL) { err = dav_fs_copymove_state(is_move, p, src_dir, src_state2, dst_dir, dst_state2, pbuf); if (err != NULL) { /* ### CRAP. inconsistency. */ /* ### should perform some cleanup at the target if we still ### have the original files */ /* Change the error to reflect the bad server state. */ err->status = HTTP_INTERNAL_SERVER_ERROR; err->desc = "Could not fully copy/move the properties. " "The server is now in an inconsistent state."; } } return err; } static dav_error *dav_fs_deleteset(pool *p, const dav_resource *resource) { const char *dirpath; const char *fname; const char *state1; const char *state2; const char *pathname; /* Get directory, filename, and state-file names for the resource */ dav_fs_dir_file_name(resource, &dirpath, &fname); dav_dbm_get_statefiles(p, fname, &state1, &state2); /* build the propset pathname for the file */ pathname = ap_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/", state1, NULL); /* note: we may get ENOENT if the state dir is not present */ if (remove(pathname) != 0 && errno != ENOENT) { return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not remove properties."); } if (state2 != NULL) { /* build the propset pathname for the file */ pathname = ap_pstrcat(p, dirpath, "/" DAV_FS_STATE_DIR "/", state2, NULL); if (remove(pathname) != 0 && errno != ENOENT) { /* ### CRAP. only removed half. */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not fully remove properties. " "The server is now in an inconsistent " "state."); } } return NULL; } /* -------------------------------------------------------------------- ** ** REPOSITORY HOOK FUNCTIONS */ static dav_resource * dav_fs_get_resource( request_rec *r, const char *root_dir, const char *workspace) { dav_resource_private *ctx; dav_resource *resource; char *s; char *filename; size_t len; /* ### optimize this into a single allocation! */ /* Create private resource context descriptor */ ctx = ap_pcalloc(r->pool, sizeof(*ctx)); ctx->pool = r->pool; ctx->finfo = r->finfo; (void) ap_update_mtime(r, r->finfo.st_mtime); /* Preserve case on OSes which fold canonical filenames */ #if MODULE_MAGIC_NUMBER_MAJOR > 19990320 || (MODULE_MAGIC_NUMBER_MAJOR == 19990320 && MODULE_MAGIC_NUMBER_MINOR >= 8) filename = r->case_preserved_filename; #else filename = r->filename; #endif /* ** If there is anything in the path_info, then this indicates that the ** entire path was not used to specify the file/dir. We want to append ** it onto the filename so that we get a "valid" pathname for null ** resources. */ s = ap_pstrcat(r->pool, filename, r->path_info, NULL); /* make sure the pathname does not have a trailing "/" */ len = strlen(s); if (len > 1 && s[len - 1] == '/') { s[len - 1] = '\0'; } ctx->pathname = s; /* Create resource descriptor */ resource = ap_pcalloc(r->pool, sizeof(*resource)); resource->type = DAV_RESOURCE_TYPE_REGULAR; resource->info = ctx; resource->hooks = &dav_hooks_repository_fs; /* make sure the URI does not have a trailing "/" */ len = strlen(r->uri); if (len > 1 && r->uri[len - 1] == '/') { s = ap_pstrdup(r->pool, r->uri); s[len - 1] = '\0'; resource->uri = s; } else { resource->uri = r->uri; } if (r->finfo.st_mode != 0) { resource->exists = 1; resource->collection = S_ISDIR(r->finfo.st_mode); /* unused info in the URL will indicate a null resource */ if (r->path_info != NULL && *r->path_info != '\0') { if (resource->collection) { /* only a trailing "/" is allowed */ if (*r->path_info != '/' || r->path_info[1] != '\0') { /* ** This URL/filename represents a locknull resource or ** possibly a destination of a MOVE/COPY */ resource->exists = 0; resource->collection = 0; } } else { /* ** The base of the path refers to a file -- nothing should ** be in path_info. The resource is simply an error: it ** can't be a null or a locknull resource. */ return NULL; /* becomes HTTP_NOT_FOUND */ } /* retain proper integrity across the structures */ if (!resource->exists) { ctx->finfo.st_mode = 0; } } } return resource; } static dav_resource * dav_fs_get_parent_resource(const dav_resource *resource) { dav_resource_private *ctx = resource->info; dav_resource_private *parent_ctx; dav_resource *parent_resource; char *dirpath; /* If given resource is root, then there is no parent */ if (strcmp(resource->uri, "/") == 0 || #ifdef WIN32 (strlen(ctx->pathname) == 3 && ctx->pathname[1] == ':' && ctx->pathname[2] == '/') #else strcmp(ctx->pathname, "/") == 0 #endif ) return NULL; /* ### optimize this into a single allocation! */ /* Create private resource context descriptor */ parent_ctx = ap_pcalloc(ctx->pool, sizeof(*parent_ctx)); parent_ctx->pool = ctx->pool; dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname); if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/') dirpath[strlen(dirpath) - 1] = '\0'; parent_ctx->pathname = dirpath; parent_resource = ap_pcalloc(ctx->pool, sizeof(*parent_resource)); parent_resource->info = parent_ctx; parent_resource->collection = 1; parent_resource->hooks = &dav_hooks_repository_fs; if (resource->uri != NULL) { char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri); if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/') uri[strlen(uri) - 1] = '\0'; parent_resource->uri = uri; } if (stat(parent_ctx->pathname, &parent_ctx->finfo) == 0) { parent_resource->exists = 1; } return parent_resource; } static int dav_fs_is_same_resource( const dav_resource *res1, const dav_resource *res2) { dav_resource_private *ctx1 = res1->info; dav_resource_private *ctx2 = res2->info; if (res1->hooks != res2->hooks) return 0; #ifdef WIN32 return stricmp(ctx1->pathname, ctx2->pathname) == 0; #else if (ctx1->finfo.st_mode != 0) return ctx1->finfo.st_ino == ctx2->finfo.st_ino; else return strcmp(ctx1->pathname, ctx2->pathname) == 0; #endif } static int dav_fs_is_parent_resource( const dav_resource *res1, const dav_resource *res2) { dav_resource_private *ctx1 = res1->info; dav_resource_private *ctx2 = res2->info; size_t len1 = strlen(ctx1->pathname); size_t len2; if (res1->hooks != res2->hooks) return 0; /* it is safe to use ctx2 now */ len2 = strlen(ctx2->pathname); return (len2 > len1 && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0 && ctx2->pathname[len1] == '/'); } static dav_error * dav_fs_open_stream(const dav_resource *resource, dav_stream_mode mode, dav_stream **stream) { pool *p = resource->info->pool; dav_stream *ds = ap_palloc(p, sizeof(*ds)); int flags; switch (mode) { case DAV_MODE_READ: case DAV_MODE_READ_SEEKABLE: default: flags = O_RDONLY; break; case DAV_MODE_WRITE_TRUNC: flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; break; case DAV_MODE_WRITE_SEEKABLE: flags = O_WRONLY | O_CREAT | O_BINARY; break; } ds->p = p; ds->pathname = resource->info->pathname; ds->fd = open(ds->pathname, flags, DAV_FS_MODE_FILE); if (ds->fd == -1) { /* ### use something besides 500? */ return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "An error occurred while opening a resource."); } ap_note_cleanups_for_fd(p, ds->fd); *stream = ds; return NULL; } static dav_error * dav_fs_close_stream(dav_stream *stream, int commit) { ap_kill_cleanups_for_fd(stream->p, stream->fd); close(stream->fd); if (!commit) { if (remove(stream->pathname) != 0) { /* ### use a better description? */ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, "There was a problem removing (rolling " "back) the resource " "when it was being closed."); } } return NULL; } static dav_error * dav_fs_read_stream(dav_stream *stream, void *buf, size_t *bufsize) { ssize_t amt; amt = read(stream->fd, buf, *bufsize); if (amt == -1) { /* ### use something besides 500? */ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, "An error occurred while reading from a " "resource."); } *bufsize = (size_t)amt; return NULL; } static dav_error * dav_fs_write_stream(dav_stream *stream, const void *buf, size_t bufsize) { if (dav_sync_write(stream->fd, buf, bufsize) != 0) { if (errno == ENOSPC) { return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0, "There is not enough storage to write to " "this resource."); } /* ### use something besides 500? */ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, "An error occurred while writing to a " "resource."); } return NULL; } static dav_error * dav_fs_seek_stream(dav_stream *stream, off_t abs_pos) { if (lseek(stream->fd, abs_pos, SEEK_SET) == (off_t)-1) { /* ### use something besides 500? */ return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not seek to specified position in the " "resource."); } return NULL; } static dav_error * dav_fs_set_headers(request_rec *r, const dav_resource *resource) { /* ### this function isn't really used since we have a get_pathname */ #if DEBUG_GET_HANDLER if (!resource->exists) return NULL; /* make sure the proper mtime is in the request record */ ap_update_mtime(r, resource->info->finfo.st_mtime); /* ### note that these use r->filename rather than */ ap_set_last_modified(r); ap_set_etag(r); /* we accept byte-ranges */ ap_table_setn(r->headers_out, "Accept-Ranges", "bytes"); /* set up the Content-Length header */ ap_set_content_length(r, resource->info->finfo.st_size); /* ### how to set the content type? */ /* ### until this is resolved, the Content-Type header is busted */ #endif return NULL; } #if DEBUG_PATHNAME_STYLE static const char * dav_fs_get_pathname( const dav_resource *resource, void **free_handle_p) { return resource->info->pathname; } #endif static void dav_fs_free_file(void *free_handle) { /* nothing to free ... */ } static dav_error * dav_fs_create_collection(pool *p, dav_resource *resource) { dav_resource_private *ctx = resource->info; if (mkdir(ctx->pathname, DAV_FS_MODE_DIR) != 0) { if (errno == ENOSPC) return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0, "There is not enough storage to create " "this collection."); /* ### refine this error message? */ return dav_new_error(p, HTTP_FORBIDDEN, 0, "Unable to create collection."); } /* update resource state to show it exists as a collection */ resource->exists = 1; resource->collection = 1; return NULL; } static dav_error * dav_fs_copymove_walker(dav_walker_ctx *ctx, int calltype) { dav_resource_private *srcinfo = ctx->resource->info; dav_resource_private *dstinfo = ctx->res2->info; dav_error *err = NULL; if (ctx->resource->collection) { if (calltype == DAV_CALLTYPE_POSTFIX) { /* Postfix call for MOVE. delete the source dir. * Note: when copying, we do not enable the postfix-traversal. */ /* ### we are ignoring any error here; what should we do? */ (void) rmdir(srcinfo->pathname); } else { /* copy/move of a collection. Create the new, target collection */ if (mkdir(dstinfo->pathname, DAV_FS_MODE_DIR) != 0) { /* ### assume it was a permissions problem */ /* ### need a description here */ err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL); } } } else { err = dav_fs_copymove_file(ctx->is_move, ctx->pool, srcinfo->pathname, dstinfo->pathname, &srcinfo->finfo, &dstinfo->finfo, &ctx->work_buf); /* ### push a higher-level description? */ } /* ** If we have a "not so bad" error, then it might need to go into a ** multistatus response. ** ** For a MOVE, it will always go into the multistatus. It could be ** that everything has been moved *except* for the root. Using a ** multistatus (with no errors for the other resources) will signify ** this condition. ** ** For a COPY, we are traversing in a prefix fashion. If the root fails, ** then we can just bail out now. */ if (err != NULL && !ap_is_HTTP_SERVER_ERROR(err->status) && (ctx->is_move || !dav_fs_is_same_resource(ctx->resource, ctx->root))) { /* ### use errno to generate DAV:responsedescription? */ dav_add_response(ctx, ctx->resource->uri, err->status, NULL); /* the error is in the multistatus now. do not stop the traversal. */ return NULL; } return err; } static dav_error *dav_fs_copymove_resource( int is_move, const dav_resource *src, const dav_resource *dst, int depth, dav_response **response) { dav_error *err = NULL; dav_buffer work_buf = { 0 }; *response = NULL; /* if a collection, recursively copy/move it and its children, * including the state dirs */ if (src->collection) { dav_walker_ctx ctx = { 0 }; ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_HIDDEN; ctx.func = dav_fs_copymove_walker; ctx.pool = src->info->pool; ctx.resource = src; ctx.res2 = dst; ctx.is_move = is_move; ctx.postfix = is_move; /* needed for MOVE to delete source dirs */ /* copy over the source URI */ dav_buffer_init(ctx.pool, &ctx.uri, src->uri); if ((err = dav_fs_walk(&ctx, depth)) != NULL) { /* on a "real" error, then just punt. nothing else to do. */ return err; } if ((*response = ctx.response) != NULL) { /* some multistatus responses exist. wrap them in a 207 */ return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0, "Error(s) occurred on some resources during " "the COPY/MOVE process."); } return NULL; } /* not a collection */ if ((err = dav_fs_copymove_file(is_move, src->info->pool, src->info->pathname, dst->info->pathname, &src->info->finfo, &dst->info->finfo, &work_buf)) != NULL) { /* ### push a higher-level description? */ return err; } /* copy/move properties as well */ return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf); } static dav_error * dav_fs_copy_resource( const dav_resource *src, dav_resource *dst, int depth, dav_response **response) { dav_error *err; #if DAV_DEBUG if (src->hooks != dst->hooks) { /* ** ### strictly speaking, this is a design error; we should not ** ### have reached this point. */ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "DESIGN ERROR: a mix of repositories " "was passed to copy_resource."); } #endif if ((err = dav_fs_copymove_resource(0, src, dst, depth, response)) == NULL) { /* update state of destination resource to show it exists */ dst->exists = 1; dst->collection = src->collection; } return err; } static dav_error * dav_fs_move_resource( dav_resource *src, dav_resource *dst, dav_response **response) { dav_resource_private *srcinfo = src->info; dav_resource_private *dstinfo = dst->info; dav_error *err; int can_rename = 0; #if DAV_DEBUG if (src->hooks != dst->hooks) { /* ** ### strictly speaking, this is a design error; we should not ** ### have reached this point. */ return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "DESIGN ERROR: a mix of repositories " "was passed to move_resource."); } #endif /* determine whether a simple rename will work. * Assume source exists, else we wouldn't get called. */ if (dstinfo->finfo.st_mode != 0) { if (dstinfo->finfo.st_dev == srcinfo->finfo.st_dev) { /* target exists and is on the same device. */ can_rename = 1; } } else { const char *dirpath; struct stat finfo; /* destination does not exist, but the parent directory should, * so try it */ dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname); if (stat(dirpath, &finfo) == 0 && finfo.st_dev == srcinfo->finfo.st_dev) { can_rename = 1; } } /* if we can't simply renamed, then do it the hard way... */ if (!can_rename) { if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY, response)) == NULL) { /* update resource states */ dst->exists = 1; dst->collection = src->collection; src->exists = 0; src->collection = 0; } return err; } /* a rename should work. do it, and move properties as well */ /* no multistatus response */ *response = NULL; if (rename(srcinfo->pathname, dstinfo->pathname) != 0) { /* ### should have a better error than this. */ return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not rename resource."); } /* update resource states */ dst->exists = 1; dst->collection = src->collection; src->exists = 0; src->collection = 0; if ((err = dav_fs_copymoveset(1, src->info->pool, src, dst, NULL)) == NULL) { /* no error. we're done. go ahead and return now. */ return NULL; } /* error occurred during properties move; try to put resource back */ if (rename(dstinfo->pathname, srcinfo->pathname) != 0) { /* couldn't put it back! */ return dav_push_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "The resource was moved, but a failure " "occurred during the move of its " "properties. The resource could not be " "restored to its original location. The " "server is now in an inconsistent state.", err); } /* update resource states again */ src->exists = 1; src->collection = dst->collection; dst->exists = 0; dst->collection = 0; /* resource moved back, but properties may be inconsistent */ return dav_push_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "The resource was moved, but a failure " "occurred during the move of its properties. " "The resource was moved back to its original " "location, but its properties may have been " "partially moved. The server may be in an " "inconsistent state.", err); } static dav_error * dav_fs_delete_walker(dav_walker_ctx *ctx, int calltype) { dav_resource_private *info = ctx->resource->info; /* do not attempt to remove a null resource, * or a collection with children */ if (ctx->resource->exists && (!ctx->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) { /* try to remove the resource */ int result; result = ctx->resource->collection ? rmdir(info->pathname) : remove(info->pathname); /* ** If an error occurred, then add it to multistatus response. ** Note that we add it for the root resource, too. It is quite ** possible to delete the whole darn tree, yet fail on the root. ** ** (also: remember we are deleting via a postfix traversal) */ if (result != 0) { /* ### assume there is a permissions problem */ /* ### use errno to generate DAV:responsedescription? */ dav_add_response(ctx, ctx->resource->uri, HTTP_FORBIDDEN, NULL); } } return NULL; } static dav_error * dav_fs_remove_resource(dav_resource *resource, dav_response **response) { dav_resource_private *info = resource->info; *response = NULL; /* if a collection, recursively remove it and its children, * including the state dirs */ if (resource->collection) { dav_walker_ctx ctx = { 0 }; dav_error *err = NULL; ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_HIDDEN; ctx.postfix = 1; ctx.func = dav_fs_delete_walker; ctx.pool = info->pool; ctx.resource = resource; dav_buffer_init(info->pool, &ctx.uri, resource->uri); if ((err = dav_fs_walk(&ctx, DAV_INFINITY)) != NULL) { /* on a "real" error, then just punt. nothing else to do. */ return err; } if ((*response = ctx.response) != NULL) { /* some multistatus responses exist. wrap them in a 207 */ return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0, "Error(s) occurred on some resources during " "the deletion process."); } /* no errors... update resource state */ resource->exists = 0; resource->collection = 0; return NULL; } /* not a collection; remove the file and its properties */ if (remove(info->pathname) != 0) { /* ### put a description in here */ return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL); } /* update resource state */ resource->exists = 0; resource->collection = 0; /* remove properties and return its result */ return dav_fs_deleteset(info->pool, resource); } /* ### move this to dav_util? */ /* Walk recursively down through directories, * * including lock-null resources as we go. */ dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth) { dav_error *err = NULL; dav_walker_ctx *wctx = fsctx->wctx; int isdir = wctx->resource->collection; DIR *dirp; struct dirent *ep; /* ensure the context is prepared properly, then call the func */ err = (*wctx->func)(wctx, isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER); if (err != NULL) { return err; } if (depth == 0 || !isdir) { return NULL; } /* put a trailing slash onto the directory, in preparation for appending * files to it as we discovery them within the directory */ dav_check_bufsize(wctx->pool, &fsctx->path1, DAV_BUFFER_PAD); fsctx->path1.buf[fsctx->path1.cur_len++] = '/'; fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */ /* if a secondary path is present, then do that, too */ if (fsctx->path2.buf != NULL) { dav_check_bufsize(wctx->pool, &fsctx->path2, DAV_BUFFER_PAD); fsctx->path2.buf[fsctx->path2.cur_len++] = '/'; fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */ } /* Note: the URI should ALREADY have a trailing "/" */ /* for this first pass of files, all resources exist */ fsctx->res1.exists = 1; /* a file is the default; we'll adjust if we hit a directory */ fsctx->res1.collection = 0; fsctx->res2.collection = 0; /* open and scan the directory */ if ((dirp = opendir(fsctx->path1.buf)) == NULL) { /* ### need a better error */ return dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL); } while ((ep = readdir(dirp)) != NULL) { size_t len = strlen(ep->d_name); /* avoid recursing into our current, parent, or state directories */ if (ep->d_name[0] == '.' && (len == 1 || (ep->d_name[1] == '.' && len == 2))) { continue; } if (wctx->walk_type & DAV_WALKTYPE_AUTH) { /* ### need to authorize each file */ /* ### example: .htaccess is normally configured to fail auth */ /* stuff in the state directory is never authorized! */ if (!strcmp(ep->d_name, DAV_FS_STATE_DIR)) { continue; } } /* skip the state dir unless a HIDDEN is performed */ if (!(wctx->walk_type & DAV_WALKTYPE_HIDDEN) && !strcmp(ep->d_name, DAV_FS_STATE_DIR)) { continue; } /* append this file onto the path buffer (copy null term) */ dav_buffer_place_mem(wctx->pool, &fsctx->path1, ep->d_name, len + 1, 0); if (lstat(fsctx->path1.buf, &fsctx->info1.finfo) != 0) { /* woah! where'd it go? */ /* ### should have a better error here */ err = dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL); break; } /* copy the file to the URI, too. NOTE: we will pad an extra byte for the trailing slash later. */ dav_buffer_place_mem(wctx->pool, &wctx->uri, ep->d_name, len + 1, 1); /* if there is a secondary path, then do that, too */ if (fsctx->path2.buf != NULL) { dav_buffer_place_mem(wctx->pool, &fsctx->path2, ep->d_name, len + 1, 0); } /* set up the (internal) pathnames for the two resources */ fsctx->info1.pathname = fsctx->path1.buf; fsctx->info2.pathname = fsctx->path2.buf; /* set up the URI for the current resource */ fsctx->res1.uri = wctx->uri.buf; /* ### for now, only process regular files (e.g. skip symlinks) */ if (S_ISREG(fsctx->info1.finfo.st_mode)) { /* call the function for the specified dir + file */ if ((err = (*wctx->func)(wctx, DAV_CALLTYPE_MEMBER)) != NULL) { /* ### maybe add a higher-level description? */ break; } } else if (S_ISDIR(fsctx->info1.finfo.st_mode)) { size_t save_path_len = fsctx->path1.cur_len; size_t save_uri_len = wctx->uri.cur_len; size_t save_path2_len = fsctx->path2.cur_len; /* adjust length to incorporate the subdir name */ fsctx->path1.cur_len += len; fsctx->path2.cur_len += len; /* adjust URI length to incorporate subdir and a slash */ wctx->uri.cur_len += len + 1; wctx->uri.buf[wctx->uri.cur_len - 1] = '/'; wctx->uri.buf[wctx->uri.cur_len] = '\0'; /* switch over to a collection */ fsctx->res1.collection = 1; fsctx->res2.collection = 1; /* recurse on the subdir */ /* ### don't always want to quit on error from single child */ if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) { /* ### maybe add a higher-level description? */ break; } /* put the various information back */ fsctx->path1.cur_len = save_path_len; fsctx->path2.cur_len = save_path2_len; wctx->uri.cur_len = save_uri_len; fsctx->res1.collection = 0; fsctx->res2.collection = 0; /* assert: res1.exists == 1 */ } } /* ### check the return value of this? */ closedir(dirp); if (err != NULL) return err; if (wctx->walk_type & DAV_WALKTYPE_LOCKNULL) { size_t offset = 0; /* null terminate the directory name */ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0'; /* Include any lock null resources found in this collection */ fsctx->res1.collection = 1; if ((err = dav_fs_get_locknull_members(&fsctx->res1, &fsctx->locknull_buf)) != NULL) { /* ### maybe add a higher-level description? */ return err; } /* put a slash back on the end of the directory */ fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/'; /* these are all non-existant (files) */ fsctx->res1.exists = 0; fsctx->res1.collection = 0; memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo)); while (offset < fsctx->locknull_buf.cur_len) { size_t len = strlen(fsctx->locknull_buf.buf + offset); dav_lock *locks = NULL; /* ** Append the locknull file to the paths and the URI. Note that ** we don't have to pad the URI for a slash since a locknull ** resource is not a collection. */ dav_buffer_place_mem(wctx->pool, &fsctx->path1, fsctx->locknull_buf.buf + offset, len + 1, 0); dav_buffer_place_mem(wctx->pool, &wctx->uri, fsctx->locknull_buf.buf + offset, len + 1, 0); if (fsctx->path2.buf != NULL) { dav_buffer_place_mem(wctx->pool, &fsctx->path2, fsctx->locknull_buf.buf + offset, len + 1, 0); } /* set up the (internal) pathnames for the two resources */ fsctx->info1.pathname = fsctx->path1.buf; fsctx->info2.pathname = fsctx->path2.buf; /* set up the URI for the current resource */ fsctx->res1.uri = wctx->uri.buf; /* ** To prevent a PROPFIND showing an expired locknull ** resource, query the lock database to force removal ** of both the lock entry and .locknull, if necessary.. ** Sure, the query in PROPFIND would do this.. after ** the locknull resource was already included in the ** return. ** ** NOTE: we assume the caller has opened the lock database ** if they have provided DAV_WALKTYPE_LOCKNULL. */ /* ### we should also look into opening it read-only and ### eliding timed-out items from the walk, yet leaving ### them in the locknull database until somebody opens ### the thing writable. */ /* ### probably ought to use has_locks. note the problem ### mentioned above, though... we would traverse this as ### a locknull, but then a PROPFIND would load the lock ### info, causing a timeout and the locks would not be ### reported. Therefore, a null resource would be returned ### in the PROPFIND. ### ### alternative: just load unresolved locks. any direct ### locks will be timed out (correct). any indirect will ### not (correct; consider if a parent timed out -- the ### timeout routines do not walk and remove indirects; ### even the resolve func would probably fail when it ### tried to find a timed-out direct lock). */ if ((err = dav_lock_query(wctx->lockdb, wctx->resource, &locks)) != NULL) { /* ### maybe add a higher-level description? */ return err; } /* call the function for the specified dir + file */ if (locks != NULL && (err = (*wctx->func)(wctx, DAV_CALLTYPE_LOCKNULL)) != NULL) { /* ### maybe add a higher-level description? */ return err; } offset += len + 1; } /* reset the exists flag */ fsctx->res1.exists = 1; } if (wctx->postfix) { /* replace the dirs' trailing slashes with null terms */ fsctx->path1.buf[--fsctx->path1.cur_len] = '\0'; wctx->uri.buf[--wctx->uri.cur_len] = '\0'; if (fsctx->path2.buf != NULL) { fsctx->path2.buf[--fsctx->path2.cur_len] = '\0'; } /* this is a collection which exists */ fsctx->res1.collection = 1; return (*wctx->func)(wctx, DAV_CALLTYPE_POSTFIX); } return NULL; } static dav_error * dav_fs_walk(dav_walker_ctx *wctx, int depth) { dav_fs_walker_context fsctx = { 0 }; #if DAV_DEBUG if ((wctx->walk_type & DAV_WALKTYPE_LOCKNULL) != 0 && wctx->lockdb == NULL) { return dav_new_error(wctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "DESIGN ERROR: walker called to walk locknull " "resources, but a lockdb was not provided."); } /* ### an assertion that we have space for a trailing slash */ if (wctx->uri.cur_len + 1 > wctx->uri.alloc_len) { return dav_new_error(wctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "DESIGN ERROR: walker should have been called " "with padding in the URI buffer."); } #endif fsctx.wctx = wctx; wctx->root = wctx->resource; /* ### zero out versioned, working, baselined? */ fsctx.res1 = *wctx->resource; fsctx.res1.info = &fsctx.info1; fsctx.info1 = *wctx->resource->info; dav_buffer_init(wctx->pool, &fsctx.path1, fsctx.info1.pathname); fsctx.info1.pathname = fsctx.path1.buf; if (wctx->res2 != NULL) { fsctx.res2 = *wctx->res2; fsctx.res2.exists = 0; fsctx.res2.collection = 0; fsctx.res2.info = &fsctx.info2; fsctx.info2 = *wctx->res2->info; /* res2 does not exist -- clear its finfo structure */ memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo)); dav_buffer_init(wctx->pool, &fsctx.path2, fsctx.info2.pathname); fsctx.info2.pathname = fsctx.path2.buf; } /* if we have a directory, then ensure the URI has a trailing "/" */ if (fsctx.res1.collection && wctx->uri.buf[wctx->uri.cur_len - 1] != '/') { /* this will fall into the pad area */ wctx->uri.buf[wctx->uri.cur_len++] = '/'; wctx->uri.buf[wctx->uri.cur_len] = '\0'; } /* ** URI is tracked in the walker context. Ensure that people do not try ** to fetch it from res2. We will ensure that res1 and uri will remain ** synchronized. */ fsctx.res1.uri = wctx->uri.buf; fsctx.res2.uri = NULL; /* use our resource structures */ wctx->resource = &fsctx.res1; wctx->res2 = &fsctx.res2; return dav_fs_walker(&fsctx, depth); } /* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag * for file path. * ### do we need to return weak tags sometimes? */ static const char *dav_fs_getetag(const dav_resource *resource) { dav_resource_private *ctx = resource->info; if (!resource->exists) return ap_pstrdup(ctx->pool, ""); if (ctx->finfo.st_mode != 0) { return ap_psprintf(ctx->pool, "\"%lx-%lx-%lx\"", (unsigned long) ctx->finfo.st_ino, (unsigned long) ctx->finfo.st_size, (unsigned long) ctx->finfo.st_mtime); } return ap_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.st_mtime); } static const dav_hooks_repository dav_hooks_repository_fs = { DEBUG_GET_HANDLER, /* normally: special GET handling not required */ dav_fs_get_resource, dav_fs_get_parent_resource, dav_fs_is_same_resource, dav_fs_is_parent_resource, dav_fs_open_stream, dav_fs_close_stream, dav_fs_read_stream, dav_fs_write_stream, dav_fs_seek_stream, dav_fs_set_headers, #if DEBUG_PATHNAME_STYLE dav_fs_get_pathname, #else 0, #endif dav_fs_free_file, dav_fs_create_collection, dav_fs_copy_resource, dav_fs_move_resource, dav_fs_remove_resource, dav_fs_walk, dav_fs_getetag, }; static int dav_fs_find_prop(const char *ns_uri, const char *name) { const dav_fs_liveprop_name *scan; int ns; if (*ns_uri == 'h' && strcmp(ns_uri, dav_fs_namespace_uris[DAV_FS_URI_MYPROPS]) == 0) { ns = DAV_FS_URI_MYPROPS; } else if (*ns_uri == 'D' && strcmp(ns_uri, "DAV:") == 0) { ns = DAV_FS_URI_DAV; } else { /* we don't define this property */ return 0; } for (scan = dav_fs_props; scan->name != NULL; ++scan) if (ns == scan->ns && strcmp(name, scan->name) == 0) return scan->propid; return 0; } static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource, int propid, int insvalue, const int *ns_map, dav_text_header *phdr) { const char *value; const char *s; dav_prop_insert which; pool *p = resource->info->pool; const dav_fs_liveprop_name *scan; int ns; /* an HTTP-date can be 29 chars plus a null term */ /* a 64-bit size can be 20 chars plus a null term */ char buf[DAV_TIMEBUF_SIZE]; if (!DAV_PROPID_FS_OURS(propid)) return DAV_PROP_INSERT_NOTME; /* ** None of FS provider properties are defined if the resource does not ** exist. Just bail for this case. ** ** Note that DAV:displayname and DAV:source will be stored as dead ** properties; the NOTDEF return code indicates that dav_props.c should ** look there for the value. ** ** Even though we state that the FS properties are not defined, the ** client cannot store dead values -- we deny that thru the is_writable ** hook function. */ if (!resource->exists) return DAV_PROP_INSERT_NOTDEF; switch (propid) { case DAV_PROPID_FS_creationdate: /* ** Closest thing to a creation date. since we don't actually ** perform the operations that would modify ctime (after we ** create the file), then we should be pretty safe here. */ dav_format_time(DAV_STYLE_ISO8601, resource->info->finfo.st_ctime, buf); value = buf; break; case DAV_PROPID_FS_getcontentlength: /* our property, but not defined on collection resources */ if (resource->collection) return DAV_PROP_INSERT_NOTDEF; (void) sprintf(buf, "%ld", (unsigned long) resource->info->finfo.st_size); value = buf; break; case DAV_PROPID_FS_getetag: value = dav_fs_getetag(resource); break; case DAV_PROPID_FS_getlastmodified: dav_format_time(DAV_STYLE_RFC822, resource->info->finfo.st_mtime, buf); value = buf; break; case DAV_PROPID_FS_executable: #ifdef WIN32 /* our property, but not defined on the Win32 platform */ return DAV_PROP_INSERT_NOTDEF; #else /* our property, but not defined on collection resources */ if (resource->collection) return DAV_PROP_INSERT_NOTDEF; /* the files are "ours" so we only need to check owner exec privs */ if (resource->info->finfo.st_mode & DAV_FS_MODE_XUSR) value = "T"; else value = "F"; break; #endif /* WIN32 */ case DAV_PROPID_FS_displayname: case DAV_PROPID_FS_source: default: /* ** This property is not defined. However, it may be a dead ** property. */ return DAV_PROP_INSERT_NOTDEF; } /* assert: value != NULL */ for (scan = dav_fs_props; scan->name != NULL; ++scan) if (scan->propid == propid) break; /* assert: scan->name != NULL */ /* map our NS index into a global NS index */ ns = ns_map[scan->ns]; /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */ if (insvalue) { /* use D: prefix to refer to the DAV: namespace URI */ s = ap_psprintf(p, "%s" DEBUG_CR, ns, scan->name, value, ns, scan->name); which = DAV_PROP_INSERT_VALUE; } else { /* use D: prefix to refer to the DAV: namespace URI */ s = ap_psprintf(p, "" DEBUG_CR, ns, scan->name); which = DAV_PROP_INSERT_NAME; } dav_text_append(p, phdr, s); /* we inserted a name or value (this prop is done) */ return which; } static void dav_fs_insert_all(const dav_resource *resource, int insvalue, const int *ns_map, dav_text_header *phdr) { if (!resource->exists) { /* a lock-null resource */ /* ** ### technically, we should insert empty properties. dunno offhand ** ### what part of the spec said this, but it was essentially thus: ** ### "the properties should be defined, but may have no value". */ return; } (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_creationdate, insvalue, ns_map, phdr); (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getcontentlength, insvalue, ns_map, phdr); (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getlastmodified, insvalue, ns_map, phdr); (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getetag, insvalue, ns_map, phdr); #ifndef WIN32 /* ** Note: this property is not defined on the Win32 platform. ** dav_fs_insert_prop() won't insert it, but we may as ** well not even call it. */ (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable, insvalue, ns_map, phdr); #endif /* ### we know the others aren't defined as liveprops */ } static dav_prop_rw dav_fs_is_writeable(const dav_resource *resource, int propid) { if (!DAV_PROPID_FS_OURS(propid)) return DAV_PROP_RW_NOTME; if (propid == DAV_PROPID_FS_displayname || propid == DAV_PROPID_FS_source #ifndef WIN32 /* this property is not usable (writeable) on the Win32 platform */ || (propid == DAV_PROPID_FS_executable && !resource->collection) #endif ) return DAV_PROP_RW_YES; return DAV_PROP_RW_NO; } static dav_error *dav_fs_patch_validate(const dav_resource *resource, const dav_xml_elem *elem, int operation, void **context, int *defer_to_dead) { const dav_text *cdata; const dav_text *f_cdata; char value; if (elem->propid != DAV_PROPID_FS_executable) { *defer_to_dead = 1; return NULL; } if (operation == DAV_PROP_OP_DELETE) { return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, "The 'executable' property cannot be removed."); } cdata = elem->first_cdata.first; f_cdata = elem->last_child == NULL ? NULL : elem->last_child->following_cdata.first; /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */ if (cdata == NULL) { if (f_cdata == NULL) { return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, "The 'executable' property expects a single " "character, valued 'T' or 'F'. There was no " "value submitted."); } cdata = f_cdata; } else if (f_cdata != NULL) goto too_long; if (cdata->next != NULL || strlen(cdata->text) != 1) goto too_long; value = cdata->text[0]; if (value != 'T' && value != 'F') { return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, "The 'executable' property expects a single " "character, valued 'T' or 'F'. The value " "submitted is invalid."); } *context = (void *)(value == 'T'); return NULL; too_long: return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, "The 'executable' property expects a single " "character, valued 'T' or 'F'. The value submitted" "has too many characters."); } static dav_error *dav_fs_patch_exec(dav_resource *resource, const dav_xml_elem *elem, int operation, void *context, dav_liveprop_rollback **rollback_ctx) { int value = context != NULL; mode_t mode = resource->info->finfo.st_mode; int old_value = (resource->info->finfo.st_mode & DAV_FS_MODE_XUSR) != 0; /* assert: prop == executable. operation == SET. */ /* don't do anything if there is no change. no rollback info either. */ if (value == old_value) return NULL; mode &= ~DAV_FS_MODE_XUSR; if (value) mode |= DAV_FS_MODE_XUSR; if (chmod(resource->info->pathname, mode) == -1) { return dav_new_error(resource->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not set the executable flag of the " "target resource."); } /* update the resource and set up the rollback context */ resource->info->finfo.st_mode = mode; *rollback_ctx = (dav_liveprop_rollback *)old_value; return NULL; } static void dav_fs_patch_commit(dav_resource *resource, int operation, void *context, dav_liveprop_rollback *rollback_ctx) { /* nothing to do */ } static dav_error *dav_fs_patch_rollback(dav_resource *resource, int operation, void *context, dav_liveprop_rollback *rollback_ctx) { mode_t mode = resource->info->finfo.st_mode & ~DAV_FS_MODE_XUSR; int value = rollback_ctx != NULL; /* assert: prop == executable. operation == SET. */ /* restore the executable bit */ if (value) mode |= DAV_FS_MODE_XUSR; if (chmod(resource->info->pathname, mode) == -1) { return dav_new_error(resource->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "After a failure occurred, the resource's " "executable flag could not be restored."); } /* restore the resource's state */ resource->info->finfo.st_mode = mode; return NULL; } static const dav_hooks_liveprop dav_hooks_liveprop_fs = { #ifdef WIN32 NULL, #else "http://apache.org/dav/propset/fs/1", /* filesystem, set 1 */ #endif dav_fs_find_prop, dav_fs_insert_prop, dav_fs_insert_all, dav_fs_is_writeable, dav_fs_namespace_uris, dav_fs_patch_validate, dav_fs_patch_exec, dav_fs_patch_commit, dav_fs_patch_rollback, }; /* ** Note: we do not provide an is_active function at this point. In the ** future, mod_dav may use that to determine if a particular provider is ** active/enabled, but it doesn't now. */ static const dav_dyn_provider dav_dyn_providers_fs[] = { /* repository provider */ { DAV_FS_PROVIDER_ID, DAV_DYN_TYPE_REPOSITORY, &dav_hooks_repository_fs, NULL }, /* liveprop provider */ { DAV_FS_PROVIDER_ID, DAV_DYN_TYPE_LIVEPROP, &dav_hooks_liveprop_fs, NULL }, /* propdb provider */ { DAV_FS_PROVIDER_ID, DAV_DYN_TYPE_PROPDB, &dav_hooks_db_dbm, NULL }, /* locks provider */ { DAV_FS_PROVIDER_ID, DAV_DYN_TYPE_LOCKS, &dav_hooks_locks_fs, NULL }, /* must always be last */ DAV_DYN_END_MARKER }; const dav_dyn_module dav_dyn_module_default = { DAV_DYN_MAGIC, DAV_DYN_VERSION, "filesystem", NULL, /* module_open */ NULL, /* module_close */ NULL, /* dir_open */ NULL, /* dir_param */ NULL, /* dir_merge */ NULL, /* dir_close */ dav_dyn_providers_fs };