/*
 * Copyright (c) 1980, 1990 Regents of the University of California. All
 * rights reserved.
 * 
 * This code is derived from software contributed to Berkeley by Robert Elz at
 * The University of Melbourne.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution. 3. All advertising
 * materials mentioning features or use of this software must display the
 * following acknowledgement: This product includes software developed by the
 * University of California, Berkeley and its contributors. 4. Neither the
 * name of the University nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific
 * prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1980, 1990 Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char rcsid[] = "$Id: repquota.c,v 1.4 1995/07/23 09:58:06 mvw Exp mvw $";
#endif /* not lint */

/*
 * Quota report
 */
#include <sys/param.h>
#include <sys/stat.h>
#include <linux/quota.h>
#include <mntent.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <errno.h>

extern char *qfextension[];

struct fileusage {
   struct fileusage *fu_next;
   struct dqblk fu_dqblk;
   u_long fu_id;
   char fu_name[1]; /* actually bigger */
};

#define FUHASH 1024 /* must be power of two */
struct fileusage *fuhead[MAXQUOTAS][FUHASH];
struct fileusage *lookup();
struct fileusage *addid();
u_long highid[MAXQUOTAS];   /* highest addid()'ed identifier per type */

int vflag; /* verbose */
int aflag; /* all file systems */

main(int argc, char **argv)
{
   register struct mntent *mnt;
   register struct passwd *pw;
   register struct group *gr;
   FILE *fp;
   int gflag = 0, uflag = 0, errs = 0;
   long cnt, argnum, done = 0;
   extern char *optarg;
   extern int optind;
   char ch, *qfnp;

   while ((ch = getopt(argc, argv, "aguv")) != EOF) {
      switch (ch) {
         case 'a':
            aflag++;
            break;
         case 'g':
            gflag++;
            break;
         case 'u':
            uflag++;
            break;
         case 'v':
            vflag++;
            break;
         default:
            usage();
      }
   }
   argc -= optind;
   argv += optind;

   if (argc == 0 && !aflag)
      usage();
   if (!gflag && !uflag) {
      if (aflag)
         gflag++;
      uflag++;
   }
   if (gflag) {
      setgrent();
      while ((gr = getgrent()) != 0)
         (void) addid((u_long) gr->gr_gid, GRPQUOTA, gr->gr_name);
      endgrent();
   }
   if (uflag) {
      setpwent();
      while ((pw = getpwent()) != 0)
         (void) addid((u_long) pw->pw_uid, USRQUOTA, pw->pw_name);
      endpwent();
   }
   fp = setmntent(MNTTAB, "r");
   while ((mnt = getmntent(fp)) != (struct mntent *) 0) {
      if (aflag) {
         if (hasmntopt(mnt, MNTOPT_NOAUTO))
            continue;
         if (gflag && hasquota(mnt, GRPQUOTA, &qfnp))
            errs += repquota(mnt, GRPQUOTA, qfnp);
         if (uflag && hasquota(mnt, USRQUOTA, &qfnp))
            errs += repquota(mnt, USRQUOTA, qfnp);
         continue;
      }
      if ((argnum = oneof(mnt->mnt_dir, argv, argc)) >= 0 ||
          (argnum = oneof(mnt->mnt_fsname, argv, argc)) >= 0) {
         done |= 1 << argnum;
         if (gflag && hasquota(mnt, GRPQUOTA, &qfnp))
            errs += repquota(mnt, GRPQUOTA, qfnp);
         if (uflag && hasquota(mnt, USRQUOTA, &qfnp))
            errs += repquota(mnt, USRQUOTA, qfnp);
      }
   }
   endmntent(fp);

   for (cnt = 0; cnt < argc; cnt++)
      if ((done & (1 << cnt)) == 0)
         fprintf(stderr, "%s not found in fstab\n", argv[cnt]);
   exit(errs);
}

usage()
{
   fprintf(stderr, "Usage:\n\t%s\n\t%s\n",
      "repquota [-v] [-g] [-u] -a",
      "repquota [-v] [-g] [-u] filesys ...");
   exit(1);
}

