482 lines
12 KiB
C
482 lines
12 KiB
C
/* 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 <count> 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 <sys/types.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <utmp.h>
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
#include <paths.h>
|
|
|
|
#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);
|
|
}
|
|
|