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