xref: /netbsd/lib/libquota/quota_oldfiles.c (revision 5c1b183c)
1 /*	$NetBSD: quota_oldfiles.c,v 1.10 2022/04/26 15:36:42 hannken 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/cdefs.h>
36 __RCSID("$NetBSD: quota_oldfiles.c,v 1.10 2022/04/26 15:36:42 hannken Exp $");
37 
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <fcntl.h>
45 #include <limits.h>
46 #include <fstab.h>
47 #include <errno.h>
48 #include <err.h>
49 
50 #include <ufs/ufs/quota1.h>
51 
52 #include <quota.h>
53 #include "quotapvt.h"
54 
55 struct oldfiles_fstabentry {
56 	char *ofe_mountpoint;
57 	int ofe_hasuserquota;
58 	int ofe_hasgroupquota;
59 	char *ofe_userquotafile;
60 	char *ofe_groupquotafile;
61 };
62 
63 struct oldfiles_quotacursor {
64 	unsigned oqc_doingusers;
65 	unsigned oqc_doinggroups;
66 
67 	unsigned oqc_numusers;
68 	unsigned oqc_numgroups;
69 
70 	unsigned oqc_didusers;
71 	unsigned oqc_didgroups;
72 	unsigned oqc_diddefault;
73 	unsigned oqc_pos;
74 	unsigned oqc_didblocks;
75 };
76 
77 static struct oldfiles_fstabentry *__quota_oldfiles_fstab;
78 static unsigned __quota_oldfiles_numfstab;
79 static unsigned __quota_oldfiles_maxfstab;
80 static int __quota_oldfiles_fstab_loaded;
81 
82 static const struct oldfiles_fstabentry *
__quota_oldfiles_find_fstabentry(const char * mountpoint)83 __quota_oldfiles_find_fstabentry(const char *mountpoint)
84 {
85 	unsigned i;
86 
87 	for (i = 0; i < __quota_oldfiles_numfstab; i++) {
88 		if (!strcmp(mountpoint,
89 			    __quota_oldfiles_fstab[i].ofe_mountpoint)) {
90 			return &__quota_oldfiles_fstab[i];
91 		}
92 	}
93 	return NULL;
94 }
95 
96 static int
__quota_oldfiles_add_fstabentry(struct oldfiles_fstabentry * ofe)97 __quota_oldfiles_add_fstabentry(struct oldfiles_fstabentry *ofe)
98 {
99 	unsigned newmax;
100 	struct oldfiles_fstabentry *newptr;
101 
102 	if (__quota_oldfiles_numfstab + 1 >= __quota_oldfiles_maxfstab) {
103 		if (__quota_oldfiles_maxfstab == 0) {
104 			newmax = 4;
105 		} else {
106 			newmax = __quota_oldfiles_maxfstab * 2;
107 		}
108 		newptr = realloc(__quota_oldfiles_fstab,
109 				 newmax * sizeof(__quota_oldfiles_fstab[0]));
110 		if (newptr == NULL) {
111 			return -1;
112 		}
113 		__quota_oldfiles_maxfstab = newmax;
114 		__quota_oldfiles_fstab = newptr;
115 	}
116 
117 	__quota_oldfiles_fstab[__quota_oldfiles_numfstab++] = *ofe;
118 	return 0;
119 }
120 
121 static int
__quota_oldfiles_fill_fstabentry(const struct fstab * fs,struct oldfiles_fstabentry * ofe)122 __quota_oldfiles_fill_fstabentry(const struct fstab *fs,
123 				 struct oldfiles_fstabentry *ofe)
124 {
125 	char buf[256];
126 	char *opt, *state, *s;
127 	int serrno;
128 	int ret = 0;
129 
130 	/*
131 	 * Inspect the mount options to find the quota files.
132 	 * XXX this info should be gotten from the kernel.
133 	 *
134 	 * The options are:
135 	 *    userquota[=path]          enable user quotas
136 	 *    groupquota[=path]         enable group quotas
137 	 */
138 
139 	ofe->ofe_mountpoint = NULL;
140 	ofe->ofe_hasuserquota = ofe->ofe_hasgroupquota = 0;
141 	ofe->ofe_userquotafile = ofe->ofe_groupquotafile = NULL;
142 
143 	strlcpy(buf, fs->fs_mntops, sizeof(buf));
144 	for (opt = strtok_r(buf, ",", &state);
145 	     opt != NULL;
146 	     opt = strtok_r(NULL, ",", &state)) {
147 		s = strchr(opt, '=');
148 		if (s != NULL) {
149 			*(s++) = '\0';
150 		}
151 		if (!strcmp(opt, "userquota")) {
152 			ret = 1;
153 			ofe->ofe_hasuserquota = 1;
154 			if (s != NULL) {
155 				ofe->ofe_userquotafile = strdup(s);
156 				if (ofe->ofe_userquotafile == NULL) {
157 					goto fail;
158 				}
159 			}
160 		} else if (!strcmp(opt, "groupquota")) {
161 			ret = 1;
162 			ofe->ofe_hasgroupquota = 1;
163 			if (s != NULL) {
164 				ofe->ofe_groupquotafile = strdup(s);
165 				if (ofe->ofe_groupquotafile == NULL) {
166 					goto fail;
167 				}
168 			}
169 		}
170 	}
171 
172 	if (ret == 1) {
173 		ofe->ofe_mountpoint = strdup(fs->fs_file);
174 		if (ofe->ofe_mountpoint == NULL) {
175 			goto fail;
176 		}
177 	}
178 
179 	return ret;
180 
181 fail:
182 	serrno = errno;
183 	if (ofe->ofe_mountpoint != NULL) {
184 		free(ofe->ofe_mountpoint);
185 	}
186 	if (ofe->ofe_groupquotafile != NULL) {
187 		free(ofe->ofe_groupquotafile);
188 	}
189 	if (ofe->ofe_userquotafile != NULL) {
190 		free(ofe->ofe_userquotafile);
191 	}
192 	errno = serrno;
193 	return -1;
194 }
195 
196 void
__quota_oldfiles_load_fstab(void)197 __quota_oldfiles_load_fstab(void)
198 {
199 	struct oldfiles_fstabentry ofe;
200 	struct fstab *fs;
201 	int result;
202 
203 	if (__quota_oldfiles_fstab_loaded) {
204 		return;
205 	}
206 
207 	/*
208 	 * Check if fstab file exists before trying to parse it.
209 	 * Avoid warnings from {get,set}fsent() if missing.
210 	 */
211 	if (access(_PATH_FSTAB, F_OK) == -1 && errno == ENOENT)
212 		return;
213 
214 	/*
215 	 * XXX: should be able to handle ext2fs quota1 files too
216 	 *
217 	 * XXX: should use getfsent_r(), but there isn't one.
218 	 */
219 	setfsent();
220 	while ((fs = getfsent()) != NULL) {
221 		if (!strcmp(fs->fs_vfstype, "ffs") ||
222 		    !strcmp(fs->fs_vfstype, "lfs")) {
223 			result = __quota_oldfiles_fill_fstabentry(fs, &ofe);
224 			if (result == -1) {
225 				goto failed;
226 			}
227 			if (result == 0) {
228 				continue;
229 			}
230 			if (__quota_oldfiles_add_fstabentry(&ofe)) {
231 				goto failed;
232 			}
233 		}
234 	}
235 	endfsent();
236 	__quota_oldfiles_fstab_loaded = 1;
237 
238 	return;
239 failed:
240 	warn("Failed reading fstab");
241 	return;
242 }
243 
244 int
__quota_oldfiles_infstab(const char * mountpoint)245 __quota_oldfiles_infstab(const char *mountpoint)
246 {
247 	return __quota_oldfiles_find_fstabentry(mountpoint) != NULL;
248 }
249 
250 static void
__quota_oldfiles_defquotafile(struct quotahandle * qh,int idtype,char * buf,size_t maxlen)251 __quota_oldfiles_defquotafile(struct quotahandle *qh, int idtype,
252 			      char *buf, size_t maxlen)
253 {
254 	static const char *const names[] = INITQFNAMES;
255 
256 	(void)snprintf(buf, maxlen, "%s/%s.%s",
257 		       qh->qh_mountpoint,
258 		       QUOTAFILENAME, names[idtype]);
259 }
260 
261 const char *
__quota_oldfiles_getquotafile(struct quotahandle * qh,int idtype,char * buf,size_t maxlen)262 __quota_oldfiles_getquotafile(struct quotahandle *qh, int idtype,
263 			      char *buf, size_t maxlen)
264 {
265 	const struct oldfiles_fstabentry *ofe;
266 	const char *file;
267 
268 	ofe = __quota_oldfiles_find_fstabentry(qh->qh_mountpoint);
269 	if (ofe == NULL) {
270 		errno = ENXIO;
271 		return NULL;
272 	}
273 
274 	switch (idtype) {
275 	    case USRQUOTA:
276 		if (!ofe->ofe_hasuserquota) {
277 			errno = ENXIO;
278 			return NULL;
279 		}
280 		file = ofe->ofe_userquotafile;
281 		break;
282 	    case GRPQUOTA:
283 		if (!ofe->ofe_hasgroupquota) {
284 			errno = ENXIO;
285 			return NULL;
286 		}
287 		file = ofe->ofe_groupquotafile;
288 		break;
289 	    default:
290 		errno = EINVAL;
291 		return NULL;
292 	}
293 
294 	if (file == NULL) {
295 		__quota_oldfiles_defquotafile(qh, idtype, buf, maxlen);
296 		file = buf;
297 	}
298 	return file;
299 }
300 
301 static uint64_t
dqblk_getlimit(uint32_t val)302 dqblk_getlimit(uint32_t val)
303 {
304 	if (val == 0) {
305 		return QUOTA_NOLIMIT;
306 	} else {
307 		return val - 1;
308 	}
309 }
310 
311 static uint32_t
dqblk_setlimit(uint64_t val)312 dqblk_setlimit(uint64_t val)
313 {
314 	if (val == QUOTA_NOLIMIT && val >= 0xffffffffUL) {
315 		return 0;
316 	} else {
317 		return (uint32_t)val + 1;
318 	}
319 }
320 
321 static void
dqblk_getblocks(const struct dqblk * dq,struct quotaval * qv)322 dqblk_getblocks(const struct dqblk *dq, struct quotaval *qv)
323 {
324 	qv->qv_hardlimit = dqblk_getlimit(dq->dqb_bhardlimit);
325 	qv->qv_softlimit = dqblk_getlimit(dq->dqb_bsoftlimit);
326 	qv->qv_usage = dq->dqb_curblocks;
327 	qv->qv_expiretime = dq->dqb_btime;
328 	qv->qv_grace = QUOTA_NOTIME;
329 }
330 
331 static void
dqblk_getfiles(const struct dqblk * dq,struct quotaval * qv)332 dqblk_getfiles(const struct dqblk *dq, struct quotaval *qv)
333 {
334 	qv->qv_hardlimit = dqblk_getlimit(dq->dqb_ihardlimit);
335 	qv->qv_softlimit = dqblk_getlimit(dq->dqb_isoftlimit);
336 	qv->qv_usage = dq->dqb_curinodes;
337 	qv->qv_expiretime = dq->dqb_itime;
338 	qv->qv_grace = QUOTA_NOTIME;
339 }
340 
341 static void
dqblk_putblocks(const struct quotaval * qv,struct dqblk * dq)342 dqblk_putblocks(const struct quotaval *qv, struct dqblk *dq)
343 {
344 	dq->dqb_bhardlimit = dqblk_setlimit(qv->qv_hardlimit);
345 	dq->dqb_bsoftlimit = dqblk_setlimit(qv->qv_softlimit);
346 	dq->dqb_curblocks = qv->qv_usage;
347 	dq->dqb_btime = qv->qv_expiretime;
348 	/* ignore qv->qv_grace */
349 }
350 
351 static void
dqblk_putfiles(const struct quotaval * qv,struct dqblk * dq)352 dqblk_putfiles(const struct quotaval *qv, struct dqblk *dq)
353 {
354 	dq->dqb_ihardlimit = dqblk_setlimit(qv->qv_hardlimit);
355 	dq->dqb_isoftlimit = dqblk_setlimit(qv->qv_softlimit);
356 	dq->dqb_curinodes = qv->qv_usage;
357 	dq->dqb_itime = qv->qv_expiretime;
358 	/* ignore qv->qv_grace */
359 }
360 
361 static int
__quota_oldfiles_open(struct quotahandle * qh,const char * path,int * fd_ret)362 __quota_oldfiles_open(struct quotahandle *qh, const char *path, int *fd_ret)
363 {
364 	int fd;
365 
366 	fd = open(path, O_RDWR);
367 	if (fd < 0 && (errno == EACCES || errno == EROFS)) {
368 		fd = open(path, O_RDONLY);
369 		if (fd < 0) {
370 			return -1;
371 		}
372 	}
373 	*fd_ret = fd;
374 	return 0;
375 }
376 
377 int
__quota_oldfiles_initialize(struct quotahandle * qh)378 __quota_oldfiles_initialize(struct quotahandle *qh)
379 {
380 	const struct oldfiles_fstabentry *ofe;
381 	char path[PATH_MAX];
382 	const char *userquotafile, *groupquotafile;
383 
384 	if (qh->qh_oldfilesopen) {
385 		/* already initialized */
386 		return 0;
387 	}
388 
389 	/*
390 	 * Find the fstab entry.
391 	 */
392 	ofe = __quota_oldfiles_find_fstabentry(qh->qh_mountpoint);
393 	if (ofe == NULL) {
394 		warnx("%s not found in fstab", qh->qh_mountpoint);
395 		errno = ENXIO;
396 		return -1;
397 	}
398 
399 	if (!ofe->ofe_hasuserquota && !ofe->ofe_hasgroupquota) {
400 		errno = ENXIO;
401 		return -1;
402 	}
403 
404 	if (ofe->ofe_hasuserquota) {
405 		userquotafile = ofe->ofe_userquotafile;
406 		if (userquotafile == NULL) {
407 			__quota_oldfiles_defquotafile(qh, USRQUOTA,
408 						      path, sizeof(path));
409 			userquotafile = path;
410 		}
411 		if (__quota_oldfiles_open(qh, userquotafile,
412 					  &qh->qh_userfile)) {
413 			return -1;
414 		}
415 	}
416 	if (ofe->ofe_hasgroupquota) {
417 		groupquotafile = ofe->ofe_groupquotafile;
418 		if (groupquotafile == NULL) {
419 			__quota_oldfiles_defquotafile(qh, GRPQUOTA,
420 						      path, sizeof(path));
421 			groupquotafile = path;
422 		}
423 		if (__quota_oldfiles_open(qh, groupquotafile,
424 					  &qh->qh_groupfile)) {
425 			return -1;
426 		}
427 	}
428 
429 	qh->qh_oldfilesopen = 1;
430 
431 	return 0;
432 }
433 
434 const char *
__quota_oldfiles_getimplname(struct quotahandle * qh)435 __quota_oldfiles_getimplname(struct quotahandle *qh)
436 {
437 	return "ufs/ffs quota v1 file access";
438 }
439 
440 int
__quota_oldfiles_quotaon(struct quotahandle * qh,int idtype)441 __quota_oldfiles_quotaon(struct quotahandle *qh, int idtype)
442 {
443 	int result;
444 
445 	/*
446 	 * If we have the quota files open, close them.
447 	 */
448 
449 	if (qh->qh_oldfilesopen) {
450 		if (qh->qh_userfile >= 0) {
451 			close(qh->qh_userfile);
452 			qh->qh_userfile = -1;
453 		}
454 		if (qh->qh_groupfile >= 0) {
455 			close(qh->qh_groupfile);
456 			qh->qh_groupfile = -1;
457 		}
458 		qh->qh_oldfilesopen = 0;
459 	}
460 
461 	/*
462 	 * Go over to the syscall interface.
463 	 */
464 
465 	result = __quota_kernel_quotaon(qh, idtype);
466 	if (result < 0) {
467 		return -1;
468 	}
469 
470 	/*
471 	 * We succeeded, so all further access should be via the
472 	 * kernel.
473 	 */
474 
475 	qh->qh_mode = QUOTA_MODE_KERNEL;
476 	return 0;
477 }
478 
479 static int
__quota_oldfiles_doget(struct quotahandle * qh,const struct quotakey * qk,struct quotaval * qv,int * isallzero)480 __quota_oldfiles_doget(struct quotahandle *qh, const struct quotakey *qk,
481 		       struct quotaval *qv, int *isallzero)
482 {
483 	int file;
484 	off_t pos;
485 	struct dqblk dq;
486 	ssize_t result;
487 
488 	if (!qh->qh_oldfilesopen) {
489 		if (__quota_oldfiles_initialize(qh)) {
490 			return -1;
491 		}
492 	}
493 
494 	switch (qk->qk_idtype) {
495 	    case QUOTA_IDTYPE_USER:
496 		file = qh->qh_userfile;
497 		break;
498 	    case QUOTA_IDTYPE_GROUP:
499 		file = qh->qh_groupfile;
500 		break;
501 	    default:
502 		errno = EINVAL;
503 		return -1;
504 	}
505 
506 	if (qk->qk_id == QUOTA_DEFAULTID) {
507 		pos = 0;
508 	} else {
509 		pos = qk->qk_id * sizeof(struct dqblk);
510 	}
511 
512 	result = pread(file, &dq, sizeof(dq), pos);
513 	if (result < 0) {
514 		return -1;
515 	} else if (result == 0) {
516 		/* Past EOF; no quota info on file for this ID */
517 		errno = ENOENT;
518 		return -1;
519 	} else if ((size_t)result != sizeof(dq)) {
520 		errno = EFTYPE;
521 		return -1;
522 	}
523 
524 	switch (qk->qk_objtype) {
525 	    case QUOTA_OBJTYPE_BLOCKS:
526 		dqblk_getblocks(&dq, qv);
527 		break;
528 	    case QUOTA_OBJTYPE_FILES:
529 		dqblk_getfiles(&dq, qv);
530 		break;
531 	    default:
532 		errno = EINVAL;
533 		return -1;
534 	}
535 
536 	if (qk->qk_id == QUOTA_DEFAULTID) {
537 		qv->qv_usage = 0;
538 		qv->qv_grace = qv->qv_expiretime;
539 		qv->qv_expiretime = QUOTA_NOTIME;
540 	} else if (qk->qk_id == 0) {
541 		qv->qv_hardlimit = 0;
542 		qv->qv_softlimit = 0;
543 		qv->qv_expiretime = QUOTA_NOTIME;
544 		qv->qv_grace = QUOTA_NOTIME;
545 	}
546 
547 	if (isallzero != NULL) {
548 		if (dq.dqb_bhardlimit == 0 &&
549 		    dq.dqb_bsoftlimit == 0 &&
550 		    dq.dqb_curblocks == 0 &&
551 		    dq.dqb_ihardlimit == 0 &&
552 		    dq.dqb_isoftlimit == 0 &&
553 		    dq.dqb_curinodes == 0 &&
554 		    dq.dqb_btime == 0 &&
555 		    dq.dqb_itime == 0) {
556 			*isallzero = 1;
557 		} else {
558 			*isallzero = 0;
559 		}
560 	}
561 
562 	return 0;
563 }
564 
565 static int
__quota_oldfiles_doput(struct quotahandle * qh,const struct quotakey * qk,const struct quotaval * qv)566 __quota_oldfiles_doput(struct quotahandle *qh, const struct quotakey *qk,
567 		       const struct quotaval *qv)
568 {
569 	int file;
570 	off_t pos;
571 	struct quotaval qv2;
572 	struct dqblk dq;
573 	ssize_t result;
574 
575 	if (!qh->qh_oldfilesopen) {
576 		if (__quota_oldfiles_initialize(qh)) {
577 			return -1;
578 		}
579 	}
580 
581 	switch (qk->qk_idtype) {
582 	    case QUOTA_IDTYPE_USER:
583 		file = qh->qh_userfile;
584 		break;
585 	    case QUOTA_IDTYPE_GROUP:
586 		file = qh->qh_groupfile;
587 		break;
588 	    default:
589 		errno = EINVAL;
590 		return -1;
591 	}
592 
593 	if (qk->qk_id == QUOTA_DEFAULTID) {
594 		pos = 0;
595 	} else {
596 		pos = qk->qk_id * sizeof(struct dqblk);
597 	}
598 
599 	result = pread(file, &dq, sizeof(dq), pos);
600 	if (result < 0) {
601 		return -1;
602 	} else if (result == 0) {
603 		/* Past EOF; fill in a blank dq to start from */
604 		dq.dqb_bhardlimit = 0;
605 		dq.dqb_bsoftlimit = 0;
606 		dq.dqb_curblocks = 0;
607 		dq.dqb_ihardlimit = 0;
608 		dq.dqb_isoftlimit = 0;
609 		dq.dqb_curinodes = 0;
610 		dq.dqb_btime = 0;
611 		dq.dqb_itime = 0;
612 	} else if ((size_t)result != sizeof(dq)) {
613 		errno = EFTYPE;
614 		return -1;
615 	}
616 
617 	switch (qk->qk_objtype) {
618 	    case QUOTA_OBJTYPE_BLOCKS:
619 		dqblk_getblocks(&dq, &qv2);
620 		break;
621 	    case QUOTA_OBJTYPE_FILES:
622 		dqblk_getfiles(&dq, &qv2);
623 		break;
624 	    default:
625 		errno = EINVAL;
626 		return -1;
627 	}
628 
629 	if (qk->qk_id == QUOTA_DEFAULTID) {
630 		qv2.qv_hardlimit = qv->qv_hardlimit;
631 		qv2.qv_softlimit = qv->qv_softlimit;
632 		/* leave qv2.qv_usage unchanged */
633 		qv2.qv_expiretime = qv->qv_grace;
634 		/* skip qv2.qv_grace */
635 
636 		/* ignore qv->qv_usage */
637 		/* ignore qv->qv_expiretime */
638 	} else if (qk->qk_id == 0) {
639 		/* leave qv2.qv_hardlimit unchanged */
640 		/* leave qv2.qv_softlimit unchanged */
641 		qv2.qv_usage = qv->qv_usage;
642 		/* leave qv2.qv_expiretime unchanged */
643 		/* skip qv2.qv_grace */
644 
645 		/* ignore qv->qv_hardlimit */
646 		/* ignore qv->qv_softlimit */
647 		/* ignore qv->qv_expiretime */
648 		/* ignore qv->qv_grace */
649 	} else {
650 		qv2 = *qv;
651 	}
652 
653 	switch (qk->qk_objtype) {
654 	    case QUOTA_OBJTYPE_BLOCKS:
655 		dqblk_putblocks(&qv2, &dq);
656 		break;
657 	    case QUOTA_OBJTYPE_FILES:
658 		dqblk_putfiles(&qv2, &dq);
659 		break;
660 	    default:
661 		errno = EINVAL;
662 		return -1;
663 	}
664 
665 	result = pwrite(file, &dq, sizeof(dq), pos);
666 	if (result < 0) {
667 		return -1;
668 	} else if ((size_t)result != sizeof(dq)) {
669 		/* ? */
670 		errno = EFTYPE;
671 		return -1;
672 	}
673 
674 	return 0;
675 }
676 
677 int
__quota_oldfiles_get(struct quotahandle * qh,const struct quotakey * qk,struct quotaval * qv)678 __quota_oldfiles_get(struct quotahandle *qh, const struct quotakey *qk,
679 		     struct quotaval *qv)
680 {
681 	return __quota_oldfiles_doget(qh, qk, qv, NULL);
682 }
683 
684 int
__quota_oldfiles_put(struct quotahandle * qh,const struct quotakey * qk,const struct quotaval * qv)685 __quota_oldfiles_put(struct quotahandle *qh, const struct quotakey *qk,
686 		     const struct quotaval *qv)
687 {
688 	return __quota_oldfiles_doput(qh, qk, qv);
689 }
690 
691 int
__quota_oldfiles_delete(struct quotahandle * qh,const struct quotakey * qk)692 __quota_oldfiles_delete(struct quotahandle *qh, const struct quotakey *qk)
693 {
694 	struct quotaval qv;
695 
696 	quotaval_clear(&qv);
697 	return __quota_oldfiles_doput(qh, qk, &qv);
698 }
699 
700 struct oldfiles_quotacursor *
__quota_oldfiles_cursor_create(struct quotahandle * qh)701 __quota_oldfiles_cursor_create(struct quotahandle *qh)
702 {
703 	struct oldfiles_quotacursor *oqc;
704 	struct stat st;
705 	int serrno;
706 
707 	/* quota_opencursor calls initialize for us, no need to do it here */
708 
709 	oqc = malloc(sizeof(*oqc));
710 	if (oqc == NULL) {
711 		return NULL;
712 	}
713 
714 	oqc->oqc_didusers = 0;
715 	oqc->oqc_didgroups = 0;
716 	oqc->oqc_diddefault = 0;
717 	oqc->oqc_pos = 0;
718 	oqc->oqc_didblocks = 0;
719 
720 	if (qh->qh_userfile >= 0) {
721 		oqc->oqc_doingusers = 1;
722 	} else {
723 		oqc->oqc_doingusers = 0;
724 		oqc->oqc_didusers = 1;
725 	}
726 
727 	if (qh->qh_groupfile >= 0) {
728 		oqc->oqc_doinggroups = 1;
729 	} else {
730 		oqc->oqc_doinggroups = 0;
731 		oqc->oqc_didgroups = 1;
732 	}
733 
734 	if (fstat(qh->qh_userfile, &st) < 0) {
735 		serrno = errno;
736 		free(oqc);
737 		errno = serrno;
738 		return NULL;
739 	}
740 	oqc->oqc_numusers = st.st_size / sizeof(struct dqblk);
741 
742 	if (fstat(qh->qh_groupfile, &st) < 0) {
743 		serrno = errno;
744 		free(oqc);
745 		errno = serrno;
746 		return NULL;
747 	}
748 	oqc->oqc_numgroups = st.st_size / sizeof(struct dqblk);
749 
750 	return oqc;
751 }
752 
753 void
__quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor * oqc)754 __quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
755 {
756 	free(oqc);
757 }
758 
759 int
__quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor * oqc,int idtype)760 __quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor *oqc,
761 				   int idtype)
762 {
763 	switch (idtype) {
764 	    case QUOTA_IDTYPE_USER:
765 		oqc->oqc_doingusers = 0;
766 		oqc->oqc_didusers = 1;
767 		break;
768 	    case QUOTA_IDTYPE_GROUP:
769 		oqc->oqc_doinggroups = 0;
770 		oqc->oqc_didgroups = 1;
771 		break;
772 	    default:
773 		errno = EINVAL;
774 		return -1;
775 	}
776 	return 0;
777 }
778 
779 int
__quota_oldfiles_cursor_get(struct quotahandle * qh,struct oldfiles_quotacursor * oqc,struct quotakey * key,struct quotaval * val)780 __quota_oldfiles_cursor_get(struct quotahandle *qh,
781 			    struct oldfiles_quotacursor *oqc,
782 			    struct quotakey *key, struct quotaval *val)
783 {
784 	unsigned maxpos;
785 	int isallzero;
786 
787 	/* in case one of the sizes is zero */
788 	if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
789 		oqc->oqc_didusers = 1;
790 	}
791 	if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
792 		oqc->oqc_didgroups = 1;
793 	}
794 
795  again:
796 	/*
797 	 * Figure out what to get
798 	 */
799 
800 	if (!oqc->oqc_didusers) {
801 		key->qk_idtype = QUOTA_IDTYPE_USER;
802 		maxpos = oqc->oqc_numusers;
803 	} else if (!oqc->oqc_didgroups) {
804 		key->qk_idtype = QUOTA_IDTYPE_GROUP;
805 		maxpos = oqc->oqc_numgroups;
806 	} else {
807 		errno = ENOENT;
808 		return -1;
809 	}
810 
811 	if (!oqc->oqc_diddefault) {
812 		key->qk_id = QUOTA_DEFAULTID;
813 	} else {
814 		key->qk_id = oqc->oqc_pos;
815 	}
816 
817 	if (!oqc->oqc_didblocks) {
818 		key->qk_objtype = QUOTA_OBJTYPE_BLOCKS;
819 	} else {
820 		key->qk_objtype = QUOTA_OBJTYPE_FILES;
821 	}
822 
823 	/*
824 	 * Get it
825 	 */
826 
827 	if (__quota_oldfiles_doget(qh, key, val, &isallzero)) {
828 		return -1;
829 	}
830 
831 	/*
832 	 * Advance the cursor
833 	 */
834 	if (!oqc->oqc_didblocks) {
835 		oqc->oqc_didblocks = 1;
836 	} else {
837 		oqc->oqc_didblocks = 0;
838 		if (!oqc->oqc_diddefault) {
839 			oqc->oqc_diddefault = 1;
840 		} else {
841 			oqc->oqc_pos++;
842 			if (oqc->oqc_pos >= maxpos) {
843 				oqc->oqc_pos = 0;
844 				oqc->oqc_diddefault = 0;
845 				if (!oqc->oqc_didusers) {
846 					oqc->oqc_didusers = 1;
847 				} else {
848 					oqc->oqc_didgroups = 1;
849 				}
850 			}
851 		}
852 	}
853 
854 	/*
855 	 * If we got an all-zero dqblk (e.g. from the middle of a hole
856 	 * in the quota file) don't bother returning it to the caller.
857 	 *
858 	 * ...unless we're at the end of the data, to avoid going past
859 	 * the end and generating a spurious failure. There's no
860 	 * reasonable way to make _atend detect empty entries at the
861 	 * end of the quota files.
862 	 */
863 	if (isallzero && (!oqc->oqc_didusers || !oqc->oqc_didgroups)) {
864 		goto again;
865 	}
866 	return 0;
867 }
868 
869 int
__quota_oldfiles_cursor_getn(struct quotahandle * qh,struct oldfiles_quotacursor * oqc,struct quotakey * keys,struct quotaval * vals,unsigned maxnum)870 __quota_oldfiles_cursor_getn(struct quotahandle *qh,
871 			     struct oldfiles_quotacursor *oqc,
872 			     struct quotakey *keys, struct quotaval *vals,
873 			     unsigned maxnum)
874 {
875 	unsigned i;
876 
877 	if (maxnum > INT_MAX) {
878 		/* joker, eh? */
879 		errno = EINVAL;
880 		return -1;
881 	}
882 
883 	for (i=0; i<maxnum; i++) {
884 		if (__quota_oldfiles_cursor_atend(oqc)) {
885 			break;
886 		}
887 		if (__quota_oldfiles_cursor_get(qh, oqc, &keys[i], &vals[i])) {
888 			if (i > 0) {
889 				/*
890 				 * Succeed witih what we have so far;
891 				 * the next attempt will hit the same
892 				 * error again.
893 				 */
894 				break;
895 			}
896 			return -1;
897 		}
898 	}
899 	return i;
900 
901 }
902 
903 int
__quota_oldfiles_cursor_atend(struct oldfiles_quotacursor * oqc)904 __quota_oldfiles_cursor_atend(struct oldfiles_quotacursor *oqc)
905 {
906 	/* in case one of the sizes is zero */
907 	if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
908 		oqc->oqc_didusers = 1;
909 	}
910 	if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
911 		oqc->oqc_didgroups = 1;
912 	}
913 
914 	return oqc->oqc_didusers && oqc->oqc_didgroups;
915 }
916 
917 int
__quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor * oqc)918 __quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor *oqc)
919 {
920 	oqc->oqc_didusers = 0;
921 	oqc->oqc_didgroups = 0;
922 	oqc->oqc_diddefault = 0;
923 	oqc->oqc_pos = 0;
924 	oqc->oqc_didblocks = 0;
925 	return 0;
926 }
927