Mailing List Archive

Re: svn commit: r1907984 - /httpd/httpd/trunk/modules/dav/fs/quota.c
Hi Emmanuel,

On Thu, Mar 2, 2023 at 4:46 PM <manu@apache.org> wrote:
>
> Author: manu
> Date: Thu Mar 2 15:46:12 2023
> New Revision: 1907984
>
> URL: http://svn.apache.org/viewvc?rev=1907984&view=rev
> Log:
> Add RFC4331 quotas for mod_dav_fs
>
> Address forgotten svn add in previous commit
>
> Added:
> httpd/httpd/trunk/modules/dav/fs/quota.c

Some warnings (errors with -Werror) raised by the ci:
https://github.com/apache/httpd/actions/runs/4315730441/jobs/7530520652#step:10:2809

Regards;
Yann.
Re: svn commit: r1907984 - /httpd/httpd/trunk/modules/dav/fs/quota.c [ In reply to ]
On 3/2/23 4:46 PM, manu@apache.org wrote:
> Author: manu
> Date: Thu Mar 2 15:46:12 2023
> New Revision: 1907984
>
> URL: http://svn.apache.org/viewvc?rev=1907984&view=rev
> Log:
> Add RFC4331 quotas for mod_dav_fs
>
> Address forgotten svn add in previous commit
>
> Added:
> httpd/httpd/trunk/modules/dav/fs/quota.c
>
> Added: httpd/httpd/trunk/modules/dav/fs/quota.c
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/fs/quota.c?rev=1907984&view=auto
> ==============================================================================
> --- httpd/httpd/trunk/modules/dav/fs/quota.c (added)
> +++ httpd/httpd/trunk/modules/dav/fs/quota.c Thu Mar 2 15:46:12 2023
> @@ -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;
> + }

Why do we need to check this again? We already checked this above.

> +
> + 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)) {

Please use ap_parse_strict_length to parse the content length instead of atol to
correctly detect invalid content-length headers.

> + 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;
> +}
>
>
>

Regards

RĂ¼diger