summaryrefslogtreecommitdiff
path: root/modules/dav
diff options
context:
space:
mode:
authormanu <manu@unknown>2023-03-02 15:46:12 +0000
committermanu <manu@unknown>2023-03-02 15:46:12 +0000
commitb75ba394e50c6dc2f56ff6912ce0088f77a03775 (patch)
tree384f6524b0c3381049226f2ef0753fec1a43dead /modules/dav
parent6c9b8075bc1ce8e88f6df067ecb737f078b8ea23 (diff)
downloadhttpd-b75ba394e50c6dc2f56ff6912ce0088f77a03775.tar.gz
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
Diffstat (limited to 'modules/dav')
-rw-r--r--modules/dav/fs/quota.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/modules/dav/fs/quota.c b/modules/dav/fs/quota.c
new file mode 100644
index 0000000000..cbbc6871a0
--- /dev/null
+++ b/modules/dav/fs/quota.c
@@ -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, &quota) != 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, &quota) != 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, &quota)) != 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;
+}