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