/*
 * Copyright (c) 2013 Fujitsu Ltd.
 * Author: DAN LI <li.dan@cn.fujitsu.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * Description:
 *	This tests basic flags of quotactl() syscall:
 *	1) Q_XQUOTAOFF - Turn off quotas for an XFS file system.
 *	2) Q_XQUOTAON - Turn on quotas for an XFS file system.
 *	3) Q_XGETQUOTA - Get disk quota limits and current usage for user id.
 *	4) Q_XSETQLIM - Set disk quota limits for user id.
 *	5) Q_XGETQSTAT - Get XFS file system specific quota information.
 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <errno.h>
#include <sys/mount.h>
#include <linux/fs.h>
#include <sys/types.h>

#include "config.h"
#if defined(HAVE_XFS_QUOTA)
# include <xfs/xqm.h>
#endif
#include "test.h"
#include "usctest.h"
#include "linux_syscall_numbers.h"
#include "safe_macros.h"

#define USRQCMD(cmd)	((cmd) << 8)
#define RTBLIMIT	2000

char *TCID = "quotactl02";
int TST_TOTAL = 5;

#if defined(HAVE_XFS_QUOTA)
static void check_qoff(void);
static void check_qon(void);
static void check_getq(void);
static void setup_setqlim(void), check_setqlim(void);
static void check_getqstat(void);

static void setup(void);
static void cleanup(void);

static int i;
static int uid;
static const char *block_dev;
static int mount_flag;
static struct fs_disk_quota dquota;
static struct fs_quota_stat qstat;
static unsigned int qflag = XFS_QUOTA_UDQ_ENFD;
static const char mntpoint[] = "mnt_point";

static struct test_case_t {
	int cmd;
	void *addr;
	void (*func_test) ();
	void (*func_setup) ();
} TC[] = {
	{Q_XQUOTAOFF, &qflag, check_qoff, NULL},
	{Q_XQUOTAON, &qflag, check_qon, NULL},
	{Q_XGETQUOTA, &dquota, check_getq, NULL},
	{Q_XSETQLIM, &dquota, check_setqlim, setup_setqlim},
	{Q_XGETQSTAT, &qstat, check_getqstat, NULL},
};

int main(int argc, char *argv[])
{
	int lc;
	const char *msg;

	msg = parse_opts(argc, argv, NULL, NULL);
	if (msg != NULL)
		tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg);

	setup();

	for (lc = 0; TEST_LOOPING(lc); ++lc) {

		tst_count = 0;

		for (i = 0; i < TST_TOTAL; i++) {

			if (TC[i].func_setup != NULL)
				(*TC[i].func_setup) ();

			TEST(ltp_syscall(__NR_quotactl,
					 USRQCMD(TC[i].cmd), block_dev,
					 uid, TC[i].addr));

			if (TEST_RETURN != 0)
				tst_resm(TFAIL | TERRNO,
					 "cmd=0x%x failed", TC[i].cmd);

			(*TC[i].func_test) ();
		}
	}
	cleanup();
	tst_exit();
}

static void check_qoff(void)
{
	int ret;

	ret = ltp_syscall(__NR_quotactl, USRQCMD(Q_XGETQSTAT),
			  block_dev, uid, &qstat);
	if (ret != 0)
		tst_brkm(TBROK | TERRNO, cleanup, "fail to get quota stat");

	if (qstat.qs_flags & XFS_QUOTA_UDQ_ENFD) {
		tst_resm(TFAIL, "enforcement is not off");
		return;
	}

	tst_resm(TPASS, "enforcement is off");
}

static void check_qon(void)
{
	int ret;
	ret = ltp_syscall(__NR_quotactl, USRQCMD(Q_XGETQSTAT),
			  block_dev, uid, &qstat);
	if (ret != 0)
		tst_brkm(TBROK | TERRNO, cleanup, "fail to get quota stat");

	if (!(qstat.qs_flags & XFS_QUOTA_UDQ_ENFD)) {
		tst_resm(TFAIL, "enforcement is off");
		return;
	}

	tst_resm(TPASS, "enforcement is on");
}

static void check_getq(void)
{
	if (!(dquota.d_flags & XFS_USER_QUOTA)) {
		tst_resm(TFAIL, "get incorrect quota type");
		return;
	}

	tst_resm(TPASS, "get the right quota type");
}

static void setup_setqlim(void)
{
	dquota.d_rtb_hardlimit = RTBLIMIT;
	dquota.d_fieldmask = FS_DQ_LIMIT_MASK;
}

static void check_setqlim(void)
{
	int ret;
	ret = ltp_syscall(__NR_quotactl, USRQCMD(Q_XGETQUOTA),
			  block_dev, uid, &dquota);
	if (ret != 0)
		tst_brkm(TFAIL | TERRNO, NULL,
			 "fail to get quota information");

	if (dquota.d_rtb_hardlimit != RTBLIMIT) {
		tst_resm(TFAIL, "limit on RTB, except %lu get %lu",
			 (uint64_t)RTBLIMIT,
			 (uint64_t)dquota.d_rtb_hardlimit);
		return;
	}

	tst_resm(TPASS, "quotactl works fine with Q_XSETQLIM");
}

static void check_getqstat(void)
{
	if (qstat.qs_version != FS_QSTAT_VERSION) {
		tst_resm(TFAIL, "get incorrect qstat version");
		return;
	}

	tst_resm(TPASS, "get correct qstat version");
}

static void setup(void)
{

	tst_require_root(NULL);

	TEST_PAUSE;

	tst_tmpdir();

	SAFE_MKDIR(cleanup, mntpoint, 0755);

	block_dev = tst_acquire_device(cleanup);

	if (!block_dev)
		tst_brkm(TCONF, cleanup, "Failed to obtain block device");

	tst_mkfs(cleanup, block_dev, "xfs", NULL);

	if (mount(block_dev, mntpoint, "xfs", 0, "uquota") < 0)
		tst_brkm(TFAIL | TERRNO, NULL, "mount(2) fail");
	mount_flag = 1;
}

static void cleanup(void)
{
	if (mount_flag && umount(mntpoint) < 0)
		tst_resm(TWARN | TERRNO, "umount(2) failed");

	if (block_dev)
		tst_release_device(NULL, block_dev);

	TEST_CLEANUP;
	tst_rmdir();
}
#else
int main(void)
{
	tst_brkm(TCONF, NULL, "This system doesn't support xfs quota");
}
#endif
