quota_oldfiles.c revision 1.2 1 /* $NetBSD: quota_oldfiles.c,v 1.2 2012/01/09 15:45:19 dholland 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/types.h>
36 #include <sys/stat.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <limits.h>
43 #include <fstab.h>
44 #include <errno.h>
45 #include <err.h>
46
47 #include <ufs/ufs/quota1.h>
48
49 #include <quota.h>
50 #include "quotapvt.h"
51
52 struct oldfiles_quotacursor {
53 unsigned oqc_doingusers;
54 unsigned oqc_doinggroups;
55
56 unsigned oqc_numusers;
57 unsigned oqc_numgroups;
58
59 unsigned oqc_didusers;
60 unsigned oqc_didgroups;
61 unsigned oqc_diddefault;
62 unsigned oqc_pos;
63 unsigned oqc_didblocks;
64 };
65
66 static uint64_t
67 dqblk_getlimit(uint32_t val)
68 {
69 if (val == 0) {
70 return QUOTA_NOLIMIT;
71 } else {
72 return val - 1;
73 }
74 }
75
76 static uint32_t
77 dqblk_setlimit(uint64_t val)
78 {
79 if (val == QUOTA_NOLIMIT && val >= 0xffffffffUL) {
80 return 0;
81 } else {
82 return (uint32_t)val + 1;
83 }
84 }
85
86 static void
87 dqblk_getblocks(const struct dqblk *dq, struct quotaval *qv)
88 {
89 qv->qv_hardlimit = dqblk_getlimit(dq->dqb_bhardlimit);
90 qv->qv_softlimit = dqblk_getlimit(dq->dqb_bsoftlimit);
91 qv->qv_usage = dq->dqb_curblocks;
92 qv->qv_expiretime = dq->dqb_btime;
93 qv->qv_grace = QUOTA_NOTIME;
94 }
95
96 static void
97 dqblk_getfiles(const struct dqblk *dq, struct quotaval *qv)
98 {
99 qv->qv_hardlimit = dqblk_getlimit(dq->dqb_ihardlimit);
100 qv->qv_softlimit = dqblk_getlimit(dq->dqb_isoftlimit);
101 qv->qv_usage = dq->dqb_curinodes;
102 qv->qv_expiretime = dq->dqb_itime;
103 qv->qv_grace = QUOTA_NOTIME;
104 }
105
106 static void
107 dqblk_putblocks(const struct quotaval *qv, struct dqblk *dq)
108 {
109 dq->dqb_bhardlimit = dqblk_setlimit(qv->qv_hardlimit);
110 dq->dqb_bsoftlimit = dqblk_setlimit(qv->qv_softlimit);
111 dq->dqb_curblocks = qv->qv_usage;
112 dq->dqb_btime = qv->qv_expiretime;
113 /* ignore qv->qv_grace */
114 }
115
116 static void
117 dqblk_putfiles(const struct quotaval *qv, struct dqblk *dq)
118 {
119 dq->dqb_ihardlimit = dqblk_setlimit(qv->qv_hardlimit);
120 dq->dqb_isoftlimit = dqblk_setlimit(qv->qv_softlimit);
121 dq->dqb_curinodes = qv->qv_usage;
122 dq->dqb_itime = qv->qv_expiretime;
123 /* ignore qv->qv_grace */
124 }
125
126 static int
127 __quota_oldfiles_open(struct quotahandle *qh, const char *path, int *fd_ret)
128 {
129 int fd;
130
131 fd = open(path, O_RDWR);
132 if (fd < 0 && (errno == EACCES || errno == EROFS)) {
133 fd = open(path, O_RDONLY);
134 if (fd < 0) {
135 return -1;
136 }
137 }
138 *fd_ret = fd;
139 return 0;
140 }
141
142 int
143 __quota_oldfiles_initialize(struct quotahandle *qh)
144 {
145 static const char *const names[] = INITQFNAMES;
146
147 struct fstab *fs;
148 char buf[sizeof(fs->fs_mntops)];
149 char *opt, *state, *s;
150 char path[PATH_MAX];
151 const char *userquotafile, *groupquotafile;
152 int hasuserquota, hasgroupquota;
153
154 if (qh->qh_hasoldfiles) {
155 /* already initialized */
156 return 0;
157 }
158
159 /*
160 * Find the fstab entry.
161 *
162 * XXX: should be able to handle not just ffs quota1 files but
163 * also lfs and even ext2fs.
164 */
165 setfsent();
166 while ((fs = getfsent()) != NULL) {
167 if (!strcmp(fs->fs_vfstype, "ffs") &&
168 !strcmp(fs->fs_file, qh->qh_mountpoint)) {
169 break;
170 }
171 }
172 endfsent();
173
174 if (fs == NULL) {
175 warnx("%s not found in fstab", qh->qh_mountpoint);
176 errno = ENXIO;
177 return -1;
178 }
179
180 /*
181 * Inspect the mount options to find the quota files.
182 * XXX this info should be gotten from the kernel.
183 *
184 * The options are:
185 * userquota[=path] enable user quotas
186 * groupquota[=path] enable group quotas
187 */
188 hasuserquota = hasgroupquota = 0;
189 userquotafile = groupquotafile = NULL;
190 strlcpy(buf, fs->fs_mntops, sizeof(buf));
191 for (opt = strtok_r(buf, ",", &state);
192 opt != NULL;
193 opt = strtok_r(NULL, ",", &state)) {
194 s = strchr(opt, '=');
195 if (s != NULL) {
196 *(s++) = '\0';
197 }
198 if (!strcmp(opt, "userquota")) {
199 hasuserquota = 1;
200 if (s != NULL) {
201 userquotafile = s;
202 }
203 } else if (!strcmp(opt, "groupquota")) {
204 hasgroupquota = 1;
205 if (s != NULL) {
206 groupquotafile = s;
207 }
208 }
209 }
210
211 if (!hasuserquota && !hasgroupquota) {
212 errno = ENXIO;
213 return -1;
214 }
215
216 if (hasuserquota) {
217 if (userquotafile == NULL) {
218 (void)snprintf(path, sizeof(path), "%s/%s.%s",
219 fs->fs_file,
220 QUOTAFILENAME, names[USRQUOTA]);
221 userquotafile = path;
222 }
223 if (__quota_oldfiles_open(qh, userquotafile,
224 &qh->qh_userfile)) {
225 return -1;
226 }
227 }
228 if (hasgroupquota) {
229 if (groupquotafile == NULL) {
230 (void)snprintf(path, sizeof(path), "%s/%s.%s",
231 fs->fs_file,
232 QUOTAFILENAME, names[GRPQUOTA]);
233 groupquotafile = path;
234 }
235 if (__quota_oldfiles_open(qh, groupquotafile,
236 &qh->qh_groupfile)) {
237 return -1;
238 }
239 }
240
241 qh->qh_hasoldfiles = 1;
242
243 return 0;
244 }
245
246 const char *
247 __quota_oldfiles_getimplname(struct quotahandle *qh)
248 {
249 return "ffs quota1 direct file access";
250 }
251
252 static int
253 __quota_oldfiles_doget(struct quotahandle *qh, const struct quotakey *qk,
254 struct quotaval *qv, int *isallzero)
255 {
256 int file;
257 off_t pos;
258 struct dqblk dq;
259 ssize_t result;
260
261 switch (qk->qk_idtype) {
262 case QUOTA_IDTYPE_USER:
263 file = qh->qh_userfile;
264 break;
265 case QUOTA_IDTYPE_GROUP:
266 file = qh->qh_groupfile;
267 break;
268 default:
269 errno = EINVAL;
270 return -1;
271 }
272
273 if (qk->qk_id == QUOTA_DEFAULTID) {
274 pos = 0;
275 } else {
276 pos = qk->qk_id * sizeof(struct dqblk);
277 }
278
279 result = pread(file, &dq, sizeof(dq), pos);
280 if (result < 0) {
281 return -1;
282 } else if (result == 0) {
283 /* Past EOF; no quota info on file for this ID */
284 errno = ENOENT;
285 return -1;
286 } else if ((size_t)result != sizeof(dq)) {
287 errno = EFTYPE;
288 return -1;
289 }
290
291 switch (qk->qk_objtype) {
292 case QUOTA_OBJTYPE_BLOCKS:
293 dqblk_getblocks(&dq, qv);
294 break;
295 case QUOTA_OBJTYPE_FILES:
296 dqblk_getfiles(&dq, qv);
297 break;
298 default:
299 errno = EINVAL;
300 return -1;
301 }
302
303 if (qk->qk_id == QUOTA_DEFAULTID) {
304 qv->qv_usage = 0;
305 qv->qv_grace = qv->qv_expiretime;
306 qv->qv_expiretime = QUOTA_NOTIME;
307 } else if (qk->qk_id == 0) {
308 qv->qv_hardlimit = 0;
309 qv->qv_softlimit = 0;
310 qv->qv_expiretime = QUOTA_NOTIME;
311 qv->qv_grace = QUOTA_NOTIME;
312 }
313
314 if (isallzero != NULL) {
315 if (dq.dqb_bhardlimit == 0 &&
316 dq.dqb_bsoftlimit == 0 &&
317 dq.dqb_curblocks == 0 &&
318 dq.dqb_ihardlimit == 0 &&
319 dq.dqb_isoftlimit == 0 &&
320 dq.dqb_curinodes == 0 &&
321 dq.dqb_btime == 0 &&
322 dq.dqb_itime == 0) {
323 *isallzero = 1;
324 } else {
325 *isallzero = 0;
326 }
327 }
328
329 return 0;
330 }
331
332 static int
333 __quota_oldfiles_doput(struct quotahandle *qh, const struct quotakey *qk,
334 const struct quotaval *qv)
335 {
336 int file;
337 off_t pos;
338 struct quotaval qv2;
339 struct dqblk dq;
340 ssize_t result;
341
342 switch (qk->qk_idtype) {
343 case QUOTA_IDTYPE_USER:
344 file = qh->qh_userfile;
345 break;
346 case QUOTA_IDTYPE_GROUP:
347 file = qh->qh_groupfile;
348 break;
349 default:
350 errno = EINVAL;
351 return -1;
352 }
353
354 if (qk->qk_id == QUOTA_DEFAULTID) {
355 pos = 0;
356 } else {
357 pos = qk->qk_id * sizeof(struct dqblk);
358 }
359
360 result = pread(file, &dq, sizeof(dq), pos);
361 if (result < 0) {
362 return -1;
363 } else if (result == 0) {
364 /* Past EOF; fill in a blank dq to start from */
365 dq.dqb_bhardlimit = 0;
366 dq.dqb_bsoftlimit = 0;
367 dq.dqb_curblocks = 0;
368 dq.dqb_ihardlimit = 0;
369 dq.dqb_isoftlimit = 0;
370 dq.dqb_curinodes = 0;
371 dq.dqb_btime = 0;
372 dq.dqb_itime = 0;
373 } else if ((size_t)result != sizeof(dq)) {
374 errno = EFTYPE;
375 return -1;
376 }
377
378 switch (qk->qk_objtype) {
379 case QUOTA_OBJTYPE_BLOCKS:
380 dqblk_getblocks(&dq, &qv2);
381 break;
382 case QUOTA_OBJTYPE_FILES:
383 dqblk_getfiles(&dq, &qv2);
384 break;
385 default:
386 errno = EINVAL;
387 return -1;
388 }
389
390 if (qk->qk_id == QUOTA_DEFAULTID) {
391 qv2.qv_hardlimit = qv->qv_hardlimit;
392 qv2.qv_softlimit = qv->qv_softlimit;
393 /* leave qv2.qv_usage unchanged */
394 qv2.qv_expiretime = qv->qv_grace;
395 /* skip qv2.qv_grace */
396
397 /* ignore qv->qv_usage */
398 /* ignore qv->qv_expiretime */
399 } else if (qk->qk_id == 0) {
400 /* leave qv2.qv_hardlimit unchanged */
401 /* leave qv2.qv_softlimit unchanged */
402 qv2.qv_usage = qv->qv_usage;
403 /* leave qv2.qv_expiretime unchanged */
404 /* skip qv2.qv_grace */
405
406 /* ignore qv->qv_hardlimit */
407 /* ignore qv->qv_softlimit */
408 /* ignore qv->qv_expiretime */
409 /* ignore qv->qv_grace */
410 } else {
411 qv2 = *qv;
412 }
413
414 switch (qk->qk_objtype) {
415 case QUOTA_OBJTYPE_BLOCKS:
416 dqblk_putblocks(&qv2, &dq);
417 break;
418 case QUOTA_OBJTYPE_FILES:
419 dqblk_putfiles(&qv2, &dq);
420 break;
421 default:
422 errno = EINVAL;
423 return -1;
424 }
425
426 result = pwrite(file, &dq, sizeof(dq), pos);
427 if (result < 0) {
428 return -1;
429 } else if ((size_t)result != sizeof(dq)) {
430 /* ? */
431 errno = EFTYPE;
432 return -1;
433 }
434
435 return 0;
436 }
437
438 int
439 __quota_oldfiles_get(struct quotahandle *qh, const struct quotakey *qk,
440 struct quotaval *qv)
441 {
442 return __quota_oldfiles_doget(qh, qk, qv, NULL);
443 }
444
445 int
446 __quota_oldfiles_put(struct quotahandle *qh, const struct quotakey *qk,
447 const struct quotaval *qv)
448 {
449 return __quota_oldfiles_doput(qh, qk, qv);
450 }
451
452 int
453 __quota_oldfiles_delete(struct quotahandle *qh, const struct quotakey *qk)
454 {
455 struct quotaval qv;
456
457 quotaval_clear(&qv);
458 return __quota_oldfiles_doput(qh, qk, &qv);
459 }
460
461 struct oldfiles_quotacursor *
462 __quota_oldfiles_cursor_create(struct quotahandle *qh)
463 {
464 struct oldfiles_quotacursor *oqc;
465 struct stat st;
466 int serrno;
467
468 oqc = malloc(sizeof(*oqc));
469 if (oqc == NULL) {
470 return NULL;
471 }
472
473 oqc->oqc_didusers = 0;
474 oqc->oqc_didgroups = 0;
475 oqc->oqc_diddefault = 0;
476 oqc->oqc_pos = 0;
477 oqc->oqc_didblocks = 0;
478
479 if (qh->qh_userfile >= 0) {
480 oqc->oqc_doingusers = 1;
481 } else {
482 oqc->oqc_doingusers = 0;
483 oqc->oqc_didusers = 1;
484 }
485
486 if (qh->qh_groupfile >= 0) {
487 oqc->oqc_doinggroups = 1;
488 } else {
489 oqc->oqc_doinggroups = 0;
490 oqc->oqc_didgroups = 1;
491 }
492
493 if (fstat(qh->qh_userfile, &st) < 0) {
494 serrno = errno;
495 free(oqc);
496 errno = serrno;
497 return NULL;
498 }
499 oqc->oqc_numusers = st.st_size / sizeof(struct dqblk);
500
501 if (fstat(qh->qh_groupfile, &st) < 0) {
502 serrno = errno;
503 free(oqc);
504 errno = serrno;
505 return NULL;
506 }
507 oqc->oqc_numgroups = st.st_size / sizeof(struct dqblk);
508
509 return oqc;
510 }
511
512 void
513 __quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc)
514 {
515 free(oqc);
516 }
517
518 int
519 __quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor *oqc,
520 unsigned idtype)
521 {
522 switch (idtype) {
523 case QUOTA_IDTYPE_USER:
524 oqc->oqc_doingusers = 0;
525 oqc->oqc_didusers = 1;
526 break;
527 case QUOTA_IDTYPE_GROUP:
528 oqc->oqc_doinggroups = 0;
529 oqc->oqc_didgroups = 1;
530 break;
531 default:
532 errno = EINVAL;
533 return -1;
534 }
535 return 0;
536 }
537
538 int
539 __quota_oldfiles_cursor_get(struct quotahandle *qh,
540 struct oldfiles_quotacursor *oqc,
541 struct quotakey *key, struct quotaval *val)
542 {
543 unsigned maxpos;
544 int isallzero;
545
546 /* in case one of the sizes is zero */
547 if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
548 oqc->oqc_didusers = 1;
549 }
550 if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
551 oqc->oqc_didgroups = 1;
552 }
553
554 again:
555 /*
556 * Figure out what to get
557 */
558
559 if (!oqc->oqc_didusers) {
560 key->qk_idtype = QUOTA_IDTYPE_USER;
561 maxpos = oqc->oqc_numusers;
562 } else if (!oqc->oqc_didgroups) {
563 key->qk_idtype = QUOTA_IDTYPE_GROUP;
564 maxpos = oqc->oqc_numgroups;
565 } else {
566 errno = ENOENT;
567 return -1;
568 }
569
570 if (!oqc->oqc_diddefault) {
571 key->qk_id = QUOTA_DEFAULTID;
572 } else {
573 key->qk_id = oqc->oqc_pos;
574 }
575
576 if (!oqc->oqc_didblocks) {
577 key->qk_objtype = QUOTA_OBJTYPE_BLOCKS;
578 } else {
579 key->qk_objtype = QUOTA_OBJTYPE_FILES;
580 }
581
582 /*
583 * Get it
584 */
585
586 if (__quota_oldfiles_doget(qh, key, val, &isallzero)) {
587 return -1;
588 }
589
590 /*
591 * Advance the cursor
592 */
593 if (!oqc->oqc_didblocks) {
594 oqc->oqc_didblocks = 1;
595 } else {
596 oqc->oqc_didblocks = 0;
597 if (!oqc->oqc_diddefault) {
598 oqc->oqc_diddefault = 1;
599 } else {
600 oqc->oqc_pos++;
601 if (oqc->oqc_pos >= maxpos) {
602 oqc->oqc_pos = 0;
603 oqc->oqc_diddefault = 0;
604 if (!oqc->oqc_didusers) {
605 oqc->oqc_didusers = 1;
606 } else {
607 oqc->oqc_didgroups = 1;
608 }
609 }
610 }
611 }
612
613 /*
614 * If we got an all-zero dqblk (e.g. from the middle of a hole
615 * in the quota file) don't bother returning it to the caller.
616 *
617 * ...unless we're at the end of the data, to avoid going past
618 * the end and generating a spurious failure. There's no
619 * reasonable way to make _atend detect empty entries at the
620 * end of the quota files.
621 */
622 if (isallzero && (!oqc->oqc_didusers || !oqc->oqc_didgroups)) {
623 goto again;
624 }
625 return 0;
626 }
627
628 int
629 __quota_oldfiles_cursor_getn(struct quotahandle *qh,
630 struct oldfiles_quotacursor *oqc,
631 struct quotakey *keys, struct quotaval *vals,
632 unsigned maxnum)
633 {
634 unsigned i;
635
636 if (maxnum > INT_MAX) {
637 /* joker, eh? */
638 errno = EINVAL;
639 return -1;
640 }
641
642 for (i=0; i<maxnum; i++) {
643 if (__quota_oldfiles_cursor_atend(oqc)) {
644 break;
645 }
646 if (__quota_oldfiles_cursor_get(qh, oqc, &keys[i], &vals[i])) {
647 if (i > 0) {
648 /*
649 * Succeed witih what we have so far;
650 * the next attempt will hit the same
651 * error again.
652 */
653 break;
654 }
655 return -1;
656 }
657 }
658 return i;
659
660 }
661
662 int
663 __quota_oldfiles_cursor_atend(struct oldfiles_quotacursor *oqc)
664 {
665 /* in case one of the sizes is zero */
666 if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) {
667 oqc->oqc_didusers = 1;
668 }
669 if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) {
670 oqc->oqc_didgroups = 1;
671 }
672
673 return oqc->oqc_didusers && oqc->oqc_didgroups;
674 }
675
676 int
677 __quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor *oqc)
678 {
679 oqc->oqc_didusers = 0;
680 oqc->oqc_didgroups = 0;
681 oqc->oqc_diddefault = 0;
682 oqc->oqc_pos = 0;
683 oqc->oqc_didblocks = 0;
684 return 0;
685 }
686