view src/plugins/quota/quota-fs.c @ 9658:8ba4253adc9b HEAD tip

*-login: SSL connections didn't get closed when the client got destroyed.
author Timo Sirainen <tss@iki.fi>
date Thu, 08 May 2014 16:41:29 +0300
parents edc7da213d4c
children
line wrap: on
line source

/* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */

/* Only for reporting filesystem quota */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "mountpoint.h"
#include "quota-private.h"
#include "quota-fs.h"

#ifdef HAVE_FS_QUOTA

#include <stdio.h>
#include <stdlib.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

/* Older sys/quota.h doesn't define _LINUX_QUOTA_VERSION at all, which means
   it supports only v1 quota */
#ifndef _LINUX_QUOTA_VERSION
#  define _LINUX_QUOTA_VERSION 1
#endif

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;

	unsigned int inode_per_mail:1;
	unsigned int user_disabled:1;
	unsigned int group_disabled:1;
};

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 int fs_quota_init(struct quota_root *_root, const char *args)
{
	struct fs_quota_root *root = (struct fs_quota_root *)_root;
	const char *const *tmp;

	if (args == NULL)
		return 0;

	for (tmp = t_strsplit(args, ":"); *tmp != NULL; tmp++) {
		if (strcmp(*tmp, "user") == 0)
			root->group_disabled = TRUE;
		else if (strcmp(*tmp, "group") == 0)
			root->user_disabled = TRUE;
		else if (strcmp(*tmp, "inode_per_mail") == 0)
			root->inode_per_mail = TRUE;
		else if (strcmp(*tmp, "noenforcing") == 0)
			_root->no_enforcing = TRUE;
		else if (strncmp(*tmp, "mount=", 6) == 0) {
			i_free(root->storage_mount_path);
			root->storage_mount_path = i_strdup(*tmp + 6);
		} else {
			i_error("fs quota: Invalid parameter: %s", *tmp);
			return -1;
		}
	}
	return 0;
}

static void fs_quota_mountpoint_free(struct fs_quota_mountpoint *mount)
{
	if (--mount->refcount > 0)
		return;

#ifdef FS_QUOTA_SOLARIS
	if (mount->fd != -1) {
		if (close(mount->fd) < 0)
			i_error("close(%s) failed: %m", 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 (strcmp(mount->type, "nfs") == 0) {
		if (strchr(mount->device_path, ':') == NULL) {
			i_error("quota-fs: %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(&quota->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)
{
	struct quota_root *const *roots;
	unsigned int i, count;

#ifdef FS_QUOTA_SOLARIS
#ifdef HAVE_RQUOTA
	if (strcmp(mount->type, "nfs") == 0) {
		/* 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)
			i_error("open(%s) failed: %m", mount->path);
	}
#endif
	root->mount = mount;

	/* 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(&quota->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);
	}
}

static void fs_quota_storage_added(struct quota *quota,
				   struct mail_storage *storage)
{
	struct fs_quota_mountpoint *mount;
	struct fs_quota_root *root;
	const char *dir;
	bool is_file;

	dir = mail_storage_get_mailbox_path(storage, "", &is_file);
	mount = fs_quota_mountpoint_get(dir);
	if (mount != NULL) {
		if (quota->set->debug) {
			i_info("fs quota add storage dir = %s", dir);
			i_info("fs quota block device = %s", mount->device_path);
			i_info("fs quota mount point = %s", mount->mount_path);
			i_info("fs quota mount type = %s", mount->type);
		}

		root = fs_quota_root_find_mountpoint(quota, mount);
		if (root != NULL && root->mount == NULL)
			fs_quota_mount_init(root, mount);
		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;
}

#ifdef HAVE_RQUOTA
static int do_rquota_user(struct fs_quota_root *root, bool bytes,
			  uint64_t *value_r, uint64_t *limit_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++;

	if (root->root.quota->set->debug) {
		i_info("quota-fs: host=%s, path=%s, uid=%s, %s",
		       host, path, dec2str(root->uid),
		       bytes ? "bytes" : "files");
	}

	/* clnt_create() polls for a while to establish a connection */
	cl = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp");
	if (cl == NULL) {
		i_error("quota-fs: 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);

		i_error("quota-fs: remote rquota call failed: %s",
			rpc_error_msg);
		return -1;
	}

	switch (result.status) {
	case Q_OK: {
		/* convert the results from blocks to bytes */
		const rquota *rq = &result.getquota_rslt_u.gqr_rquota;

		if (rq->rq_active) {
			if (bytes) {
				*value_r = (uint64_t)rq->rq_curblocks *
					(uint64_t)rq->rq_bsize;
				*limit_r = (uint64_t)rq->rq_bsoftlimit *
					(uint64_t)rq->rq_bsize;
			} else {
				*value_r = rq->rq_curfiles;
				*limit_r = rq->rq_fsoftlimit;
			}
		}
		if (root->root.quota->set->debug) {
			i_info("quota-fs: uid=%s, value=%llu, "
			       "limit=%llu, active=%d", dec2str(root->uid),
			       (unsigned long long)*value_r,
			       (unsigned long long)*limit_r, rq->rq_active);
		}
		return 1;
	}
	case Q_NOQUOTA:
		if (root->root.quota->set->debug) {
			i_info("quota-fs: uid=%s, limit=unlimited",
			       dec2str(root->uid));
		}
		return 1;
	case Q_EPERM:
		i_error("quota-fs: permission denied to rquota service");
		return -1;
	default:
		i_error("quota-fs: unrecognized status code (%d) "
			"from rquota service", result.status);
		return -1;
	}
}

static int do_rquota_group(struct fs_quota_root *root, bool bytes,