[comp.os.minix] last

pascal@ucrmath.UUCP (Freeman P. Pascal IV) (11/26/88)

Hello,

Well, here's something for those of who are spoiled by BSD.  It's a last(1)
command for MINIX v1.3.  It has some of the BSD mannerisms and some of my
own.  Also, the display format is quite abit different.  To install just chop
off everything before the shar text and run through sh(1) or unshar(1).

You'll have to apply the cdiffs to date.c and utmp.h.  These diffs modify
date.c and utmp.h to handle the extra utmp markers for new and old system
times.

After you have applied the cdiffs you might want to edit the Makefile to
suit your own installation.  Then type "make" or "make install", the
latter will install last(1).  There is a man page source that is compatible
with nroff and nro.

Usage:	last [-RTr] [-N lines] [user ...]
	last [-r] reboot
	last [-r] time

Examples of use:

	last			# displays all entries in utmp
	last -r guest		# displays all login/logouts of guest until
				# the last reboot
	last -r reboot		# display the last reboot
	last -rR		#   ditto
	last time		# display all time changes recorded
	last -T			#   ditto

I wrote this after seeing the SysV version come across comp.sources.  My
code uses a couple of ideas from the SysV version but the rest is of my
own invention.  This code is dedicated to Glen Overby (ncoverby@ndsuvax)
who always wanted me to write a last command for the 3b2 and MINIX. I
finally did it (at least for MINIX, he'll have to use the SysV version
from comp.source for the 3b2 :-).

I would appreciate any bug reports, although you can send any flames to
/dev/null.

Freeman P. Pascal IV

---- Cut Here ---- Cut Here ---- Cut Here ---- Cut Here ---- Cut Here ----

