/* w - show users and their processes
 *
 * Copyright (c) 1993 Larry Greenfield
 * Distributable under the terms of the copyleft, etc.
 *
 * greenfie@gauss.rutgers.edu: Initial version
 *
 * modifications by Michael K. Johnson, johnsonm@sunsite.unc.edu
 *
 * $Log: w.c,v $
 * Revision 1.8  1994/01/29  17:31:52  johnsonm
 * Fixed isnice(), and made the "from" field not shown by default, with
 * the -f switch being a toggle.  Also possible to compile so that the
 * "from" field is shown by default; just define W_SHOWFROM.  The Makefile
 * contains a line to do this if you wish.
 *
 * Revision 1.7  1994/01/01  18:47:07  johnsonm
 * Made only printable characters appear in the hostname field, since
 * utmps apparantly get screwed up all over.  Wrote a terrible
 * inline function called isnice(), which needs to be replaced
 * with something better someday.  See the comment near it.
 *
 * Revision 1.6  1994/01/01  18:05:40  johnsonm
 * Added from field, and fixed get_maxcmd() to work correctly with all
 * possible combinations of options.
 *
 * Revision 1.5  1994/01/01  14:24:31  johnsonm
 * JCPU and PCPU appear to work now.
 *
 * Revision 1.4  1994/01/01  12:47:29  johnsonm
 * The tty and what fields now work.  JCPU and PCPU don't yet.
 *
 * Visible change log (for those great doc writers):
 *  - Feb 11, 1993 (LG):  added "u" switch to ignore users while figuring
 *                        process time and current processes.
 *  - Mar 07, 1993 (LG):  changed the output to go to the next date form
 *                        sooner
 *  - Mar 07, 1993 (LG):  added help on bad command
 *  - Mar 07, 1993 (LG):  added the option of supplying a user name
 *  - Mar 15, 1993 (mkj): fixed formatting with diffs from Quowong P Liu
 *  - Apr 13, 1993 (mkj): fixed Liu's formatting strings to not display
 *			  old bug with string length.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <utmp.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>

#include "ps.h"
#include "whattime.h"

struct ps_proc_head *ph; /* a global variable */
int ignoreuser=0; /* another global, for use with -u */

static char rcsid[] = "$Id: w.c,v 1.8 1994/01/29 17:31:52 johnsonm Exp $";
static char *weekday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
                           "Aug", "Sep", "Oct", "Nov", "Dec" };

void showinfo(struct utmp *user, int formtype, int maxcmd, int from);
char *stripblank(char *instr);
int get_maxcmd(int longform, int from);
char *logintime(struct utmp *user);
char *idletime(struct utmp *user);
void getproc(struct utmp *user, char *name, int *jcpu, int *pcpu, 
	     int maxcmd);

int main(int argc, char **argv)
{
  int header=1, longform=1; /* parameters */
#ifdef W_SHOWFROM
  int from=1;
#else /* default case, from field not shown */
  int from=0;
#endif
  int args=0; /* were there any args? */
  char ch, *watchuser=NULL;
  FILE *ut; /* /etc/utmp */
  struct utmp *ut_rec; /* utmp record */
  int maxcmd;

/* get options */
  while ((ch = getopt(argc, argv, "hlusf")) != EOF)
    switch (ch) {
    case 'h':
      header = 0; args++;
      break;
    case 'l':
      longform=1; args++;
      break;
    case 's':
      longform=0; args++;
      break;
    case 'u':
      ignoreuser=1; args++;
      break;
    case 'f':
      if(from)
	from = 0;
      else
	from = 1;
      args++;
      break;
    default:
      printf("usage: w -hlsu [user]\n");
      exit(1);
      break;
    }

/* was there any user to look for? */
  if ((argv[args ? 2 : 1]) != NULL)
    watchuser = (argv[args ? 2 : 1]);

/* get maximum command length */
  maxcmd = get_maxcmd(longform, from);

/* get running processes -- want all processes, we want the username as well
   as id, don't need processes without controlling
   terminals, don't need memory stats, uid doesn't matter, and ctty is NULL
   so that it doesn't get considered. */
  ph = take_snapshot(1, 1, 0, 0, 0, 0, 0);

/* print uptime */
  if (header) {
    print_uptime();
      printf("User     tty      ");
    if(from)
      printf("from            ");
    if (longform)
      printf(" login@  idle   JCPU   PCPU  what\n");
    else
      printf("  idle   what\n");
  }
/* open utmp */

  ut = fopen(UTMP_FILE, "r");
  if (watchuser == NULL) {
    while (ut_rec = getutent())
      if ((ut_rec->ut_type == USER_PROCESS) &&
	  (ut_rec->ut_name[0] != '\000'))
	showinfo(ut_rec, longform, maxcmd, from);
  } else {
    while (ut_rec = getutent())
      if (!strcmp(ut_rec->ut_name, watchuser) &&
	  (ut_rec->ut_type == USER_PROCESS))
	showinfo(ut_rec, longform, maxcmd, from);
  }
  fclose(ut);
  return 0;
}


/* For showinfo.  The (c < 128) is because some terminals are being screwed
 * up by high-order characters that some utmp-modifying programs are
 * either putting or leaving in utmp.
 */
inline int isnice(char c)
{
  return (isprint(c) && ((unsigned char)c < 128));
}

