mirror of
https://github.com/apache/httpd.git
synced 2025-08-03 16:33:59 +00:00

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1908152 13f79535-47bb-0310-9956-ffa450edef68
359 lines
10 KiB
C
359 lines
10 KiB
C
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*
|
|
** DAV filesystem-based quota routines
|
|
*/
|
|
|
|
#include "apr.h"
|
|
#include "apr_strings.h"
|
|
|
|
#include "httpd.h"
|
|
#include "http_log.h"
|
|
#include "http_main.h"
|
|
|
|
#include "mod_dav.h"
|
|
#include "repos.h"
|
|
|
|
/*
|
|
* Just use a configure test? fields have been standardized for
|
|
* while: https://pubs.opengroup.org/onlinepubs/7908799/xsh/sysstatvfs.h.html
|
|
*/
|
|
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(OpenBSD) || \
|
|
defined(linux)
|
|
#include <sys/statvfs.h>
|
|
#define HAVE_STATVFS
|
|
#endif
|
|
|
|
#define DAV_TRUE 1
|
|
#define DAV_FALSE 0
|
|
|
|
/* Forwared declaration, since it calls itself */
|
|
static apr_status_t get_dir_used_bytes_walk(request_rec *r,
|
|
const char *path,
|
|
apr_off_t *used);
|
|
|
|
static apr_status_t get_dir_used_bytes_walk(request_rec *r,
|
|
const char *path,
|
|
apr_off_t *used)
|
|
{
|
|
apr_dir_t *dir = NULL;
|
|
apr_finfo_t finfo;
|
|
apr_status_t rv;
|
|
|
|
if ((rv = apr_dir_open(&dir, path, r->pool)) != APR_SUCCESS) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
|
|
"failed to open \"%s\"", path);
|
|
goto out;
|
|
}
|
|
|
|
do {
|
|
apr_int32_t wanted;
|
|
char *newpath;
|
|
|
|
wanted = APR_FINFO_DIRENT|APR_FINFO_TYPE|APR_FINFO_SIZE|APR_FINFO_NAME;
|
|
rv = apr_dir_read(&finfo, wanted, dir);
|
|
if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
|
|
break;
|
|
|
|
if ((finfo.valid & APR_FINFO_NAME) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Cannot get entry name in \"%s\"", path);
|
|
goto out;
|
|
}
|
|
|
|
if (!strcmp(finfo.name, ".") ||
|
|
!strcmp(finfo.name, "..") ||
|
|
!strcmp(finfo.name, DAV_FS_STATE_DIR) ||
|
|
!strncmp(finfo.name, DAV_FS_TMP_PREFIX, strlen(DAV_FS_TMP_PREFIX)))
|
|
continue;
|
|
|
|
if ((finfo.valid & APR_FINFO_TYPE) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Cannot get entry type in \"%s\"", path);
|
|
goto out;
|
|
}
|
|
|
|
switch (finfo.filetype) {
|
|
case APR_REG:
|
|
if ((finfo.valid & APR_FINFO_SIZE) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Cannot get entry size in \"%s\"", path);
|
|
goto out;
|
|
}
|
|
*used += finfo.size;
|
|
break;
|
|
|
|
case APR_DIR:
|
|
rv = apr_filepath_merge(&newpath, path, finfo.name, 0, r->pool);
|
|
if (rv != APR_SUCCESS) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
|
|
"apr_filepath_merge \"%s\" \"%s\" failed",
|
|
path, finfo.name);
|
|
goto out;
|
|
}
|
|
|
|
rv = get_dir_used_bytes_walk(r, newpath, used);
|
|
if (rv != APR_SUCCESS)
|
|
goto out;
|
|
break;
|
|
|
|
default:
|
|
/* skip other types */
|
|
break;
|
|
}
|
|
} while (1 /* CONSTCOND */);
|
|
|
|
if (rv == APR_ENOENT)
|
|
rv = APR_SUCCESS;
|
|
else
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
|
|
"apr_dir_read failed on \"%s\"", path);
|
|
out:
|
|
if (dir)
|
|
(void)apr_dir_close(dir);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static apr_off_t get_dir_used_bytes(request_rec *r, const char *path)
|
|
{
|
|
apr_off_t used_bytes = 0;
|
|
apr_status_t rv;
|
|
|
|
rv = get_dir_used_bytes_walk(r, path, &used_bytes);
|
|
|
|
return (rv == APR_SUCCESS) ? used_bytes : DAV_FS_BYTES_ERROR;
|
|
}
|
|
|
|
static apr_off_t get_fs_used_bytes(const char *path)
|
|
{
|
|
apr_off_t used_bytes = DAV_FS_BYTES_ERROR;
|
|
#ifdef HAVE_STATVFS
|
|
struct statvfs f;
|
|
|
|
if (statvfs(path, &f) != 0)
|
|
goto out;
|
|
|
|
#ifdef __NetBSD__
|
|
used_bytes = (f.f_blocks - f.f_bfree) * f.f_frsize;
|
|
#else
|
|
used_bytes = (f.f_blocks - f.f_bfree) * f.f_bsize;
|
|
#endif
|
|
|
|
out:
|
|
#endif
|
|
return used_bytes;
|
|
}
|
|
|
|
static apr_off_t get_fs_available_bytes(const char *path)
|
|
{
|
|
apr_off_t available_bytes = DAV_FS_BYTES_ERROR;
|
|
#ifdef HAVE_STATVFS
|
|
struct statvfs f;
|
|
|
|
if (statvfs(path, &f) != 0)
|
|
goto out;
|
|
|
|
#ifdef __NetBSD__
|
|
available_bytes = f.f_bavail * f.f_frsize;
|
|
#else
|
|
available_bytes = f.f_bavail * f.f_bsize;
|
|
#endif
|
|
out:
|
|
#endif
|
|
return available_bytes;
|
|
}
|
|
|
|
apr_off_t dav_fs_get_used_bytes(request_rec *r, const char *path)
|
|
{
|
|
apr_off_t quota;
|
|
apr_off_t used_bytes = DAV_FS_BYTES_ERROR;
|
|
|
|
if (dav_fs_get_quota(r, path, "a) != NULL)
|
|
goto out;
|
|
|
|
switch (quota) {
|
|
case DAV_FS_QUOTA_UNSET: /* FALLTHOTUGH */
|
|
case DAV_FS_QUOTA_OFF:
|
|
break;
|
|
|
|
case DAV_FS_QUOTA_NONE:;
|
|
used_bytes = get_fs_used_bytes(path);
|
|
break;
|
|
|
|
default:
|
|
used_bytes = get_dir_used_bytes(r, path);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return used_bytes;
|
|
}
|
|
|
|
apr_off_t dav_fs_get_available_bytes(request_rec *r,
|
|
const char *path, int *fs_low)
|
|
{
|
|
apr_off_t quota;
|
|
apr_off_t used_bytes;
|
|
apr_off_t fs_available_bytes;
|
|
apr_off_t available_bytes = DAV_FS_BYTES_ERROR;
|
|
int _fs_low = DAV_FALSE;
|
|
|
|
if (dav_fs_get_quota(r, path, "a) != NULL)
|
|
goto out;
|
|
|
|
switch (quota) {
|
|
case DAV_FS_QUOTA_UNSET: /* FALLTHROUGH */
|
|
case DAV_FS_QUOTA_OFF:
|
|
break;
|
|
|
|
case DAV_FS_QUOTA_NONE:
|
|
available_bytes = get_fs_available_bytes(path);
|
|
if (available_bytes != DAV_FS_BYTES_ERROR)
|
|
_fs_low = DAV_TRUE;
|
|
break;
|
|
|
|
default:
|
|
used_bytes = get_dir_used_bytes(r, path);
|
|
if (used_bytes != DAV_FS_BYTES_ERROR) {
|
|
if (used_bytes > quota)
|
|
available_bytes = 0;
|
|
else
|
|
available_bytes = quota - used_bytes;
|
|
|
|
/*
|
|
* Use available space from filesystem rather than quota
|
|
* if it is smaller
|
|
*/
|
|
fs_available_bytes = get_fs_available_bytes(path);
|
|
if (fs_available_bytes != DAV_FS_BYTES_ERROR) {
|
|
if (fs_available_bytes < available_bytes) {
|
|
available_bytes = fs_available_bytes;
|
|
_fs_low = DAV_TRUE;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (available_bytes != DAV_FS_BYTES_ERROR && fs_low)
|
|
*fs_low = _fs_low;
|
|
|
|
return available_bytes;
|
|
}
|
|
|
|
|
|
int dav_fs_quota_precondition(request_rec *r,
|
|
dav_resource *src, const dav_resource *dst,
|
|
const apr_xml_doc *doc, dav_error **err)
|
|
{
|
|
apr_off_t quota;
|
|
apr_off_t available_bytes;
|
|
apr_off_t size;
|
|
const char *path;
|
|
const char *lenhdr;
|
|
const char *tag;
|
|
const char *msg;
|
|
int status = DECLINED;
|
|
int fs_low;
|
|
|
|
if (r->method_number == M_COPY || r->method_number == M_MOVE) {
|
|
/*
|
|
* dav_method_copymove() calls dav_run_method_precondition()
|
|
* twice, with dst NULL on first call and set on the second call.
|
|
*/
|
|
if (dst == NULL)
|
|
goto out;
|
|
path = dav_fs_fname(dst);
|
|
} else {
|
|
path = dav_fs_fname(src);
|
|
}
|
|
|
|
path = ap_make_dirstr_parent(r->pool, path);
|
|
if ((*err = dav_fs_get_quota(r, path, "a)) != NULL)
|
|
goto out;
|
|
|
|
if (quota == DAV_FS_QUOTA_OFF || quota == DAV_FS_QUOTA_UNSET)
|
|
goto out;
|
|
|
|
available_bytes = dav_fs_get_available_bytes(r, path, &fs_low);
|
|
if (available_bytes == DAV_FS_BYTES_ERROR) {
|
|
if (quota != DAV_FS_QUOTA_NONE) {
|
|
status = HTTP_INTERNAL_SERVER_ERROR;
|
|
*err = dav_new_error(r->pool, status, 0, 0,
|
|
"Quota enabled, but failed to compute "
|
|
"available space.");
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
tag = fs_low ? "sufficient-disk-space" : "quota-not-exceeded";
|
|
msg = fs_low ? "Insufficient disk space" : "Quota exceeded";
|
|
|
|
/*
|
|
* For all operations, report overquota before the operation.
|
|
*/
|
|
if (available_bytes == 0) {
|
|
status = HTTP_INSUFFICIENT_STORAGE;
|
|
*err = dav_new_error_tag(r->pool, status, 0, 0,
|
|
msg, NULL, tag);
|
|
goto out;
|
|
}
|
|
|
|
switch (r->method_number) {
|
|
case M_PUT:
|
|
/*
|
|
* If PUT has Content-Length, we can forecast overquota
|
|
*/
|
|
if ((lenhdr = apr_table_get(r->headers_in, "Content-Length"))) {
|
|
if (!ap_parse_strict_length(&size, lenhdr)) {
|
|
status = HTTP_BAD_REQUEST;
|
|
*err = dav_new_error(r->pool, status, 0, 0,
|
|
"client sent invalid Content-Length");
|
|
goto out;
|
|
}
|
|
|
|
if (size > available_bytes) {
|
|
status = HTTP_INSUFFICIENT_STORAGE;
|
|
*err = dav_new_error_tag(r->pool, status, 0, 0,
|
|
msg, NULL, tag);
|
|
goto out;
|
|
}
|
|
}
|
|
break;
|
|
case M_COPY: /* FALLTHROUGH */
|
|
case M_MOVE:
|
|
/*
|
|
* If source size is known, we can forecast ovequota
|
|
*/
|
|
if ((size = dav_fs_size(src) != DAV_FS_BYTES_ERROR) &&
|
|
(size > available_bytes)) {
|
|
status = HTTP_INSUFFICIENT_STORAGE;
|
|
*err = dav_new_error_tag(r->pool, status, 0, 0,
|
|
msg, "DAV:", tag);
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return status;
|
|
}
|