/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Disk quota editor.
 */
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/mnttab.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/fs/ufs_quota.h>
#include <sys/fs/ufs_fs.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iso/limits_iso.h>

#define	DEFEDITOR	"/usr/bin/vi"

#if DEV_BSIZE < 1024
#define	dbtok(x)	((x) / (1024 / DEV_BSIZE))
#define	ktodb(x)	((x) * (1024 / DEV_BSIZE))
#else
#define	dbtok(x)	((x) * (DEV_BSIZE / 1024))
#define	ktodb(x)	((x) / (DEV_BSIZE / 1024))
#endif

struct fsquot {
	struct fsquot *fsq_next;
	struct dqblk fsq_dqb;
	char *fsq_fs;
	char *fsq_dev;
	char *fsq_qfile;
};

static struct fsquot *fsqlist;

static char	tmpfil[] = "/tmp/EdP.aXXXXXX";
#define	QFNAME	"quotas"

static uid_t getentry(char *);
static int editit(void);
static void getprivs(uid_t);
static void putprivs(uid_t);
static void gettimes(uid_t);
static void puttimes(uid_t);
static char *next(char *, char *);
static int alldigits(char *);
static void fmttime(char *, ulong_t);
static int unfmttime(double, char *, uint32_t *);
static void setupfs(void);
static void getdiscq(uid_t);
static void putdiscq(uid_t);
static void sigsetmask(uint_t);
static uint_t sigblock(uint_t);
static void usage(void);
static int quotactl(int, char *, uid_t, caddr_t);

int
main(int argc, char **argv)
{
	uid_t	uid;
	char	*basename;
	int	opt;
	int	i;
	int	tmpfd = -1;

	basename = argv[0];
	if (argc < 2) {
		usage();
	}
	if (quotactl(Q_SYNC, (char *)NULL, 0, (caddr_t)NULL) < 0 &&
	    errno == EINVAL) {
		(void) printf("Warning: "
			"Quotas are not compiled into this kernel\n");
		(void) sleep(3);
	}
	if (getuid()) {
		(void) fprintf(stderr, "%s: permission denied\n", basename);
		exit(32);
	}
	setupfs();
	if (fsqlist == NULL) {
		(void) fprintf(stderr, "%s: no UFS filesystems with %s file\n",
		    MNTTAB, QFNAME);
		exit(32);
	}
	tmpfd = mkstemp(tmpfil);
	if (tmpfd == -1 || fchown(tmpfd, getuid(), getgid()) == -1) {
		fprintf(stderr, "failure in temporary file %s\n", tmpfil);
		exit(32);
	}
	(void) close(tmpfd);
	while ((opt = getopt(argc, argv, "p:tV")) != EOF)
		switch (opt) {
		case 't':
			gettimes(0);
			if (editit())
				puttimes(0);
			(void) unlink(tmpfil);
			exit(0);
			/*NOTREACHED*/

		case 'p':
			uid = getentry(optarg);
			if (uid > MAXUID) {
				(void) unlink(tmpfil);
				exit(32);
			}
			getprivs(uid);
			if (optind == argc) {
				(void) unlink(tmpfil);
				usage();
			}
			for (i = optind; i < argc; i++) {
				uid = getentry(argv[i]);
				if (uid > MAXUID) {
					(void) unlink(tmpfil);
					exit(32);
				}
				getdiscq(uid);
				putprivs(uid);
			}
			(void) unlink(tmpfil);
			exit(0);
			/*NOTREACHED*/

		case 'V':		/* Print command line */
			{
				char		*optt;
				int		optc;

				(void) printf("edquota -F UFS");
				for (optc = 1; optc < argc; optc++) {
					optt = argv[optc];
					if (optt)
						(void) printf(" %s ", optt);
				}
				(void) putchar('\n');
			}
			break;

		case '?':
			usage();
		}

	for (i = optind; i < argc; i++) {
		uid = getentry(argv[i]);
		if (uid > MAXUID)
			continue;
		getprivs(uid);
		if (editit())
			putprivs(uid);
		if (uid == 0) {
			(void) printf("edquota: Note that uid 0's quotas "
			    "are used as default values for other users,\n");
			(void) printf("not as a limit on the uid 0 user.\n");
		}
	}
	(void) unlink(tmpfil);
	return (0);
}

