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, ",")) {
87229403Sed		if ((cp = strchr(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;
140255007Sjilles	if ((qf->fd = open(qf->qfname, qf->accmode|O_CLOEXEC)) < 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 */
171255007Sjilles	if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0)) <
172255007Sjilles	    0)
173197532Sdes		goto error;
174188604Smckusick	qf->wordsize = 64;
175187914Sdes	memset(&dqh, 0, sizeof(dqh));
176187914Sdes	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
177187914Sdes	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
178187914Sdes	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
179187914Sdes	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
180201037Smckusick	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
181201037Smckusick		/* it was one we created ourselves */
182201037Smckusick		unlink(qf->qfname);
183197532Sdes		goto error;
184201037Smckusick	}
185187914Sdes	grp = getgrnam(QUOTAGROUP);
186187914Sdes	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
187187914Sdes	fchmod(qf->fd, 0640);
188187914Sdes	return (qf);
189197532Sdeserror:
190197532Sdes	serrno = errno;
191197532Sdes	/* did we have an open file? */
192201037Smckusick	if (qf->fd != -1)
193197532Sdes		close(qf->fd);
194197532Sdes	free(qf);
195197532Sdes	errno = serrno;
196197532Sdes	return (NULL);
197187914Sdes}
198187914Sdes
199187914Sdesvoid
200187914Sdesquota_close(struct quotafile *qf)
201187914Sdes{
202187914Sdes
203188604Smckusick	if (qf->fd != -1)
204188604Smckusick		close(qf->fd);
205187914Sdes	free(qf);
206187914Sdes}
207187914Sdes
208199328Smckusickint
209199328Smckusickquota_on(struct quotafile *qf)
210199328Smckusick{
211199328Smckusick	int qcmd;
212199328Smckusick
213199328Smckusick	qcmd = QCMD(Q_QUOTAON, qf->quotatype);
214199328Smckusick	return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
215199328Smckusick}
216199328Smckusick
217199328Smckusickint
218199328Smckusickquota_off(struct quotafile *qf)
219199328Smckusick{
220199328Smckusick
221199328Smckusick	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
222199328Smckusick}
223199328Smckusick
224197532Sdesconst char *
225197532Sdesquota_fsname(const struct quotafile *qf)
226197532Sdes{
227197532Sdes
228197532Sdes	return (qf->fsname);
229197532Sdes}
230197532Sdes
231197532Sdesconst char *
232197532Sdesquota_qfname(const struct quotafile *qf)
233197532Sdes{
234197532Sdes
235197532Sdes	return (qf->qfname);
236197532Sdes}
237197532Sdes
238197532Sdesint
239197532Sdesquota_check_path(const struct quotafile *qf, const char *path)
240197532Sdes{
241197532Sdes	struct stat st;
242197532Sdes
243197532Sdes	if (stat(path, &st) == -1)
244197532Sdes		return (-1);
245197532Sdes	return (st.st_dev == qf->dev);
246197532Sdes}
247197532Sdes
248198265Smckusickint
249198265Smckusickquota_maxid(struct quotafile *qf)
250198265Smckusick{
251198265Smckusick	struct stat st;
252205207Smckusick	int maxid;
253198265Smckusick
254198265Smckusick	if (stat(qf->qfname, &st) < 0)
255198265Smckusick		return (0);
256198265Smckusick	switch (qf->wordsize) {
257198265Smckusick	case 32:
258205207Smckusick		maxid = st.st_size / sizeof(struct dqblk32) - 1;
259205207Smckusick		break;
260198265Smckusick	case 64:
261205207Smckusick		maxid = st.st_size / sizeof(struct dqblk64) - 2;
262205207Smckusick		break;
263198265Smckusick	default:
264205207Smckusick		maxid = 0;
265205207Smckusick		break;
266198265Smckusick	}
267205207Smckusick	return (maxid > 0 ? maxid : 0);
268198265Smckusick}
269198265Smckusick
270187914Sdesstatic int
271187914Sdesquota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
272187914Sdes{
273187914Sdes	struct dqblk32 dqb32;
274187914Sdes	off_t off;
275187914Sdes
276187914Sdes	off = id * sizeof(struct dqblk32);
277187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
278187914Sdes		return (-1);
279187914Sdes	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
280187914Sdes	case 0:
281198265Smckusick		memset(dqb, 0, sizeof(*dqb));
282187914Sdes		return (0);
283187914Sdes	case sizeof(dqb32):
284187914Sdes		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
285187914Sdes		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
286187914Sdes		dqb->dqb_curblocks = dqb32.dqb_curblocks;
287187914Sdes		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
288187914Sdes		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
289187914Sdes		dqb->dqb_curinodes = dqb32.dqb_curinodes;
290187914Sdes		dqb->dqb_btime = dqb32.dqb_btime;
291187914Sdes		dqb->dqb_itime = dqb32.dqb_itime;
292187914Sdes		return (0);
293187914Sdes	default:
294187914Sdes		return (-1);
295187914Sdes	}
296187914Sdes}
297187914Sdes
298187914Sdesstatic int
299187914Sdesquota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
300187914Sdes{
301187914Sdes	struct dqblk64 dqb64;
302187914Sdes	off_t off;
303187914Sdes
304187914Sdes	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
305187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
306187914Sdes		return (-1);
307187914Sdes	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
308187914Sdes	case 0:
309198265Smckusick		memset(dqb, 0, sizeof(*dqb));
310187914Sdes		return (0);
311187914Sdes	case sizeof(dqb64):
312187914Sdes		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
313187914Sdes		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
314187914Sdes		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
315187914Sdes		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
316187914Sdes		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
317187914Sdes		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
318187914Sdes		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
319187914Sdes		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
320187914Sdes		return (0);
321187914Sdes	default:
322187914Sdes		return (-1);
323187914Sdes	}
324187914Sdes}
325187914Sdes
326187914Sdesint
327187914Sdesquota_read(struct quotafile *qf, struct dqblk *dqb, int id)
328187914Sdes{
329188604Smckusick	int qcmd;
330187914Sdes
331188604Smckusick	if (qf->fd == -1) {
332188604Smckusick		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
333188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
334188604Smckusick	}
335188604Smckusick	switch (qf->wordsize) {
336187914Sdes	case 32:
337197510Sdes		return (quota_read32(qf, dqb, id));
338187914Sdes	case 64:
339197510Sdes		return (quota_read64(qf, dqb, id));
340187914Sdes	default:
341187914Sdes		errno = EINVAL;
342187914Sdes		return (-1);
343187914Sdes	}
344187914Sdes	/* not reached */
345187914Sdes}
346187914Sdes
347187914Sdes#define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
348187914Sdes
349187914Sdesstatic int
350187914Sdesquota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
351187914Sdes{
352187914Sdes	struct dqblk32 dqb32;
353187914Sdes	off_t off;
354187914Sdes
355187914Sdes	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
356187914Sdes	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
357187914Sdes	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
358187914Sdes	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
359187914Sdes	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
360187914Sdes	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
361187914Sdes	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
362187914Sdes	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
363187914Sdes
364187914Sdes	off = id * sizeof(struct dqblk32);
365187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
366187914Sdes		return (-1);
367188604Smckusick	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
368188604Smckusick		return (0);
369188604Smckusick	return (-1);
370187914Sdes}
371187914Sdes
372187914Sdesstatic int
373187914Sdesquota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
374187914Sdes{
375187914Sdes	struct dqblk64 dqb64;
376187914Sdes	off_t off;
377187914Sdes
378187914Sdes	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
379187914Sdes	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
380187914Sdes	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
381187914Sdes	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
382187914Sdes	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
383187914Sdes	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
384187914Sdes	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
385187914Sdes	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
386187914Sdes
387187914Sdes	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
388187914Sdes	if (lseek(qf->fd, off, SEEK_SET) == -1)
389187914Sdes		return (-1);
390188604Smckusick	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
391188604Smckusick		return (0);
392188604Smckusick	return (-1);
393187914Sdes}
394187914Sdes
395187914Sdesint
396188604Smckusickquota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
397187914Sdes{
398188604Smckusick	struct dqblk dqbuf;
399188604Smckusick	int qcmd;
400187914Sdes
401188604Smckusick	if (qf->fd == -1) {
402188604Smckusick		qcmd = QCMD(Q_SETUSE, qf->quotatype);
403188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
404188604Smckusick	}
405188604Smckusick	/*
406188604Smckusick	 * Have to do read-modify-write of quota in file.
407188604Smckusick	 */
408205207Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR) {
409205207Smckusick		errno = EBADF;
410205207Smckusick		return (-1);
411205207Smckusick	}
412188604Smckusick	if (quota_read(qf, &dqbuf, id) != 0)
413188604Smckusick		return (-1);
414188604Smckusick	/*
415188604Smckusick	 * Reset time limit if have a soft limit and were
416188604Smckusick	 * previously under it, but are now over it.
417188604Smckusick	 */
418188604Smckusick	if (dqbuf.dqb_bsoftlimit && id != 0 &&
419188604Smckusick	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
420188604Smckusick	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
421188604Smckusick		dqbuf.dqb_btime = 0;
422188604Smckusick	if (dqbuf.dqb_isoftlimit && id != 0 &&
423188604Smckusick	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
424188604Smckusick	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
425188604Smckusick		dqbuf.dqb_itime = 0;
426188604Smckusick	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
427188604Smckusick	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
428188604Smckusick	/*
429188604Smckusick	 * Write it back.
430188604Smckusick	 */
431188604Smckusick	switch (qf->wordsize) {
432187914Sdes	case 32:
433197510Sdes		return (quota_write32(qf, &dqbuf, id));
434187914Sdes	case 64:
435197510Sdes		return (quota_write64(qf, &dqbuf, id));
436187914Sdes	default:
437187914Sdes		errno = EINVAL;
438187914Sdes		return (-1);
439187914Sdes	}
440187914Sdes	/* not reached */
441187914Sdes}
442188568Smckusick
443188568Smckusickint
444188604Smckusickquota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
445188568Smckusick{
446188604Smckusick	struct dqblk dqbuf;
447188604Smckusick	int qcmd;
448188568Smckusick
449188604Smckusick	if (qf->fd == -1) {
450188604Smckusick		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
451188604Smckusick		return (quotactl(qf->fsname, qcmd, id, dqb));
452188568Smckusick	}
453188568Smckusick	/*
454188604Smckusick	 * Have to do read-modify-write of quota in file.
455188568Smckusick	 */
456205207Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR) {
457205207Smckusick		errno = EBADF;
458205207Smckusick		return (-1);
459205207Smckusick	}
460188604Smckusick	if (quota_read(qf, &dqbuf, id) != 0)
461188604Smckusick		return (-1);
462188604Smckusick	/*
463188604Smckusick	 * Reset time limit if have a soft limit and were
464188604Smckusick	 * previously under it, but are now over it
465188604Smckusick	 * or if there previously was no soft limit, but
466188604Smckusick	 * now have one and are over it.
467188604Smckusick	 */
468188604Smckusick	if (dqbuf.dqb_bsoftlimit && id != 0 &&
469188604Smckusick	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
470188604Smckusick	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
471188604Smckusick		dqb->dqb_btime = 0;
472188604Smckusick	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
473188604Smckusick	    dqb->dqb_bsoftlimit > 0 &&
474188604Smckusick	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
475188604Smckusick		dqb->dqb_btime = 0;
476188604Smckusick	if (dqbuf.dqb_isoftlimit && id != 0 &&
477188604Smckusick	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
478188604Smckusick	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
479188604Smckusick		dqb->dqb_itime = 0;
480188604Smckusick	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
481188604Smckusick	    dqb->dqb_isoftlimit > 0 &&
482188604Smckusick	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
483188604Smckusick		dqb->dqb_itime = 0;
484188604Smckusick	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
485188604Smckusick	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
486188604Smckusick	/*
487188604Smckusick	 * Write it back.
488188604Smckusick	 */
489188604Smckusick	switch (qf->wordsize) {
490188604Smckusick	case 32:
491197510Sdes		return (quota_write32(qf, dqb, id));
492188604Smckusick	case 64:
493197510Sdes		return (quota_write64(qf, dqb, id));
494188604Smckusick	default:
495188604Smckusick		errno = EINVAL;
496188604Smckusick		return (-1);
497188568Smckusick	}
498188604Smckusick	/* not reached */
499188568Smckusick}
500201144Smckusick
501201144Smckusick/*
502201144Smckusick * Convert a quota file from one format to another.
503201144Smckusick */
504201144Smckusickint
505201144Smckusickquota_convert(struct quotafile *qf, int wordsize)
506201144Smckusick{
507201144Smckusick	struct quotafile *newqf;
508201144Smckusick	struct dqhdr64 dqh;
509201144Smckusick	struct dqblk dqblk;
510201144Smckusick	struct group *grp;
511201144Smckusick	int serrno, maxid, id, fd;
512201144Smckusick
513201144Smckusick	/*
514201144Smckusick	 * Quotas must not be active and quotafile must be open
515201144Smckusick	 * for reading and writing.
516201144Smckusick	 */
517201144Smckusick	if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
518201144Smckusick		errno = EBADF;
519201144Smckusick		return (-1);
520201144Smckusick	}
521201144Smckusick	if ((wordsize != 32 && wordsize != 64) ||
522201144Smckusick	     wordsize == qf->wordsize) {
523201144Smckusick		errno = EINVAL;
524201144Smckusick		return (-1);
525201144Smckusick	}
526201144Smckusick	maxid = quota_maxid(qf);
527201144Smckusick	if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
528201144Smckusick		errno = ENOMEM;
529201144Smckusick		return (-1);
530201144Smckusick	}
531201144Smckusick	*newqf = *qf;
532201144Smckusick	snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
533201144Smckusick	    qf->wordsize);
534201144Smckusick	if (rename(qf->qfname, newqf->qfname) < 0) {
535201144Smckusick		free(newqf);
536201144Smckusick		return (-1);
537201144Smckusick	}
538255007Sjilles	if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,
539255007Sjilles	    0)) < 0) {
540201144Smckusick		serrno = errno;
541201144Smckusick		goto error;
542201144Smckusick	}
543201144Smckusick	newqf->wordsize = wordsize;
544201144Smckusick	if (wordsize == 64) {
545201144Smckusick		memset(&dqh, 0, sizeof(dqh));
546201144Smckusick		memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
547201144Smckusick		dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
548201144Smckusick		dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
549201144Smckusick		dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
550201144Smckusick		if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
551201144Smckusick			serrno = errno;
552201144Smckusick			goto error;
553201144Smckusick		}
554201144Smckusick	}
555201144Smckusick	grp = getgrnam(QUOTAGROUP);
556201144Smckusick	fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
557201144Smckusick	fchmod(newqf->fd, 0640);
558201144Smckusick	for (id = 0; id <= maxid; id++) {
559201144Smckusick		if ((quota_read(qf, &dqblk, id)) < 0)
560201144Smckusick			break;
561201144Smckusick		switch (newqf->wordsize) {
562201144Smckusick		case 32:
563201144Smckusick			if ((quota_write32(newqf, &dqblk, id)) < 0)
564201144Smckusick				break;
565201144Smckusick			continue;
566201144Smckusick		case 64:
567201144Smckusick			if ((quota_write64(newqf, &dqblk, id)) < 0)
568201144Smckusick				break;
569201144Smckusick			continue;
570201144Smckusick		default:
571201144Smckusick			errno = EINVAL;
572201144Smckusick			break;
573201144Smckusick		}
574201144Smckusick	}
575201144Smckusick	if (id < maxid) {
576201144Smckusick		serrno = errno;
577201144Smckusick		goto error;
578201144Smckusick	}
579201144Smckusick	/*
580201144Smckusick	 * Update the passed in quotafile to reference the new file
581201144Smckusick	 * of the converted format size.
582201144Smckusick	 */
583201144Smckusick	fd = qf->fd;
584201144Smckusick	qf->fd = newqf->fd;
585201144Smckusick	newqf->fd = fd;
586201144Smckusick	qf->wordsize = newqf->wordsize;
587201144Smckusick	quota_close(newqf);
588201144Smckusick	return (0);
589201144Smckusickerror:
590201144Smckusick	/* put back the original file */
591201144Smckusick	(void) rename(newqf->qfname, qf->qfname);
592201144Smckusick	quota_close(newqf);
593201144Smckusick	errno = serrno;
594201144Smckusick	return (-1);
595201144Smckusick}
596