NetBSD/lib/libquota/quota_oldfiles.c

927 lines
20 KiB
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;
}