Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!tut.cis.ohio-state.edu!bloom-beacon!apple!vsi1!wyse!mips!prls!philabs!micomvax!ncc!alberta!ubc-cs!ubc-bdcvax!holm From: holm@ubc-bdcvax.UUCP (Terrence W. Holm) Newsgroups: comp.os.minix Subject: last(1) Message-ID: <414@ubc-bdcvax.UUCP> Date: 2 Feb 89 00:13:18 GMT Lines: 731 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 X#include X#include 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 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 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 X * who 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 , or the current user. X */ X X X#include 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) / ----------------------------------------------------------