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