1/*      $NetBSD$ */
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
42static char sccsid[] = "from: @(#)edquota.c	8.3 (Berkeley) 4/27/95";
43#else
44__RCSID("$NetBSD$");
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.h>
60
61#include <assert.h>
62#include <err.h>
63#include <errno.h>
64#include <fstab.h>
65#include <pwd.h>
66#include <grp.h>
67#include <ctype.h>
68#include <signal.h>
69#include <stdbool.h>
70#include <stdio.h>
71#include <stdlib.h>
72#include <string.h>
73#include <unistd.h>
74
75#include "printquota.h"
76
77#include "pathnames.h"
78
79/*
80 * XXX. Ideally we shouldn't compile this in, but it'll take some
81 * reworking to avoid it and it'll be ok for now.
82 */
83#define EDQUOTA_NUMOBJTYPES	2
84
85#if 0
86static const char *quotagroup = QUOTAGROUP;
87#endif
88
89#define MAX_TMPSTR	(100+MAXPATHLEN)
90
91enum sources {
92	SRC_EDITED,	/* values came from user */
93	SRC_QUOTA,	/* values came from a specific quota entry */
94	SRC_DEFAULT,	/* values were copied from the default quota entry */
95	SRC_CLEAR,	/* values arose by zeroing out a quota entry */
96};
97
98struct quotause {
99	struct	quotause *next;
100	unsigned found:1,	/* found after running editor */
101		xgrace:1,	/* grace periods are per-id */
102		isdefault:1;
103
104	struct	quotaval qv[EDQUOTA_NUMOBJTYPES];
105	enum sources source[EDQUOTA_NUMOBJTYPES];
106	char	fsname[MAXPATHLEN + 1];
107	char	implementation[32];
108};
109
110struct quotalist {
111	struct quotause *head;
112	struct quotause *tail;
113	char *idtypename;
114};
115
116static void	usage(void) __dead;
117
118static int Hflag = 0;
119
120/* more compact form of constants */
121#define QO_BLK QUOTA_OBJTYPE_BLOCKS
122#define QO_FL  QUOTA_OBJTYPE_FILES
123
124////////////////////////////////////////////////////////////
125// support code
126
127/*
128 * This routine converts a name for a particular quota class to
129 * an identifier. This routine must agree with the kernel routine
130 * getinoquota as to the interpretation of quota classes.
131 */
132static int
133getidbyname(const char *name, int idtype)
134{
135	struct passwd *pw;
136	struct group *gr;
137
138	if (alldigits(name))
139		return atoi(name);
140	switch (idtype) {
141	case QUOTA_IDTYPE_USER:
142		if ((pw = getpwnam(name)) != NULL)
143			return pw->pw_uid;
144		warnx("%s: no such user", name);
145		break;
146	case QUOTA_IDTYPE_GROUP:
147		if ((gr = getgrnam(name)) != NULL)
148			return gr->gr_gid;
149		warnx("%s: no such group", name);
150		break;
151	default:
152		warnx("%d: unknown quota type", idtype);
153		break;
154	}
155	sleep(1);
156	return -1;
157}
158
159/*
160 * check if a source is "real" (reflects actual data) or not
161 */
162static bool
163source_is_real(enum sources source)
164{
165	switch (source) {
166	    case SRC_EDITED:
167	    case SRC_QUOTA:
168		return true;
169	    case SRC_DEFAULT:
170	    case SRC_CLEAR:
171		return false;
172	}
173	assert(!"encountered invalid source");
174	return false;
175}
176
177/*
178 * some simple string tools
179 */
180
181static /*const*/ char *
182skipws(/*const*/ char *s)
183{
184	while (*s == ' ' || *s == '\t') {
185		s++;
186	}
187	return s;
188}
189
190static /*const*/ char *
191skipword(/*const*/ char *s)
192{
193	while (*s != '\0' && *s != '\n' && *s != ' ' && *s != '\t') {
194		s++;
195	}
196	return s;
197}
198
199////////////////////////////////////////////////////////////
200// quotause operations
201
202/*
203 * Create an empty quotause structure.
204 */
205static struct quotause *
206quotause_create(void)
207{
208	struct quotause *qup;
209	unsigned i;
210
211	qup = malloc(sizeof(*qup));
212	if (qup == NULL) {
213		err(1, "malloc");
214	}
215
216	qup->next = NULL;
217	qup->found = 0;
218	qup->xgrace = 0;
219	qup->isdefault = 0;
220	for (i=0; i<EDQUOTA_NUMOBJTYPES; i++) {
221		quotaval_clear(&qup->qv[i]);
222		qup->source[i] = SRC_CLEAR;
223	}
224	qup->fsname[0] = '\0';
225
226	return qup;
227}
228
229/*
230 * Free a quotause structure.
231 */
232static void
233quotause_destroy(struct quotause *qup)
234{
235	free(qup);
236}
237
238////////////////////////////////////////////////////////////
239// quotalist operations
240
241/*
242 * Create a quotause list.
243 */
244static struct quotalist *
245quotalist_create(void)
246{
247	struct quotalist *qlist;
248
249	qlist = malloc(sizeof(*qlist));
250	if (qlist == NULL) {
251		err(1, "malloc");
252	}
253
254	qlist->head = NULL;
255	qlist->tail = NULL;
256	qlist->idtypename = NULL;
257
258	return qlist;
259}
260
261/*
262 * Free a list of quotause structures.
263 */
264static void
265quotalist_destroy(struct quotalist *qlist)
266{
267	struct quotause *qup, *nextqup;
268
269	for (qup = qlist->head; qup; qup = nextqup) {
270		nextqup = qup->next;
271		quotause_destroy(qup);
272	}
273	free(qlist->idtypename);
274	free(qlist);
275}
276
277#if 0
278static bool
279quotalist_empty(struct quotalist *qlist)
280{
281	return qlist->head == NULL;
282}
283#endif
284
285static void
286quotalist_append(struct quotalist *qlist, struct quotause *qup)
287{
288	/* should not already be on a list */
289	assert(qup->next == NULL);
290
291	if (qlist->head == NULL) {
292		qlist->head = qup;
293	} else {
294		qlist->tail->next = qup;
295	}
296	qlist->tail = qup;
297}
298
299////////////////////////////////////////////////////////////
300// ffs quota v1
301
302#if 0
303static void
304putprivs1(uint32_t id, int idtype, struct quotause *qup)
305{
306	struct dqblk dqblk;
307	int fd;
308
309	quotavals_to_dqblk(&qup->qv[QUOTA_LIMIT_BLOCK],
310			   &qup->qv[QUOTA_LIMIT_FILE],
311			   &dqblk);
312	assert((qup->flags & DEFAULT) == 0);
313
314	if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
315		warnx("open `%s'", qup->qfname);
316	} else {
317		(void)lseek(fd,
318		    (off_t)(id * (long)sizeof (struct dqblk)),
319		    SEEK_SET);
320		if (write(fd, &dqblk, sizeof (struct dqblk)) !=
321		    sizeof (struct dqblk))
322			warnx("writing `%s'", qup->qfname);
323		close(fd);
324	}
325}
326
327static struct quotause *
328getprivs1(long id, int idtype, const char *filesys)
329{
330	struct fstab *fs;
331	char qfpathname[MAXPATHLEN];
332	struct quotause *qup;
333	struct dqblk dqblk;
334	int fd;
335
336	setfsent();
337	while ((fs = getfsent()) != NULL) {
338		if (strcmp(fs->fs_vfstype, "ffs"))
339			continue;
340		if (strcmp(fs->fs_spec, filesys) == 0 ||
341		    strcmp(fs->fs_file, filesys) == 0)
342			break;
343	}
344	if (fs == NULL)
345		return NULL;
346
347	if (!hasquota(qfpathname, sizeof(qfpathname), fs,
348	    quota_idtype_to_ufs(idtype)))
349		return NULL;
350
351	qup = quotause_create();
352	strcpy(qup->fsname, fs->fs_file);
353	if ((fd = open(qfpathname, O_RDONLY)) < 0) {
354		fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
355		if (fd < 0 && errno != ENOENT) {
356			warnx("open `%s'", qfpathname);
357			quotause_destroy(qup);
358			return NULL;
359		}
360		warnx("Creating quota file %s", qfpathname);
361		sleep(3);
362		(void)fchown(fd, getuid(),
363		    getidbyname(quotagroup, QUOTA_CLASS_GROUP));
364		(void)fchmod(fd, 0640);
365	}
366	(void)lseek(fd, (off_t)(id * sizeof(struct dqblk)),
367	    SEEK_SET);
368	switch (read(fd, &dqblk, sizeof(struct dqblk))) {
369	case 0:			/* EOF */
370		/*
371		 * Convert implicit 0 quota (EOF)
372		 * into an explicit one (zero'ed dqblk)
373		 */
374		memset(&dqblk, 0, sizeof(struct dqblk));
375		break;
376
377	case sizeof(struct dqblk):	/* OK */
378		break;
379
380	default:		/* ERROR */
381		warn("read error in `%s'", qfpathname);
382		close(fd);
383		quotause_destroy(qup);
384		return NULL;
385	}
386	close(fd);
387	qup->qfname = qfpathname;
388	endfsent();
389	dqblk_to_quotavals(&dqblk,
390			   &qup->qv[QUOTA_LIMIT_BLOCK],
391			   &qup->qv[QUOTA_LIMIT_FILE]);
392	return qup;
393}
394#endif
395
396////////////////////////////////////////////////////////////
397// generic quota interface
398
399static int
400dogetprivs2(struct quotahandle *qh, int idtype, id_t id, int defaultq,
401	    int objtype, struct quotause *qup)
402{
403	struct quotakey qk;
404
405	qk.qk_idtype = idtype;
406	qk.qk_id = defaultq ? QUOTA_DEFAULTID : id;
407	qk.qk_objtype = objtype;
408	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
409		/* succeeded */
410		qup->source[objtype] = SRC_QUOTA;
411		return 0;
412	}
413	if (errno != ENOENT) {
414		/* serious failure */
415		return -1;
416	}
417
418	/* no entry, get default entry */
419	qk.qk_id = QUOTA_DEFAULTID;
420	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
421		/* succeeded */
422		qup->source[objtype] = SRC_DEFAULT;
423		return 0;
424	}
425	if (errno != ENOENT) {
426		return -1;
427	}
428
429	/* use a zeroed-out entry */
430	quotaval_clear(&qup->qv[objtype]);
431	qup->source[objtype] = SRC_CLEAR;
432	return 0;
433}
434
435static struct quotause *
436getprivs2(long id, int idtype, const char *filesys, int defaultq,
437	  char **idtypename_p)
438{
439	struct quotause *qup;
440	struct quotahandle *qh;
441	const char *impl;
442	unsigned restrictions;
443	const char *idtypename;
444	int serrno;
445
446	qup = quotause_create();
447	strcpy(qup->fsname, filesys);
448	if (defaultq)
449		qup->isdefault = 1;
450
451	qh = quota_open(filesys);
452	if (qh == NULL) {
453		serrno = errno;
454		quotause_destroy(qup);
455		errno = serrno;
456		return NULL;
457	}
458
459	impl = quota_getimplname(qh);
460	if (impl == NULL) {
461		impl = "???";
462	}
463	strlcpy(qup->implementation, impl, sizeof(qup->implementation));
464
465	restrictions = quota_getrestrictions(qh);
466	if ((restrictions & QUOTA_RESTRICT_UNIFORMGRACE) == 0) {
467		qup->xgrace = 1;
468	}
469
470	if (*idtypename_p == NULL) {
471		idtypename = quota_idtype_getname(qh, idtype);
472		*idtypename_p = strdup(idtypename);
473		if (*idtypename_p == NULL) {
474			errx(1, "Out of memory");
475		}
476	}
477
478	if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_BLOCKS, qup)) {
479		serrno = errno;
480		quota_close(qh);
481		quotause_destroy(qup);
482		errno = serrno;
483		return NULL;
484	}
485
486	if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_FILES, qup)) {
487		serrno = errno;
488		quota_close(qh);
489		quotause_destroy(qup);
490		errno = serrno;
491		return NULL;
492	}
493
494	quota_close(qh);
495
496	return qup;
497}
498
499static void
500putprivs2(uint32_t id, int idtype, struct quotause *qup)
501{
502	struct quotahandle *qh;
503	struct quotakey qk;
504	char idname[32];
505
506	if (qup->isdefault) {
507		snprintf(idname, sizeof(idname), "%s default",
508			 idtype == QUOTA_IDTYPE_USER ? "user" : "group");
509		id = QUOTA_DEFAULTID;
510	} else {
511		snprintf(idname, sizeof(idname), "%s %u",
512			 idtype == QUOTA_IDTYPE_USER ? "uid" : "gid", id);
513	}
514
515	qh = quota_open(qup->fsname);
516	if (qh == NULL) {
517		err(1, "%s: quota_open", qup->fsname);
518	}
519
520	if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
521		qk.qk_idtype = idtype;
522		qk.qk_id = id;
523		qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
524		if (quota_put(qh, &qk, &qup->qv[QO_BLK])) {
525			err(1, "%s: quota_put (%s blocks)", qup->fsname,
526			    idname);
527		}
528	}
529
530	if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
531		qk.qk_idtype = idtype;
532		qk.qk_id = id;
533		qk.qk_objtype = QUOTA_OBJTYPE_FILES;
534		if (quota_put(qh, &qk, &qup->qv[QO_FL])) {
535			err(1, "%s: quota_put (%s files)", qup->fsname,
536			    idname);
537		}
538	}
539
540	quota_close(qh);
541}
542
543////////////////////////////////////////////////////////////
544// quota format switch
545
546/*
547 * Collect the requested quota information.
548 */
549static struct quotalist *
550getprivs(long id, int defaultq, int idtype, const char *filesys)
551{
552	struct statvfs *fst;
553	int nfst, i;
554	struct quotalist *qlist;
555	struct quotause *qup;
556	int seenany = 0;
557
558	qlist = quotalist_create();
559
560	nfst = getmntinfo(&fst, MNT_WAIT);
561	if (nfst == 0)
562		errx(1, "no filesystems mounted!");
563
564	for (i = 0; i < nfst; i++) {
565		if ((fst[i].f_flag & ST_QUOTA) == 0)
566			continue;
567		seenany = 1;
568		if (filesys &&
569		    strcmp(fst[i].f_mntonname, filesys) != 0 &&
570		    strcmp(fst[i].f_mntfromname, filesys) != 0)
571			continue;
572		qup = getprivs2(id, idtype, fst[i].f_mntonname, defaultq,
573				&qlist->idtypename);
574		if (qup == NULL) {
575			/* XXX the atf tests demand failing silently */
576			/*warn("Reading quotas failed for id %ld", id);*/
577			continue;
578		}
579
580		quotalist_append(qlist, qup);
581	}
582
583	if (!seenany) {
584		errx(1, "No mounted filesystems have quota support");
585	}
586
587#if 0
588	if (filesys && quotalist_empty(qlist)) {
589		if (defaultq)
590			errx(1, "no default quota for version 1");
591		/* if we get there, filesys is not mounted. try the old way */
592		qup = getprivs1(id, idtype, filesys);
593		if (qup == NULL) {
594			warnx("getprivs1 failed");
595			return qlist;
596		}
597		quotalist_append(qlist, qup);
598	}
599#endif
600	return qlist;
601}
602
603/*
604 * Store the requested quota information.
605 */
606static void
607putprivs(uint32_t id, int idtype, struct quotalist *qlist)
608{
609	struct quotause *qup;
610
611        for (qup = qlist->head; qup; qup = qup->next) {
612#if 0
613		if (qup->qfname != NULL)
614			putprivs1(id, idtype, qup);
615		else
616#endif
617			putprivs2(id, idtype, qup);
618	}
619}
620
621static void
622clearpriv(int argc, char **argv, const char *filesys, int idtype)
623{
624	struct statvfs *fst;
625	int nfst, i;
626	int id;
627	id_t *ids;
628	unsigned nids, maxids, j;
629	struct quotahandle *qh;
630	struct quotakey qk;
631	char idname[32];
632
633	maxids = 4;
634	nids = 0;
635	ids = malloc(maxids * sizeof(ids[0]));
636	if (ids == NULL) {
637		err(1, "malloc");
638	}
639
640	for ( ; argc > 0; argc--, argv++) {
641		if ((id = getidbyname(*argv, idtype)) == -1)
642			continue;
643
644		if (nids + 1 > maxids) {
645			maxids *= 2;
646			ids = realloc(ids, maxids * sizeof(ids[0]));
647			if (ids == NULL) {
648				err(1, "realloc");
649			}
650		}
651		ids[nids++] = id;
652	}
653
654	/* now loop over quota-enabled filesystems */
655	nfst = getmntinfo(&fst, MNT_WAIT);
656	if (nfst == 0)
657		errx(1, "no filesystems mounted!");
658
659	for (i = 0; i < nfst; i++) {
660		if ((fst[i].f_flag & ST_QUOTA) == 0)
661			continue;
662		if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
663		    strcmp(fst[i].f_mntfromname, filesys) != 0)
664			continue;
665
666		qh = quota_open(fst[i].f_mntonname);
667		if (qh == NULL) {
668			err(1, "%s: quota_open", fst[i].f_mntonname);
669		}
670
671		for (j = 0; j < nids; j++) {
672			snprintf(idname, sizeof(idname), "%s %u",
673				 idtype == QUOTA_IDTYPE_USER ?
674				 "uid" : "gid", ids[j]);
675
676			qk.qk_idtype = idtype;
677			qk.qk_id = ids[j];
678			qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
679			if (quota_delete(qh, &qk)) {
680				err(1, "%s: quota_delete (%s blocks)",
681				    fst[i].f_mntonname, idname);
682			}
683
684			qk.qk_idtype = idtype;
685			qk.qk_id = ids[j];
686			qk.qk_objtype = QUOTA_OBJTYPE_FILES;
687			if (quota_delete(qh, &qk)) {
688				if (errno == ENOENT) {
689 					/*
690					 * XXX ignore this case; due
691					 * to a weakness in the quota
692					 * proplib interface it can
693					 * appear spuriously.
694					 */
695				} else {
696					err(1, "%s: quota_delete (%s files)",
697					    fst[i].f_mntonname, idname);
698				}
699			}
700		}
701
702		quota_close(qh);
703	}
704
705	free(ids);
706}
707
708////////////////////////////////////////////////////////////
709// editor
710
711/*
712 * Take a list of privileges and get it edited.
713 */
714static int
715editit(const char *ltmpfile)
716{
717	pid_t pid;
718	int lst;
719	char p[MAX_TMPSTR];
720	const char *ed;
721	sigset_t s, os;
722
723	sigemptyset(&s);
724	sigaddset(&s, SIGINT);
725	sigaddset(&s, SIGQUIT);
726	sigaddset(&s, SIGHUP);
727	if (sigprocmask(SIG_BLOCK, &s, &os) == -1)
728		err(1, "sigprocmask");
729top:
730	switch ((pid = fork())) {
731	case -1:
732		if (errno == EPROCLIM) {
733			warnx("You have too many processes");
734			return 0;
735		}
736		if (errno == EAGAIN) {
737			sleep(1);
738			goto top;
739		}
740		warn("fork");
741		return 0;
742	case 0:
743		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
744			err(1, "sigprocmask");
745		setgid(getgid());
746		setuid(getuid());
747		if ((ed = getenv("EDITOR")) == (char *)0)
748			ed = _PATH_VI;
749		if (strlen(ed) + strlen(ltmpfile) + 2 >= MAX_TMPSTR) {
750			errx(1, "%s", "editor or filename too long");
751		}
752		snprintf(p, sizeof(p), "%s %s", ed, ltmpfile);
753		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
754		err(1, "%s", ed);
755	default:
756		if (waitpid(pid, &lst, 0) == -1)
757			err(1, "waitpid");
758		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
759			err(1, "sigprocmask");
760		if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0)
761			return 0;
762		return 1;
763	}
764}
765
766/*
767 * Convert a quotause list to an ASCII file.
768 */
769static int
770writeprivs(struct quotalist *qlist, int outfd, const char *name,
771    int idtype, const char *idtypename)
772{
773	struct quotause *qup;
774	FILE *fd;
775	char b0[32], b1[32], b2[32], b3[32];
776	const char *comm;
777
778	(void)ftruncate(outfd, 0);
779	(void)lseek(outfd, (off_t)0, SEEK_SET);
780	if ((fd = fdopen(dup(outfd), "w")) == NULL)
781		errx(1, "fdopen");
782	if (name == NULL) {
783		fprintf(fd, "Default %s quotas:\n", idtypename);
784	} else {
785		fprintf(fd, "Quotas for %s %s:\n", idtypename, name);
786	}
787	for (qup = qlist->head; qup; qup = qup->next) {
788		struct quotaval *q = qup->qv;
789
790		fprintf(fd, "%s (%s):\n", qup->fsname, qup->implementation);
791
792		comm = source_is_real(qup->source[QO_BLK]) ? "" : "#";
793		fprintf(fd, "\tblocks:%s\n",
794			Hflag ? "" : " (sizes in 1K-blocks)");
795		fprintf(fd, "\t\t%susage: %s\n", comm,
796			intprt(b1, 21, q[QO_BLK].qv_usage,
797			       HN_NOSPACE | HN_B, Hflag));
798		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
799			intprt(b2, 21, q[QO_BLK].qv_softlimit,
800			       HN_NOSPACE | HN_B, Hflag),
801			intprt(b3, 21, q[QO_BLK].qv_hardlimit,
802			       HN_NOSPACE | HN_B, Hflag));
803		if (qup->xgrace || qup->isdefault) {
804			fprintf(fd, "\t\t%sgrace: %s\n", comm,
805				timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
806		}
807
808		comm = source_is_real(qup->source[QO_FL]) ? "" : "#";
809		fprintf(fd, "\tinodes:\n");
810		fprintf(fd, "\t\t%susage: %s\n", comm,
811			intprt(b1, 21, q[QO_FL].qv_usage,
812			       HN_NOSPACE, Hflag));
813		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
814			intprt(b2, 21, q[QO_FL].qv_softlimit,
815			       HN_NOSPACE, Hflag),
816			intprt(b3, 21, q[QO_FL].qv_hardlimit,
817			       HN_NOSPACE, Hflag));
818		if (qup->xgrace || qup->isdefault) {
819			fprintf(fd, "\t\t%sgrace: %s\n", comm,
820				timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
821		}
822	}
823	fclose(fd);
824	return 1;
825}
826
827/*
828 * Merge changes to an ASCII file into a quotause list.
829 */
830static int
831readprivs(struct quotalist *qlist, int infd, int dflag)
832{
833	FILE *fd;			/* file */
834	unsigned lineno;		/* line number in file */
835	char line[BUFSIZ];		/* input buffer */
836	size_t len;			/* length of input buffer */
837	bool seenheader;		/* true if past the file header */
838	struct quotause *qup;		/* current filesystem */
839	bool haveobjtype;		/* true if objtype is valid */
840	unsigned objtype;		/* current object type */
841	int objtypeflags;		/* humanize flags */
842	/* XXX make following const later (requires non-local cleanup) */
843	/*const*/ char *text, *text2, *t; /* scratch values */
844	char b0[32], b1[32];
845	uint64_t soft, hard, current;
846	time_t grace;
847	struct quotaval *qv;
848
849	lineno = 0;
850	seenheader = false;
851	qup = NULL;
852	haveobjtype = false;
853	objtype = QUOTA_OBJTYPE_BLOCKS; /* for gcc 4.5 */
854	objtypeflags = HN_B;
855
856	(void)lseek(infd, (off_t)0, SEEK_SET);
857	fd = fdopen(dup(infd), "r");
858	if (fd == NULL) {
859		warn("Can't re-read temp file");
860		return -1;
861	}
862
863	/*
864	 * Read back the same format we wrote.
865	 */
866
867	while (fgets(line, sizeof(line), fd)) {
868		lineno++;
869		if (!seenheader) {
870			if ((!strncmp(line, "Default ", 8) && dflag) ||
871			    (!strncmp(line, "Quotas for ", 11) && !dflag)) {
872				/* ok. */
873				seenheader = 1;
874				continue;
875			}
876			warnx("Header line missing");
877			goto fail;
878		}
879		len = strlen(line);
880		if (len == 0) {
881			/* ? */
882			continue;
883		}
884		if (line[len - 1] != '\n') {
885			warnx("Line %u too long", lineno);
886			goto fail;
887		}
888		line[--len] = '\0';
889		if (line[len - 1] == '\r') {
890			line[--len] = '\0';
891		}
892		if (len == 0) {
893			/* blank line */
894			continue;
895		}
896
897		/*
898		 * If the line has:     it is:
899                 *      two tabs        values
900		 *      one tab         the next objtype
901		 *      no tabs         the next filesystem
902		 */
903		if (line[0] == '\t' && line[1] == '\t') {
904			if (qup == NULL) {
905				warnx("Line %u: values with no filesystem",
906				      lineno);
907				goto fail;
908			}
909			if (!haveobjtype) {
910				warnx("Line %u: values with no object type",
911				      lineno);
912				goto fail;
913			}
914			qv = &qup->qv[objtype];
915
916			text = line + 2;
917			if (*text == '#') {
918				/* commented out; ignore */
919				continue;
920			}
921			else if (!strncmp(text, "usage:", 6)) {
922
923				/* usage: %llu */
924				text += 6;
925				t = skipws(text);
926				if (intrd(t, &current, objtypeflags) != 0) {
927					warnx("Line %u: Bad number %s",
928					      lineno, t);
929					goto fail;
930				}
931
932				/*
933				 * Because the humanization can lead
934				 * to roundoff, check if the two
935				 * values produce the same humanized
936				 * string, rather than if they're the
937				 * same number. Sigh.
938				 */
939				intprt(b0, 21, current,
940				       HN_NOSPACE | objtypeflags, Hflag);
941				intprt(b1, 21, qv->qv_usage,
942				       HN_NOSPACE | objtypeflags, Hflag);
943				if (strcmp(b0, b1)) {
944					warnx("Line %u: cannot change usage",
945					      lineno);
946				}
947				continue;
948
949			} else if (!strncmp(text, "limits:", 7)) {
950
951				/* limits: soft %llu, hard %llu */
952				text += 7;
953				text2 = strchr(text, ',');
954				if (text2 == NULL) {
955					warnx("Line %u: expected comma",
956					      lineno);
957					goto fail;
958				}
959				*text2 = '\0';
960				text2++;
961
962				t = skipws(text);
963				t = skipword(t);
964				t = skipws(t);
965				if (intrd(t, &soft, objtypeflags) != 0) {
966					warnx("Line %u: Bad number %s",
967					      lineno, t);
968					goto fail;
969				}
970				t = skipws(text2);
971				t = skipword(t);
972				t = skipws(t);
973				if (intrd(t, &hard, objtypeflags) != 0) {
974					warnx("Line %u: Bad number %s",
975					      lineno, t);
976					goto fail;
977				}
978
979				/*
980				 * Cause time limit to be reset when the quota
981				 * is next used if previously had no soft limit
982				 * or were under it, but now have a soft limit
983				 * and are over it.
984				 */
985				if (qv->qv_usage && qv->qv_usage >= soft &&
986				    (qv->qv_softlimit == 0 ||
987				     qv->qv_usage < qv->qv_softlimit)) {
988					qv->qv_expiretime = 0;
989				}
990				if (soft != qv->qv_softlimit ||
991				    hard != qv->qv_hardlimit) {
992					qv->qv_softlimit = soft;
993					qv->qv_hardlimit = hard;
994					qup->source[objtype] = SRC_EDITED;
995				}
996				qup->found = 1;
997
998			} else if (!strncmp(text, "grace:", 6)) {
999
1000				text += 6;
1001				/* grace: %llu */
1002				t = skipws(text);
1003				if (timeprd(t, &grace) != 0) {
1004					warnx("Line %u: Bad number %s",
1005					      lineno, t);
1006					goto fail;
1007				}
1008				if (qup->isdefault || qup->xgrace) {
1009					if (grace != qv->qv_grace) {
1010						qv->qv_grace = grace;
1011						qup->source[objtype] =
1012							SRC_EDITED;
1013					}
1014					qup->found = 1;
1015				} else {
1016					warnx("Line %u: Cannot set individual "
1017					      "grace time on this filesystem",
1018					      lineno);
1019					goto fail;
1020				}
1021
1022			} else {
1023				warnx("Line %u: Unknown/unexpected value line",
1024				      lineno);
1025				goto fail;
1026			}
1027		} else if (line[0] == '\t') {
1028			text = line + 1;
1029			if (*text == '#') {
1030				/* commented out; ignore */
1031				continue;
1032			}
1033			else if (!strncmp(text, "blocks:", 7)) {
1034				objtype = QUOTA_OBJTYPE_BLOCKS;
1035				objtypeflags = HN_B;
1036				haveobjtype = true;
1037			} else if (!strncmp(text, "inodes:", 7)) {
1038				objtype = QUOTA_OBJTYPE_FILES;
1039				objtypeflags = 0;
1040				haveobjtype = true;
1041			} else {
1042				warnx("Line %u: Unknown/unexpected object "
1043				      "type (%s)", lineno, text);
1044				goto fail;
1045			}
1046		} else {
1047			t = strchr(line, ' ');
1048			if (t == NULL) {
1049				t = strchr(line, ':');
1050				if (t == NULL) {
1051					t = line + len;
1052				}
1053			}
1054			*t = '\0';
1055
1056			if (*line == '#') {
1057				/* commented out; ignore */
1058				continue;
1059			}
1060
1061			for (qup = qlist->head; qup; qup = qup->next) {
1062				if (!strcmp(line, qup->fsname))
1063					break;
1064			}
1065			if (qup == NULL) {
1066				warnx("Line %u: Filesystem %s invalid or has "
1067				      "no quota support", lineno, line);
1068				goto fail;
1069			}
1070			haveobjtype = false;
1071		}
1072	}
1073
1074	fclose(fd);
1075
1076	/*
1077	 * Disable quotas for any filesystems that we didn't see,
1078	 * because they must have been deleted in the editor.
1079	 *
1080	 * XXX this should be improved so it results in
1081	 * quota_delete(), not just writing out a blank quotaval.
1082	 */
1083	for (qup = qlist->head; qup; qup = qup->next) {
1084		if (qup->found) {
1085			qup->found = 0;
1086			continue;
1087		}
1088
1089		if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
1090			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_BLOCKS]);
1091			qup->source[QUOTA_OBJTYPE_BLOCKS] = SRC_EDITED;
1092		}
1093
1094		if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
1095			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_FILES]);
1096			qup->source[QUOTA_OBJTYPE_FILES] = SRC_EDITED;
1097		}
1098	}
1099	return 0;
1100
1101fail:
1102	sleep(3);
1103	fclose(fd);
1104	return -1;
1105}
1106
1107////////////////////////////////////////////////////////////
1108// actions
1109
1110static void
1111replicate(const char *fs, int idtype, const char *protoname,
1112	  char **names, int numnames)
1113{
1114	long protoid, id;
1115	struct quotalist *protoprivs;
1116	struct quotause *qup;
1117	int i;
1118
1119	if ((protoid = getidbyname(protoname, idtype)) == -1)
1120		exit(1);
1121	protoprivs = getprivs(protoid, 0, idtype, fs);
1122	for (qup = protoprivs->head; qup; qup = qup->next) {
1123		qup->qv[QO_BLK].qv_expiretime = 0;
1124		qup->qv[QO_FL].qv_expiretime = 0;
1125		qup->source[QO_BLK] = SRC_EDITED;
1126		qup->source[QO_FL] = SRC_EDITED;
1127	}
1128	for (i=0; i<numnames; i++) {
1129		id = getidbyname(names[i], idtype);
1130		if (id == -1)
1131			continue;
1132		putprivs(id, idtype, protoprivs);
1133	}
1134	/* XXX */
1135	/* quotalist_destroy(protoprivs); */
1136}
1137
1138static void
1139assign(const char *fs, int idtype,
1140       char *soft, char *hard, char *grace,
1141       char **names, int numnames)
1142{
1143	struct quotalist *curprivs;
1144	struct quotause *lqup;
1145	u_int64_t softb, hardb, softi, hardi;
1146	time_t  graceb, gracei;
1147	char *str;
1148	long id;
1149	int dflag;
1150	int i;
1151
1152	if (soft) {
1153		str = strsep(&soft, "/");
1154		if (str[0] == '\0' || soft == NULL || soft[0] == '\0')
1155			usage();
1156
1157		if (intrd(str, &softb, HN_B) != 0)
1158			errx(1, "%s: bad number", str);
1159		if (intrd(soft, &softi, 0) != 0)
1160			errx(1, "%s: bad number", soft);
1161	}
1162	if (hard) {
1163		str = strsep(&hard, "/");
1164		if (str[0] == '\0' || hard == NULL || hard[0] == '\0')
1165			usage();
1166
1167		if (intrd(str, &hardb, HN_B) != 0)
1168			errx(1, "%s: bad number", str);
1169		if (intrd(hard, &hardi, 0) != 0)
1170			errx(1, "%s: bad number", hard);
1171	}
1172	if (grace) {
1173		str = strsep(&grace, "/");
1174		if (str[0] == '\0' || grace == NULL || grace[0] == '\0')
1175			usage();
1176
1177		if (timeprd(str, &graceb) != 0)
1178			errx(1, "%s: bad number", str);
1179		if (timeprd(grace, &gracei) != 0)
1180			errx(1, "%s: bad number", grace);
1181	}
1182	for (i=0; i<numnames; i++) {
1183		if (names[i] == NULL) {
1184			id = 0;
1185			dflag = 1;
1186		} else {
1187			id = getidbyname(names[i], idtype);
1188			if (id == -1)
1189				continue;
1190			dflag = 0;
1191		}
1192
1193		curprivs = getprivs(id, dflag, idtype, fs);
1194		for (lqup = curprivs->head; lqup; lqup = lqup->next) {
1195			struct quotaval *q = lqup->qv;
1196			if (soft) {
1197				if (!dflag && softb &&
1198				    q[QO_BLK].qv_usage >= softb &&
1199				    (q[QO_BLK].qv_softlimit == 0 ||
1200				     q[QO_BLK].qv_usage <
1201				     q[QO_BLK].qv_softlimit))
1202					q[QO_BLK].qv_expiretime = 0;
1203				if (!dflag && softi &&
1204				    q[QO_FL].qv_usage >= softb &&
1205				    (q[QO_FL].qv_softlimit == 0 ||
1206				     q[QO_FL].qv_usage <
1207				     q[QO_FL].qv_softlimit))
1208					q[QO_FL].qv_expiretime = 0;
1209				q[QO_BLK].qv_softlimit = softb;
1210				q[QO_FL].qv_softlimit = softi;
1211			}
1212			if (hard) {
1213				q[QO_BLK].qv_hardlimit = hardb;
1214				q[QO_FL].qv_hardlimit = hardi;
1215			}
1216			if (grace) {
1217				q[QO_BLK].qv_grace = graceb;
1218				q[QO_FL].qv_grace = gracei;
1219			}
1220			lqup->source[QO_BLK] = SRC_EDITED;
1221			lqup->source[QO_FL] = SRC_EDITED;
1222		}
1223		putprivs(id, idtype, curprivs);
1224		quotalist_destroy(curprivs);
1225	}
1226}
1227
1228static void
1229clear(const char *fs, int idtype, char **names, int numnames)
1230{
1231	clearpriv(numnames, names, fs, idtype);
1232}
1233
1234static void
1235editone(const char *fs, int idtype, const char *name,
1236	int tmpfd, const char *tmppath)
1237{
1238	struct quotalist *curprivs;
1239	long id;
1240	int dflag;
1241
1242	if (name == NULL) {
1243		id = 0;
1244		dflag = 1;
1245	} else {
1246		id = getidbyname(name, idtype);
1247		if (id == -1)
1248			return;
1249		dflag = 0;
1250	}
1251	curprivs = getprivs(id, dflag, idtype, fs);
1252
1253	if (writeprivs(curprivs, tmpfd, name, idtype,
1254		       curprivs->idtypename) == 0)
1255		goto fail;
1256
1257	if (editit(tmppath) == 0)
1258		goto fail;
1259
1260	if (readprivs(curprivs, tmpfd, dflag) < 0)
1261		goto fail;
1262
1263	putprivs(id, idtype, curprivs);
1264fail:
1265	quotalist_destroy(curprivs);
1266}
1267
1268static void
1269edit(const char *fs, int idtype, char **names, int numnames)
1270{
1271	char tmppath[] = _PATH_TMPFILE;
1272	int tmpfd, i;
1273
1274	tmpfd = mkstemp(tmppath);
1275	fchown(tmpfd, getuid(), getgid());
1276
1277	for (i=0; i<numnames; i++) {
1278		editone(fs, idtype, names[i], tmpfd, tmppath);
1279	}
1280
1281	close(tmpfd);
1282	unlink(tmppath);
1283}
1284
1285////////////////////////////////////////////////////////////
1286// main
1287
1288static void
1289usage(void)
1290{
1291	const char *p = getprogname();
1292	fprintf(stderr,
1293	    "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] "
1294		"-d | <username> ...\n"
1295	    "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] "
1296		"-d | <groupname> ...\n"
1297	    "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1298		"-d | <username> ...\n"
1299	    "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1300		"-d | <groupname> ...\n"
1301	    "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n"
1302	    "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n",
1303	    p, p, p, p, p, p);
1304	exit(1);
1305}
1306
1307int
1308main(int argc, char *argv[])
1309{
1310	int idtype;
1311	char *protoname;
1312	char *soft = NULL, *hard = NULL, *grace = NULL;
1313	char *fs = NULL;
1314	int ch;
1315	int pflag = 0;
1316	int cflag = 0;
1317	int dflag = 0;
1318
1319	if (argc < 2)
1320		usage();
1321	if (getuid())
1322		errx(1, "permission denied");
1323	protoname = NULL;
1324	idtype = QUOTA_IDTYPE_USER;
1325	while ((ch = getopt(argc, argv, "Hcdugp:s:h:t:f:")) != -1) {
1326		switch(ch) {
1327		case 'H':
1328			Hflag++;
1329			break;
1330		case 'c':
1331			cflag++;
1332			break;
1333		case 'd':
1334			dflag++;
1335			break;
1336		case 'p':
1337			protoname = optarg;
1338			pflag++;
1339			break;
1340		case 'g':
1341			idtype = QUOTA_IDTYPE_GROUP;
1342			break;
1343		case 'u':
1344			idtype = QUOTA_IDTYPE_USER;
1345			break;
1346		case 's':
1347			soft = optarg;
1348			break;
1349		case 'h':
1350			hard = optarg;
1351			break;
1352		case 't':
1353			grace = optarg;
1354			break;
1355		case 'f':
1356			fs = optarg;
1357			break;
1358		default:
1359			usage();
1360		}
1361	}
1362	argc -= optind;
1363	argv += optind;
1364
1365	if (pflag) {
1366		if (soft || hard || grace || dflag || cflag)
1367			usage();
1368		replicate(fs, idtype, protoname, argv, argc);
1369	} else if (soft || hard || grace) {
1370		if (cflag)
1371			usage();
1372		if (dflag) {
1373			/* use argv[argc], which is null, to mean 'default' */
1374			argc++;
1375		}
1376		assign(fs, idtype, soft, hard, grace, argv, argc);
1377	} else if (cflag) {
1378		if (dflag)
1379			usage();
1380		clear(fs, idtype, argv, argc);
1381	} else {
1382		if (dflag) {
1383			/* use argv[argc], which is null, to mean 'default' */
1384			argc++;
1385		}
1386		edit(fs, idtype, argv, argc);
1387	}
1388	return 0;
1389}
1390