edquota.c revision 1.35 1 /* $NetBSD: edquota.c,v 1.35 2011/07/10 07:54:49 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.35 2011/07/10 07:54:49 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/quotaprop.h>
60 #include <quota/quota.h>
61 #include <ufs/ufs/quota1.h>
62 #include <sys/quota.h>
63
64 #include <assert.h>
65 #include <err.h>
66 #include <errno.h>
67 #include <fstab.h>
68 #include <pwd.h>
69 #include <grp.h>
70 #include <ctype.h>
71 #include <signal.h>
72 #include <stdio.h>
73 #include <stdlib.h>
74 #include <string.h>
75 #include <unistd.h>
76
77 #include "printquota.h"
78 #include "getvfsquota.h"
79 #include "quotautil.h"
80
81 #include "pathnames.h"
82
83 static const char *quotagroup = QUOTAGROUP;
84 static char tmpfil[] = _PATH_TMPFILE;
85
86 struct quotause {
87 struct quotause *next;
88 long flags;
89 struct ufs_quota_entry qe[QUOTA_NLIMITS];
90 char fsname[MAXPATHLEN + 1];
91 char *qfname;
92 };
93 #define FOUND 0x01
94 #define QUOTA2 0x02
95 #define DEFAULT 0x04
96
97 #define MAX_TMPSTR (100+MAXPATHLEN)
98
99 static void usage(void) __dead;
100 static struct quotause * getprivs(long, int, const char *, int);
101 static struct quotause * getprivs2(long, int, const char *, int);
102 static struct quotause * getprivs1(long, int, const char *);
103 static void putprivs(uint32_t, int, struct quotause *);
104 static void putprivs2(uint32_t, int, struct quotause *);
105 static void putprivs1(uint32_t, int, struct quotause *);
106 static int editit(const char *);
107 static int writeprivs(struct quotause *, int, const char *, int);
108 static int readprivs(struct quotause *, int);
109 static void freeprivs(struct quotause *);
110 static void clearpriv(int, char **, const char *, int);
111
112 static int Hflag = 0;
113 static int Dflag = 0;
114 static int dflag = 0;
115
116 /* more compact form of constants */
117 #define QL_BLK QUOTA_LIMIT_BLOCK
118 #define QL_FL QUOTA_LIMIT_FILE
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 quotaclass)
130 {
131 struct passwd *pw;
132 struct group *gr;
133
134 if (alldigits(name))
135 return atoi(name);
136 switch(quotaclass) {
137 case QUOTA_CLASS_USER:
138 if ((pw = getpwnam(name)) != NULL)
139 return pw->pw_uid;
140 warnx("%s: no such user", name);
141 break;
142 case QUOTA_CLASS_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", quotaclass);
149 break;
150 }
151 sleep(1);
152 return -1;
153 }
154
155 ////////////////////////////////////////////////////////////
156 // quotause operations
157
158 /*
159 * Free a quotause structure.
160 */
161 static void
162 quotause_destroy(struct quotause *qup)
163 {
164 free(qup->qfname);
165 free(qup);
166 }
167
168 /*
169 * Free a list of quotause structures.
170 */
171 static void
172 freeprivs(struct quotause *quplist)
173 {
174 struct quotause *qup, *nextqup;
175
176 for (qup = quplist; qup; qup = nextqup) {
177 nextqup = qup->next;
178 quotause_destroy(qup);
179 }
180 }
181
182 ////////////////////////////////////////////////////////////
183 // ffs quota v1
184
185 static void
186 putprivs1(uint32_t id, int quotaclass, struct quotause *qup)
187 {
188 struct dqblk dqblk;
189 int fd;
190
191 ufsqe2dqblk(qup->qe, &dqblk);
192 assert((qup->flags & DEFAULT) == 0);
193
194 if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
195 warnx("open `%s'", qup->qfname);
196 } else {
197 (void)lseek(fd,
198 (off_t)(id * (long)sizeof (struct dqblk)),
199 SEEK_SET);
200 if (write(fd, &dqblk, sizeof (struct dqblk)) !=
201 sizeof (struct dqblk))
202 warnx("writing `%s'", qup->qfname);
203 close(fd);
204 }
205 }
206
207 static struct quotause *
208 getprivs1(long id, int quotaclass, const char *filesys)
209 {
210 struct fstab *fs;
211 char qfpathname[MAXPATHLEN];
212 struct quotause *qup;
213 struct dqblk dqblk;
214 int fd;
215
216 setfsent();
217 while ((fs = getfsent()) != NULL) {
218 if (strcmp(fs->fs_vfstype, "ffs"))
219 continue;
220 if (strcmp(fs->fs_spec, filesys) == 0 ||
221 strcmp(fs->fs_file, filesys) == 0)
222 break;
223 }
224 if (fs == NULL)
225 return NULL;
226
227 if (!hasquota(qfpathname, sizeof(qfpathname), fs,
228 ufsclass2qtype(quotaclass)))
229 return NULL;
230 if ((qup = malloc(sizeof(*qup))) == NULL)
231 err(1, "out of memory");
232 strcpy(qup->fsname, fs->fs_file);
233 if ((fd = open(qfpathname, O_RDONLY)) < 0) {
234 fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
235 if (fd < 0 && errno != ENOENT) {
236 warnx("open `%s'", qfpathname);
237 quotause_destroy(qup);
238 return NULL;
239 }
240 warnx("Creating quota file %s", qfpathname);
241 sleep(3);
242 (void)fchown(fd, getuid(),
243 getidbyname(quotagroup, QUOTA_CLASS_GROUP));
244 (void)fchmod(fd, 0640);
245 }
246 (void)lseek(fd, (off_t)(id * sizeof(struct dqblk)),
247 SEEK_SET);
248 switch (read(fd, &dqblk, sizeof(struct dqblk))) {
249 case 0: /* EOF */
250 /*
251 * Convert implicit 0 quota (EOF)
252 * into an explicit one (zero'ed dqblk)
253 */
254 memset(&dqblk, 0, sizeof(struct dqblk));
255 break;
256
257 case sizeof(struct dqblk): /* OK */
258 break;
259
260 default: /* ERROR */
261 warn("read error in `%s'", qfpathname);
262 close(fd);
263 quotause_destroy(qup);
264 return NULL;
265 }
266 close(fd);
267 qup->qfname = qfpathname;
268 endfsent();
269 dqblk2ufsqe(&dqblk, qup->qe);
270 return qup;
271 }
272
273 ////////////////////////////////////////////////////////////
274 // ffs quota v2
275
276 static struct quotause *
277 getprivs2(long id, int quotaclass, const char *filesys, int defaultq)
278 {
279 struct quotause *qup;
280 int8_t version;
281
282 if ((qup = malloc(sizeof(*qup))) == NULL)
283 err(1, "out of memory");
284 memset(qup, 0, sizeof(*qup));
285 strcpy(qup->fsname, filesys);
286 if (defaultq)
287 qup->flags |= DEFAULT;
288 if (!getvfsquota(filesys, qup->qe, &version,
289 id, quotaclass, defaultq, Dflag)) {
290 /* no entry, get default entry */
291 if (!getvfsquota(filesys, qup->qe, &version,
292 id, quotaclass, 1, Dflag)) {
293 free(qup);
294 return NULL;
295 }
296 }
297 if (version == 2)
298 qup->flags |= QUOTA2;
299 return qup;
300 }
301
302 static void
303 putprivs2(uint32_t id, int quotaclass, struct quotause *qup)
304 {
305
306 prop_dictionary_t dict, data, cmd;
307 prop_array_t cmds, datas;
308 struct plistref pref;
309 int8_t error8;
310 uint64_t *valuesp[QUOTA_NLIMITS];
311
312 valuesp[QL_BLK] =
313 &qup->qe[QL_BLK].ufsqe_hardlimit;
314 valuesp[QL_FL] =
315 &qup->qe[QL_FL].ufsqe_hardlimit;
316
317 data = quota64toprop(id, (qup->flags & DEFAULT) ? 1 : 0,
318 valuesp, ufs_quota_entry_names, UFS_QUOTA_NENTRIES,
319 ufs_quota_limit_names, QUOTA_NLIMITS);
320
321 if (data == NULL)
322 err(1, "quota64toprop(id)");
323
324 dict = quota_prop_create();
325 cmds = prop_array_create();
326 datas = prop_array_create();
327
328 if (dict == NULL || cmds == NULL || datas == NULL) {
329 errx(1, "can't allocate proplist");
330 }
331
332 if (!prop_array_add_and_rel(datas, data))
333 err(1, "prop_array_add(data)");
334
335 if (!quota_prop_add_command(cmds, "set",
336 ufs_quota_class_names[quotaclass], datas))
337 err(1, "prop_add_command");
338 if (!prop_dictionary_set(dict, "commands", cmds))
339 err(1, "prop_dictionary_set(command)");
340 if (Dflag)
341 printf("message to kernel:\n%s\n",
342 prop_dictionary_externalize(dict));
343
344 if (!prop_dictionary_send_syscall(dict, &pref))
345 err(1, "prop_dictionary_send_syscall");
346 prop_object_release(dict);
347
348 if (quotactl(qup->fsname, &pref) != 0)
349 err(1, "quotactl");
350
351 if ((errno = prop_dictionary_recv_syscall(&pref, &dict)) != 0) {
352 err(1, "prop_dictionary_recv_syscall");
353 }
354
355 if (Dflag)
356 printf("reply from kernel:\n%s\n",
357 prop_dictionary_externalize(dict));
358
359 if ((errno = quota_get_cmds(dict, &cmds)) != 0) {
360 err(1, "quota_get_cmds");
361 }
362 /* only one command, no need to iter */
363 cmd = prop_array_get(cmds, 0);
364 if (cmd == NULL)
365 err(1, "prop_array_get(cmd)");
366
367 if (!prop_dictionary_get_int8(cmd, "return", &error8))
368 err(1, "prop_get(return)");
369
370 if (error8) {
371 errno = error8;
372 if (qup->flags & DEFAULT)
373 warn("set default %s quota",
374 ufs_quota_class_names[quotaclass]);
375 else
376 warn("set %s quota for %u",
377 ufs_quota_class_names[quotaclass], id);
378 }
379 prop_object_release(dict);
380 }
381
382 ////////////////////////////////////////////////////////////
383 // quota format switch
384
385 /*
386 * Collect the requested quota information.
387 */
388 static struct quotause *
389 getprivs(long id, int quotaclass, const char *filesys, int defaultq)
390 {
391 struct statvfs *fst;
392 int nfst, i;
393 struct quotause *qup, *quptail = NULL;
394 struct quotause *quphead = NULL;
395
396 nfst = getmntinfo(&fst, MNT_WAIT);
397 if (nfst == 0)
398 errx(1, "no filesystems mounted!");
399
400 for (i = 0; i < nfst; i++) {
401 if ((fst[i].f_flag & ST_QUOTA) == 0)
402 continue;
403 if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
404 strcmp(fst[i].f_mntfromname, filesys) != 0)
405 continue;
406 qup = getprivs2(id, quotaclass, fst[i].f_mntonname, defaultq);
407 if (qup == NULL)
408 return NULL;
409 if (quphead == NULL)
410 quphead = qup;
411 else
412 quptail->next = qup;
413 quptail = qup;
414 qup->next = 0;
415 }
416
417 if (filesys && quphead == NULL) {
418 if (defaultq)
419 errx(1, "no default quota for version 1");
420 /* if we get there, filesys is not mounted. try the old way */
421 qup = getprivs1(id, quotaclass, filesys);
422 if (qup == NULL)
423 return NULL;
424 if (quphead == NULL)
425 quphead = qup;
426 else
427 quptail->next = qup;
428 quptail = qup;
429 qup->next = 0;
430 }
431 return quphead;
432 }
433
434 /*
435 * Store the requested quota information.
436 */
437 void
438 putprivs(uint32_t id, int quotaclass, struct quotause *quplist)
439 {
440 struct quotause *qup;
441
442 for (qup = quplist; qup; qup = qup->next) {
443 if (qup->qfname == NULL)
444 putprivs2(id, quotaclass, qup);
445 else
446 putprivs1(id, quotaclass, qup);
447 }
448 }
449
450 static void
451 clearpriv(int argc, char **argv, const char *filesys, int quotaclass)
452 {
453 prop_array_t cmds, datas;
454 prop_dictionary_t protodict, dict, data, cmd;
455 struct plistref pref;
456 bool ret;
457 struct statvfs *fst;
458 int nfst, i;
459 int8_t error8;
460 int id;
461
462 /* build a generic command */
463 protodict = quota_prop_create();
464 cmds = prop_array_create();
465 datas = prop_array_create();
466 if (protodict == NULL || cmds == NULL || datas == NULL) {
467 errx(1, "can't allocate proplist");
468 }
469
470 for ( ; argc > 0; argc--, argv++) {
471 if ((id = getidbyname(*argv, quotaclass)) == -1)
472 continue;
473 data = prop_dictionary_create();
474 if (data == NULL)
475 errx(1, "can't allocate proplist");
476
477 ret = prop_dictionary_set_uint32(data, "id", id);
478 if (!ret)
479 err(1, "prop_dictionary_set(id)");
480 if (!prop_array_add_and_rel(datas, data))
481 err(1, "prop_array_add(data)");
482 }
483 if (!quota_prop_add_command(cmds, "clear",
484 ufs_quota_class_names[quotaclass], datas))
485 err(1, "prop_add_command");
486
487 if (!prop_dictionary_set(protodict, "commands", cmds))
488 err(1, "prop_dictionary_set(command)");
489
490 /* now loop over quota-enabled filesystems */
491 nfst = getmntinfo(&fst, MNT_WAIT);
492 if (nfst == 0)
493 errx(1, "no filesystems mounted!");
494
495 for (i = 0; i < nfst; i++) {
496 if ((fst[i].f_flag & ST_QUOTA) == 0)
497 continue;
498 if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
499 strcmp(fst[i].f_mntfromname, filesys) != 0)
500 continue;
501 if (Dflag) {
502 fprintf(stderr, "message to kernel for %s:\n%s\n",
503 fst[i].f_mntonname,
504 prop_dictionary_externalize(protodict));
505 }
506
507 if (!prop_dictionary_send_syscall(protodict, &pref))
508 err(1, "prop_dictionary_send_syscall");
509 if (quotactl(fst[i].f_mntonname, &pref) != 0)
510 err(1, "quotactl");
511
512 if ((errno = prop_dictionary_recv_syscall(&pref, &dict)) != 0) {
513 err(1, "prop_dictionary_recv_syscall");
514 }
515
516 if (Dflag) {
517 fprintf(stderr, "reply from kernel for %s:\n%s\n",
518 fst[i].f_mntonname,
519 prop_dictionary_externalize(dict));
520 }
521 if ((errno = quota_get_cmds(dict, &cmds)) != 0) {
522 err(1, "quota_get_cmds");
523 }
524 /* only one command, no need to iter */
525 cmd = prop_array_get(cmds, 0);
526 if (cmd == NULL)
527 err(1, "prop_array_get(cmd)");
528
529 if (!prop_dictionary_get_int8(cmd, "return", &error8))
530 err(1, "prop_get(return)");
531 if (error8) {
532 errno = error8;
533 warn("clear %s quota entries on %s",
534 ufs_quota_class_names[quotaclass],
535 fst[i].f_mntonname);
536 }
537 prop_object_release(dict);
538 }
539 prop_object_release(protodict);
540 }
541
542 ////////////////////////////////////////////////////////////
543 // editor
544
545 /*
546 * Take a list of privileges and get it edited.
547 */
548 static int
549 editit(const char *ltmpfile)
550 {
551 pid_t pid;
552 int lst;
553 char p[MAX_TMPSTR];
554 const char *ed;
555 sigset_t s, os;
556
557 sigemptyset(&s);
558 sigaddset(&s, SIGINT);
559 sigaddset(&s, SIGQUIT);
560 sigaddset(&s, SIGHUP);
561 if (sigprocmask(SIG_BLOCK, &s, &os) == -1)
562 err(1, "sigprocmask");
563 top:
564 switch ((pid = fork())) {
565 case -1:
566 if (errno == EPROCLIM) {
567 warnx("You have too many processes");
568 return 0;
569 }
570 if (errno == EAGAIN) {
571 sleep(1);
572 goto top;
573 }
574 warn("fork");
575 return 0;
576 case 0:
577 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
578 err(1, "sigprocmask");
579 setgid(getgid());
580 setuid(getuid());
581 if ((ed = getenv("EDITOR")) == (char *)0)
582 ed = _PATH_VI;
583 if (strlen(ed) + strlen(ltmpfile) + 2 >= MAX_TMPSTR) {
584 errx(1, "%s", "editor or filename too long");
585 }
586 snprintf(p, sizeof(p), "%s %s", ed, ltmpfile);
587 execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
588 err(1, "%s", ed);
589 default:
590 if (waitpid(pid, &lst, 0) == -1)
591 err(1, "waitpid");
592 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
593 err(1, "sigprocmask");
594 if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0)
595 return 0;
596 return 1;
597 }
598 }
599
600 /*
601 * Convert a quotause list to an ASCII file.
602 */
603 static int
604 writeprivs(struct quotause *quplist, int outfd, const char *name,
605 int quotaclass)
606 {
607 struct quotause *qup;
608 FILE *fd;
609 char b0[32], b1[32], b2[32], b3[32];
610
611 (void)ftruncate(outfd, 0);
612 (void)lseek(outfd, (off_t)0, SEEK_SET);
613 if ((fd = fdopen(dup(outfd), "w")) == NULL)
614 errx(1, "fdopen `%s'", tmpfil);
615 if (dflag) {
616 fprintf(fd, "Default %s quotas:\n",
617 ufs_quota_class_names[quotaclass]);
618 } else {
619 fprintf(fd, "Quotas for %s %s:\n",
620 ufs_quota_class_names[quotaclass], name);
621 }
622 for (qup = quplist; qup; qup = qup->next) {
623 struct ufs_quota_entry *q = qup->qe;
624 fprintf(fd, "%s (version %d):\n",
625 qup->fsname, (qup->flags & QUOTA2) ? 2 : 1);
626 if ((qup->flags & DEFAULT) == 0 || (qup->flags & QUOTA2) != 0) {
627 fprintf(fd, "\tblocks in use: %s, "
628 "limits (soft = %s, hard = %s",
629 intprt(b1, 21, q[QL_BLK].ufsqe_cur,
630 HN_NOSPACE | HN_B, Hflag),
631 intprt(b2, 21, q[QL_BLK].ufsqe_softlimit,
632 HN_NOSPACE | HN_B, Hflag),
633 intprt(b3, 21, q[QL_BLK].ufsqe_hardlimit,
634 HN_NOSPACE | HN_B, Hflag));
635 if (qup->flags & QUOTA2)
636 fprintf(fd, ", ");
637 } else
638 fprintf(fd, "\tblocks: (");
639
640 if (qup->flags & (QUOTA2|DEFAULT)) {
641 fprintf(fd, "grace = %s",
642 timepprt(b0, 21, q[QL_BLK].ufsqe_grace, Hflag));
643 }
644 fprintf(fd, ")\n");
645 if ((qup->flags & DEFAULT) == 0 || (qup->flags & QUOTA2) != 0) {
646 fprintf(fd, "\tinodes in use: %s, "
647 "limits (soft = %s, hard = %s",
648 intprt(b1, 21, q[QL_FL].ufsqe_cur,
649 HN_NOSPACE, Hflag),
650 intprt(b2, 21, q[QL_FL].ufsqe_softlimit,
651 HN_NOSPACE, Hflag),
652 intprt(b3, 21, q[QL_FL].ufsqe_hardlimit,
653 HN_NOSPACE, Hflag));
654 if (qup->flags & QUOTA2)
655 fprintf(fd, ", ");
656 } else
657 fprintf(fd, "\tinodes: (");
658
659 if (qup->flags & (QUOTA2|DEFAULT)) {
660 fprintf(fd, "grace = %s",
661 timepprt(b0, 21, q[QL_FL].ufsqe_grace, Hflag));
662 }
663 fprintf(fd, ")\n");
664 }
665 fclose(fd);
666 return 1;
667 }
668
669 /*
670 * Merge changes to an ASCII file into a quotause list.
671 */
672 static int
673 readprivs(struct quotause *quplist, int infd)
674 {
675 struct quotause *qup;
676 FILE *fd;
677 int cnt;
678 char fsp[BUFSIZ];
679 static char line0[BUFSIZ], line1[BUFSIZ], line2[BUFSIZ];
680 static char scurb[BUFSIZ], scuri[BUFSIZ], ssoft[BUFSIZ], shard[BUFSIZ];
681 static char stime[BUFSIZ];
682 uint64_t softb, hardb, softi, hardi;
683 time_t graceb = -1, gracei = -1;
684 int version;
685
686 (void)lseek(infd, (off_t)0, SEEK_SET);
687 fd = fdopen(dup(infd), "r");
688 if (fd == NULL) {
689 warn("Can't re-read temp file");
690 return 0;
691 }
692 /*
693 * Discard title line, then read pairs of lines to process.
694 */
695 (void) fgets(line1, sizeof (line1), fd);
696 while (fgets(line0, sizeof (line0), fd) != NULL &&
697 fgets(line1, sizeof (line2), fd) != NULL &&
698 fgets(line2, sizeof (line2), fd) != NULL) {
699 if (sscanf(line0, "%s (version %d):\n", fsp, &version) != 2) {
700 warnx("%s: bad format", line0);
701 goto out;
702 }
703 #define last_char(str) ((str)[strlen(str) - 1])
704 if (last_char(line1) != '\n') {
705 warnx("%s:%s: bad format", fsp, line1);
706 goto out;
707 }
708 last_char(line1) = '\0';
709 if (last_char(line2) != '\n') {
710 warnx("%s:%s: bad format", fsp, line2);
711 goto out;
712 }
713 last_char(line2) = '\0';
714 if (dflag && version == 1) {
715 if (sscanf(line1,
716 "\tblocks: (grace = %s\n", stime) != 1) {
717 warnx("%s:%s: bad format", fsp, line1);
718 goto out;
719 }
720 if (last_char(stime) != ')') {
721 warnx("%s:%s: bad format", fsp, line1);
722 goto out;
723 }
724 last_char(stime) = '\0';
725 if (timeprd(stime, &graceb) != 0) {
726 warnx("%s:%s: bad number", fsp, stime);
727 goto out;
728 }
729 if (sscanf(line2,
730 "\tinodes: (grace = %s\n", stime) != 1) {
731 warnx("%s:%s: bad format", fsp, line2);
732 goto out;
733 }
734 if (last_char(stime) != ')') {
735 warnx("%s:%s: bad format", fsp, line2);
736 goto out;
737 }
738 last_char(stime) = '\0';
739 if (timeprd(stime, &gracei) != 0) {
740 warnx("%s:%s: bad number", fsp, stime);
741 goto out;
742 }
743 } else {
744 cnt = sscanf(line1,
745 "\tblocks in use: %s limits (soft = %s hard = %s "
746 "grace = %s", scurb, ssoft, shard, stime);
747 if (cnt == 3) {
748 if (version != 1 ||
749 last_char(scurb) != ',' ||
750 last_char(ssoft) != ',' ||
751 last_char(shard) != ')') {
752 warnx("%s:%s: bad format %d",
753 fsp, line1, cnt);
754 goto out;
755 }
756 stime[0] = '\0';
757 } else if (cnt == 4) {
758 if (version < 2 ||
759 last_char(scurb) != ',' ||
760 last_char(ssoft) != ',' ||
761 last_char(shard) != ',' ||
762 last_char(stime) != ')') {
763 warnx("%s:%s: bad format %d",
764 fsp, line1, cnt);
765 goto out;
766 }
767 } else {
768 warnx("%s: %s: bad format cnt %d", fsp, line1,
769 cnt);
770 goto out;
771 }
772 /* drop last char which is ',' or ')' */
773 last_char(scurb) = '\0';
774 last_char(ssoft) = '\0';
775 last_char(shard) = '\0';
776 last_char(stime) = '\0';
777
778 if (intrd(ssoft, &softb, HN_B) != 0) {
779 warnx("%s:%s: bad number", fsp, ssoft);
780 goto out;
781 }
782 if (intrd(shard, &hardb, HN_B) != 0) {
783 warnx("%s:%s: bad number", fsp, shard);
784 goto out;
785 }
786 if (cnt == 4) {
787 if (timeprd(stime, &graceb) != 0) {
788 warnx("%s:%s: bad number", fsp, stime);
789 goto out;
790 }
791 }
792
793 cnt = sscanf(line2,
794 "\tinodes in use: %s limits (soft = %s hard = %s "
795 "grace = %s", scuri, ssoft, shard, stime);
796 if (cnt == 3) {
797 if (version != 1 ||
798 last_char(scuri) != ',' ||
799 last_char(ssoft) != ',' ||
800 last_char(shard) != ')') {
801 warnx("%s:%s: bad format %d",
802 fsp, line2, cnt);
803 goto out;
804 }
805 stime[0] = '\0';
806 } else if (cnt == 4) {
807 if (version < 2 ||
808 last_char(scuri) != ',' ||
809 last_char(ssoft) != ',' ||
810 last_char(shard) != ',' ||
811 last_char(stime) != ')') {
812 warnx("%s:%s: bad format %d",
813 fsp, line2, cnt);
814 goto out;
815 }
816 } else {
817 warnx("%s: %s: bad format", fsp, line2);
818 goto out;
819 }
820 /* drop last char which is ',' or ')' */
821 last_char(scuri) = '\0';
822 last_char(ssoft) = '\0';
823 last_char(shard) = '\0';
824 last_char(stime) = '\0';
825 if (intrd(ssoft, &softi, 0) != 0) {
826 warnx("%s:%s: bad number", fsp, ssoft);
827 goto out;
828 }
829 if (intrd(shard, &hardi, 0) != 0) {
830 warnx("%s:%s: bad number", fsp, shard);
831 goto out;
832 }
833 if (cnt == 4) {
834 if (timeprd(stime, &gracei) != 0) {
835 warnx("%s:%s: bad number", fsp, stime);
836 goto out;
837 }
838 }
839 }
840 for (qup = quplist; qup; qup = qup->next) {
841 struct ufs_quota_entry *q = qup->qe;
842 char b1[32], b2[32];
843 if (strcmp(fsp, qup->fsname))
844 continue;
845 if (version == 1 && dflag) {
846 q[QL_BLK].ufsqe_grace = graceb;
847 q[QL_FL].ufsqe_grace = gracei;
848 qup->flags |= FOUND;
849 continue;
850 }
851
852 if (strcmp(intprt(b1, 21, q[QL_BLK].ufsqe_cur,
853 HN_NOSPACE | HN_B, Hflag),
854 scurb) != 0 ||
855 strcmp(intprt(b2, 21, q[QL_FL].ufsqe_cur,
856 HN_NOSPACE, Hflag),
857 scuri) != 0) {
858 warnx("%s: cannot change current allocation",
859 fsp);
860 break;
861 }
862 /*
863 * Cause time limit to be reset when the quota
864 * is next used if previously had no soft limit
865 * or were under it, but now have a soft limit
866 * and are over it.
867 */
868 if (q[QL_BLK].ufsqe_cur &&
869 q[QL_BLK].ufsqe_cur >= softb &&
870 (q[QL_BLK].ufsqe_softlimit == 0 ||
871 q[QL_BLK].ufsqe_cur < q[QL_BLK].ufsqe_softlimit))
872 q[QL_BLK].ufsqe_time = 0;
873 if (q[QL_FL].ufsqe_cur &&
874 q[QL_FL].ufsqe_cur >= softi &&
875 (q[QL_FL].ufsqe_softlimit == 0 ||
876 q[QL_FL].ufsqe_cur < q[QL_FL].ufsqe_softlimit))
877 q[QL_FL].ufsqe_time = 0;
878 q[QL_BLK].ufsqe_softlimit = softb;
879 q[QL_BLK].ufsqe_hardlimit = hardb;
880 if (version == 2)
881 q[QL_BLK].ufsqe_grace = graceb;
882 q[QL_FL].ufsqe_softlimit = softi;
883 q[QL_FL].ufsqe_hardlimit = hardi;
884 if (version == 2)
885 q[QL_FL].ufsqe_grace = gracei;
886 qup->flags |= FOUND;
887 }
888 }
889 out:
890 fclose(fd);
891 /*
892 * Disable quotas for any filesystems that have not been found.
893 */
894 for (qup = quplist; qup; qup = qup->next) {
895 struct ufs_quota_entry *q = qup->qe;
896 if (qup->flags & FOUND) {
897 qup->flags &= ~FOUND;
898 continue;
899 }
900 q[QL_BLK].ufsqe_softlimit = UQUAD_MAX;
901 q[QL_BLK].ufsqe_hardlimit = UQUAD_MAX;
902 q[QL_BLK].ufsqe_grace = 0;
903 q[QL_FL].ufsqe_softlimit = UQUAD_MAX;
904 q[QL_FL].ufsqe_hardlimit = UQUAD_MAX;
905 q[QL_FL].ufsqe_grace = 0;
906 }
907 return 1;
908 }
909
910 ////////////////////////////////////////////////////////////
911 // main
912
913 static void
914 usage(void)
915 {
916 const char *p = getprogname();
917 fprintf(stderr,
918 "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] "
919 "-d | <username> ...\n"
920 "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] "
921 "-d | <groupname> ...\n"
922 "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
923 "-d | <username> ...\n"
924 "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
925 "-d | <groupname> ...\n"
926 "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n"
927 "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n",
928 p, p, p, p, p, p);
929 exit(1);
930 }
931
932 int
933 main(int argc, char *argv[])
934 {
935 struct quotause *qup, *protoprivs, *curprivs;
936 long id, protoid;
937 int quotaclass, tmpfd;
938 char *protoname;
939 char *soft = NULL, *hard = NULL, *grace = NULL;
940 char *fs = NULL;
941 int ch;
942 int pflag = 0;
943 int cflag = 0;
944
945 if (argc < 2)
946 usage();
947 if (getuid())
948 errx(1, "permission denied");
949 protoname = NULL;
950 quotaclass = QUOTA_CLASS_USER;
951 while ((ch = getopt(argc, argv, "DHcdugp:s:h:t:f:")) != -1) {
952 switch(ch) {
953 case 'D':
954 Dflag++;
955 break;
956 case 'H':
957 Hflag++;
958 break;
959 case 'c':
960 cflag++;
961 break;
962 case 'd':
963 dflag++;
964 break;
965 case 'p':
966 protoname = optarg;
967 pflag++;
968 break;
969 case 'g':
970 quotaclass = QUOTA_CLASS_GROUP;
971 break;
972 case 'u':
973 quotaclass = QUOTA_CLASS_USER;
974 break;
975 case 's':
976 soft = optarg;
977 break;
978 case 'h':
979 hard = optarg;
980 break;
981 case 't':
982 grace = optarg;
983 break;
984 case 'f':
985 fs = optarg;
986 break;
987 default:
988 usage();
989 }
990 }
991 argc -= optind;
992 argv += optind;
993
994 if (pflag) {
995 if (soft || hard || grace || dflag || cflag)
996 usage();
997 if ((protoid = getidbyname(protoname, quotaclass)) == -1)
998 return 1;
999 protoprivs = getprivs(protoid, quotaclass, fs, 0);
1000 for (qup = protoprivs; qup; qup = qup->next) {
1001 qup->qe[QL_BLK].ufsqe_time = 0;
1002 qup->qe[QL_FL].ufsqe_time = 0;
1003 }
1004 while (argc-- > 0) {
1005 if ((id = getidbyname(*argv++, quotaclass)) < 0)
1006 continue;
1007 putprivs(id, quotaclass, protoprivs);
1008 }
1009 return 0;
1010 }
1011 if (soft || hard || grace) {
1012 struct quotause *lqup;
1013 u_int64_t softb, hardb, softi, hardi;
1014 time_t graceb, gracei;
1015 char *str;
1016
1017 if (cflag)
1018 usage();
1019 if (soft) {
1020 str = strsep(&soft, "/");
1021 if (str[0] == '\0' || soft == NULL || soft[0] == '\0')
1022 usage();
1023
1024 if (intrd(str, &softb, HN_B) != 0)
1025 errx(1, "%s: bad number", str);
1026 if (intrd(soft, &softi, 0) != 0)
1027 errx(1, "%s: bad number", soft);
1028 }
1029 if (hard) {
1030 str = strsep(&hard, "/");
1031 if (str[0] == '\0' || hard == NULL || hard[0] == '\0')
1032 usage();
1033
1034 if (intrd(str, &hardb, HN_B) != 0)
1035 errx(1, "%s: bad number", str);
1036 if (intrd(hard, &hardi, 0) != 0)
1037 errx(1, "%s: bad number", hard);
1038 }
1039 if (grace) {
1040 str = strsep(&grace, "/");
1041 if (str[0] == '\0' || grace == NULL || grace[0] == '\0')
1042 usage();
1043
1044 if (timeprd(str, &graceb) != 0)
1045 errx(1, "%s: bad number", str);
1046 if (timeprd(grace, &gracei) != 0)
1047 errx(1, "%s: bad number", grace);
1048 }
1049 if (dflag) {
1050 curprivs = getprivs(0, quotaclass, fs, 1);
1051 for (lqup = curprivs; lqup; lqup = lqup->next) {
1052 struct ufs_quota_entry *q = lqup->qe;
1053 if (soft) {
1054 q[QL_BLK].ufsqe_softlimit = softb;
1055 q[QL_FL].ufsqe_softlimit = softi;
1056 }
1057 if (hard) {
1058 q[QL_BLK].ufsqe_hardlimit = hardb;
1059 q[QL_FL].ufsqe_hardlimit = hardi;
1060 }
1061 if (grace) {
1062 q[QL_BLK].ufsqe_grace = graceb;
1063 q[QL_FL].ufsqe_grace = gracei;
1064 }
1065 }
1066 putprivs(0, quotaclass, curprivs);
1067 freeprivs(curprivs);
1068 return 0;
1069 }
1070 for ( ; argc > 0; argc--, argv++) {
1071 if ((id = getidbyname(*argv, quotaclass)) == -1)
1072 continue;
1073 curprivs = getprivs(id, quotaclass, fs, 0);
1074 for (lqup = curprivs; lqup; lqup = lqup->next) {
1075 struct ufs_quota_entry *q = lqup->qe;
1076 if (soft) {
1077 if (softb &&
1078 q[QL_BLK].ufsqe_cur >= softb &&
1079 (q[QL_BLK].ufsqe_softlimit == 0 ||
1080 q[QL_BLK].ufsqe_cur <
1081 q[QL_BLK].ufsqe_softlimit))
1082 q[QL_BLK].ufsqe_time = 0;
1083 if (softi &&
1084 q[QL_FL].ufsqe_cur >= softb &&
1085 (q[QL_FL].ufsqe_softlimit == 0 ||
1086 q[QL_FL].ufsqe_cur <
1087 q[QL_FL].ufsqe_softlimit))
1088 q[QL_FL].ufsqe_time = 0;
1089 q[QL_BLK].ufsqe_softlimit = softb;
1090 q[QL_FL].ufsqe_softlimit = softi;
1091 }
1092 if (hard) {
1093 q[QL_BLK].ufsqe_hardlimit = hardb;
1094 q[QL_FL].ufsqe_hardlimit = hardi;
1095 }
1096 if (grace) {
1097 q[QL_BLK].ufsqe_grace = graceb;
1098 q[QL_FL].ufsqe_grace = gracei;
1099 }
1100 }
1101 putprivs(id, quotaclass, curprivs);
1102 freeprivs(curprivs);
1103 }
1104 return 0;
1105 }
1106 if (cflag) {
1107 if (dflag)
1108 usage();
1109 clearpriv(argc, argv, fs, quotaclass);
1110 return 0;
1111 }
1112 tmpfd = mkstemp(tmpfil);
1113 fchown(tmpfd, getuid(), getgid());
1114 if (dflag) {
1115 curprivs = getprivs(0, quotaclass, fs, 1);
1116 if (writeprivs(curprivs, tmpfd, NULL, quotaclass) &&
1117 editit(tmpfil) && readprivs(curprivs, tmpfd))
1118 putprivs(0, quotaclass, curprivs);
1119 freeprivs(curprivs);
1120 }
1121 for ( ; argc > 0; argc--, argv++) {
1122 if ((id = getidbyname(*argv, quotaclass)) == -1)
1123 continue;
1124 curprivs = getprivs(id, quotaclass, fs, 0);
1125 if (writeprivs(curprivs, tmpfd, *argv, quotaclass) == 0)
1126 continue;
1127 if (editit(tmpfil) && readprivs(curprivs, tmpfd))
1128 putprivs(id, quotaclass, curprivs);
1129 freeprivs(curprivs);
1130 }
1131 close(tmpfd);
1132 unlink(tmpfil);
1133 return 0;
1134 }
1135