/*
 * 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: quota.c,v 1.7 2000/09/05 18:47:15 mvw Exp mvw $";
#endif /* not lint */

/*
 * Disk quota reporting program.
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/quota.h>
#include <stdio.h>
#include <mntent.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <quotaops.h>
#ifdef RPC
#include <rpc/rpc.h>
#include <rquota.h>
#endif

#include "pot.h"

extern char *qfextension[];

int qflag;
int vflag;

int showuid(uid_t uid);
int showgid(gid_t gid);

void showversion(void)
{
  printf(
#ifdef RPC
    #ifdef EXT2_DIRECT
     _("quota %s, with RPC and EXT2_DIRECT options.\n")
    #else
     _("quota %s, with RPC options.\n")
    #endif
#else
    #ifdef EXT2_DIRECT
     _("quota %s, with EXT2_DIRECT options.\n")
    #else
     _("quota %s, without special options.\n")
    #endif
#endif
  , QUOTA_VERSION);
  exit(1);
}

main(int argc, char **argv)
{
   int ngroups;
   gid_t gidset[NGROUPS];
   int i, gflag = 0, uflag = 0, nflag = 0;
   char ch;
   extern char *optarg;
   extern int optind, errno;
  
   gettexton();
   
   while ((ch = getopt(argc, argv, "gnuqvV")) != EOF) {
      switch (ch) {
         case 'g':
            gflag++;
            break;
         case 'n':
            nflag++;
            break;
         case 'u':
            uflag++;
            break;
         case 'q':
            qflag++;
            break;
         case 'v':
            vflag++;
            break;
         case 'V':
            showversion();
         default:
            usage();
      }
   }
   argc -= optind;
   argv += optind;

   if (!uflag && !gflag)
      uflag++;

   if (argc == 0) {
      if (uflag)
         showuid(getuid());
      if (gflag) {
         ngroups = getgroups(NGROUPS, gidset);
         if (ngroups < 0) {
            perror("quota: getgroups");
            exit(1);
         }
         for (i = 0; i < ngroups; i++)
            showgid(gidset[i]);
      }
      exit(0);
   }

   if (uflag && gflag)
      usage();

   if (uflag) {
      for (; argc > 0; argc--, argv++) {
         if (!nflag)
            showusrname(*argv);
         else
            showuid(atoi(*argv));
      }
      exit(0);
   }

   if (gflag) {
      for (; argc > 0; argc--, argv++) {
         if (!nflag)
            showgrpname(*argv);
         else
            showgid(atoi(*argv));
      }
      exit(0);
   }
}

usage(void)
{
   fprintf(stderr, "%s\n%s\n%s\n",
      _("Usage: quota [-guqvV]"),
      _("\tquota [-qv] -u username ..."),
      _("\tquota [-qv] -g groupname ..."));
   exit(1);
}

/*
 * Print out quotas for a specified user identifier.
 */
showuid(uid_t uid)
{
   struct passwd *pwd = getpwuid(uid);
   u_long myuid;
   char *name;

   if (pwd == NULL)
      name = _("(no account)");
   else
      name = pwd->pw_name;
   myuid = getuid();
   if (uid != myuid && myuid != 0) {
      printf(_("quota: %s (uid %d): permission denied\n"), name, uid);
      return;
   }
   showquotas(USRQUOTA, uid, name);
}

/*
 * Print out quotas for a specifed user name.
 */
showusrname(char *name)
{
   struct passwd *pwd = getpwnam(name);
   u_long myuid;

   if (pwd == NULL) {
      fprintf(stderr, _("quota: %s: unknown user\n"), name);
      return;
   }
   myuid = getuid();
   if (pwd->pw_uid != myuid && myuid != 0) {
      fprintf(stderr, _("quota: %s (uid %d): permission denied\n"),
         name, pwd->pw_uid);
      return;
   }
   showquotas(USRQUOTA, pwd->pw_uid, name);
}

/*
 * Print out quotas for a specified group identifier.
 */
showgid(gid_t gid)
{
   struct group  *grp = getgrgid(gid);
   int ngroups;
   gid_t gidset[NGROUPS];
   register int i;
   char *name;

   if (grp == NULL)
      name = _("(no entry)");
   else
      name = grp->gr_name;

   ngroups = getgroups(NGROUPS, gidset);
   if (ngroups < 0) {
      perror("quota: getgroups");
      return;
   }
   for (i = 0; i < ngroups; i++)
      if (gid == gidset[i])
         break;
   if (i >= ngroups && getuid() != 0) {
      fprintf(stderr, _("quota: %s (gid %d): permission denied\n"),
         name, gid);
      return;
   }
   showquotas(GRPQUOTA, gid, name);
}

/*
 * Print out quotas for a specifed group name.
 */