echo x - ReadMe
sed '/^X/s///' > ReadMe << '/'
XHello,
X
XWell, here's something for those of who are spoiled by BSD.  It's a last(1)
Xcommand for MINIX v1.3.  It has some of the BSD mannerisms and some of my
Xown.  Also, the display format is quite abit different.  To install just chop
Xoff everything before the shar text and run through sh(1) or unshar(1).
X
XYou'll have to apply the cdiffs to date.c and utmp.h.  These diffs modify
Xdate.c and utmp.h to handle the extra utmp markers for new and old system
Xtimes.
X
XAfter you have applied the cdiffs you might want to edit the Makefile to
Xsuit your own installation.  Then type "make" or "make install", the
Xlatter will install last(1).  There is a man page source that is compatible
Xwith nroff and nro.
X
XUsage:	last [-RTr] [-N lines] [user ...]
X	last [-r] reboot
X	last [-r] time
X
XExamples of use:
X
X	last			# displays all entries in utmp
X	last -r guest		# displays all login/logouts of guest until
X				# the last reboot
X	last -r reboot		# display the last reboot
X	last -rR		#   ditto
X	last time		# display all time changes recorded
X	last -T			#   ditto
X
XI wrote this after seeing the SysV version come across comp.sources.  My
Xcode uses a couple of ideas from the SysV version but the rest is of my
Xown invention.  This code is dedicated to Glen Overby (ncoverby@ndsuvax)
Xwho always wanted me to write a last command for the 3b2 and MINIX. I
Xfinally did it (at least for MINIX, he'll have to use the SysV version
Xfrom comp.source for the 3b2 :-).
X
XI would appreciate any bug reports, although you can send any flames to
X/dev/null.
X
XFreeman P. Pascal IV
/
echo x - Makefile
sed '/^X/s///' > Makefile << '/'
X#
X#	last Makefile
X#
X
XCFLAGS	= -i -F -O 
XDEST	= /usr/lbin
XOWNER	= bin
XGROUP	= sys
XMODE	= 755
XMEM	= 4096
X
Xall:	last
X	@echo "Finished"
X
Xlast:	last.c
X	cc $(CFLAGS) -o last last.c
X
Xinstall:	all
X	@cp	last			$(DEST)
X	@chown	$(OWNER).$(GROUP)	$(DEST)/last
X	@chmod	$(MODE)			$(DEST)/last
X	@chmem	=$(MEM)			$(DEST)/last
X
Xclean:
X	rm -f *.s *.bak
X
/
echo x - date.c.cdif
sed '/^X/s///' > date.c.cdif << '/'
X*** date.c.orig	Thu Nov 24 16:06:41 1988
X--- date.c	Thu Nov 24 17:51:24 1988
X***************
X*** 2,7 ****
X--- 2,13 ----
X  
X  #include <stdio.h>
X  #include <time.h>
X+ #include <sys/types.h>
X+ #include <sys/file.h>
X+ #include <utmp.h>
X+ 
X+ #define	WTMPSIZE	8
X+ #define	SUPER_USER	0	/* super user's id	*/
X  
X  #define	MIN	60L		/* # seconds in a minute */
X  #define	HOUR	(60 * MIN)	/* # seconds in an hour */
X***************
X*** 81,88 ****
X    ct += p->tm_hour * HOUR;
X    ct += p->tm_min * MIN;
X    ct += p->tm_sec;
X!   if (stime(ct))
X! 	fprintf(stderr, "Set date not allowed\n");
X  }
X  
X  conv(ptr, max)
X--- 87,98 ----
X    ct += p->tm_hour * HOUR;
X    ct += p->tm_min * MIN;
X    ct += p->tm_sec;
X!   if (getuid() == SUPER_USER)	wtmp( U_OLD_TIME );
X!   if (stime(ct)) {
X! 	fprintf(stderr, "Set date not allowed\n");
X! 	exit( 1 );
X!   }
X!   wtmp( U_NEW_TIME );
X  }
X  
X  conv(ptr, max)
X***************
X*** 119,121 ****
X--- 129,154 ----
X    else
X  	return(0);
X  }
X+ 
X+ wtmp( special )
X+ char	special;
X+ {
X+ /* Make an entry in /usr/adm/wtmp. */
X+ 
X+   int		i, fd;
X+   long		time();
X+   struct utmp	u_buf;
X+ 
X+   fd = open( WTMP, O_WRONLY );
X+   if (fd < 0) return;		/* if wtmp does not exist, no accounting */
X+   i =lseek(fd, 0L, L_XTND);	/* append to file */
X+ 
X+   for (i = 0; i < WTMPSIZE; i++) {
X+ 	u_buf.ut_line[i] = 0;
X+ 	u_buf.ut_name[i] = 0;		/* user name will be empty	*/
X+   }
X+   *u_buf.ut_line = special;		/* mark utmp entry as specail	*/
X+   time(u_buf.ut_time );		/* set current time 		*/
X+   write( fd, u_buf, sizeof( struct utmp ));
X+   close(fd);
X+ }
/
echo x - last.1
sed '/^X/s///' > last.1 << '/'
X.TH last 1 "24 November 1988" "MINIX 1.3" "MINIX User's Referenc Manual"
X.SH NAME
Xlast - display last login information
X.SH SYNOPSIS
X.B last
X[-rRT] [-N lines] [user ...]
X.br
X.B last
Xreboot
X.br
X.B last
Xtime
X.SH DESCRIPTION
X.I Last
Xwill display the last time a user logged in and the duration.  It will 
Xalso display other information, such as reboots and system time changes.
X.sp
XIf no users are given as arguments then everything is displayed.  Otherwise,
Xinformation for only those users listed will only be displayed.
X.sp
XIf the pseudo user name
X.I reboot
Xor then
X.I -R
Xoption is given then only information for system reboots will be displayed.
X.sp
XThe pseudo user name
X.I time
Xor the
X.I -T
Xoption can be used to display only information for system time changes.
X.sp
XThe
X.I -r
Xoption informs
X.I last(1)
Xto stop at the first reboot encountered.
X.SH NOTE
XIn order for
X.I last(1)
Xto work login accounting must be active.  To do this, the file /usr/adm/wtmp
Xmust be present.
X.SH FILES
X/usr/adm/wmtp     login accounting
X.SH "SEE ALSO"
Xwho(1), utmp(5)
X.SH AUTHOR
XFreeman P. Pascal IV, uunet!ucsd!ucrmath!pascal
X.SH BUGS
XIf the /usr/adm/wtmp file was started incorrectly the final entry displayed
X(the first entry in /usr/adm/wtmp) will show a bogus logout time of Thr 1
XJan 00:00 1970.
/
echo x - last.1.man
sed '/^X/s///' > last.1.man << '/'
X
Xlast(1)           MINIX User's Referenc Manual            last(1)
X
X
XNNAAMMEE 
X     last - display last login information 
X
XSSYYNNOOPPSSIISS 
X     llaasstt [-rRT] [-N lines] [user ...] 
X     llaasstt reboot 
X     llaasstt time 
X
XDDEESSCCRRIIPPTTIIOONN 
X     _L_a_s_t  will  display  the  last time a user logged in and the
X     duration.  It will  also display other information, such  as
X     reboots and system time changes.  
X
X     If  no  users  are  given  as  arguments  then everything is
X     displayed.  Otherwise,  information  for  only  those  users
X     listed will only be displayed.  
X
X     If  the  pseudo  user name _r_e_b_o_o_t or then -_R option is given
X     then only information for system reboots will be displayed.  
X
X     The pseudo user name _t_i_m_e or the -_T option can  be  used  to
X     display only information for system time changes.  
X
X     The  -_r  option  informs _l_a_s_t(_1) to stop at the first reboot
X     encountered.  
X
XNNOOTTEE 
X     In order for  _l_a_s_t(_1)  to  work  login  accounting  must  be
X     active.    To  do  this,  the  file  /usr/adm/wtmp  must  be
X     present.  
X
XFFIILLEESS 
X     /usr/adm/wmtp login accounting 
X
XSSEEEE AALLSSOO 
X     who(1), utmp(5) 
X
XAAUUTTHHOORR 
X     Freeman P. Pascal IV, uunet!ucsd!ucrmath!pascal 
X
XBBUUGGSS 
X     If the /usr/adm/wtmp file was started incorrectly the  final
X     entry displayed (the first entry in /usr/adm/wtmp) will show 
X     a bogus logout time of Thr 1 Jan 00:00 1970.  
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
XMINIX 1.3               24 November 1988                        1
X
/
echo x - last.c
sed '/^X/s///' > last.c << '/'
X/*  last.c - check when someone last logged in        Freeman P. Pascal IV */
X
X/*  Copyright (C) November 1988, Freeman P. Pascal IV
X *
X *  This program and it's binaries can be freely distributed as long as
X *  the follow limitations are meet:
X *
X *	- this copyright notice must be present and unaltered
X *	- Source code must accompany any distributions
X *	- use is for non-profit
X *
X *  Also, I'm not responsible for any damage that might occure.  I also
X *  do not garentee the validity of this code, bugs may still exist.
X *  Use at your own risk.
X */
X
X/*  Usage:  last [-RTr] [-N lines] [user ...]
X *          last [-r] reboot
X *	    last [-r] time
X */
X
X#include	<stdio.h>
X#include	<sys/types.h>
X#include	<sys/file.h>
X#include	<utmp.h>
X#include	<time.h>
X#include	<limits.h>
X
X/*  Some MINIX installations may not have these in their 
X *  /usr/include/utmp.h file.
X */
X#ifndef	U_SYS_REBOOT
X#define	U_SYS_REBOOT	'~'	/* system reboot		*/
X#endif
X#ifndef	U_OLD_TIME
X#define U_OLD_TIME	'|'	/* time marker for old time	*/
X#endif
X#ifndef	U_NEW_TIME
X#define	U_NEW_TIME	'{'	/* time marker for new time	*/
X#endif
X
X/*  pseudo user names */
X#define REBOOT_MSG		"reboot"
X#define NEW_TIME_MSG		"new system time"
X#define OLD_TIME_MSG		"old system time"
X#define	UNKNOWN_MSG		"unknown marker"
X
X#define	STILL_LOGGED_IN		-1L
X
Xtypedef	int	BOOL;
X#define	FALSE	((BOOL) 0)
X#define	TRUE	((BOOL) ~FALSE)
X
Xchar		*wday[] = { "Sun","Mon", "Tue", "Wen", "Thr", "Fri", "Sat" };
Xchar		*month[]= { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
X			    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
X
Xtypedef	struct entry_s {
X  char			name[9];	/* user name		*/
X  char			line[9];	/* terminal 		*/
X  time_t		x_time;		/* login time		*/
X  time_t		y_time;		/* logout time		*/
X  struct entry_s	*next;		/* link to next entry	*/
X} ENTRY;
X
XBOOL		Uspecial();
XENTRY		*conv_entry();
XENTRY		*resolve_entry();
Xchar		*delta_time();
X
XENTRY		*elist = (ENTRY *) NULL;
XBOOL		Rflg;
XBOOL		Tflg;
XBOOL		rflg;
Xchar		*argv0;
Xchar		**show_list;
Xint		show_cnt;
X
Xextern char	*optarg;	/* for getopt(3)	*/
Xextern int	optind;		/*  ditto		*/
X
Xmain( argc, argv )
Xint	argc;
Xchar	*argv[];
X{
X  int		line_cnt;	/* value set by -Nn */
X  int		wtmp_fd;
X  off_t		wtmp_fpos;	/* byte position within /usr/adm/wtmp	*/
X  int		i;
X  char		opt;		/* argv option		*/
X  ENTRY		*ep;
X  struct utmp	u_buf;		/* read buffer		*/
X
X  argv0 = argv[0];
X  Rflg = Tflg = rflg = FALSE;
X  line_cnt = 0;
X  while(( opt = getopt( argc, argv, "N:RTrx" )) != EOF)
X	switch( opt ) {
X		case 'N' :	/* stop after n lines		*/
X			line_cnt = atoi( optarg );
X			break;
X		case 'R' :	/* show only reboots		*/
X			Rflg = TRUE;
X			break;
X		case 'T' :	/* show only time changes	*/
X			Tflg = TRUE;
X			break;
X		case 'r' :	/* stop at latest reboot	*/
X			rflg = TRUE;
X			break;
X		default :	/* none of the above		*/
X			usage();
X	}
X
X  /*  any arguments left over must be a list of users to display
X   */
X  show_cnt = argc - optind;	/* save number to users to show */
X  show_list = argv[ optind ];	/* save user list to show	*/
X
X  /*  if last was invoked with either "reboot" or "time" as it's
X   *  only argument then show entires for system reboots and
X   *  system time changes respectively.
X   */
X  if (show_cnt == 1) {
X	if (strcmp( argv[ optind ], "reboot" ) == 0)
X		Rflg = TRUE;
X	if (strcmp( argv[ optind ], "time" ) == 0)
X		Tflg = TRUE;
X  }
X
X  if (( wtmp_fd = open( WTMP, O_RDONLY )) < 0 ) {
X	fprintf( stderr, "%s: Cannot open %s\n", argv[0], WTMP );
X	exit( 1 );
X  }
X  wtmp_fpos = lseek( wtmp_fd, 0L, L_XTND );	/* skip to end of wtmp file */
X
X  i = 0;
X  while( i < line_cnt || line_cnt == 0 ) {
X
X	/*  seek to next (previous actually) utmp entry  */
X	wtmp_fpos = lseek( wtmp_fd, wtmp_fpos - sizeof( struct utmp ), L_SET);
X	if (wtmp_fpos == -1) {
X		fprintf( stderr, "End of %s file.\n", WTMP );
X		exit(0);
X	}
X
X	/*  attempt to read entry
X	 */
X	if (read( wtmp_fd, u_buf, sizeof( struct utmp )) < 0 ) {
X		perror( argv0 );
X		exit( 1 );
X	}			 
X
X	ep = conv_entry( u_buf );
X
X	switch( *ep->line ) {
X		case U_SYS_REBOOT :	/* system reboot found */
X			if (show_cnt <= 0 || Rflg) {
X				print_entry( ep );
X				i++;	/* we've printed something */
X			}
X			/*  If rflg is true then we'll work as
X			 *  normal until we find a reboot and
X			 *  exit.
X			 */
X			if (rflg)  exit( 0 );
X
X			/*  If Rflg is true then we dion't need
X			 *  to save anything since all we're 
X			 *  concerned about is reboot times
X			 */
X			if (Rflg)
X				continue;
X			else
X				add_entry( ep );
X			break;
X
X		case U_OLD_TIME :	/* time prior to 'date' command	*/
X		case U_NEW_TIME :	/* time after 'date' command	*/
X			if (show_cnt <= 0 || Tflg) {
X				print_entry( ep );
X				i++;
X			}
X
X		default :
X			/*  User login/out entries...
X			 *
X			 *  if Rflg (show only reboots) or Tflg
X			 *  (show only time changes) are true
X			 *  then skip showing user login/logouts.
X			 */
X			if (Rflg || Tflg)
X				continue;
X			 
X			/*  the name field will contain the user's name
X			 *  for logins and is empty for logouts.
X			 */
X			if (*ep->name)
X				print_entry( resolve_entry( ep ));
X			else
X				add_entry( ep );
X	}
X  }
X}
X
X/************************************************************************\
X**				conv_entry()				**
X\************************************************************************/
XENTRY *
Xconv_entry( u_ep )
Xstruct utmp	*u_ep;
X{
X/*  Convert utmp entry into ENTRY type
X */
X  ENTRY		*ep;
X
X  if (( ep = (ENTRY *) malloc( sizeof( ENTRY ))) == NULL ) {
X	perror( argv0 );
X	exit( 1 );
X  }
X
X  strcpy( ep->line, u_ep->ut_line );	/*  copy terminal	*/
X  strcpy( ep->name, u_ep->ut_name );	/*  copy user name	*/
X
X  ep->x_time = ep->y_time = 0;		/*  clear times		*/
X
X  if ( Uspecial( ep ) || *ep->name )
X	ep->x_time = u_ep->ut_time;
X  else
X  	ep->y_time = u_ep->ut_time;
X	
X  ep->next = NULL;
X
X  return( ep );
X}
X
X/************************************************************************\
X**				add_entry()				**
X\************************************************************************/
Xadd_entry( ep )
XENTRY	*ep;
X{
X/*  added entry to list of unresolved logouts.
X */
X
X  ep->next = elist;
X  elist = ep;
X}
X
X/************************************************************************\
X**			      resolve_entry()				**
X\************************************************************************/
XENTRY *
Xresolve_entry( ep )
XENTRY	*ep;
X{
X/*  Search for and return a coresponding login entry.
X */
X  register ENTRY	*sep;
X  register ENTRY	*tep;		/* trailer */
X  ENTRY			*reboot = NULL;	/* reboot seen */
X  static ENTRY		e_buf;		/* for users still logged in */
X
X  /*  Search for a logout entry for the same terminal as the login is
X   *  on.  While searching if we find a reboot entry that is later
X   *  (occured after the login) then the reboot must have occured
X   *  while the user was still logged in.  We'll use the reboot time
X   *  as our logout time.
X   */
X  sep = elist;
X  while( sep & strcmp( sep->line, ep->line ) != 0 ) {
X	/*  Check if a reboot occured at some time.  If so check if
X	 *  the reboot occured later than the login/logout pair
X	 *  which we're searching for.
X	 */
X	if ( *sep->line == U_SYS_REBOOT & sep->x_time > ep->x_time ) {
X		reboot = sep;
X		break;
X	}
X	tep = sep;
X	sep = sep->next;
X  }
X
X  /*  No matter if we found a match we need to copy these entries
X   */
X  strcpy( e_buf.name, ep->name );		/* user's name		*/
X  strcpy( e_buf.line, ep->line );		/* terminal name	*/
X
X  if ( sep ) {	/* entry resolved (logout found) */
X	/*  Two possiblities are present at this stage.  One, we have 
X	 *  found a valid logout.  Two, a reboot occured while the user
X	 *  was still logged in.
X	 */
X	if ( reboot != NULL ) {
X		/*  Reboot occured before user logged out
X		 */
X		e_buf.x_time = ep->x_time;
X		e_buf.y_time = reboot->x_time;
X	} else {
X		/*  Valid logout. - No reboot has occured since
X		 *  user has logged in.
X		 */
X		tep->next = sep->next;		/* patch list		*/
X		e_buf.x_time = ep->x_time;	/* save login time	*/
X		e_buf.y_time = sep->y_time;	/* save logout time	*/
X		free( sep );			/* destroy entry	*/
X	}
X  } else {
X	/*  User is still logged in
X	 */
X	e_buf.x_time = ep->x_time;
X	e_buf.y_time = STILL_LOGGED_IN;		/* mark as still on	*/
X	tep->next = sep->next;			/* remove from list	*/
X	free( sep );				/* destroy entry	*/
X  }
X  free( ep );					/* free login entry	*/
X  return( e_buf );
X}
X
X/************************************************************************\
X**				print_entry()				**
X\************************************************************************/
Xprint_entry( ep )
XENTRY		*ep;
X{
X/*  Print entry as required by it's type.
X */
X  int		show_indx;	/* index into show_list		*/
X  char		*pseudo_name;	/* for non user entries		*/
X  BOOL		show = FALSE;	/* TRUE if entry is to be shown	*/
X  struct tm	*tm;
X  struct tm	*localtime();
X
X  /*  Check to see if we're suppose to show this user
X   */
X  if ( Uspecial( ep ) || show_cnt <= 0 )
X	show = TRUE;
X  else
X	for( show_indx = 0; show_indx < show_cnt; show_indx++ )
X		if ( strcmp( show_list[ show_indx ], ep->name ) == 0 ) {
X			show = TRUE;
X			break;
X  		}
X
X  if (! show ) return;		/* entry not be shown */
X
X  tm = localtime( ep->x_time );
X
X  /*  user is to be shown, do so now  */
X  if (Uspecial( ep )) {
X	/*  Print a special entry
X	 */
X	if (*ep->line == U_SYS_REBOOT)  /* system reboot entry	*/
X		pseudo_name = REBOOT_MSG;
X	else if (*ep->line== U_NEW_TIME)
X		pseudo_name = NEW_TIME_MSG;
X	else if (*ep->line == U_OLD_TIME)
X		pseudo_name = OLD_TIME_MSG;
X	else
X		pseudo_name = UNKNOWN_MSG;
X
X	printf( "%-15s              %3s %3s %2d %02d:%02d\n",
X		pseudo_name,
X		wday[tm->tm_wday],		/* day of week	*/
X		month[tm->tm_mon],
X		tm->tm_mday,			/* day of month	*/
X		tm->tm_hour,			/* hour		*/
X		tm->tm_min			/* minute	*/
X	);
X  } else {
X	/*  Print a normal logout
X	 */
X	printf( "%-8s         %-8s    %3s %3s %02d %02d:%02d - ",
X		ep->name,
X		ep->line,
X		wday[tm->tm_wday],
X		month[tm->tm_mon],
X		tm->tm_mday,			/* login day of month	*/
X		tm->tm_hour,			/*   "   hour		*/
X		tm->tm_min			/*   "   minute		*/
X	);
X
X	if ( ep->y_time == STILL_LOGGED_IN )
X		printf( "STILL LOGGED IN\n" );
X	else
X		if (ep->y_time == 0 )
X			printf( "NO LOGOUT TIME\n" );
X		else {
X			tm = localtime( ep->y_time );
X			printf( "%02d:%02d (%s)\n",
X				tm->tm_hour,	/* logout hour		*/
X				tm->tm_min,	/*   "    minute	*/
X				delta_time( ep->x_time, ep->y_time )
X			);
X		}
X  }
X}
X
X/************************************************************************\
X**				delta_time()				**
X\************************************************************************/
Xchar *
Xdelta_time( x_time, y_time )
Xtime_t	x_time, y_time;
X{
X/*  Return the difference in time between x_time and y_time.
X */
X  static char	time_str[20];
X  time_t	delta_t;
X  int		secs;
X  int		mins;
X  int		hours;
X  int		days;
X
X  delta_t = y_time - x_time;	/* difference between login and logout	*/
X
X  secs = delta_t % 60L;				/* get diff of seconds	*/
X  delta_t = (delta_t - (time_t) secs)/60L;
X
X  mins = delta_t % 60L;				/* minutes */
X  delta_t = (delta_t - (time_t) mins)/60L;
X
X  hours = delta_t % 24;				/* hours */
X  delta_t = (delta_t - (time_t) hours)/24L;
X
X  days = delta_t;
X
X  if (days)
X	sprintf( time_str, "%02d:%02d:%02d  %d Day%s",
X		hours,
X		mins,
X		secs,
X		days,
X		(days > 1) ? "s" : ""
X	);
X  else
X	sprintf( time_str, "%02d:%02d:%02d", hours, mins, secs );
X
X  return( time_str );
X}
X
X/************************************************************************\
X**				Uspecial()				**
X\************************************************************************/
XBOOL
XUspecial( ep )
XENTRY	*ep;
X{
X/*  Test if entry has any special meaning in the utmp file
X */
X  if (  *ep->line == U_SYS_REBOOT || 
X	*ep->line == U_NEW_TIME || 
X	*ep->line == U_OLD_TIME )
X	return TRUE;
X  else
X	return FALSE;
X}
X
X/************************************************************************\
X**				usage()					**
X\************************************************************************/
Xusage()
X{
X  fprintf( stderr, "Usage: %s [-RrT] [-N lines] [user ...]\n       %s [-r] reboot\n       %s [-r] time\n", argv0, argv0, argv0 );
X  exit( 1 );
X}
/
echo x - utmp.h.cdif
sed '/^X/s///' > utmp.h.cdif << '/'
X*** utmp.h.orig	Fri Nov 25 16:36:09 1988
X--- utmp.h	Fri Nov 25 16:35:26 1988
X***************
X*** 1,4 ****
X! /*  utmp.h - Used by login(1), init, and who(1)  */
X  
X  #define WTMP  "/usr/adm/wtmp"
X  
X--- 1,4 ----
X! /*  utmp.h - Used by login(1), init, last(1), date(1), and who(1)  */
X  
X  #define WTMP  "/usr/adm/wtmp"
X  
X***************
X*** 8,10 ****
X--- 8,19 ----
X    char ut_name[8];		/* user name */
X    long ut_time;			/* login/out time */
X  };
X+ 
X+ /*  Special markers for ut_line field  */
X+ #define	U_SYS_REBOOT	'~'	/* system reboot		*/
X+ 
X+ /*  These markers are used to denote a system time change right
X+  *  before and after the actual time change.  Used by Date(1).
X+  */
X+ #define U_OLD_TIME	'|'	/* time marker for old time	*/
X+ #define	U_NEW_TIME	'{'	/* time marker for new time	*/
/
exit 0