static uid_t
getentry(char *name)
{
	struct passwd *pw;
	uid_t uid;

	if (alldigits(name)) {
		errno = 0;
		uid = strtol(name, NULL, 10);
		if (errno == ERANGE) {
			/* name would cause overflow in uid */
			(void) fprintf(stderr, "edquota: uid %s too large\n",
			    name);
			(void) sleep(1);
			return (-1);
		}
	} else if (pw = getpwnam(name))
		uid = pw->pw_uid;
	else {
		(void) fprintf(stderr, "%s: no such user\n", name);
		(void) sleep(1);
		return (-1);
	}
	return (uid);
}

#define	RESPSZ	128

static int
editit(void)
{
	pid_t pid, xpid;
	char *ed;
	char resp[RESPSZ];
	int status, omask;

#define	mask(s)	(1 << ((s) - 1))
	omask = sigblock(mask(SIGINT)|mask(SIGQUIT)|mask(SIGHUP));

	if ((ed = getenv("EDITOR")) == (char *)0)
		ed = DEFEDITOR;

	/*CONSTANTCONDITION*/
	while (1) {
		if ((pid = fork()) < 0) {
			if (errno == EAGAIN) {
				(void) fprintf(stderr,
					"You have too many processes\n");
				return (0);
			}
			perror("fork");
			return (0);
		}
		if (pid == 0) {
			(void) sigsetmask(omask);
			(void) setgid(getgid());
			(void) setuid(getuid());
			(void) execlp(ed, ed, tmpfil, 0);
			(void) fprintf(stderr,
				"Can't exec editor \"%s\": ", ed);
			perror("");
			exit(32);
		}
		while ((xpid = wait(&status)) >= 0)
			if (xpid == pid)
				break;

		if (!isatty(fileno(stdin))) {	/* Non-interactive */
			break;
		}

		/*
		 * Certain editors can exit with a non-zero status even
		 * though everything is peachy. Best to ask the user what
		 * s/he really wants to do. (N.B.: if we're non-interactive
		 * we'll "break" the while loop before we get here.)
		 */
		if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) {
			(void) printf("Non-zero return from \"%s\", ", ed);
			(void) printf("updated file may contain errors.\n");
			/*CONSTANTCONDITION*/
			while (1) {
				(void) printf("Edit again (e) or quit, "
				    "discarding changes (q)? ");
				(void) fflush(stdout);
				if (gets(resp) == NULL) {
					return (0);
				}
				if ((*resp == 'e') || (*resp == 'q')) {
					break;
				}
			}

			if (*resp == 'e') {
				continue;
			} else {
				/*
				 * Since (*resp == 'q'), then we just
				 * want to break out of here and return
				 * the failure.
				 */
				break;
			}
		} else {
			break;	/* Successful return from editor */
		}
	}
	(void) sigsetmask(omask);
	return (!status);
}

static void
getprivs(uid_t uid)
{
	struct fsquot *fsqp;
	FILE *fd;

	getdiscq(uid);
	if ((fd = fopen64(tmpfil, "w")) == NULL) {
		(void) fprintf(stderr, "edquota: ");
		perror(tmpfil);
		(void) unlink(tmpfil);
		exit(32);
	}
	for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next)
		(void) fprintf(fd,
		    "fs %s blocks (soft = %lu, hard = %lu) "
		    "inodes (soft = %lu, hard = %lu)\n",
		    fsqp->fsq_fs,
		    dbtok(fsqp->fsq_dqb.dqb_bsoftlimit),
		    dbtok(fsqp->fsq_dqb.dqb_bhardlimit),
		    fsqp->fsq_dqb.dqb_fsoftlimit,
		    fsqp->fsq_dqb.dqb_fhardlimit);
	(void) fclose(fd);
}

