xref: /freebsd/lib/libutil/quotafile.c (revision 4f52dfbb8d6c4d446500c5b097e3806ec219fbd4)
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 
52 struct 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 
62 static const char *qfextension[] = INITQFNAMES;
63 
64 /*
65  * Check to see if a particular quota is to be enabled.
66  */
67 static int
68 hasquota(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 		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 
114 struct quotafile *
115 quota_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 (strcmp(fs->fs_vfstype, "ufs"))
124 		return (NULL);
125 	if ((qf = calloc(1, sizeof(*qf))) == NULL)
126 		return (NULL);
127 	qf->fd = -1;
128 	qf->quotatype = quotatype;
129 	strlcpy(qf->fsname, fs->fs_file, sizeof(qf->fsname));
130 	if (stat(qf->fsname, &st) != 0)
131 		goto error;
132 	qf->dev = st.st_dev;
133 	serrno = hasquota(fs, quotatype, qf->qfname, sizeof(qf->qfname));
134 	qcmd = QCMD(Q_GETQUOTASIZE, quotatype);
135 	if (quotactl(qf->fsname, qcmd, 0, &qf->wordsize) == 0)
136 		return (qf);
137 	if (serrno == 0) {
138 		errno = EOPNOTSUPP;
139 		goto error;
140 	}
141 	qf->accmode = openflags & O_ACCMODE;
142 	if ((qf->fd = open(qf->qfname, qf->accmode|O_CLOEXEC)) < 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|O_CLOEXEC, 0)) <
174 	    0)
175 		goto error;
176 	qf->wordsize = 64;
177 	memset(&dqh, 0, sizeof(dqh));
178 	memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
179 	dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
180 	dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
181 	dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
182 	if (write(qf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
183 		/* it was one we created ourselves */
184 		unlink(qf->qfname);
185 		goto error;
186 	}
187 	grp = getgrnam(QUOTAGROUP);
188 	fchown(qf->fd, 0, grp ? grp->gr_gid : 0);
189 	fchmod(qf->fd, 0640);
190 	return (qf);
191 error:
192 	serrno = errno;
193 	/* did we have an open file? */
194 	if (qf->fd != -1)
195 		close(qf->fd);
196 	free(qf);
197 	errno = serrno;
198 	return (NULL);
199 }
200 
201 void
202 quota_close(struct quotafile *qf)
203 {
204 
205 	if (qf->fd != -1)
206 		close(qf->fd);
207 	free(qf);
208 }
209 
210 int
211 quota_on(struct quotafile *qf)
212 {
213 	int qcmd;
214 
215 	qcmd = QCMD(Q_QUOTAON, qf->quotatype);
216 	return (quotactl(qf->fsname, qcmd, 0, qf->qfname));
217 }
218 
219 int
220 quota_off(struct quotafile *qf)
221 {
222 
223 	return (quotactl(qf->fsname, QCMD(Q_QUOTAOFF, qf->quotatype), 0, 0));
224 }
225 
226 const char *
227 quota_fsname(const struct quotafile *qf)
228 {
229 
230 	return (qf->fsname);
231 }
232 
233 const char *
234 quota_qfname(const struct quotafile *qf)
235 {
236 
237 	return (qf->qfname);
238 }
239 
240 int
241 quota_check_path(const struct quotafile *qf, const char *path)
242 {
243 	struct stat st;
244 
245 	if (stat(path, &st) == -1)
246 		return (-1);
247 	return (st.st_dev == qf->dev);
248 }
249 
250 int
251 quota_maxid(struct quotafile *qf)
252 {
253 	struct stat st;
254 	int maxid;
255 
256 	if (stat(qf->qfname, &st) < 0)
257 		return (0);
258 	switch (qf->wordsize) {
259 	case 32:
260 		maxid = st.st_size / sizeof(struct dqblk32) - 1;
261 		break;
262 	case 64:
263 		maxid = st.st_size / sizeof(struct dqblk64) - 2;
264 		break;
265 	default:
266 		maxid = 0;
267 		break;
268 	}
269 	return (maxid > 0 ? maxid : 0);
270 }
271 
272 static int
273 quota_read32(struct quotafile *qf, struct dqblk *dqb, int id)
274 {
275 	struct dqblk32 dqb32;
276 	off_t off;
277 
278 	off = id * sizeof(struct dqblk32);
279 	if (lseek(qf->fd, off, SEEK_SET) == -1)
280 		return (-1);
281 	switch (read(qf->fd, &dqb32, sizeof(dqb32))) {
282 	case 0:
283 		memset(dqb, 0, sizeof(*dqb));
284 		return (0);
285 	case sizeof(dqb32):
286 		dqb->dqb_bhardlimit = dqb32.dqb_bhardlimit;
287 		dqb->dqb_bsoftlimit = dqb32.dqb_bsoftlimit;
288 		dqb->dqb_curblocks = dqb32.dqb_curblocks;
289 		dqb->dqb_ihardlimit = dqb32.dqb_ihardlimit;
290 		dqb->dqb_isoftlimit = dqb32.dqb_isoftlimit;
291 		dqb->dqb_curinodes = dqb32.dqb_curinodes;
292 		dqb->dqb_btime = dqb32.dqb_btime;
293 		dqb->dqb_itime = dqb32.dqb_itime;
294 		return (0);
295 	default:
296 		return (-1);
297 	}
298 }
299 
300 static int
301 quota_read64(struct quotafile *qf, struct dqblk *dqb, int id)
302 {
303 	struct dqblk64 dqb64;
304 	off_t off;
305 
306 	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
307 	if (lseek(qf->fd, off, SEEK_SET) == -1)
308 		return (-1);
309 	switch (read(qf->fd, &dqb64, sizeof(dqb64))) {
310 	case 0:
311 		memset(dqb, 0, sizeof(*dqb));
312 		return (0);
313 	case sizeof(dqb64):
314 		dqb->dqb_bhardlimit = be64toh(dqb64.dqb_bhardlimit);
315 		dqb->dqb_bsoftlimit = be64toh(dqb64.dqb_bsoftlimit);
316 		dqb->dqb_curblocks = be64toh(dqb64.dqb_curblocks);
317 		dqb->dqb_ihardlimit = be64toh(dqb64.dqb_ihardlimit);
318 		dqb->dqb_isoftlimit = be64toh(dqb64.dqb_isoftlimit);
319 		dqb->dqb_curinodes = be64toh(dqb64.dqb_curinodes);
320 		dqb->dqb_btime = be64toh(dqb64.dqb_btime);
321 		dqb->dqb_itime = be64toh(dqb64.dqb_itime);
322 		return (0);
323 	default:
324 		return (-1);
325 	}
326 }
327 
328 int
329 quota_read(struct quotafile *qf, struct dqblk *dqb, int id)
330 {
331 	int qcmd;
332 
333 	if (qf->fd == -1) {
334 		qcmd = QCMD(Q_GETQUOTA, qf->quotatype);
335 		return (quotactl(qf->fsname, qcmd, id, dqb));
336 	}
337 	switch (qf->wordsize) {
338 	case 32:
339 		return (quota_read32(qf, dqb, id));
340 	case 64:
341 		return (quota_read64(qf, dqb, id));
342 	default:
343 		errno = EINVAL;
344 		return (-1);
345 	}
346 	/* not reached */
347 }
348 
349 #define CLIP32(u64) ((u64) > UINT32_MAX ? UINT32_MAX : (uint32_t)(u64))
350 
351 static int
352 quota_write32(struct quotafile *qf, const struct dqblk *dqb, int id)
353 {
354 	struct dqblk32 dqb32;
355 	off_t off;
356 
357 	dqb32.dqb_bhardlimit = CLIP32(dqb->dqb_bhardlimit);
358 	dqb32.dqb_bsoftlimit = CLIP32(dqb->dqb_bsoftlimit);
359 	dqb32.dqb_curblocks = CLIP32(dqb->dqb_curblocks);
360 	dqb32.dqb_ihardlimit = CLIP32(dqb->dqb_ihardlimit);
361 	dqb32.dqb_isoftlimit = CLIP32(dqb->dqb_isoftlimit);
362 	dqb32.dqb_curinodes = CLIP32(dqb->dqb_curinodes);
363 	dqb32.dqb_btime = CLIP32(dqb->dqb_btime);
364 	dqb32.dqb_itime = CLIP32(dqb->dqb_itime);
365 
366 	off = id * sizeof(struct dqblk32);
367 	if (lseek(qf->fd, off, SEEK_SET) == -1)
368 		return (-1);
369 	if (write(qf->fd, &dqb32, sizeof(dqb32)) == sizeof(dqb32))
370 		return (0);
371 	return (-1);
372 }
373 
374 static int
375 quota_write64(struct quotafile *qf, const struct dqblk *dqb, int id)
376 {
377 	struct dqblk64 dqb64;
378 	off_t off;
379 
380 	dqb64.dqb_bhardlimit = htobe64(dqb->dqb_bhardlimit);
381 	dqb64.dqb_bsoftlimit = htobe64(dqb->dqb_bsoftlimit);
382 	dqb64.dqb_curblocks = htobe64(dqb->dqb_curblocks);
383 	dqb64.dqb_ihardlimit = htobe64(dqb->dqb_ihardlimit);
384 	dqb64.dqb_isoftlimit = htobe64(dqb->dqb_isoftlimit);
385 	dqb64.dqb_curinodes = htobe64(dqb->dqb_curinodes);
386 	dqb64.dqb_btime = htobe64(dqb->dqb_btime);
387 	dqb64.dqb_itime = htobe64(dqb->dqb_itime);
388 
389 	off = sizeof(struct dqhdr64) + id * sizeof(struct dqblk64);
390 	if (lseek(qf->fd, off, SEEK_SET) == -1)
391 		return (-1);
392 	if (write(qf->fd, &dqb64, sizeof(dqb64)) == sizeof(dqb64))
393 		return (0);
394 	return (-1);
395 }
396 
397 int
398 quota_write_usage(struct quotafile *qf, struct dqblk *dqb, int id)
399 {
400 	struct dqblk dqbuf;
401 	int qcmd;
402 
403 	if (qf->fd == -1) {
404 		qcmd = QCMD(Q_SETUSE, qf->quotatype);
405 		return (quotactl(qf->fsname, qcmd, id, dqb));
406 	}
407 	/*
408 	 * Have to do read-modify-write of quota in file.
409 	 */
410 	if ((qf->accmode & O_RDWR) != O_RDWR) {
411 		errno = EBADF;
412 		return (-1);
413 	}
414 	if (quota_read(qf, &dqbuf, id) != 0)
415 		return (-1);
416 	/*
417 	 * Reset time limit if have a soft limit and were
418 	 * previously under it, but are now over it.
419 	 */
420 	if (dqbuf.dqb_bsoftlimit && id != 0 &&
421 	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
422 	    dqb->dqb_curblocks >= dqbuf.dqb_bsoftlimit)
423 		dqbuf.dqb_btime = 0;
424 	if (dqbuf.dqb_isoftlimit && id != 0 &&
425 	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
426 	    dqb->dqb_curinodes >= dqbuf.dqb_isoftlimit)
427 		dqbuf.dqb_itime = 0;
428 	dqbuf.dqb_curinodes = dqb->dqb_curinodes;
429 	dqbuf.dqb_curblocks = dqb->dqb_curblocks;
430 	/*
431 	 * Write it back.
432 	 */
433 	switch (qf->wordsize) {
434 	case 32:
435 		return (quota_write32(qf, &dqbuf, id));
436 	case 64:
437 		return (quota_write64(qf, &dqbuf, id));
438 	default:
439 		errno = EINVAL;
440 		return (-1);
441 	}
442 	/* not reached */
443 }
444 
445 int
446 quota_write_limits(struct quotafile *qf, struct dqblk *dqb, int id)
447 {
448 	struct dqblk dqbuf;
449 	int qcmd;
450 
451 	if (qf->fd == -1) {
452 		qcmd = QCMD(Q_SETQUOTA, qf->quotatype);
453 		return (quotactl(qf->fsname, qcmd, id, dqb));
454 	}
455 	/*
456 	 * Have to do read-modify-write of quota in file.
457 	 */
458 	if ((qf->accmode & O_RDWR) != O_RDWR) {
459 		errno = EBADF;
460 		return (-1);
461 	}
462 	if (quota_read(qf, &dqbuf, id) != 0)
463 		return (-1);
464 	/*
465 	 * Reset time limit if have a soft limit and were
466 	 * previously under it, but are now over it
467 	 * or if there previously was no soft limit, but
468 	 * now have one and are over it.
469 	 */
470 	if (dqbuf.dqb_bsoftlimit && id != 0 &&
471 	    dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit &&
472 	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
473 		dqb->dqb_btime = 0;
474 	if (dqbuf.dqb_bsoftlimit == 0 && id != 0 &&
475 	    dqb->dqb_bsoftlimit > 0 &&
476 	    dqbuf.dqb_curblocks >= dqb->dqb_bsoftlimit)
477 		dqb->dqb_btime = 0;
478 	if (dqbuf.dqb_isoftlimit && id != 0 &&
479 	    dqbuf.dqb_curinodes < dqbuf.dqb_isoftlimit &&
480 	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
481 		dqb->dqb_itime = 0;
482 	if (dqbuf.dqb_isoftlimit == 0 && id !=0 &&
483 	    dqb->dqb_isoftlimit > 0 &&
484 	    dqbuf.dqb_curinodes >= dqb->dqb_isoftlimit)
485 		dqb->dqb_itime = 0;
486 	dqb->dqb_curinodes = dqbuf.dqb_curinodes;
487 	dqb->dqb_curblocks = dqbuf.dqb_curblocks;
488 	/*
489 	 * Write it back.
490 	 */
491 	switch (qf->wordsize) {
492 	case 32:
493 		return (quota_write32(qf, dqb, id));
494 	case 64:
495 		return (quota_write64(qf, dqb, id));
496 	default:
497 		errno = EINVAL;
498 		return (-1);
499 	}
500 	/* not reached */
501 }
502 
503 /*
504  * Convert a quota file from one format to another.
505  */
506 int
507 quota_convert(struct quotafile *qf, int wordsize)
508 {
509 	struct quotafile *newqf;
510 	struct dqhdr64 dqh;
511 	struct dqblk dqblk;
512 	struct group *grp;
513 	int serrno, maxid, id, fd;
514 
515 	/*
516 	 * Quotas must not be active and quotafile must be open
517 	 * for reading and writing.
518 	 */
519 	if ((qf->accmode & O_RDWR) != O_RDWR || qf->fd == -1) {
520 		errno = EBADF;
521 		return (-1);
522 	}
523 	if ((wordsize != 32 && wordsize != 64) ||
524 	     wordsize == qf->wordsize) {
525 		errno = EINVAL;
526 		return (-1);
527 	}
528 	maxid = quota_maxid(qf);
529 	if ((newqf = calloc(1, sizeof(*qf))) == NULL) {
530 		errno = ENOMEM;
531 		return (-1);
532 	}
533 	*newqf = *qf;
534 	snprintf(newqf->qfname, MAXPATHLEN + 1, "%s_%d.orig", qf->qfname,
535 	    qf->wordsize);
536 	if (rename(qf->qfname, newqf->qfname) < 0) {
537 		free(newqf);
538 		return (-1);
539 	}
540 	if ((newqf->fd = open(qf->qfname, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC,
541 	    0)) < 0) {
542 		serrno = errno;
543 		goto error;
544 	}
545 	newqf->wordsize = wordsize;
546 	if (wordsize == 64) {
547 		memset(&dqh, 0, sizeof(dqh));
548 		memcpy(dqh.dqh_magic, Q_DQHDR64_MAGIC, sizeof(dqh.dqh_magic));
549 		dqh.dqh_version = htobe32(Q_DQHDR64_VERSION);
550 		dqh.dqh_hdrlen = htobe32(sizeof(struct dqhdr64));
551 		dqh.dqh_reclen = htobe32(sizeof(struct dqblk64));
552 		if (write(newqf->fd, &dqh, sizeof(dqh)) != sizeof(dqh)) {
553 			serrno = errno;
554 			goto error;
555 		}
556 	}
557 	grp = getgrnam(QUOTAGROUP);
558 	fchown(newqf->fd, 0, grp ? grp->gr_gid : 0);
559 	fchmod(newqf->fd, 0640);
560 	for (id = 0; id <= maxid; id++) {
561 		if ((quota_read(qf, &dqblk, id)) < 0)
562 			break;
563 		switch (newqf->wordsize) {
564 		case 32:
565 			if ((quota_write32(newqf, &dqblk, id)) < 0)
566 				break;
567 			continue;
568 		case 64:
569 			if ((quota_write64(newqf, &dqblk, id)) < 0)
570 				break;
571 			continue;
572 		default:
573 			errno = EINVAL;
574 			break;
575 		}
576 	}
577 	if (id < maxid) {
578 		serrno = errno;
579 		goto error;
580 	}
581 	/*
582 	 * Update the passed in quotafile to reference the new file
583 	 * of the converted format size.
584 	 */
585 	fd = qf->fd;
586 	qf->fd = newqf->fd;
587 	newqf->fd = fd;
588 	qf->wordsize = newqf->wordsize;
589 	quota_close(newqf);
590 	return (0);
591 error:
592 	/* put back the original file */
593 	(void) rename(newqf->qfname, qf->qfname);
594 	quota_close(newqf);
595 	errno = serrno;
596 	return (-1);
597 }
598