1 /* $NetBSD: quota2.c,v 1.8 2023/07/04 20:40:53 riastradh Exp $ */
2 
3 /*-
4   * Copyright (c) 2010 Manuel Bouyer
5   * All rights reserved.
6   *
7   * Redistribution and use in source and binary forms, with or without
8   * modification, are permitted provided that the following conditions
9   * are met:
10   * 1. Redistributions of source code must retain the above copyright
11   *    notice, this list of conditions and the following disclaimer.
12   * 2. Redistributions in binary form must reproduce the above copyright
13   *    notice, this list of conditions and the following disclaimer in the
14   *    documentation and/or other materials provided with the distribution.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17   * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   */
28 
29 #include <sys/param.h>
30 #include <sys/time.h>
31 
32 #include <ufs/ufs/dinode.h>
33 #include <ufs/ffs/fs.h>
34 #include <ufs/ffs/ffs_extern.h>
35 #include <ufs/ufs/ufs_bswap.h>
36 
37 #include <err.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <ufs/ufs/quota2.h>
41 
42 #include "fsutil.h"
43 #include "fsck.h"
44 #include "extern.h"
45 #include "exitvalues.h"
46 
47 static char **quotamap;
48 
49 void
quota2_create_inode(struct fs * fs,int type)50 quota2_create_inode(struct fs *fs, int type)
51 {
52           ino_t ino;
53           struct bufarea *bp;
54           union dinode *dp;
55 
56           ino = allocino(0, IFREG);
57           dp = ginode(ino);
58           DIP_SET(dp, nlink, iswap16(1));
59           inodirty();
60 
61           if (readblk(dp, 0, &bp) != sblock->fs_bsize ||
62               bp->b_errs != 0) {
63                     freeino(ino);
64                     return;
65           }
66           quota2_create_blk0(sblock->fs_bsize, bp->b_un.b_buf,
67               q2h_hash_shift, type, needswap);
68           dirty(bp);
69           bp->b_flags &= ~B_INUSE;
70           sblock->fs_quotafile[type] = ino;
71           sbdirty();
72           return;
73 }
74 
75 int
quota2_alloc_quota(union dinode * dp,struct bufarea * hbp,uid_t uid,uint64_t u_b,uint64_t u_i)76 quota2_alloc_quota(union dinode * dp, struct bufarea *hbp,
77           uid_t uid, uint64_t u_b, uint64_t u_i)
78 {
79           struct bufarea *bp;
80           struct quota2_header *q2h = (void *)hbp->b_un.b_buf;
81           struct quota2_entry *q2e;
82           uint64_t off;
83           uint64_t baseoff;
84 
85           off = iswap64(q2h->q2h_free);
86           if (off == 0) {
87                     baseoff = iswap64(DIP(dp, size));
88                     if ((bp = expandfile(dp)) == NULL) {
89                               pfatal("SORRY, CAN'T EXPAND QUOTA INODE\n");
90                               markclean = 0;
91                               return (0);
92                     }
93                     quota2_addfreeq2e(q2h, bp->b_un.b_buf, baseoff,
94                         sblock->fs_bsize, needswap);
95                     dirty(bp);
96                     bp->b_flags &= ~B_INUSE;
97                     off = iswap64(q2h->q2h_free);
98                     if (off == 0)
99                               errexit("INTERNAL ERROR: "
100                                   "addfreeq2e didn't fill free list\n");
101           }
102           if (off < (uint64_t)sblock->fs_bsize) {
103                     /* in the header block */
104                     bp = hbp;
105           } else {
106                     if (readblk(dp, off, &bp) != sblock->fs_bsize ||
107                         bp->b_errs != 0) {
108                               pwarn("CAN'T READ QUOTA BLOCK\n");
109                               return FSCK_EXIT_CHECK_FAILED;
110                     }
111           }
112           q2e = (void *)((caddr_t)(bp->b_un.b_buf) +
113               (off % sblock->fs_bsize));
114           /* remove from free list */
115           q2h->q2h_free = q2e->q2e_next;
116 
117           memcpy(q2e, &q2h->q2h_defentry, sizeof(*q2e));
118           q2e->q2e_uid = iswap32(uid);
119           q2e->q2e_val[QL_BLOCK].q2v_cur = iswap64(u_b);
120           q2e->q2e_val[QL_FILE].q2v_cur = iswap64(u_i);
121           /* insert in hash list */
122           q2e->q2e_next = q2h->q2h_entries[uid & q2h_hash_mask];
123           q2h->q2h_entries[uid & q2h_hash_mask] = iswap64(off);
124           dirty(bp);
125           dirty(hbp);
126 
127           if (bp != hbp)
128                     bp->b_flags &= ~B_INUSE;
129           return 0;
130 }
131 
132 /* walk a quota entry list, calling the callback for each entry */
133 static int quota2_walk_list(union dinode *, struct bufarea *, uint64_t *,
134     void *, int (*func)(uint64_t *, struct quota2_entry *, uint64_t, void *));
135 /* flags used by callbacks return */
136 #define Q2WL_ABORT 0x01
137 #define Q2WL_DIRTY 0x02
138 
139 static int
quota2_walk_list(union dinode * dp,struct bufarea * hbp,uint64_t * offp,void * a,int (* func)(uint64_t *,struct quota2_entry *,uint64_t,void *))140 quota2_walk_list(union dinode *dp, struct bufarea *hbp, uint64_t *offp, void *a,
141     int (*func)(uint64_t *, struct quota2_entry *, uint64_t, void *))
142 {
143           daddr_t off = iswap64(*offp);
144           struct bufarea *bp, *obp = hbp;
145           int ret;
146           struct quota2_entry *q2e;
147 
148           while (off != 0) {
149                     if (off < sblock->fs_bsize) {
150                               /* in the header block */
151                               bp = hbp;
152                     } else {
153                               if (readblk(dp, off, &bp) != sblock->fs_bsize ||
154                                   bp->b_errs != 0) {
155                                         pwarn("CAN'T READ QUOTA BLOCK");
156                                         return FSCK_EXIT_CHECK_FAILED;
157                               }
158                     }
159                     q2e = (void *)((caddr_t)(bp->b_un.b_buf) +
160                         (off % sblock->fs_bsize));
161                     ret = (*func)(offp, q2e, off, a);
162                     if (ret & Q2WL_DIRTY)
163                               dirty(bp);
164                     if (ret & Q2WL_ABORT)
165                               return FSCK_EXIT_CHECK_FAILED;
166                     if ((uint64_t)off != iswap64(*offp)) {
167                               /* callback changed parent's pointer, redo */
168                               dirty(obp);
169                               off = iswap64(*offp);
170                               if (bp != hbp && bp != obp)
171                                         bp->b_flags &= ~B_INUSE;
172                     } else {
173                               /* parent if now current */
174                               if (obp != bp && obp != hbp)
175                                         obp->b_flags &= ~B_INUSE;
176                               obp = bp;
177                               offp = &(q2e->q2e_next);
178                               off = iswap64(*offp);
179                     }
180           }
181           if (obp != hbp)
182                     obp->b_flags &= ~B_INUSE;
183           return 0;
184 }
185 
186 static int quota2_list_check(uint64_t *, struct quota2_entry *, uint64_t,
187     void *);
188 static int
quota2_list_check(uint64_t * offp,struct quota2_entry * q2e,uint64_t off,void * v)189 quota2_list_check(uint64_t *offp, struct quota2_entry *q2e, uint64_t off,
190     void *v)
191 {
192           int *hash = v;
193           const int quota2_hash_size = 1 << q2h_hash_shift;
194           const int quota2_full_header_size = sizeof(struct quota2_header) +
195               sizeof(uint64_t) * quota2_hash_size;
196           uint64_t blk = off / sblock->fs_bsize;
197           uint64_t boff = off % sblock->fs_bsize;
198           int qidx = off2qindex((blk == 0) ? quota2_full_header_size : 0, boff);
199 
200           /* check that we're not already in a list */
201           if (!isset(quotamap[blk], qidx)) {
202                     pwarn("DUPLICATE QUOTA ENTRY");
203           } else {
204                     clrbit(quotamap[blk], qidx);
205                     /* check that we're in the right hash entry */
206                     if (*hash < 0)
207                               return 0;
208                     if ((uint32_t)*hash == (iswap32(q2e->q2e_uid) & q2h_hash_mask))
209                               return 0;
210 
211                     pwarn("QUOTA uid %d IN WRONG HASH LIST %d",
212                         iswap32(q2e->q2e_uid), *hash);
213                     /*
214                      * remove from list, but keep the bit so
215                      * it'll be added back to the free list
216                      */
217                     setbit(quotamap[blk], qidx);
218           }
219 
220           if (preen)
221                     printf(" (FIXED)\n");
222           else if (!reply("FIX")) {
223                     markclean = 0;
224                     return 0;
225           }
226           /* remove this entry from the list */
227           *offp = q2e->q2e_next;
228           q2e->q2e_next = 0;
229           return Q2WL_DIRTY;
230 }
231 
232 int
quota2_check_inode(int type)233 quota2_check_inode(int type)
234 {
235           const char *strtype = (type == USRQUOTA) ? "user" : "group";
236           const char *capstrtype = (type == USRQUOTA) ? "USER" : "GROUP";
237 
238           struct bufarea *bp, *hbp;
239           union dinode *dp;
240           struct quota2_header *q2h;
241           struct quota2_entry *q2e;
242           int freei = 0;
243           int mode;
244           daddr_t off;
245           int nq2e, nq2map, i, j, ret;
246           uint64_t /* blocks, e_blocks, */ filesize;
247 
248           const int quota2_hash_size = 1 << q2h_hash_shift;
249           const int quota2_full_header_size = sizeof(struct quota2_header) +
250               sizeof(q2h->q2h_entries[0]) * quota2_hash_size;
251 
252           if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(type)) == 0)
253                     return 0;
254           if (sblock->fs_quotafile[type] != 0) {
255                     struct inostat *info;
256 
257                     info = inoinfo(sblock->fs_quotafile[type]);
258                     switch(info->ino_state) {
259                     case FSTATE:
260                               break;
261                     case DSTATE:
262                               freei = 1;
263                               /* FALLTHROUGH */
264                     case DFOUND:
265                               pwarn("%s QUOTA INODE %" PRIu64 " IS A DIRECTORY",
266                                   capstrtype, sblock->fs_quotafile[type]);
267                               goto clear;
268                     case USTATE:
269                     case DCLEAR:
270                     case FCLEAR:
271                               pwarn("UNALLOCATED %s QUOTA INODE %" PRIu64,
272                                   capstrtype, sblock->fs_quotafile[type]);
273                               goto clear;
274                     default:
275                               pfatal("INTERNAL ERROR: wrong quota inode %" PRIu64
276                                   " type %d\n", sblock->fs_quotafile[type],
277                                   info->ino_state);
278                               exit(FSCK_EXIT_CHECK_FAILED);
279                     }
280                     dp = ginode(sblock->fs_quotafile[type]);
281                     mode = iswap16(DIP(dp, mode)) & IFMT;
282                     switch(mode) {
283                     case IFREG:
284                               break;
285                     default:
286                               pwarn("WRONG TYPE %d for %s QUOTA INODE %" PRIu64,
287                                   mode, capstrtype, sblock->fs_quotafile[type]);
288                               freei = 1;
289                               goto clear;
290                     }
291 #if 0
292                     blocks = is_ufs2 ? iswap64(dp->dp2.di_blocks) :
293                         iswap32(dp->dp1.di_blocks);
294                     filesize = iswap64(DIP(dp, size));
295                     e_blocks = btodb(filesize);
296                     if (btodb(filesize) != blocks) {
297                               pwarn("%s QUOTA INODE %" PRIu64 " HAS EMPTY BLOCKS",
298                                   capstrtype, sblock->fs_quotafile[type]);
299                               freei = 1;
300                               goto clear;
301                     }
302 #endif
303                     if (readblk(dp, 0, &hbp) != sblock->fs_bsize ||
304                         hbp->b_errs != 0) {
305                               freeino(sblock->fs_quotafile[type]);
306                               sblock->fs_quotafile[type] = 0;
307                               goto alloc;
308                     }
309                     q2h = (void *)hbp->b_un.b_buf;
310                     if (q2h->q2h_magic_number != iswap32(Q2_HEAD_MAGIC) ||
311                         q2h->q2h_type != type ||
312                         q2h->q2h_hash_shift != q2h_hash_shift ||
313                         q2h->q2h_hash_size != iswap16(quota2_hash_size)) {
314                               pwarn("CORRUPTED %s QUOTA INODE %" PRIu64, capstrtype,
315                                   sblock->fs_quotafile[type]);
316                               freei = 1;
317                               hbp->b_flags &= ~B_INUSE;
318 clear:
319                               if (preen)
320                                         printf(" (CLEARED)\n");
321                               else {
322                                         if (!reply("CLEAR")) {
323                                                   markclean = 0;
324                                                   return FSCK_EXIT_CHECK_FAILED;
325                                         }
326                               }
327                               if (freei)
328                                         freeino(sblock->fs_quotafile[type]);
329                               sblock->fs_quotafile[type] = 0;
330                     }
331           }
332 alloc:
333           if (sblock->fs_quotafile[type] == 0) {
334                     pwarn("NO %s QUOTA INODE", capstrtype);
335                     if (preen)
336                               printf(" (CREATED)\n");
337                     else {
338                               if (!reply("CREATE")) {
339                                         markclean = 0;
340                                         return FSCK_EXIT_CHECK_FAILED;
341                               }
342                     }
343                     quota2_create_inode(sblock, type);
344           }
345 
346           dp = ginode(sblock->fs_quotafile[type]);
347           if (readblk(dp, 0, &hbp) != sblock->fs_bsize ||
348               hbp->b_errs != 0) {
349                     pfatal("can't re-read %s quota header\n", strtype);
350                     exit(FSCK_EXIT_CHECK_FAILED);
351           }
352           q2h = (void *)hbp->b_un.b_buf;
353           filesize = iswap64(DIP(dp, size));
354           nq2map = filesize / sblock->fs_bsize;
355           quotamap = malloc(sizeof(*quotamap) * nq2map);
356           /* map for full blocks */
357           for (i = 0; i < nq2map; i++) {
358                     nq2e = (sblock->fs_bsize -
359                         ((i == 0) ? quota2_full_header_size : 0)) / sizeof(*q2e);
360                     quotamap[i] = calloc(roundup(howmany(nq2e, NBBY),
361                         sizeof(int16_t)), sizeof(char));
362                     for (j = 0; j < nq2e; j++)
363                               setbit(quotamap[i], j);
364           }
365 
366           /* check that all entries are in the lists (and only once) */
367           i = -1;
368           ret = quota2_walk_list(dp, hbp, &q2h->q2h_free, &i, quota2_list_check);
369           if (ret)
370                     return ret;
371           for (i = 0; i < quota2_hash_size; i++) {
372                     ret = quota2_walk_list(dp, hbp, &q2h->q2h_entries[i], &i,
373                         quota2_list_check);
374                     if (ret)
375                               return ret;
376           }
377           for (i = 0; i < nq2map; i++) {
378                     nq2e = (sblock->fs_bsize -
379                         ((i == 0) ? quota2_full_header_size : 0)) / sizeof(*q2e);
380                     for (j = 0; j < nq2e; j++) {
381                               if (!isset(quotamap[i], j))
382                                         continue;
383                               pwarn("QUOTA ENTRY NOT IN LIST");
384                               if (preen)
385                                         printf(" (FIXED)\n");
386                               else if (!reply("FIX")) {
387                                         markclean = 0;
388                                         break;
389                               }
390                               off = qindex2off(
391                                   (i == 0) ? quota2_full_header_size : 0, j);
392                               if (i == 0)
393                                         bp = hbp;
394                               else {
395                                         if (readblk(dp, i * sblock->fs_bsize, &bp)
396                                             != sblock->fs_bsize || bp->b_errs != 0) {
397                                                   pfatal("can't read %s quota entry\n",
398                                                       strtype);
399                                                   break;
400                                         }
401                               }
402                               q2e = (void *)((caddr_t)(bp->b_un.b_buf) + off);
403                               q2e->q2e_next = q2h->q2h_free;
404                               q2h->q2h_free = iswap64(off + i * sblock->fs_bsize);
405                               dirty(bp);
406                               dirty(hbp);
407                               if (bp != hbp)
408                                         bp->b_flags &= ~B_INUSE;
409                     }
410           }
411           hbp->b_flags &= ~B_INUSE;
412           return 0;
413 }
414 
415 /* compare/update on-disk usages to what we computed */
416 
417 struct qcheck_arg {
418           const char *capstrtype;
419           struct uquot_hash *uquot_hash;
420 };
421 
422 static int quota2_list_qcheck(uint64_t *, struct quota2_entry *, uint64_t,
423     void *);
424 static int
quota2_list_qcheck(uint64_t * offp,struct quota2_entry * q2e,uint64_t off,void * v)425 quota2_list_qcheck(uint64_t *offp, struct quota2_entry *q2e, uint64_t off,
426     void *v)
427 {
428           uid_t uid = iswap32(q2e->q2e_uid);
429           struct qcheck_arg *a = v;
430           struct uquot *uq;
431           struct uquot uq_null;
432 
433           memset(&uq_null, 0, sizeof(uq_null));
434 
435           uq = find_uquot(a->uquot_hash, uid, 0);
436 
437           if (uq == NULL)
438                     uq = &uq_null;
439           else
440                     remove_uquot(a->uquot_hash, uq);
441 
442           if (iswap64(q2e->q2e_val[QL_BLOCK].q2v_cur) == uq->uq_b &&
443               iswap64(q2e->q2e_val[QL_FILE].q2v_cur) == uq->uq_i)
444                     return 0;
445           pwarn("%s QUOTA MISMATCH FOR ID %d: %" PRIu64 "/%" PRIu64 " SHOULD BE "
446               "%" PRIu64 "/%" PRIu64, a->capstrtype, uid,
447               iswap64(q2e->q2e_val[QL_BLOCK].q2v_cur),
448               iswap64(q2e->q2e_val[QL_FILE].q2v_cur), uq->uq_b, uq->uq_i);
449           if (preen) {
450                     printf(" (FIXED)\n");
451           } else if (!reply("FIX")) {
452                     markclean = 0;
453                     return 0;
454           }
455           q2e->q2e_val[QL_BLOCK].q2v_cur = iswap64(uq->uq_b);
456           q2e->q2e_val[QL_FILE].q2v_cur = iswap64(uq->uq_i);
457           return Q2WL_DIRTY;
458 }
459 
460 int
quota2_check_usage(int type)461 quota2_check_usage(int type)
462 {
463           const char *strtype = (type == USRQUOTA) ? "user" : "group";
464           const char *capstrtype = (type == USRQUOTA) ? "USER" : "GROUP";
465 
466           struct bufarea *hbp;
467           union dinode *dp;
468           struct quota2_header *q2h;
469           struct qcheck_arg a;
470           int i, ret;
471           const int quota2_hash_size = 1 << q2h_hash_shift;
472 
473           if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(type)) == 0)
474                     return 0;
475 
476           a.capstrtype = capstrtype;
477           a.uquot_hash =
478               (type == USRQUOTA) ? uquot_user_hash : uquot_group_hash;
479           dp = ginode(sblock->fs_quotafile[type]);
480           if (readblk(dp, 0, &hbp) != sblock->fs_bsize ||
481               hbp->b_errs != 0) {
482                     pfatal("can't re-read %s quota header\n", strtype);
483                     exit(FSCK_EXIT_CHECK_FAILED);
484           }
485           q2h = (void *)hbp->b_un.b_buf;
486           for (i = 0; i < quota2_hash_size; i++) {
487                     ret = quota2_walk_list(dp, hbp, &q2h->q2h_entries[i], &a,
488                         quota2_list_qcheck);
489                     if (ret)
490                               return ret;
491           }
492 
493           for (i = 0; i < quota2_hash_size; i++) {
494                     struct uquot *uq;
495                     SLIST_FOREACH(uq, &a.uquot_hash[i], uq_entries) {
496                               pwarn("%s QUOTA MISMATCH FOR ID %d: 0/0"
497                                   " SHOULD BE %" PRIu64 "/%" PRIu64, capstrtype,
498                                   uq->uq_uid, uq->uq_b, uq->uq_i);
499                               if (preen) {
500                                         printf(" (ALLOCATED)\n");
501                               } else if (!reply("ALLOC")) {
502                                         markclean = 0;
503                                         return 0;
504                               }
505                               ret = quota2_alloc_quota(dp, hbp,
506                                   uq->uq_uid, uq->uq_b, uq->uq_i);
507                               if (ret)
508                                         return ret;
509                     }
510           }
511           hbp->b_flags &= ~B_INUSE;
512           return 0;
513 }
514 
515 /*
516  * check if a quota check needs to be run, regardless of the clean flag
517  */
518 int
quota2_check_doquota(void)519 quota2_check_doquota(void)
520 {
521           int retval = 1;
522 
523           if ((sblock->fs_flags & FS_DOQUOTA2) == 0)
524                     return 1;
525           if (sblock->fs_quota_magic != Q2_HEAD_MAGIC) {
526                     pfatal("Invalid quota magic number\n");
527                     if (preen)
528                               return 0;
529                     if (reply("CONTINUE") == 0) {
530                               exit(FSCK_EXIT_CHECK_FAILED);
531                     }
532                     return 0;
533           }
534           if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(USRQUOTA)) &&
535               sblock->fs_quotafile[USRQUOTA] == 0) {
536                     pwarn("no user quota inode\n");
537                     retval = 0;
538           }
539           if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(GRPQUOTA)) &&
540               sblock->fs_quotafile[GRPQUOTA] == 0) {
541                     pwarn("no group quota inode\n");
542                     retval = 0;
543           }
544           if (preen)
545                     return retval;
546           if (!retval) {
547                     if (reply("CONTINUE") == 0) {
548                               exit(FSCK_EXIT_CHECK_FAILED);
549                     }
550                     return 0;
551           }
552           return 1;
553 }
554