quotafile.c revision 197532
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 197532 2009-09-26 23:16:06Z des $
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 * XXX merge into quota_open
65 */
66static int
67hasquota(struct fstab *fs, int type, char *qfnamep, int qfbufsize)
68{
69	char *opt;
70	char *cp;
71	struct statfs sfb;
72	char buf[BUFSIZ];
73	static char initname, usrname[100], grpname[100];
74
75	/*
76	 * XXX
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 = index(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		strncpy(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;
122
123	if ((qf = calloc(1, sizeof(*qf))) == NULL)
124		return (NULL);
125	qf->fd = -1;
126	qf->quotatype = quotatype;
127	strncpy(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	qcmd = QCMD(Q_GETQUOTA, quotatype);
132	if (quotactl(fs->fs_file, qcmd, 0, &dqh) == 0) {
133		qf->wordsize = 64;
134		qf->fd = -1;
135		return (qf);
136	}
137	if (!hasquota(fs, quotatype, qf->qfname, sizeof(qf->qfname))) {
138		errno = EOPNOTSUPP;
139		goto error;
140	}
141	qf->accmode = openflags & O_ACCMODE;
142	if ((qf->fd = open(qf->qfname, qf->accmode)) < 0 &&
143	    (openflags & O_CREAT) != O_CREAT)
144		goto error;
145	/* File open worked, so process it */
146	if (qf->fd != -1) {
147		qf->wordsize = 32;
148		switch (read(qf->fd, &dqh, sizeof(dqh))) {
149		case -1:
150			goto error;
151		case sizeof(dqh):
152			if (strcmp(dqh.dqh_magic, Q_DQHDR64_MAGIC) != 0) {
153				/* no magic, assume 32 bits */
154				qf->wordsize = 32;
155				return (qf);
156			}
157			if (be32toh(dqh.dqh_version) != Q_DQHDR64_VERSION ||
158			    be32toh(dqh.dqh_hdrlen) != sizeof(struct dqhdr64) ||
159			    be32toh(dqh.dqh_reclen) != sizeof(struct dqblk64)) {
160				/* correct magic, wrong version / lengths */
161				errno = EINVAL;
162				goto error;
163			}
164			qf->wordsize = 64;
165			return (qf);
166		default:
167			qf->wordsize = 32;
168			return (qf);
169		}
170		/* not reached */
171	}
172	/* open failed, but O_CREAT was specified, so create a new file */
173	if ((qf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC, 0)) < 0)
174		goto error;
175	qf->wordsize = 64;
176	memset(&dqh, 0, sizeof(dqh));
177	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
178	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
179	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
180	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
181	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh))
182		goto error;
183	grp = getgrnam(QUOTAGROUP);
184	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
185	fchmod(qf->fd, 0640);
186	return (qf);
187error:
188	serrno = errno;
189	/* did we have an open file? */
190	if (qf->fd != -1) {
191		/* was it one we created ourselves? */
192		if ((openflags & O_CREAT) == O_CREAT)
193			unlink(qf->qfname);
194		close(qf->fd);
195	}
196	free(qf);
197	errno = serrno;
198	return (NULL);
199}
200
201void
202quota_close(struct quotafile *qf)
203{
204
205	if (qf->fd != -1)
206		close(qf->fd);
207	free(qf);
208}
209
210const char *
211quota_fsname(const struct quotafile *qf)
212{
213
214	return (qf->fsname);
215}
216
217const char *
218quota_qfname(const struct quotafile *qf)
219{
220
221	return (qf->qfname);
222}
223
224int
225quota_check_path(const struct quotafile *qf, const char *path)
226{
227	struct stat st;
228
229	if (stat(path, &st) == -1)
230		return (-1);
231	return (st.st_dev == qf->dev);
232}
233
234static int
235quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
236{
237	struct dqblk32 dqb32;
238	off_t off;
239
240	off = id * sizeof(struct dqblk32);
241	if (lseek(qf->fd, off, SEEK_SET) == -1)
242		return (-1);
243	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
244	case 0:
245		memset(&dqb, 0, sizeof(*dqb));
246		return (0);
247	case sizeof(dqb32):
248		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
249		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
250		dqb->dqb_curblocks = dqb32.dqb_curblocks;
251		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
252		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
253		dqb->dqb_curinodes = dqb32.dqb_curinodes;
254		dqb->dqb_btime = dqb32.dqb_btime;
255		dqb->dqb_itime = dqb32.dqb_itime;
256		return (0);
257	default:
258		return (-1);
259	}
260}
261
262static int
263quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
264{
265	struct dqblk64 dqb64;
266	off_t off;
267
268	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
269	if (lseek(qf->fd, off, SEEK_SET) == -1)
270		return (-1);
271	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
272	case 0:
273		memset(&dqb, 0, sizeof(*dqb));
274		return (0);
275	case sizeof(dqb64):
276		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
277		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
278		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
279		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
280		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
281		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
282		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
283		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
284		return (0);
285	default:
286		return (-1);
287	}
288}
289
290int
291quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
292{
293	int qcmd;
294
295	if (qf->fd == -1) {
296		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
297		return (quotactl(qf->fsname, qcmd, id, dqb));
298	}
299	switch (qf->wordsize) {
300	case 32:
301		return (quota_read32(qf, dqb, id));
302	case 64:
303		return (quota_read64(qf, dqb, id));
304	default:
305		errno = EINVAL;
306		return (-1);
307	}
308	/* not reached */
309}
310
311#define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
312
313static int
314quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
315{
316	struct dqblk32 dqb32;
317	off_t off;
318
319	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
320	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
321	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
322	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
323	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
324	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
325	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
326	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
327
328	off = id * sizeof(struct dqblk32);
329	if (lseek(qf->fd, off, SEEK_SET) == -1)
330		return (-1);
331	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
332		return (0);
333	return (-1);
334}
335
336static int
337quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
338{
339	struct dqblk64 dqb64;
340	off_t off;
341
342	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
343	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
344	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
345	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
346	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
347	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
348	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
349	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
350
351	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
352	if (lseek(qf->fd, off, SEEK_SET) == -1)
353		return (-1);
354	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
355		return (0);
356	return (-1);
357}
358
359int
360quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
361{
362	struct dqblk dqbuf;
363	int qcmd;
364
365	if ((qf->accmode & O_RDWR) != O_RDWR) {
366		errno = EBADF;
367		return (-1);
368	}
369	if (qf->fd == -1) {
370		qcmd = QCMD(Q_SETUSE, qf->quotatype);
371		return (quotactl(qf->fsname, qcmd, id, dqb));
372	}
373	/*
374	 * Have to do read-modify-write of quota in file.
375	 */
376	if (quota_read(qf, &dqbuf, id) != 0)
377		return (-1);
378	/*
379	 * Reset time limit if have a soft limit and were
380	 * previously under it, but are now over it.
381	 */
382	if (dqbuf.dqb_bsoftlimit && id != 0 &&
383	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
384	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
385		dqbuf.dqb_btime = 0;
386	if (dqbuf.dqb_isoftlimit && id != 0 &&
387	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
388	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
389		dqbuf.dqb_itime = 0;
390	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
391	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
392	/*
393	 * Write it back.
394	 */
395	switch (qf->wordsize) {
396	case 32:
397		return (quota_write32(qf, &dqbuf, id));
398	case 64:
399		return (quota_write64(qf, &dqbuf, id));
400	default:
401		errno = EINVAL;
402		return (-1);
403	}
404	/* not reached */
405}
406
407int
408quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
409{
410	struct dqblk dqbuf;
411	int qcmd;
412
413	if ((qf->accmode & O_RDWR) != O_RDWR) {
414		errno = EBADF;
415		return (-1);
416	}
417	if (qf->fd == -1) {
418		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
419		return (quotactl(qf->fsname, qcmd, id, dqb));
420	}
421	/*
422	 * Have to do read-modify-write of quota in file.
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	 * or if there previously was no soft limit, but
430	 * now have one and are over it.
431	 */
432	if (dqbuf.dqb_bsoftlimit && id != 0 &&
433	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
434	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
435		dqb->dqb_btime = 0;
436	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
437	    dqb->dqb_bsoftlimit > 0 &&
438	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
439		dqb->dqb_btime = 0;
440	if (dqbuf.dqb_isoftlimit && id != 0 &&
441	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
442	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
443		dqb->dqb_itime = 0;
444	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
445	    dqb->dqb_isoftlimit > 0 &&
446	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
447		dqb->dqb_itime = 0;
448	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
449	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
450	/*
451	 * Write it back.
452	 */
453	switch (qf->wordsize) {
454	case 32:
455		return (quota_write32(qf, dqb, id));
456	case 64:
457		return (quota_write64(qf, dqb, id));
458	default:
459		errno = EINVAL;
460		return (-1);
461	}
462	/* not reached */
463}
464