/*
 *	State changes for the XFS Quota Manager.
 *	Copyright (c) 2001 Silicon Graphics, Inc.
 */

#include "config.h"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "quotaon.h"
#include "dqblk_xfs.h"

#define QOFF	1
#define ACCT	2
#define ENFD	3

/*
 *	Ensure we don't attempt to go into a dodgey state.
 */

static int xfs_state_check(int qcmd, int type, int flags, struct mount_entry *mnt, int roothack, int xopts)
{
	struct xfs_mem_dqinfo info;
	int state;
	char *acctstr = "";

	/* we never want to operate via -a in XFS quota */
	if (flags & STATEFLAG_ALL)
		return 0;	/* noop */

	if (quotactl_mnt(Q_XFS_GETQSTAT, type, mnt, 0, (void *)&info) < 0) {
		errstr(_("quotactl() on %s: %s\n"), mnt->me_devname, strerror(errno));
		return -1;
	}

	/* establish current state before any transition */
	state = QOFF;
	if (type == USRQUOTA) {
		if (info.qs_flags & XFS_QUOTA_UDQ_ACCT)
			state = ACCT;
		if (info.qs_flags & XFS_QUOTA_UDQ_ENFD)
			state = ENFD;
	}
	else if (type == GRPQUOTA) {
		if (info.qs_flags & XFS_QUOTA_GDQ_ACCT)
			state = ACCT;
		if (info.qs_flags & XFS_QUOTA_GDQ_ENFD)
			state = ENFD;
	}
	else if (type == PRJQUOTA) {
		if (info.qs_flags & XFS_QUOTA_PDQ_ACCT)
			state = ACCT;
		if (info.qs_flags & XFS_QUOTA_PDQ_ENFD)
			state = ENFD;
	}

	switch (state) {
	  case QOFF:
		  switch (qcmd) {
		    case Q_XFS_QUOTARM:
			    return 1;
		    case Q_XFS_QUOTAON:
			    if (roothack) {
				    pinfo(_("Enabling %s quota on root filesystem"
					    " (reboot to take effect)\n"), _(type2name(type)));
				    return 1;
			    }
			    errstr(_("Enable XFS %s quota accounting during mount\n"),
				    _(type2name(type)));
			    return -1;
		    case Q_XFS_QUOTAOFF:
			    return 0;	/* noop */
		  }
		  break;
	  case ACCT:
		  switch (qcmd) {
		    case Q_XFS_QUOTARM:
			    errstr(_("Cannot delete %s quota on %s - "
					      "switch quota accounting off first\n"),
				    _(type2name(type)), mnt->me_devname);
			    return -1;
		    case Q_XFS_QUOTAON:
			    if (roothack) {
				    pinfo(_("Enabling %s quota on root filesystem"
					    " (reboot to take effect)\n"), _(type2name(type)));
				    return 1;
			    }
			    if (xopts & XFS_QUOTA_UDQ_ENFD ||
				xopts & XFS_QUOTA_GDQ_ENFD ||
				xopts & XFS_QUOTA_PDQ_ENFD) {
				    pinfo(_("Enabling %s quota enforcement on %s\n"), _(type2name(type)),
					  mnt->me_devname);
				    return 1;
			    }
			    errstr(_("Already accounting %s quota on %s\n"),
					_(type2name(type)), mnt->me_devname);
			    return -1;
		    case Q_XFS_QUOTAOFF:
			    if (xopts & XFS_QUOTA_UDQ_ACCT ||
				xopts & XFS_QUOTA_GDQ_ACCT ||
				xopts & XFS_QUOTA_PDQ_ACCT) {
				    pinfo(_("Disabling %s quota accounting on %s\n"),
					   _(type2name(type)), mnt->me_devname);
			    	    return 1;
			    }
			    errstr(_("Quota enforcement already disabled for %s on %s\n"),
					_(type2name(type)), mnt->me_devname);
			    return -1;
		  }
		  break;

	  case ENFD:
		  switch (qcmd) {
		    case Q_XFS_QUOTARM:
			    errstr(_("Cannot delete %s quota on %s - "
				      "switch quota enforcement and accounting off first\n"),
				    _(type2name(type)), mnt->me_devname);
			    return -1;
		    case Q_XFS_QUOTAON:
			    errstr(_("Enforcing %s quota already on %s\n"),
				    _(type2name(type)), mnt->me_devname);
			    return -1;
		    case Q_XFS_QUOTAOFF:
			    if (xopts == XFS_QUOTA_UDQ_ACCT ||
				xopts == XFS_QUOTA_GDQ_ACCT ||
				xopts == XFS_QUOTA_PDQ_ACCT) {
				    errstr(_("Cannot switch off %s quota "
					"accounting on %s when enforcement is on\n"),
					_(type2name(type)), mnt->me_devname);
				    return -1;
			    }
			    if (xopts & XFS_QUOTA_UDQ_ACCT ||
				xopts & XFS_QUOTA_GDQ_ACCT ||
				xopts & XFS_QUOTA_PDQ_ACCT)
		    		    acctstr = _("and accounting ");
			    pinfo(_("Disabling %s quota enforcement %son %s\n"),
				  _(type2name(type)), acctstr, mnt->me_devname);
			    return 1;
		  }
		  break;
	}
	errstr(_("Unexpected XFS quota state sought on %s\n"), mnt->me_devname);
	return -1;
}

