mirror of
https://github.com/apache/httpd.git
synced 2025-08-10 02:56:11 +00:00
Add RFC4331 quotas for mod_dav_fs
Address forgotten svn add in previous commit git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1907984 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
359
modules/dav/fs/quota.c
Normal file
359
modules/dav/fs/quota.c
Normal file
@ -0,0 +1,359 @@
|
||||
/* 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:
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
out:
|
||||
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
|
||||
|
||||
#endif
|
||||
out:
|
||||
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
|
||||
#endif
|
||||
out:
|
||||
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 used_bytes;
|
||||
apr_off_t available_bytes;
|
||||
apr_off_t size;
|
||||
const char *path;
|
||||
const char *lenhdr;
|
||||
const char *tag;
|
||||
const char *msg;
|
||||
apr_status_t rv;
|
||||
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")) &&
|
||||
(atol(lenhdr) > 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;
|
||||
}
|
Reference in New Issue
Block a user