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