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) / ----------------------------------------------------------