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