diff options
Diffstat (limited to '')
-rw-r--r-- | src/plugins/quota/quota-fs.c | 970 |
1 files changed, 970 insertions, 0 deletions
diff --git a/src/plugins/quota/quota-fs.c b/src/plugins/quota/quota-fs.c new file mode 100644 index 0000000..57620c1 --- /dev/null +++ b/src/plugins/quota/quota-fs.c @@ -0,0 +1,970 @@ +/* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ + +/* Only for reporting filesystem quota */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "hostpid.h" +#include "mountpoint.h" +#include "quota-private.h" +#include "quota-fs.h" + +#ifdef HAVE_FS_QUOTA + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#ifdef HAVE_LINUX_DQBLK_XFS_H +# include <linux/dqblk_xfs.h> +# define HAVE_XFS_QUOTA +#elif defined (HAVE_XFS_XQM_H) +# include <xfs/xqm.h> /* CentOS 4.x at least uses this */ +# define HAVE_XFS_QUOTA +#endif + +#ifdef HAVE_RQUOTA +# include "rquota.h" +# define RQUOTA_GETQUOTA_TIMEOUT_SECS 10 +#endif + +#ifndef DEV_BSIZE +# ifdef DQBSIZE +# define DEV_BSIZE DQBSIZE /* AIX */ +# else +# define DEV_BSIZE 512 +# endif +#endif + +#ifdef HAVE_STRUCT_DQBLK_CURSPACE +# define dqb_curblocks dqb_curspace +#endif + +/* Very old sys/quota.h doesn't define _LINUX_QUOTA_VERSION at all, which means + it supports only v1 quota. However, new sys/quota.h (glibc 2.25) removes + support for v1 entirely and again it doesn't define it. I guess we can just + assume v2 now, and if someone still wants v1 support they can add + -D_LINUX_QUOTA_VERSION=1 to CFLAGS. */ +#ifndef _LINUX_QUOTA_VERSION +# define _LINUX_QUOTA_VERSION 2 +#endif + +#define mount_type_is_nfs(mount) \ + (strcmp((mount)->type, "nfs") == 0 || \ + strcmp((mount)->type, "nfs4") == 0) + +struct fs_quota_mountpoint { + int refcount; + + char *mount_path; + char *device_path; + char *type; + unsigned int block_size; + +#ifdef FS_QUOTA_SOLARIS + int fd; + char *path; +#endif +}; + +struct fs_quota_root { + struct quota_root root; + char *storage_mount_path; + + uid_t uid; + gid_t gid; + struct fs_quota_mountpoint *mount; + + bool inode_per_mail:1; + bool user_disabled:1; + bool group_disabled:1; +#ifdef FS_QUOTA_NETBSD + struct quotahandle *qh; +#endif +}; + +extern struct quota_backend quota_backend_fs; + +static struct quota_root *fs_quota_alloc(void) +{ + struct fs_quota_root *root; + + root = i_new(struct fs_quota_root, 1); + root->uid = geteuid(); + root->gid = getegid(); + + return &root->root; +} + +static void handle_user_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->group_disabled = TRUE; +} + +static void handle_group_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->user_disabled = TRUE; +} + +static void handle_inode_param(struct quota_root *_root, const char *param_value ATTR_UNUSED) +{ + ((struct fs_quota_root *)_root)->inode_per_mail = TRUE; +} + +static void handle_mount_param(struct quota_root *_root, const char *param_value) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + i_free(root->storage_mount_path); + root->storage_mount_path = i_strdup(param_value); +} + +static int fs_quota_init(struct quota_root *_root, const char *args, + const char **error_r) +{ + const struct quota_param_parser fs_params[] = { + {.param_name = "user", .param_handler = handle_user_param}, + {.param_name = "group", .param_handler = handle_group_param}, + {.param_name = "mount=", .param_handler = handle_mount_param}, + {.param_name = "inode_per_mail", .param_handler = handle_inode_param}, + quota_param_hidden, quota_param_noenforcing, quota_param_ns, + {.param_name = NULL} + }; + + event_set_append_log_prefix(_root->backend.event, "quota-fs: "); + + if (quota_parse_parameters(_root, &args, error_r, fs_params, TRUE) < 0) + return -1; + _root->auto_updating = TRUE; + return 0; +} + +static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount) +{ + if (--mount->refcount > 0) + return; + +#ifdef FS_QUOTA_SOLARIS + i_close_fd_path(&mount->fd, mount->path); + i_free(mount->path); +#endif + + i_free(mount->device_path); + i_free(mount->mount_path); + i_free(mount->type); + i_free(mount); +} + +static void fs_quota_deinit(struct quota_root *_root) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + + if (root->mount != NULL) + fs_quota_mountpoint_free(root->mount); + i_free(root->storage_mount_path); + i_free(root); +} + +static struct fs_quota_mountpoint *fs_quota_mountpoint_get(const char *dir) +{ + struct fs_quota_mountpoint *mount; + struct mountpoint point; + int ret; + + ret = mountpoint_get(dir, default_pool, &point); + if (ret <= 0) + return NULL; + + mount = i_new(struct fs_quota_mountpoint, 1); + mount->refcount = 1; + mount->device_path = point.device_path; + mount->mount_path = point.mount_path; + mount->type = point.type; + mount->block_size = point.block_size; +#ifdef FS_QUOTA_SOLARIS + mount->fd = -1; +#endif + + if (mount_type_is_nfs(mount)) { + if (strchr(mount->device_path, ':') == NULL) { + e_error(quota_backend_fs.event, + "%s is not a valid NFS device path", + mount->device_path); + fs_quota_mountpoint_free(mount); + return NULL; + } + } + return mount; +} + +#define QUOTA_ROOT_MATCH(root, mount) \ + ((root)->root.backend.name == quota_backend_fs.name && \ + ((root)->storage_mount_path == NULL || \ + strcmp((root)->storage_mount_path, (mount)->mount_path) == 0)) + +static struct fs_quota_root * +fs_quota_root_find_mountpoint(struct quota *quota, + const struct fs_quota_mountpoint *mount) +{ + struct quota_root *const *roots; + struct fs_quota_root *empty = NULL; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + struct fs_quota_root *root = (struct fs_quota_root *)roots[i]; + if (QUOTA_ROOT_MATCH(root, mount)) { + if (root->mount == NULL) + empty = root; + else if (strcmp(root->mount->mount_path, + mount->mount_path) == 0) + return root; + } + } + return empty; +} + +static void +fs_quota_mount_init(struct fs_quota_root *root, + struct fs_quota_mountpoint *mount, const char *dir) +{ + struct quota_root *const *roots; + unsigned int i, count; + +#ifdef FS_QUOTA_SOLARIS +#ifdef HAVE_RQUOTA + if (mount_type_is_nfs(mount)) { + /* using rquota for this mount */ + } else +#endif + if (mount->path == NULL) { + mount->path = i_strconcat(mount->mount_path, "/quotas", NULL); + mount->fd = open(mount->path, O_RDONLY); + if (mount->fd == -1 && errno != ENOENT) + e_error(root->root.backend.event, + "open(%s) failed: %m", mount->path); + } +#endif + root->mount = mount; + + e_debug(root->root.backend.event, "fs quota add mailbox dir = %s", dir); + e_debug(root->root.backend.event, "fs quota block device = %s", mount->device_path); + e_debug(root->root.backend.event, "fs quota mount point = %s", mount->mount_path); + e_debug(root->root.backend.event, "fs quota mount type = %s", mount->type); + + /* if there are more unused quota roots, copy this mount to them */ + roots = array_get(&root->root.quota->roots, &count); + for (i = 0; i < count; i++) { + root = (struct fs_quota_root *)roots[i]; + if (QUOTA_ROOT_MATCH(root, mount) && root->mount == NULL) { + mount->refcount++; + root->mount = mount; + } + } +} + +static void fs_quota_add_missing_mounts(struct quota *quota) +{ + struct fs_quota_mountpoint *mount; + struct quota_root *const *roots; + unsigned int i, count; + + roots = array_get("a->roots, &count); + for (i = 0; i < count; i++) { + struct fs_quota_root *root = (struct fs_quota_root *)roots[i]; + + if (root->root.backend.name != quota_backend_fs.name || + root->storage_mount_path == NULL || root->mount != NULL) + continue; + + mount = fs_quota_mountpoint_get(root->storage_mount_path); + if (mount != NULL) { + fs_quota_mount_init(root, mount, + root->storage_mount_path); + } + } +} + +static void fs_quota_namespace_added(struct quota *quota, + struct mail_namespace *ns) +{ + struct fs_quota_mountpoint *mount; + struct fs_quota_root *root; + const char *dir; + + if (!mailbox_list_get_root_path(ns->list, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &dir)) + mount = NULL; + else + mount = fs_quota_mountpoint_get(dir); + if (mount != NULL) { + root = fs_quota_root_find_mountpoint(quota, mount); + if (root != NULL && root->mount == NULL) + fs_quota_mount_init(root, mount, dir); + else + fs_quota_mountpoint_free(mount); + } + + /* we would actually want to do this only once after all quota roots + are created, but there's no way to do this right now */ + fs_quota_add_missing_mounts(quota); +} + +static const char *const * +fs_quota_root_get_resources(struct quota_root *_root) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + static const char *resources_kb[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + NULL + }; + static const char *resources_kb_messages[] = { + QUOTA_NAME_STORAGE_KILOBYTES, + QUOTA_NAME_MESSAGES, + NULL + }; + + return root->inode_per_mail ? resources_kb_messages : resources_kb; +} + +#if defined(FS_QUOTA_LINUX) || defined(FS_QUOTA_BSDAIX) || \ + defined(FS_QUOTA_NETBSD) || defined(HAVE_RQUOTA) +static void fs_quota_root_disable(struct fs_quota_root *root, bool group) +{ + if (group) + root->group_disabled = TRUE; + else + root->user_disabled = TRUE; +} +#endif + +#ifdef HAVE_RQUOTA +static void +rquota_get_result(const rquota *rq, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r) +{ + /* use soft limits if they exist, fallback to hard limits */ + + /* convert the results from blocks to bytes */ + *bytes_value_r = (uint64_t)rq->rq_curblocks * + (uint64_t)rq->rq_bsize; + if (rq->rq_bsoftlimit != 0) { + *bytes_limit_r = (uint64_t)rq->rq_bsoftlimit * + (uint64_t)rq->rq_bsize; + } else { + *bytes_limit_r = (uint64_t)rq->rq_bhardlimit * + (uint64_t)rq->rq_bsize; + } + + *count_value_r = rq->rq_curfiles; + if (rq->rq_fsoftlimit != 0) + *count_limit_r = rq->rq_fsoftlimit; + else + *count_limit_r = rq->rq_fhardlimit; +} + +static int +do_rquota_user(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct getquota_rslt result; + struct getquota_args args; + struct timeval timeout; + enum clnt_stat call_status; + CLIENT *cl; + struct fs_quota_mountpoint *mount = root->mount; + const char *host; + char *path; + + path = strchr(mount->device_path, ':'); + i_assert(path != NULL); + + host = t_strdup_until(mount->device_path, path); + path++; + + /* For NFSv4, we send the filesystem path without initial /. Server + prepends proper NFS pseudoroot automatically and uses this for + detection of NFSv4 mounts. */ + if (strcmp(root->mount->type, "nfs4") == 0) { + while (*path == '/') + path++; + } + + e_debug(root->root.backend.event, "host=%s, path=%s, uid=%s", + host, path, dec2str(root->uid)); + + /* clnt_create() polls for a while to establish a connection */ + cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp"); + if (cl == NULL) { + *error_r = t_strdup_printf( + "could not contact RPC service on %s", host); + return -1; + } + + /* Establish some RPC credentials */ + auth_destroy(cl->cl_auth); + cl->cl_auth = authunix_create_default(); + + /* make the rquota call on the remote host */ + args.gqa_pathp = path; + args.gqa_uid = root->uid; + + timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS; + timeout.tv_usec = 0; + call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA, + (xdrproc_t)xdr_getquota_args, (char *)&args, + (xdrproc_t)xdr_getquota_rslt, (char *)&result, + timeout); + + /* the result has been deserialized, let the client go */ + auth_destroy(cl->cl_auth); + clnt_destroy(cl); + + if (call_status != RPC_SUCCESS) { + const char *rpc_error_msg = clnt_sperrno(call_status); + + *error_r = t_strdup_printf( + "remote rquota call failed: %s", + rpc_error_msg); + return -1; + } + + switch (result.status) { + case Q_OK: { + rquota_get_result(&result.getquota_rslt_u.gqr_rquota, + bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r); + e_debug(root->root.backend.event, "uid=%s, bytes=%"PRIu64"/%"PRIu64" " + "files=%"PRIu64"/%"PRIu64, + dec2str(root->uid), + *bytes_value_r, *bytes_limit_r, + *count_value_r, *count_limit_r); + return 1; + } + case Q_NOQUOTA: + e_debug(root->root.backend.event, "uid=%s, limit=unlimited", + dec2str(root->uid)); + fs_quota_root_disable(root, FALSE); + return 0; + case Q_EPERM: + *error_r = "permission denied to rquota service"; + return -1; + default: + *error_r = t_strdup_printf( + "unrecognized status code (%d) from rquota service", + result.status); + return -1; + } +} + +static int +do_rquota_group(struct fs_quota_root *root ATTR_UNUSED, + uint64_t *bytes_value_r ATTR_UNUSED, + uint64_t *bytes_limit_r ATTR_UNUSED, + uint64_t *count_value_r ATTR_UNUSED, + uint64_t *count_limit_r ATTR_UNUSED, + const char **error_r) +{ +#if defined(EXT_RQUOTAVERS) && defined(GRPQUOTA) + struct getquota_rslt result; + ext_getquota_args args; + struct timeval timeout; + enum clnt_stat call_status; + CLIENT *cl; + struct fs_quota_mountpoint *mount = root->mount; + const char *host; + char *path; + + path = strchr(mount->device_path, ':'); + i_assert(path != NULL); + + host = t_strdup_until(mount->device_path, path); + path++; + + e_debug(root->root.backend.event, "host=%s, path=%s, gid=%s", + host, path, dec2str(root->gid)); + + /* clnt_create() polls for a while to establish a connection */ + cl = clnt_create(host, RQUOTAPROG, EXT_RQUOTAVERS, "udp"); + if (cl == NULL) { + *error_r = t_strdup_printf( + "could not contact RPC service on %s (group)", host); + return -1; + } + + /* Establish some RPC credentials */ + auth_destroy(cl->cl_auth); + cl->cl_auth = authunix_create_default(); + + /* make the rquota call on the remote host */ + args.gqa_pathp = path; + args.gqa_id = root->gid; + args.gqa_type = GRPQUOTA; + timeout.tv_sec = RQUOTA_GETQUOTA_TIMEOUT_SECS; + timeout.tv_usec = 0; + + call_status = clnt_call(cl, RQUOTAPROC_GETQUOTA, + (xdrproc_t)xdr_ext_getquota_args, (char *)&args, + (xdrproc_t)xdr_getquota_rslt, (char *)&result, + timeout); + + /* the result has been deserialized, let the client go */ + auth_destroy(cl->cl_auth); + clnt_destroy(cl); + + if (call_status != RPC_SUCCESS) { + const char *rpc_error_msg = clnt_sperrno(call_status); + + *error_r = t_strdup_printf( + "remote ext rquota call failed: %s", rpc_error_msg); + return -1; + } + + switch (result.status) { + case Q_OK: { + rquota_get_result(&result.getquota_rslt_u.gqr_rquota, + bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r); + e_debug(root->root.backend.event, "gid=%s, bytes=%"PRIu64"/%"PRIu64" " + "files=%"PRIu64"/%"PRIu64, + dec2str(root->gid), + *bytes_value_r, *bytes_limit_r, + *count_value_r, *count_limit_r); + return 1; + } + case Q_NOQUOTA: + e_debug(root->root.backend.event, "gid=%s, limit=unlimited", + dec2str(root->gid)); + fs_quota_root_disable(root, TRUE); + return 0; + case Q_EPERM: + *error_r = "permission denied to ext rquota service"; + return -1; + default: + *error_r = t_strdup_printf( + "unrecognized status code (%d) from ext rquota service", + result.status); + return -1; + } +#else + *error_r = "rquota not compiled with group support"; + return -1; +#endif +} +#endif + +#ifdef FS_QUOTA_LINUX +static int +fs_quota_get_linux(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + int type, id; + + type = group ? GRPQUOTA : USRQUOTA; + id = group ? root->gid : root->uid; + +#ifdef HAVE_XFS_QUOTA + if (strcmp(root->mount->type, "xfs") == 0) { + struct fs_disk_quota xdqblk; + + if (quotactl(QCMD(Q_XGETQUOTA, type), + root->mount->device_path, + id, (caddr_t)&xdqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "errno=%d, quotactl(Q_XGETQUOTA, %s) failed: %m", + errno, root->mount->device_path); + return -1; + } + + /* values always returned in 512 byte blocks */ + *bytes_value_r = xdqblk.d_bcount * 512ULL; + *bytes_limit_r = xdqblk.d_blk_softlimit * 512ULL; + if (*bytes_limit_r == 0) { + *bytes_limit_r = xdqblk.d_blk_hardlimit * 512ULL; + } + *count_value_r = xdqblk.d_icount; + *count_limit_r = xdqblk.d_ino_softlimit; + if (*count_limit_r == 0) { + *count_limit_r = xdqblk.d_ino_hardlimit; + } + } else +#endif + { + /* ext2, ext3 */ + if (quotactl(QCMD(Q_GETQUOTA, type), + root->mount->device_path, + id, (caddr_t)&dqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->device_path); + if (errno == EINVAL) { + *error_r = t_strdup_printf("%s, " + "Dovecot was compiled with Linux quota " + "v%d support, try changing it " + "(CPPFLAGS=-D_LINUX_QUOTA_VERSION=%d configure)", + *error_r, + _LINUX_QUOTA_VERSION, + _LINUX_QUOTA_VERSION == 1 ? 2 : 1); + } + return -1; + } + +#if _LINUX_QUOTA_VERSION == 1 + *bytes_value_r = dqblk.dqb_curblocks * 1024ULL; +#else + *bytes_value_r = dqblk.dqb_curblocks; +#endif + *bytes_limit_r = dqblk.dqb_bsoftlimit * 1024ULL; + if (*bytes_limit_r == 0) { + *bytes_limit_r = dqblk.dqb_bhardlimit * 1024ULL; + } + *count_value_r = dqblk.dqb_curinodes; + *count_limit_r = dqblk.dqb_isoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_ihardlimit; + } + } + return 1; +} +#endif + +#ifdef FS_QUOTA_BSDAIX +static int +fs_quota_get_bsdaix(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + int type, id; + + type = group ? GRPQUOTA : USRQUOTA; + id = group ? root->gid : root->uid; + + if (quotactl(root->mount->mount_path, QCMD(Q_GETQUOTA, type), + id, (void *)&dqblk) < 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->mount_path); + return -1; + } + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE; + } + *count_value_r = dqblk.dqb_curinodes; + *count_limit_r = dqblk.dqb_isoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_ihardlimit; + } + return 1; +} +#endif + +#ifdef FS_QUOTA_NETBSD +static int +fs_quota_get_netbsd(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct quotakey qk; + struct quotaval qv; + struct quotahandle *qh; + int ret; + + if ((qh = quota_open(root->mount->mount_path)) == NULL) { + *error_r = t_strdup_printf("cannot open quota for %s: %m", + root->mount->mount_path); + fs_quota_root_disable(root, group); + return 0; + } + + qk.qk_idtype = group ? QUOTA_IDTYPE_GROUP : QUOTA_IDTYPE_USER; + qk.qk_id = group ? root->gid : root->uid; + + for (int i = 0; i < 2; i++) { + qk.qk_objtype = i == 0 ? QUOTA_OBJTYPE_BLOCKS : QUOTA_OBJTYPE_FILES; + + if (quota_get(qh, &qk, &qv) != 0) { + if (errno == ESRCH) { + fs_quota_root_disable(root, group); + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->mount_path); + ret = -1; + break; + } + if (i == 0) { + *bytes_value_r = qv.qv_usage * DEV_BSIZE; + *bytes_limit_r = qv.qv_softlimit * DEV_BSIZE; + } else { + *count_value_r = qv.qv_usage; + *count_limit_r = qv.qv_softlimit; + } + ret = 1; + } + quota_close(qh); + return ret; +} +#endif + +#ifdef FS_QUOTA_HPUX +static int +fs_quota_get_hpux(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + + if (quotactl(Q_GETQUOTA, root->mount->device_path, + root->uid, &dqblk) < 0) { + if (errno == ESRCH) { + root->user_disabled = TRUE; + return 0; + } + *error_r = t_strdup_printf( + "quotactl(Q_GETQUOTA, %s) failed: %m", + root->mount->device_path); + return -1; + } + + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * + root->mount->block_size; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * + root->mount->block_size; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * + root->mount->block_size; + } + *count_value_r = dqblk.dqb_curfiles; + *count_limit_r = dqblk.dqb_fsoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_fhardlimit; + } + return 1; +} +#endif + +#ifdef FS_QUOTA_SOLARIS +static int +fs_quota_get_solaris(struct fs_quota_root *root, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + struct dqblk dqblk; + struct quotctl ctl; + + if (root->mount->fd == -1) + return 0; + + ctl.op = Q_GETQUOTA; + ctl.uid = root->uid; + ctl.addr = (caddr_t)&dqblk; + if (ioctl(root->mount->fd, Q_QUOTACTL, &ctl) < 0) { + *error_r = t_strdup_printf( + "ioctl(%s, Q_QUOTACTL) failed: %m", + root->mount->path); + return -1; + } + *bytes_value_r = (uint64_t)dqblk.dqb_curblocks * DEV_BSIZE; + *bytes_limit_r = (uint64_t)dqblk.dqb_bsoftlimit * DEV_BSIZE; + if (*bytes_limit_r == 0) { + *bytes_limit_r = (uint64_t)dqblk.dqb_bhardlimit * DEV_BSIZE; + } + *count_value_r = dqblk.dqb_curfiles; + *count_limit_r = dqblk.dqb_fsoftlimit; + if (*count_limit_r == 0) { + *count_limit_r = dqblk.dqb_fhardlimit; + } + return 1; +} +#endif + +static int +fs_quota_get_resources(struct fs_quota_root *root, bool group, + uint64_t *bytes_value_r, uint64_t *bytes_limit_r, + uint64_t *count_value_r, uint64_t *count_limit_r, + const char **error_r) +{ + if (group) { + if (root->group_disabled) + return 0; + } else { + if (root->user_disabled) + return 0; + } +#ifdef FS_QUOTA_LINUX + return fs_quota_get_linux(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#elif defined (FS_QUOTA_NETBSD) + return fs_quota_get_netbsd(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#elif defined (FS_QUOTA_BSDAIX) + return fs_quota_get_bsdaix(root, group, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#else + if (group) { + /* not supported */ + return 0; + } +#ifdef FS_QUOTA_HPUX + return fs_quota_get_hpux(root, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#else + return fs_quota_get_solaris(root, bytes_value_r, bytes_limit_r, + count_value_r, count_limit_r, error_r); +#endif +#endif +} + +static bool fs_quota_match_box(struct quota_root *_root, struct mailbox *box) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + struct stat mst, rst; + const char *mailbox_path; + bool match; + + if (root->storage_mount_path == NULL) + return TRUE; + + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, + &mailbox_path) <= 0) + return FALSE; + if (stat(mailbox_path, &mst) < 0) { + if (errno != ENOENT) + e_error(_root->backend.event, + "stat(%s) failed: %m", mailbox_path); + return FALSE; + } + if (stat(root->storage_mount_path, &rst) < 0) { + e_debug(_root->backend.event, "stat(%s) failed: %m", + root->storage_mount_path); + return FALSE; + } + match = CMP_DEV_T(mst.st_dev, rst.st_dev); + e_debug(_root->backend.event, "box=%s mount=%s match=%s", mailbox_path, + root->storage_mount_path, match ? "yes" : "no"); + return match; +} + +static enum quota_get_result +fs_quota_get_resource(struct quota_root *_root, const char *name, + uint64_t *value_r, const char **error_r) +{ + struct fs_quota_root *root = (struct fs_quota_root *)_root; + uint64_t bytes_value, count_value; + uint64_t bytes_limit = 0, count_limit = 0; + int ret; + + *value_r = 0; + + if (root->mount == NULL) { + if (root->storage_mount_path != NULL) + *error_r = t_strdup_printf( + "Mount point unknown for path %s", + root->storage_mount_path); + else + *error_r = "Mount point unknown"; + return QUOTA_GET_RESULT_INTERNAL_ERROR; + } + if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) != 0 && + strcasecmp(name, QUOTA_NAME_MESSAGES) != 0) { + *error_r = QUOTA_UNKNOWN_RESOURCE_ERROR_STRING; + return QUOTA_GET_RESULT_UNKNOWN_RESOURCE; + } + +#ifdef HAVE_RQUOTA + if (mount_type_is_nfs(root->mount)) { + ret = root->user_disabled ? 0 : + do_rquota_user(root, &bytes_value, &bytes_limit, + &count_value, &count_limit, error_r); + if (ret == 0 && !root->group_disabled) + ret = do_rquota_group(root, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + } else +#endif + { + ret = fs_quota_get_resources(root, FALSE, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + if (ret == 0) { + /* fallback to group quota */ + ret = fs_quota_get_resources(root, TRUE, &bytes_value, + &bytes_limit, &count_value, + &count_limit, error_r); + } + } + if (ret < 0) + return QUOTA_GET_RESULT_INTERNAL_ERROR; + else if (ret == 0) + return QUOTA_GET_RESULT_LIMITED; + + if (strcasecmp(name, QUOTA_NAME_STORAGE_BYTES) == 0) + *value_r = bytes_value; + else + *value_r = count_value; + if (_root->bytes_limit != (int64_t)bytes_limit || + _root->count_limit != (int64_t)count_limit) { + /* update limit */ + _root->bytes_limit = bytes_limit; + _root->count_limit = count_limit; + + /* limits have changed, so we'll need to recalculate + relative quota rules */ + quota_root_recalculate_relative_rules(_root->set, bytes_limit, count_limit); + } + return QUOTA_GET_RESULT_LIMITED; +} + +static int +fs_quota_update(struct quota_root *root ATTR_UNUSED, + struct quota_transaction_context *ctx ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ + return 0; +} + +struct quota_backend quota_backend_fs = { + .name = "fs", + + .v = { + .alloc = fs_quota_alloc, + .init = fs_quota_init, + .deinit = fs_quota_deinit, + + .namespace_added = fs_quota_namespace_added, + + .get_resources = fs_quota_root_get_resources, + .get_resource = fs_quota_get_resource, + .update = fs_quota_update, + + .match_box = fs_quota_match_box, + } +}; + +#endif |