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