xref: /netbsd-src/lib/libquota/quota_oldfiles.c (revision 1897181a7231d5fc7ab48994d1447fcbc4e13a49)
1 /*	$NetBSD: quota_oldfiles.c,v 1.2 2012/01/09 15:45:19 dholland Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Robert Elz at The University of Melbourne.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <limits.h>
43 #include <fstab.h>
44 #include <errno.h>
45 #include <err.h>
46 
47 #include <ufs/ufs/quota1.h>
48 
49 #include <quota.h>
50 #include "quotapvt.h"
51 
52 struct oldfiles_quotacursor {
53 	unsigned oqc_doingusers;
54 	unsigned oqc_doinggroups;
55 
56 	unsigned oqc_numusers;
57 	unsigned oqc_numgroups;
58 
59 	unsigned oqc_didusers;
60 	unsigned oqc_didgroups;
61 	unsigned oqc_diddefault;
62 	unsigned oqc_pos;
63 	unsigned oqc_didblocks;
64 };
65 
66 static uint64_t
67 dqblk_getlimit(uint32_t val)
68 {
69 	if (val == 0) {
70 		return QUOTA_NOLIMIT;
71 	} else {
72 		return val - 1;
73 	}
74 }
75 
76 static uint32_t
77 dqblk_setlimit(uint64_t val)
78 {
79 	if (val == QUOTA_NOLIMIT && val >= 0xffffffffUL) {
80 		return 0;
81 	} else {
82 		return (uint32_t)val + 1;
83 	}
84 }
85 
86 static void
87 dqblk_getblocks(const struct dqblk *dq, struct quotaval *qv)
88 {
89 	qv->qv_hardlimit = dqblk_getlimit(dq->dqb_bhardlimit);
90 	qv->qv_softlimit = dqblk_getlimit(dq->dqb_bsoftlimit);
91 	qv->qv_usage = dq->dqb_curblocks;
92 	qv->qv_expiretime = dq->dqb_btime;
93 	qv->qv_grace = QUOTA_NOTIME;
94 }
95 
96 static void
97 dqblk_getfiles(const struct dqblk *dq, struct quotaval *qv)
98 {
99 	qv->qv_hardlimit = dqblk_getlimit(dq->dqb_ihardlimit);
100 	qv->qv_softlimit = dqblk_getlimit(dq->dqb_isoftlimit);
101 	qv->qv_usage = dq->dqb_curinodes;
102 	qv->qv_expiretime = dq->dqb_itime;
103 	qv->qv_grace = QUOTA_NOTIME;
104 }
105 
106 static void
107 dqblk_putblocks(const struct quotaval *qv, struct dqblk *dq)
108 {
109 	dq->dqb_bhardlimit = dqblk_setlimit(qv->qv_hardlimit);
110 	dq->dqb_bsoftlimit = dqblk_setlimit(qv->qv_softlimit);
111 	dq->dqb_curblocks = qv->qv_usage;
112 	dq->dqb_btime = qv->qv_expiretime;
113 	/* ignore qv->qv_grace */
114 }
115 
116 static void
117 dqblk_putfiles(const struct quotaval *qv, struct dqblk *dq)
118 {
119 	dq->dqb_ihardlimit = dqblk_setlimit(qv->qv_hardlimit);
120 	dq->dqb_isoftlimit = dqblk_setlimit(qv->qv_softlimit);
121 	dq->dqb_curinodes = qv->qv_usage;
122 	dq->dqb_itime = qv->qv_expiretime;
123 	/* ignore qv->qv_grace */
124 }
125 
126 static int
127 __quota_oldfiles_open(struct quotahandle *qh, const char *path, int *fd_ret)
128 {
129 	int fd;
130 
131 	fd = open(path, O_RDWR);
132 	if (fd < 0 && (errno == EACCES || errno == EROFS)) {
133 		fd = open(path, O_RDONLY);
134 		if (fd < 0) {
135 			return -1;
136 		}
137 	}
138 	*fd_ret = fd;
139 	return 0;
140 }
141 
142 int
143 __quota_oldfiles_initialize(struct quotahandle *qh)
144 {
145 	static const char *const names[] = INITQFNAMES;
146 
147 	struct fstab *fs;
148 	char buf[sizeof(fs->fs_mntops)];
149 	char *opt, *state, *s;
150 	char path[PATH_MAX];
151 	const char *userquotafile, *groupquotafile;
152 	int hasuserquota, hasgroupquota;
153 
154 	if (qh->qh_hasoldfiles) {
155 		/* already initialized */
156 		return 0;
157 	}
158 
159 	/*
160 	 * Find the fstab entry.
161 	 *
162 	 * XXX: should be able to handle not just ffs quota1 files but
163 	 * also lfs and even ext2fs.
164 	 */
165 	setfsent();
166 	while ((fs = getfsent()) != NULL) {
167 		if (!strcmp(fs->fs_vfstype, "ffs") &&
168 		    !strcmp(fs->fs_file, qh->qh_mountpoint)) {
169 			break;
170 		}
171 	}
172 	endfsent();
173 
174 	if (fs == NULL) {
175 		warnx("%s not found in fstab", qh->qh_mountpoint);
176 		errno = ENXIO;
177 		return -1;
178 	}
179 
180 	/*
181 	 * Inspect the mount options to find the quota files.
182 	 * XXX this info should be gotten from the kernel.
183 	 *
184 	 * The options are:
185 	 *    userquota[=path]          enable user quotas
186 	 *    groupquota[=path]         enable group quotas
187 	 */
188 	hasuserquota = hasgroupquota = 0;
189 	userquotafile = groupquotafile = NULL;
190 	strlcpy(buf, fs->fs_mntops, sizeof(buf));
191 	for (opt = strtok_r(buf, ",", &state);
192 	     opt != NULL;
193 	     opt = strtok_r(NULL, ",", &state)) {
194 		s = strchr(opt, '=');
195 		if (s != NULL) {
196 			*(s++) = '\0';
197 		}
198 		if (!strcmp(opt, "userquota")) {
199 			hasuserquota = 1;
200 			if (s != NULL) {
201 				userquotafile = s;
202 			}
203 		} else if (!strcmp(opt, "groupquota")) {
204 			hasgroupquota = 1;
205 			if (s != NULL) {
206 				groupquotafile = s;
207 			}
208 		}
209 	}
210 
211 	if (!hasuserquota && !hasgroupquota) {
212 		errno = ENXIO;
213 		return -1;
214 	}
215 
216 	if (hasuserquota) {
217 		if (userquotafile == NULL) {
218 			(void)snprintf(path, sizeof(path), "%s/%s.%s",
219 				       fs->fs_file,
220 				       QUOTAFILENAME, names[USRQUOTA]);
221 			userquotafile = path;
222 		}
223 		if (__quota_oldfiles_open(qh, userquotafile,
224 					  &qh->qh_userfile)) {
225 			return -1;
226 		}
227 	}
228 	if (hasgroupquota) {
229 		if (groupquotafile == NULL) {
230 			(void)snprintf(path, sizeof(path), "%s/%s.%s",
231 				       fs->fs_file,
232 				       QUOTAFILENAME, names[GRPQUOTA]);
233 			groupquotafile = path;
234 		}
235 		if (__quota_oldfiles_open(qh, groupquotafile,
236 					  &qh->qh_groupfile)) {
237 			return -1;
238 		}
239 	}
240 
241 	qh->qh_hasoldfiles = 1;
242 
243 	return 0;
244 }
245 
246 const char *
247 __quota_oldfiles_getimplname(struct quotahandle *qh)
248 {
249 	return "ffs quota1 direct file access";
250 }
251 
252 static int
253 __quota_oldfiles_doget(struct quotahandle *qh, const struct quotakey *qk,
254 		       struct quotaval *qv, int *isallzero)
255 {
256 	int file;
257 	off_t pos;
258 	struct dqblk dq;
259 	ssize_t result;
260 
261 	switch (qk->qk_idtype) {
262 	    case QUOTA_IDTYPE_USER:
263 		file = qh->qh_userfile;
264 		break;
265 	    case QUOTA_IDTYPE_GROUP:
266 		file = qh->qh_groupfile;
267 		break;
268 	    default:
269 		errno = EINVAL;
270 		return -1;
271 	}
272 
273 	if (qk->qk_id == QUOTA_DEFAULTID) {
274 		pos = 0;
275 	} else {
276 		pos = qk->qk_id * sizeof(struct dqblk);
277 	}
278 
279 	result = pread(file, &dq, sizeof(dq), pos);
280 	if (result < 0) {
281 		return -1;
282 	} else if (result == 0) {
283 		/* Past EOF; no quota info on file for this ID */
284 		errno = ENOENT;
285 		return -1;
286 	} else if ((size_t)result != sizeof(dq)) {
287 		errno = EFTYPE;
288 		return -1;
289 	}
290 
291 	switch (qk->qk_objtype) {
292 	    case QUOTA_OBJTYPE_BLOCKS:
293 		dqblk_getblocks(&dq, qv);
294 		break;
295 	    case QUOTA_OBJTYPE_FILES:
296 		dqblk_getfiles(&dq, qv);
297 		break;
298 	    default:
299 		errno = EINVAL;
300 		return -1;
301 	}
302 
303 	if (qk->qk_id == QUOTA_DEFAULTID) {
304 		qv->qv_usage = 0;
305 		qv->qv_grace = qv->qv_expiretime;
306 		qv->qv_expiretime = QUOTA_NOTIME;
307 	} else if (qk->qk_id == 0) {
308 		qv->qv_hardlimit = 0;
309 		qv->qv_softlimit = 0;
310 		qv->qv_expiretime = QUOTA_NOTIME;
311 		qv->qv_grace = QUOTA_NOTIME;
312 	}
313 
314 	if (isallzero != NULL) {
315 		if (dq.dqb_bhardlimit == 0 &&
316 		    dq.dqb_bsoftlimit == 0 &&
317 		    dq.dqb_curblocks == 0 &&
318 		    dq.dqb_ihardlimit == 0 &&
319 		    dq.dqb_isoftlimit == 0 &&
320 		    dq.dqb_curinodes == 0 &&
321 		    dq.dqb_btime == 0 &&
322 		    dq.dqb_itime == 0) {
323 			*isallzero = 1;
324 		} else {
325 			*isallzero = 0;
326 		}
327 	}
328 
329 	return 0;
330 }
331 
332 static int
333 __quota_oldfiles_doput(struct quotahandle *qh, const struct quotakey *qk,
334 		       const struct quotaval *qv)
335 {
336 	int file;
337 	off_t pos;
338 	struct quotaval qv2;
339 	struct dqblk dq;
340 	ssize_t result;
341 
342 	switch (qk->qk_idtype) {
343 	    case QUOTA_IDTYPE_USER:
344 		file = qh->qh_userfile;
345 		break;
346 	    case QUOTA_IDTYPE_GROUP:
347 		file = qh->qh_groupfile;
348 		break;
349 	    default:
350 		errno = EINVAL;
351 		return -1;
352 	}
353 
354 	if (qk->qk_id == QUOTA_DEFAULTID) {
355 		pos = 0;
356 	} else {
357 		pos = qk->qk_id * sizeof(struct dqblk);
358 	}
359 
360 	result = pread(file, &dq, sizeof(dq), pos);
361 	if (result < 0) {
362 		return -1;
363 	} else if (result == 0) {
364 		/* Past EOF; fill in a blank dq to start from */
365 		dq.dqb_bhardlimit = 0;
366 		dq.dqb_bsoftlimit = 0;
367 		dq.dqb_curblocks = 0;
368 		dq.dqb_ihardlimit = 0;
369 		dq.dqb_isoftlimit = 0;
370 		dq.dqb_curinodes = 0;
371 		dq.dqb_btime = 0;
372 		dq.dqb_itime = 0;
373 	} else if ((size_t)result != sizeof(dq)) {
374 		errno = EFTYPE;
375 		return -1;
376 	}
377 
378 	switch (qk->qk_objtype) {
379 	    case QUOTA_OBJTYPE_BLOCKS:
380 		dqblk_getblocks(&dq, &qv2);
381 		break;
382 	    case QUOTA_OBJTYPE_FILES:
383 		dqblk_getfiles(&dq, &qv2);
384 		break;
385 	    default:
386 		errno = EINVAL;
387 		return -1;
388 	}
389 
390 	if (qk->qk_id == QUOTA_DEFAULTID) {
391 		qv2.qv_hardlimit = qv->qv_hardlimit;
392 		qv2.qv_softlimit = qv->qv_softlimit;
393 		/* leave qv2.qv_usage unchanged */
394 		qv2.qv_expiretime = qv->qv_grace;
395 		/* skip qv2.qv_grace */
396 
397 		/* ignore qv->qv_usage */
398 		/* ignore qv->qv_expiretime */
399 	} else if (qk->qk_id == 0) {
400 		/* leave qv2.qv_hardlimit unchanged */
401 		/* leave qv2.qv_softlimit unchanged */
402 		qv2.qv_usage = qv->qv_usage;
403 		/* leave qv2.qv_expiretime unchanged */
404 		/* skip qv2.qv_grace */
405 
406 		/* ignore qv->qv_hardlimit */
407 		/* ignore qv->qv_softlimit */
408 		/* ignore qv->qv_expiretime */
409 		/* ignore qv->qv_grace */
410 	} else {
411 		qv2 = *qv;
412 	}
413 
414 	switch (qk->qk_objtype) {
415 	    case QUOTA_OBJTYPE_BLOCKS:
416 		dqblk_putblocks(&qv2, &dq);
417 		break;
418 	    case QUOTA_OBJTYPE_FILES:
419 		dqblk_putfiles(&qv2, &dq);
420 		break;
421 	    default:
422 		errno = EINVAL;
423 		return -1;
424 	}
425 
426 	result = pwrite(file, &dq, sizeof(dq), pos);
427 	if (result < 0) {
428 		return -1;
429 	} else if ((size_t)result != sizeof(dq)) {
430 		/* ? */
431 		errno = EFTYPE;
432 		return -1;
433 	}
434 
435 	return 0;
436 }
437 
438 int
439 __quota_oldfiles_get(struct quotahandle *qh, const struct quotakey *qk,
440 		     struct quotaval *qv)
441 {
442 	return __quota_oldfiles_doget(qh, qk, qv, NULL);
443 }
444 
445 int
446 __quota_oldfiles_put(struct quotahandle *qh, const struct quotakey *qk,
447 		     const struct quotaval *qv)
448 {
449 	return __quota_oldfiles_doput(qh, qk, qv);
450 }
451 
452 int
453 __quota_oldfiles_delete(struct quotahandle *qh, const struct quotakey *qk)
454 {
455 	struct quotaval qv;
456 
457 	quotaval_clear(&qv);
458 	return __quota_oldfiles_doput(qh, qk, &qv);
459 }
460 
461 struct oldfiles_quotacursor *
462 __quota_oldfiles_cursor_create(struct quotahandle *qh)
463 {
464 	struct oldfiles_quotacursor *oqc;
465 	struct stat st;
466 	int serrno;
467 
468 	oqc = malloc(sizeof(*oqc));
469 	if (oqc == NULL) {
470 		return NULL;
471 	}
472 
473 	oqc->oqc_didusers = 0;
474 	oqc->oqc_didgroups = 0;
475 	oqc->oqc_diddefault = 0;
476 	oqc->oqc_pos = 0;
477 	oqc->oqc_didblocks = 0;
478 
479 	if (qh->qh_userfile >= 0) {
480 		oqc->oqc_doingusers = 1;
481 	} else {
482 		oqc->oqc_doingusers = 0;
483 		oqc->oqc_didusers = 1;
484 	}
485 
486 	if (qh->qh_groupfile >= 0) {
487 		oqc->oqc_doinggroups = 1;
488 	} else {
489 		oqc->oqc_doinggroups = 0;
490 		oqc->oqc_didgroups = 1;
491 	}
492 
493 	if (fstat(qh->qh_userfile, &st) < 0) {
494 		serrno = errno;
495 		free(oqc);
496 		errno = serrno;
497 		return NULL;
498 	}
499 	oqc->oqc_numusers = st.st_size / sizeof(struct dqblk);
500 
501 	if (fstat(qh->qh_groupfile, &st) < 0) {
502 		serrno = errno;
503 		free(oqc);
504 		errno = serrno;
505 		return NULL;
506 	}
507 	oqc->oqc_numgroups = st.st_size / sizeof(struct dqblk);
508 
509 	return oqc;
510 }
511 
512 void
513 __quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
514 {
515 	free(oqc);
516 }
517 
518 int
519 __quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor *oqc,
520 				   unsigned idtype)
521 {
522 	switch (idtype) {
523 	    case QUOTA_IDTYPE_USER:
524 		oqc->oqc_doingusers = 0;
525 		oqc->oqc_didusers = 1;
526 		break;
527 	    case QUOTA_IDTYPE_GROUP:
528 		oqc->oqc_doinggroups = 0;
529 		oqc->oqc_didgroups = 1;
530 		break;
531 	    default:
532 		errno = EINVAL;
533 		return -1;
534 	}
535 	return 0;
536 }
537 
538 int
539 __quota_oldfiles_cursor_get(struct quotahandle *qh,
540 			    struct oldfiles_quotacursor *oqc,
541 			    struct quotakey *key, struct quotaval *val)
542 {
543 	unsigned maxpos;
544 	int isallzero;
545 
546 	/* in case one of the sizes is zero */
547 	if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
548 		oqc->oqc_didusers = 1;
549 	}
550 	if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
551 		oqc->oqc_didgroups = 1;
552 	}
553 
554  again:
555 	/*
556 	 * Figure out what to get
557 	 */
558 
559 	if (!oqc->oqc_didusers) {
560 		key->qk_idtype = QUOTA_IDTYPE_USER;
561 		maxpos = oqc->oqc_numusers;
562 	} else if (!oqc->oqc_didgroups) {
563 		key->qk_idtype = QUOTA_IDTYPE_GROUP;
564 		maxpos = oqc->oqc_numgroups;
565 	} else {
566 		errno = ENOENT;
567 		return -1;
568 	}
569 
570 	if (!oqc->oqc_diddefault) {
571 		key->qk_id = QUOTA_DEFAULTID;
572 	} else {
573 		key->qk_id = oqc->oqc_pos;
574 	}
575 
576 	if (!oqc->oqc_didblocks) {
577 		key->qk_objtype = QUOTA_OBJTYPE_BLOCKS;
578 	} else {
579 		key->qk_objtype = QUOTA_OBJTYPE_FILES;
580 	}
581 
582 	/*
583 	 * Get it
584 	 */
585 
586 	if (__quota_oldfiles_doget(qh, key, val, &isallzero)) {
587 		return -1;
588 	}
589 
590 	/*
591 	 * Advance the cursor
592 	 */
593 	if (!oqc->oqc_didblocks) {
594 		oqc->oqc_didblocks = 1;
595 	} else {
596 		oqc->oqc_didblocks = 0;
597 		if (!oqc->oqc_diddefault) {
598 			oqc->oqc_diddefault = 1;
599 		} else {
600 			oqc->oqc_pos++;
601 			if (oqc->oqc_pos >= maxpos) {
602 				oqc->oqc_pos = 0;
603 				oqc->oqc_diddefault = 0;
604 				if (!oqc->oqc_didusers) {
605 					oqc->oqc_didusers = 1;
606 				} else {
607 					oqc->oqc_didgroups = 1;
608 				}
609 			}
610 		}
611 	}
612 
613 	/*
614 	 * If we got an all-zero dqblk (e.g. from the middle of a hole
615 	 * in the quota file) don't bother returning it to the caller.
616 	 *
617 	 * ...unless we're at the end of the data, to avoid going past
618 	 * the end and generating a spurious failure. There's no
619 	 * reasonable way to make _atend detect empty entries at the
620 	 * end of the quota files.
621 	 */
622 	if (isallzero && (!oqc->oqc_didusers || !oqc->oqc_didgroups)) {
623 		goto again;
624 	}
625 	return 0;
626 }
627 
628 int
629 __quota_oldfiles_cursor_getn(struct quotahandle *qh,
630 			     struct oldfiles_quotacursor *oqc,
631 			     struct quotakey *keys, struct quotaval *vals,
632 			     unsigned maxnum)
633 {
634 	unsigned i;
635 
636 	if (maxnum > INT_MAX) {
637 		/* joker, eh? */
638 		errno = EINVAL;
639 		return -1;
640 	}
641 
642 	for (i=0; i<maxnum; i++) {
643 		if (__quota_oldfiles_cursor_atend(oqc)) {
644 			break;
645 		}
646 		if (__quota_oldfiles_cursor_get(qh, oqc, &keys[i], &vals[i])) {
647 			if (i > 0) {
648 				/*
649 				 * Succeed witih what we have so far;
650 				 * the next attempt will hit the same
651 				 * error again.
652 				 */
653 				break;
654 			}
655 			return -1;
656 		}
657 	}
658 	return i;
659 
660 }
661 
662 int
663 __quota_oldfiles_cursor_atend(struct oldfiles_quotacursor *oqc)
664 {
665 	/* in case one of the sizes is zero */
666 	if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
667 		oqc->oqc_didusers = 1;
668 	}
669 	if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
670 		oqc->oqc_didgroups = 1;
671 	}
672 
673 	return oqc->oqc_didusers && oqc->oqc_didgroups;
674 }
675 
676 int
677 __quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor *oqc)
678 {
679 	oqc->oqc_didusers = 0;
680 	oqc->oqc_didgroups = 0;
681 	oqc->oqc_diddefault = 0;
682 	oqc->oqc_pos = 0;
683 	oqc->oqc_didblocks = 0;
684 	return 0;
685 }
686