Path: utzoo!utgpu!watmath!clyde!att!osu-cis!tut.cis.ohio-state.edu!rutgers!ucsd!sdcsvax!ucsdhub!ucrmath!pascal From: pascal@ucrmath.UUCP (Freeman P. Pascal IV) Newsgroups: comp.os.minix Subject: Last(1) for MINIX v1.3 Message-ID: <544@ucrmath.UUCP> Date: 26 Nov 88 01:53:14 GMT Reply-To: pascal@ucrmath.UUCP (Freeman P. Pascal IV) Organization: University of California, Riverside Lines: 820 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 X #include X+ #include X+ #include X+ #include 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 X#include X#include X#include X#include X#include 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 :-)