static void
putprivs(uid_t uid)
{
	FILE *fd;
	uint64_t tmp_bsoftlimit, tmp_bhardlimit, tmp_fsoftlimit,
	    tmp_fhardlimit;
	char line[BUFSIZ];
	int changed = 0;
	uint32_t max_limit;
	int	quota_entry_printed;

	fd = fopen64(tmpfil, "r");
	if (fd == NULL) {
		(void) fprintf(stderr, "Can't re-read temp file!!\n");
		return;
	}
	while (fgets(line, sizeof (line), fd) != NULL) {
		struct fsquot *fsqp;
		char *cp, *dp;
		int n;

		cp = next(line, " \t");
		if (cp == NULL)
			break;
		*cp++ = '\0';
		while (*cp && *cp == '\t' && *cp == ' ')
			cp++;
		dp = cp, cp = next(cp, " \t");
		if (cp == NULL)
			break;
		*cp++ = '\0';
		for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) {
			if (strcmp(dp, fsqp->fsq_fs) == 0)
				break;
		}
		if (fsqp == NULL) {
			(void) fprintf(stderr, "%s: unknown file system\n", cp);
			continue;
		}
		while (*cp && *cp == '\t' && *cp == ' ')
			cp++;

		/*
		 * At this point, dp points to the mount point of the
		 * file system and cp points to the remainder of the
		 * quota definition string.
		 */
		n = sscanf(cp,
		    "blocks (soft = %llu, hard = %llu) "
		    "inodes (soft = %llu, hard = %llu)\n",
			&tmp_bsoftlimit,
			&tmp_bhardlimit,
			&tmp_fsoftlimit,
			&tmp_fhardlimit);

		if (n != 4) {
			(void) fprintf(stderr, "%s: bad format\n", cp);
			continue;
		}

		/*
		 * The values in dqb_bsoftlimit and dqb_bhardlimit
		 * are specified in 1k blocks in the edited quota
		 * file (the one we're reading), but are specified in
		 * disk blocks in the data structure passed to quotactl().
		 * That means that the maximum allowed value for the
		 * hard and soft block limits in the edited quota file
		 * is the maximum number of disk blocks allowed in a
		 * quota (which is 2^32 - 1, since it's a 32-bit unsigned
		 * quantity), converted to 1k blocks.
		 */
		max_limit = dbtok(UINT_MAX);

		quota_entry_printed = 0; /* only print quota entry once */

		if (tmp_bsoftlimit > max_limit) {
			tmp_bsoftlimit = max_limit;
			if (!quota_entry_printed) {
				(void) fprintf(stderr, "%s %s%\n", dp, cp);
				quota_entry_printed = 1;
			}
			(void) fprintf(stderr,
	"error: soft limit for blocks exceeds maximum allowed value,\n"
	"    soft limit for blocks set to %lu\n", max_limit);
		}

		if (tmp_bhardlimit > max_limit) {
			tmp_bhardlimit = max_limit;
			if (!quota_entry_printed) {
				(void) fprintf(stderr, "%s %s%\n", dp, cp);
				quota_entry_printed = 1;
			}
			(void) fprintf(stderr,
	"error: hard limit for blocks exceeds maximum allowed value,\n"
	"    hard limit for blocks set to %lu\n", max_limit);
		}


		/*
		 * Now check the file limits against their maximum, which
		 * is UINT_MAX (since it must fit in a uint32_t).
		 */
		max_limit = UINT_MAX;

		if (tmp_fsoftlimit > max_limit) {
			tmp_fsoftlimit = max_limit;
			if (!quota_entry_printed) {
				(void) fprintf(stderr, "%s %s%\n", dp, cp);
				quota_entry_printed = 1;
			}
			(void) fprintf(stderr,
	"error: soft limit for files exceeds maximum allowed value,\n"
	"    soft limit for files set to %lu\n", max_limit);
		}

		if (tmp_fhardlimit > max_limit) {
			tmp_fhardlimit = max_limit;
			if (!quota_entry_printed) {
				(void) fprintf(stderr, "%s %s%\n", dp, cp);
				quota_entry_printed = 1;
			}
			(void) fprintf(stderr,
	"error: hard limit for files exceeds maximum allowed value,\n"
	"    hard limit for files set to %lu\n", max_limit);
		}

		changed++;
		tmp_bsoftlimit = ktodb(tmp_bsoftlimit);
		tmp_bhardlimit = ktodb(tmp_bhardlimit);
		/*
		 * It we are decreasing the soft limits, set the time limits
		 * to zero, in case the user is now over quota.
		 * the time limit will be started the next time the
		 * user does an allocation.
		 */
		if (tmp_bsoftlimit < fsqp->fsq_dqb.dqb_bsoftlimit)
			fsqp->fsq_dqb.dqb_btimelimit = 0;
		if (tmp_fsoftlimit < fsqp->fsq_dqb.dqb_fsoftlimit)
			fsqp->fsq_dqb.dqb_ftimelimit = 0;
		fsqp->fsq_dqb.dqb_bsoftlimit = tmp_bsoftlimit;
		fsqp->fsq_dqb.dqb_bhardlimit = tmp_bhardlimit;
		fsqp->fsq_dqb.dqb_fsoftlimit = tmp_fsoftlimit;
		fsqp->fsq_dqb.dqb_fhardlimit = tmp_fhardlimit;
	}
	(void) fclose(fd);
	if (changed)
		putdiscq(uid);
}

