1 /*
2 * Copyright (c) 1980, 1990, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Robert Elz at The University of Melbourne.
7 *
8 * %sccs.include.redist.c%
9 */
10
11 #ifndef lint
12 static char copyright[] =
13 "@(#) Copyright (c) 1980, 1990, 1993\n\
14 The Regents of the University of California. All rights reserved.\n";
15 #endif /* not lint */
16
17 #ifndef lint
18 static char sccsid[] = "@(#)edquota.c 8.3 (Berkeley) 04/27/95";
19 #endif /* not lint */
20
21 /*
22 * Disk quota editor.
23 */
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #include <sys/file.h>
27 #include <sys/wait.h>
28 #include <sys/queue.h>
29 #include <ufs/ufs/quota.h>
30 #include <errno.h>
31 #include <fstab.h>
32 #include <pwd.h>
33 #include <grp.h>
34 #include <ctype.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include "pathnames.h"
39
40 char *qfname = QUOTAFILENAME;
41 char *qfextension[] = INITQFNAMES;
42 char *quotagroup = QUOTAGROUP;
43 char tmpfil[] = _PATH_TMP;
44
45 struct quotause {
46 struct quotause *next;
47 long flags;
48 struct dqblk dqblk;
49 char fsname[MAXPATHLEN + 1];
50 char qfname[1]; /* actually longer */
51 } *getprivs();
52 #define FOUND 0x01
53
main(argc,argv)54 main(argc, argv)
55 register char **argv;
56 int argc;
57 {
58 register struct quotause *qup, *protoprivs, *curprivs;
59 extern char *optarg;
60 extern int optind;
61 register long id, protoid;
62 register int quotatype, tmpfd;
63 char *protoname, ch;
64 int tflag = 0, pflag = 0;
65
66 if (argc < 2)
67 usage();
68 if (getuid()) {
69 fprintf(stderr, "edquota: permission denied\n");
70 exit(1);
71 }
72 quotatype = USRQUOTA;
73 while ((ch = getopt(argc, argv, "ugtp:")) != EOF) {
74 switch(ch) {
75 case 'p':
76 protoname = optarg;
77 pflag++;
78 break;
79 case 'g':
80 quotatype = GRPQUOTA;
81 break;
82 case 'u':
83 quotatype = USRQUOTA;
84 break;
85 case 't':
86 tflag++;
87 break;
88 default:
89 usage();
90 }
91 }
92 argc -= optind;
93 argv += optind;
94 if (pflag) {
95 if ((protoid = getentry(protoname, quotatype)) == -1)
96 exit(1);
97 protoprivs = getprivs(protoid, quotatype);
98 for (qup = protoprivs; qup; qup = qup->next) {
99 qup->dqblk.dqb_btime = 0;
100 qup->dqblk.dqb_itime = 0;
101 }
102 while (argc-- > 0) {
103 if ((id = getentry(*argv++, quotatype)) < 0)
104 continue;
105 putprivs(id, quotatype, protoprivs);
106 }
107 exit(0);
108 }
109 tmpfd = mkstemp(tmpfil);
110 fchown(tmpfd, getuid(), getgid());
111 if (tflag) {
112 protoprivs = getprivs(0, quotatype);
113 if (writetimes(protoprivs, tmpfd, quotatype) == 0)
114 exit(1);
115 if (editit(tmpfil) && readtimes(protoprivs, tmpfd))
116 putprivs(0, quotatype, protoprivs);
117 freeprivs(protoprivs);
118 exit(0);
119 }
120 for ( ; argc > 0; argc--, argv++) {
121 if ((id = getentry(*argv, quotatype)) == -1)
122 continue;
123 curprivs = getprivs(id, quotatype);
124 if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
125 continue;
126 if (editit(tmpfil) && readprivs(curprivs, tmpfd))
127 putprivs(id, quotatype, curprivs);
128 freeprivs(curprivs);
129 }
130 close(tmpfd);
131 unlink(tmpfil);
132 exit(0);
133 }
134
usage()135 usage()
136 {
137 fprintf(stderr, "%s%s%s%s",
138 "Usage: edquota [-u] [-p username] username ...\n",
139 "\tedquota -g [-p groupname] groupname ...\n",
140 "\tedquota [-u] -t\n", "\tedquota -g -t\n");
141 exit(1);
142 }
143
144 /*
145 * This routine converts a name for a particular quota type to
146 * an identifier. This routine must agree with the kernel routine
147 * getinoquota as to the interpretation of quota types.
148 */
getentry(name,quotatype)149 getentry(name, quotatype)
150 char *name;
151 int quotatype;
152 {
153 struct passwd *pw;
154 struct group *gr;
155
156 if (alldigits(name))
157 return (atoi(name));
158 switch(quotatype) {
159 case USRQUOTA:
160 if (pw = getpwnam(name))
161 return (pw->pw_uid);
162 fprintf(stderr, "%s: no such user\n", name);
163 break;
164 case GRPQUOTA:
165 if (gr = getgrnam(name))
166 return (gr->gr_gid);
167 fprintf(stderr, "%s: no such group\n", name);
168 break;
169 default:
170 fprintf(stderr, "%d: unknown quota type\n", quotatype);
171 break;
172 }
173 sleep(1);
174 return (-1);
175 }
176
177 /*
178 * Collect the requested quota information.
179 */
180 struct quotause *
getprivs(id,quotatype)181 getprivs(id, quotatype)
182 register long id;
183 int quotatype;
184 {
185 register struct fstab *fs;
186 register struct quotause *qup, *quptail;
187 struct quotause *quphead;
188 int qcmd, qupsize, fd;
189 char *qfpathname;
190 static int warned = 0;
191 extern int errno;
192
193 setfsent();
194 quphead = (struct quotause *)0;
195 qcmd = QCMD(Q_GETQUOTA, quotatype);
196 while (fs = getfsent()) {
197 if (strcmp(fs->fs_vfstype, "ufs"))
198 continue;
199 if (!hasquota(fs, quotatype, &qfpathname))
200 continue;
201 qupsize = sizeof(*qup) + strlen(qfpathname);
202 if ((qup = (struct quotause *)malloc(qupsize)) == NULL) {
203 fprintf(stderr, "edquota: out of memory\n");
204 exit(2);
205 }
206 if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
207 if (errno == EOPNOTSUPP && !warned) {
208 warned++;
209 fprintf(stderr, "Warning: %s\n",
210 "Quotas are not compiled into this kernel");
211 sleep(3);
212 }
213 if ((fd = open(qfpathname, O_RDONLY)) < 0) {
214 fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
215 if (fd < 0 && errno != ENOENT) {
216 perror(qfpathname);
217 free(qup);
218 continue;
219 }
220 fprintf(stderr, "Creating quota file %s\n",
221 qfpathname);
222 sleep(3);
223 (void) fchown(fd, getuid(),
224 getentry(quotagroup, GRPQUOTA));
225 (void) fchmod(fd, 0640);
226 }
227 lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET);
228 switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
229 case 0: /* EOF */
230 /*
231 * Convert implicit 0 quota (EOF)
232 * into an explicit one (zero'ed dqblk)
233 */
234 bzero((caddr_t)&qup->dqblk,
235 sizeof(struct dqblk));
236 break;
237
238 case sizeof(struct dqblk): /* OK */
239 break;
240
241 default: /* ERROR */
242 fprintf(stderr, "edquota: read error in ");
243 perror(qfpathname);
244 close(fd);
245 free(qup);
246 continue;
247 }
248 close(fd);
249 }
250 strcpy(qup->qfname, qfpathname);
251 strcpy(qup->fsname, fs->fs_file);
252 if (quphead == NULL)
253 quphead = qup;
254 else
255 quptail->next = qup;
256 quptail = qup;
257 qup->next = 0;
258 }
259 endfsent();
260 return (quphead);
261 }
262
263 /*
264 * Store the requested quota information.
265 */
putprivs(id,quotatype,quplist)266 putprivs(id, quotatype, quplist)
267 long id;
268 int quotatype;
269 struct quotause *quplist;
270 {
271 register struct quotause *qup;
272 int qcmd, fd;
273
274 qcmd = QCMD(Q_SETQUOTA, quotatype);
275 for (qup = quplist; qup; qup = qup->next) {
276 if (quotactl(qup->fsname, qcmd, id, &qup->dqblk) == 0)
277 continue;
278 if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
279 perror(qup->qfname);
280 } else {
281 lseek(fd,
282 (off_t)(id * (long)sizeof (struct dqblk)), L_SET);
283 if (write(fd, &qup->dqblk, sizeof (struct dqblk)) !=
284 sizeof (struct dqblk)) {
285 fprintf(stderr, "edquota: ");
286 perror(qup->qfname);
287 }
288 close(fd);
289 }
290 }
291 }
292
293 /*
294 * Take a list of priviledges and get it edited.
295 */
editit(tmpfile)296 editit(tmpfile)
297 char *tmpfile;
298 {
299 long omask;
300 int pid, stat;
301 extern char *getenv();
302
303 omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
304 top:
305 if ((pid = fork()) < 0) {
306 extern errno;
307
308 if (errno == EPROCLIM) {
309 fprintf(stderr, "You have too many processes\n");
310 return(0);
311 }
312 if (errno == EAGAIN) {
313 sleep(1);
314 goto top;
315 }
316 perror("fork");
317 return (0);
318 }
319 if (pid == 0) {
320 register char *ed;
321
322 sigsetmask(omask);
323 setgid(getgid());
324 setuid(getuid());
325 if ((ed = getenv("EDITOR")) == (char *)0)
326 ed = _PATH_VI;
327 execlp(ed, ed, tmpfile, 0);
328 perror(ed);
329 exit(1);
330 }
331 waitpid(pid, &stat, 0);
332 sigsetmask(omask);
333 if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0)
334 return (0);
335 return (1);
336 }
337
338 /*
339 * Convert a quotause list to an ASCII file.
340 */
341 writeprivs(quplist, outfd, name, quotatype)
342 struct quotause *quplist;
343 int outfd;
344 char *name;
345 int quotatype;
346 {
347 register struct quotause *qup;
348 FILE *fd;
349
350 ftruncate(outfd, 0);
351 lseek(outfd, 0, L_SET);
352 if ((fd = fdopen(dup(outfd), "w")) == NULL) {
353 fprintf(stderr, "edquota: ");
354 perror(tmpfil);
355 exit(1);
356 }
357 fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
358 for (qup = quplist; qup; qup = qup->next) {
359 fprintf(fd, "%s: %s %d, limits (soft = %d, hard = %d)\n",
360 qup->fsname, "blocks in use:",
361 dbtob(qup->dqblk.dqb_curblocks) / 1024,
362 dbtob(qup->dqblk.dqb_bsoftlimit) / 1024,
363 dbtob(qup->dqblk.dqb_bhardlimit) / 1024);
364 fprintf(fd, "%s %d, limits (soft = %d, hard = %d)\n",
365 "\tinodes in use:", qup->dqblk.dqb_curinodes,
366 qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit);
367 }
368 fclose(fd);
369 return (1);
370 }
371
372 /*
373 * Merge changes to an ASCII file into a quotause list.
374 */
375 readprivs(quplist, infd)
376 struct quotause *quplist;
377 int infd;
378 {
379 register struct quotause *qup;
380 FILE *fd;
381 int cnt;
382 register char *cp;
383 struct dqblk dqblk;
384 char *fsp, line1[BUFSIZ], line2[BUFSIZ];
385
386 lseek(infd, 0, L_SET);
387 fd = fdopen(dup(infd), "r");
388 if (fd == NULL) {
389 fprintf(stderr, "Can't re-read temp file!!\n");
390 return (0);
391 }
392 /*
393 * Discard title line, then read pairs of lines to process.
394 */
395 (void) fgets(line1, sizeof (line1), fd);
396 while (fgets(line1, sizeof (line1), fd) != NULL &&
397 fgets(line2, sizeof (line2), fd) != NULL) {
398 if ((fsp = strtok(line1, " \t:")) == NULL) {
399 fprintf(stderr, "%s: bad format\n", line1);
400 return (0);
401 }
402 if ((cp = strtok((char *)0, "\n")) == NULL) {
403 fprintf(stderr, "%s: %s: bad format\n", fsp,
404 &fsp[strlen(fsp) + 1]);
405 return (0);
406 }
407 cnt = sscanf(cp,
408 " blocks in use: %d, limits (soft = %d, hard = %d)",
409 &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit,
410 &dqblk.dqb_bhardlimit);
411 if (cnt != 3) {
412 fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
413 return (0);
414 }
415 dqblk.dqb_curblocks = btodb(dqblk.dqb_curblocks * 1024);
416 dqblk.dqb_bsoftlimit = btodb(dqblk.dqb_bsoftlimit * 1024);
417 dqblk.dqb_bhardlimit = btodb(dqblk.dqb_bhardlimit * 1024);
418 if ((cp = strtok(line2, "\n")) == NULL) {
419 fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
420 return (0);
421 }
422 cnt = sscanf(cp,
423 "\tinodes in use: %d, limits (soft = %d, hard = %d)",
424 &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit,
425 &dqblk.dqb_ihardlimit);
426 if (cnt != 3) {
427 fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
428 return (0);
429 }
430 for (qup = quplist; qup; qup = qup->next) {
431 if (strcmp(fsp, qup->fsname))
432 continue;
433 /*
434 * Cause time limit to be reset when the quota
435 * is next used if previously had no soft limit
436 * or were under it, but now have a soft limit
437 * and are over it.
438 */
439 if (dqblk.dqb_bsoftlimit &&
440 qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
441 (qup->dqblk.dqb_bsoftlimit == 0 ||
442 qup->dqblk.dqb_curblocks <
443 qup->dqblk.dqb_bsoftlimit))
444 qup->dqblk.dqb_btime = 0;
445 if (dqblk.dqb_isoftlimit &&
446 qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
447 (qup->dqblk.dqb_isoftlimit == 0 ||
448 qup->dqblk.dqb_curinodes <
449 qup->dqblk.dqb_isoftlimit))
450 qup->dqblk.dqb_itime = 0;
451 qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
452 qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
453 qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
454 qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
455 qup->flags |= FOUND;
456 if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
457 dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes)
458 break;
459 fprintf(stderr,
460 "%s: cannot change current allocation\n", fsp);
461 break;
462 }
463 }
464 fclose(fd);
465 /*
466 * Disable quotas for any filesystems that have not been found.
467 */
468 for (qup = quplist; qup; qup = qup->next) {
469 if (qup->flags & FOUND) {
470 qup->flags &= ~FOUND;
471 continue;
472 }
473 qup->dqblk.dqb_bsoftlimit = 0;
474 qup->dqblk.dqb_bhardlimit = 0;
475 qup->dqblk.dqb_isoftlimit = 0;
476 qup->dqblk.dqb_ihardlimit = 0;
477 }
478 return (1);
479 }
480
481 /*
482 * Convert a quotause list to an ASCII file of grace times.
483 */
484 writetimes(quplist, outfd, quotatype)
485 struct quotause *quplist;
486 int outfd;
487 int quotatype;
488 {
489 register struct quotause *qup;
490 char *cvtstoa();
491 FILE *fd;
492
493 ftruncate(outfd, 0);
494 lseek(outfd, 0, L_SET);
495 if ((fd = fdopen(dup(outfd), "w")) == NULL) {
496 fprintf(stderr, "edquota: ");
497 perror(tmpfil);
498 exit(1);
499 }
500 fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
501 fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
502 qfextension[quotatype]);
503 for (qup = quplist; qup; qup = qup->next) {
504 fprintf(fd, "%s: block grace period: %s, ",
505 qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
506 fprintf(fd, "file grace period: %s\n",
507 cvtstoa(qup->dqblk.dqb_itime));
508 }
509 fclose(fd);
510 return (1);
511 }
512
513 /*
514 * Merge changes of grace times in an ASCII file into a quotause list.
515 */
516 readtimes(quplist, infd)
517 struct quotause *quplist;
518 int infd;
519 {
520 register struct quotause *qup;
521 FILE *fd;
522 int cnt;
523 register char *cp;
524 time_t itime, btime, iseconds, bseconds;
525 char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
526
527 lseek(infd, 0, L_SET);
528 fd = fdopen(dup(infd), "r");
529 if (fd == NULL) {
530 fprintf(stderr, "Can't re-read temp file!!\n");
531 return (0);
532 }
533 /*
534 * Discard two title lines, then read lines to process.
535 */
536 (void) fgets(line1, sizeof (line1), fd);
537 (void) fgets(line1, sizeof (line1), fd);
538 while (fgets(line1, sizeof (line1), fd) != NULL) {
539 if ((fsp = strtok(line1, " \t:")) == NULL) {
540 fprintf(stderr, "%s: bad format\n", line1);
541 return (0);
542 }
543 if ((cp = strtok((char *)0, "\n")) == NULL) {
544 fprintf(stderr, "%s: %s: bad format\n", fsp,
545 &fsp[strlen(fsp) + 1]);
546 return (0);
547 }
548 cnt = sscanf(cp,
549 " block grace period: %d %s file grace period: %d %s",
550 &btime, bunits, &itime, iunits);
551 if (cnt != 4) {
552 fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
553 return (0);
554 }
555 if (cvtatos(btime, bunits, &bseconds) == 0)
556 return (0);
557 if (cvtatos(itime, iunits, &iseconds) == 0)
558 return (0);
559 for (qup = quplist; qup; qup = qup->next) {
560 if (strcmp(fsp, qup->fsname))
561 continue;
562 qup->dqblk.dqb_btime = bseconds;
563 qup->dqblk.dqb_itime = iseconds;
564 qup->flags |= FOUND;
565 break;
566 }
567 }
568 fclose(fd);
569 /*
570 * reset default grace periods for any filesystems
571 * that have not been found.
572 */
573 for (qup = quplist; qup; qup = qup->next) {
574 if (qup->flags & FOUND) {
575 qup->flags &= ~FOUND;
576 continue;
577 }
578 qup->dqblk.dqb_btime = 0;
579 qup->dqblk.dqb_itime = 0;
580 }
581 return (1);
582 }
583
584 /*
585 * Convert seconds to ASCII times.
586 */
587 char *
cvtstoa(time)588 cvtstoa(time)
589 time_t time;
590 {
591 static char buf[20];
592
593 if (time % (24 * 60 * 60) == 0) {
594 time /= 24 * 60 * 60;
595 sprintf(buf, "%d day%s", time, time == 1 ? "" : "s");
596 } else if (time % (60 * 60) == 0) {
597 time /= 60 * 60;
598 sprintf(buf, "%d hour%s", time, time == 1 ? "" : "s");
599 } else if (time % 60 == 0) {
600 time /= 60;
601 sprintf(buf, "%d minute%s", time, time == 1 ? "" : "s");
602 } else
603 sprintf(buf, "%d second%s", time, time == 1 ? "" : "s");
604 return (buf);
605 }
606
607 /*
608 * Convert ASCII input times to seconds.
609 */
cvtatos(time,units,seconds)610 cvtatos(time, units, seconds)
611 time_t time;
612 char *units;
613 time_t *seconds;
614 {
615
616 if (bcmp(units, "second", 6) == 0)
617 *seconds = time;
618 else if (bcmp(units, "minute", 6) == 0)
619 *seconds = time * 60;
620 else if (bcmp(units, "hour", 4) == 0)
621 *seconds = time * 60 * 60;
622 else if (bcmp(units, "day", 3) == 0)
623 *seconds = time * 24 * 60 * 60;
624 else {
625 printf("%s: bad units, specify %s\n", units,
626 "days, hours, minutes, or seconds");
627 return (0);
628 }
629 return (1);
630 }
631
632 /*
633 * Free a list of quotause structures.
634 */
635 freeprivs(quplist)
636 struct quotause *quplist;
637 {
638 register struct quotause *qup, *nextqup;
639
640 for (qup = quplist; qup; qup = nextqup) {
641 nextqup = qup->next;
642 free(qup);
643 }
644 }
645
646 /*
647 * Check whether a string is completely composed of digits.
648 */
alldigits(s)649 alldigits(s)
650 register char *s;
651 {
652 register c;
653
654 c = *s++;
655 do {
656 if (!isdigit(c))
657 return (0);
658 } while (c = *s++);
659 return (1);
660 }
661
662 /*
663 * Check to see if a particular quota is to be enabled.
664 */
hasquota(fs,type,qfnamep)665 hasquota(fs, type, qfnamep)
666 register struct fstab *fs;
667 int type;
668 char **qfnamep;
669 {
670 register char *opt;
671 char *cp, *index(), *strtok();
672 static char initname, usrname[100], grpname[100];
673 static char buf[BUFSIZ];
674
675 if (!initname) {
676 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
677 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
678 initname = 1;
679 }
680 strcpy(buf, fs->fs_mntops);
681 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
682 if (cp = index(opt, '='))
683 *cp++ = '\0';
684 if (type == USRQUOTA && strcmp(opt, usrname) == 0)
685 break;
686 if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
687 break;
688 }
689 if (!opt)
690 return (0);
691 if (cp) {
692 *qfnamep = cp;
693 return (1);
694 }
695 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
696 *qfnamep = buf;
697 return (1);
698 }
699