text: /*
   * tc.who.c: Watch logins and logouts...
   */
  /*-
   * Copyright (c) 1980, 1991 The Regents of the University of California.
   * All rights reserved.
   *
   * 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. 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.
   */
  #include "sh.h"
  #include "tc.h"
  
  #ifndef HAVENOUTMP
  /*
   * kfk 26 Jan 1984 - for login watch functions.
   */
  #include <ctype.h>
  
  #ifdef HAVE_UTMPX_H
  # include <utmpx.h>
  # define UTNAMLEN	sizeof(((struct utmpx *) 0)->ut_name)
  # define UTLINLEN	sizeof(((struct utmpx *) 0)->ut_line)
  # ifdef HAVE_STRUCT_UTMPX_UT_HOST
  #  define UTHOSTLEN	sizeof(((struct utmpx *) 0)->ut_host)
  # endif
  /* I just redefine a few words here.  Changing every occurrence below
   * seems like too much of work.  All UTMP functions have equivalent
   * UTMPX counterparts, so they can be added all here when needed.
   * Kimmo Suominen, Oct 14 1991
   */
  # if defined(__UTMPX_FILE) && !defined(UTMPX_FILE)
  #  define TCSH_PATH_UTMP __UTMPX_FILE
  # elif defined(_PATH_UTMPX)
  #  define TCSH_PATH_UTMP _PATH_UTMPX
  # elif defined(UTMPX_FILE)
  #  define TCSH_PATH_UTMP UTMPX_FILE
  # elif __FreeBSD_version >= 900000
  #  /* Why isn't this defined somewhere? */
  #  define TCSH_PATH_UTMP "/var/run/utx.active"
  # elif defined(__hpux)
  #  define TCSH_PATH_UTMP "/etc/utmpx"
  # elif defined(IBMAIX) && defined(UTMP_FILE)
  #  define TCSH_PATH_UTMP UTMP_FILE
  # endif
  # if defined(TCSH_PATH_UTMP) || !defined(HAVE_UTMP_H)
  #  define utmp utmpx
  #  define TCSH_USE_UTMPX
  #  if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
  #   define getutent getutxent
  #   define setutent setutxent
  #   define endutent endutxent
  #  endif /* HAVE_GETUTENT || HAVE_GETUTXENT */
  #  if defined(HAVE_STRUCT_UTMPX_UT_TV)
  #   define ut_time ut_tv.tv_sec
  #  elif defined(HAVE_STRUCT_UTMPX_UT_XTIME)
  #   define ut_time ut_xtime
  #  endif
  #  if defined(HAVE_STRUCT_UTMPX_UT_USER)
  #   define ut_name ut_user
  #  endif
  # endif /* TCSH_PATH_UTMP || !HAVE_UTMP_H */
  #endif /* HAVE_UTMPX_H */
  
  #if !defined(TCSH_USE_UTMPX) && defined(HAVE_UTMP_H)
  # include <utmp.h>
  # if defined(HAVE_STRUCT_UTMP_UT_TV)
  #  define ut_time ut_tv.tv_sec
  # elif defined(HAVE_STRUCT_UTMP_UT_XTIME)
  #  define ut_time ut_xtime
  # endif
  # if defined(HAVE_STRUCT_UTMP_UT_USER)
  #  define ut_name ut_user
  # endif
  # ifndef BROKEN_CC
  #  define UTNAMLEN	sizeof(((struct utmp *) 0)->ut_name)
  #  define UTLINLEN	sizeof(((struct utmp *) 0)->ut_line)
  #  ifdef HAVE_STRUCT_UTMP_UT_HOST
  #   ifdef _SEQUENT_
  #    define UTHOSTLEN	100
  #   else
  #    define UTHOSTLEN	sizeof(((struct utmp *) 0)->ut_host)
  #   endif
  #  endif	/* HAVE_STRUCT_UTMP_UT_HOST */
  # else
  /* give poor cc a little help if it needs it */
  struct utmp __ut;
  #  define UTNAMLEN	sizeof(__ut.ut_name)
  #  define UTLINLEN	sizeof(__ut.ut_line)
  #  ifdef HAVE_STRUCT_UTMP_UT_HOST
  #   ifdef _SEQUENT_
  #    define UTHOSTLEN	100
  #   else
  #    define UTHOSTLEN	sizeof(__ut.ut_host)
  #   endif
  #  endif /* HAVE_STRUCT_UTMP_UT_HOST */
  # endif /* BROKEN_CC */
  # ifndef TCSH_PATH_UTMP
  #  ifdef UTMP_FILE
  #   define TCSH_PATH_UTMP UTMP_FILE
  #  elif defined(_PATH_UTMP)
  #   define TCSH_PATH_UTMP _PATH_UTMP
  #  else
  #   define TCSH_PATH_UTMP "/etc/utmp"
  #  endif /* UTMP_FILE */
  # endif /* TCSH_PATH_UTMP */
  #endif /* !TCSH_USE_UTMPX && HAVE_UTMP_H */
  
  #ifndef UTNAMLEN
  #define UTNAMLEN 64
  #endif
  #ifndef UTLINLEN
  #define UTLINLEN 64
  #endif
  
  struct who {
      struct who *who_next;
      struct who *who_prev;
      char    who_name[UTNAMLEN + 1];
      char    who_new[UTNAMLEN + 1];
      char    who_tty[UTLINLEN + 1];
  #ifdef UTHOSTLEN
      char    who_host[UTHOSTLEN + 1];
  #endif /* UTHOSTLEN */
      time_t  who_time;
      int     who_status;
  };
  
  static struct who whohead, whotail;
  static time_t watch_period = 0;
  static time_t stlast = 0;
  #ifdef WHODEBUG
  static	void	debugwholist	(struct who *, struct who *);
  #endif
  static	void	print_who	(struct who *);
  
  
  #define ONLINE		01
  #define OFFLINE		02
  #define CHANGED		04
  #define STMASK		07
  #define ANNOUNCE	010
  #define CLEARED		020
  
  /*
   * Karl Kleinpaste, 26 Jan 1984.
   * Initialize the dummy tty list for login watch.
   * This dummy list eliminates boundary conditions
   * when doing pointer-chase searches.
   */
  void
  initwatch(void)
  {
      whohead.who_next = &whotail;
      whotail.who_prev = &whohead;
      stlast = 1;
  #ifdef WHODEBUG
      debugwholist(NULL, NULL);
  #endif /* WHODEBUG */
  }
  
  void
  resetwatch(void)
  {
      watch_period = 0;
      stlast = 0;
  }
  
  /*
   * Karl Kleinpaste, 26 Jan 1984.
   * Watch /etc/utmp for login/logout changes.
   */
  void
  watch_login(int force)
  {
      int     comp = -1, alldone;
      int	    firsttime = stlast == 1;
  #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
      struct utmp *uptr;
  #else
      int utmpfd;
  #endif
      struct utmp utmp;
      struct who *wp, *wpnew;
      struct varent *v;
      Char  **vp = NULL;
      time_t  t, interval = MAILINTVL;
      struct stat sta;
  #if defined(HAVE_STRUCT_UTMP_UT_HOST) && defined(_SEQUENT_)
      char   *host, *ut_find_host();
  #endif
  #ifdef WINNT_NATIVE
      USE(utmp);
      USE(utmpfd);
      USE(sta);
      USE(wpnew);
  #endif /* WINNT_NATIVE */
  
      /* stop SIGINT, lest our login list get trashed. */
      pintr_disabled++;
      cleanup_push(&pintr_disabled, disabled_cleanup);
  
      v = adrof(STRwatch);
      if ((v == NULL || v->vec == NULL) && !force) {
  	cleanup_until(&pintr_disabled);
  	return;			/* no names to watch */
      }
      if (!force) {
  	trim(vp = v->vec);
  	if (blklen(vp) % 2)		/* odd # args: 1st == # minutes. */
  	    interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL;
      }
      else
  	interval = 0;
  
      (void) time(&t);
      if (t - watch_period < interval) {
  	cleanup_until(&pintr_disabled);
  	return;			/* not long enough yet... */
      }
      watch_period = t;
  #ifndef WINNT_NATIVE
      /*
       * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
       * Don't open utmp all the time, stat it first...
       */
      if (stat(TCSH_PATH_UTMP, &sta)) {
  	if (!force)
  	    xprintf(CGETS(26, 1,
  			  "cannot stat %s.  Please \"unset watch\".\n"),
  		    TCSH_PATH_UTMP);
  	cleanup_until(&pintr_disabled);
  	return;
      }
      if (stlast == sta.st_mtime) {
  	cleanup_until(&pintr_disabled);
  	return;
      }
      stlast = sta.st_mtime;
  #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
      setutent();
  #else
      if ((utmpfd = xopen(TCSH_PATH_UTMP, O_RDONLY|O_LARGEFILE)) < 0) {
  	if (!force)
  	    xprintf(CGETS(26, 2,
  			  "%s cannot be opened.  Please \"unset watch\".\n"),
  		    TCSH_PATH_UTMP);
  	cleanup_until(&pintr_disabled);
  	return;
      }
      cleanup_push(&utmpfd, open_cleanup);
  #endif
  
      /*
       * xterm clears the entire utmp entry - mark everyone on the status list
       * OFFLINE or we won't notice X "logouts"
       */
      for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next)
  	wp->who_status = OFFLINE | CLEARED;
  
      /*
       * Read in the utmp file, sort the entries, and update existing entries or
       * add new entries to the status list.
       */
  #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
      while ((uptr = getutent()) != NULL) {
          memcpy(&utmp, uptr, sizeof (utmp));
  #else
      while (xread(utmpfd, &utmp, sizeof utmp) == sizeof utmp) {
  #endif
  
  # ifdef DEAD_PROCESS
  #  ifndef IRIS4D
  	if (utmp.ut_type != USER_PROCESS)
  	    continue;
  #  else
  	/* Why is that? Cause the utmp file is always corrupted??? */
  	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
  	    continue;
  #  endif /* IRIS4D */
  # endif /* DEAD_PROCESS */
  
  	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
  	    continue;	/* completely void entry */
  # ifdef DEAD_PROCESS
  	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
  	    continue;
  # endif /* DEAD_PROCESS */
  	wp = whohead.who_next;
  	while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
  	    wp = wp->who_next;/* find that tty! */
  
  	if (wp->who_next && comp == 0) {	/* found the tty... */
  	    if (utmp.ut_time < wp->who_time)
  	        continue;
  # ifdef DEAD_PROCESS
  	    if (utmp.ut_type == DEAD_PROCESS) {
  		wp->who_time = utmp.ut_time;
  		wp->who_status = OFFLINE;
  	    }
  	    else
  # endif /* DEAD_PROCESS */
  	    if (utmp.ut_name[0] == '\0') {
  		wp->who_time = utmp.ut_time;
  		wp->who_status = OFFLINE;
  	    }
  	    else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
  		/* someone is logged in */
  		wp->who_time = utmp.ut_time;
  		wp->who_status = ONLINE | ANNOUNCE;	/* same guy */
  	    }
  	    else {
  		(void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
  # ifdef UTHOSTLEN
  #  ifdef _SEQUENT_
  		host = ut_find_host(wp->who_tty);
  		if (host)
  		    (void) strncpy(wp->who_host, host, UTHOSTLEN);
  		else
  		    wp->who_host[0] = 0;
  #  else
  		(void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
  #  endif
  # endif /* UTHOSTLEN */
  		wp->who_time = utmp.ut_time;
  		if (wp->who_name[0] == '\0')
  		    wp->who_status = ONLINE;
  		else
  		    wp->who_status = CHANGED;
  	    }
  	}
  	else {		/* new tty in utmp */
  	    wpnew = xcalloc(1, sizeof *wpnew);
  	    (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
  # ifdef UTHOSTLEN
  #  ifdef _SEQUENT_
  	    host = ut_find_host(wpnew->who_tty);
  	    if (host)
  		(void) strncpy(wpnew->who_host, host, UTHOSTLEN);
  	    else
  		wpnew->who_host[0] = 0;
  #  else
  	    (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
  #  endif
  # endif /* UTHOSTLEN */
  	    wpnew->who_time = utmp.ut_time;
  # ifdef DEAD_PROCESS
  	    if (utmp.ut_type == DEAD_PROCESS)
  		wpnew->who_status = OFFLINE;
  	    else
  # endif /* DEAD_PROCESS */
  	    if (utmp.ut_name[0] == '\0')
  		wpnew->who_status = OFFLINE;
  	    else {
  		(void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
  		wpnew->who_status = ONLINE;
  	    }
  # ifdef WHODEBUG
  	    debugwholist(wpnew, wp);
  # endif /* WHODEBUG */
  
  	    wpnew->who_next = wp;	/* link in a new 'who' */
  	    wpnew->who_prev = wp->who_prev;
  	    wpnew->who_prev->who_next = wpnew;
  	    wp->who_prev = wpnew;	/* linked in now */
  	}
      }
  #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
      endutent();
  #else
      cleanup_until(&utmpfd);
  #endif
  #endif /* !WINNT_NATIVE */
  
      if (force || vp == NULL) {
  	cleanup_until(&pintr_disabled);
  	return;
      }
  
      /*
       * The state of all logins is now known, so we can search the user's list
       * of watchables to print the interesting ones.
       */
      for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
  	 *(vp + 1) != NULL && **(vp + 1) != '\0';
  	 vp += 2) {		/* args used in pairs... */
  
  	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
  	    alldone = 1;
  
  	for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
  	    if (wp->who_status & ANNOUNCE ||
  		(!eq(STRany, vp[0]) &&
  		 !Gmatch(str2short(wp->who_name), vp[0]) &&
  		 !Gmatch(str2short(wp->who_new),  vp[0])) ||
  		(!Gmatch(str2short(wp->who_tty),  vp[1]) &&
  		 !eq(STRany, vp[1])))
  		continue;	/* entry doesn't qualify */
  	    /* already printed or not right one to print */
  
  
  	    if (wp->who_status & CLEARED) {/* utmp entry was cleared */
  		wp->who_time = watch_period;
  		wp->who_status &= ~CLEARED;
  	    }
  
  	    if ((wp->who_status & OFFLINE) &&
  		(wp->who_name[0] != '\0')) {
  		if (!firsttime)
  		    print_who(wp);
  		wp->who_name[0] = '\0';
  		wp->who_status |= ANNOUNCE;
  		continue;
  	    }
  	    if (wp->who_status & ONLINE) {
  		if (!firsttime)
  		    print_who(wp);
  		(void) strcpy(wp->who_name, wp->who_new);
  		wp->who_status |= ANNOUNCE;
  		continue;
  	    }
  	    if (wp->who_status & CHANGED) {
  		if (!firsttime)
  		    print_who(wp);
  		(void) strcpy(wp->who_name, wp->who_new);
  		wp->who_status |= ANNOUNCE;
  		continue;
  	    }
  	}
      }
      cleanup_until(&pintr_disabled);
  }
  
  #ifdef WHODEBUG
  static void
  debugwholist(struct who *new, struct who *wp)
  {
      struct who *a;
  
      a = whohead.who_next;
      while (a->who_next != NULL) {
  	xprintf("%s/%s -> ", a->who_name, a->who_tty);
  	a = a->who_next;
      }
      xprintf("TAIL\n");
      if (a != &whotail) {
  	xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
  	abort();
      }
      a = whotail.who_prev;
      xprintf(CGETS(26, 4, "backward: "));
      while (a->who_prev != NULL) {
  	xprintf("%s/%s -> ", a->who_name, a->who_tty);
  	a = a->who_prev;
      }
      xprintf("HEAD\n");
      if (a != &whohead) {
  	xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
  	abort();
      }
      if (new)
  	xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
      if (wp)
  	xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
  }
  #endif /* WHODEBUG */
  
  
  static void
  print_who(struct who *wp)
  {
  #ifdef UTHOSTLEN
      Char   *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
  #else
      Char   *cp = str2short(CGETS(26, 8, "%n has %a %l."));
  #endif /* UTHOSTLEN */
      struct varent *vp = adrof(STRwho);
      Char *str;
  
      if (vp && vp->vec && vp->vec[0])
  	cp = vp->vec[0];
  
      str = tprintf(FMT_WHO, cp, NULL, wp->who_time, wp);
      cleanup_push(str, xfree);
      for (cp = str; *cp;)
  	xputwchar(*cp++);
      cleanup_until(str);
      xputchar('\n');
  } /* end print_who */
  
  
  char *
  who_info(ptr_t ptr, int c)
  {
      struct who *wp = ptr;
      char *wbuf;
  #ifdef UTHOSTLEN
      char *wb;
      int flg;
      char *pb;
  #endif /* UTHOSTLEN */
  
      switch (c) {
      case 'n':		/* user name */
  	switch (wp->who_status & STMASK) {
  	case ONLINE:
  	case CHANGED:
  	    return strsave(wp->who_new);
  	case OFFLINE:
  	    return strsave(wp->who_name);
  	default:
  	    break;
  	}
  	break;
  
      case 'a':
  	switch (wp->who_status & STMASK) {
  	case ONLINE:
  	    return strsave(CGETS(26, 9, "logged on"));
  	case OFFLINE:
  	    return strsave(CGETS(26, 10, "logged off"));
  	case CHANGED:
  	    return xasprintf(CGETS(26, 11, "replaced %s on"), wp->who_name);
  	default:
  	    break;
  	}
  	break;
  
  #ifdef UTHOSTLEN
      case 'm':
  	if (wp->who_host[0] == '\0')
  	    return strsave(CGETS(26, 12, "local"));
  	else {
  	    pb = wp->who_host;
  	    wbuf = xmalloc(strlen(pb) + 1);
  	    wb = wbuf;
  	    /* the ':' stuff is for <host>:<display>.<screen> */
  	    for (flg = isdigit((unsigned char)*pb) ? '\0' : '.';
  		 *pb != '\0' && (*pb != flg || ((pb = strchr(pb, ':')) != 0));
  		 pb++) {
  		if (*pb == ':')
  		    flg = '\0';
  		*wb++ = isupper((unsigned char)*pb) ?
  		    tolower((unsigned char)*pb) : *pb;
  	    }
  	    *wb = '\0';
  	    return wbuf;
  	}
  
      case 'M':
  	if (wp->who_host[0] == '\0')
  	    return strsave(CGETS(26, 12, "local"));
  	else {
  	    pb = wp->who_host;
  	    wbuf = xmalloc(strlen(pb) + 1);
  	    wb = wbuf;
  	    for (; *pb != '\0'; pb++)
  		*wb++ = isupper((unsigned char)*pb) ?
  		    tolower((unsigned char)*pb) : *pb;
  	    *wb = '\0';
  	    return wbuf;
  	}
  #endif /* UTHOSTLEN */
  
      case 'l':
  	return strsave(wp->who_tty);
  
      default:
  	wbuf = xmalloc(3);
  	wbuf[0] = '%';
  	wbuf[1] = (char) c;
  	wbuf[2] = '\0';
  	return wbuf;
      }
      return NULL;
  }
  
  void
  /*ARGSUSED*/
  dolog(Char **v, struct command *c)
  {
      struct who *wp;
      struct varent *vp;
  
      USE(v);
      USE(c);
      vp = adrof(STRwatch);	/* lint insists vp isn't used unless we */
      if (vp == NULL)		/* unless we assign it outside the if */
  	stderror(ERR_NOWATCH);
      resetwatch();
      wp = whohead.who_next;
      while (wp->who_next != NULL) {
  	wp->who_name[0] = '\0';
  	wp = wp->who_next;
      }
  }
  
  # ifdef UTHOSTLEN
  size_t
  utmphostsize(void)
  {
      return UTHOSTLEN;
  }
  
  char *
  utmphost(void)
  {
      char *tty = short2str(varval(STRtty));
      struct who *wp;
      char *host = NULL;
  
      watch_login(1);
  
      for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
  	if (strcmp(tty, wp->who_tty) == 0)
  	    host = wp->who_host;
  	wp->who_name[0] = '\0';
      }
      resetwatch();
      return host;
  }
  # endif /* UTHOSTLEN */
  
  #endif /* HAVENOUTMP */
  