repquota(struct mntent *mnt, int type, char *qfpathname)
{
   register struct fileusage *fup;
   FILE *qf;
   u_long id;
   struct dqblk dqbuf;
   char *timeprt();
   static struct dqblk zerodqblk;
   static int warned = 0;
   static int multiple = 0;
   extern int errno;

   if (quotactl(QCMD(Q_SYNC, type), mnt->mnt_fsname, 0, (caddr_t)0) < 0 &&
       errno == ENOSYS && !warned && vflag) {
      warned++;
      fprintf(stdout,
       "*** Warning: Quotas are not compiled into this kernel\n");
   }
   if (multiple++)
      printf("\n");
   if (vflag)
      fprintf(stdout, "*** Report for %s quotas on %s (%s)\n",
         qfextension[type], mnt->mnt_fsname, mnt->mnt_dir);
   if ((qf = fopen(qfpathname, "r")) == NULL) {
      perror(qfpathname);
      return (1);
   }
   for (id = 0;; id++) {
      fread(&dqbuf, sizeof(struct dqblk), 1, qf);
      if (feof(qf))
         break;
      if (dqbuf.dqb_curinodes == 0 && dqbuf.dqb_curblocks == 0)
         continue;
      if ((fup = lookup(id, type)) == 0)
         fup = addid(id, type, (char *) 0);
      fup->fu_dqblk = dqbuf;
   }
   fclose(qf);
   printf("                        Block limits               File limits\n");
   printf("User            used    soft    hard  grace    used  soft  hard  grace\n");
   for (id = 0; id <= highid[type]; id++) {
      fup = lookup(id, type);
      if (fup == 0)
         continue;
      if (fup->fu_dqblk.dqb_curinodes == 0 &&
          fup->fu_dqblk.dqb_curblocks == 0)
         continue;
      printf("%-10s", fup->fu_name);
      printf("%c%c%8d%8d%8d%7s",
             fup->fu_dqblk.dqb_bsoftlimit &&
             fup->fu_dqblk.dqb_curblocks >=
             fup->fu_dqblk.dqb_bsoftlimit ? '+' : '-',
             fup->fu_dqblk.dqb_isoftlimit &&
             fup->fu_dqblk.dqb_curinodes >=
             fup->fu_dqblk.dqb_isoftlimit ? '+' : '-',
             dbtob(fup->fu_dqblk.dqb_curblocks) / 1024,
             dbtob(fup->fu_dqblk.dqb_bsoftlimit) / 1024,
             dbtob(fup->fu_dqblk.dqb_bhardlimit) / 1024,
             fup->fu_dqblk.dqb_bsoftlimit &&
             fup->fu_dqblk.dqb_curblocks >=
             fup->fu_dqblk.dqb_bsoftlimit ?
             timeprt(fup->fu_dqblk.dqb_btime) : "");
      printf("  %6d%6d%6d%7s\n",
             fup->fu_dqblk.dqb_curinodes,
             fup->fu_dqblk.dqb_isoftlimit,
             fup->fu_dqblk.dqb_ihardlimit,
             fup->fu_dqblk.dqb_isoftlimit &&
             fup->fu_dqblk.dqb_curinodes >=
             fup->fu_dqblk.dqb_isoftlimit ?
             timeprt(fup->fu_dqblk.dqb_itime) : "");
      fup->fu_dqblk = zerodqblk;
   }
   return (0);
}

/*
 * Check to see if target appears in list of size cnt.
 */
oneof(char *target, char *list[], int cnt)
{
   register int i;

   for (i = 0; i < cnt; i++)
      if (strcmp(target, list[i]) == 0)
         return (i);
   return (-1);
}

/*
 * Routines to manage the file usage table.
 * 
 * Lookup an id of a specific type.
 */
struct fileusage *lookup(u_long id, int type)
{
   register struct fileusage *fup;

   for (fup = fuhead[type][id & (FUHASH - 1)]; fup != 0; fup = fup->fu_next)
      if (fup->fu_id == id)
         return (fup);
   return ((struct fileusage *) 0);
}

/*
 * Add a new file usage id if it does not already exist.
 */
struct fileusage *addid(u_long id, int type, char *name)
{
   struct fileusage *fup, **fhp;
   extern char *calloc();

   if (fup = lookup(id, type))
      return (fup);
   if ((fup = (struct fileusage *) calloc(1, sizeof(*fup) + 10)) == NULL) {
      fprintf(stderr, "out of memory for fileusage structures\n");
      exit(1);
   }
   fhp = &fuhead[type][id & (FUHASH - 1)];
   fup->fu_next = *fhp;
   *fhp = fup;
   fup->fu_id = id;
   if (id > highid[type])
      highid[type] = id;
   if (name) {
      memcpy(fup->fu_name, name, 10);
   } else {
      sprintf(fup->fu_name, "%u", id);
   }
   return (fup);
}

/*
 * Calculate the grace period and return a printable string for it.
 */
char *timeprt(time_t seconds)
{
   time_t hours, minutes;
   static char buf[20];
   static time_t now;

   if (now == 0)
      time(&now);
   if (now > seconds)
      return ("none");
   seconds -= now;
   minutes = (seconds + 30) / 60;
   hours = (minutes + 30) / 60;
   if (hours >= 36) {
      sprintf(buf, "%ddays", (hours + 12) / 24);
      return (buf);
   }
   if (minutes >= 60) {
      sprintf(buf, "%2d:%d", minutes / 60, minutes % 60);
      return (buf);
   }
   sprintf(buf, "%2d", minutes);
   return (buf);
}
