root/lib/libquota/quota_oldfiles.c
/*      $NetBSD: quota_oldfiles.c,v 1.10 2022/04/26 15:36:42 hannken Exp $      */

/*
 * Copyright (c) 1980, 1990, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Robert Elz at The University of Melbourne.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: quota_oldfiles.c,v 1.10 2022/04/26 15:36:42 hannken Exp $");

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <fstab.h>
#include <errno.h>
#include <err.h>

#include <ufs/ufs/quota1.h>

#include <quota.h>
#include "quotapvt.h"

struct oldfiles_fstabentry {
        char *ofe_mountpoint;
        int ofe_hasuserquota;
        int ofe_hasgroupquota;
        char *ofe_userquotafile;
        char *ofe_groupquotafile;
};

struct oldfiles_quotacursor {
        unsigned oqc_doingusers;
        unsigned oqc_doinggroups;

        unsigned oqc_numusers;
        unsigned oqc_numgroups;

        unsigned oqc_didusers;
        unsigned oqc_didgroups;
        unsigned oqc_diddefault;
        unsigned oqc_pos;
        unsigned oqc_didblocks;
};

static struct oldfiles_fstabentry *__quota_oldfiles_fstab;
static unsigned __quota_oldfiles_numfstab;
static unsigned __quota_oldfiles_maxfstab;
static int __quota_oldfiles_fstab_loaded;

static const struct oldfiles_fstabentry *
__quota_oldfiles_find_fstabentry(const char *mountpoint)
{
        unsigned i;

        for (i = 0; i < __quota_oldfiles_numfstab; i++) {
                if (!strcmp(mountpoint,
                            __quota_oldfiles_fstab[i].ofe_mountpoint)) {
                        return &__quota_oldfiles_fstab[i];
                }
        }
        return NULL;
}

static int
__quota_oldfiles_add_fstabentry(struct oldfiles_fstabentry *ofe)
{
        unsigned newmax;
        struct oldfiles_fstabentry *newptr;

        if (__quota_oldfiles_numfstab + 1 >= __quota_oldfiles_maxfstab) {
                if (__quota_oldfiles_maxfstab == 0) {
                        newmax = 4;
                } else {
                        newmax = __quota_oldfiles_maxfstab * 2;
                }
                newptr = realloc(__quota_oldfiles_fstab,
                                 newmax * sizeof(__quota_oldfiles_fstab[0]));
                if (newptr == NULL) {
                        return -1;
                }
                __quota_oldfiles_maxfstab = newmax;
                __quota_oldfiles_fstab = newptr;
        }

        __quota_oldfiles_fstab[__quota_oldfiles_numfstab++] = *ofe;
        return 0;
}

static int
__quota_oldfiles_fill_fstabentry(const struct fstab *fs,
                                 struct oldfiles_fstabentry *ofe)
{
        char buf[256];
        char *opt, *state, *s;
        int serrno;
        int ret = 0;

        /*
         * Inspect the mount options to find the quota files.
         * XXX this info should be gotten from the kernel.
         *
         * The options are:
         *    userquota[=path]          enable user quotas
         *    groupquota[=path]         enable group quotas
         */

        ofe->ofe_mountpoint = NULL;
        ofe->ofe_hasuserquota = ofe->ofe_hasgroupquota = 0;
        ofe->ofe_userquotafile = ofe->ofe_groupquotafile = NULL;

        strlcpy(buf, fs->fs_mntops, sizeof(buf));
        for (opt = strtok_r(buf, ",", &state);
             opt != NULL;
             opt = strtok_r(NULL, ",", &state)) {
                s = strchr(opt, '=');
                if (s != NULL) {
                        *(s++) = '\0';
                }
                if (!strcmp(opt, "userquota")) {
                        ret = 1;
                        ofe->ofe_hasuserquota = 1;
                        if (s != NULL) {
                                ofe->ofe_userquotafile = strdup(s);
                                if (ofe->ofe_userquotafile == NULL) {
                                        goto fail;
                                }
                        }
                } else if (!strcmp(opt, "groupquota")) {
                        ret = 1;
                        ofe->ofe_hasgroupquota = 1;
                        if (s != NULL) {
                                ofe->ofe_groupquotafile = strdup(s);
                                if (ofe->ofe_groupquotafile == NULL) {
                                        goto fail;
                                }
                        }
                }
        }

        if (ret == 1) {
                ofe->ofe_mountpoint = strdup(fs->fs_file);
                if (ofe->ofe_mountpoint == NULL) {
                        goto fail;
                }
        }

        return ret;

