From ce60272039ea11952b15fefb653892dd0da02217 Mon Sep 17 00:00:00 2001 From: Ondrej Oprala Date: Tue, 6 Aug 2013 18:19:37 +0200 Subject: last: merge sysvinit last/lastb Signed-off-by: Ondrej Oprala Signed-off-by: Karel Zak --- bash-completion/Makemodule.am | 2 +- configure.ac | 14 +- login-utils/Makemodule.am | 16 +- login-utils/last-deprecated.1 | 62 +++ login-utils/last-deprecated.c | 483 +++++++++++++++++ login-utils/last.1 | 174 ++++-- login-utils/last.c | 1198 +++++++++++++++++++++++++++-------------- login-utils/lastb.1 | 1 + 8 files changed, 1474 insertions(+), 476 deletions(-) create mode 100644 login-utils/last-deprecated.1 create mode 100644 login-utils/last-deprecated.c create mode 100644 login-utils/lastb.1 diff --git a/bash-completion/Makemodule.am b/bash-completion/Makemodule.am index c3791e71a..aaf2737d3 100644 --- a/bash-completion/Makemodule.am +++ b/bash-completion/Makemodule.am @@ -92,7 +92,7 @@ dist_bashcompletion_DATA += \ bash-completion/chfn \ bash-completion/chsh endif -if BUILD_LAST +if BUILD_DEPRECATED_LAST dist_bashcompletion_DATA += \ bash-completion/last endif diff --git a/configure.ac b/configure.ac index 7252e9b35..d0c54247f 100644 --- a/configure.ac +++ b/configure.ac @@ -668,7 +668,6 @@ AC_ARG_ENABLE([most-builds], AS_IF([test "x$enable_most_builds" = xyes], [ enable_chfn_chsh=yes enable_elvtune=check - enable_last=yes enable_line=yes enable_mesg=yes enable_newgrp=yes @@ -1062,9 +1061,18 @@ UL_REQUIRES_LINUX([kill]) AM_CONDITIONAL([BUILD_KILL], [test "x$build_kill" = xyes]) +AC_ARG_ENABLE([deprecated-last], + AS_HELP_STRING([--enable-deprecated-last], [build old deprecated last]), + [], [enable_deprecated_last=no] +) +UL_BUILD_INIT([deprecated_last]) +UL_CONFLICTS_BUILD([last], [deprecated_last], [old deprecated last version]) +AM_CONDITIONAL([BUILD_DEPRECATED_LAST], [test "x$build_deprecated_last" = xyes]) + + AC_ARG_ENABLE([last], - AS_HELP_STRING([--enable-last], [build last]), - [], [enable_last=no] + AS_HELP_STRING([--disable-last], [do not build last]), + [], [enable_last=yes] ) UL_BUILD_INIT([last]) AM_CONDITIONAL([BUILD_LAST], [test "x$build_last" = xyes]) diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am index 8283935f9..6600f27c6 100644 --- a/login-utils/Makemodule.am +++ b/login-utils/Makemodule.am @@ -1,10 +1,24 @@ if BUILD_LAST usrbin_exec_PROGRAMS += last -dist_man_MANS += login-utils/last.1 +dist_man_MANS += + login-utils/last.1 \ + login-utils/lastb.1 last_SOURCES = login-utils/last.c + +install-exec-hook-last: + cd $(DESTDIR)$(usrsbin_execdir) && ln -sf last lastb + +INSTALL_EXEC_HOOKS += install-exec-hook-last endif +if BUILD_DEPRECATED_LAST + +usrbin_exec_PROGRAMS += last +dist_man_MANS += login-utils/last-deprecated.1 +last_SOURCES = login-utils/last-deprecated.c + +endif # BUILD_DEPRECATED_LAST if BUILD_SULOGIN sbin_PROGRAMS += sulogin diff --git a/login-utils/last-deprecated.1 b/login-utils/last-deprecated.1 new file mode 100644 index 000000000..beb691784 --- /dev/null +++ b/login-utils/last-deprecated.1 @@ -0,0 +1,62 @@ +.TH LAST 1 "March 1992" "util-linux" "User Commands" +.SH NAME +last \(em indicate last logins by user or terminal +.SH SYNOPSIS +.ad l +.B last +.RB [ \-\fP\fInumber\fP ] +.RB [ \-f +.IR filename ] +.RB [ \-t +.IR tty ] +.RB [ \-h +.IR hostname ] +.RB [ \-i +.IR address ] +.RB [ \-l ] +.RB [ \-y ] +.RI [ name ...] +.ad b +.SH DESCRIPTION +\fBLast\fP looks back in the \fBwtmp\fP file which records all logins +and logouts for information about a user, a teletype or any group of +users and teletypes. Arguments specify names of users or teletypes of +interest. If multiple arguments are given, the information which +applies to any of the arguments is printed. For example ``\fBlast root +console\fP'' would list all of root's sessions as well as all sessions +on the console terminal. \fBLast\fP displays the sessions of the +specified users and teletypes, most recent first, indicating the times +at which the session began, the duration of the session, and the +teletype which the session took place on. If the session is still +continuing or was cut short by a reboot, \fBlast\fP so indicates. +.LP +The pseudo-user \fBreboot\fP logs in at reboots of the system. +.LP +\fBLast\fP with no arguments displays a record of all logins and +logouts, in reverse order. +.LP +If \fBlast\fP is interrupted, it indicates how far the search has +progressed in \fBwtmp\fP. If interrupted with a quit signal \fBlast\fP +indicates how far the search has progressed so far, and the search +continues. +.SH OPTIONS +.IP \fB\-\fP\fInumber\fP +limit the number of entries displayed to that specified by \fInumber\fP. +.IP "\fB\-f\fP \fIfilename\fP" +Use \fIfilename\fP as the name of the accounting file instead of +.BR /var/log/wtmp . +.IP "\fB\-h\fP \fIhostname\fP" +List only logins from \fIhostname\fP. +.IP "\fB\-i\fP \fIIP address\fP" +List only logins from \fIIP address\fP. +.IP "\fB\-l\fP" +List IP addresses of remote hosts instead of truncated host names. +.IP "\fB\-t\fP \fItty\fP" +List only logins on \fItty\fP. +.IP "\fB\-y\fP" +Also report year of dates. +.SH FILES +/var/log/wtmp \(em login data base +.SH AVAILABILITY +The last command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/login-utils/last-deprecated.c b/login-utils/last-deprecated.c new file mode 100644 index 000000000..d3fdc8c6c --- /dev/null +++ b/login-utils/last-deprecated.c @@ -0,0 +1,483 @@ +/* + * Berkeley last for Linux. Currently maintained by poe@daimi.aau.dk at + * ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil* + * + * Copyright (c) 1987 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + + /* 1999-02-22 Arkadiusz Miśkiewicz + * - added Native Language Support + */ + + /* 2001-02-14 Marek Zelem + * - using mmap() on Linux - great speed improvement + */ + +/* + * This command is deprecated. The utility is in maintenance mode, + * meaning we keep them in source tree for backward compatibility + * only. Do not waste time making this command better, unless the + * fix is about security or other very critical issue. + * + * See Documentation/deprecated.txt for more information. + */ + +/* + * last + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "closestream.h" +#include "pathnames.h" +#include "nls.h" +#include "xalloc.h" +#include "c.h" + +#define SECDAY (24*60*60) /* seconds in a day */ +#define NO 0 /* false/no */ +#define YES 1 /* true/yes */ + +static struct utmp utmpbuf; + +#define HMAX (int)sizeof(utmpbuf.ut_host) /* size of utmp host field */ +#define LMAX (int)sizeof(utmpbuf.ut_line) /* size of utmp tty field */ +#define NMAX (int)sizeof(utmpbuf.ut_name) /* size of utmp name field */ + +/* maximum sizes used for printing */ +/* probably we want a two-pass version that computes the right length */ +#define P_HMAX min(HMAX, 16) +#define P_LMAX min(LMAX, 8) +#define P_NMAX min(NMAX, 16) + +typedef struct arg { + char *name; /* argument */ +#define HOST_TYPE -2 +#define TTY_TYPE -3 +#define USER_TYPE -4 +#define INET_TYPE -5 + int type; /* type of arg */ + struct arg *next; /* linked list pointer */ +} ARG; +ARG *arglist; /* head of linked list */ + +typedef struct ttytab { + long logout; /* log out time */ + char tty[LMAX + 1]; /* terminal name */ + struct ttytab *next; /* linked list pointer */ +} TTY; +TTY *ttylist; /* head of linked list */ + +static long currentout, /* current logout value */ + maxrec; /* records to display */ +static char *file = _PATH_WTMP; /* wtmp file */ + +static int doyear = 0; /* output year in dates */ +static int dolong = 0; /* print also ip-addr */ + +static void wtmp(void); +static void addarg(int, char *); +static void hostconv(char *); +static void onintr(int); +static int want(struct utmp *, int); +TTY *addtty(char *); +static char *ttyconv(char *); + +int +main(int argc, char **argv) { + int ch; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((ch = getopt(argc, argv, "0123456789yli:f:h:t:")) != -1) + switch((char)ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* + * kludge: last was originally designed to take + * a number after a dash. + */ + if (!maxrec) + maxrec = atol(argv[optind - 1] + 1); + break; + case 'f': + file = optarg; + break; + case 'h': + hostconv(optarg); + addarg(HOST_TYPE, optarg); + break; + case 't': + addarg(TTY_TYPE, ttyconv(optarg)); + break; + case 'y': + doyear = 1; + break; + case 'l': + dolong = 1; + break; + case 'i': + addarg(INET_TYPE, optarg); + break; + case '?': + default: + fputs(_("usage: last [-#] [-f file] [-t tty] [-h hostname] [user ...]\n"), stderr); + exit(EXIT_FAILURE); + } + for (argv += optind; *argv; ++argv) { +#define COMPATIBILITY +#ifdef COMPATIBILITY + /* code to allow "last p5" to work */ + addarg(TTY_TYPE, ttyconv(*argv)); +#endif + addarg(USER_TYPE, *argv); + } + wtmp(); + + return EXIT_SUCCESS; +} + +static char *utmp_ctime(struct utmp *u) +{ + time_t t = (time_t) u->ut_time; + return ctime(&t); +} + +/* + * print_partial_line -- + * print the first part of each output line according to specified format + */ +static void +print_partial_line(struct utmp *bp) { + char *ct; + + ct = utmp_ctime(bp); + printf("%-*.*s %-*.*s ", P_NMAX, P_NMAX, bp->ut_name, + P_LMAX, P_LMAX, bp->ut_line); + + if (dolong) { + if (bp->ut_addr) { + struct in_addr foo; + foo.s_addr = bp->ut_addr; + printf("%-*.*s ", P_HMAX, P_HMAX, inet_ntoa(foo)); + } else { + printf("%-*.*s ", P_HMAX, P_HMAX, ""); + } + } else { + printf("%-*.*s ", P_HMAX, P_HMAX, bp->ut_host); + } + + if (doyear) { + printf("%10.10s %4.4s %5.5s ", ct, ct + 20, ct + 11); + } else { + printf("%10.10s %5.5s ", ct, ct + 11); + } +} + +/* + * wtmp -- + * read through the wtmp file + */ +static void +wtmp(void) { + register struct utmp *bp; /* current structure */ + register TTY *T; /* tty list entry */ + long delta; /* time difference */ + char *crmsg = NULL; + char *ct = NULL; + int fd; + struct utmp *utl; + struct stat st; + int utl_len; + int listnr = 0; + int i; + + utmpname(file); + + { +#if defined(_HAVE_UT_TV) + struct timeval tv; + gettimeofday(&tv, NULL); + utmpbuf.ut_tv.tv_sec = tv.tv_sec; + utmpbuf.ut_tv.tv_usec = tv.tv_usec; +#else + time_t t; + time(&t); + utmpbuf.ut_time = t; +#endif + } + + (void)signal(SIGINT, onintr); + (void)signal(SIGQUIT, onintr); + + if ((fd = open(file,O_RDONLY)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), file); + + fstat(fd, &st); + utl_len = st.st_size; + utl = mmap(NULL, utl_len, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_FILE, fd, 0); + if (utl == NULL) + err(EXIT_FAILURE, _("%s: mmap failed"), file); + + listnr = utl_len/sizeof(struct utmp); + + if(listnr) + ct = utmp_ctime(&utl[0]); + + for(i = listnr - 1; i >= 0; i--) { + bp = utl+i; + /* + * if the terminal line is '~', the machine stopped. + * see utmp(5) for more info. + */ + if (!strncmp(bp->ut_line, "~", LMAX)) { + /* + * utmp(5) also mentions that the user + * name should be 'shutdown' or 'reboot'. + * Not checking the name causes e.g. runlevel + * changes to be displayed as 'crash'. -thaele + */ + if (!strncmp(bp->ut_user, "reboot", NMAX) || + !strncmp(bp->ut_user, "shutdown", NMAX)) { + /* everybody just logged out */ + for (T = ttylist; T; T = T->next) + T->logout = -bp->ut_time; + } + + currentout = -bp->ut_time; + crmsg = (strncmp(bp->ut_name, "shutdown", NMAX) + ? "crash" : "down "); + if (!bp->ut_name[0]) + (void)strcpy(bp->ut_name, "reboot"); + if (want(bp, NO)) { + ct = utmp_ctime(bp); + if(bp->ut_type != LOGIN_PROCESS) { + print_partial_line(bp); + putchar('\n'); + } + if (maxrec && !--maxrec) + return; + } + continue; + } + /* find associated tty */ + for (T = ttylist;; T = T->next) { + if (!T) { + /* add new one */ + T = addtty(bp->ut_line); + break; + } + if (!strncmp(T->tty, bp->ut_line, LMAX)) + break; + } + if (bp->ut_name[0] && bp->ut_type != LOGIN_PROCESS + && bp->ut_type != DEAD_PROCESS + && want(bp, YES)) { + + print_partial_line(bp); + + if (!T->logout) + puts(_(" still logged in")); + else { + if (T->logout < 0) { + T->logout = -T->logout; + printf("- %s", crmsg); + } + else + printf("- %5.5s", ctime(&T->logout)+11); + delta = T->logout - bp->ut_time; + if (delta < SECDAY) + printf(" (%5.5s)\n", asctime(gmtime(&delta))+11); + else + printf(" (%ld+%5.5s)\n", delta / SECDAY, asctime(gmtime(&delta))+11); + } + if (maxrec != -1 && !--maxrec) + return; + } + T->logout = bp->ut_time; + utmpbuf.ut_time = bp->ut_time; + } + munmap(utl,utl_len); + close(fd); + if(ct) printf(_("\nwtmp begins %s"), ct); /* ct already ends in \n */ +} + +/* + * want -- + * see if want this entry + */ +static int +want(struct utmp *bp, int check) { + register ARG *step; + + if (check) { + /* + * when uucp and ftp log in over a network, the entry in + * the utmp file is the name plus their process id. See + * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. + */ + if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) + bp->ut_line[3] = '\0'; + else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) + bp->ut_line[4] = '\0'; + } + if (!arglist) + return YES; + + for (step = arglist; step; step = step->next) + switch(step->type) { + case HOST_TYPE: + if (!strncmp(step->name, bp->ut_host, HMAX)) + return YES; + break; + case TTY_TYPE: + if (!strncmp(step->name, bp->ut_line, LMAX)) + return YES; + break; + case USER_TYPE: + if (!strncmp(step->name, bp->ut_name, NMAX)) + return YES; + break; + case INET_TYPE: + if ((in_addr_t) bp->ut_addr == inet_addr(step->name)) + return YES; + break; + default: + abort(); + } + return NO; +} + +/* + * addarg -- + * add an entry to a linked list of arguments + */ +static void +addarg(int type, char *arg) { + register ARG *cur; + + cur = xmalloc(sizeof(ARG)); + cur->next = arglist; + cur->type = type; + cur->name = arg; + arglist = cur; +} + +/* + * addtty -- + * add an entry to a linked list of ttys + */ +TTY * +addtty(char *ttyname) { + register TTY *cur; + + cur = xmalloc(sizeof(TTY)); + cur->next = ttylist; + cur->logout = currentout; + memcpy(cur->tty, ttyname, LMAX); + return(ttylist = cur); +} + +/* + * hostconv -- + * convert the hostname to search pattern; if the supplied host name + * has a domain attached that is the same as the current domain, rip + * off the domain suffix since that's what login(1) does. + */ +static void +hostconv(char *arg) { + static int first = 1; + static char *hostdot, *name; + + char *argdot; + + if (!(argdot = strchr(arg, '.'))) + return; + + if (first) { + first = 0; + name = xgethostname(); + if (!name) + err(EXIT_FAILURE, _("gethostname failed")); + + hostdot = strchr(name, '.'); + } + if (hostdot && !strcmp(hostdot, argdot)) + *argdot = '\0'; +} + +/* + * ttyconv -- + * convert tty to correct name. + */ +static char * +ttyconv(char *arg) { + char *mval; + + /* + * kludge -- we assume that all tty's end with + * a two character suffix. + */ + if (strlen(arg) == 2) { + /* either 6 for "ttyxx" or 8 for "console" */ + mval = xmalloc(8); + if (!strncmp(arg, "co", 2)) + (void)strcpy(mval, "console"); + else { + (void)strcpy(mval, "tty"); + (void)strncpy(mval + 3, arg, 4); + } + return mval; + } + if (!strncmp(arg, "/dev/", sizeof("/dev/") - 1)) + return arg + 5; + + return arg; +} + +/* + * onintr -- + * on interrupt, we inform the user how far we've gotten + */ +static void +onintr(int signo) { + char *ct; + + ct = utmp_ctime(&utmpbuf); + printf(_("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11); + if (signo == SIGINT) + _exit(EXIT_FAILURE); + fflush(stdout); /* fix required for rsh */ +} diff --git a/login-utils/last.1 b/login-utils/last.1 index beb691784..4afc6c1f9 100644 --- a/login-utils/last.1 +++ b/login-utils/last.1 @@ -1,62 +1,124 @@ -.TH LAST 1 "March 1992" "util-linux" "User Commands" +'\" -*- coding: UTF-8 -*- +.\" Copyright (C) 1998-2004 Miquel van Smoorenburg. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +.\" +.\"{{{}}} +.\"{{{ Title +.TH LAST,LASTB 1 "Jul 31, 2004" "" "Linux System Administrator's Manual" +.\"}}} +.\"{{{ Name .SH NAME -last \(em indicate last logins by user or terminal +last, lastb \- show listing of last logged in users +.\"}}} +.\"{{{ Synopsis .SH SYNOPSIS -.ad l .B last -.RB [ \-\fP\fInumber\fP ] -.RB [ \-f -.IR filename ] -.RB [ \-t -.IR tty ] -.RB [ \-h -.IR hostname ] -.RB [ \-i -.IR address ] -.RB [ \-l ] -.RB [ \-y ] -.RI [ name ...] -.ad b +.RB [ \-R ] +.RB [ \-\fInum\fP ] +.RB "[ \-\fBn\fP \fInum\fP ]" +.RB [ \-adFiowx ] +.RB "[ \-\fBf\fP \fIfile\fP ]" +.RB "[ \-\fBt\fP \fIYYYYMMDDHHMMSS\fP ]" +.RI [ name... ] +.RI [ tty... ] +.br +.B lastb +.RB [ \-R ] +.RB [ \-\fInum\fP ] +.RB "[ \-\fBn\fP \fInum\fP ]" +.RB "[ \-\fBf\fP \fIfile\fP ]" +.RB [ \-adFiowx ] +.RI [ name... ] +.RI [ tty... ] +.\"}}} +.\"{{{ Description .SH DESCRIPTION -\fBLast\fP looks back in the \fBwtmp\fP file which records all logins -and logouts for information about a user, a teletype or any group of -users and teletypes. Arguments specify names of users or teletypes of -interest. If multiple arguments are given, the information which -applies to any of the arguments is printed. For example ``\fBlast root -console\fP'' would list all of root's sessions as well as all sessions -on the console terminal. \fBLast\fP displays the sessions of the -specified users and teletypes, most recent first, indicating the times -at which the session began, the duration of the session, and the -teletype which the session took place on. If the session is still -continuing or was cut short by a reboot, \fBlast\fP so indicates. -.LP -The pseudo-user \fBreboot\fP logs in at reboots of the system. -.LP -\fBLast\fP with no arguments displays a record of all logins and -logouts, in reverse order. -.LP -If \fBlast\fP is interrupted, it indicates how far the search has -progressed in \fBwtmp\fP. If interrupted with a quit signal \fBlast\fP -indicates how far the search has progressed so far, and the search -continues. +.B Last +searches back through the file \fB/var/log/wtmp\fP (or the file +designated by the \fB\-f\fP flag) and displays a list of all +users logged in (and out) since that file was created. Names of users +and tty's can be given, in which case \fBlast\fP will show only those entries +matching the arguments. Names of ttys can be abbreviated, thus \fBlast +0\fP is the same as \fBlast tty0\fP. +.PP +When \fBlast\fP catches a \s-2SIGINT\s0 signal (generated by the interrupt key, +usually control-C) or a \s-2SIGQUIT\s0 signal (generated by the quit key, +usually control-\e), \fBlast\fP will show how far it has searched through the +file; in the case of the \s-2SIGINT\s0 signal \fBlast\fP will then terminate. +.PP +The pseudo user \fBreboot\fP logs in each time the system is rebooted. +Thus \fBlast reboot\fP will show a log of all reboots since the log file +was created. +.PP +\fBLastb\fP is the same as \fBlast\fP, except that by default it shows a log +of the file \fB/var/log/btmp\fP, which contains all the bad login attempts. +.\"}}} +.\"{{{ Options .SH OPTIONS -.IP \fB\-\fP\fInumber\fP -limit the number of entries displayed to that specified by \fInumber\fP. -.IP "\fB\-f\fP \fIfilename\fP" -Use \fIfilename\fP as the name of the accounting file instead of -.BR /var/log/wtmp . -.IP "\fB\-h\fP \fIhostname\fP" -List only logins from \fIhostname\fP. -.IP "\fB\-i\fP \fIIP address\fP" -List only logins from \fIIP address\fP. -.IP "\fB\-l\fP" -List IP addresses of remote hosts instead of truncated host names. -.IP "\fB\-t\fP \fItty\fP" -List only logins on \fItty\fP. -.IP "\fB\-y\fP" -Also report year of dates. +.IP "\fB\-f\fP \fIfile\fP" +Tells \fBlast\fP to use a specific file instead of \fB/var/log/wtmp\fP. +.IP \fB\-\fP\fInum\fP +This is a count telling \fBlast\fP how many lines to show. +.IP "\fB\-n\fP \fInum\fP" +The same. +.IP "\fB\-t\fP \fIYYYYMMDDHHMMSS\fP" +Display the state of logins as of the specified time. This is +useful, e.g., to determine easily who was logged in at a particular +time -- specify that time with \fB\-t\fP and look for "still logged +in". +.IP "\fB\-f\fP \fIfile\fP" +Specifies a file to search other than \fB/var/log/wtmp\fP. +.IP \fB\-R\fP +Suppresses the display of the hostname field. +.IP \fB\-a\fP +Display the hostname in the last column. Useful in combination +with the next flag. +.IP \fB\-d\fP +For non-local logins, Linux stores not only the host name of the remote +host but its IP number as well. This option translates the IP number +back into a hostname. +.IP \fB\-F\fP +Print full login and logout times and dates. +.IP \fB\-i\fP +This option is like \fB-d\fP in that it displays the IP number of the remote +host, but it displays the IP number in numbers-and-dots notation. +.IP \fB\-w\fP +Display full user and domain names in the output. +.IP \fB\-x\fP +Display the system shutdown entries and run level changes. +.\"}}} +.SH NOTES +The files \fIwtmp\fP and \fIbtmp\fP might not be found. The system only +logs information in these files if they are present. This is a local +configuration issue. If you want the files to be used, they can be +created with a simple \fBtouch\fP(1) command (for example, +\fItouch /var/log/wtmp\fP). +.\"{{{ Files .SH FILES -/var/log/wtmp \(em login data base -.SH AVAILABILITY -The last command is part of the util-linux package and is available from -ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +/var/log/wtmp +.br +/var/log/btmp +.\"}}} +.\"{{{ Author +.SH AUTHOR +Miquel van Smoorenburg, miquels@cistron.nl +.\"}}} +.\"{{{ See also +.SH "SEE ALSO" +.BR shutdown (8), +.BR login (1), +.BR init (8) +.\"}}} diff --git a/login-utils/last.c b/login-utils/last.c index c3fbaf66a..06017df15 100644 --- a/login-utils/last.c +++ b/login-utils/last.c @@ -1,483 +1,851 @@ /* - * Berkeley last for Linux. Currently maintained by poe@daimi.aau.dk at - * ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil* + * last.c Re-implementation of the 'last' command, this time + * for Linux. Yes I know there is BSD last, but I + * just felt like writing this. No thanks :-). + * Also, this version gives lots more info (especially with -x) * - * Copyright (c) 1987 Regents of the University of California. - * All rights reserved. + * Author: Miquel van Smoorenburg, miquels@cistron.nl * - * Redistribution and use in source and binary forms are permitted - * provided that the above copyright notice and this paragraph are - * duplicated in all such forms and that any documentation, - * advertising materials, and other materials related to such - * distribution and use acknowledge that the software was developed - * by the University of California, Berkeley. The name of the - * University may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - */ - - /* 1999-02-22 Arkadiusz Miśkiewicz - * - added Native Language Support - */ - - /* 2001-02-14 Marek Zelem - * - using mmap() on Linux - great speed improvement - */ - -/* - * This command is deprecated. The utility is in maintenance mode, - * meaning we keep them in source tree for backward compatibility - * only. Do not waste time making this command better, unless the - * fix is about security or other very critical issue. + * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl + * + * This file is part of the sysvinit suite, + * Copyright (C) 1991-2004 Miquel van Smoorenburg. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * See Documentation/deprecated.txt for more information. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/* - * last +/* Deleting the -o option as well as any related code (utmp libc5 support), + * declaring functions static and fixing a few warnings(sighandlers) + * 06-Aug-2013 Ondrej Oprala */ -#include -#include -#include + #include -#include -#include -#include +#include +#include #include -#include #include -#include +#include +#include +#include +#include #include #include - -#include +#include +#include +#include #include +#include #include -#include "closestream.h" -#include "pathnames.h" -#include "nls.h" -#include "xalloc.h" -#include "c.h" - -#define SECDAY (24*60*60) /* seconds in a day */ -#define NO 0 /* false/no */ -#define YES 1 /* true/yes */ - -static struct utmp utmpbuf; - -#define HMAX (int)sizeof(utmpbuf.ut_host) /* size of utmp host field */ -#define LMAX (int)sizeof(utmpbuf.ut_line) /* size of utmp tty field */ -#define NMAX (int)sizeof(utmpbuf.ut_name) /* size of utmp name field */ - -/* maximum sizes used for printing */ -/* probably we want a two-pass version that computes the right length */ -#define P_HMAX min(HMAX, 16) -#define P_LMAX min(LMAX, 8) -#define P_NMAX min(NMAX, 16) - -typedef struct arg { - char *name; /* argument */ -#define HOST_TYPE -2 -#define TTY_TYPE -3 -#define USER_TYPE -4 -#define INET_TYPE -5 - int type; /* type of arg */ - struct arg *next; /* linked list pointer */ -} ARG; -ARG *arglist; /* head of linked list */ - -typedef struct ttytab { - long logout; /* log out time */ - char tty[LMAX + 1]; /* terminal name */ - struct ttytab *next; /* linked list pointer */ -} TTY; -TTY *ttylist; /* head of linked list */ - -static long currentout, /* current logout value */ - maxrec; /* records to display */ -static char *file = _PATH_WTMP; /* wtmp file */ - -static int doyear = 0; /* output year in dates */ -static int dolong = 0; /* print also ip-addr */ - -static void wtmp(void); -static void addarg(int, char *); -static void hostconv(char *); -static void onintr(int); -static int want(struct utmp *, int); -TTY *addtty(char *); -static char *ttyconv(char *); - -int -main(int argc, char **argv) { - int ch; - - setlocale(LC_ALL, ""); - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); - atexit(close_stdout); - - while ((ch = getopt(argc, argv, "0123456789yli:f:h:t:")) != -1) - switch((char)ch) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - /* - * kludge: last was originally designed to take - * a number after a dash. - */ - if (!maxrec) - maxrec = atol(argv[optind - 1] + 1); - break; - case 'f': - file = optarg; - break; - case 'h': - hostconv(optarg); - addarg(HOST_TYPE, optarg); - break; - case 't': - addarg(TTY_TYPE, ttyconv(optarg)); - break; - case 'y': - doyear = 1; - break; - case 'l': - dolong = 1; - break; - case 'i': - addarg(INET_TYPE, optarg); - break; - case '?': - default: - fputs(_("usage: last [-#] [-f file] [-t tty] [-h hostname] [user ...]\n"), stderr); - exit(EXIT_FAILURE); - } - for (argv += optind; *argv; ++argv) { -#define COMPATIBILITY -#ifdef COMPATIBILITY - /* code to allow "last p5" to work */ - addarg(TTY_TYPE, ttyconv(*argv)); +#ifndef SHUTDOWN_TIME +# define SHUTDOWN_TIME 254 #endif - addarg(USER_TYPE, *argv); + +char *Version = "@(#) last 2.85 31-Apr-2004 miquels"; + +#define CHOP_DOMAIN 0 /* Define to chop off local domainname. */ +#define UCHUNKSIZE 16384 /* How much we read at once. */ + +/* Double linked list of struct utmp's */ +struct utmplist { + struct utmp ut; + struct utmplist *next; + struct utmplist *prev; +}; +struct utmplist *utmplist = NULL; + +/* Types of listing */ +#define R_CRASH 1 /* No logout record, system boot in between */ +#define R_DOWN 2 /* System brought down in decent way */ +#define R_NORMAL 3 /* Normal */ +#define R_NOW 4 /* Still logged in */ +#define R_REBOOT 5 /* Reboot record. */ +#define R_PHANTOM 6 /* No logout record but session is stale. */ +#define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */ + +/* Global variables */ +int maxrecs = 0; /* Maximum number of records to list. */ +int recsdone = 0; /* Number of records listed */ +int showhost = 1; /* Show hostname too? */ +int altlist = 0; /* Show hostname at the end. */ +int usedns = 0; /* Use DNS to lookup the hostname. */ +int useip = 0; /* Print IP address in number format */ +int fulltime = 0; /* Print full dates and times */ +int name_len = 8; /* Default print 8 characters of name */ +int domain_len = 16; /* Default print 16 characters of domain */ +char **show = NULL; /* What do they want us to show */ +char *ufile; /* Filename of this file */ +time_t lastdate; /* Last date we've seen */ +char *progname; /* Name of this program */ +#if CHOP_DOMAIN +char hostname[256]; /* For gethostbyname() */ +char *domainname; /* Our domainname. */ +#endif + +/* + * Read one utmp entry, return in new format. + * Automatically reposition file pointer. + */ +static int uread(FILE *fp, struct utmp *u, int *quit) +{ + static int utsize; + static char buf[UCHUNKSIZE]; + char tmp[1024]; + static off_t fpos; + static int bpos; + off_t o; + + if (quit == NULL && u != NULL) { + /* + * Normal read. + */ + return fread(u, sizeof(struct utmp), 1, fp); + } + + if (u == NULL) { + /* + * Initialize and position. + */ + utsize = sizeof(struct utmp); + fseeko(fp, 0, SEEK_END); + fpos = ftello(fp); + if (fpos == 0) + return 0; + o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE; + if (fseeko(fp, o, SEEK_SET) < 0) { + fprintf(stderr, "%s: seek failed!\n", progname); + return 0; + } + bpos = (int)(fpos - o); + if (fread(buf, bpos, 1, fp) != 1) { + fprintf(stderr, "%s: read failed!\n", progname); + return 0; + } + fpos = o; + return 1; + } + + /* + * Read one struct. From the buffer if possible. + */ + bpos -= utsize; + if (bpos >= 0) { + memcpy(u, buf + bpos, sizeof(struct utmp)); + return 1; } - wtmp(); - return EXIT_SUCCESS; + /* + * Oops we went "below" the buffer. We should be able to + * seek back UCHUNKSIZE bytes. + */ + fpos -= UCHUNKSIZE; + if (fpos < 0) + return 0; + + /* + * Copy whatever is left in the buffer. + */ + memcpy(tmp + (-bpos), buf, utsize + bpos); + if (fseeko(fp, fpos, SEEK_SET) < 0) { + perror("fseek"); + return 0; + } + + /* + * Read another UCHUNKSIZE bytes. + */ + if (fread(buf, UCHUNKSIZE, 1, fp) != 1) { + perror("fread"); + return 0; + } + + /* + * The end of the UCHUNKSIZE byte buffer should be the first + * few bytes of the current struct utmp. + */ + memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos); + bpos += UCHUNKSIZE; + + memcpy(u, tmp, sizeof(struct utmp)); + + return 1; } -static char *utmp_ctime(struct utmp *u) +/* + * Try to be smart about the location of the BTMP file + */ +#ifndef BTMP_FILE +#define BTMP_FILE getbtmp() +static char *getbtmp(void) { - time_t t = (time_t) u->ut_time; - return ctime(&t); + static char btmp[128]; + char *p; + + strcpy(btmp, WTMP_FILE); + if ((p = strrchr(btmp, '/')) == NULL) + p = btmp; + else + p++; + *p = 0; + strcat(btmp, "btmp"); + return btmp; } +#endif /* - * print_partial_line -- - * print the first part of each output line according to specified format + * Print a short date. */ -static void -print_partial_line(struct utmp *bp) { - char *ct; - - ct = utmp_ctime(bp); - printf("%-*.*s %-*.*s ", P_NMAX, P_NMAX, bp->ut_name, - P_LMAX, P_LMAX, bp->ut_line); - - if (dolong) { - if (bp->ut_addr) { - struct in_addr foo; - foo.s_addr = bp->ut_addr; - printf("%-*.*s ", P_HMAX, P_HMAX, inet_ntoa(foo)); - } else { - printf("%-*.*s ", P_HMAX, P_HMAX, ""); - } - } else { - printf("%-*.*s ", P_HMAX, P_HMAX, bp->ut_host); - } - - if (doyear) { - printf("%10.10s %4.4s %5.5s ", ct, ct + 20, ct + 11); - } else { - printf("%10.10s %5.5s ", ct, ct + 11); - } +static char *showdate(void) +{ + char *s = ctime(&lastdate); + s[16] = 0; + return s; } /* - * wtmp -- - * read through the wtmp file + * SIGINT handler */ -static void -wtmp(void) { - register struct utmp *bp; /* current structure */ - register TTY *T; /* tty list entry */ - long delta; /* time difference */ - char *crmsg = NULL; - char *ct = NULL; - int fd; - struct utmp *utl; - struct stat st; - int utl_len; - int listnr = 0; - int i; - - utmpname(file); - - { -#if defined(_HAVE_UT_TV) - struct timeval tv; - gettimeofday(&tv, NULL); - utmpbuf.ut_tv.tv_sec = tv.tv_sec; - utmpbuf.ut_tv.tv_usec = tv.tv_usec; -#else - time_t t; - time(&t); - utmpbuf.ut_time = t; -#endif - } +static void int_handler(int sig __attribute__((unused))) +{ + printf("Interrupted %s\n", showdate()); + exit(1); +} - (void)signal(SIGINT, onintr); - (void)signal(SIGQUIT, onintr); +/* + * SIGQUIT handler + */ +static void quit_handler(int sig __attribute__((unused))) +{ + printf("Interrupted %s\n", showdate()); + signal(SIGQUIT, quit_handler); +} - if ((fd = open(file,O_RDONLY)) < 0) - err(EXIT_FAILURE, _("cannot open %s"), file); +/* + * Get the basename of a filename + */ +static char *mybasename(char *s) +{ + char *p; - fstat(fd, &st); - utl_len = st.st_size; - utl = mmap(NULL, utl_len, PROT_READ|PROT_WRITE, - MAP_PRIVATE|MAP_FILE, fd, 0); - if (utl == NULL) - err(EXIT_FAILURE, _("%s: mmap failed"), file); + if ((p = strrchr(s, '/')) != NULL) + p++; + else + p = s; + return p; +} - listnr = utl_len/sizeof(struct utmp); +/* + * Lookup a host with DNS. + */ +static int dns_lookup(char *result, int size, int useip, int32_t *a) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr *sa; + int salen, flags; + int mapped = 0; - if(listnr) - ct = utmp_ctime(&utl[0]); + flags = useip ? NI_NUMERICHOST : 0; - for(i = listnr - 1; i >= 0; i--) { - bp = utl+i; - /* - * if the terminal line is '~', the machine stopped. - * see utmp(5) for more info. - */ - if (!strncmp(bp->ut_line, "~", LMAX)) { - /* - * utmp(5) also mentions that the user - * name should be 'shutdown' or 'reboot'. - * Not checking the name causes e.g. runlevel - * changes to be displayed as 'crash'. -thaele - */ - if (!strncmp(bp->ut_user, "reboot", NMAX) || - !strncmp(bp->ut_user, "shutdown", NMAX)) { - /* everybody just logged out */ - for (T = ttylist; T; T = T->next) - T->logout = -bp->ut_time; - } - - currentout = -bp->ut_time; - crmsg = (strncmp(bp->ut_name, "shutdown", NMAX) - ? "crash" : "down "); - if (!bp->ut_name[0]) - (void)strcpy(bp->ut_name, "reboot"); - if (want(bp, NO)) { - ct = utmp_ctime(bp); - if(bp->ut_type != LOGIN_PROCESS) { - print_partial_line(bp); - putchar('\n'); - } - if (maxrec && !--maxrec) - return; - } - continue; - } - /* find associated tty */ - for (T = ttylist;; T = T->next) { - if (!T) { - /* add new one */ - T = addtty(bp->ut_line); - break; - } - if (!strncmp(T->tty, bp->ut_line, LMAX)) - break; - } - if (bp->ut_name[0] && bp->ut_type != LOGIN_PROCESS - && bp->ut_type != DEAD_PROCESS - && want(bp, YES)) { - - print_partial_line(bp); - - if (!T->logout) - puts(_(" still logged in")); - else { - if (T->logout < 0) { - T->logout = -T->logout; - printf("- %s", crmsg); - } - else - printf("- %5.5s", ctime(&T->logout)+11); - delta = T->logout - bp->ut_time; - if (delta < SECDAY) - printf(" (%5.5s)\n", asctime(gmtime(&delta))+11); - else - printf(" (%ld+%5.5s)\n", delta / SECDAY, asctime(gmtime(&delta))+11); - } - if (maxrec != -1 && !--maxrec) - return; - } - T->logout = bp->ut_time; - utmpbuf.ut_time = bp->ut_time; + /* + * IPv4 or IPv6 ? + * 1. If last 3 4bytes are 0, must be IPv4 + * 2. If IPv6 in IPv4, handle as IPv4 + * 3. Anything else is IPv6 + * + * Ugly. + */ + if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff)) + mapped = 1; + + if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) { + /* IPv4 */ + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = mapped ? a[3] : a[0]; + sa = (struct sockaddr *)&sin; + salen = sizeof(sin); + } else { + /* IPv6 */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = 0; + memcpy(sin6.sin6_addr.s6_addr, a, 16); + sa = (struct sockaddr *)&sin6; + salen = sizeof(sin6); } - munmap(utl,utl_len); - close(fd); - if(ct) printf(_("\nwtmp begins %s"), ct); /* ct already ends in \n */ + + return getnameinfo(sa, salen, result, size, NULL, 0, flags); } /* - * want -- - * see if want this entry + * Show one line of information on screen */ -static int -want(struct utmp *bp, int check) { - register ARG *step; +static int list(struct utmp *p, time_t t, int what) +{ + time_t secs, tmp; + char logintime[32]; + char logouttime[32]; + char length[32]; + char final[512]; + char utline[UT_LINESIZE+1]; + char domain[256]; + char *s, **walk; + int mins, hours, days; + int r, len; - if (check) { - /* - * when uucp and ftp log in over a network, the entry in - * the utmp file is the name plus their process id. See - * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. - */ - if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) - bp->ut_line[3] = '\0'; - else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) - bp->ut_line[4] = '\0'; + /* + * uucp and ftp have special-type entries + */ + utline[0] = 0; + strncat(utline, p->ut_line, UT_LINESIZE); + if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3])) + utline[3] = 0; + if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4])) + utline[4] = 0; + + /* + * Is this something we wanna show? + */ + if (show) { + for (walk = show; *walk; walk++) { + if (strncmp(p->ut_name, *walk, UT_NAMESIZE) == 0 || + strcmp(utline, *walk) == 0 || + (strncmp(utline, "tty", 3) == 0 && + strcmp(utline + 3, *walk) == 0)) break; + } + if (*walk == NULL) return 0; } - if (!arglist) - return YES; - - for (step = arglist; step; step = step->next) - switch(step->type) { - case HOST_TYPE: - if (!strncmp(step->name, bp->ut_host, HMAX)) - return YES; + + /* + * Calculate times + */ + tmp = (time_t)p->ut_time; + strcpy(logintime, ctime(&tmp)); + if (fulltime) + sprintf(logouttime, "- %s", ctime(&t)); + else { + logintime[16] = 0; + sprintf(logouttime, "- %s", ctime(&t) + 11); + logouttime[7] = 0; + } + secs = t - p->ut_time; + mins = (secs / 60) % 60; + hours = (secs / 3600) % 24; + days = secs / 86400; + if (days) + sprintf(length, "(%d+%02d:%02d)", days, hours, mins); + else + sprintf(length, " (%02d:%02d)", hours, mins); + + switch(what) { + case R_CRASH: + sprintf(logouttime, "- crash"); + break; + case R_DOWN: + sprintf(logouttime, "- down "); + break; + case R_NOW: + length[0] = 0; + if (fulltime) + sprintf(logouttime, " still logged in"); + else { + sprintf(logouttime, " still"); + sprintf(length, "logged in"); + } break; - case TTY_TYPE: - if (!strncmp(step->name, bp->ut_line, LMAX)) - return YES; + case R_PHANTOM: + length[0] = 0; + if (fulltime) + sprintf(logouttime, " gone - no logout"); + else { + sprintf(logouttime, " gone"); + sprintf(length, "- no logout"); + } + break; + case R_REBOOT: break; - case USER_TYPE: - if (!strncmp(step->name, bp->ut_name, NMAX)) - return YES; + case R_TIMECHANGE: + logouttime[0] = 0; + length[0] = 0; break; - case INET_TYPE: - if ((in_addr_t) bp->ut_addr == inet_addr(step->name)) - return YES; + case R_NORMAL: break; - default: - abort(); + } + + /* + * Look up host with DNS if needed. + */ + r = -1; + if (usedns || useip) + r = dns_lookup(domain, sizeof(domain), useip, p->ut_addr_v6); + if (r < 0) { + len = UT_HOSTSIZE; + if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1; + domain[0] = 0; + strncat(domain, p->ut_host, len); + } + + if (showhost) { +#if CHOP_DOMAIN + /* + * See if this is in our domain. + */ + if (!usedns && (s = strchr(p->ut_host, '.')) != NULL && + strcmp(s + 1, domainname) == 0) *s = 0; +#endif + if (!altlist) { + len = snprintf(final, sizeof(final), + fulltime ? + "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" : + "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n", + name_len, p->ut_name, utline, + domain_len, domain, logintime, logouttime, length); + } else { + len = snprintf(final, sizeof(final), + fulltime ? + "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" : + "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n", + name_len, p->ut_name, utline, + logintime, logouttime, length, domain); } - return NO; -} + } else + len = snprintf(final, sizeof(final), + fulltime ? + "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" : + "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n", + name_len, p->ut_name, utline, + logintime, logouttime, length); + +#if defined(__GLIBC__) +# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0) + final[sizeof(final)-1] = '\0'; +# endif +#endif -/* - * addarg -- - * add an entry to a linked list of arguments - */ -static void -addarg(int type, char *arg) { - register ARG *cur; - - cur = xmalloc(sizeof(ARG)); - cur->next = arglist; - cur->type = type; - cur->name = arg; - arglist = cur; + /* + * Print out "final" string safely. + */ + for (s = final; *s; s++) { + if (*s == '\n' || (*s >= 32 && (unsigned char)*s <= 126)) + putchar(*s); + else + putchar('*'); + } + + if (len < 0 || (size_t)len >= sizeof(final)) + putchar('\n'); + + recsdone++; + if (maxrecs && recsdone >= maxrecs) + return 1; + + return 0; } + /* - * addtty -- - * add an entry to a linked list of ttys + * show usage */ -TTY * -addtty(char *ttyname) { - register TTY *cur; - - cur = xmalloc(sizeof(TTY)); - cur->next = ttylist; - cur->logout = currentout; - memcpy(cur->tty, ttyname, LMAX); - return(ttylist = cur); +static void usage(char *s) +{ + fprintf(stderr, "Usage: %s [-num | -n num] [-f file] " + "[-t YYYYMMDDHHMMSS] " + "[-R] [-adixFw] [username..] [tty..]\n", s); + exit(1); } -/* - * hostconv -- - * convert the hostname to search pattern; if the supplied host name - * has a domain attached that is the same as the current domain, rip - * off the domain suffix since that's what login(1) does. - */ -static void -hostconv(char *arg) { - static int first = 1; - static char *hostdot, *name; +static time_t parsetm(char *ts) +{ + struct tm u, origu; + time_t tm; - char *argdot; + memset(&tm, 0, sizeof(tm)); - if (!(argdot = strchr(arg, '.'))) - return; + if (sscanf(ts, "%4d%2d%2d%2d%2d%2d", &u.tm_year, + &u.tm_mon, &u.tm_mday, &u.tm_hour, &u.tm_min, + &u.tm_sec) != 6) + return (time_t)-1; - if (first) { - first = 0; - name = xgethostname(); - if (!name) - err(EXIT_FAILURE, _("gethostname failed")); + u.tm_year -= 1900; + u.tm_mon -= 1; + u.tm_isdst = -1; - hostdot = strchr(name, '.'); - } - if (hostdot && !strcmp(hostdot, argdot)) - *argdot = '\0'; + origu = u; + + if ((tm = mktime(&u)) == (time_t)-1) + return tm; + + /* + * Unfortunately mktime() is much more forgiving than + * it should be. For example, it'll gladly accept + * "30" as a valid month number. This behavior is by + * design, but we don't like it, so we want to detect + * it and complain. + */ + if (u.tm_year != origu.tm_year || + u.tm_mon != origu.tm_mon || + u.tm_mday != origu.tm_mday || + u.tm_hour != origu.tm_hour || + u.tm_min != origu.tm_min || + u.tm_sec != origu.tm_sec) + return (time_t)-1; + + return tm; } -/* - * ttyconv -- - * convert tty to correct name. - */ -static char * -ttyconv(char *arg) { - char *mval; +int main(int argc, char **argv) +{ + FILE *fp; /* Filepointer of wtmp file */ + + struct utmp ut; /* Current utmp entry */ + struct utmplist *p; /* Pointer into utmplist */ + struct utmplist *next;/* Pointer into utmplist */ + + time_t lastboot = 0; /* Last boottime */ + time_t lastrch = 0; /* Last run level change */ + time_t lastdown; /* Last downtime */ + time_t begintime; /* When wtmp begins */ + int whydown = 0; /* Why we went down: crash or shutdown */ + + int c, x; /* Scratch */ + struct stat st; /* To stat the [uw]tmp file */ + int quit = 0; /* Flag */ + int down = 0; /* Down flag */ + int lastb = 0; /* Is this 'lastb' ? */ + int extended = 0; /* Lots of info. */ + char *altufile = NULL;/* Alternate wtmp */ + + time_t until = 0; /* at what time to stop parsing the file */ + + progname = mybasename(argv[0]); + + /* Process the arguments. */ + while((c = getopt(argc, argv, "f:n:RxadFit:0123456789w")) != EOF) + switch(c) { + case 'R': + showhost = 0; + break; + case 'x': + extended = 1; + break; + case 'n': + maxrecs = atoi(optarg); + break; + case 'f': + if((altufile = malloc(strlen(optarg)+1)) == NULL) { + fprintf(stderr, "%s: out of memory\n", + progname); + exit(1); + } + strcpy(altufile, optarg); + break; + case 'd': + usedns++; + break; + case 'i': + useip++; + break; + case 'a': + altlist++; + break; + case 'F': + fulltime++; + break; + case 't': + if ((until = parsetm(optarg)) == (time_t)-1) { + fprintf(stderr, "%s: Invalid time value \"%s\"\n", + progname, optarg); + usage(progname); + } + break; + case 'w': + if (UT_NAMESIZE > name_len) + name_len = UT_NAMESIZE; + if (UT_HOSTSIZE > domain_len) + domain_len = UT_HOSTSIZE; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + maxrecs = 10*maxrecs + c - '0'; + break; + default: + usage(progname); + break; + } + if (optind < argc) show = argv + optind; + + /* + * Which file do we want to read? + */ + if (strcmp(progname, "lastb") == 0) { + ufile = BTMP_FILE; + lastb = 1; + } else + ufile = WTMP_FILE; + if (altufile) + ufile = altufile; + time(&lastdown); + lastrch = lastdown; + + /* + * Fill in 'lastdate' + */ + lastdate = lastdown; + +#if CHOP_DOMAIN + /* + * Find out domainname. + * + * This doesn't work on modern systems, where only a DNS + * lookup of the result from hostname() will get you the domainname. + * Remember that domainname() is the NIS domainname, not DNS. + * So basically this whole piece of code is bullshit. + */ + hostname[0] = 0; + (void) gethostname(hostname, sizeof(hostname)); + if ((domainname = strchr(hostname, '.')) != NULL) domainname++; + if (domainname == NULL || domainname[0] == 0) { + hostname[0] = 0; + (void) getdomainname(hostname, sizeof(hostname)); + hostname[sizeof(hostname) - 1] = 0; + domainname = hostname; + if (strcmp(domainname, "(none)") == 0 || domainname[0] == 0) + domainname = NULL; + } +#endif + + /* + * Install signal handlers + */ + signal(SIGINT, int_handler); + signal(SIGQUIT, quit_handler); + + /* + * Open the utmp file + */ + if ((fp = fopen(ufile, "r")) == NULL) { + x = errno; + fprintf(stderr, "%s: %s: %s\n", progname, ufile, strerror(errno)); + if (altufile == NULL && x == ENOENT) + fprintf(stderr, "Perhaps this file was removed by the " + "operator to prevent logging %s info.\n", progname); + exit(1); + } + + /* + * Optimize the buffer size. + */ + setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE); + + /* + * Read first structure to capture the time field + */ + if (uread(fp, &ut, NULL) == 1) + begintime = ut.ut_time; + else { + fstat(fileno(fp), &st); + begintime = st.st_ctime; + quit = 1; + } + + /* + * Go to end of file minus one structure + * and/or initialize utmp reading code. + */ + uread(fp, NULL, NULL); + + /* + * Read struct after struct backwards from the file. + */ + while(!quit) { + + if (uread(fp, &ut, &quit) != 1) + break; + + if (until && until < ut.ut_time) + continue; + + lastdate = ut.ut_time; + + if (lastb) { + quit = list(&ut, ut.ut_time, R_NORMAL); + continue; + } /* - * kludge -- we assume that all tty's end with - * a two character suffix. + * Set ut_type to the correct type. + */ + if (strncmp(ut.ut_line, "~", 1) == 0) { + if (strncmp(ut.ut_user, "shutdown", 8) == 0) + ut.ut_type = SHUTDOWN_TIME; + else if (strncmp(ut.ut_user, "reboot", 6) == 0) + ut.ut_type = BOOT_TIME; + else if (strncmp(ut.ut_user, "runlevel", 8) == 0) + ut.ut_type = RUN_LVL; + } +#if 1 /*def COMPAT*/ + /* + * For stupid old applications that don't fill in + * ut_type correctly. */ - if (strlen(arg) == 2) { - /* either 6 for "ttyxx" or 8 for "console" */ - mval = xmalloc(8); - if (!strncmp(arg, "co", 2)) - (void)strcpy(mval, "console"); - else { - (void)strcpy(mval, "tty"); - (void)strncpy(mval + 3, arg, 4); + else { + if (ut.ut_type != DEAD_PROCESS && + ut.ut_name[0] && ut.ut_line[0] && + strcmp(ut.ut_name, "LOGIN") != 0) + ut.ut_type = USER_PROCESS; + /* + * Even worse, applications that write ghost + * entries: ut_type set to USER_PROCESS but + * empty ut_name... + */ + if (ut.ut_name[0] == 0) + ut.ut_type = DEAD_PROCESS; + + /* + * Clock changes. + */ + if (strcmp(ut.ut_name, "date") == 0) { + if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME; + if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME; } - return mval; } - if (!strncmp(arg, "/dev/", sizeof("/dev/") - 1)) - return arg + 5; +#endif - return arg; -} + switch (ut.ut_type) { + case SHUTDOWN_TIME: + if (extended) { + strcpy(ut.ut_line, "system down"); + quit = list(&ut, lastboot, R_NORMAL); + } + lastdown = lastrch = ut.ut_time; + down = 1; + break; + case OLD_TIME: + case NEW_TIME: + if (extended) { + strcpy(ut.ut_line, + ut.ut_type == NEW_TIME ? "new time" : + "old time"); + quit = list(&ut, lastdown, R_TIMECHANGE); + } + break; + case BOOT_TIME: + strcpy(ut.ut_line, "system boot"); + quit = list(&ut, lastdown, R_REBOOT); + lastboot = ut.ut_time; + down = 1; + break; + case RUN_LVL: + x = ut.ut_pid & 255; + if (extended) { + sprintf(ut.ut_line, "(to lvl %c)", x); + quit = list(&ut, lastrch, R_NORMAL); + } + if (x == '0' || x == '6') { + lastdown = ut.ut_time; + down = 1; + ut.ut_type = SHUTDOWN_TIME; + } + lastrch = ut.ut_time; + break; -/* - * onintr -- - * on interrupt, we inform the user how far we've gotten - */ -static void -onintr(int signo) { - char *ct; - - ct = utmp_ctime(&utmpbuf); - printf(_("\ninterrupted %10.10s %5.5s \n"), ct, ct + 11); - if (signo == SIGINT) - _exit(EXIT_FAILURE); - fflush(stdout); /* fix required for rsh */ + case USER_PROCESS: + /* + * This was a login - show the first matching + * logout record and delete all records with + * the same ut_line. + */ + c = 0; + for (p = utmplist; p; p = next) { + next = p->next; + if (strncmp(p->ut.ut_line, ut.ut_line, + UT_LINESIZE) == 0) { + /* Show it */ + if (c == 0) { + quit = list(&ut, p->ut.ut_time, + R_NORMAL); + c = 1; + } + if (p->next) p->next->prev = p->prev; + if (p->prev) + p->prev->next = p->next; + else + utmplist = p->next; + free(p); + } + } + /* + * Not found? Then crashed, down, still + * logged in, or missing logout record. + */ + if (c == 0) { + if (lastboot == 0) { + c = R_NOW; + /* Is process still alive? */ + if (ut.ut_pid > 0 && + kill(ut.ut_pid, 0) != 0 && + errno == ESRCH) + c = R_PHANTOM; + } else + c = whydown; + quit = list(&ut, lastboot, c); + } + /* FALLTHRU */ + + case DEAD_PROCESS: + /* + * Just store the data if it is + * interesting enough. + */ + if (ut.ut_line[0] == 0) + break; + if ((p = malloc(sizeof(struct utmplist))) == NULL) { + fprintf(stderr, "%s: out of memory\n", + progname); + exit(1); + } + memcpy(&p->ut, &ut, sizeof(struct utmp)); + p->next = utmplist; + p->prev = NULL; + if (utmplist) utmplist->prev = p; + utmplist = p; + break; + + } + /* + * If we saw a shutdown/reboot record we can remove + * the entire current utmplist. + */ + if (down) { + lastboot = ut.ut_time; + whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH; + for (p = utmplist; p; p = next) { + next = p->next; + free(p); + } + utmplist = NULL; + down = 0; + } + } + printf("\n%s begins %s", mybasename(ufile), ctime(&begintime)); + + fclose(fp); + + /* + * Should we free memory here? Nah. This is not NT :) + */ + return 0; } diff --git a/login-utils/lastb.1 b/login-utils/lastb.1 new file mode 100644 index 000000000..f57c02a6e --- /dev/null +++ b/login-utils/lastb.1 @@ -0,0 +1 @@ +.so man1/last.1 -- cgit v1.2.3-55-g7522