static void
gettimes(uid_t uid)
{
	struct fsquot *fsqp;
	FILE *fd;
	char btime[80], ftime[80];

	getdiscq(uid);
	if ((fd = fopen64(tmpfil, "w")) == NULL) {
		(void) fprintf(stderr, "edquota: ");
		perror(tmpfil);
		(void) unlink(tmpfil);
		exit(32);
	}
	for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) {
		fmttime(btime, fsqp->fsq_dqb.dqb_btimelimit);
		fmttime(ftime, fsqp->fsq_dqb.dqb_ftimelimit);
		(void) fprintf(fd,
		    "fs %s blocks time limit = %s, files time limit = %s\n",
		    fsqp->fsq_fs, btime, ftime);
	}
	(void) fclose(fd);
}

static void
puttimes(uid_t uid)
{
	FILE *fd;
	char line[BUFSIZ];
	int changed = 0;
	double btimelimit, ftimelimit;
	char bunits[80], funits[80];

	fd = fopen64(tmpfil, "r");
	if (fd == NULL) {
		(void) fprintf(stderr, "Can't re-read temp file!!\n");
		return;
	}
	while (fgets(line, sizeof (line), fd) != NULL) {
		struct fsquot *fsqp;
		char *cp, *dp;
		int n;

		cp = next(line, " \t");
		if (cp == NULL)
			break;
		*cp++ = '\0';
		while (*cp && *cp == '\t' && *cp == ' ')
			cp++;
		dp = cp, cp = next(cp, " \t");
		if (cp == NULL)
			break;
		*cp++ = '\0';
		for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) {
			if (strcmp(dp, fsqp->fsq_fs) == 0)
				break;
		}
		if (fsqp == NULL) {
			(void) fprintf(stderr, "%s: unknown file system\n", cp);
			continue;
		}
		while (*cp && *cp == '\t' && *cp == ' ')
			cp++;
		n = sscanf(cp,
		    "blocks time limit = %lf %[^,], "
		    "files time limit = %lf %s\n",
		    &btimelimit, bunits, &ftimelimit, funits);
		if (n != 4 ||
		    !unfmttime(btimelimit, bunits,
			&fsqp->fsq_dqb.dqb_btimelimit) ||
		    !unfmttime(ftimelimit, funits,
			&fsqp->fsq_dqb.dqb_ftimelimit)) {
			(void) fprintf(stderr, "%s: bad format\n", cp);
			continue;
		}
		changed++;
	}
	(void) fclose(fd);
	if (changed)
		putdiscq(uid);
}

static char *
next(char *cp, char *match)
{
	char *dp;

	while (cp && *cp) {
		for (dp = match; dp && *dp; dp++)
			if (*dp == *cp)
				return (cp);
		cp++;
	}
	return ((char *)0);
}

static int
alldigits(char *s)
{
	int c = *s++;

	do {
		if (!isdigit(c))
			return (0);
	} while ((c = *s++) != '\0');

	return (1);
}

static struct {
	int c_secs;			/* conversion units in secs */
	char *c_str;			/* unit string */
} cunits [] = {
	{60*60*24*28, "month"},
	{60*60*24*7, "week"},
	{60*60*24, "day"},
	{60*60, "hour"},
	{60, "min"},
	{1, "sec"}
};