void showinfo(struct utmp *user, int formtype, int maxcmd, int from)
{
  char name[254], uname[UT_NAMESIZE + 1], *ltime, *itime;
  int jcpu, pcpu;

  getproc(user, name, &jcpu, &pcpu, maxcmd);
  ltime = logintime(user); 
  itime = idletime(user);
  strncpy(uname, user->ut_user, 8); uname[8] = 0;
  if (formtype) {
    printf("%-9.8s%-9.8s", user->ut_user, user->ut_line);
    if (from) {
      int i;
      for (i=0;i<16;i++) {
	if (!isnice(user->ut_host[i]))
	  user->ut_host[i] = ' ';
      }
      printf("%-16.16s", user->ut_host);
    }
    printf("%-7.7s%-7.7s", ltime, itime);
    if (jcpu/60) printf ("%3d:%02d", jcpu/60, jcpu%60);
    else if (jcpu) printf ("    %2d", jcpu);
    else printf ("      ");
    if (pcpu/60) printf (" %3d:%02d  %s\n", pcpu/60, pcpu%60, name);
    else if (pcpu%60) printf ("     %2d  %s\n", pcpu, name);
    else printf ("         %s\n", name);
  }
  else {
    printf("%-9.8s%-9.8s", user->ut_user, user->ut_line);
    if (from) {
      int i;
      for (i=0;i<16;i++) {
	if (!isnice(user->ut_host[i]))
	  user->ut_host[i] = ' ';
      }
      printf("%-16.16s", user->ut_host);
    }
    printf("%-7.7s  %s\n", itime, name);

  }
}

char *logintime(struct utmp *user)

/* get the login time in order */

{
  time_t curtime;
  struct tm *logintime, *curtm;
  int hour, am, curday, logday;
  static char give[20];

  curtime = time(NULL);
  curtm = localtime(&curtime);
  curday = curtm->tm_yday;
  logintime = localtime(&user->ut_time);
  hour = logintime->tm_hour;
  logday = logintime->tm_yday;
  am = (hour < 12);
  if (!am)
    hour -= 12;
  if (hour == 0)
    hour = 12;
/* This is a newer behavior: it waits 12 hours and the next day, and then
   goes to the 2nd time format. This should reduce confusion.
   It then waits only 6 days (not till the last moment) to go the last
   time format.
*/
  if ((curtime > (user->ut_time + (60 * 60 * 12))) && (logday != curday))
    if (curtime > (user->ut_time + (60 * 60 * 24 * 6)))
      sprintf(give, "%2d%3s%2d", logintime->tm_mday, month[logintime->tm_mon],
	     (logintime->tm_year % 100));
    else
      sprintf(give, "%*s%2d%s", 3, weekday[logintime->tm_wday],
	     hour, am ? "am" : "pm");
  else
    sprintf(give, "%2d:%02d%s", hour, logintime->tm_min, am ? "am" : "pm");
  return give;
}

char *idletime(struct utmp *user)

/* find idle time */

{
  struct stat terminfo;
  unsigned long idle;
  char ttytmp[40];
  static char give[20];
  time_t curtime;

  curtime = time(NULL);
  sprintf(ttytmp, "/dev/%s", stripblank(user->ut_line));
  stat(ttytmp, &terminfo);
  idle = (unsigned long) curtime - (unsigned long) terminfo.st_atime;
  if (idle >= (60 * 60)) /* more than an hour */
    if (idle >= (60 * 60 * 48)) /* more than two days */
      sprintf(give, "%2ludays", idle / (60 * 60 * 24));
    else
      sprintf(give, " %2lu:%02u", idle / (60 * 60), 
	      (unsigned) ((idle / 60) % 60));
  else
    if (idle / 60)
      sprintf(give, "%6lu", idle / 60);
    else
      give[0]=0;
/*      sprintf(give, "");*/
  return give;
}

void getproc(struct utmp *user, char *name, int *jcpu, int *pcpu, 
	     int maxcmd)

/* Find current commands */

{
  struct ps_proc *finder, *best;
  static char line[4];

  (*jcpu) = 0; (*pcpu) = 0;
  best = NULL;
  strncpy(line, dev3(user->ut_line), 3);
  line[3] = '\0';
  if (ignoreuser) {
    for (finder = ph->head; finder != NULL; finder = finder->next) {
      if (!strcmp(finder->ttyc, line)) {
	(*jcpu) += finder->utime + finder->stime;
	if ((finder->pid == finder->tpgid) &&
	    ((best == NULL) || (finder->pid > best->pid)))
	  best = finder;
      }
    }
  } else { /* pay attention to users */
    for (finder = ph->head; finder != NULL; finder = finder->next) {
      if (!strcmp(finder->ttyc, line)) {
	(*jcpu) += finder->utime + finder->stime;
	if (!strncmp(finder->user, user->ut_user, 8)) {
	  if ((finder->pid == finder->tpgid) &&
	      ((best == NULL) ||(finder->pid > best->pid)))
	    best = finder;
	}
      }
    }
  }
  (*jcpu) /= 100;
  if (best != NULL) {
    (*pcpu) = (best->utime + best->stime) / 100;
    
/* print the command line, but no more than will go off the edge of the
   screen.  This is standard obfuscated c... ;-)  best->cmd is used if
   the process is swapped out. */
    sprintf(name, "%s", strlen(best->cmdline) ?
	   best->cmdline[maxcmd]=0,best->cmdline : best->cmd );
  } else {
    sprintf(name, "-");
  }
}


char *stripblank(char *instr)

/* truncates the string at the site of a ' ' */

{
  int c=0;

  while (instr[c] != ' ' && instr[c] != '\000')
    c++;
  instr[c] = '\000';
  return instr;
}


int get_maxcmd(int longform, int from)
{
    struct winsize win;
    int cols = 80;

    if (ioctl(1, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
	cols = win.ws_col;

    cols -= 28;
    if (from)
      cols -= 16;
    if (longform)
      cols -= 20;
    if (cols < 3) {
      fprintf(stderr, "Screen size too small: %d", win.ws_col);
      exit(1);
    }
    return cols;
}

