edquota.c revision 1.47 1 /* $NetBSD: edquota.c,v 1.47 2012/04/07 05:07:32 christos Exp $ */
2 /*
3 * Copyright (c) 1980, 1990, 1993
4 * The Regents of the University of California. All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * Robert Elz at The University of Melbourne.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #include <sys/cdefs.h>
35 #ifndef lint
36 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\
37 The Regents of the University of California. All rights reserved.");
38 #endif /* not lint */
39
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "from: @(#)edquota.c 8.3 (Berkeley) 4/27/95";
43 #else
44 __RCSID("$NetBSD: edquota.c,v 1.47 2012/04/07 05:07:32 christos Exp $");
45 #endif
46 #endif /* not lint */
47
48 /*
49 * Disk quota editor.
50 */
51 #include <sys/param.h>
52 #include <sys/stat.h>
53 #include <sys/file.h>
54 #include <sys/wait.h>
55 #include <sys/queue.h>
56 #include <sys/types.h>
57 #include <sys/statvfs.h>
58
59 #include <quota.h>
60
61 #include <assert.h>
62 #include <err.h>
63 #include <errno.h>
64 #include <fstab.h>
65 #include <pwd.h>
66 #include <grp.h>
67 #include <ctype.h>
68 #include <signal.h>
69 #include <stdbool.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74 #include <util.h>
75
76 #include "printquota.h"
77
78 #include "pathnames.h"
79
80 /*
81 * XXX. Ideally we shouldn't compile this in, but it'll take some
82 * reworking to avoid it and it'll be ok for now.
83 */
84 #define EDQUOTA_NUMOBJTYPES 2
85
86 #if 0
87 static const char *quotagroup = QUOTAGROUP;
88 #endif
89
90 #define MAX_TMPSTR (100+MAXPATHLEN)
91
92 /* flags for quotause */
93 #define FOUND 0x01
94 #define XGRACE 0x02 /* extended grace periods (per-id) */
95 #define DEFAULT 0x04
96
97 struct quotause {
98 struct quotause *next;
99 long flags;
100 struct quotaval qv[EDQUOTA_NUMOBJTYPES];
101 char fsname[MAXPATHLEN + 1];
102 char implementation[32];
103 char *qfname;
104 };
105
106 struct quotalist {
107 struct quotause *head;
108 struct quotause *tail;
109 char *idtypename;
110 };
111
112 static void usage(void) __dead;
113
114 static int Hflag = 0;
115
116 /* more compact form of constants */
117 #define QO_BLK QUOTA_OBJTYPE_BLOCKS
118 #define QO_FL QUOTA_OBJTYPE_FILES
119
120 ////////////////////////////////////////////////////////////
121 // support code
122
123 /*
124 * This routine converts a name for a particular quota class to
125 * an identifier. This routine must agree with the kernel routine
126 * getinoquota as to the interpretation of quota classes.
127 */
128 static int
129 getidbyname(const char *name, int idtype)
130 {
131 struct passwd *pw;
132 struct group *gr;
133
134 if (alldigits(name))
135 return atoi(name);
136 switch (idtype) {
137 case QUOTA_IDTYPE_USER:
138 if ((pw = getpwnam(name)) != NULL)
139 return pw->pw_uid;
140 warnx("%s: no such user", name);
141 break;
142 case QUOTA_IDTYPE_GROUP:
143 if ((gr = getgrnam(name)) != NULL)
144 return gr->gr_gid;
145 warnx("%s: no such group", name);
146 break;
147 default:
148 warnx("%d: unknown quota type", idtype);
149 break;
150 }
151 sleep(1);
152 return -1;
153 }
154
155 ////////////////////////////////////////////////////////////
156 // quotause operations
157
158 /*
159 * Create an empty quotause structure.
160 */
161 static struct quotause *
162 quotause_create(void)
163 {
164 struct quotause *qup;
165
166 qup = malloc(sizeof(*qup));
167 if (qup == NULL) {
168 err(1, "malloc");
169 }
170
171 qup->next = NULL;
172 qup->flags = 0;
173 memset(qup->qv, 0, sizeof(qup->qv));
174 qup->fsname[0] = '\0';
175 qup->qfname = NULL;
176
177 return qup;
178 }
179
180 /*
181 * Free a quotause structure.
182 */
183 static void
184 quotause_destroy(struct quotause *qup)
185 {
186 free(qup->qfname);
187 free(qup);
188 }
189
190 ////////////////////////////////////////////////////////////
191 // quotalist operations
192
193 /*
194 * Create a quotause list.
195 */
196 static struct quotalist *
197 quotalist_create(void)
198 {
199 struct quotalist *qlist;
200
201 qlist = malloc(sizeof(*qlist));
202 if (qlist == NULL) {
203 err(1, "malloc");
204 }
205
206 qlist->head = NULL;
207 qlist->tail = NULL;
208 qlist->idtypename = NULL;
209
210 return qlist;
211 }
212
213 /*
214 * Free a list of quotause structures.
215 */
216 static void
217 quotalist_destroy(struct quotalist *qlist)
218 {
219 struct quotause *qup, *nextqup;
220
221 for (qup = qlist->head; qup; qup = nextqup) {
222 nextqup = qup->next;
223 quotause_destroy(qup);
224 }
225 free(qlist->idtypename);
226 free(qlist);
227 }
228
229 #if 0
230 static bool
231 quotalist_empty(struct quotalist *qlist)
232 {
233 return qlist->head == NULL;
234 }
235 #endif
236
237 static void
238 quotalist_append(struct quotalist *qlist, struct quotause *qup)
239 {
240 /* should not already be on a list */
241 assert(qup->next == NULL);
242
243 if (qlist->head == NULL) {
244 qlist->head = qup;
245 } else {
246 qlist->tail->next = qup;
247 }
248 qlist->tail = qup;
249 }
250
251 ////////////////////////////////////////////////////////////
252 // ffs quota v1
253
254 #if 0
255 static void
256 putprivs1(uint32_t id, int idtype, struct quotause *qup)
257 {
258 struct dqblk dqblk;
259 int fd;
260
261 quotavals_to_dqblk(&qup->qv[QUOTA_LIMIT_BLOCK],
262 &qup->qv[QUOTA_LIMIT_FILE],
263 &dqblk);
264 assert((qup->flags & DEFAULT) == 0);
265
266 if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
267 warnx("open `%s'", qup->qfname);
268 } else {
269 (void)lseek(fd,
270 (off_t)(id * (long)sizeof (struct dqblk)),
271 SEEK_SET);
272 if (write(fd, &dqblk, sizeof (struct dqblk)) !=
273 sizeof (struct dqblk))
274 warnx("writing `%s'", qup->qfname);
275 close(fd);
276 }
277 }
278
279 static struct quotause *
280 getprivs1(long id, int idtype, const char *filesys)
281 {
282 struct fstab *fs;
283 char qfpathname[MAXPATHLEN], xbuf[MAXPATHLEN];
284 const char *fsspec;
285 struct quotause *qup;
286 struct dqblk dqblk;
287 int fd;
288
289 setfsent();
290 while ((fs = getfsent()) != NULL) {
291 if (strcmp(fs->fs_vfstype, "ffs"))
292 continue;
293 fsspec = getfsspecname(xbuf, sizeof(xbuf), fs->fs_spec);
294 if (fsspec == NULL) {
295 warn("%s", xbuf);
296 continue;
297 }
298 if (strcmp(fsspec, filesys) == 0 ||
299 strcmp(fs->fs_file, filesys) == 0)
300 break;
301 }
302 if (fs == NULL)
303 return NULL;
304
305 if (!hasquota(qfpathname, sizeof(qfpathname), fs,
306 quota_idtype_to_ufs(idtype)))
307 return NULL;
308
309 qup = quotause_create();
310 strcpy(qup->fsname, fs->fs_file);
311 if ((fd = open(qfpathname, O_RDONLY)) < 0) {
312 fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
313 if (fd < 0 && errno != ENOENT) {
314 warnx("open `%s'", qfpathname);
315 quotause_destroy(qup);
316 return NULL;
317 }
318 warnx("Creating quota file %s", qfpathname);
319 sleep(3);
320 (void)fchown(fd, getuid(),
321 getidbyname(quotagroup, QUOTA_CLASS_GROUP));
322 (void)fchmod(fd, 0640);
323 }
324 (void)lseek(fd, (off_t)(id * sizeof(struct dqblk)),
325 SEEK_SET);
326 switch (read(fd, &dqblk, sizeof(struct dqblk))) {
327 case 0: /* EOF */
328 /*
329 * Convert implicit 0 quota (EOF)
330 * into an explicit one (zero'ed dqblk)
331 */
332 memset(&dqblk, 0, sizeof(struct dqblk));
333 break;
334
335 case sizeof(struct dqblk): /* OK */
336 break;
337
338 default: /* ERROR */
339 warn("read error in `%s'", qfpathname);
340 close(fd);
341 quotause_destroy(qup);
342 return NULL;
343 }
344 close(fd);
345 qup->qfname = qfpathname;
346 endfsent();
347 dqblk_to_quotavals(&dqblk,
348 &qup->qv[QUOTA_LIMIT_BLOCK],
349 &qup->qv[QUOTA_LIMIT_FILE]);
350 return qup;
351 }
352 #endif
353
354 ////////////////////////////////////////////////////////////
355 // ffs quota v2
356
357 static int
358 dogetprivs2(struct quotahandle *qh, int idtype, id_t id, int defaultq,
359 int objtype, struct quotause *qup)
360 {
361 struct quotakey qk;
362
363 qk.qk_idtype = idtype;
364 qk.qk_id = defaultq ? QUOTA_DEFAULTID : id;
365 qk.qk_objtype = objtype;
366 if (quota_get(qh, &qk, &qup->qv[objtype])) {
367 /* no entry, get default entry */
368 qk.qk_id = QUOTA_DEFAULTID;
369 if (quota_get(qh, &qk, &qup->qv[objtype])) {
370 return -1;
371 }
372 }
373 return 0;
374 }
375
376 static struct quotause *
377 getprivs2(long id, int idtype, const char *filesys, int defaultq,
378 char **idtypename_p)
379 {
380 struct quotause *qup;
381 struct quotahandle *qh;
382 const char *impl;
383 unsigned restrictions;
384 const char *idtypename;
385
386 qup = quotause_create();
387 strcpy(qup->fsname, filesys);
388 if (defaultq)
389 qup->flags |= DEFAULT;
390
391 qh = quota_open(filesys);
392 if (qh == NULL) {
393 quotause_destroy(qup);
394 return NULL;
395 }
396
397 impl = quota_getimplname(qh);
398 if (impl == NULL) {
399 impl = "???";
400 }
401 strlcpy(qup->implementation, impl, sizeof(qup->implementation));
402
403 restrictions = quota_getrestrictions(qh);
404 if ((restrictions & QUOTA_RESTRICT_UNIFORMGRACE) == 0) {
405 qup->flags |= XGRACE;
406 }
407
408 if (*idtypename_p == NULL) {
409 idtypename = quota_idtype_getname(qh, idtype);
410 *idtypename_p = strdup(idtypename);
411 if (*idtypename_p == NULL) {
412 errx(1, "Out of memory");
413 }
414 }
415
416 if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_BLOCKS, qup)) {
417 quota_close(qh);
418 quotause_destroy(qup);
419 return NULL;
420 }
421
422 if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_FILES, qup)) {
423 quota_close(qh);
424 quotause_destroy(qup);
425 return NULL;
426 }
427
428 quota_close(qh);
429
430 return qup;
431 }
432
433 static void
434 putprivs2(uint32_t id, int idtype, struct quotause *qup)
435 {
436 struct quotahandle *qh;
437 struct quotakey qk;
438 char idname[32];
439
440 if (qup->flags & DEFAULT) {
441 snprintf(idname, sizeof(idname), "%s default",
442 idtype == QUOTA_IDTYPE_USER ? "user" : "group");
443 id = QUOTA_DEFAULTID;
444 } else {
445 snprintf(idname, sizeof(idname), "%s %u",
446 idtype == QUOTA_IDTYPE_USER ? "uid" : "gid", id);
447 }
448
449 qh = quota_open(qup->fsname);
450 if (qh == NULL) {
451 err(1, "%s: quota_open", qup->fsname);
452 }
453
454 qk.qk_idtype = idtype;
455 qk.qk_id = id;
456 qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
457 if (quota_put(qh, &qk, &qup->qv[QO_BLK])) {
458 err(1, "%s: quota_put (%s blocks)", qup->fsname, idname);
459 }
460
461 qk.qk_idtype = idtype;
462 qk.qk_id = id;
463 qk.qk_objtype = QUOTA_OBJTYPE_FILES;
464 if (quota_put(qh, &qk, &qup->qv[QO_FL])) {
465 err(1, "%s: quota_put (%s files)", qup->fsname, idname);
466 }
467
468 quota_close(qh);
469 }
470
471 ////////////////////////////////////////////////////////////
472 // quota format switch
473
474 /*
475 * Collect the requested quota information.
476 */
477 static struct quotalist *
478 getprivs(long id, int defaultq, int idtype, const char *filesys)
479 {
480 struct statvfs *fst;
481 int nfst, i;
482 struct quotalist *qlist;
483 struct quotause *qup;
484
485 qlist = quotalist_create();
486
487 nfst = getmntinfo(&fst, MNT_WAIT);
488 if (nfst == 0)
489 errx(1, "no filesystems mounted!");
490
491 for (i = 0; i < nfst; i++) {
492 if ((fst[i].f_flag & ST_QUOTA) == 0)
493 continue;
494 if (filesys &&
495 strcmp(fst[i].f_mntonname, filesys) != 0 &&
496 strcmp(fst[i].f_mntfromname, filesys) != 0)
497 continue;
498 qup = getprivs2(id, idtype, fst[i].f_mntonname, defaultq,
499 &qlist->idtypename);
500 if (qup == NULL) {
501 /*
502 * XXX: returning NULL is totally wrong. On
503 * serious error, abort; on minor error, warn
504 * and continue.
505 *
506 * Note: we cannot warn unconditionally here
507 * because this case apparently includes "no
508 * quota entry on this volume" and that causes
509 * the atf tests to fail. Bletch.
510 */
511 /*return NULL;*/
512 /*warnx("getprivs2 failed");*/
513 continue;
514 }
515
516 quotalist_append(qlist, qup);
517 }
518
519 #if 0
520 if (filesys && quotalist_empty(qlist)) {
521 if (defaultq)
522 errx(1, "no default quota for version 1");
523 /* if we get there, filesys is not mounted. try the old way */
524 qup = getprivs1(id, idtype, filesys);
525 if (qup == NULL) {
526 /* XXX. see above */
527 /*return NULL;*/
528 /*warnx("getprivs1 failed");*/
529 return qlist;
530 }
531 quotalist_append(qlist, qup);
532 }
533 #endif
534 return qlist;
535 }
536
537 /*
538 * Store the requested quota information.
539 */
540 static void
541 putprivs(uint32_t id, int idtype, struct quotalist *qlist)
542 {
543 struct quotause *qup;
544
545 for (qup = qlist->head; qup; qup = qup->next) {
546 if (qup->qfname == NULL)
547 putprivs2(id, idtype, qup);
548 #if 0
549 else
550 putprivs1(id, idtype, qup);
551 #endif
552 }
553 }
554
555 static void
556 clearpriv(int argc, char **argv, const char *filesys, int idtype)
557 {
558 struct statvfs *fst;
559 int nfst, i;
560 int id;
561 id_t *ids;
562 unsigned nids, maxids, j;
563 struct quotahandle *qh;
564 struct quotakey qk;
565 char idname[32];
566
567 maxids = 4;
568 nids = 0;
569 ids = malloc(maxids * sizeof(ids[0]));
570 if (ids == NULL) {
571 err(1, "malloc");
572 }
573
574 for ( ; argc > 0; argc--, argv++) {
575 if ((id = getidbyname(*argv, idtype)) == -1)
576 continue;
577
578 if (nids + 1 > maxids) {
579 maxids *= 2;
580 ids = realloc(ids, maxids * sizeof(ids[0]));
581 if (ids == NULL) {
582 err(1, "realloc");
583 }
584 }
585 ids[nids++] = id;
586 }
587
588 /* now loop over quota-enabled filesystems */
589 nfst = getmntinfo(&fst, MNT_WAIT);
590 if (nfst == 0)
591 errx(1, "no filesystems mounted!");
592
593 for (i = 0; i < nfst; i++) {
594 if ((fst[i].f_flag & ST_QUOTA) == 0)
595 continue;
596 if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
597 strcmp(fst[i].f_mntfromname, filesys) != 0)
598 continue;
599
600 qh = quota_open(fst[i].f_mntonname);
601 if (qh == NULL) {
602 err(1, "%s: quota_open", fst[i].f_mntonname);
603 }
604
605 for (j = 0; j < nids; j++) {
606 snprintf(idname, sizeof(idname), "%s %u",
607 idtype == QUOTA_IDTYPE_USER ?
608 "uid" : "gid", ids[j]);
609
610 qk.qk_idtype = idtype;
611 qk.qk_id = ids[j];
612 qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
613 if (quota_delete(qh, &qk)) {
614 err(1, "%s: quota_delete (%s blocks)",
615 fst[i].f_mntonname, idname);
616 }
617
618 qk.qk_idtype = idtype;
619 qk.qk_id = ids[j];
620 qk.qk_objtype = QUOTA_OBJTYPE_FILES;
621 if (quota_delete(qh, &qk)) {
622 if (errno == ENOENT) {
623 /*
624 * XXX ignore this case; due
625 * to a weakness in the quota
626 * proplib interface it can
627 * appear spuriously.
628 */
629 } else {
630 err(1, "%s: quota_delete (%s files)",
631 fst[i].f_mntonname, idname);
632 }
633 }
634 }
635
636 quota_close(qh);
637 }
638
639 free(ids);
640 }
641
642 ////////////////////////////////////////////////////////////
643 // editor
644
645 /*
646 * Take a list of privileges and get it edited.
647 */
648 static int
649 editit(const char *ltmpfile)
650 {
651 pid_t pid;
652 int lst;
653 char p[MAX_TMPSTR];
654 const char *ed;
655 sigset_t s, os;
656
657 sigemptyset(&s);
658 sigaddset(&s, SIGINT);
659 sigaddset(&s, SIGQUIT);
660 sigaddset(&s, SIGHUP);
661 if (sigprocmask(SIG_BLOCK, &s, &os) == -1)
662 err(1, "sigprocmask");
663 top:
664 switch ((pid = fork())) {
665 case -1:
666 if (errno == EPROCLIM) {
667 warnx("You have too many processes");
668 return 0;
669 }
670 if (errno == EAGAIN) {
671 sleep(1);
672 goto top;
673 }
674 warn("fork");
675 return 0;
676 case 0:
677 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
678 err(1, "sigprocmask");
679 setgid(getgid());
680 setuid(getuid());
681 if ((ed = getenv("EDITOR")) == (char *)0)
682 ed = _PATH_VI;
683 if (strlen(ed) + strlen(ltmpfile) + 2 >= MAX_TMPSTR) {
684 errx(1, "%s", "editor or filename too long");
685 }
686 snprintf(p, sizeof(p), "%s %s", ed, ltmpfile);
687 execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
688 err(1, "%s", ed);
689 default:
690 if (waitpid(pid, &lst, 0) == -1)
691 err(1, "waitpid");
692 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
693 err(1, "sigprocmask");
694 if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0)
695 return 0;
696 return 1;
697 }
698 }
699
700 /*
701 * Convert a quotause list to an ASCII file.
702 */
703 static int
704 writeprivs(struct quotalist *qlist, int outfd, const char *name,
705 int idtype, const char *idtypename)
706 {
707 struct quotause *qup;
708 FILE *fd;
709 char b0[32], b1[32], b2[32], b3[32];
710
711 (void)ftruncate(outfd, 0);
712 (void)lseek(outfd, (off_t)0, SEEK_SET);
713 if ((fd = fdopen(dup(outfd), "w")) == NULL)
714 errx(1, "fdopen");
715 if (name == NULL) {
716 fprintf(fd, "Default %s quotas:\n", idtypename);
717 } else {
718 fprintf(fd, "Quotas for %s %s:\n", idtypename, name);
719 }
720 for (qup = qlist->head; qup; qup = qup->next) {
721 struct quotaval *q = qup->qv;
722 fprintf(fd, "%s (%s):\n",
723 qup->fsname, qup->implementation);
724 if ((qup->flags & DEFAULT) == 0 || (qup->flags & XGRACE) != 0) {
725 fprintf(fd, "\tblocks in use: %s, "
726 "limits (soft = %s, hard = %s",
727 intprt(b1, 21, q[QO_BLK].qv_usage,
728 HN_NOSPACE | HN_B, Hflag),
729 intprt(b2, 21, q[QO_BLK].qv_softlimit,
730 HN_NOSPACE | HN_B, Hflag),
731 intprt(b3, 21, q[QO_BLK].qv_hardlimit,
732 HN_NOSPACE | HN_B, Hflag));
733 if (qup->flags & XGRACE)
734 fprintf(fd, ", ");
735 } else
736 fprintf(fd, "\tblocks: (");
737
738 if (qup->flags & (XGRACE|DEFAULT)) {
739 fprintf(fd, "grace = %s",
740 timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
741 }
742 fprintf(fd, ")\n");
743 if ((qup->flags & DEFAULT) == 0 || (qup->flags & XGRACE) != 0) {
744 fprintf(fd, "\tinodes in use: %s, "
745 "limits (soft = %s, hard = %s",
746 intprt(b1, 21, q[QO_FL].qv_usage,
747 HN_NOSPACE, Hflag),
748 intprt(b2, 21, q[QO_FL].qv_softlimit,
749 HN_NOSPACE, Hflag),
750 intprt(b3, 21, q[QO_FL].qv_hardlimit,
751 HN_NOSPACE, Hflag));
752 if (qup->flags & XGRACE)
753 fprintf(fd, ", ");
754 } else
755 fprintf(fd, "\tinodes: (");
756
757 if (qup->flags & (XGRACE|DEFAULT)) {
758 fprintf(fd, "grace = %s",
759 timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
760 }
761 fprintf(fd, ")\n");
762 }
763 fclose(fd);
764 return 1;
765 }
766
767 /*
768 * Merge changes to an ASCII file into a quotause list.
769 */
770 static int
771 readprivs(struct quotalist *qlist, int infd, int dflag)
772 {
773 struct quotause *qup;
774 FILE *fd;
775 int cnt;
776 char fsp[BUFSIZ];
777 static char line0[BUFSIZ], line1[BUFSIZ], line2[BUFSIZ];
778 static char scurb[BUFSIZ], scuri[BUFSIZ], ssoft[BUFSIZ], shard[BUFSIZ];
779 static char stime[BUFSIZ];
780 uint64_t softb, hardb, softi, hardi;
781 time_t graceb = -1, gracei = -1;
782 int version;
783
784 (void)lseek(infd, (off_t)0, SEEK_SET);
785 fd = fdopen(dup(infd), "r");
786 if (fd == NULL) {
787 warn("Can't re-read temp file");
788 return 0;
789 }
790 /*
791 * Discard title line, then read pairs of lines to process.
792 */
793 (void) fgets(line1, sizeof (line1), fd);
794 while (fgets(line0, sizeof (line0), fd) != NULL &&
795 fgets(line1, sizeof (line2), fd) != NULL &&
796 fgets(line2, sizeof (line2), fd) != NULL) {
797 if (sscanf(line0, "%s (version %d):\n", fsp, &version) != 2) {
798 warnx("%s: bad format", line0);
799 goto out;
800 }
801 #define last_char(str) ((str)[strlen(str) - 1])
802 if (last_char(line1) != '\n') {
803 warnx("%s:%s: bad format", fsp, line1);
804 goto out;
805 }
806 last_char(line1) = '\0';
807 if (last_char(line2) != '\n') {
808 warnx("%s:%s: bad format", fsp, line2);
809 goto out;
810 }
811 last_char(line2) = '\0';
812 if (dflag && version == 1) {
813 if (sscanf(line1,
814 "\tblocks: (grace = %s\n", stime) != 1) {
815 warnx("%s:%s: bad format", fsp, line1);
816 goto out;
817 }
818 if (last_char(stime) != ')') {
819 warnx("%s:%s: bad format", fsp, line1);
820 goto out;
821 }
822 last_char(stime) = '\0';
823 if (timeprd(stime, &graceb) != 0) {
824 warnx("%s:%s: bad number", fsp, stime);
825 goto out;
826 }
827 if (sscanf(line2,
828 "\tinodes: (grace = %s\n", stime) != 1) {
829 warnx("%s:%s: bad format", fsp, line2);
830 goto out;
831 }
832 if (last_char(stime) != ')') {
833 warnx("%s:%s: bad format", fsp, line2);
834 goto out;
835 }
836 last_char(stime) = '\0';
837 if (timeprd(stime, &gracei) != 0) {
838 warnx("%s:%s: bad number", fsp, stime);
839 goto out;
840 }
841 } else {
842 cnt = sscanf(line1,
843 "\tblocks in use: %s limits (soft = %s hard = %s "
844 "grace = %s", scurb, ssoft, shard, stime);
845 if (cnt == 3) {
846 if (version != 1 ||
847 last_char(scurb) != ',' ||
848 last_char(ssoft) != ',' ||
849 last_char(shard) != ')') {
850 warnx("%s:%s: bad format %d",
851 fsp, line1, cnt);
852 goto out;
853 }
854 stime[0] = '\0';
855 } else if (cnt == 4) {
856 if (version < 2 ||
857 last_char(scurb) != ',' ||
858 last_char(ssoft) != ',' ||
859 last_char(shard) != ',' ||
860 last_char(stime) != ')') {
861 warnx("%s:%s: bad format %d",
862 fsp, line1, cnt);
863 goto out;
864 }
865 } else {
866 warnx("%s: %s: bad format cnt %d", fsp, line1,
867 cnt);
868 goto out;
869 }
870 /* drop last char which is ',' or ')' */
871 last_char(scurb) = '\0';
872 last_char(ssoft) = '\0';
873 last_char(shard) = '\0';
874 last_char(stime) = '\0';
875
876 if (intrd(ssoft, &softb, HN_B) != 0) {
877 warnx("%s:%s: bad number", fsp, ssoft);
878 goto out;
879 }
880 if (intrd(shard, &hardb, HN_B) != 0) {
881 warnx("%s:%s: bad number", fsp, shard);
882 goto out;
883 }
884 if (cnt == 4) {
885 if (timeprd(stime, &graceb) != 0) {
886 warnx("%s:%s: bad number", fsp, stime);
887 goto out;
888 }
889 }
890
891 cnt = sscanf(line2,
892 "\tinodes in use: %s limits (soft = %s hard = %s "
893 "grace = %s", scuri, ssoft, shard, stime);
894 if (cnt == 3) {
895 if (version != 1 ||
896 last_char(scuri) != ',' ||
897 last_char(ssoft) != ',' ||
898 last_char(shard) != ')') {
899 warnx("%s:%s: bad format %d",
900 fsp, line2, cnt);
901 goto out;
902 }
903 stime[0] = '\0';
904 } else if (cnt == 4) {
905 if (version < 2 ||
906 last_char(scuri) != ',' ||
907 last_char(ssoft) != ',' ||
908 last_char(shard) != ',' ||
909 last_char(stime) != ')') {
910 warnx("%s:%s: bad format %d",
911 fsp, line2, cnt);
912 goto out;
913 }
914 } else {
915 warnx("%s: %s: bad format", fsp, line2);
916 goto out;
917 }
918 /* drop last char which is ',' or ')' */
919 last_char(scuri) = '\0';
920 last_char(ssoft) = '\0';
921 last_char(shard) = '\0';
922 last_char(stime) = '\0';
923 if (intrd(ssoft, &softi, 0) != 0) {
924 warnx("%s:%s: bad number", fsp, ssoft);
925 goto out;
926 }
927 if (intrd(shard, &hardi, 0) != 0) {
928 warnx("%s:%s: bad number", fsp, shard);
929 goto out;
930 }
931 if (cnt == 4) {
932 if (timeprd(stime, &gracei) != 0) {
933 warnx("%s:%s: bad number", fsp, stime);
934 goto out;
935 }
936 }
937 }
938 for (qup = qlist->head; qup; qup = qup->next) {
939 struct quotaval *q = qup->qv;
940 char b1[32], b2[32];
941 if (strcmp(fsp, qup->fsname))
942 continue;
943 if (version == 1 && dflag) {
944 q[QO_BLK].qv_grace = graceb;
945 q[QO_FL].qv_grace = gracei;
946 qup->flags |= FOUND;
947 continue;
948 }
949
950 if (strcmp(intprt(b1, 21, q[QO_BLK].qv_usage,
951 HN_NOSPACE | HN_B, Hflag),
952 scurb) != 0 ||
953 strcmp(intprt(b2, 21, q[QO_FL].qv_usage,
954 HN_NOSPACE, Hflag),
955 scuri) != 0) {
956 warnx("%s: cannot change current allocation",
957 fsp);
958 break;
959 }
960 /*
961 * Cause time limit to be reset when the quota
962 * is next used if previously had no soft limit
963 * or were under it, but now have a soft limit
964 * and are over it.
965 */
966 if (q[QO_BLK].qv_usage &&
967 q[QO_BLK].qv_usage >= softb &&
968 (q[QO_BLK].qv_softlimit == 0 ||
969 q[QO_BLK].qv_usage < q[QO_BLK].qv_softlimit))
970 q[QO_BLK].qv_expiretime = 0;
971 if (q[QO_FL].qv_usage &&
972 q[QO_FL].qv_usage >= softi &&
973 (q[QO_FL].qv_softlimit == 0 ||
974 q[QO_FL].qv_usage < q[QO_FL].qv_softlimit))
975 q[QO_FL].qv_expiretime = 0;
976 q[QO_BLK].qv_softlimit = softb;
977 q[QO_BLK].qv_hardlimit = hardb;
978 if (version == 2)
979 q[QO_BLK].qv_grace = graceb;
980 q[QO_FL].qv_softlimit = softi;
981 q[QO_FL].qv_hardlimit = hardi;
982 if (version == 2)
983 q[QO_FL].qv_grace = gracei;
984 qup->flags |= FOUND;
985 }
986 }
987 out:
988 fclose(fd);
989 /*
990 * Disable quotas for any filesystems that have not been found.
991 */
992 for (qup = qlist->head; qup; qup = qup->next) {
993 struct quotaval *q = qup->qv;
994 if (qup->flags & FOUND) {
995 qup->flags &= ~FOUND;
996 continue;
997 }
998 q[QO_BLK].qv_softlimit = UQUAD_MAX;
999 q[QO_BLK].qv_hardlimit = UQUAD_MAX;
1000 q[QO_BLK].qv_grace = 0;
1001 q[QO_FL].qv_softlimit = UQUAD_MAX;
1002 q[QO_FL].qv_hardlimit = UQUAD_MAX;
1003 q[QO_FL].qv_grace = 0;
1004 }
1005 return 1;
1006 }
1007
1008 ////////////////////////////////////////////////////////////
1009 // actions
1010
1011 static void
1012 replicate(const char *fs, int idtype, const char *protoname,
1013 char **names, int numnames)
1014 {
1015 long protoid, id;
1016 struct quotalist *protoprivs;
1017 struct quotause *qup;
1018 int i;
1019
1020 if ((protoid = getidbyname(protoname, idtype)) == -1)
1021 exit(1);
1022 protoprivs = getprivs(protoid, 0, idtype, fs);
1023 for (qup = protoprivs->head; qup; qup = qup->next) {
1024 qup->qv[QO_BLK].qv_expiretime = 0;
1025 qup->qv[QO_FL].qv_expiretime = 0;
1026 }
1027 for (i=0; i<numnames; i++) {
1028 id = getidbyname(names[i], idtype);
1029 if (id == -1)
1030 continue;
1031 putprivs(id, idtype, protoprivs);
1032 }
1033 /* XXX */
1034 /* quotalist_destroy(protoprivs); */
1035 }
1036
1037 static void
1038 assign(const char *fs, int idtype,
1039 char *soft, char *hard, char *grace,
1040 char **names, int numnames)
1041 {
1042 struct quotalist *curprivs;
1043 struct quotause *lqup;
1044 u_int64_t softb, hardb, softi, hardi;
1045 time_t graceb, gracei;
1046 char *str;
1047 long id;
1048 int dflag;
1049 int i;
1050
1051 if (soft) {
1052 str = strsep(&soft, "/");
1053 if (str[0] == '\0' || soft == NULL || soft[0] == '\0')
1054 usage();
1055
1056 if (intrd(str, &softb, HN_B) != 0)
1057 errx(1, "%s: bad number", str);
1058 if (intrd(soft, &softi, 0) != 0)
1059 errx(1, "%s: bad number", soft);
1060 }
1061 if (hard) {
1062 str = strsep(&hard, "/");
1063 if (str[0] == '\0' || hard == NULL || hard[0] == '\0')
1064 usage();
1065
1066 if (intrd(str, &hardb, HN_B) != 0)
1067 errx(1, "%s: bad number", str);
1068 if (intrd(hard, &hardi, 0) != 0)
1069 errx(1, "%s: bad number", hard);
1070 }
1071 if (grace) {
1072 str = strsep(&grace, "/");
1073 if (str[0] == '\0' || grace == NULL || grace[0] == '\0')
1074 usage();
1075
1076 if (timeprd(str, &graceb) != 0)
1077 errx(1, "%s: bad number", str);
1078 if (timeprd(grace, &gracei) != 0)
1079 errx(1, "%s: bad number", grace);
1080 }
1081 for (i=0; i<numnames; i++) {
1082 if (names[i] == NULL) {
1083 id = 0;
1084 dflag = 1;
1085 } else {
1086 id = getidbyname(names[i], idtype);
1087 if (id == -1)
1088 continue;
1089 dflag = 0;
1090 }
1091
1092 curprivs = getprivs(id, dflag, idtype, fs);
1093 for (lqup = curprivs->head; lqup; lqup = lqup->next) {
1094 struct quotaval *q = lqup->qv;
1095 if (soft) {
1096 if (!dflag && softb &&
1097 q[QO_BLK].qv_usage >= softb &&
1098 (q[QO_BLK].qv_softlimit == 0 ||
1099 q[QO_BLK].qv_usage <
1100 q[QO_BLK].qv_softlimit))
1101 q[QO_BLK].qv_expiretime = 0;
1102 if (!dflag && softi &&
1103 q[QO_FL].qv_usage >= softb &&
1104 (q[QO_FL].qv_softlimit == 0 ||
1105 q[QO_FL].qv_usage <
1106 q[QO_FL].qv_softlimit))
1107 q[QO_FL].qv_expiretime = 0;
1108 q[QO_BLK].qv_softlimit = softb;
1109 q[QO_FL].qv_softlimit = softi;
1110 }
1111 if (hard) {
1112 q[QO_BLK].qv_hardlimit = hardb;
1113 q[QO_FL].qv_hardlimit = hardi;
1114 }
1115 if (grace) {
1116 q[QO_BLK].qv_grace = graceb;
1117 q[QO_FL].qv_grace = gracei;
1118 }
1119 }
1120 putprivs(id, idtype, curprivs);
1121 quotalist_destroy(curprivs);
1122 }
1123 }
1124
1125 static void
1126 clear(const char *fs, int idtype, char **names, int numnames)
1127 {
1128 clearpriv(numnames, names, fs, idtype);
1129 }
1130
1131 static void
1132 editone(const char *fs, int idtype, const char *name,
1133 int tmpfd, const char *tmppath)
1134 {
1135 struct quotalist *curprivs;
1136 long id;
1137 int dflag;
1138
1139 if (name == NULL) {
1140 id = 0;
1141 dflag = 1;
1142 } else {
1143 id = getidbyname(name, idtype);
1144 if (id == -1)
1145 return;
1146 dflag = 0;
1147 }
1148 curprivs = getprivs(id, dflag, idtype, fs);
1149
1150 if (writeprivs(curprivs, tmpfd, name, idtype,
1151 curprivs->idtypename) == 0)
1152 goto fail;
1153
1154 if (editit(tmppath) == 0)
1155 goto fail;
1156
1157 if (readprivs(curprivs, tmpfd, dflag) == 0)
1158 goto fail;
1159
1160 putprivs(id, idtype, curprivs);
1161 fail:
1162 quotalist_destroy(curprivs);
1163 }
1164
1165 static void
1166 edit(const char *fs, int idtype, char **names, int numnames)
1167 {
1168 char tmppath[] = _PATH_TMPFILE;
1169 int tmpfd, i;
1170
1171 tmpfd = mkstemp(tmppath);
1172 fchown(tmpfd, getuid(), getgid());
1173
1174 for (i=0; i<numnames; i++) {
1175 editone(fs, idtype, names[i], tmpfd, tmppath);
1176 }
1177
1178 close(tmpfd);
1179 unlink(tmppath);
1180 }
1181
1182 ////////////////////////////////////////////////////////////
1183 // main
1184
1185 static void
1186 usage(void)
1187 {
1188 const char *p = getprogname();
1189 fprintf(stderr,
1190 "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] "
1191 "-d | <username> ...\n"
1192 "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] "
1193 "-d | <groupname> ...\n"
1194 "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1195 "-d | <username> ...\n"
1196 "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1197 "-d | <groupname> ...\n"
1198 "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n"
1199 "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n",
1200 p, p, p, p, p, p);
1201 exit(1);
1202 }
1203
1204 int
1205 main(int argc, char *argv[])
1206 {
1207 int idtype;
1208 char *protoname;
1209 char *soft = NULL, *hard = NULL, *grace = NULL;
1210 char *fs = NULL;
1211 int ch;
1212 int pflag = 0;
1213 int cflag = 0;
1214 int dflag = 0;
1215
1216 if (argc < 2)
1217 usage();
1218 if (getuid())
1219 errx(1, "permission denied");
1220 protoname = NULL;
1221 idtype = QUOTA_IDTYPE_USER;
1222 while ((ch = getopt(argc, argv, "Hcdugp:s:h:t:f:")) != -1) {
1223 switch(ch) {
1224 case 'H':
1225 Hflag++;
1226 break;
1227 case 'c':
1228 cflag++;
1229 break;
1230 case 'd':
1231 dflag++;
1232 break;
1233 case 'p':
1234 protoname = optarg;
1235 pflag++;
1236 break;
1237 case 'g':
1238 idtype = QUOTA_IDTYPE_GROUP;
1239 break;
1240 case 'u':
1241 idtype = QUOTA_IDTYPE_USER;
1242 break;
1243 case 's':
1244 soft = optarg;
1245 break;
1246 case 'h':
1247 hard = optarg;
1248 break;
1249 case 't':
1250 grace = optarg;
1251 break;
1252 case 'f':
1253 fs = optarg;
1254 break;
1255 default:
1256 usage();
1257 }
1258 }
1259 argc -= optind;
1260 argv += optind;
1261
1262 if (pflag) {
1263 if (soft || hard || grace || dflag || cflag)
1264 usage();
1265 replicate(fs, idtype, protoname, argv, argc);
1266 } else if (soft || hard || grace) {
1267 if (cflag)
1268 usage();
1269 if (dflag) {
1270 /* use argv[argc], which is null, to mean 'default' */
1271 argc++;
1272 }
1273 assign(fs, idtype, soft, hard, grace, argv, argc);
1274 } else if (cflag) {
1275 if (dflag)
1276 usage();
1277 clear(fs, idtype, argv, argc);
1278 } else {
1279 if (dflag) {
1280 /* use argv[argc], which is null, to mean 'default' */
1281 argc++;
1282 }
1283 edit(fs, idtype, argv, argc);
1284 }
1285 return 0;
1286 }
1287