fail:
        serrno = errno;
        if (ofe->ofe_mountpoint != NULL) {
                free(ofe->ofe_mountpoint);
        }
        if (ofe->ofe_groupquotafile != NULL) {
                free(ofe->ofe_groupquotafile);
        }
        if (ofe->ofe_userquotafile != NULL) {
                free(ofe->ofe_userquotafile);
        }
        errno = serrno;
        return -1;
}

void
__quota_oldfiles_load_fstab(void)
{
        struct oldfiles_fstabentry ofe;
        struct fstab *fs;
        int result;

        if (__quota_oldfiles_fstab_loaded) {
                return;
        }

        /*
         * Check if fstab file exists before trying to parse it.
         * Avoid warnings from {get,set}fsent() if missing.
         */
        if (access(_PATH_FSTAB, F_OK) == -1 && errno == ENOENT)
                return;

        /*
         * XXX: should be able to handle ext2fs quota1 files too
         *
         * XXX: should use getfsent_r(), but there isn't one.
         */
        setfsent();
        while ((fs = getfsent()) != NULL) {
                if (!strcmp(fs->fs_vfstype, "ffs") ||
                    !strcmp(fs->fs_vfstype, "lfs")) {
                        result = __quota_oldfiles_fill_fstabentry(fs, &ofe);
                        if (result == -1) {
                                goto failed;
                        }
                        if (result == 0) {
                                continue;
                        }
                        if (__quota_oldfiles_add_fstabentry(&ofe)) {
                                goto failed;
                        }
                }
        }
        endfsent();
        __quota_oldfiles_fstab_loaded = 1;

        return;
failed:
        warn("Failed reading fstab");
        return;
}

int
__quota_oldfiles_infstab(const char *mountpoint)
{
        return __quota_oldfiles_find_fstabentry(mountpoint) != NULL;
}

static void
__quota_oldfiles_defquotafile(struct quotahandle *qh, int idtype,
                              char *buf, size_t maxlen)
{
        static const char *const names[] = INITQFNAMES;

        (void)snprintf(buf, maxlen, "%s/%s.%s",
                       qh->qh_mountpoint,
                       QUOTAFILENAME, names[idtype]);
}

const char *
__quota_oldfiles_getquotafile(struct quotahandle *qh, int idtype,
                              char *buf, size_t maxlen)
{
        const struct oldfiles_fstabentry *ofe;
        const char *file;

        ofe = __quota_oldfiles_find_fstabentry(qh->qh_mountpoint);
        if (ofe == NULL) {
                errno = ENXIO;
                return NULL;
        }

        switch (idtype) {
            case USRQUOTA:
                if (!ofe->ofe_hasuserquota) {
                        errno = ENXIO;
                        return NULL;
                }
                file = ofe->ofe_userquotafile;
                break;
            case GRPQUOTA:
                if (!ofe->ofe_hasgroupquota) {
                        errno = ENXIO;
                        return NULL;
                }
                file = ofe->ofe_groupquotafile;
                break;
            default:
                errno = EINVAL;
                return NULL;
        }

        if (file == NULL) {
                __quota_oldfiles_defquotafile(qh, idtype, buf, maxlen);
                file = buf;
        }
        return file;
}

static uint64_t
dqblk_getlimit(uint32_t val)
{
        if (val == 0) {
                return QUOTA_NOLIMIT;
        } else {
                return val - 1;
        }
}

static uint32_t
dqblk_setlimit(uint64_t val)
{
        if (val == QUOTA_NOLIMIT && val >= 0xffffffffUL) {
                return 0;
        } else {
                return (uint32_t)val + 1;
        }
}

static void
dqblk_getblocks(const struct dqblk *dq, struct quotaval *qv)
{
        qv->qv_hardlimit = dqblk_getlimit(dq->dqb_bhardlimit);
        qv->qv_softlimit = dqblk_getlimit(dq->dqb_bsoftlimit);
        qv->qv_usage = dq->dqb_curblocks;
        qv->qv_expiretime = dq->dqb_btime;
        qv->qv_grace = QUOTA_NOTIME;
}

