1187914Sdes/*-
2187914Sdes * Copyright (c) 2008 Dag-Erling Co��dan Sm��rgrav
3188604Smckusick * Copyright (c) 2008 Marshall Kirk McKusick
4187914Sdes * All rights reserved.
5187914Sdes *
6187914Sdes * Redistribution and use in source and binary forms, with or without
7187914Sdes * modification, are permitted provided that the following conditions
8187914Sdes * are met:
9187914Sdes * 1. Redistributions of source code must retain the above copyright
10187914Sdes *    notice, this list of conditions and the following disclaimer
11187914Sdes *    in this position and unchanged.
12187914Sdes * 2. Redistributions in binary form must reproduce the above copyright
13187914Sdes *    notice, this list of conditions and the following disclaimer in the
14187914Sdes *    documentation and/or other materials provided with the distribution.
15187914Sdes *
16187914Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17187914Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18187914Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19187914Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20187914Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21187914Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22187914Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23187914Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24187914Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25187914Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26187914Sdes * SUCH DAMAGE.
27187914Sdes *
28187914Sdes * $FreeBSD$
29187914Sdes */
30187914Sdes
31187914Sdes#include <sys/types.h>
32187914Sdes#include <sys/endian.h>
33188568Smckusick#include <sys/mount.h>
34187914Sdes#include <sys/stat.h>
35187914Sdes
36187914Sdes#include <ufs/ufs/quota.h>
37187914Sdes
38187914Sdes#include <errno.h>
39187914Sdes#include <fcntl.h>
40188568Smckusick#include <fstab.h>
41187914Sdes#include <grp.h>
42187914Sdes#include <pwd.h>
43187914Sdes#include <libutil.h>
44187914Sdes#include <stdint.h>
45188568Smckusick#include <stdio.h>
46187914Sdes#include <stdlib.h>
47187914Sdes#include <string.h>
48187914Sdes#include <unistd.h>
49187914Sdes
50187914Sdesstruct quotafile {
51188604Smckusick	int fd;				/* -1 means using quotactl for access */
52197532Sdes	int accmode;			/* access mode */
53188604Smckusick	int wordsize;			/* 32-bit or 64-bit limits */
54188604Smckusick	int quotatype;			/* USRQUOTA or GRPQUOTA */
55197532Sdes	dev_t dev;			/* device */
56188604Smckusick	char fsname[MAXPATHLEN + 1];	/* mount point of filesystem */
57188604Smckusick	char qfname[MAXPATHLEN + 1];	/* quota file if not using quotactl */
58187914Sdes};
59187914Sdes
60188568Smckusickstatic const char *qfextension[] = INITQFNAMES;
61188568Smckusick
62188604Smckusick/*
63188604Smckusick * Check to see if a particular quota is to be enabled.
64188604Smckusick */
65188604Smckusickstatic int
66188604Smckusickhasquota(struct fstab *fs, int type, char *qfnamep, int qfbufsize)
67188604Smckusick{
68188604Smckusick	char *opt;
69188604Smckusick	char *cp;
70188604Smckusick	struct statfs sfb;
71188604Smckusick	char buf[BUFSIZ];
72188604Smckusick	static char initname, usrname[100], grpname[100];
73188604Smckusick
74197532Sdes	/*
75197532Sdes	 * 1) we only need one of these
76197532Sdes	 * 2) fstab may specify a different filename
77197532Sdes	 */
78188604Smckusick	if (!initname) {
79188604Smckusick		(void)snprintf(usrname, sizeof(usrname), "%s%s",
80188604Smckusick		    qfextension[USRQUOTA], QUOTAFILENAME);
81188604Smckusick		(void)snprintf(grpname, sizeof(grpname), "%s%s",
82188604Smckusick		    qfextension[GRPQUOTA], QUOTAFILENAME);
83188604Smckusick		initname = 1;
84188604Smckusick	}
85188604Smckusick	strcpy(buf, fs->fs_mntops);
86188604Smckusick	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
87188604Smckusick		if ((cp = index(opt, '=')))
88188604Smckusick			*cp++ = '\0';
89188604Smckusick		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
90188604Smckusick			break;
91188604Smckusick		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
92188604Smckusick			break;
93188604Smckusick	}
94188604Smckusick	if (!opt)
95188604Smckusick		return (0);
96188604Smckusick	/*
97188604Smckusick	 * Ensure that the filesystem is mounted.
98188604Smckusick	 */
99188604Smckusick	if (statfs(fs->fs_file, &sfb) != 0 ||
100188604Smckusick	    strcmp(fs->fs_file, sfb.f_mntonname)) {
101188604Smckusick		return (0);
102188604Smckusick	}
103188604Smckusick	if (cp) {
104188604Smckusick		strncpy(qfnamep, cp, qfbufsize);
105188604Smckusick	} else {
106188604Smckusick		(void)snprintf(qfnamep, qfbufsize, "%s/%s.%s", fs->fs_file,
107188604Smckusick		    QUOTAFILENAME, qfextension[type]);
108188604Smckusick	}
109188604Smckusick	return (1);
110188604Smckusick}
111188604Smckusick
112187914Sdesstruct quotafile *
113188604Smckusickquota_open(struct fstab *fs, int quotatype, int openflags)
114187914Sdes{
115187914Sdes	struct quotafile *qf;
116187914Sdes	struct dqhdr64 dqh;
117188604Smckusick	struct group *grp;
118197532Sdes	struct stat st;
119188604Smckusick	int qcmd, serrno;
120187914Sdes
121201037Smckusick	if (strcmp(fs->fs_vfstype, "ufs"))
122201037Smckusick		return (NULL);
123187914Sdes	if ((qf = calloc(1, sizeof(*qf))) == NULL)
124187914Sdes		return (NULL);
125197532Sdes	qf->fd = -1;
126188604Smckusick	qf->quotatype = quotatype;
127188604Smckusick	strncpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
128197532Sdes	if (stat(qf->fsname, &st) != 0)
129197532Sdes		goto error;
130197532Sdes	qf->dev = st.st_dev;
131198265Smckusick	serrno = hasquota(fs, quotatype, qf->qfname, sizeof(qf->qfname));
132205207Smckusick	qcmd = QCMD(Q_GETQUOTASIZE, quotatype);
133205207Smckusick	if (quotactl(qf->fsname, qcmd, 0, &qf->wordsize) == 0)
134188604Smckusick		return (qf);
135198265Smckusick	if (serrno == 0) {
136188604Smckusick		errno = EOPNOTSUPP;
137197532Sdes		goto error;
138187914Sdes	}
139197532Sdes	qf->accmode = openflags & O_ACCMODE;
140197532Sdes	if ((qf->fd = open(qf->qfname, qf->accmode)) < 0 &&
141197532Sdes	    (openflags & O_CREAT) != O_CREAT)
142197532Sdes		goto error;
143188604Smckusick	/* File open worked, so process it */
144188604Smckusick	if (qf->fd != -1) {
145188604Smckusick		qf->wordsize = 32;
146188604Smckusick		switch (read(qf->fd, &dqh, sizeof(dqh))) {
147188604Smckusick		case -1:
148197532Sdes			goto error;
149188604Smckusick		case sizeof(dqh):
150188604Smckusick			if (strcmp(dqh.dqh_magic, Q_DQHDR64_MAGIC) != 0) {
151188604Smckusick				/* no magic, assume 32 bits */
152188604Smckusick				qf->wordsize = 32;
153188604Smckusick				return (qf);
154188604Smckusick			}
155188604Smckusick			if (be32toh(dqh.dqh_version) != Q_DQHDR64_VERSION ||
156188604Smckusick			    be32toh(dqh.dqh_hdrlen) != sizeof(struct dqhdr64) ||
157188604Smckusick			    be32toh(dqh.dqh_reclen) != sizeof(struct dqblk64)) {
158188604Smckusick				/* correct magic, wrong version / lengths */
159188604Smckusick				errno = EINVAL;
160197532Sdes				goto error;
161188604Smckusick			}
162188604Smckusick			qf->wordsize = 64;
163188604Smckusick			return (qf);
164188604Smckusick		default:
165188604Smckusick			qf->wordsize = 32;
166188604Smckusick			return (qf);
167187914Sdes		}
168188604Smckusick		/* not reached */
169187914Sdes	}
170197532Sdes	/* open failed, but O_CREAT was specified, so create a new file */
171197532Sdes	if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC, 0)) < 0)
172197532Sdes		goto error;
173188604Smckusick	qf->wordsize = 64;
174187914Sdes	memset(&dqh, 0, sizeof(dqh));
175187914Sdes	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
176187914Sdes	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
177187914Sdes	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
178187914Sdes	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
179201037Smckusick	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
180201037Smckusick		/* it was one we created ourselves */
181201037Smckusick		unlink(qf->qfname);
182197532Sdes		goto error;
183201037Smckusick	}
184187914Sdes	grp = getgrnam(QUOTAGROUP);
185187914Sdes	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
186187914Sdes	fchmod(qf->fd, 0640);
187187914Sdes	return (qf);
188197532Sdeserror:
189197532Sdes	serrno = errno;
190197532Sdes	/* did we have an open file? */
191201037Smckusick	if (qf->fd != -1)
192197532Sdes		close(qf->fd);
193197532Sdes	free(qf);
194197532Sdes	errno = serrno;
195197532Sdes	return (NULL);
196187914Sdes}
197187914Sdes
198187914Sdesvoid
199187914Sdesquota_close(struct quotafile *qf)
200187914Sdes{
201187914Sdes
202188604Smckusick	if (qf->fd != -1)
203188604Smckusick		close(qf->fd);
204187914Sdes	free(qf);
205187914Sdes}
206187914Sdes
207199328Smckusickint
208199328Smckusickquota_on(struct quotafile *qf)
209199328Smckusick{
210199328Smckusick	int qcmd;
211199328Smckusick
212199328Smckusick	qcmd = QCMD(Q_QUOTAON, qf->quotatype);
213199328Smckusick	return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
214199328Smckusick}
215199328Smckusick
216199328Smckusickint
217199328Smckusickquota_off(struct quotafile *qf)
218199328Smckusick{
219199328Smckusick
220199328Smckusick	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
221199328Smckusick}
222199328Smckusick
223197532Sdesconst char *
224197532Sdesquota_fsname(const struct quotafile *qf)
225197532Sdes{
226197532Sdes
227197532Sdes	return (qf->fsname);
228197532Sdes}
229197532Sdes
230197532Sdesconst char *
231197532Sdesquota_qfname(const struct quotafile *qf)
232197532Sdes{
233197532Sdes
234197532Sdes	return (qf->qfname);
235197532Sdes}
236197532Sdes
237197532Sdesint
238197532Sdesquota_check_path(const struct quotafile *qf, const char *path)
239197532Sdes{
240197532Sdes	struct stat st;
241197532Sdes
242197532Sdes	if (stat(path, &st) == -1)
243197532Sdes		return (-1);
244197532Sdes	return (st.st_dev == qf->dev);
245197532Sdes}
246197532Sdes
247198265Smckusickint
248198265Smckusickquota_maxid(struct quotafile *qf)
249198265Smckusick{
250198265Smckusick	struct stat st;
251205207Smckusick	int maxid;
252198265Smckusick
253198265Smckusick	if (stat(qf->qfname, &st) < 0)
254198265Smckusick		return (0);
255198265Smckusick	switch (qf->wordsize) {
256198265Smckusick	case 32:
257205207Smckusick		maxid = st.st_size / sizeof(struct dqblk32) - 1;
258205207Smckusick		break;
259198265Smckusick	case 64:
260205207Smckusick		maxid = st.st_size / sizeof(struct dqblk64) - 2;
261205207Smckusick		break;
262198265Smckusick	default:
263205207Smckusick		maxid = 0;
264205207Smckusick		break;
265198265Smckusick	}
266205207Smckusick	return (maxid > 0 ? maxid : 0);
267198265Smckusick}
268198265Smckusick
269187914Sdesstatic int
270187914Sdesquota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
271187914Sdes{
272187914Sdes	struct dqblk32 dqb32;
273187914Sdes	off_t off;
274187914Sdes
275187914Sdes	off = id * sizeof(struct dqblk32);
276187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
277187914Sdes		return (-1);
278187914Sdes	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
279187914Sdes	case 0:
280198265Smckusick		memset(dqb, 0, sizeof(*dqb));
281187914Sdes		return (0);
282187914Sdes	case sizeof(dqb32):
283187914Sdes		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
284187914Sdes		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
285187914Sdes		dqb->dqb_curblocks = dqb32.dqb_curblocks;
286187914Sdes		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
287187914Sdes		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
288187914Sdes		dqb->dqb_curinodes = dqb32.dqb_curinodes;
289187914Sdes		dqb->dqb_btime = dqb32.dqb_btime;
290187914Sdes		dqb->dqb_itime = dqb32.dqb_itime;
291187914Sdes		return (0);
292187914Sdes	default:
293187914Sdes		return (-1);
294187914Sdes	}
295187914Sdes}
296187914Sdes
297187914Sdesstatic int
298187914Sdesquota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
299187914Sdes{
300187914Sdes	struct dqblk64 dqb64;
301187914Sdes	off_t off;
302187914Sdes
303187914Sdes	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
304187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
305187914Sdes		return (-1);
306187914Sdes	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
307187914Sdes	case 0:
308198265Smckusick		memset(dqb, 0, sizeof(*dqb));
309187914Sdes		return (0);
310187914Sdes	case sizeof(dqb64):
311187914Sdes		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
312187914Sdes		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
313187914Sdes		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
314187914Sdes		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
315187914Sdes		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
316187914Sdes		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
317187914Sdes		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
318187914Sdes		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
319187914Sdes		return (0);
320187914Sdes	default:
321187914Sdes		return (-1);
322187914Sdes	}
323187914Sdes}
324187914Sdes
325187914Sdesint
326187914Sdesquota_read(struct quotafile *qf, struct dqblk *dqb, int id)
327187914Sdes{
328188604Smckusick	int qcmd;
329187914Sdes
330188604Smckusick	if (qf->fd == -1) {
331188604Smckusick		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
332188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
333188604Smckusick	}
334188604Smckusick	switch (qf->wordsize) {
335187914Sdes	case 32:
336197510Sdes		return (quota_read32(qf, dqb, id));
337187914Sdes	case 64:
338197510Sdes		return (quota_read64(qf, dqb, id));
339187914Sdes	default:
340187914Sdes		errno = EINVAL;
341187914Sdes		return (-1);
342187914Sdes	}
343187914Sdes	/* not reached */
344187914Sdes}
345187914Sdes
346187914Sdes#define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
347187914Sdes
348187914Sdesstatic int
349187914Sdesquota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
350187914Sdes{
351187914Sdes	struct dqblk32 dqb32;
352187914Sdes	off_t off;
353187914Sdes
354187914Sdes	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
355187914Sdes	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
356187914Sdes	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
357187914Sdes	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
358187914Sdes	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
359187914Sdes	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
360187914Sdes	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
361187914Sdes	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
362187914Sdes
363187914Sdes	off = id * sizeof(struct dqblk32);
364187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
365187914Sdes		return (-1);
366188604Smckusick	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
367188604Smckusick		return (0);
368188604Smckusick	return (-1);
369187914Sdes}
370187914Sdes
371187914Sdesstatic int
372187914Sdesquota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
373187914Sdes{
374187914Sdes	struct dqblk64 dqb64;
375187914Sdes	off_t off;
376187914Sdes
377187914Sdes	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
378187914Sdes	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
379187914Sdes	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
380187914Sdes	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
381187914Sdes	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
382187914Sdes	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
383187914Sdes	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
384187914Sdes	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
385187914Sdes
386187914Sdes	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
387187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
388187914Sdes		return (-1);
389188604Smckusick	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
390188604Smckusick		return (0);
391188604Smckusick	return (-1);
392187914Sdes}
393187914Sdes
394187914Sdesint
395188604Smckusickquota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
396187914Sdes{
397188604Smckusick	struct dqblk dqbuf;
398188604Smckusick	int qcmd;
399187914Sdes
400188604Smckusick	if (qf->fd == -1) {
401188604Smckusick		qcmd = QCMD(Q_SETUSE, qf->quotatype);
402188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
403188604Smckusick	}
404188604Smckusick	/*
405188604Smckusick	 * Have to do read-modify-write of quota in file.
406188604Smckusick	 */
407205207Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR) {
408205207Smckusick		errno = EBADF;
409205207Smckusick		return (-1);
410205207Smckusick	}
411188604Smckusick	if (quota_read(qf, &dqbuf, id) != 0)
412188604Smckusick		return (-1);
413188604Smckusick	/*
414188604Smckusick	 * Reset time limit if have a soft limit and were
415188604Smckusick	 * previously under it, but are now over it.
416188604Smckusick	 */
417188604Smckusick	if (dqbuf.dqb_bsoftlimit && id != 0 &&
418188604Smckusick	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
419188604Smckusick	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
420188604Smckusick		dqbuf.dqb_btime = 0;
421188604Smckusick	if (dqbuf.dqb_isoftlimit && id != 0 &&
422188604Smckusick	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
423188604Smckusick	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
424188604Smckusick		dqbuf.dqb_itime = 0;
425188604Smckusick	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
426188604Smckusick	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
427188604Smckusick	/*
428188604Smckusick	 * Write it back.
429188604Smckusick	 */
430188604Smckusick	switch (qf->wordsize) {
431187914Sdes	case 32:
432197510Sdes		return (quota_write32(qf, &dqbuf, id));
433187914Sdes	case 64:
434197510Sdes		return (quota_write64(qf, &dqbuf, id));
435187914Sdes	default:
436187914Sdes		errno = EINVAL;
437187914Sdes		return (-1);
438187914Sdes	}
439187914Sdes	/* not reached */
440187914Sdes}
441188568Smckusick
442188568Smckusickint
443188604Smckusickquota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
444188568Smckusick{
445188604Smckusick	struct dqblk dqbuf;
446188604Smckusick	int qcmd;
447188568Smckusick
448188604Smckusick	if (qf->fd == -1) {
449188604Smckusick		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
450188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
451188568Smckusick	}
452188568Smckusick	/*
453188604Smckusick	 * Have to do read-modify-write of quota in file.
454188568Smckusick	 */
455205207Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR) {
456205207Smckusick		errno = EBADF;
457205207Smckusick		return (-1);
458205207Smckusick	}
459188604Smckusick	if (quota_read(qf, &dqbuf, id) != 0)
460188604Smckusick		return (-1);
461188604Smckusick	/*
462188604Smckusick	 * Reset time limit if have a soft limit and were
463188604Smckusick	 * previously under it, but are now over it
464188604Smckusick	 * or if there previously was no soft limit, but
465188604Smckusick	 * now have one and are over it.
466188604Smckusick	 */
467188604Smckusick	if (dqbuf.dqb_bsoftlimit && id != 0 &&
468188604Smckusick	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
469188604Smckusick	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
470188604Smckusick		dqb->dqb_btime = 0;
471188604Smckusick	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
472188604Smckusick	    dqb->dqb_bsoftlimit > 0 &&
473188604Smckusick	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
474188604Smckusick		dqb->dqb_btime = 0;
475188604Smckusick	if (dqbuf.dqb_isoftlimit && id != 0 &&
476188604Smckusick	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
477188604Smckusick	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
478188604Smckusick		dqb->dqb_itime = 0;
479188604Smckusick	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
480188604Smckusick	    dqb->dqb_isoftlimit > 0 &&
481188604Smckusick	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
482188604Smckusick		dqb->dqb_itime = 0;
483188604Smckusick	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
484188604Smckusick	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
485188604Smckusick	/*
486188604Smckusick	 * Write it back.
487188604Smckusick	 */
488188604Smckusick	switch (qf->wordsize) {
489188604Smckusick	case 32:
490197510Sdes		return (quota_write32(qf, dqb, id));
491188604Smckusick	case 64:
492197510Sdes		return (quota_write64(qf, dqb, id));
493188604Smckusick	default:
494188604Smckusick		errno = EINVAL;
495188604Smckusick		return (-1);
496188568Smckusick	}
497188604Smckusick	/* not reached */
498188568Smckusick}
499201144Smckusick
500201144Smckusick/*
501201144Smckusick * Convert a quota file from one format to another.
502201144Smckusick */
503201144Smckusickint
504201144Smckusickquota_convert(struct quotafile *qf, int wordsize)
505201144Smckusick{
506201144Smckusick	struct quotafile *newqf;
507201144Smckusick	struct dqhdr64 dqh;
508201144Smckusick	struct dqblk dqblk;
509201144Smckusick	struct group *grp;
510201144Smckusick	int serrno, maxid, id, fd;
511201144Smckusick
512201144Smckusick	/*
513201144Smckusick	 * Quotas must not be active and quotafile must be open
514201144Smckusick	 * for reading and writing.
515201144Smckusick	 */
516201144Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
517201144Smckusick		errno = EBADF;
518201144Smckusick		return (-1);
519201144Smckusick	}
520201144Smckusick	if ((wordsize != 32 && wordsize != 64) ||
521201144Smckusick	     wordsize == qf->wordsize) {
522201144Smckusick		errno = EINVAL;
523201144Smckusick		return (-1);
524201144Smckusick	}
525201144Smckusick	maxid = quota_maxid(qf);
526201144Smckusick	if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
527201144Smckusick		errno = ENOMEM;
528201144Smckusick		return (-1);
529201144Smckusick	}
530201144Smckusick	*newqf = *qf;
531201144Smckusick	snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
532201144Smckusick	    qf->wordsize);
533201144Smckusick	if (rename(qf->qfname, newqf->qfname) < 0) {
534201144Smckusick		free(newqf);
535201144Smckusick		return (-1);
536201144Smckusick	}
537201144Smckusick	if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC, 0)) < 0) {
538201144Smckusick		serrno = errno;
539201144Smckusick		goto error;
540201144Smckusick	}
541201144Smckusick	newqf->wordsize = wordsize;
542201144Smckusick	if (wordsize == 64) {
543201144Smckusick		memset(&dqh, 0, sizeof(dqh));
544201144Smckusick		memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
545201144Smckusick		dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
546201144Smckusick		dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
547201144Smckusick		dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
548201144Smckusick		if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
549201144Smckusick			serrno = errno;
550201144Smckusick			goto error;
551201144Smckusick		}
552201144Smckusick	}
553201144Smckusick	grp = getgrnam(QUOTAGROUP);
554201144Smckusick	fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
555201144Smckusick	fchmod(newqf->fd, 0640);
556201144Smckusick	for (id = 0; id <= maxid; id++) {
557201144Smckusick		if ((quota_read(qf, &dqblk, id)) < 0)
558201144Smckusick			break;
559201144Smckusick		switch (newqf->wordsize) {
560201144Smckusick		case 32:
561201144Smckusick			if ((quota_write32(newqf, &dqblk, id)) < 0)
562201144Smckusick				break;
563201144Smckusick			continue;
564201144Smckusick		case 64:
565201144Smckusick			if ((quota_write64(newqf, &dqblk, id)) < 0)
566201144Smckusick				break;
567201144Smckusick			continue;
568201144Smckusick		default:
569201144Smckusick			errno = EINVAL;
570201144Smckusick			break;
571201144Smckusick		}
572201144Smckusick	}
573201144Smckusick	if (id < maxid) {
574201144Smckusick		serrno = errno;
575201144Smckusick		goto error;
576201144Smckusick	}
577201144Smckusick	/*
578201144Smckusick	 * Update the passed in quotafile to reference the new file
579201144Smckusick	 * of the converted format size.
580201144Smckusick	 */
581201144Smckusick	fd = qf->fd;
582201144Smckusick	qf->fd = newqf->fd;
583201144Smckusick	newqf->fd = fd;
584201144Smckusick	qf->wordsize = newqf->wordsize;
585201144Smckusick	quota_close(newqf);
586201144Smckusick	return (0);
587201144Smckusickerror:
588201144Smckusick	/* put back the original file */
589201144Smckusick	(void) rename(newqf->qfname, qf->qfname);
590201144Smckusick	quota_close(newqf);
591201144Smckusick	errno = serrno;
592201144Smckusick	return (-1);
593201144Smckusick}
594