-- 
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-[  M I N I X  ]-=-=-=-=-=-=-=-=-=-=-=-=-=-=
uunet!ucsd!ucrmath!pascal
uunet!ucsd!ucrmath!gizmo1!pascal              MINIX - just say YES!
KA0TGN (North Dakota)                   ( Until you can afford you Sun :-)

tholm@uvicctr.UUCP (Terrence W. Holm) (11/30/88)

In article <544@ucrmath.UUCP> pascal@ucrmath.UUCP (Freeman P. Pascal IV) writes
>
>Well, here's something for those of who are spoiled by BSD.  It's a last(1)
>command for MINIX v1.3.  It has some of the BSD mannerisms and some of my
>own.  Also, the display format is quite abit different.

Was there something wrong with the current MINIX last(1)?
It was posted in June 1988.

			Terrence W. Holm
			  uunet!uw-beaver!uvicctr!tholm
			  tholm%uvunix.bitnet
			  tholm%sirius.UVic.ca@relay.ubc.ca

holm@ubc-bdcvax.UUCP (Terrence W. Holm) (02/02/89)

EFTH MINIX report #65  - February 1989 -  last(1)


Last(1) was originally posted as EFTH report #21 (June 1988).
Here it is again, for those who missed it.

You should change the first log call in tools/init.c to
wtmp("~", "reboot") because last(1) knows about both reboot
and shutdown records and we should get this right in init.

		Terrence W. Holm
		  holm@bdc.ubc.ca