static void
dqblk_getfiles(const struct dqblk *dq, struct quotaval *qv)
{
        qv->qv_hardlimit = dqblk_getlimit(dq->dqb_ihardlimit);
        qv->qv_softlimit = dqblk_getlimit(dq->dqb_isoftlimit);
        qv->qv_usage = dq->dqb_curinodes;
        qv->qv_expiretime = dq->dqb_itime;
        qv->qv_grace = QUOTA_NOTIME;
}

static void
dqblk_putblocks(const struct quotaval *qv, struct dqblk *dq)
{
        dq->dqb_bhardlimit = dqblk_setlimit(qv->qv_hardlimit);
        dq->dqb_bsoftlimit = dqblk_setlimit(qv->qv_softlimit);
        dq->dqb_curblocks = qv->qv_usage;
        dq->dqb_btime = qv->qv_expiretime;
        /* ignore qv->qv_grace */
}

static void
dqblk_putfiles(const struct quotaval *qv, struct dqblk *dq)
{
        dq->dqb_ihardlimit = dqblk_setlimit(qv->qv_hardlimit);
        dq->dqb_isoftlimit = dqblk_setlimit(qv->qv_softlimit);
        dq->dqb_curinodes = qv->qv_usage;
        dq->dqb_itime = qv->qv_expiretime;
        /* ignore qv->qv_grace */
}

static int
__quota_oldfiles_open(struct quotahandle *qh, const char *path, int *fd_ret)
{
        int fd;

        fd = open(path, O_RDWR);
        if (fd < 0 && (errno == EACCES || errno == EROFS)) {
                fd = open(path, O_RDONLY);
                if (fd < 0) {
                        return -1;
                }
        }
        *fd_ret = fd;
        return 0;
}

int
__quota_oldfiles_initialize(struct quotahandle *qh)
{
        const struct oldfiles_fstabentry *ofe;
        char path[PATH_MAX];
        const char *userquotafile, *groupquotafile;

        if (qh->qh_oldfilesopen) {
                /* already initialized */
                return 0;
        }

        /*
         * Find the fstab entry.
         */
        ofe = __quota_oldfiles_find_fstabentry(qh->qh_mountpoint);
        if (ofe == NULL) {
                warnx("%s not found in fstab", qh->qh_mountpoint);
                errno = ENXIO;
                return -1;
        }

        if (!ofe->ofe_hasuserquota && !ofe->ofe_hasgroupquota) {
                errno = ENXIO;
                return -1;
        }

        if (ofe->ofe_hasuserquota) {
                userquotafile = ofe->ofe_userquotafile;
                if (userquotafile == NULL) {
                        __quota_oldfiles_defquotafile(qh, USRQUOTA,
                                                      path, sizeof(path));
                        userquotafile = path;
                }
                if (__quota_oldfiles_open(qh, userquotafile,
                                          &qh->qh_userfile)) {
                        return -1;
                }
        }
        if (ofe->ofe_hasgroupquota) {
                groupquotafile = ofe->ofe_groupquotafile;
                if (groupquotafile == NULL) {
                        __quota_oldfiles_defquotafile(qh, GRPQUOTA,
                                                      path, sizeof(path));
                        groupquotafile = path;
                }
                if (__quota_oldfiles_open(qh, groupquotafile,
                                          &qh->qh_groupfile)) {
                        return -1;
                }
        }

        qh->qh_oldfilesopen = 1;

        return 0;
}

const char *
__quota_oldfiles_getimplname(struct quotahandle *qh)
{
        return "ufs/ffs quota v1 file access";
}

int
__quota_oldfiles_quotaon(struct quotahandle *qh, int idtype)
{
        int result;

        /*
         * If we have the quota files open, close them.
         */

        if (qh->qh_oldfilesopen) {
                if (qh->qh_userfile >= 0) {
                        close(qh->qh_userfile);
                        qh->qh_userfile = -1;
                }
                if (qh->qh_groupfile >= 0) {
                        close(qh->qh_groupfile);
                        qh->qh_groupfile = -1;
                }
                qh->qh_oldfilesopen = 0;
        }

        /*
         * Go over to the syscall interface.
         */

        result = __quota_kernel_quotaon(qh, idtype);
        if (result < 0) {
                return -1;
        }

        /*
         * We succeeded, so all further access should be via the
         * kernel.
         */

        qh->qh_mode = QUOTA_MODE_KERNEL;
        return 0;
}

