1/*	$NetBSD$	*/
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$");
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
55struct oldfiles_fstabentry {
56	char *ofe_mountpoint;
57	int ofe_hasuserquota;
58	int ofe_hasgroupquota;
59	char *ofe_userquotafile;
60	char *ofe_groupquotafile;
61};
62
63struct 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
77static struct oldfiles_fstabentry *__quota_oldfiles_fstab;
78static unsigned __quota_oldfiles_numfstab;
79static unsigned __quota_oldfiles_maxfstab;
80static int __quota_oldfiles_fstab_loaded;
81
82static const struct oldfiles_fstabentry *
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
96static int
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
121static int
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
181fail:
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
196void
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;
239failed:
240	warn("Failed reading fstab");
241	return;
242}
243
244int
245__quota_oldfiles_infstab(const char *mountpoint)
246{
247	return __quota_oldfiles_find_fstabentry(mountpoint) != NULL;
248}
249
250static void
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[USRQUOTA]);
259}
260
261const char *
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
301static uint64_t
302dqblk_getlimit(uint32_t val)
303{
304	if (val == 0) {
305		return QUOTA_NOLIMIT;
306	} else {
307		return val - 1;
308	}
309}
310
311static uint32_t
312dqblk_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
321static void
322dqblk_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
331static void
332dqblk_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
341static void
342dqblk_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
351static void
352dqblk_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
361static int
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
377int
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
434const char *
435__quota_oldfiles_getimplname(struct quotahandle *qh)
436{
437	return "ufs/ffs quota v1 file access";
438}
439
440int
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
479static int
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
565static int
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
677int
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
684int
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
691int
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
700struct oldfiles_quotacursor *
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
753void
754__quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
755{
756	free(oqc);
757}
758
759int
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
779int
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
869int
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
903int
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
917int
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