xref: /freebsd/lib/libutil/quotafile.c (revision 076ad2f836d5f49dc1375f1677335a48fe0d4b82)
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$
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 
50 struct 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 
60 static const char *qfextension[] = INITQFNAMES;
61 
62 /*
63  * Check to see if a particular quota is to be enabled.
64  */
65 static int
66 hasquota(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 
112 struct quotafile *
113 quota_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);
189 error:
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 
199 void
200 quota_close(struct quotafile *qf)
201 {
202 
203 	if (qf->fd != -1)
204 		close(qf->fd);
205 	free(qf);
206 }
207 
208 int
209 quota_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 
217 int
218 quota_off(struct quotafile *qf)
219 {
220 
221 	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
222 }
223 
224 const char *
225 quota_fsname(const struct quotafile *qf)
226 {
227 
228 	return (qf->fsname);
229 }
230 
231 const char *
232 quota_qfname(const struct quotafile *qf)
233 {
234 
235 	return (qf->qfname);
236 }
237 
238 int
239 quota_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 
248 int
249 quota_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 
270 static int
271 quota_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 
298 static int
299 quota_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 
326 int
327 quota_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 
349 static int
350 quota_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 
372 static int
373 quota_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 
395 int
396 quota_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 
443 int
444 quota_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  */
504 int
505 quota_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);
589 error:
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