static int
__quota_oldfiles_doget(struct quotahandle *qh, const struct quotakey *qk,
                       struct quotaval *qv, int *isallzero)
{
        int file;
        off_t pos;
        struct dqblk dq;
        ssize_t result;

        if (!qh->qh_oldfilesopen) {
                if (__quota_oldfiles_initialize(qh)) {
                        return -1;
                }
        }

        switch (qk->qk_idtype) {
            case QUOTA_IDTYPE_USER:
                file = qh->qh_userfile;
                break;
            case QUOTA_IDTYPE_GROUP:
                file = qh->qh_groupfile;
                break;
            default:
                errno = EINVAL;
                return -1;
        }

        if (qk->qk_id == QUOTA_DEFAULTID) {
                pos = 0;
        } else {
                pos = qk->qk_id * sizeof(struct dqblk);
        }

        result = pread(file, &dq, sizeof(dq), pos);
        if (result < 0) {
                return -1;
        } else if (result == 0) {
                /* Past EOF; no quota info on file for this ID */
                errno = ENOENT;
                return -1;
        } else if ((size_t)result != sizeof(dq)) {
                errno = EFTYPE;
                return -1;
        }

        switch (qk->qk_objtype) {
            case QUOTA_OBJTYPE_BLOCKS:
                dqblk_getblocks(&dq, qv);
                break;
            case QUOTA_OBJTYPE_FILES:
                dqblk_getfiles(&dq, qv);
                break;
            default:
                errno = EINVAL;
                return -1;
        }

        if (qk->qk_id == QUOTA_DEFAULTID) {
                qv->qv_usage = 0;
                qv->qv_grace = qv->qv_expiretime;
                qv->qv_expiretime = QUOTA_NOTIME;
        } else if (qk->qk_id == 0) {
                qv->qv_hardlimit = 0;
                qv->qv_softlimit = 0;
                qv->qv_expiretime = QUOTA_NOTIME;
                qv->qv_grace = QUOTA_NOTIME;
        }

        if (isallzero != NULL) {
                if (dq.dqb_bhardlimit == 0 &&
                    dq.dqb_bsoftlimit == 0 &&
                    dq.dqb_curblocks == 0 &&
                    dq.dqb_ihardlimit == 0 &&
                    dq.dqb_isoftlimit == 0 &&
                    dq.dqb_curinodes == 0 &&
                    dq.dqb_btime == 0 &&
                    dq.dqb_itime == 0) {
                        *isallzero = 1;
                } else {
                        *isallzero = 0;
                }
        }

        return 0;
}