static void
fmttime(char *buf, ulong_t time)
{
	double value;
	int i;

	if (time == 0) {
		(void) strcpy(buf, "0 (default)");
		return;
	}
	for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++)
		if (time >= cunits[i].c_secs)
			break;

	value = (double)time / cunits[i].c_secs;
	(void) sprintf(buf, "%.2f %s%s",
		value, cunits[i].c_str, value > 1.0 ? "s" : "");
}

static int
unfmttime(double value, char *units, uint32_t *timep)
{
	int i;

	if (value == 0.0) {
		*timep = 0;
		return (1);
	}
	for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) {
		if (strncmp(cunits[i].c_str, units,
		    strlen(cunits[i].c_str)) == 0)
			break;
	}
	if (i >= sizeof (cunits) / sizeof (cunits[0]))
		return (0);
	*timep = (ulong_t)(value * cunits[i].c_secs);
	return (1);
}

static void
setupfs(void)
{
	struct mnttab mntp;
	struct fsquot *fsqp;
	struct stat64 statb;
	dev_t fsdev;
	FILE *mtab;
	char qfilename[MAXPATHLEN];

	if ((mtab = fopen(MNTTAB, "r")) == (FILE *)0) {
		perror("/etc/mnttab");
		exit(31+1);
	}
	while (getmntent(mtab, &mntp) == 0) {
		if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0)
			continue;
		if (stat64(mntp.mnt_special, &statb) < 0)
			continue;
		if ((statb.st_mode & S_IFMT) != S_IFBLK)
			continue;
		fsdev = statb.st_rdev;
		(void) snprintf(qfilename, sizeof (qfilename), "%s/%s",
			mntp.mnt_mountp, QFNAME);
		if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev)
			continue;
		fsqp = malloc(sizeof (struct fsquot));
		if (fsqp == NULL) {
			(void) fprintf(stderr, "out of memory\n");
			exit(31+1);
		}
		fsqp->fsq_next = fsqlist;
		fsqp->fsq_fs = strdup(mntp.mnt_mountp);
		fsqp->fsq_dev = strdup(mntp.mnt_special);
		fsqp->fsq_qfile = strdup(qfilename);
		if (fsqp->fsq_fs == NULL || fsqp->fsq_dev == NULL ||
		    fsqp->fsq_qfile == NULL) {
			(void) fprintf(stderr, "out of memory\n");
			exit(31+1);
		}
		fsqlist = fsqp;
	}
	(void) fclose(mtab);
}

static void
getdiscq(uid_t uid)
{
	struct fsquot *fsqp;
	int fd;

	for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) {
		if (quotactl(Q_GETQUOTA, fsqp->fsq_dev, uid,
		    (caddr_t)&fsqp->fsq_dqb) != 0) {
			if ((fd = open64(fsqp->fsq_qfile, O_RDONLY)) < 0) {
				(void) fprintf(stderr, "edquota: ");
				perror(fsqp->fsq_qfile);
				continue;
			}
			(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
			switch (read(fd, (char *)&fsqp->fsq_dqb,
			    sizeof (struct dqblk))) {
			case 0:
				/*
				 * Convert implicit 0 quota (EOF)
				 * into an explicit one (zero'ed dqblk)
				 */
				bzero((caddr_t)&fsqp->fsq_dqb,
				    sizeof (struct dqblk));
				break;

			case sizeof (struct dqblk):	/* OK */
				break;

			default:			/* ERROR */
				(void) fprintf(stderr,
				    "edquota: read error in ");
				perror(fsqp->fsq_qfile);
				break;
			}
			(void) close(fd);
		}
	}
}

static void
putdiscq(uid_t uid)
{
	struct fsquot *fsqp;

	for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) {
		if (quotactl(Q_SETQLIM, fsqp->fsq_dev, uid,
		    (caddr_t)&fsqp->fsq_dqb) != 0) {
			int fd;

			if ((fd = open64(fsqp->fsq_qfile, O_RDWR)) < 0) {
				(void) fprintf(stderr, "edquota: ");
				perror(fsqp->fsq_qfile);
				continue;
			}
			(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
			if (write(fd, (char *)&fsqp->fsq_dqb,
			    sizeof (struct dqblk)) != sizeof (struct dqblk)) {
				(void) fprintf(stderr, "edquota: ");
				perror(fsqp->fsq_qfile);
			}
			(void) close(fd);
		}
	}
}