static int xfs_onoff(struct mount_entry *mnt, int type, int flags, int roothack, int xopts)
{
	int qoff, qcmd, check;

	qoff = (flags & STATEFLAG_OFF);
	qcmd = qoff ? Q_XFS_QUOTAOFF : Q_XFS_QUOTAON;
	check = xfs_state_check(qcmd, type, flags, mnt, roothack, xopts);
	if (check != 1)
		return (check < 0);

	if (quotactl_mnt(qcmd, type, mnt, 0, (void *)&xopts) < 0) {
		errstr(_("quotactl on %s: %s\n"), mnt->me_devname, strerror(errno));
		return 1;
	}
	if (qoff)
		pinfo(_("%s: %s quotas turned off\n"), mnt->me_devname, _(type2name(type)));
	else
		pinfo(_("%s: %s quotas turned on\n"), mnt->me_devname, _(type2name(type)));
	return 0;
}

static int xfs_delete(struct mount_entry *mnt, int type, int flags, int roothack, int xopts)
{
	int qcmd, check;

	qcmd = Q_XFS_QUOTARM;
	check = xfs_state_check(qcmd, type, flags, mnt, roothack, xopts);
	if (check != 1)
		return (check < 0);

	if (quotactl_mnt(qcmd, type, mnt, 0, (void *)&xopts) < 0) {
		errstr(_("Failed to delete quota: %s\n"),
			strerror(errno));
		return 1;
	}

	pinfo(_("%s: deleted %s quota blocks\n"), mnt->me_devname,
	      _(type2name(type)));
	return 0;
}

/*
 *	Change state for given filesystem - on/off, acct/enfd, & delete.
 *	Must consider existing state and also whether or not this is the
 *	root filesystem.
 *	We are passed in the new requested state through "type" & "xarg".
 */
int xfs_newstate(struct mount_entry *mnt, int type, char *xarg, int flags)
{
	int err = 1;
	int xopts = 0;
	int roothack = 0;

#ifdef XFS_ROOTHACK
	/*
	 * Old XFS filesystems (up to XFS 1.2 / Linux 2.5.47) had a
	 * hack to allow enabling quota on the root filesystem without
	 * having to specify it at mount time.
	 */
	if ((strcmp(mnt->me_dir, "/") == 0)) {
		struct xfs_mem_dqinfo info;
		u_int16_t sbflags = 0;

		if (!quotactl_mnt(Q_XFS_GETQSTAT, type, mnt, 0, (void *)&info))
			sbflags = (info.qs_flags & 0xff00) >> 8;

		if ((type == USRQUOTA && (sbflags & XFS_QUOTA_UDQ_ACCT)) &&
		    (type == GRPQUOTA && (sbflags & XFS_QUOTA_GDQ_ACCT)))
			roothack = 1;
	}
#endif /* XFS_ROOTHACK */

	if (xarg == NULL || strcmp(xarg, "enforce") == 0) {	/* only enfd on/off */
		if (type == USRQUOTA)
			xopts |= XFS_QUOTA_UDQ_ENFD;
		else if (type == GRPQUOTA)
			xopts |= XFS_QUOTA_GDQ_ENFD;
		else if (type == PRJQUOTA)
			xopts |= XFS_QUOTA_PDQ_ENFD;
		err = xfs_onoff(mnt, type, flags, roothack, xopts);
	}
	else if (strcmp(xarg, "account") == 0) {
		if (type == USRQUOTA)
			xopts |= XFS_QUOTA_UDQ_ACCT;
		else if (type == GRPQUOTA)
			xopts |= XFS_QUOTA_GDQ_ACCT;
		else if (type == PRJQUOTA)
			xopts |= XFS_QUOTA_PDQ_ACCT;
		err = xfs_onoff(mnt, type, flags, roothack, xopts);
	}
	else if (strcmp(xarg, "delete") == 0) {
		if (type == USRQUOTA)
			xopts |= XFS_USER_QUOTA;
		else if (type == GRPQUOTA)
			xopts |= XFS_GROUP_QUOTA;
		else if (type == PRJQUOTA)
			xopts |= XFS_PROJ_QUOTA;
		err = xfs_delete(mnt, type, flags, roothack, xopts);
	}
	else
		die(1, _("Invalid argument \"%s\"\n"), xarg);
	return err;
}