static int
__quota_oldfiles_doput(struct quotahandle *qh, const struct quotakey *qk,
                       const struct quotaval *qv)
{
        int file;
        off_t pos;
        struct quotaval qv2;
        struct dqblk dq;
        ssize_t result;

        if (!qh->qh_oldfilesopen) {
                if (__quota_oldfiles_initialize(qh)) {
                        return -1;
                }
        }

        switch (qk->qk_idtype) {
            case QUOTA_IDTYPE_USER:
                file = qh->qh_userfile;
                break;
            case QUOTA_IDTYPE_GROUP:
                file = qh->qh_groupfile;
                break;
            default:
                errno = EINVAL;
                return -1;
        }

        if (qk->qk_id == QUOTA_DEFAULTID) {
                pos = 0;
        } else {
                pos = qk->qk_id * sizeof(struct dqblk);
        }

        result = pread(file, &dq, sizeof(dq), pos);
        if (result < 0) {
                return -1;
        } else if (result == 0) {
                /* Past EOF; fill in a blank dq to start from */
                dq.dqb_bhardlimit = 0;
                dq.dqb_bsoftlimit = 0;
                dq.dqb_curblocks = 0;
                dq.dqb_ihardlimit = 0;
                dq.dqb_isoftlimit = 0;
                dq.dqb_curinodes = 0;
                dq.dqb_btime = 0;
                dq.dqb_itime = 0;
        } else if ((size_t)result != sizeof(dq)) {
                errno = EFTYPE;
                return -1;
        }

        switch (qk->qk_objtype) {
            case QUOTA_OBJTYPE_BLOCKS:
                dqblk_getblocks(&dq, &qv2);
                break;
            case QUOTA_OBJTYPE_FILES:
                dqblk_getfiles(&dq, &qv2);
                break;
            default:
                errno = EINVAL;
                return -1;
        }

        if (qk->qk_id == QUOTA_DEFAULTID) {
                qv2.qv_hardlimit = qv->qv_hardlimit;
                qv2.qv_softlimit = qv->qv_softlimit;
                /* leave qv2.qv_usage unchanged */
                qv2.qv_expiretime = qv->qv_grace;
                /* skip qv2.qv_grace */

                /* ignore qv->qv_usage */
                /* ignore qv->qv_expiretime */
        } else if (qk->qk_id == 0) {
                /* leave qv2.qv_hardlimit unchanged */
                /* leave qv2.qv_softlimit unchanged */
                qv2.qv_usage = qv->qv_usage;
                /* leave qv2.qv_expiretime unchanged */
                /* skip qv2.qv_grace */

                /* ignore qv->qv_hardlimit */
                /* ignore qv->qv_softlimit */
                /* ignore qv->qv_expiretime */
                /* ignore qv->qv_grace */
        } else {
                qv2 = *qv;
        }

        switch (qk->qk_objtype) {
            case QUOTA_OBJTYPE_BLOCKS:
                dqblk_putblocks(&qv2, &dq);
                break;
            case QUOTA_OBJTYPE_FILES:
                dqblk_putfiles(&qv2, &dq);
                break;
            default:
                errno = EINVAL;
                return -1;
        }

        result = pwrite(file, &dq, sizeof(dq), pos);
        if (result < 0) {
                return -1;
        } else if ((size_t)result != sizeof(dq)) {
                /* ? */
                errno = EFTYPE;
                return -1;
        }

        return 0;
}

int
__quota_oldfiles_get(struct quotahandle *qh, const struct quotakey *qk,
                     struct quotaval *qv)
{
        return __quota_oldfiles_doget(qh, qk, qv, NULL);
}

int
__quota_oldfiles_put(struct quotahandle *qh, const struct quotakey *qk,
                     const struct quotaval *qv)
{
        return __quota_oldfiles_doput(qh, qk, qv);
}

int
__quota_oldfiles_delete(struct quotahandle *qh, const struct quotakey *qk)
{
        struct quotaval qv;

        quotaval_clear(&qv);
        return __quota_oldfiles_doput(qh, qk, &qv);
}

struct oldfiles_quotacursor *
__quota_oldfiles_cursor_create(struct quotahandle *qh)
{
        struct oldfiles_quotacursor *oqc;
        struct stat st;
        int serrno;

        /* quota_opencursor calls initialize for us, no need to do it here */

        oqc = malloc(sizeof(*oqc));
        if (oqc == NULL) {
                return NULL;
        }

        oqc->oqc_didusers = 0;
        oqc->oqc_didgroups = 0;
        oqc->oqc_diddefault = 0;
        oqc->oqc_pos = 0;
        oqc->oqc_didblocks = 0;

        if (qh->qh_userfile >= 0) {
                oqc->oqc_doingusers = 1;
        } else {
                oqc->oqc_doingusers = 0;
                oqc->oqc_didusers = 1;
        }

        if (qh->qh_groupfile >= 0) {
                oqc->oqc_doinggroups = 1;
        } else {
                oqc->oqc_doinggroups = 0;
                oqc->oqc_didgroups = 1;
        }

        if (fstat(qh->qh_userfile, &st) < 0) {
                serrno = errno;
                free(oqc);
                errno = serrno;
                return NULL;
        }
        oqc->oqc_numusers = st.st_size / sizeof(struct dqblk);

        if (fstat(qh->qh_groupfile, &st) < 0) {
                serrno = errno;
                free(oqc);
                errno = serrno;
                return NULL;
        }
        oqc->oqc_numgroups = st.st_size / sizeof(struct dqblk);

        return oqc;
}

void
__quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
{
        free(oqc);
}