showgrpname(char *name)
{
   struct group *grp = getgrnam(name);
   int ngroups;
   gid_t gidset[NGROUPS];
   register int i;

   if (grp == NULL) {
      fprintf(stderr, _("quota: %s: unknown group\n"), name);
      return;
   }
   ngroups = getgroups(NGROUPS, gidset);
   if (ngroups < 0) {
      perror("quota: getgroups");
      return;
   }
   for (i = 0; i < ngroups; i++)
      if (grp->gr_gid == gidset[i])
         break;
   if (i >= ngroups && getuid() != 0) {
      fprintf(stderr, _("quota: %s (gid %d): permission denied\n"),
              name, grp->gr_gid);
      return;
   }
   showquotas(GRPQUOTA, grp->gr_gid, name);
}

showquotas(int type, int id, char *name)
{
   register struct quotause *qup;
   struct quotause *quplist;
   char *msgi, *msgb, *timeprt();
   int myuid, fd, lines = 0;
   static int first;
   static time_t now;

   if (now == 0)
      time(&now);
   quplist = getprivs(id, type, 0);
   for (qup = quplist; qup; qup = qup->next) {
      if (!vflag &&
          qup->dqblk.dqb_isoftlimit == 0 &&
          qup->dqblk.dqb_ihardlimit == 0 &&
          qup->dqblk.dqb_bsoftlimit == 0 &&
          qup->dqblk.dqb_bhardlimit == 0)
         continue;
      msgi = (char *) 0;
      if (qup->dqblk.dqb_ihardlimit &&
          qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
         msgi = _("File limit reached on");
      else if (qup->dqblk.dqb_isoftlimit &&
            qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit)
         if (qup->dqblk.dqb_itime > now)
            msgi = _("In file grace period on");
         else
            msgi = _("Over file quota on");
      msgb = (char *) 0;
      if (qup->dqblk.dqb_bhardlimit &&
          qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
         msgb = _("Block limit reached on");
      else if (qup->dqblk.dqb_bsoftlimit &&
            qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit)
         if (qup->dqblk.dqb_btime > now)
            msgb = _("In block grace period on");
         else
            msgb = _("Over block quota on");
      if (qflag) {
         if ((msgi != (char *) 0 || msgb != (char *) 0) &&
             lines++ == 0)
            heading(type, id, name, "");
         if (msgi != (char *) 0)
            printf("\t%s %s\n", msgi, qup->fsname);
         if (msgb != (char *) 0)
            printf("\t%s %s\n", msgb, qup->fsname);
         continue;
      }
      if (vflag ||
          qup->dqblk.dqb_curblocks ||
          qup->dqblk.dqb_curinodes) {
         if (lines++ == 0)
            heading(type, id, name, "");
         if (strlen(qup->fsname) > 15) {
            printf("%s\n", qup->fsname);
            printf("%15s%8d%c%7d%8d%8s"
                ,""
                ,qup->dqblk.dqb_curblocks
                ,(msgb == (char *) 0) ? ' ' : '*'
                ,qup->dqblk.dqb_bsoftlimit
                ,qup->dqblk.dqb_bhardlimit
                ,(msgb == (char *) 0) ? ""
                : timeprt(qup->dqblk.dqb_btime));
         } else {
            printf("%15s%8d%c%7d%8d%8s"
                   ,qup->fsname
                   ,qup->dqblk.dqb_curblocks
                   ,(msgb == (char *) 0) ? ' ' : '*'
                   ,qup->dqblk.dqb_bsoftlimit
                   ,qup->dqblk.dqb_bhardlimit
                   ,(msgb == (char *) 0) ? ""
                   : timeprt(qup->dqblk.dqb_btime));
         }

         printf("%8d%c%7d%8d%8s\n"
                ,qup->dqblk.dqb_curinodes
                ,(msgi == (char *) 0) ? ' ' : '*'
                ,qup->dqblk.dqb_isoftlimit
                ,qup->dqblk.dqb_ihardlimit
                ,(msgi == (char *) 0) ? ""
                : timeprt(qup->dqblk.dqb_itime)
            );
         continue;
      }
   }
   if (!qflag && lines == 0)
      heading(type, id, name, _("none"));
}

heading(int type, u_long id, char *name, char *tag)
{
   printf(_("Disk quotas for %s %s (%cid %d): %s\n"), qfextension[type],
          name, *qfextension[type], id, tag);
   if (!qflag && tag[0] == '\0') {
      printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n"
             ,_("Filesystem")
             ,_("blocks")
             ,_("quota")
             ,_("limit")
             ,_("grace")
             ,_("files")
             ,_("quota")
             ,_("limit")
             ,_("grace")
         );
   }
}

/*
 * 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, "%d days", (hours + 12) / 24);
      return (buf);
   }
   if (minutes >= 60) {
      sprintf(buf, "%2d:%d", minutes / 60, minutes % 60);
      return (buf);
   }
   sprintf(buf, "%2d", minutes);
   return (buf);
}
