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