static void
sigsetmask(uint_t omask)
{
	int i;

	for (i = 0; i < 32; i++)
		if (omask & (1 << i)) {
			if (sigignore(1 << i) == (int)SIG_ERR) {
				(void) fprintf(stderr,
				    "Bad signal 0x%x\n", (1 << i));
				exit(31+1);
			}
		}
}

static uint_t
sigblock(uint_t omask)
{
	uint_t previous = 0;
	uint_t temp;
	int i;

	for (i = 0; i < 32; i++)
		if (omask & (1 << i)) {
			if ((temp = sigignore(1 << i)) == (int)SIG_ERR) {
				(void) fprintf(stderr,
				    "Bad signal 0x%x\n", (1 << i));
				exit(31+1);
			}
			if (i == 0)
				previous = temp;
		}

	return (previous);
}

static void
usage(void)
{
	(void) fprintf(stderr, "ufs usage:\n");
	(void) fprintf(stderr, "\tedquota [-p username] username ...\n");
	(void) fprintf(stderr, "\tedquota -t\n");
	exit(1);
}

static int
quotactl(int cmd, char *special, uid_t uid, caddr_t addr)
{
	int		fd;
	int		status;
	struct quotctl	quota;
	char		qfile[MAXPATHLEN];
	FILE		*fstab;
	struct mnttab	mntp;

	if ((special == NULL) && (cmd == Q_SYNC)) {
		cmd = Q_ALLSYNC;
		/*
		 * need to find an acceptable fd to send this Q_ALLSYNC down
		 * on, it needs to be a ufs fd for vfs to at least call the
		 * real quotactl() in the kernel
		 * Here, try to simply find the starting mountpoint of the
		 * first mounted ufs file system
		 */
	}

	/*
	 * Find the mount point of the special device.   This is
	 * because the fcntl that implements the quotactl call has
	 * to go to a real file, and not to the block device.
	 */
	if ((fstab = fopen(MNTTAB, "r")) == NULL) {
		(void) fprintf(stderr, "%s: ", MNTTAB);
		perror("open");
		exit(31+1);
	}
	qfile[0] = '\0';
	while ((status = getmntent(fstab, &mntp)) == NULL) {
		/*
		 * check that it is a ufs file system
		 * for all quotactl()s except Q_ALLSYNC check that
		 * the file system is read-write since changes in the
		 * quotas file may be required
		 * for Q_ALLSYNC, this check is skipped since this option
		 * is to determine if quotas are configured into the system
		 */
		if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
		    ((cmd != Q_ALLSYNC) && hasmntopt(&mntp, MNTOPT_RO)))
			continue;
		if (cmd == Q_ALLSYNC) {	/* implies (special==0) too */
			if (strlcpy(qfile, mntp.mnt_mountp,
				    sizeof (qfile)) >= sizeof (qfile)) {
				errno = ENOENT;
				return (-1);
			}
			break;
		}
		if (strcmp(special, mntp.mnt_special) == 0) {
			if (strlcpy(qfile, mntp.mnt_mountp,
				    sizeof (qfile)) >= sizeof (qfile)) {
				errno = ENOENT;
				return (-1);
			}
		}
	}
	(void) fclose(fstab);
	if (qfile[0] == '\0') {
		errno = ENOENT;
		return (-1);
	}
	{
		int open_flags;

		if (cmd == Q_ALLSYNC) {
			open_flags = O_RDONLY;
		} else {
			if (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
			    sizeof (qfile)) {
				errno = ENOENT;
				return (-1);
			}
			open_flags = O_RDWR;
		}

		if ((fd = open64(qfile, open_flags)) < 0) {
			(void) fprintf(stderr, "quotactl: ");
			perror("open");
			exit(31+1);
		}
	}

	quota.op = cmd;
	quota.uid = uid;
	quota.addr = addr;
	status = ioctl(fd, Q_QUOTACTL, &quota);
	(void) close(fd);
	return (status);
}