/* last - display login history Author: Terrence W. Holm */ /* last- Display the user log-in history. * Last(1) searches backwards through the file of log-in * records (/usr/adm/wtmp), displaying the length of * log-in sessions as requested by the options: * * Usage: last [-r] [-count] [-f file] [name] [tty] ... * * -r Search backwards only until the last reboot * record. * * -count Only print out records. Last(1) stops * when either -r or -count is satisfied, or at * the end of the file if neither is given. * * -f file Use "file" instead of "/usr/adm/wtmp". * * name Print records for the user "name". * * tty Print records for the terminal "tty". Actually, * a list of names may be given and all records * that match either the user or tty name are * printed. If no names are given then all records * are displayed. * * A sigquit (^\) causes last(1) to display how far it * has gone back in the log-in record file, it then * continues. This is used to check on the progress of * long running searches. A sigint will stop last(1). * * Author: Terrence W. Holm May 1988 * * Revision: * Fred van Kempen, October 1989 * -Adapted to MSS. * -Adapted to new utmp database. * * Fred van Kempen, December 1989 * -Adapted to POSIX (MINIX 1.5) * * Fred van Kempen, January 1990 * -Final edit for 1.5 * * Philip Homburg, March 1992 * -Include host in output * * Kees J. Bot, July 1997 * -Approximate system uptime from last reboot record */ #include #include #include #include #include #include #include #include #include #define FALSE 0 #define TRUE 1 #define RLOGIN 1 #define BUFFER_SIZE 4096 /* Room for wtmp records */ #define MAX_WTMP_COUNT ( BUFFER_SIZE / sizeof(struct utmp) ) #define min( a, b ) ( (a < b) ? a : b ) #define max( a, b ) ( (a > b) ? a : b ) typedef struct logout { /* A logout time record */ char line[12]; /* The terminal name */ long time; /* The logout time */ struct logout *next; /* Next in linked list */ } logout; static char *Version = "@(#) LAST 1.7 (10/24/92)"; /* command-line option flags */ char boot_limit = FALSE; /* stop on latest reboot */ char count_limit = FALSE; /* stop after print_count */ char tell_uptime = FALSE; /* tell uptime since last reboot */ int print_count; char *prog; /* name of this program */ int arg_count; /* used to select specific */ char **args; /* users and ttys */ /* global variables */ long boot_time = 0; /* Zero means no reboot yet */ char *boot_down; /* "crash" or "down " flag */ logout *first_link = NULL; /* List of logout times */ int interrupt = FALSE; /* If sigint or sigquit occurs */ _PROTOTYPE(int main, (int argc, char **argv)); _PROTOTYPE(void Sigint, (int sig)); _PROTOTYPE(void Sigquit, (int sig)); _PROTOTYPE(void usage, (void)); _PROTOTYPE(void Process, (struct utmp *wtmp)); _PROTOTYPE(int Print_Record, (struct utmp *wtmp)); _PROTOTYPE(void Print_Duration, (long from, long to)); _PROTOTYPE(void Print_Uptime, (void)); _PROTOTYPE(void Record_Logout_Time, (struct utmp *wtmp)); /* Sigint() and Sigquit() Flag occurrence of an interrupt. */ void Sigint(sig) int sig; { interrupt = SIGINT; } void Sigquit(sig) int sig; { interrupt = SIGQUIT; } void usage() { fprintf(stderr, "Usage: last [-r] [-u] [-count] [-f file] [name] [tty] ...\n"); exit(-1); } /* A log-in record format file contains four types of records. * * [1] generated on a system reboot: * * line="~", name="reboot", host="", time=date() * * * [2] generated after a shutdown: * * line="~", name="shutdown", host="", time=date() * * * [3] generated on a successful login(1) * * line=ttyname(), name=cuserid(), host=, time=date() * * * [4] generated by init(8) on a logout * * line=ttyname(), name="", host="", time=date() * * * Note: This version of last(1) does not recognize the '|' and '}' time * change records. Last(1) pairs up line login's and logout's to * generate four types of output lines: * * [1] a system reboot or shutdown * * reboot ~ Mon May 16 14:16 * shutdown ~ Mon May 16 14:15 * * [2] a login with a matching logout * * edwin tty1 Thu May 26 20:05 - 20:32 (00:27) * * [3] a login followed by a reboot or shutdown * * root tty0 Mon May 16 13:57 - crash (00:19) * root tty1 Mon May 16 13:45 - down (00:30) * * [4] a login not followed by a logout or reboot * * terry tty0 Thu May 26 21:19 still logged in */ void Process(wtmp) struct utmp *wtmp; { logout *link; logout *next_link; char is_reboot; /* suppress the job number on an "ftp" line */ if (!strncmp(wtmp->ut_line, "ftp", (size_t)3)) strncpy(wtmp->ut_line, "ftp", (size_t)8); if (!strcmp(wtmp->ut_line, "~")) { /* A reboot or shutdown record */ if (boot_limit) exit(0); if (Print_Record(wtmp)) putchar('\n'); boot_time = wtmp->ut_time; is_reboot = !strcmp(wtmp->ut_name, "reboot"); if (is_reboot) boot_down = "crash"; else boot_down = "down "; if (tell_uptime) { if (!is_reboot) { fprintf(stderr, "%s: no reboot record added to wtmp file on system boot!\n", prog); exit(1); } Print_Uptime(); exit(0); } /* remove any logout records */ for (link = first_link; link != NULL; link = next_link) { next_link = link->next; free(link); } first_link = NULL; } else if (wtmp->ut_name[0] == '\0') { /* A logout record */ Record_Logout_Time(wtmp); } else { /* A login record */ for (link = first_link; link != NULL; link = link->next) if (!strncmp(link->line, wtmp->ut_line, (size_t)8)) { /* found corresponding logout record */ if (Print_Record(wtmp)) { printf("- %.5s ", ctime(&link->time) + 11); Print_Duration(wtmp->ut_time, link->time); } /* record login time */ link->time = wtmp->ut_time; return; } /* could not find a logout record for this login tty */ if (Print_Record(wtmp)) if (boot_time == 0) /* still on */ printf(" still logged in\n"); else { /* system crashed while on */ printf("- %s ", boot_down); Print_Duration(wtmp->ut_time, boot_time); } Record_Logout_Time(wtmp); /* Needed in case of 2 * consecutive logins */ } } /* Print_Record(wtmp) If the record was requested, then print out * the user name, terminal, host and time. */ int Print_Record(wtmp) struct utmp *wtmp; { int i; char print_flag = FALSE; /* just interested in the uptime? */ if (tell_uptime) return(FALSE); /* check if we have already printed the requested number of records */ if (count_limit && print_count == 0) exit(0); for (i = 0; i < arg_count; ++i) if (!strncmp(args[i], wtmp->ut_name, sizeof(wtmp->ut_name)) || !strncmp(args[i], wtmp->ut_line, sizeof(wtmp->ut_line))) print_flag = TRUE; if (arg_count == 0 || print_flag) { #ifdef RLOGIN printf("%-8.8s %-8.8s %-16.16s %.16s ", wtmp->ut_name, wtmp->ut_line, wtmp->ut_host, ctime(&wtmp->ut_time)); #else printf("%-8.8s %-8.8s %.16s ", wtmp->ut_name, wtmp->ut_line, ctime(&wtmp->ut_time)); #endif --print_count; return(TRUE); } return(FALSE); } /* Print_Duration(from, to) Calculate and print the days and hh:mm between * the log-in and the log-out. */ void Print_Duration(from, to) long from; long to; { long delta, days, hours, minutes; delta = max(to - from, 0); days = delta / (24L * 60L * 60L); delta = delta % (24L * 60L * 60L); hours = delta / (60L * 60L); delta = delta % (60L * 60L); minutes = delta / 60L; if (days > 0) printf("(%ld+", days); else printf(" ("); printf("%02ld:%02ld)\n", hours, minutes); } /* Print_Uptime() Calculate and print the "uptime" between the last recorded * boot and the current time. */ void Print_Uptime() { #define NLOADS 3 int nloads; double loads[NLOADS]; char *utmp_file = _PATH_UTMP; unsigned nusers; struct utmp ut; FILE *uf; time_t now; struct tm *tm; unsigned long up; /* Count the number of active users in the utmp file. */ if ((uf = fopen(utmp_file, "r")) == NULL) { fprintf(stderr, "%s: %s: %s\n", prog, utmp_file, strerror(errno)); exit(1); } nusers = 0; while (fread(&ut, sizeof(ut), 1, uf) == 1) { #ifdef USER_PROCESS if (ut.ut_type == USER_PROCESS) nusers++; #else if (ut.ut_name[0] != 0 && ut.ut_line[0] != 0) nusers++; #endif } fclose(uf); /* Current time. */ now = time((time_t *) NULL); tm = localtime(&now); /* Uptime. */ up = now - boot_time; printf(" %d:%02d up", tm->tm_hour, tm->tm_min); if (up >= 24 * 3600L) { unsigned long days = up / (24 * 3600L); printf(" %lu day%s,", days, days == 1 ? "" : "s"); } printf(" %lu:%02lu,", (up % (24 * 3600L)) / 3600, (up % 3600) / 60); printf(" %u user%s", nusers, nusers == 1 ? "" : "s"); if((nloads = getloadavg(loads, NLOADS)) > 0) { int i; printf(", load averages:"); for(i = 0; i < nloads; i++) printf("%s %.2f", (i > 0) ? "," : "", loads[i]); } printf("\n"); } /* Record_Logout_Time(wtmp) A linked list of "last logout time" is kept. * Each element of the list is for one terminal. */ void Record_Logout_Time(wtmp) struct utmp *wtmp; { logout *link; /* see if the terminal is already in the list */ for (link = first_link; link != NULL; link = link->next) if (!strncmp(link->line, wtmp->ut_line, (size_t)8)) { link->time = wtmp->ut_time; return; } /* allocate a new logout record, for a tty not previously encountered */ link = (logout *) malloc(sizeof(logout)); if (link == NULL) { fprintf(stderr, "%s: malloc failure\n", prog); exit(1); } strncpy(link->line, wtmp->ut_line, (size_t)8); link->time = wtmp->ut_time; link->next = first_link; first_link = link; } int main(argc, argv) int argc; char *argv[]; { char *wtmp_file = _PATH_WTMP; FILE *f; long size; /* Number of wtmp records in the file */ int wtmp_count; /* How many to read into wtmp_buffer */ struct utmp wtmp_buffer[MAX_WTMP_COUNT]; if ((prog = strrchr(argv[0], '/')) == NULL) prog = argv[0]; else prog++; --argc; ++argv; while (argc > 0 && *argv[0] == '-') { if (!strcmp(argv[0], "-r")) boot_limit = TRUE; else if (!strcmp(argv[0], "-u")) tell_uptime = TRUE; else if (argc > 1 && !strcmp(argv[0], "-f")) { wtmp_file = argv[1]; --argc; ++argv; } else if ((print_count = atoi(argv[0] + 1)) > 0) count_limit = TRUE; else usage(); --argc; ++argv; } arg_count = argc; args = argv; if (!strcmp(prog, "uptime")) tell_uptime = TRUE; if ((f = fopen(wtmp_file, "r")) == NULL) { perror(wtmp_file); exit(1); } if (fseek(f, 0L, 2) != 0 || (size = ftell(f)) % sizeof(struct utmp) != 0) { fprintf(stderr, "%s: invalid wtmp file\n", prog); exit(1); } if (signal(SIGINT, SIG_IGN) != SIG_IGN) { signal(SIGINT, Sigint); signal(SIGQUIT, Sigquit); } size /= sizeof(struct utmp); /* Number of records in wtmp */ if (size == 0) wtmp_buffer[0].ut_time = time((time_t *)0); while (size > 0) { wtmp_count = (int) min(size, MAX_WTMP_COUNT); size -= (long) wtmp_count; fseek(f, size * sizeof(struct utmp), 0); if (fread(&wtmp_buffer[0], sizeof(struct utmp), (size_t)wtmp_count, f) != wtmp_count) { fprintf(stderr, "%s: read error on wtmp file\n", prog); exit(1); } while (--wtmp_count >= 0) { Process(&wtmp_buffer[wtmp_count]); if (interrupt) { printf("\ninterrupted %.16s \n", ctime(&wtmp_buffer[wtmp_count].ut_time)); if (interrupt == SIGINT) exit(2); interrupt = FALSE; signal(SIGQUIT, Sigquit); } } } /* end while(size > 0) */ if (tell_uptime) { fprintf(stderr, "%s: no reboot record in wtmp file to compute uptime from\n", prog); return(1); } printf("\nwtmp begins %.16s \n", ctime(&wtmp_buffer[0].ut_time)); return(0); }