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