int
__quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor *oqc,
                                   int idtype)
{
        switch (idtype) {
            case QUOTA_IDTYPE_USER:
                oqc->oqc_doingusers = 0;
                oqc->oqc_didusers = 1;
                break;
            case QUOTA_IDTYPE_GROUP:
                oqc->oqc_doinggroups = 0;
                oqc->oqc_didgroups = 1;
                break;
            default:
                errno = EINVAL;
                return -1;
        }
        return 0;
}

int
__quota_oldfiles_cursor_get(struct quotahandle *qh,
                            struct oldfiles_quotacursor *oqc,
                            struct quotakey *key, struct quotaval *val)
{
        unsigned maxpos;
        int isallzero;

        /* in case one of the sizes is zero */
        if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
                oqc->oqc_didusers = 1;
        }
        if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
                oqc->oqc_didgroups = 1;
        }

 again:
        /*
         * Figure out what to get
         */

        if (!oqc->oqc_didusers) {
                key->qk_idtype = QUOTA_IDTYPE_USER;
                maxpos = oqc->oqc_numusers;
        } else if (!oqc->oqc_didgroups) {
                key->qk_idtype = QUOTA_IDTYPE_GROUP;
                maxpos = oqc->oqc_numgroups;
        } else {
                errno = ENOENT;
                return -1;
        }

        if (!oqc->oqc_diddefault) {
                key->qk_id = QUOTA_DEFAULTID;
        } else {
                key->qk_id = oqc->oqc_pos;
        }

        if (!oqc->oqc_didblocks) {
                key->qk_objtype = QUOTA_OBJTYPE_BLOCKS;
        } else {
                key->qk_objtype = QUOTA_OBJTYPE_FILES;
        }

        /*
         * Get it
         */

        if (__quota_oldfiles_doget(qh, key, val, &isallzero)) {
                return -1;
        }

        /*
         * Advance the cursor
         */
        if (!oqc->oqc_didblocks) {
                oqc->oqc_didblocks = 1;
        } else {
                oqc->oqc_didblocks = 0;
                if (!oqc->oqc_diddefault) {
                        oqc->oqc_diddefault = 1;
                } else {
                        oqc->oqc_pos++;
                        if (oqc->oqc_pos >= maxpos) {
                                oqc->oqc_pos = 0;
                                oqc->oqc_diddefault = 0;
                                if (!oqc->oqc_didusers) {
                                        oqc->oqc_didusers = 1;
                                } else {
                                        oqc->oqc_didgroups = 1;
                                }
                        }
                }
        }

        /*
         * If we got an all-zero dqblk (e.g. from the middle of a hole
         * in the quota file) don't bother returning it to the caller.
         *
         * ...unless we're at the end of the data, to avoid going past
         * the end and generating a spurious failure. There's no
         * reasonable way to make _atend detect empty entries at the
         * end of the quota files.
         */
        if (isallzero && (!oqc->oqc_didusers || !oqc->oqc_didgroups)) {
                goto again;
        }
        return 0;
}

int
__quota_oldfiles_cursor_getn(struct quotahandle *qh,
                             struct oldfiles_quotacursor *oqc,
                             struct quotakey *keys, struct quotaval *vals,
                             unsigned maxnum)
{
        unsigned i;

        if (maxnum > INT_MAX) {
                /* joker, eh? */
                errno = EINVAL;
                return -1;
        }

        for (i=0; i<maxnum; i++) {
                if (__quota_oldfiles_cursor_atend(oqc)) {
                        break;
                }
                if (__quota_oldfiles_cursor_get(qh, oqc, &keys[i], &vals[i])) {
                        if (i > 0) {
                                /*
                                 * Succeed witih what we have so far;
                                 * the next attempt will hit the same
                                 * error again.
                                 */
                                break;
                        }
                        return -1;
                }
        }
        return i;
        
}

int
__quota_oldfiles_cursor_atend(struct oldfiles_quotacursor *oqc)
{
        /* in case one of the sizes is zero */
        if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
                oqc->oqc_didusers = 1;
        }
        if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
                oqc->oqc_didgroups = 1;
        }

        return oqc->oqc_didusers && oqc->oqc_didgroups;
}

int
__quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor *oqc)
{
        oqc->oqc_didusers = 0;
        oqc->oqc_didgroups = 0;
        oqc->oqc_diddefault = 0;
        oqc->oqc_pos = 0;
        oqc->oqc_didblocks = 0;
        return 0;
}