1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2008 Dag-Erling Sm��rgrav
5 * Copyright (c) 2008 Marshall Kirk McKusick
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer
13 *    in this position and unchanged.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/types.h>
32#include <sys/endian.h>
33#include <sys/mount.h>
34#include <sys/stat.h>
35
36#include <ufs/ufs/quota.h>
37
38#include <errno.h>
39#include <fcntl.h>
40#include <fstab.h>
41#include <grp.h>
42#include <pwd.h>
43#include <libutil.h>
44#include <stdint.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49
50struct quotafile {
51	int fd;				/* -1 means using quotactl for access */
52	int accmode;			/* access mode */
53	int wordsize;			/* 32-bit or 64-bit limits */
54	int quotatype;			/* USRQUOTA or GRPQUOTA */
55	dev_t dev;			/* device */
56	char fsname[MAXPATHLEN + 1];	/* mount point of filesystem */
57	char qfname[MAXPATHLEN + 1];	/* quota file if not using quotactl */
58};
59
60static const char *qfextension[] = INITQFNAMES;
61
62/*
63 * Check to see if a particular quota is to be enabled.
64 */
65static int
66hasquota(struct fstab *fs, int type, char *qfnamep, int qfbufsize)
67{
68	char *opt;
69	char *cp;
70	struct statfs sfb;
71	char buf[BUFSIZ];
72	static char initname, usrname[100], grpname[100];
73
74	/*
75	 * 1) we only need one of these
76	 * 2) fstab may specify a different filename
77	 */
78	if (!initname) {
79		(void)snprintf(usrname, sizeof(usrname), "%s%s",
80		    qfextension[USRQUOTA], QUOTAFILENAME);
81		(void)snprintf(grpname, sizeof(grpname), "%s%s",
82		    qfextension[GRPQUOTA], QUOTAFILENAME);
83		initname = 1;
84	}
85	strcpy(buf, fs->fs_mntops);
86	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
87		if ((cp = strchr(opt, '=')))
88			*cp++ = '\0';
89		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
90			break;
91		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
92			break;
93	}
94	if (!opt)
95		return (0);
96	/*
97	 * Ensure that the filesystem is mounted.
98	 */
99	if (statfs(fs->fs_file, &sfb) != 0 ||
100	    strcmp(fs->fs_file, sfb.f_mntonname)) {
101		return (0);
102	}
103	if (cp) {
104		strlcpy(qfnamep, cp, qfbufsize);
105	} else {
106		(void)snprintf(qfnamep, qfbufsize, "%s/%s.%s", fs->fs_file,
107		    QUOTAFILENAME, qfextension[type]);
108	}
109	return (1);
110}
111
112struct quotafile *
113quota_open(struct fstab *fs, int quotatype, int openflags)
114{
115	struct quotafile *qf;
116	struct dqhdr64 dqh;
117	struct group *grp;
118	struct stat st;
119	int qcmd, serrno = 0;
120	int ufs;
121
122	if ((qf = calloc(1, sizeof(*qf))) == NULL)
123		return (NULL);
124	qf->fd = -1;
125	qf->quotatype = quotatype;
126	strlcpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
127	if (stat(qf->fsname, &st) != 0)
128		goto error;
129	qf->dev = st.st_dev;
130	qcmd = QCMD(Q_GETQUOTASIZE, quotatype);
131	ufs = strcmp(fs->fs_vfstype, "ufs") == 0;
132	/*
133	 * On UFS, hasquota() fills in qf->qfname. But we only care about
134	 * this for UFS.  So we need to call hasquota() for UFS, first.
135	 */
136	if (ufs) {
137		serrno = hasquota(fs, quotatype, qf->qfname,
138		    sizeof(qf->qfname));
139	}
140	if (quotactl(qf->fsname, qcmd, 0, &qf->wordsize) == 0)
141		return (qf);
142	if (!ufs) {
143		errno = 0;
144		goto error;
145	} else if (serrno == 0) {
146		errno = EOPNOTSUPP;
147		goto error;
148	}
149	qf->accmode = openflags & O_ACCMODE;
150	if ((qf->fd = open(qf->qfname, qf->accmode|O_CLOEXEC)) < 0 &&
151	    (openflags & O_CREAT) != O_CREAT)
152		goto error;
153	/* File open worked, so process it */
154	if (qf->fd != -1) {
155		qf->wordsize = 32;
156		switch (read(qf->fd, &dqh, sizeof(dqh))) {
157		case -1:
158			goto error;
159		case sizeof(dqh):
160			if (strcmp(dqh.dqh_magic, Q_DQHDR64_MAGIC) != 0) {
161				/* no magic, assume 32 bits */
162				qf->wordsize = 32;
163				return (qf);
164			}
165			if (be32toh(dqh.dqh_version) != Q_DQHDR64_VERSION ||
166			    be32toh(dqh.dqh_hdrlen) != sizeof(struct dqhdr64) ||
167			    be32toh(dqh.dqh_reclen) != sizeof(struct dqblk64)) {
168				/* correct magic, wrong version / lengths */
169				errno = EINVAL;
170				goto error;
171			}
172			qf->wordsize = 64;
173			return (qf);
174		default:
175			qf->wordsize = 32;
176			return (qf);
177		}
178		/* not reached */
179	}
180	/* open failed, but O_CREAT was specified, so create a new file */
181	if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0)) <
182	    0)
183		goto error;
184	qf->wordsize = 64;
185	memset(&dqh, 0, sizeof(dqh));
186	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
187	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
188	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
189	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
190	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
191		/* it was one we created ourselves */
192		unlink(qf->qfname);
193		goto error;
194	}
195	grp = getgrnam(QUOTAGROUP);
196	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
197	fchmod(qf->fd, 0640);
198	return (qf);
199error:
200	serrno = errno;
201	/* did we have an open file? */
202	if (qf->fd != -1)
203		close(qf->fd);
204	free(qf);
205	errno = serrno;
206	return (NULL);
207}
208
209void
210quota_close(struct quotafile *qf)
211{
212
213	if (qf->fd != -1)
214		close(qf->fd);
215	free(qf);
216}
217
218int
219quota_on(struct quotafile *qf)
220{
221	int qcmd;
222
223	qcmd = QCMD(Q_QUOTAON, qf->quotatype);
224	return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
225}
226
227int
228quota_off(struct quotafile *qf)
229{
230
231	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
232}
233
234const char *
235quota_fsname(const struct quotafile *qf)
236{
237
238	return (qf->fsname);
239}
240
241const char *
242quota_qfname(const struct quotafile *qf)
243{
244
245	return (qf->qfname);
246}
247
248int
249quota_check_path(const struct quotafile *qf, const char *path)
250{
251	struct stat st;
252
253	if (stat(path, &st) == -1)
254		return (-1);
255	return (st.st_dev == qf->dev);
256}
257
258int
259quota_maxid(struct quotafile *qf)
260{
261	struct stat st;
262	int maxid;
263
264	if (stat(qf->qfname, &st) < 0)
265		return (0);
266	switch (qf->wordsize) {
267	case 32:
268		maxid = st.st_size / sizeof(struct dqblk32) - 1;
269		break;
270	case 64:
271		maxid = st.st_size / sizeof(struct dqblk64) - 2;
272		break;
273	default:
274		maxid = 0;
275		break;
276	}
277	return (maxid > 0 ? maxid : 0);
278}
279
280static int
281quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
282{
283	struct dqblk32 dqb32;
284	off_t off;
285
286	off = id * sizeof(struct dqblk32);
287	if (lseek(qf->fd, off, SEEK_SET) == -1)
288		return (-1);
289	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
290	case 0:
291		memset(dqb, 0, sizeof(*dqb));
292		return (0);
293	case sizeof(dqb32):
294		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
295		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
296		dqb->dqb_curblocks = dqb32.dqb_curblocks;
297		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
298		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
299		dqb->dqb_curinodes = dqb32.dqb_curinodes;
300		dqb->dqb_btime = dqb32.dqb_btime;
301		dqb->dqb_itime = dqb32.dqb_itime;
302		return (0);
303	default:
304		return (-1);
305	}
306}
307
308static int
309quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
310{
311	struct dqblk64 dqb64;
312	off_t off;
313
314	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
315	if (lseek(qf->fd, off, SEEK_SET) == -1)
316		return (-1);
317	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
318	case 0:
319		memset(dqb, 0, sizeof(*dqb));
320		return (0);
321	case sizeof(dqb64):
322		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
323		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
324		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
325		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
326		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
327		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
328		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
329		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
330		return (0);
331	default:
332		return (-1);
333	}
334}
335
336int
337quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
338{
339	int qcmd;
340
341	if (qf->fd == -1) {
342		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
343		return (quotactl(qf->fsname, qcmd, id, dqb));
344	}
345	switch (qf->wordsize) {
346	case 32:
347		return (quota_read32(qf, dqb, id));
348	case 64:
349		return (quota_read64(qf, dqb, id));
350	default:
351		errno = EINVAL;
352		return (-1);
353	}
354	/* not reached */
355}
356
357#define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
358
359static int
360quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
361{
362	struct dqblk32 dqb32;
363	off_t off;
364
365	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
366	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
367	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
368	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
369	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
370	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
371	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
372	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
373
374	off = id * sizeof(struct dqblk32);
375	if (lseek(qf->fd, off, SEEK_SET) == -1)
376		return (-1);
377	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
378		return (0);
379	return (-1);
380}
381
382static int
383quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
384{
385	struct dqblk64 dqb64;
386	off_t off;
387
388	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
389	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
390	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
391	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
392	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
393	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
394	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
395	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
396
397	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
398	if (lseek(qf->fd, off, SEEK_SET) == -1)
399		return (-1);
400	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
401		return (0);
402	return (-1);
403}
404
405int
406quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
407{
408	struct dqblk dqbuf;
409	int qcmd;
410
411	if (qf->fd == -1) {
412		qcmd = QCMD(Q_SETUSE, qf->quotatype);
413		return (quotactl(qf->fsname, qcmd, id, dqb));
414	}
415	/*
416	 * Have to do read-modify-write of quota in file.
417	 */
418	if ((qf->accmode & O_RDWR) != O_RDWR) {
419		errno = EBADF;
420		return (-1);
421	}
422	if (quota_read(qf, &dqbuf, id) != 0)
423		return (-1);
424	/*
425	 * Reset time limit if have a soft limit and were
426	 * previously under it, but are now over it.
427	 */
428	if (dqbuf.dqb_bsoftlimit && id != 0 &&
429	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
430	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
431		dqbuf.dqb_btime = 0;
432	if (dqbuf.dqb_isoftlimit && id != 0 &&
433	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
434	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
435		dqbuf.dqb_itime = 0;
436	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
437	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
438	/*
439	 * Write it back.
440	 */
441	switch (qf->wordsize) {
442	case 32:
443		return (quota_write32(qf, &dqbuf, id));
444	case 64:
445		return (quota_write64(qf, &dqbuf, id));
446	default:
447		errno = EINVAL;
448		return (-1);
449	}
450	/* not reached */
451}
452
453int
454quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
455{
456	struct dqblk dqbuf;
457	int qcmd;
458
459	if (qf->fd == -1) {
460		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
461		return (quotactl(qf->fsname, qcmd, id, dqb));
462	}
463	/*
464	 * Have to do read-modify-write of quota in file.
465	 */
466	if ((qf->accmode & O_RDWR) != O_RDWR) {
467		errno = EBADF;
468		return (-1);
469	}
470	if (quota_read(qf, &dqbuf, id) != 0)
471		return (-1);
472	/*
473	 * Reset time limit if have a soft limit and were
474	 * previously under it, but are now over it
475	 * or if there previously was no soft limit, but
476	 * now have one and are over it.
477	 */
478	if (dqbuf.dqb_bsoftlimit && id != 0 &&
479	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
480	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
481		dqb->dqb_btime = 0;
482	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
483	    dqb->dqb_bsoftlimit > 0 &&
484	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
485		dqb->dqb_btime = 0;
486	if (dqbuf.dqb_isoftlimit && id != 0 &&
487	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
488	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
489		dqb->dqb_itime = 0;
490	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
491	    dqb->dqb_isoftlimit > 0 &&
492	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
493		dqb->dqb_itime = 0;
494	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
495	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
496	/*
497	 * Write it back.
498	 */
499	switch (qf->wordsize) {
500	case 32:
501		return (quota_write32(qf, dqb, id));
502	case 64:
503		return (quota_write64(qf, dqb, id));
504	default:
505		errno = EINVAL;
506		return (-1);
507	}
508	/* not reached */
509}
510
511/*
512 * Convert a quota file from one format to another.
513 */
514int
515quota_convert(struct quotafile *qf, int wordsize)
516{
517	struct quotafile *newqf;
518	struct dqhdr64 dqh;
519	struct dqblk dqblk;
520	struct group *grp;
521	int serrno, maxid, id, fd;
522
523	/*
524	 * Quotas must not be active and quotafile must be open
525	 * for reading and writing.
526	 */
527	if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
528		errno = EBADF;
529		return (-1);
530	}
531	if ((wordsize != 32 && wordsize != 64) ||
532	     wordsize == qf->wordsize) {
533		errno = EINVAL;
534		return (-1);
535	}
536	maxid = quota_maxid(qf);
537	if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
538		errno = ENOMEM;
539		return (-1);
540	}
541	*newqf = *qf;
542	snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
543	    qf->wordsize);
544	if (rename(qf->qfname, newqf->qfname) < 0) {
545		free(newqf);
546		return (-1);
547	}
548	if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,
549	    0)) < 0) {
550		serrno = errno;
551		goto error;
552	}
553	newqf->wordsize = wordsize;
554	if (wordsize == 64) {
555		memset(&dqh, 0, sizeof(dqh));
556		memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
557		dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
558		dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
559		dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
560		if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
561			serrno = errno;
562			goto error;
563		}
564	}
565	grp = getgrnam(QUOTAGROUP);
566	fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
567	fchmod(newqf->fd, 0640);
568	for (id = 0; id <= maxid; id++) {
569		if ((quota_read(qf, &dqblk, id)) < 0)
570			break;
571		switch (newqf->wordsize) {
572		case 32:
573			if ((quota_write32(newqf, &dqblk, id)) < 0)
574				break;
575			continue;
576		case 64:
577			if ((quota_write64(newqf, &dqblk, id)) < 0)
578				break;
579			continue;
580		default:
581			errno = EINVAL;
582			break;
583		}
584	}
585	if (id < maxid) {
586		serrno = errno;
587		goto error;
588	}
589	/*
590	 * Update the passed in quotafile to reference the new file
591	 * of the converted format size.
592	 */
593	fd = qf->fd;
594	qf->fd = newqf->fd;
595	newqf->fd = fd;
596	qf->wordsize = newqf->wordsize;
597	quota_close(newqf);
598	return (0);
599error:
600	/* put back the original file */
601	(void) rename(newqf->qfname, qf->qfname);
602	quota_close(newqf);
603	errno = serrno;
604	return (-1);
605}
606