quotafile.c revision 199328
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: projects/quota64/lib/libutil/quotafile.c 199328 2009-11-16 18:59:04Z mckusick $
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 = index(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 ((qf = calloc(1, sizeof(*qf))) == NULL)
122		return (NULL);
123	qf->fd = -1;
124	qf->quotatype = quotatype;
125	strncpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
126	if (stat(qf->fsname, &st) != 0)
127		goto error;
128	qf->dev = st.st_dev;
129	serrno = hasquota(fs, quotatype, qf->qfname, sizeof(qf->qfname));
130	qcmd = QCMD(Q_GETQUOTA, quotatype);
131	if (quotactl(fs->fs_file, qcmd, 0, &dqh) == 0) {
132		qf->wordsize = 64;
133		return (qf);
134	}
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)) < 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, 0)) < 0)
172		goto error;
173	qf->wordsize = 64;
174	memset(&dqh, 0, sizeof(dqh));
175	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
176	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
177	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
178	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
179	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh))
180		goto error;
181	grp = getgrnam(QUOTAGROUP);
182	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
183	fchmod(qf->fd, 0640);
184	return (qf);
185error:
186	serrno = errno;
187	/* did we have an open file? */
188	if (qf->fd != -1) {
189		/* was it one we created ourselves? */
190		if ((openflags & O_CREAT) == O_CREAT)
191			unlink(qf->qfname);
192		close(qf->fd);
193	}
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
253	if (stat(qf->qfname, &st) < 0)
254		return (0);
255	switch (qf->wordsize) {
256	case 32:
257		return (st.st_size / sizeof(struct dqblk32));
258	case 64:
259		return (st.st_size / sizeof(struct dqblk64) - 1);
260	default:
261		return (0);
262	}
263	/* not reached */
264}
265
266static int
267quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
268{
269	struct dqblk32 dqb32;
270	off_t off;
271
272	off = id * sizeof(struct dqblk32);
273	if (lseek(qf->fd, off, SEEK_SET) == -1)
274		return (-1);
275	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
276	case 0:
277		memset(dqb, 0, sizeof(*dqb));
278		return (0);
279	case sizeof(dqb32):
280		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
281		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
282		dqb->dqb_curblocks = dqb32.dqb_curblocks;
283		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
284		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
285		dqb->dqb_curinodes = dqb32.dqb_curinodes;
286		dqb->dqb_btime = dqb32.dqb_btime;
287		dqb->dqb_itime = dqb32.dqb_itime;
288		return (0);
289	default:
290		return (-1);
291	}
292}
293
294static int
295quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
296{
297	struct dqblk64 dqb64;
298	off_t off;
299
300	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
301	if (lseek(qf->fd, off, SEEK_SET) == -1)
302		return (-1);
303	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
304	case 0:
305		memset(dqb, 0, sizeof(*dqb));
306		return (0);
307	case sizeof(dqb64):
308		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
309		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
310		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
311		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
312		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
313		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
314		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
315		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
316		return (0);
317	default:
318		return (-1);
319	}
320}
321
322int
323quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
324{
325	int qcmd;
326
327	if (qf->fd == -1) {
328		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
329		return (quotactl(qf->fsname, qcmd, id, dqb));
330	}
331	switch (qf->wordsize) {
332	case 32:
333		return (quota_read32(qf, dqb, id));
334	case 64:
335		return (quota_read64(qf, dqb, id));
336	default:
337		errno = EINVAL;
338		return (-1);
339	}
340	/* not reached */
341}
342
343#define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
344
345static int
346quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
347{
348	struct dqblk32 dqb32;
349	off_t off;
350
351	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
352	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
353	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
354	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
355	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
356	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
357	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
358	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
359
360	off = id * sizeof(struct dqblk32);
361	if (lseek(qf->fd, off, SEEK_SET) == -1)
362		return (-1);
363	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
364		return (0);
365	return (-1);
366}
367
368static int
369quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
370{
371	struct dqblk64 dqb64;
372	off_t off;
373
374	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
375	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
376	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
377	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
378	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
379	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
380	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
381	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
382
383	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
384	if (lseek(qf->fd, off, SEEK_SET) == -1)
385		return (-1);
386	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
387		return (0);
388	return (-1);
389}
390
391int
392quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
393{
394	struct dqblk dqbuf;
395	int qcmd;
396
397	if ((qf->accmode & O_RDWR) != O_RDWR) {
398		errno = EBADF;
399		return (-1);
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 (quota_read(qf, &dqbuf, id) != 0)
409		return (-1);
410	/*
411	 * Reset time limit if have a soft limit and were
412	 * previously under it, but are now over it.
413	 */
414	if (dqbuf.dqb_bsoftlimit && id != 0 &&
415	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
416	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
417		dqbuf.dqb_btime = 0;
418	if (dqbuf.dqb_isoftlimit && id != 0 &&
419	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
420	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
421		dqbuf.dqb_itime = 0;
422	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
423	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
424	/*
425	 * Write it back.
426	 */
427	switch (qf->wordsize) {
428	case 32:
429		return (quota_write32(qf, &dqbuf, id));
430	case 64:
431		return (quota_write64(qf, &dqbuf, id));
432	default:
433		errno = EINVAL;
434		return (-1);
435	}
436	/* not reached */
437}
438
439int
440quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
441{
442	struct dqblk dqbuf;
443	int qcmd;
444
445	if ((qf->accmode & O_RDWR) != O_RDWR) {
446		errno = EBADF;
447		return (-1);
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 (quota_read(qf, &dqbuf, id) != 0)
457		return (-1);
458	/*
459	 * Reset time limit if have a soft limit and were
460	 * previously under it, but are now over it
461	 * or if there previously was no soft limit, but
462	 * now have one and are over it.
463	 */
464	if (dqbuf.dqb_bsoftlimit && id != 0 &&
465	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
466	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
467		dqb->dqb_btime = 0;
468	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
469	    dqb->dqb_bsoftlimit > 0 &&
470	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
471		dqb->dqb_btime = 0;
472	if (dqbuf.dqb_isoftlimit && id != 0 &&
473	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
474	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
475		dqb->dqb_itime = 0;
476	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
477	    dqb->dqb_isoftlimit > 0 &&
478	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
479		dqb->dqb_itime = 0;
480	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
481	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
482	/*
483	 * Write it back.
484	 */
485	switch (qf->wordsize) {
486	case 32:
487		return (quota_write32(qf, dqb, id));
488	case 64:
489		return (quota_write64(qf, dqb, id));
490	default:
491		errno = EINVAL;
492		return (-1);
493	}
494	/* not reached */
495}
496