Home | History | Annotate | Line # | Download | only in libquota
      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 *
     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
     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
    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
    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
    245 __quota_oldfiles_infstab(const char *mountpoint)
    246 {
    247 	return __quota_oldfiles_find_fstabentry(mountpoint) != NULL;
    248 }
    249 
    250 static 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[idtype]);
    259 }
    260 
    261 const 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 
    301 static uint64_t
    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
    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
    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
    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
    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
    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
    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
    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 *
    435 __quota_oldfiles_getimplname(struct quotahandle *qh)
    436 {
    437 	return "ufs/ffs quota v1 file access";
    438 }
    439 
    440 int
    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
    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
    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
    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
    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
    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 *
    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
    754 __quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
    755 {
    756 	free(oqc);
    757 }
    758 
    759 int
    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
    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
    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
    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
    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