----------------------------------------------------------
echo x - last.c
gres '^X' '' > last.c << '/'
X/* last(1)		Display the user log-in history.
X *
X * Author: Terrence W. Holm	May 1988
X */
X
X
X#include <utmp.h>
X#include <stdio.h>
X#include <signal.h>
X
X#ifndef  WTMP
X#define  WTMP   "/usr/adm/wtmp"
X#endif
X
X#define  FALSE	0
X#define  TRUE	1
X
X#define  BUFFER_SIZE     4096	/*  Room for wtmp records	*/
X#define  MAX_WTMP_COUNT  ( BUFFER_SIZE / sizeof(struct utmp) )
X
X#define  min( a, b )     ( (a < b) ? a : b )
X#define  max( a, b )	 ( (a > b) ? a : b )
X
X
Xextern long  time();
Xint   Sigint();
Xint   Sigquit();
X
X
Xtypedef  struct  logout		/*  A logout time record	*/
X  {
X  char  line[8];		/*  The terminal name		*/
X  long  time;			/*  The logout time		*/
X
X  struct logout *next;		/*  Next in linked list		*/
X  } logout;
X
X
X
X/****************************************************************/
X/*								*/
X/*	last [-r] [-count] [-f file] [name] [tty] ...		*/
X/*								*/
X/*								*/
X/*	Last(1) searches backwards through the file of log-in 	*/
X/*	records (/usr/adm/wtmp), displaying the length of 	*/
X/*	log-in sessions as requested by the options:		*/
X/*								*/
X/*								*/
X/*	-r	Search backwards only until the last reboot	*/
X/*		record.						*/
X/*								*/
X/*	-count	Only print out <count> records. Last(1) stops	*/
X/*		when either -r or -count is satisfied, or at	*/
X/*		the end of the file if neither is given.	*/
X/*								*/
X/*	-f file	Use "file" instead of "/usr/adm/wtmp".		*/
X/*								*/
X/*	name	Print records for the user "name".		*/
X/*								*/
X/*	tty	Print records for the terminal "tty". Actually,	*/
X/*		a list of names may be given and all records	*/
X/*		that match either the user or tty name are	*/
X/*		printed. If no names are given then all records	*/
X/*		are displayed.					*/
X/*								*/
X/*								*/
X/*	A sigquit (^\) causes last(1) to display how far it	*/
X/*	has gone back in the log-in record file, it then 	*/
X/*	continues. This is used to check on the progress of	*/
X/*	long running searches. A sigint will stop last(1).	*/
X/*								*/
X/****************************************************************/
X
X
X
X/****************************************************************/
X/*		Command-line option flags			*/
X/****************************************************************/
X
X
Xchar    boot_limit   = FALSE;	/*  Stop on latest reboot	*/
Xchar    count_limit  = FALSE;	/*  Stop after print_count	*/
Xint     print_count;
Xint     arg_count;		/*  Used to select specific	*/
Xchar  **args;			/*  users and ttys		*/
X
X
X
X/****************************************************************/
X/*		Global variables				*/
X/****************************************************************/
X
X
Xlong    boot_time    = 0;	/*  Zero means no reboot yet	*/
Xchar   *boot_down;		/*  "crash" or "down " flag	*/
Xlogout *first_link   = NULL;	/*  List of logout times	*/
Xint     interrupt    = FALSE;	/*  If sigint or sigquit occurs	*/
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	main()							*/
X/*								*/
X/****************************************************************/
X
X
Xmain( argc, argv )
X  int argc;
X  char *argv[];
X
X  {
X  char  *wtmp_file = WTMP;
X  FILE  *f;
X  long   size;		/*  Number of wtmp records in the file	*/
X  int    wtmp_count;	/*  How many to read into wtmp_buffer	*/
X  struct utmp  wtmp_buffer[ MAX_WTMP_COUNT ];
X
X
X  --argc;
X  ++argv;
X
X  while (  argc > 0  &&  *argv[0] == '-'  )
X    {
X    if (  strcmp( argv[0], "-r" ) == 0  )
X	boot_limit = TRUE;
X
X    else if (  argc > 1  &&  strcmp( argv[0], "-f" ) == 0  )
X	{
X	wtmp_file = argv[1];
X	--argc;
X	++argv;
X	}
X
X    else if ( (print_count = atoi( argv[0]+1 )) > 0 )
X	count_limit = TRUE;
X
X    else
X	{
X	fprintf( stderr, "Usage: last [-r] [-count] [-f file] [name] [tty] ...\n" );
X	exit( 1 );
X	}
X
X    --argc;
X    ++argv;
X    }
X
X
X  arg_count = argc;
X  args      = argv;
X
X
X
X  if( (f = fopen( wtmp_file, "r" )) == NULL )
X    {
X    perror( wtmp_file );
X    exit( 1 );
X    }
X
X  if ( fseek( f, 0L, 2 ) != 0  ||  (size = ftell(f)) % sizeof(struct utmp) != 0 )
X    {
X    fprintf( stderr, "last: invalid wtmp file\n" );
X    exit( 1 );
X    }
X
X
X  if ( signal( SIGINT, SIG_IGN ) != SIG_IGN )
X    {
X    signal( SIGINT,  Sigint  );
X    signal( SIGQUIT, Sigquit );
X    }
X
X
X
X  size /= sizeof(struct utmp);	/*  Number of records in wtmp	*/
X
X  if ( size == 0 )
X    {
X    long now = time( 0 );
X
X    printf( "\nwtmp begins %.16s \n", ctime( &now ) );
X    exit( 0 );
X    }
X
X
X  while( size > 0 )
X    {
X    wtmp_count = (int) min( size, MAX_WTMP_COUNT );
X
X    size -= (long) wtmp_count;
X
X    fseek( f, size * sizeof(struct utmp), 0 );
X
X
X    if ( fread( &wtmp_buffer[ 0 ], sizeof(struct utmp), wtmp_count, f ) != wtmp_count )
X	{
X    	fprintf( stderr, "last: read error on wtmp file\n" );
X    	exit( 1 );
X    	}
X
X
X    while ( --wtmp_count >= 0 )
X	{
X	Process( &wtmp_buffer[ wtmp_count ] );
X     
X	if ( interrupt )
X	  {
X      	  printf( "\ninterrupted %.16s \n",
X		ctime( &wtmp_buffer[ wtmp_count ].ut_time ) );
X
X    	  if ( interrupt == SIGINT )
X      	      exit( 2 );
X
X	  interrupt = FALSE;
X	  signal( SIGQUIT, Sigquit );
X	  }
X	}
X
X    }  /* end while( size > 0 ) */
X
X  printf( "\nwtmp begins %.16s \n", ctime( &wtmp_buffer[ 0 ].ut_time ) );
X  exit( 0 );
X  }
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	A log-in record format file contains four types of	*/
X/*	records.						*/
X/*								*/
X/*	  [1] generated on a system reboot:			*/
X/*								*/
X/*	   line="~", name="reboot", host="", time=date()	*/
X/*								*/
X/*								*/
X/*	  [2] generated after a shutdown:			*/
X/*								*/
X/*	   line="~", name="shutdown", host="", time=date()	*/
X/*								*/
X/*								*/
X/*	  [3] generated on a successful login(1)		*/
X/*								*/
X/*	   line=ttyname(), name=cuserid(), host=, time=date()	*/
X/*								*/
X/*								*/
X/*	  [4] generated by init(8) on a logout			*/
X/*								*/
X/*	   line=ttyname(), name="", host="", time=date()	*/
X/*								*/
X/*								*/
X/*	Note: This version of last(1) does not recognize the	*/
X/*	'|' and '}' time change records.			*/
X/*								*/
X/****************************************************************/
X/*								*/
X/*	Last(1) pairs up line login's and logout's to generate	*/
X/*	four types of output lines:				*/
X/*								*/
X/*	  [1] a system reboot or shutdown			*/
X/*								*/
X/*	   reboot    ~       Mon May 16 14:16 			*/
X/*	   shutdown  ~       Mon May 16 14:15 			*/
X/*								*/
X/*								*/
X/*	  [2] a login with a matching logout			*/
X/*								*/
X/*	   edwin     tty1    Thu May 26 20:05 - 20:32  (00:27)	*/
X/*								*/
X/*								*/
X/*	  [3] a login followed by a reboot or shutdown		*/
X/*								*/
X/*	   root      tty0    Mon May 16 13:57 - crash  (00:19)	*/
X/*	   root      tty1    Mon May 16 13:45 - down   (00:30)	*/
X/*								*/
X/*								*/
X/*	  [4] a login not followed by a logout or reboot	*/
X/*								*/
X/*	   terry     tty0    Thu May 26 21:19   still logged in	*/
X/*								*/
X/****************************************************************/
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Process( wtmp )						*/
X/*								*/
X/*		Interpret one record from the log-in record	*/
X/*		file.						*/
X/*								*/
X/****************************************************************/
X
X
XProcess( wtmp )
X  struct utmp *wtmp;
X
X  {
X  logout *link;
X  logout *next_link;
X
X
X  /*  Suppress the job number on an "ftp" line  */
X
X  if ( strncmp( wtmp->ut_line, "ftp", 3 ) == 0 )
X      strncpy( wtmp->ut_line, "ftp", 8 );
X
X
X  if ( strcmp( wtmp->ut_line, "~" ) == 0 )
X    {
X    /*  A reboot or shutdown record  */
X
X    if ( boot_limit )
X	exit( 0 );
X
X    if ( Print_Record( wtmp ) )
X    	putchar( '\n' );
X
X    boot_time = wtmp->ut_time;
X
X    if ( strcmp( wtmp->ut_name, "reboot" ) == 0 )
X	boot_down = "crash";
X    else
X	boot_down = "down ";
X
X
X    /*  Remove any logout records  */
X
X    for( link = first_link;  link != NULL;  link = next_link )
X      	{
X      	next_link = link->next;
X      	free( link );
X      	}
X
X    first_link = NULL;
X    }
X
X
X  else if ( wtmp->ut_name[0] == '\0' )
X    {
X    /*  A logout record  */
X
X    Record_Logout_Time( wtmp );
X    }
X
X
X  else
X    {
X    /*  A login record  */
X
X    for( link = first_link;  link != NULL;  link = link->next )
X      if ( strncmp( link->line, wtmp->ut_line, 8 ) == 0 )
X	{
X	/*  Found corresponding logout record  */
X
X	if ( Print_Record( wtmp ) )
X	    {
X	    printf( "- %.5s ", ctime( &link->time )+11 );
X
X  	    Print_Duration( wtmp->ut_time, link->time );
X	    }
X
X	/*  Record login time  */
X	link->time = wtmp->ut_time;
X	return;
X	}
X
X
X    /*  Could not find a logout record for this login tty  */
X
X    if ( Print_Record( wtmp ) )
X    	if ( boot_time == 0 )    /*  Still on  */
X	    printf( "  still logged in\n" );
X
X        else 	    /*  System crashed while on  */
X	    {
X	    printf( "- %s ", boot_down );
X
X	    Print_Duration( wtmp->ut_time, boot_time );
X	    }
X
X    Record_Logout_Time( wtmp );  /*  Needed in case of 2 consecutive logins  */
X    }
X  }
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Record( wtmp )					*/
X/*								*/
X/*		If the record was requested, then print out	*/
X/*		the user name, terminal, host and time.		*/
X/*								*/
X/****************************************************************/
X
X
XPrint_Record( wtmp )
X  struct utmp *wtmp;
X
X  {
X  int  i;
X  char print_flag = FALSE;
X
X  /*  Check if we have already printed the requested number of records  */
X
X  if (  count_limit  &&  print_count == 0 )
X    exit( 0 );
X
X  for ( i = 0;  i < arg_count  ; ++i )
X    if ( strcmp( args[i], wtmp->ut_name ) == 0  ||
X         strcmp( args[i], wtmp->ut_line ) == 0  )
X		print_flag = TRUE;
X
X  if (  arg_count == 0  ||  print_flag  )
X    {
X#ifdef RLOGIN
X    printf( "%-8.8s  %-8.8s %-16.16s %.16s ",
X        wtmp->ut_name, wtmp->ut_line, wtmp->ut_host, ctime( &wtmp->ut_time ) );
X#else
X    printf( "%-8.8s  %-8.8s  %.16s ",
X        wtmp->ut_name, wtmp->ut_line, ctime( &wtmp->ut_time ) );
X#endif
X
X    --print_count;
X    return( TRUE );
X    }
X
X  return( FALSE );
X  }
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Print_Duration( from, to )				*/
X/*								*/
X/*		Calculate and print the days and hh:mm between	*/
X/*		the log-in and the log-out.			*/
X/*								*/
X/****************************************************************/
X
X
XPrint_Duration( from, to )
X  long from;
X  long to;
X
X  {
X  long delta, days, hours, minutes;
X
X  delta   = max( to - from, 0 );
X  days    = delta / (24L * 60L * 60L);
X  delta   = delta % (24L * 60L * 60L);
X  hours   = delta / (60L * 60L);
X  delta   = delta % (60L * 60L);
X  minutes = delta / 60L;
X
X  if ( days > 0 )
X    printf( "(%D+", days );
X  else
X    printf( " (" );
X
X  printf( "%02D:%02D)\n", hours, minutes );
X  }
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Record_Logout_Time( wtmp )				*/
X/*								*/
X/*		A linked list of "last logout time" is kept.	*/
X/*		Each element of the list is for one terminal.	*/
X/*								*/
X/****************************************************************/
X
X
XRecord_Logout_Time( wtmp )
X  struct utmp *wtmp;
X
X  {
X  logout *link;
X
X  /*  See if the terminal is already in the list  */
X
X  for( link = first_link;  link != NULL;  link = link->next )
X    if ( strncmp( link->line, wtmp->ut_line, 8 ) == 0 )
X	{
X	link->time = wtmp->ut_time;
X	return;
X	}
X
X  /*  Allocate a new logout record, for a tty not previously encountered  */
X
X  link = (logout *) malloc( sizeof( logout ) );
X
X  if ( link == (logout *) NULL )
X    {
X    fprintf( stderr, "last: malloc failure\n" );
X    exit( 1 );
X    }
X
X  strncpy( link->line, wtmp->ut_line, 8 );
X
X  link->time = wtmp->ut_time;
X  link->next = first_link;
X
X  first_link = link;
X  }
X
X
X
X
X/****************************************************************/
X/*								*/
X/*	Sigint()						*/
X/*	Sigquit()						*/
X/*								*/
X/*		Flag occurrence of an interrupt.		*/
X/*								*/
X/****************************************************************/
X
X
XSigint()
X  {
X  interrupt = SIGINT;
X  }
X
X
X
XSigquit()
X  {
X  interrupt = SIGQUIT;
X  }
/
echo x - last.1
gres '^X' '' > last.1 << '/'
XNAME
X    last(1)		- display on-line session records
X
XSYNOPSIS
X    last  [-r]  [-count]  [-f file]  [name]  [tty]  ...
X
XDESCRIPTION
X    Last(1) searches backwards through the file of log-in
X    records displaying the length of sessions as requested
X    by the options:
X
X      -r	Search backwards only until the last reboot
X		record.
X
X      -count	Only print out <count> records. Last(1) stops
X		when either -r or -count is satisfied, or at
X		the end of the file if neither is given.
X
X      -f file	Use "file" instead of "/usr/adm/wtmp".
X
X      name	Print records for the user "name".
X
X      tty	Print records for the terminal "tty". Actually,
X		a list of names may be given and all records
X		that match either the user or tty name are
X		printed. If no names are given then all records
X		are displayed.
X
X    A sigquit (^\) causes last(1) to display how far it has gone
X    back in the log-in record file, it then continues. This is
X    used to check on the progress of long running searches. A
X    sigint will stop last(1).
X
XEXAMPLES
X    When has the system been rebooted?
X
X	last reboot
X
X    When was my last login?
X
X	last -2 $USER
X
X    Show me the last ten times someone logged onto /dev/tty0
X    or /dev/tty1:
X
X	last -10 tty0 tty1
X
XFILES
X    /usr/adm/wtmp		on-line session records
X
XSEE ALSO
X    login(1), who(1), utmp(4)
/
echo x - who.c
gres '^X' '' > who.c << '/'
X/* who(1)		Who is logged on?
X *
X * Author: Terrence W. Holm	June 1988
X *
X *
X *  who
X *  who <USER>
X *  who <DEVICE>
X *  who am i
X *
X * The user log-in name, terminal port and log-in time
X * are displayed for all current users, or restricted
X * to the specified <USER>, <DEVICE> or the current user.
X */
X
X
X#include <stdio.h>
X
X#define  LINE_SIZE  80   /*  Max. line size from last(1)  */
X
Xchar *ttyname();
XFILE *fdopen();
X
X
Xmain( argc, argv )
X  int   argc;
X  char *argv[];
X
X  {
X  char *argument;
X  int   desc[2];
X  int   pid;
X  FILE *f;
X  char  line[ LINE_SIZE ];
X
X  if ( argc == 1 )
X    argument = NULL;
X
X  else if ( argc == 2 )
X    argument = argv[1];
X
X  else if ( argc == 3  &&  strcmp(argv[1],"am") == 0  &&
X     ( strcmp(argv[2],"i") == 0  ||  strcmp(argv[2],"I") == 0 ) )
X    argument = ttyname(0) + 5;
X
X  else
X    {
X    fprintf( stderr, "Usage:  who  [  USER  |  DEVICE  |  am i  ]\n" );
X    exit( 1 );
X    }
X
X
X  /*  Create a child process to execute last(1)  */
X
X  if ( pipe( desc ) == -1 )
X    exit( 1 );
X
X  if ( (pid = fork()) == -1 )
X    exit( 1 );
X
X  if ( pid == 0 )
X    {
X    /*  The child process  */
X
X    dup2( desc[1], 1 );
X    close( desc[0] );
X    close( desc[1] );
X
X    execlp( "last", "last", "-r", argument, (char *) 0 );
X    perror( "last" );
X    exit( 127 );
X    }
X
X
X  /*  The parent process: read the standard output from last(1)  */
X
X  close( desc[1] );
X  f = fdopen( desc[0], "r" );
X
X  while ( fgets( line, LINE_SIZE, f ) != NULL )
X    if ( line[strlen(line) - 2]  == 'n' )
X#ifdef RLOGIN
X	printf( "%.18s %.12s   %.16s\n", line, line + 40, line + 19 );
X#else
X	printf( "%.18s %.12s\n", line, line + 24 );
X#endif
X
X  exit( 0 );
X  }
/
echo x - who.1
gres '^X' '' > who.1 << '/'
XNAME
X    who(1)		- who is currently on the system
X
XSYNOPSIS
X    who  [  user  |  device  |  am i  ]
X
XDESCRIPTION
X    If no options are given then who(1) displays the name,
X    terminal port and log-in time for all of the present
X    users.
X
X    A "user" or "device" name checks for a current log-in
X    by the "user" or at the "device". The command "who am I"
X    only reports on the current session.
X
XFILES
X    /usr/adm/wtmp
X
XSEE ALSO
X    last(1), whoami(1)
/
----------------------------------------------------------