xref: /freebsd/lib/libutil/quotafile.c (revision e738085b)
11b3515f3SDag-Erling Smørgrav /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
35e53a4f9SPedro F. Giffuni  *
4e738085bSDag-Erling Smørgrav  * Copyright (c) 2008 Dag-Erling Smørgrav
58bd6a3abSKirk McKusick  * Copyright (c) 2008 Marshall Kirk McKusick
61b3515f3SDag-Erling Smørgrav  * All rights reserved.
71b3515f3SDag-Erling Smørgrav  *
81b3515f3SDag-Erling Smørgrav  * Redistribution and use in source and binary forms, with or without
91b3515f3SDag-Erling Smørgrav  * modification, are permitted provided that the following conditions
101b3515f3SDag-Erling Smørgrav  * are met:
111b3515f3SDag-Erling Smørgrav  * 1. Redistributions of source code must retain the above copyright
121b3515f3SDag-Erling Smørgrav  *    notice, this list of conditions and the following disclaimer
131b3515f3SDag-Erling Smørgrav  *    in this position and unchanged.
141b3515f3SDag-Erling Smørgrav  * 2. Redistributions in binary form must reproduce the above copyright
151b3515f3SDag-Erling Smørgrav  *    notice, this list of conditions and the following disclaimer in the
161b3515f3SDag-Erling Smørgrav  *    documentation and/or other materials provided with the distribution.
171b3515f3SDag-Erling Smørgrav  *
181b3515f3SDag-Erling Smørgrav  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
191b3515f3SDag-Erling Smørgrav  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
201b3515f3SDag-Erling Smørgrav  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
211b3515f3SDag-Erling Smørgrav  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
221b3515f3SDag-Erling Smørgrav  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
231b3515f3SDag-Erling Smørgrav  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
241b3515f3SDag-Erling Smørgrav  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
251b3515f3SDag-Erling Smørgrav  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
261b3515f3SDag-Erling Smørgrav  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
271b3515f3SDag-Erling Smørgrav  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
281b3515f3SDag-Erling Smørgrav  * SUCH DAMAGE.
291b3515f3SDag-Erling Smørgrav  */
301b3515f3SDag-Erling Smørgrav 
311b3515f3SDag-Erling Smørgrav #include <sys/types.h>
321b3515f3SDag-Erling Smørgrav #include <sys/endian.h>
33916e406eSKirk McKusick #include <sys/mount.h>
341b3515f3SDag-Erling Smørgrav #include <sys/stat.h>
351b3515f3SDag-Erling Smørgrav 
361b3515f3SDag-Erling Smørgrav #include <ufs/ufs/quota.h>
371b3515f3SDag-Erling Smørgrav 
381b3515f3SDag-Erling Smørgrav #include <errno.h>
391b3515f3SDag-Erling Smørgrav #include <fcntl.h>
40916e406eSKirk McKusick #include <fstab.h>
411b3515f3SDag-Erling Smørgrav #include <grp.h>
421b3515f3SDag-Erling Smørgrav #include <pwd.h>
431b3515f3SDag-Erling Smørgrav #include <libutil.h>
441b3515f3SDag-Erling Smørgrav #include <stdint.h>
45916e406eSKirk McKusick #include <stdio.h>
461b3515f3SDag-Erling Smørgrav #include <stdlib.h>
471b3515f3SDag-Erling Smørgrav #include <string.h>
481b3515f3SDag-Erling Smørgrav #include <unistd.h>
491b3515f3SDag-Erling Smørgrav 
501b3515f3SDag-Erling Smørgrav struct quotafile {
518bd6a3abSKirk McKusick 	int fd;				/* -1 means using quotactl for access */
525666aadbSDag-Erling Smørgrav 	int accmode;			/* access mode */
538bd6a3abSKirk McKusick 	int wordsize;			/* 32-bit or 64-bit limits */
548bd6a3abSKirk McKusick 	int quotatype;			/* USRQUOTA or GRPQUOTA */
555666aadbSDag-Erling Smørgrav 	dev_t dev;			/* device */
568bd6a3abSKirk McKusick 	char fsname[MAXPATHLEN + 1];	/* mount point of filesystem */
578bd6a3abSKirk McKusick 	char qfname[MAXPATHLEN + 1];	/* quota file if not using quotactl */
581b3515f3SDag-Erling Smørgrav };
591b3515f3SDag-Erling Smørgrav 
60916e406eSKirk McKusick static const char *qfextension[] = INITQFNAMES;
61916e406eSKirk McKusick 
628bd6a3abSKirk McKusick /*
638bd6a3abSKirk McKusick  * Check to see if a particular quota is to be enabled.
648bd6a3abSKirk McKusick  */
658bd6a3abSKirk McKusick static int
hasquota(struct fstab * fs,int type,char * qfnamep,int qfbufsize)668bd6a3abSKirk McKusick hasquota(struct fstab *fs, int type, char *qfnamep, int qfbufsize)
678bd6a3abSKirk McKusick {
688bd6a3abSKirk McKusick 	char *opt;
698bd6a3abSKirk McKusick 	char *cp;
708bd6a3abSKirk McKusick 	struct statfs sfb;
718bd6a3abSKirk McKusick 	char buf[BUFSIZ];
728bd6a3abSKirk McKusick 	static char initname, usrname[100], grpname[100];
738bd6a3abSKirk McKusick 
745666aadbSDag-Erling Smørgrav 	/*
755666aadbSDag-Erling Smørgrav 	 * 1) we only need one of these
765666aadbSDag-Erling Smørgrav 	 * 2) fstab may specify a different filename
775666aadbSDag-Erling Smørgrav 	 */
788bd6a3abSKirk McKusick 	if (!initname) {
798bd6a3abSKirk McKusick 		(void)snprintf(usrname, sizeof(usrname), "%s%s",
808bd6a3abSKirk McKusick 		    qfextension[USRQUOTA], QUOTAFILENAME);
818bd6a3abSKirk McKusick 		(void)snprintf(grpname, sizeof(grpname), "%s%s",
828bd6a3abSKirk McKusick 		    qfextension[GRPQUOTA], QUOTAFILENAME);
838bd6a3abSKirk McKusick 		initname = 1;
848bd6a3abSKirk McKusick 	}
858bd6a3abSKirk McKusick 	strcpy(buf, fs->fs_mntops);
868bd6a3abSKirk McKusick 	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
87b3608ae1SEd Schouten 		if ((cp = strchr(opt, '=')))
888bd6a3abSKirk McKusick 			*cp++ = '\0';
898bd6a3abSKirk McKusick 		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
908bd6a3abSKirk McKusick 			break;
918bd6a3abSKirk McKusick 		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
928bd6a3abSKirk McKusick 			break;
938bd6a3abSKirk McKusick 	}
948bd6a3abSKirk McKusick 	if (!opt)
958bd6a3abSKirk McKusick 		return (0);
968bd6a3abSKirk McKusick 	/*
978bd6a3abSKirk McKusick 	 * Ensure that the filesystem is mounted.
988bd6a3abSKirk McKusick 	 */
998bd6a3abSKirk McKusick 	if (statfs(fs->fs_file, &sfb) != 0 ||
1008bd6a3abSKirk McKusick 	    strcmp(fs->fs_file, sfb.f_mntonname)) {
1018bd6a3abSKirk McKusick 		return (0);
1028bd6a3abSKirk McKusick 	}
1038bd6a3abSKirk McKusick 	if (cp) {
1041e4da04fSXin LI 		strlcpy(qfnamep, cp, qfbufsize);
1058bd6a3abSKirk McKusick 	} else {
1068bd6a3abSKirk McKusick 		(void)snprintf(qfnamep, qfbufsize, "%s/%s.%s", fs->fs_file,
1078bd6a3abSKirk McKusick 		    QUOTAFILENAME, qfextension[type]);
1088bd6a3abSKirk McKusick 	}
1098bd6a3abSKirk McKusick 	return (1);
1108bd6a3abSKirk McKusick }
1118bd6a3abSKirk McKusick 
1121b3515f3SDag-Erling Smørgrav struct quotafile *
quota_open(struct fstab * fs,int quotatype,int openflags)1138bd6a3abSKirk McKusick quota_open(struct fstab *fs, int quotatype, int openflags)
1141b3515f3SDag-Erling Smørgrav {
1151b3515f3SDag-Erling Smørgrav 	struct quotafile *qf;
1161b3515f3SDag-Erling Smørgrav 	struct dqhdr64 dqh;
1178bd6a3abSKirk McKusick 	struct group *grp;
1185666aadbSDag-Erling Smørgrav 	struct stat st;
11918bb746bSSean Eric Fagan 	int qcmd, serrno = 0;
120148d31b8SSean Eric Fagan 	int ufs;
1211b3515f3SDag-Erling Smørgrav 
1221b3515f3SDag-Erling Smørgrav 	if ((qf = calloc(1, sizeof(*qf))) == NULL)
1231b3515f3SDag-Erling Smørgrav 		return (NULL);
1245666aadbSDag-Erling Smørgrav 	qf->fd = -1;
1258bd6a3abSKirk McKusick 	qf->quotatype = quotatype;
1264238b561SDon Lewis 	strlcpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
1275666aadbSDag-Erling Smørgrav 	if (stat(qf->fsname, &st) != 0)
1285666aadbSDag-Erling Smørgrav 		goto error;
1295666aadbSDag-Erling Smørgrav 	qf->dev = st.st_dev;
130516ad57bSKirk McKusick 	qcmd = QCMD(Q_GETQUOTASIZE, quotatype);
131148d31b8SSean Eric Fagan 	ufs = strcmp(fs->fs_vfstype, "ufs") == 0;
132148d31b8SSean Eric Fagan 	/*
133148d31b8SSean Eric Fagan 	 * On UFS, hasquota() fills in qf->qfname. But we only care about
134148d31b8SSean Eric Fagan 	 * this for UFS.  So we need to call hasquota() for UFS, first.
135148d31b8SSean Eric Fagan 	 */
136148d31b8SSean Eric Fagan 	if (ufs) {
137148d31b8SSean Eric Fagan 		serrno = hasquota(fs, quotatype, qf->qfname,
138148d31b8SSean Eric Fagan 		    sizeof(qf->qfname));
139148d31b8SSean Eric Fagan 	}
140516ad57bSKirk McKusick 	if (quotactl(qf->fsname, qcmd, 0, &qf->wordsize) == 0)
1418bd6a3abSKirk McKusick 		return (qf);
142148d31b8SSean Eric Fagan 	if (!ufs) {
143aad5531eSSean Eric Fagan 		errno = 0;
144aad5531eSSean Eric Fagan 		goto error;
145148d31b8SSean Eric Fagan 	} else if (serrno == 0) {
1468bd6a3abSKirk McKusick 		errno = EOPNOTSUPP;
1475666aadbSDag-Erling Smørgrav 		goto error;
1488bd6a3abSKirk McKusick 	}
1495666aadbSDag-Erling Smørgrav 	qf->accmode = openflags & O_ACCMODE;
150d1d4d952SJilles Tjoelker 	if ((qf->fd = open(qf->qfname, qf->accmode|O_CLOEXEC)) < 0 &&
1515666aadbSDag-Erling Smørgrav 	    (openflags & O_CREAT) != O_CREAT)
1525666aadbSDag-Erling Smørgrav 		goto error;
1538bd6a3abSKirk McKusick 	/* File open worked, so process it */
1548bd6a3abSKirk McKusick 	if (qf->fd != -1) {
1558bd6a3abSKirk McKusick 		qf->wordsize = 32;
1561b3515f3SDag-Erling Smørgrav 		switch (read(qf->fd, &dqh, sizeof(dqh))) {
1571b3515f3SDag-Erling Smørgrav 		case -1:
1585666aadbSDag-Erling Smørgrav 			goto error;
1591b3515f3SDag-Erling Smørgrav 		case sizeof(dqh):
1601b3515f3SDag-Erling Smørgrav 			if (strcmp(dqh.dqh_magic, Q_DQHDR64_MAGIC) != 0) {
1611b3515f3SDag-Erling Smørgrav 				/* no magic, assume 32 bits */
1628bd6a3abSKirk McKusick 				qf->wordsize = 32;
1631b3515f3SDag-Erling Smørgrav 				return (qf);
1641b3515f3SDag-Erling Smørgrav 			}
1651b3515f3SDag-Erling Smørgrav 			if (be32toh(dqh.dqh_version) != Q_DQHDR64_VERSION ||
1661b3515f3SDag-Erling Smørgrav 			    be32toh(dqh.dqh_hdrlen) != sizeof(struct dqhdr64) ||
1671b3515f3SDag-Erling Smørgrav 			    be32toh(dqh.dqh_reclen) != sizeof(struct dqblk64)) {
1681b3515f3SDag-Erling Smørgrav 				/* correct magic, wrong version / lengths */
1691b3515f3SDag-Erling Smørgrav 				errno = EINVAL;
1705666aadbSDag-Erling Smørgrav 				goto error;
1711b3515f3SDag-Erling Smørgrav 			}
1728bd6a3abSKirk McKusick 			qf->wordsize = 64;
1731b3515f3SDag-Erling Smørgrav 			return (qf);
1741b3515f3SDag-Erling Smørgrav 		default:
1758bd6a3abSKirk McKusick 			qf->wordsize = 32;
1761b3515f3SDag-Erling Smørgrav 			return (qf);
1771b3515f3SDag-Erling Smørgrav 		}
1781b3515f3SDag-Erling Smørgrav 		/* not reached */
1791b3515f3SDag-Erling Smørgrav 	}
1805666aadbSDag-Erling Smørgrav 	/* open failed, but O_CREAT was specified, so create a new file */
181d1d4d952SJilles Tjoelker 	if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0)) <
182d1d4d952SJilles Tjoelker 	    0)
1835666aadbSDag-Erling Smørgrav 		goto error;
1848bd6a3abSKirk McKusick 	qf->wordsize = 64;
1851b3515f3SDag-Erling Smørgrav 	memset(&dqh, 0, sizeof(dqh));
1861b3515f3SDag-Erling Smørgrav 	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
1871b3515f3SDag-Erling Smørgrav 	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
1881b3515f3SDag-Erling Smørgrav 	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
1891b3515f3SDag-Erling Smørgrav 	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
190c3616249SKirk McKusick 	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
191c3616249SKirk McKusick 		/* it was one we created ourselves */
192c3616249SKirk McKusick 		unlink(qf->qfname);
1935666aadbSDag-Erling Smørgrav 		goto error;
194c3616249SKirk McKusick 	}
1951b3515f3SDag-Erling Smørgrav 	grp = getgrnam(QUOTAGROUP);
1961b3515f3SDag-Erling Smørgrav 	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
1971b3515f3SDag-Erling Smørgrav 	fchmod(qf->fd, 0640);
1981b3515f3SDag-Erling Smørgrav 	return (qf);
1995666aadbSDag-Erling Smørgrav error:
2005666aadbSDag-Erling Smørgrav 	serrno = errno;
2015666aadbSDag-Erling Smørgrav 	/* did we have an open file? */
202c3616249SKirk McKusick 	if (qf->fd != -1)
2035666aadbSDag-Erling Smørgrav 		close(qf->fd);
2045666aadbSDag-Erling Smørgrav 	free(qf);
2055666aadbSDag-Erling Smørgrav 	errno = serrno;
2065666aadbSDag-Erling Smørgrav 	return (NULL);
2071b3515f3SDag-Erling Smørgrav }
2081b3515f3SDag-Erling Smørgrav 
2091b3515f3SDag-Erling Smørgrav void
quota_close(struct quotafile * qf)2101b3515f3SDag-Erling Smørgrav quota_close(struct quotafile *qf)
2111b3515f3SDag-Erling Smørgrav {
2121b3515f3SDag-Erling Smørgrav 
2138bd6a3abSKirk McKusick 	if (qf->fd != -1)
2141b3515f3SDag-Erling Smørgrav 		close(qf->fd);
2151b3515f3SDag-Erling Smørgrav 	free(qf);
2161b3515f3SDag-Erling Smørgrav }
2171b3515f3SDag-Erling Smørgrav 
218e525d16aSKirk McKusick int
quota_on(struct quotafile * qf)219e525d16aSKirk McKusick quota_on(struct quotafile *qf)
220e525d16aSKirk McKusick {
221e525d16aSKirk McKusick 	int qcmd;
222e525d16aSKirk McKusick 
223e525d16aSKirk McKusick 	qcmd = QCMD(Q_QUOTAON, qf->quotatype);
224e525d16aSKirk McKusick 	return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
225e525d16aSKirk McKusick }
226e525d16aSKirk McKusick 
227e525d16aSKirk McKusick int
quota_off(struct quotafile * qf)228e525d16aSKirk McKusick quota_off(struct quotafile *qf)
229e525d16aSKirk McKusick {
230e525d16aSKirk McKusick 
231e525d16aSKirk McKusick 	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
232e525d16aSKirk McKusick }
233e525d16aSKirk McKusick 
2345666aadbSDag-Erling Smørgrav const char *
quota_fsname(const struct quotafile * qf)2355666aadbSDag-Erling Smørgrav quota_fsname(const struct quotafile *qf)
2365666aadbSDag-Erling Smørgrav {
2375666aadbSDag-Erling Smørgrav 
2385666aadbSDag-Erling Smørgrav 	return (qf->fsname);
2395666aadbSDag-Erling Smørgrav }
2405666aadbSDag-Erling Smørgrav 
2415666aadbSDag-Erling Smørgrav const char *
quota_qfname(const struct quotafile * qf)2425666aadbSDag-Erling Smørgrav quota_qfname(const struct quotafile *qf)
2435666aadbSDag-Erling Smørgrav {
2445666aadbSDag-Erling Smørgrav 
2455666aadbSDag-Erling Smørgrav 	return (qf->qfname);
2465666aadbSDag-Erling Smørgrav }
2475666aadbSDag-Erling Smørgrav 
2485666aadbSDag-Erling Smørgrav int
quota_check_path(const struct quotafile * qf,const char * path)2495666aadbSDag-Erling Smørgrav quota_check_path(const struct quotafile *qf, const char *path)
2505666aadbSDag-Erling Smørgrav {
2515666aadbSDag-Erling Smørgrav 	struct stat st;
2525666aadbSDag-Erling Smørgrav 
2535666aadbSDag-Erling Smørgrav 	if (stat(path, &st) == -1)
2545666aadbSDag-Erling Smørgrav 		return (-1);
2555666aadbSDag-Erling Smørgrav 	return (st.st_dev == qf->dev);
2565666aadbSDag-Erling Smørgrav }
2575666aadbSDag-Erling Smørgrav 
2586197731dSKirk McKusick int
quota_maxid(struct quotafile * qf)2596197731dSKirk McKusick quota_maxid(struct quotafile *qf)
2606197731dSKirk McKusick {
2616197731dSKirk McKusick 	struct stat st;
262516ad57bSKirk McKusick 	int maxid;
2636197731dSKirk McKusick 
2646197731dSKirk McKusick 	if (stat(qf->qfname, &st) < 0)
2656197731dSKirk McKusick 		return (0);
2666197731dSKirk McKusick 	switch (qf->wordsize) {
2676197731dSKirk McKusick 	case 32:
268516ad57bSKirk McKusick 		maxid = st.st_size / sizeof(struct dqblk32) - 1;
269516ad57bSKirk McKusick 		break;
2706197731dSKirk McKusick 	case 64:
271516ad57bSKirk McKusick 		maxid = st.st_size / sizeof(struct dqblk64) - 2;
272516ad57bSKirk McKusick 		break;
2736197731dSKirk McKusick 	default:
274516ad57bSKirk McKusick 		maxid = 0;
275516ad57bSKirk McKusick 		break;
2766197731dSKirk McKusick 	}
277516ad57bSKirk McKusick 	return (maxid > 0 ? maxid : 0);
2786197731dSKirk McKusick }
2796197731dSKirk McKusick 
2801b3515f3SDag-Erling Smørgrav static int
quota_read32(struct quotafile * qf,struct dqblk * dqb,int id)2811b3515f3SDag-Erling Smørgrav quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
2821b3515f3SDag-Erling Smørgrav {
2831b3515f3SDag-Erling Smørgrav 	struct dqblk32 dqb32;
2841b3515f3SDag-Erling Smørgrav 	off_t off;
2851b3515f3SDag-Erling Smørgrav 
2861b3515f3SDag-Erling Smørgrav 	off = id * sizeof(struct dqblk32);
2871b3515f3SDag-Erling Smørgrav 	if (lseek(qf->fd, off, SEEK_SET) == -1)
2881b3515f3SDag-Erling Smørgrav 		return (-1);
2891b3515f3SDag-Erling Smørgrav 	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
2901b3515f3SDag-Erling Smørgrav 	case 0:
2916197731dSKirk McKusick 		memset(dqb, 0, sizeof(*dqb));
2921b3515f3SDag-Erling Smørgrav 		return (0);
2931b3515f3SDag-Erling Smørgrav 	case sizeof(dqb32):
2941b3515f3SDag-Erling Smørgrav 		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
2951b3515f3SDag-Erling Smørgrav 		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
2961b3515f3SDag-Erling Smørgrav 		dqb->dqb_curblocks = dqb32.dqb_curblocks;
2971b3515f3SDag-Erling Smørgrav 		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
2981b3515f3SDag-Erling Smørgrav 		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
2991b3515f3SDag-Erling Smørgrav 		dqb->dqb_curinodes = dqb32.dqb_curinodes;
3001b3515f3SDag-Erling Smørgrav 		dqb->dqb_btime = dqb32.dqb_btime;
3011b3515f3SDag-Erling Smørgrav 		dqb->dqb_itime = dqb32.dqb_itime;
3021b3515f3SDag-Erling Smørgrav 		return (0);
3031b3515f3SDag-Erling Smørgrav 	default:
3041b3515f3SDag-Erling Smørgrav 		return (-1);
3051b3515f3SDag-Erling Smørgrav 	}
3061b3515f3SDag-Erling Smørgrav }
3071b3515f3SDag-Erling Smørgrav 
3081b3515f3SDag-Erling Smørgrav static int
quota_read64(struct quotafile * qf,struct dqblk * dqb,int id)3091b3515f3SDag-Erling Smørgrav quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
3101b3515f3SDag-Erling Smørgrav {
3111b3515f3SDag-Erling Smørgrav 	struct dqblk64 dqb64;
3121b3515f3SDag-Erling Smørgrav 	off_t off;
3131b3515f3SDag-Erling Smørgrav 
3141b3515f3SDag-Erling Smørgrav 	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
3151b3515f3SDag-Erling Smørgrav 	if (lseek(qf->fd, off, SEEK_SET) == -1)
3161b3515f3SDag-Erling Smørgrav 		return (-1);
3171b3515f3SDag-Erling Smørgrav 	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
3181b3515f3SDag-Erling Smørgrav 	case 0:
3196197731dSKirk McKusick 		memset(dqb, 0, sizeof(*dqb));
3201b3515f3SDag-Erling Smørgrav 		return (0);
3211b3515f3SDag-Erling Smørgrav 	case sizeof(dqb64):
3221b3515f3SDag-Erling Smørgrav 		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
3231b3515f3SDag-Erling Smørgrav 		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
3241b3515f3SDag-Erling Smørgrav 		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
3251b3515f3SDag-Erling Smørgrav 		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
3261b3515f3SDag-Erling Smørgrav 		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
3271b3515f3SDag-Erling Smørgrav 		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
3281b3515f3SDag-Erling Smørgrav 		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
3291b3515f3SDag-Erling Smørgrav 		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
3301b3515f3SDag-Erling Smørgrav 		return (0);
3311b3515f3SDag-Erling Smørgrav 	default:
3321b3515f3SDag-Erling Smørgrav 		return (-1);
3331b3515f3SDag-Erling Smørgrav 	}
3341b3515f3SDag-Erling Smørgrav }
3351b3515f3SDag-Erling Smørgrav 
3361b3515f3SDag-Erling Smørgrav int
quota_read(struct quotafile * qf,struct dqblk * dqb,int id)3371b3515f3SDag-Erling Smørgrav quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
3381b3515f3SDag-Erling Smørgrav {
3398bd6a3abSKirk McKusick 	int qcmd;
3401b3515f3SDag-Erling Smørgrav 
3418bd6a3abSKirk McKusick 	if (qf->fd == -1) {
3428bd6a3abSKirk McKusick 		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
3438bd6a3abSKirk McKusick 		return (quotactl(qf->fsname, qcmd, id, dqb));
3448bd6a3abSKirk McKusick 	}
3458bd6a3abSKirk McKusick 	switch (qf->wordsize) {
3461b3515f3SDag-Erling Smørgrav 	case 32:
347fdd356a8SDag-Erling Smørgrav 		return (quota_read32(qf, dqb, id));
3481b3515f3SDag-Erling Smørgrav 	case 64:
349fdd356a8SDag-Erling Smørgrav 		return (quota_read64(qf, dqb, id));
3501b3515f3SDag-Erling Smørgrav 	default:
3511b3515f3SDag-Erling Smørgrav 		errno = EINVAL;
3521b3515f3SDag-Erling Smørgrav 		return (-1);
3531b3515f3SDag-Erling Smørgrav 	}
3541b3515f3SDag-Erling Smørgrav 	/* not reached */
3551b3515f3SDag-Erling Smørgrav }
3561b3515f3SDag-Erling Smørgrav 
3571b3515f3SDag-Erling Smørgrav #define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
3581b3515f3SDag-Erling Smørgrav 
3591b3515f3SDag-Erling Smørgrav static int
quota_write32(struct quotafile * qf,const struct dqblk * dqb,int id)3601b3515f3SDag-Erling Smørgrav quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
3611b3515f3SDag-Erling Smørgrav {
3621b3515f3SDag-Erling Smørgrav 	struct dqblk32 dqb32;
3631b3515f3SDag-Erling Smørgrav 	off_t off;
3641b3515f3SDag-Erling Smørgrav 
3651b3515f3SDag-Erling Smørgrav 	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
3661b3515f3SDag-Erling Smørgrav 	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
3671b3515f3SDag-Erling Smørgrav 	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
3681b3515f3SDag-Erling Smørgrav 	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
3691b3515f3SDag-Erling Smørgrav 	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
3701b3515f3SDag-Erling Smørgrav 	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
3711b3515f3SDag-Erling Smørgrav 	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
3721b3515f3SDag-Erling Smørgrav 	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
3731b3515f3SDag-Erling Smørgrav 
3741b3515f3SDag-Erling Smørgrav 	off = id * sizeof(struct dqblk32);
3751b3515f3SDag-Erling Smørgrav 	if (lseek(qf->fd, off, SEEK_SET) == -1)
3761b3515f3SDag-Erling Smørgrav 		return (-1);
3778bd6a3abSKirk McKusick 	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
3788bd6a3abSKirk McKusick 		return (0);
3798bd6a3abSKirk McKusick 	return (-1);
3801b3515f3SDag-Erling Smørgrav }
3811b3515f3SDag-Erling Smørgrav 
3821b3515f3SDag-Erling Smørgrav static int
quota_write64(struct quotafile * qf,const struct dqblk * dqb,int id)3831b3515f3SDag-Erling Smørgrav quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
3841b3515f3SDag-Erling Smørgrav {
3851b3515f3SDag-Erling Smørgrav 	struct dqblk64 dqb64;
3861b3515f3SDag-Erling Smørgrav 	off_t off;
3871b3515f3SDag-Erling Smørgrav 
3881b3515f3SDag-Erling Smørgrav 	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
3891b3515f3SDag-Erling Smørgrav 	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
3901b3515f3SDag-Erling Smørgrav 	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
3911b3515f3SDag-Erling Smørgrav 	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
3921b3515f3SDag-Erling Smørgrav 	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
3931b3515f3SDag-Erling Smørgrav 	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
3941b3515f3SDag-Erling Smørgrav 	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
3951b3515f3SDag-Erling Smørgrav 	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
3961b3515f3SDag-Erling Smørgrav 
3971b3515f3SDag-Erling Smørgrav 	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
3981b3515f3SDag-Erling Smørgrav 	if (lseek(qf->fd, off, SEEK_SET) == -1)
3991b3515f3SDag-Erling Smørgrav 		return (-1);
4008bd6a3abSKirk McKusick 	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
4018bd6a3abSKirk McKusick 		return (0);
4028bd6a3abSKirk McKusick 	return (-1);
4031b3515f3SDag-Erling Smørgrav }
4041b3515f3SDag-Erling Smørgrav 
4051b3515f3SDag-Erling Smørgrav int
quota_write_usage(struct quotafile * qf,struct dqblk * dqb,int id)4068bd6a3abSKirk McKusick quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
4071b3515f3SDag-Erling Smørgrav {
4088bd6a3abSKirk McKusick 	struct dqblk dqbuf;
4098bd6a3abSKirk McKusick 	int qcmd;
4101b3515f3SDag-Erling Smørgrav 
4118bd6a3abSKirk McKusick 	if (qf->fd == -1) {
4128bd6a3abSKirk McKusick 		qcmd = QCMD(Q_SETUSE, qf->quotatype);
4138bd6a3abSKirk McKusick 		return (quotactl(qf->fsname, qcmd, id, dqb));
4148bd6a3abSKirk McKusick 	}
4158bd6a3abSKirk McKusick 	/*
4168bd6a3abSKirk McKusick 	 * Have to do read-modify-write of quota in file.
4178bd6a3abSKirk McKusick 	 */
418516ad57bSKirk McKusick 	if ((qf->accmode & O_RDWR) != O_RDWR) {
419516ad57bSKirk McKusick 		errno = EBADF;
420516ad57bSKirk McKusick 		return (-1);
421516ad57bSKirk McKusick 	}
4228bd6a3abSKirk McKusick 	if (quota_read(qf, &dqbuf, id) != 0)
4238bd6a3abSKirk McKusick 		return (-1);
4248bd6a3abSKirk McKusick 	/*
4258bd6a3abSKirk McKusick 	 * Reset time limit if have a soft limit and were
4268bd6a3abSKirk McKusick 	 * previously under it, but are now over it.
4278bd6a3abSKirk McKusick 	 */
4288bd6a3abSKirk McKusick 	if (dqbuf.dqb_bsoftlimit && id != 0 &&
4298bd6a3abSKirk McKusick 	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
4308bd6a3abSKirk McKusick 	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
4318bd6a3abSKirk McKusick 		dqbuf.dqb_btime = 0;
4328bd6a3abSKirk McKusick 	if (dqbuf.dqb_isoftlimit && id != 0 &&
4338bd6a3abSKirk McKusick 	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
4348bd6a3abSKirk McKusick 	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
4358bd6a3abSKirk McKusick 		dqbuf.dqb_itime = 0;
4368bd6a3abSKirk McKusick 	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
4378bd6a3abSKirk McKusick 	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
4388bd6a3abSKirk McKusick 	/*
4398bd6a3abSKirk McKusick 	 * Write it back.
4408bd6a3abSKirk McKusick 	 */
4418bd6a3abSKirk McKusick 	switch (qf->wordsize) {
4428bd6a3abSKirk McKusick 	case 32:
443fdd356a8SDag-Erling Smørgrav 		return (quota_write32(qf, &dqbuf, id));
4448bd6a3abSKirk McKusick 	case 64:
445fdd356a8SDag-Erling Smørgrav 		return (quota_write64(qf, &dqbuf, id));
4468bd6a3abSKirk McKusick 	default:
4478bd6a3abSKirk McKusick 		errno = EINVAL;
4488bd6a3abSKirk McKusick 		return (-1);
4498bd6a3abSKirk McKusick 	}
4508bd6a3abSKirk McKusick 	/* not reached */
4518bd6a3abSKirk McKusick }
4528bd6a3abSKirk McKusick 
4538bd6a3abSKirk McKusick int
quota_write_limits(struct quotafile * qf,struct dqblk * dqb,int id)4548bd6a3abSKirk McKusick quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
4558bd6a3abSKirk McKusick {
4568bd6a3abSKirk McKusick 	struct dqblk dqbuf;
4578bd6a3abSKirk McKusick 	int qcmd;
4588bd6a3abSKirk McKusick 
4598bd6a3abSKirk McKusick 	if (qf->fd == -1) {
4608bd6a3abSKirk McKusick 		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
4618bd6a3abSKirk McKusick 		return (quotactl(qf->fsname, qcmd, id, dqb));
4628bd6a3abSKirk McKusick 	}
4638bd6a3abSKirk McKusick 	/*
4648bd6a3abSKirk McKusick 	 * Have to do read-modify-write of quota in file.
4658bd6a3abSKirk McKusick 	 */
466516ad57bSKirk McKusick 	if ((qf->accmode & O_RDWR) != O_RDWR) {
467516ad57bSKirk McKusick 		errno = EBADF;
468516ad57bSKirk McKusick 		return (-1);
469516ad57bSKirk McKusick 	}
4708bd6a3abSKirk McKusick 	if (quota_read(qf, &dqbuf, id) != 0)
4718bd6a3abSKirk McKusick 		return (-1);
4728bd6a3abSKirk McKusick 	/*
4738bd6a3abSKirk McKusick 	 * Reset time limit if have a soft limit and were
4748bd6a3abSKirk McKusick 	 * previously under it, but are now over it
4758bd6a3abSKirk McKusick 	 * or if there previously was no soft limit, but
4768bd6a3abSKirk McKusick 	 * now have one and are over it.
4778bd6a3abSKirk McKusick 	 */
4788bd6a3abSKirk McKusick 	if (dqbuf.dqb_bsoftlimit && id != 0 &&
4798bd6a3abSKirk McKusick 	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
4808bd6a3abSKirk McKusick 	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
4818bd6a3abSKirk McKusick 		dqb->dqb_btime = 0;
4828bd6a3abSKirk McKusick 	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
4838bd6a3abSKirk McKusick 	    dqb->dqb_bsoftlimit > 0 &&
4848bd6a3abSKirk McKusick 	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
4858bd6a3abSKirk McKusick 		dqb->dqb_btime = 0;
4868bd6a3abSKirk McKusick 	if (dqbuf.dqb_isoftlimit && id != 0 &&
4878bd6a3abSKirk McKusick 	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
4888bd6a3abSKirk McKusick 	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
4898bd6a3abSKirk McKusick 		dqb->dqb_itime = 0;
4908bd6a3abSKirk McKusick 	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
4918bd6a3abSKirk McKusick 	    dqb->dqb_isoftlimit > 0 &&
4928bd6a3abSKirk McKusick 	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
4938bd6a3abSKirk McKusick 		dqb->dqb_itime = 0;
4948bd6a3abSKirk McKusick 	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
4958bd6a3abSKirk McKusick 	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
4968bd6a3abSKirk McKusick 	/*
4978bd6a3abSKirk McKusick 	 * Write it back.
4988bd6a3abSKirk McKusick 	 */
4998bd6a3abSKirk McKusick 	switch (qf->wordsize) {
5001b3515f3SDag-Erling Smørgrav 	case 32:
501fdd356a8SDag-Erling Smørgrav 		return (quota_write32(qf, dqb, id));
5021b3515f3SDag-Erling Smørgrav 	case 64:
503fdd356a8SDag-Erling Smørgrav 		return (quota_write64(qf, dqb, id));
5041b3515f3SDag-Erling Smørgrav 	default:
5051b3515f3SDag-Erling Smørgrav 		errno = EINVAL;
5061b3515f3SDag-Erling Smørgrav 		return (-1);
5071b3515f3SDag-Erling Smørgrav 	}
5081b3515f3SDag-Erling Smørgrav 	/* not reached */
5091b3515f3SDag-Erling Smørgrav }
510aee785baSKirk McKusick 
511aee785baSKirk McKusick /*
512aee785baSKirk McKusick  * Convert a quota file from one format to another.
513aee785baSKirk McKusick  */
514aee785baSKirk McKusick int
quota_convert(struct quotafile * qf,int wordsize)515aee785baSKirk McKusick quota_convert(struct quotafile *qf, int wordsize)
516aee785baSKirk McKusick {
517aee785baSKirk McKusick 	struct quotafile *newqf;
518aee785baSKirk McKusick 	struct dqhdr64 dqh;
519aee785baSKirk McKusick 	struct dqblk dqblk;
520aee785baSKirk McKusick 	struct group *grp;
521aee785baSKirk McKusick 	int serrno, maxid, id, fd;
522aee785baSKirk McKusick 
523aee785baSKirk McKusick 	/*
524aee785baSKirk McKusick 	 * Quotas must not be active and quotafile must be open
525aee785baSKirk McKusick 	 * for reading and writing.
526aee785baSKirk McKusick 	 */
527aee785baSKirk McKusick 	if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
528aee785baSKirk McKusick 		errno = EBADF;
529aee785baSKirk McKusick 		return (-1);
530aee785baSKirk McKusick 	}
531aee785baSKirk McKusick 	if ((wordsize != 32 && wordsize != 64) ||
532aee785baSKirk McKusick 	     wordsize == qf->wordsize) {
533aee785baSKirk McKusick 		errno = EINVAL;
534aee785baSKirk McKusick 		return (-1);
535aee785baSKirk McKusick 	}
536aee785baSKirk McKusick 	maxid = quota_maxid(qf);
537aee785baSKirk McKusick 	if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
538aee785baSKirk McKusick 		errno = ENOMEM;
539aee785baSKirk McKusick 		return (-1);
540aee785baSKirk McKusick 	}
541aee785baSKirk McKusick 	*newqf = *qf;
542aee785baSKirk McKusick 	snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
543aee785baSKirk McKusick 	    qf->wordsize);
544aee785baSKirk McKusick 	if (rename(qf->qfname, newqf->qfname) < 0) {
545aee785baSKirk McKusick 		free(newqf);
546aee785baSKirk McKusick 		return (-1);
547aee785baSKirk McKusick 	}
548d1d4d952SJilles Tjoelker 	if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,
549d1d4d952SJilles Tjoelker 	    0)) < 0) {
550aee785baSKirk McKusick 		serrno = errno;
551aee785baSKirk McKusick 		goto error;
552aee785baSKirk McKusick 	}
553aee785baSKirk McKusick 	newqf->wordsize = wordsize;
554aee785baSKirk McKusick 	if (wordsize == 64) {
555aee785baSKirk McKusick 		memset(&dqh, 0, sizeof(dqh));
556aee785baSKirk McKusick 		memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
557aee785baSKirk McKusick 		dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
558aee785baSKirk McKusick 		dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
559aee785baSKirk McKusick 		dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
560aee785baSKirk McKusick 		if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
561aee785baSKirk McKusick 			serrno = errno;
562aee785baSKirk McKusick 			goto error;
563aee785baSKirk McKusick 		}
564aee785baSKirk McKusick 	}
565aee785baSKirk McKusick 	grp = getgrnam(QUOTAGROUP);
566aee785baSKirk McKusick 	fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
567aee785baSKirk McKusick 	fchmod(newqf->fd, 0640);
568aee785baSKirk McKusick 	for (id = 0; id <= maxid; id++) {
569aee785baSKirk McKusick 		if ((quota_read(qf, &dqblk, id)) < 0)
570aee785baSKirk McKusick 			break;
571aee785baSKirk McKusick 		switch (newqf->wordsize) {
572aee785baSKirk McKusick 		case 32:
573aee785baSKirk McKusick 			if ((quota_write32(newqf, &dqblk, id)) < 0)
574aee785baSKirk McKusick 				break;
575aee785baSKirk McKusick 			continue;
576aee785baSKirk McKusick 		case 64:
577aee785baSKirk McKusick 			if ((quota_write64(newqf, &dqblk, id)) < 0)
578aee785baSKirk McKusick 				break;
579aee785baSKirk McKusick 			continue;
580aee785baSKirk McKusick 		default:
581aee785baSKirk McKusick 			errno = EINVAL;
582aee785baSKirk McKusick 			break;
583aee785baSKirk McKusick 		}
584aee785baSKirk McKusick 	}
585aee785baSKirk McKusick 	if (id < maxid) {
586aee785baSKirk McKusick 		serrno = errno;
587aee785baSKirk McKusick 		goto error;
588aee785baSKirk McKusick 	}
589aee785baSKirk McKusick 	/*
590aee785baSKirk McKusick 	 * Update the passed in quotafile to reference the new file
591aee785baSKirk McKusick 	 * of the converted format size.
592aee785baSKirk McKusick 	 */
593aee785baSKirk McKusick 	fd = qf->fd;
594aee785baSKirk McKusick 	qf->fd = newqf->fd;
595aee785baSKirk McKusick 	newqf->fd = fd;
596aee785baSKirk McKusick 	qf->wordsize = newqf->wordsize;
597aee785baSKirk McKusick 	quota_close(newqf);
598aee785baSKirk McKusick 	return (0);
599aee785baSKirk McKusick error:
600aee785baSKirk McKusick 	/* put back the original file */
601aee785baSKirk McKusick 	(void) rename(newqf->qfname, qf->qfname);
602aee785baSKirk McKusick 	quota_close(newqf);
603aee785baSKirk McKusick 	errno = serrno;
604aee785baSKirk McKusick 	return (-1);
605aee785baSKirk McKusick }
606