]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
last: merge sysvinit last/lastb
authorOndrej Oprala <ooprala@redhat.com>
Tue, 6 Aug 2013 16:19:37 +0000 (18:19 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 12 Aug 2013 10:07:52 +0000 (12:07 +0200)
Signed-off-by: Ondrej Oprala <ooprala@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
bash-completion/Makemodule.am
configure.ac
login-utils/Makemodule.am
login-utils/last-deprecated.1 [new file with mode: 0644]
login-utils/last-deprecated.c [new file with mode: 0644]
login-utils/last.1
login-utils/last.c
login-utils/lastb.1 [new file with mode: 0644]

index c3791e71ae259cafe314991810c7680e08f3871f..aaf2737d307131f1919ee43f7bdb9ec91a591e59 100644 (file)
@@ -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
index 7252e9b3530383b350f65e22fbca507d617fc562..d0c54247fb61b3b418b023b9fe9f3125d4139541 100644 (file)
@@ -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])
index 8283935f9cbe434916c1746339c2ac76a468c389..6600f27c6d8fc579d171679177bdf12e412e7a94 100644 (file)
@@ -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 (file)
index 0000000..beb6917
--- /dev/null
@@ -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 (file)
index 0000000..d3fdc8c
--- /dev/null
@@ -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 <misiek@pld.ORG.PL>
+  * - added Native Language Support
+  */
+
+ /* 2001-02-14 Marek Zelem <marek@fornax.sk>
+  * - 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 <sys/param.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+#include <utmp.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#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 */
+}
index beb69178475a43cddc16fc31af978aa78453c22e..4afc6c1f98fdd2ada5448bc5d834ca9024c913e7 100644 (file)
-.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)
+.\"}}}
index c3fbaf66ac07161fc3ff2c2ad61be0bbf9a2b138..06017df15e4db1168b2b94377b7164d1189c3a6d 100644 (file)
 /*
- * 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 <misiek@pld.ORG.PL>
-  * - added Native Language Support
-  */
-
- /* 2001-02-14 Marek Zelem <marek@fornax.sk>
-  * - 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 <ooprala@redhat.com>
  */
-#include <sys/param.h>
-#include <sys/stat.h>
-#include <sys/file.h>
+
 #include <sys/types.h>
-#include <sys/mman.h>
-#include <signal.h>
-#include <string.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
 #include <time.h>
-#include <utmp.h>
 #include <stdio.h>
-#include <getopt.h>
+#include <ctype.h>
+#include <utmp.h>
+#include <errno.h>
+#include <malloc.h>
 #include <stdlib.h>
 #include <unistd.h>
-
-#include <sys/socket.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
 #include <netinet/in.h>
+#include <netdb.h>
 #include <arpa/inet.h>
 
-#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 (file)
index 0000000..f57c02a
--- /dev/null
@@ -0,0 +1 @@
+.so man1/last.1