2006-12-06 17:25:32 -06:00
|
|
|
/*
|
2013-08-12 05:26:03 -05:00
|
|
|
* last(1) from sysvinit project, merged into util-linux in Aug 2013.
|
2006-12-06 17:25:32 -06:00
|
|
|
*
|
2013-08-12 05:26:03 -05:00
|
|
|
* Copyright (C) 1991-2004 Miquel van Smoorenburg.
|
|
|
|
* Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
|
2013-08-13 03:22:50 -05:00
|
|
|
* Karel Zak <kzak@redhat.com>
|
2006-12-06 17:25:32 -06:00
|
|
|
*
|
2013-08-12 05:26:03 -05:00
|
|
|
* 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)
|
2013-08-06 11:19:37 -05:00
|
|
|
*
|
|
|
|
*
|
2013-08-12 05:26:03 -05:00
|
|
|
* 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.
|
2012-02-28 16:05:59 -06:00
|
|
|
*
|
2013-08-12 05:26:03 -05:00
|
|
|
* 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.
|
2013-08-06 11:19:37 -05:00
|
|
|
*
|
2013-08-12 05:26:03 -05:00
|
|
|
* 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
|
2012-02-28 16:05:59 -06:00
|
|
|
*/
|
2006-12-06 17:25:39 -06:00
|
|
|
#include <sys/types.h>
|
2013-08-06 11:19:37 -05:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/fcntl.h>
|
2006-12-06 17:25:32 -06:00
|
|
|
#include <time.h>
|
|
|
|
#include <stdio.h>
|
2013-08-06 11:19:37 -05:00
|
|
|
#include <ctype.h>
|
|
|
|
#include <utmp.h>
|
2014-01-21 16:05:05 -06:00
|
|
|
#include <pwd.h>
|
2006-12-06 17:25:32 -06:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
2013-08-06 11:19:37 -05:00
|
|
|
#include <string.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <getopt.h>
|
2006-12-06 17:25:39 -06:00
|
|
|
#include <netinet/in.h>
|
2013-08-06 11:19:37 -05:00
|
|
|
#include <netdb.h>
|
2006-12-06 17:25:32 -06:00
|
|
|
#include <arpa/inet.h>
|
2006-12-06 17:25:39 -06:00
|
|
|
|
2013-08-12 06:39:11 -05:00
|
|
|
#include "c.h"
|
|
|
|
#include "nls.h"
|
2013-09-02 04:42:44 -05:00
|
|
|
#include "optutils.h"
|
2013-08-12 06:42:25 -05:00
|
|
|
#include "pathnames.h"
|
2013-08-12 07:10:42 -05:00
|
|
|
#include "xalloc.h"
|
|
|
|
#include "closestream.h"
|
2013-08-17 13:15:12 -05:00
|
|
|
#include "carefulputc.h"
|
2013-08-17 13:15:13 -05:00
|
|
|
#include "strutils.h"
|
2013-09-10 06:11:02 -05:00
|
|
|
#include "timeutils.h"
|
2014-11-19 04:54:47 -06:00
|
|
|
#include "monotonic.h"
|
2013-08-12 06:39:11 -05:00
|
|
|
|
2013-08-29 01:46:46 -05:00
|
|
|
#if defined(_HAVE_UT_TV)
|
|
|
|
# define UL_UT_TIME ut_tv.tv_sec
|
|
|
|
#else
|
|
|
|
# define UL_UT_TIME ut_time
|
|
|
|
#endif
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
#ifndef SHUTDOWN_TIME
|
2013-08-12 05:26:03 -05:00
|
|
|
# define SHUTDOWN_TIME 254
|
2006-12-06 17:25:32 -06:00
|
|
|
#endif
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2013-08-27 07:15:08 -05:00
|
|
|
#ifndef LAST_LOGIN_LEN
|
|
|
|
# define LAST_LOGIN_LEN 8
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef LAST_DOMAIN_LEN
|
|
|
|
# define LAST_DOMAIN_LEN 16
|
|
|
|
#endif
|
|
|
|
|
2013-08-27 07:48:27 -05:00
|
|
|
#ifndef LAST_TIMESTAMP_LEN
|
|
|
|
# define LAST_TIMESTAMP_LEN 32
|
|
|
|
#endif
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
#define UCHUNKSIZE 16384 /* How much we read at once. */
|
|
|
|
|
2013-08-27 07:15:08 -05:00
|
|
|
struct last_control {
|
2013-09-02 04:11:38 -05:00
|
|
|
unsigned int lastb :1, /* Is this command 'lastb' */
|
|
|
|
extended :1, /* Lots of info */
|
|
|
|
showhost :1, /* Show hostname */
|
|
|
|
altlist :1, /* Hostname at the end */
|
|
|
|
usedns :1, /* Use DNS to lookup the hostname */
|
2014-05-08 10:45:31 -05:00
|
|
|
useip :1; /* Print IP address in number format */
|
2013-08-27 07:15:08 -05:00
|
|
|
|
|
|
|
unsigned int name_len; /* Number of login name characters to print */
|
|
|
|
unsigned int domain_len; /* Number of domain name characters to print */
|
|
|
|
unsigned int maxrecs; /* Maximum number of records to list */
|
|
|
|
|
|
|
|
char **show; /* Match search list */
|
|
|
|
|
2014-04-23 17:32:11 -05:00
|
|
|
struct timeval boot_time; /* system boot time */
|
2013-08-27 07:15:08 -05:00
|
|
|
time_t since; /* at what time to start displaying the file */
|
|
|
|
time_t until; /* at what time to stop displaying the file */
|
|
|
|
time_t present; /* who where present at time_t */
|
2013-08-27 07:48:27 -05:00
|
|
|
unsigned int time_fmt; /* time format */
|
2013-08-27 07:15:08 -05:00
|
|
|
};
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/* Double linked list of struct utmp's */
|
|
|
|
struct utmplist {
|
2013-08-12 05:26:03 -05:00
|
|
|
struct utmp ut;
|
|
|
|
struct utmplist *next;
|
|
|
|
struct utmplist *prev;
|
2013-08-06 11:19:37 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Types of listing */
|
2013-08-17 13:15:15 -05:00
|
|
|
enum {
|
|
|
|
R_CRASH = 1, /* No logout record, system boot in between */
|
|
|
|
R_DOWN, /* System brought down in decent way */
|
|
|
|
R_NORMAL, /* Normal */
|
|
|
|
R_NOW, /* Still logged in */
|
|
|
|
R_REBOOT, /* Reboot record. */
|
|
|
|
R_PHANTOM, /* No logout record but session is stale. */
|
|
|
|
R_TIMECHANGE /* NEW_TIME or OLD_TIME */
|
|
|
|
};
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2013-08-27 07:48:27 -05:00
|
|
|
enum {
|
|
|
|
LAST_TIMEFTM_NONE = 0,
|
|
|
|
LAST_TIMEFTM_SHORT_CTIME,
|
|
|
|
LAST_TIMEFTM_FULL_CTIME,
|
|
|
|
LAST_TIMEFTM_ISO8601
|
|
|
|
};
|
|
|
|
|
2013-09-02 04:28:43 -05:00
|
|
|
struct last_timefmt {
|
|
|
|
const char *name;
|
2013-08-27 07:48:27 -05:00
|
|
|
int in;
|
|
|
|
int out;
|
|
|
|
};
|
|
|
|
|
2013-09-02 04:28:43 -05:00
|
|
|
static struct last_timefmt timefmts[] = {
|
|
|
|
[LAST_TIMEFTM_NONE] = { "notime", 0, 0 },
|
|
|
|
[LAST_TIMEFTM_SHORT_CTIME] = { "short", 16, 7},
|
|
|
|
[LAST_TIMEFTM_FULL_CTIME] = { "full", 24, 26},
|
|
|
|
[LAST_TIMEFTM_ISO8601] = { "iso", 24, 26}
|
2013-08-27 07:48:27 -05:00
|
|
|
};
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/* Global variables */
|
2013-08-17 13:15:19 -05:00
|
|
|
static unsigned int recsdone; /* Number of records listed */
|
2013-08-13 03:22:50 -05:00
|
|
|
static time_t lastdate; /* Last date we've seen */
|
2015-03-24 06:22:13 -05:00
|
|
|
static time_t currentdate; /* date when we started processing the file */
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2013-08-27 07:48:27 -05:00
|
|
|
/* --time-format=option parser */
|
|
|
|
static int which_time_format(const char *optarg)
|
|
|
|
{
|
2013-09-02 04:28:43 -05:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
|
|
|
|
if (strcmp(timefmts[i].name, optarg) == 0)
|
|
|
|
return i;
|
|
|
|
}
|
2013-08-27 07:48:27 -05:00
|
|
|
errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
|
|
|
|
}
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* Read one utmp entry, return in new format.
|
|
|
|
* Automatically reposition file pointer.
|
|
|
|
*/
|
2015-03-13 05:47:00 -05:00
|
|
|
static int uread(FILE *fp, struct utmp *u, int *quit, const char *filename)
|
2013-08-06 11:19:37 -05:00
|
|
|
{
|
|
|
|
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) {
|
2015-03-13 05:47:00 -05:00
|
|
|
warn(_("seek on %s failed"), filename);
|
2013-08-06 11:19:37 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
bpos = (int)(fpos - o);
|
|
|
|
if (fread(buf, bpos, 1, fp) != 1) {
|
2015-03-13 05:47:00 -05:00
|
|
|
warn(_("cannot read %s"), filename);
|
2013-08-06 11:19:37 -05:00
|
|
|
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;
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
2010-12-01 10:18:11 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* 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) {
|
2015-03-13 05:47:00 -05:00
|
|
|
warn(_("seek on %s failed"), filename);
|
2013-08-06 11:19:37 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read another UCHUNKSIZE bytes.
|
|
|
|
*/
|
|
|
|
if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
|
2015-03-13 05:47:00 -05:00
|
|
|
warn(_("cannot read %s"), filename);
|
2013-08-06 11:19:37 -05:00
|
|
|
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;
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-08-06 11:19:37 -05:00
|
|
|
* Print a short date.
|
2006-12-06 17:25:32 -06:00
|
|
|
*/
|
2013-08-06 11:19:37 -05:00
|
|
|
static char *showdate(void)
|
|
|
|
{
|
|
|
|
char *s = ctime(&lastdate);
|
|
|
|
s[16] = 0;
|
|
|
|
return s;
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-08-06 11:19:37 -05:00
|
|
|
* SIGINT handler
|
2006-12-06 17:25:32 -06:00
|
|
|
*/
|
2013-08-06 11:19:37 -05:00
|
|
|
static void int_handler(int sig __attribute__((unused)))
|
|
|
|
{
|
2013-08-12 07:10:42 -05:00
|
|
|
errx(EXIT_FAILURE, _("Interrupted %s"), showdate());
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
2009-11-20 07:46:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* SIGQUIT handler
|
|
|
|
*/
|
|
|
|
static void quit_handler(int sig __attribute__((unused)))
|
|
|
|
{
|
2013-08-12 07:10:42 -05:00
|
|
|
warnx(_("Interrupted %s"), showdate());
|
2013-08-06 11:19:37 -05:00
|
|
|
signal(SIGQUIT, quit_handler);
|
|
|
|
}
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* 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;
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
flags = useip ? NI_NUMERICHOST : 0;
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* 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);
|
2006-12-06 17:25:46 -06:00
|
|
|
}
|
2013-08-06 11:19:37 -05:00
|
|
|
|
|
|
|
return getnameinfo(sa, salen, result, size, NULL, 0, flags);
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
|
|
|
|
2013-08-27 07:48:27 -05:00
|
|
|
static int time_formatter(const struct last_control *ctl, char *dst,
|
|
|
|
size_t dlen, time_t *when, int pos)
|
|
|
|
{
|
|
|
|
struct tm *tm;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
switch (ctl->time_fmt) {
|
|
|
|
case LAST_TIMEFTM_NONE:
|
|
|
|
*dst = 0;
|
|
|
|
break;
|
|
|
|
case LAST_TIMEFTM_SHORT_CTIME:
|
|
|
|
if (pos == 0)
|
|
|
|
ret = sprintf(dst, "%s", ctime(when));
|
|
|
|
else {
|
|
|
|
tm = localtime(when);
|
|
|
|
if (!strftime(dst, dlen, "- %H:%M", tm))
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LAST_TIMEFTM_FULL_CTIME:
|
|
|
|
if (pos == 0)
|
|
|
|
ret = sprintf(dst, "%s", ctime(when));
|
|
|
|
else
|
|
|
|
ret = sprintf(dst, "- %s", ctime(when));
|
|
|
|
break;
|
|
|
|
case LAST_TIMEFTM_ISO8601:
|
|
|
|
tm = localtime(when);
|
|
|
|
if (pos == 0) {
|
|
|
|
if (!strftime(dst, dlen, "%Y-%m-%dT%H:%M:%S%z", tm))
|
|
|
|
ret = -1;
|
|
|
|
} else if (!strftime(dst, dlen, "- %Y-%m-%dT%H:%M:%S%z", tm))
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-08-28 12:31:48 -05:00
|
|
|
/*
|
|
|
|
* Remove trailing spaces from a string.
|
|
|
|
*/
|
|
|
|
static void trim_trailing_spaces(char *s)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
for (p = s; *p; ++p)
|
|
|
|
continue;
|
|
|
|
while (p > s && isspace(*--p))
|
|
|
|
continue;
|
|
|
|
if (p > s)
|
|
|
|
++p;
|
|
|
|
*p++ = '\n';
|
|
|
|
*p = '\0';
|
|
|
|
}
|
|
|
|
|
2006-12-06 17:25:32 -06:00
|
|
|
/*
|
2013-08-06 11:19:37 -05:00
|
|
|
* Show one line of information on screen
|
2006-12-06 17:25:32 -06:00
|
|
|
*/
|
2014-08-16 04:38:04 -05:00
|
|
|
static int list(const struct last_control *ctl, struct utmp *p, time_t logout_time, int what)
|
2013-08-06 11:19:37 -05:00
|
|
|
{
|
2015-03-24 06:22:13 -05:00
|
|
|
time_t secs, utmp_time;
|
2013-08-27 07:48:27 -05:00
|
|
|
char logintime[LAST_TIMESTAMP_LEN];
|
|
|
|
char logouttime[LAST_TIMESTAMP_LEN];
|
|
|
|
char length[LAST_TIMESTAMP_LEN];
|
2013-08-06 11:19:37 -05:00
|
|
|
char final[512];
|
|
|
|
char utline[UT_LINESIZE+1];
|
|
|
|
char domain[256];
|
2013-08-17 13:15:17 -05:00
|
|
|
char *s;
|
2013-08-06 11:19:37 -05:00
|
|
|
int mins, hours, days;
|
|
|
|
int r, len;
|
2013-09-02 04:28:43 -05:00
|
|
|
struct last_timefmt *fmt;
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
/*
|
2014-07-13 11:17:51 -05:00
|
|
|
* Is this something we want to show?
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->show) {
|
2013-08-17 13:15:17 -05:00
|
|
|
char **walk;
|
2013-08-27 07:15:08 -05:00
|
|
|
for (walk = ctl->show; *walk; walk++) {
|
2013-08-29 01:46:46 -05:00
|
|
|
if (strncmp(p->ut_user, *walk, UT_NAMESIZE) == 0 ||
|
2013-08-06 11:19:37 -05:00
|
|
|
strcmp(utline, *walk) == 0 ||
|
|
|
|
(strncmp(utline, "tty", 3) == 0 &&
|
|
|
|
strcmp(utline + 3, *walk) == 0)) break;
|
|
|
|
}
|
|
|
|
if (*walk == NULL) return 0;
|
2006-12-06 17:25:39 -06:00
|
|
|
}
|
2013-08-06 11:19:37 -05:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate times
|
|
|
|
*/
|
2014-08-16 04:38:04 -05:00
|
|
|
utmp_time = p->UL_UT_TIME;
|
2013-08-17 13:15:21 -05:00
|
|
|
|
2014-08-16 04:38:04 -05:00
|
|
|
if (ctl->present) {
|
|
|
|
if (ctl->present < utmp_time)
|
|
|
|
return 0;
|
|
|
|
if (0 < logout_time && logout_time < ctl->present)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (time_formatter(ctl, &logintime[0], sizeof(logintime), &utmp_time, 0) < 0 ||
|
|
|
|
time_formatter(ctl, &logouttime[0], sizeof(logouttime), &logout_time, 1) < 0)
|
2013-08-27 07:48:27 -05:00
|
|
|
errx(EXIT_FAILURE, _("preallocation size exceeded"));
|
|
|
|
|
2014-08-16 04:38:04 -05:00
|
|
|
secs = logout_time - utmp_time;
|
2013-08-06 11:19:37 -05:00
|
|
|
mins = (secs / 60) % 60;
|
|
|
|
hours = (secs / 3600) % 24;
|
|
|
|
days = secs / 86400;
|
2013-08-28 12:53:22 -05:00
|
|
|
|
2015-03-24 06:22:13 -05:00
|
|
|
if (logout_time == currentdate) {
|
2014-05-08 10:45:31 -05:00
|
|
|
if (ctl->time_fmt > LAST_TIMEFTM_SHORT_CTIME) {
|
2013-08-28 12:53:22 -05:00
|
|
|
sprintf(logouttime, " still running");
|
2014-05-07 07:40:05 -05:00
|
|
|
length[0] = 0;
|
|
|
|
} else {
|
2013-08-28 12:53:22 -05:00
|
|
|
sprintf(logouttime, " still");
|
|
|
|
sprintf(length, "running");
|
|
|
|
}
|
|
|
|
} else if (days)
|
2013-08-06 11:19:37 -05:00
|
|
|
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:
|
2014-05-08 11:48:21 -05:00
|
|
|
if (ctl->time_fmt > LAST_TIMEFTM_SHORT_CTIME) {
|
2013-08-06 11:19:37 -05:00
|
|
|
sprintf(logouttime, " still logged in");
|
2014-05-08 11:48:21 -05:00
|
|
|
length[0] = 0;
|
|
|
|
} else {
|
2013-08-06 11:19:37 -05:00
|
|
|
sprintf(logouttime, " still");
|
|
|
|
sprintf(length, "logged in");
|
|
|
|
}
|
2006-12-06 17:25:32 -06:00
|
|
|
break;
|
2013-08-06 11:19:37 -05:00
|
|
|
case R_PHANTOM:
|
2014-05-08 11:48:21 -05:00
|
|
|
if (ctl->time_fmt > LAST_TIMEFTM_SHORT_CTIME) {
|
2013-08-06 11:19:37 -05:00
|
|
|
sprintf(logouttime, " gone - no logout");
|
2014-05-08 11:48:21 -05:00
|
|
|
length[0] = 0;
|
2014-05-08 12:01:03 -05:00
|
|
|
} else if (ctl->time_fmt == LAST_TIMEFTM_SHORT_CTIME) {
|
2013-08-06 11:19:37 -05:00
|
|
|
sprintf(logouttime, " gone");
|
|
|
|
sprintf(length, "- no logout");
|
2014-05-08 12:01:03 -05:00
|
|
|
} else {
|
|
|
|
logouttime[0] = 0;
|
|
|
|
sprintf(length, "no logout");
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case R_REBOOT:
|
2006-12-06 17:25:32 -06:00
|
|
|
break;
|
2013-08-06 11:19:37 -05:00
|
|
|
case R_TIMECHANGE:
|
|
|
|
logouttime[0] = 0;
|
|
|
|
length[0] = 0;
|
2006-12-06 17:25:32 -06:00
|
|
|
break;
|
2013-08-06 11:19:37 -05:00
|
|
|
case R_NORMAL:
|
2006-12-06 17:25:32 -06:00
|
|
|
break;
|
2013-08-17 13:15:14 -05:00
|
|
|
default:
|
|
|
|
abort();
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Look up host with DNS if needed.
|
|
|
|
*/
|
|
|
|
r = -1;
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->usedns || ctl->useip)
|
|
|
|
r = dns_lookup(domain, sizeof(domain), ctl->useip, p->ut_addr_v6);
|
2013-08-06 11:19:37 -05:00
|
|
|
if (r < 0) {
|
|
|
|
len = UT_HOSTSIZE;
|
|
|
|
if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
|
|
|
|
domain[0] = 0;
|
|
|
|
strncat(domain, p->ut_host, len);
|
|
|
|
}
|
|
|
|
|
2013-09-02 04:28:43 -05:00
|
|
|
fmt = &timefmts[ctl->time_fmt];
|
|
|
|
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->showhost) {
|
|
|
|
if (!ctl->altlist) {
|
2013-08-06 11:19:37 -05:00
|
|
|
len = snprintf(final, sizeof(final),
|
2013-08-28 09:37:46 -05:00
|
|
|
"%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
|
2013-08-29 01:46:46 -05:00
|
|
|
ctl->name_len, p->ut_user, utline,
|
2013-08-27 07:48:27 -05:00
|
|
|
ctl->domain_len, domain,
|
2013-09-02 04:28:43 -05:00
|
|
|
fmt->in, fmt->in, logintime, fmt->out, fmt->out,
|
|
|
|
logouttime, length);
|
2013-08-06 11:19:37 -05:00
|
|
|
} else {
|
|
|
|
len = snprintf(final, sizeof(final),
|
2013-08-27 07:48:27 -05:00
|
|
|
"%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
|
2013-08-29 01:46:46 -05:00
|
|
|
ctl->name_len, p->ut_user, utline,
|
2013-09-02 04:28:43 -05:00
|
|
|
fmt->in, fmt->in, logintime, fmt->out, fmt->out,
|
|
|
|
logouttime, length, domain);
|
2011-11-19 14:52:38 -06:00
|
|
|
}
|
2013-08-06 11:19:37 -05:00
|
|
|
} else
|
|
|
|
len = snprintf(final, sizeof(final),
|
2013-08-28 09:37:46 -05:00
|
|
|
"%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
|
2013-08-29 01:46:46 -05:00
|
|
|
ctl->name_len, p->ut_user, utline,
|
2013-09-02 04:28:43 -05:00
|
|
|
fmt->in, fmt->in, logintime, fmt->out, fmt->out,
|
|
|
|
logouttime, length);
|
2013-08-06 11:19:37 -05:00
|
|
|
|
|
|
|
#if defined(__GLIBC__)
|
|
|
|
# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
|
|
|
|
final[sizeof(final)-1] = '\0';
|
|
|
|
# endif
|
|
|
|
#endif
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-28 12:31:48 -05:00
|
|
|
trim_trailing_spaces(final);
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
|
|
|
* Print out "final" string safely.
|
|
|
|
*/
|
2013-08-17 13:15:12 -05:00
|
|
|
for (s = final; *s; s++)
|
2014-03-19 07:25:45 -05:00
|
|
|
fputc_careful(*s, stdout, '*');
|
2013-08-06 11:19:37 -05:00
|
|
|
|
|
|
|
if (len < 0 || (size_t)len >= sizeof(final))
|
|
|
|
putchar('\n');
|
|
|
|
|
|
|
|
recsdone++;
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->maxrecs && ctl->maxrecs <= recsdone)
|
2013-08-06 11:19:37 -05:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2015-10-06 16:18:27 -05:00
|
|
|
static void __attribute__((__noreturn__)) usage(const struct last_control *ctl, FILE *out)
|
2013-08-06 11:19:37 -05:00
|
|
|
{
|
2013-08-12 06:39:11 -05:00
|
|
|
fputs(USAGE_HEADER, out);
|
|
|
|
fprintf(out, _(
|
2013-08-23 05:01:01 -05:00
|
|
|
" %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
|
2013-08-12 06:39:11 -05:00
|
|
|
|
2014-12-22 15:57:17 -06:00
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
|
|
fputs(_("Show a listing of last logged in users.\n"), out);
|
|
|
|
|
2013-08-12 06:39:11 -05:00
|
|
|
fputs(USAGE_OPTIONS, out);
|
|
|
|
fputs(_(" -<number> how many lines to show\n"), out);
|
|
|
|
fputs(_(" -a, --hostlast display hostnames in the last column\n"), out);
|
|
|
|
fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out);
|
|
|
|
fprintf(out,
|
2015-10-06 16:18:27 -05:00
|
|
|
_(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
|
2013-08-12 06:39:11 -05:00
|
|
|
fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out);
|
|
|
|
fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out);
|
|
|
|
fputs(_(" -n, --limit <number> how many lines to show\n"), out);
|
|
|
|
fputs(_(" -R, --nohostname don't display the hostname field\n"), out);
|
2013-08-26 15:52:54 -05:00
|
|
|
fputs(_(" -s, --since <time> display the lines since the specified time\n"), out);
|
|
|
|
fputs(_(" -t, --until <time> display the lines until the specified time\n"), out);
|
2014-07-22 15:56:27 -05:00
|
|
|
fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
|
2013-08-12 06:39:11 -05:00
|
|
|
fputs(_(" -w, --fullnames display full user and domain names\n"), out);
|
|
|
|
fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out);
|
2014-07-22 15:56:27 -05:00
|
|
|
fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
|
|
|
|
" notime|short|full|iso\n"), out);
|
2013-08-12 06:39:11 -05:00
|
|
|
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
|
|
fputs(USAGE_HELP, out);
|
|
|
|
fputs(USAGE_VERSION, out);
|
2013-08-17 18:40:52 -05:00
|
|
|
fprintf(out, USAGE_MAN_TAIL("last(1)"));
|
2013-08-12 06:39:11 -05:00
|
|
|
|
|
|
|
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
|
|
|
|
2014-04-23 17:32:11 -05:00
|
|
|
static int is_phantom(const struct last_control *ctl, struct utmp *ut)
|
2014-01-21 16:05:05 -06:00
|
|
|
{
|
|
|
|
struct passwd *pw;
|
|
|
|
char path[32];
|
2014-04-23 17:32:11 -05:00
|
|
|
int ret = 0;
|
2014-01-21 16:05:05 -06:00
|
|
|
|
2014-04-23 17:32:11 -05:00
|
|
|
if (ut->UL_UT_TIME < ctl->boot_time.tv_sec)
|
|
|
|
return 1;
|
2014-01-21 16:05:05 -06:00
|
|
|
pw = getpwnam(ut->ut_name);
|
|
|
|
if (!pw)
|
|
|
|
return 1;
|
|
|
|
sprintf(path, "/proc/%u/loginuid", ut->ut_pid);
|
2014-08-11 17:04:26 -05:00
|
|
|
if (access(path, R_OK) == 0) {
|
|
|
|
unsigned int loginuid;
|
|
|
|
FILE *f = NULL;
|
|
|
|
|
|
|
|
if (!(f = fopen(path, "r")))
|
|
|
|
return 1;
|
|
|
|
if (fscanf(f, "%u", &loginuid) != 1)
|
|
|
|
ret = 1;
|
|
|
|
fclose(f);
|
|
|
|
if (!ret && pw->pw_uid != loginuid)
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
struct stat st;
|
2014-07-17 07:39:11 -05:00
|
|
|
|
2014-08-11 17:04:26 -05:00
|
|
|
sprintf(path, "/dev/%s", ut->ut_line);
|
|
|
|
if (stat(path, &st))
|
|
|
|
return 1;
|
|
|
|
if (pw->pw_uid != st.st_uid)
|
|
|
|
return 1;
|
|
|
|
}
|
2014-01-21 16:05:05 -06:00
|
|
|
return ret;
|
|
|
|
}
|
2013-08-12 06:39:11 -05:00
|
|
|
|
2015-03-13 05:47:00 -05:00
|
|
|
static void process_wtmp_file(const struct last_control *ctl,
|
|
|
|
const char *filename)
|
2013-08-06 11:19:37 -05:00
|
|
|
{
|
2013-08-13 03:22:50 -05:00
|
|
|
FILE *fp; /* Filepointer of wtmp file */
|
|
|
|
|
|
|
|
struct utmp ut; /* Current utmp entry */
|
2015-03-13 05:13:26 -05:00
|
|
|
struct utmplist *ulist = NULL; /* All entries */
|
2013-08-13 03:22:50 -05:00
|
|
|
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 */
|
|
|
|
|
|
|
|
time(&lastdown);
|
|
|
|
/*
|
|
|
|
* Fill in 'lastdate'
|
|
|
|
*/
|
2015-03-24 06:22:13 -05:00
|
|
|
lastdate = currentdate = lastrch = lastdown;
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2013-08-13 03:22:50 -05:00
|
|
|
/*
|
|
|
|
* Install signal handlers
|
|
|
|
*/
|
|
|
|
signal(SIGINT, int_handler);
|
|
|
|
signal(SIGQUIT, quit_handler);
|
2013-08-06 11:19:37 -05:00
|
|
|
|
2013-08-13 03:22:50 -05:00
|
|
|
/*
|
|
|
|
* Open the utmp file
|
|
|
|
*/
|
2015-03-13 05:13:26 -05:00
|
|
|
if ((fp = fopen(filename, "r")) == NULL)
|
|
|
|
err(EXIT_FAILURE, _("cannot open %s"), filename);
|
2006-12-06 17:25:32 -06:00
|
|
|
|
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* Optimize the buffer size.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
2013-08-13 03:22:50 -05:00
|
|
|
setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* Read first structure to capture the time field
|
2006-12-06 17:25:32 -06:00
|
|
|
*/
|
2015-03-13 05:47:00 -05:00
|
|
|
if (uread(fp, &ut, NULL, filename) == 1)
|
2013-08-29 01:46:46 -05:00
|
|
|
begintime = ut.UL_UT_TIME;
|
2013-08-06 11:19:37 -05:00
|
|
|
else {
|
2013-10-01 09:59:43 -05:00
|
|
|
if (fstat(fileno(fp), &st) != 0)
|
2015-03-13 05:13:26 -05:00
|
|
|
err(EXIT_FAILURE, _("stat of %s failed"), filename);
|
2013-08-13 03:22:50 -05:00
|
|
|
begintime = st.st_ctime;
|
|
|
|
quit = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Go to end of file minus one structure
|
|
|
|
* and/or initialize utmp reading code.
|
|
|
|
*/
|
2015-03-13 05:47:00 -05:00
|
|
|
uread(fp, NULL, NULL, filename);
|
2013-08-13 03:22:50 -05:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Read struct after struct backwards from the file.
|
|
|
|
*/
|
|
|
|
while (!quit) {
|
|
|
|
|
2015-03-13 05:47:00 -05:00
|
|
|
if (uread(fp, &ut, &quit, filename) != 1)
|
2013-08-13 03:22:50 -05:00
|
|
|
break;
|
|
|
|
|
2013-08-29 01:46:46 -05:00
|
|
|
if (ctl->since && ut.UL_UT_TIME < ctl->since)
|
2013-08-26 15:52:54 -05:00
|
|
|
continue;
|
|
|
|
|
2013-08-29 01:46:46 -05:00
|
|
|
if (ctl->until && ctl->until < ut.UL_UT_TIME)
|
2013-08-13 03:22:50 -05:00
|
|
|
continue;
|
|
|
|
|
2013-08-29 01:46:46 -05:00
|
|
|
lastdate = ut.UL_UT_TIME;
|
2013-08-13 03:22:50 -05:00
|
|
|
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->lastb) {
|
2013-08-29 01:46:46 -05:00
|
|
|
quit = list(ctl, &ut, ut.UL_UT_TIME, R_NORMAL);
|
2013-08-13 03:22:50 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* Set ut_type to the correct type.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
2013-08-13 03:22:50 -05:00
|
|
|
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*/
|
2013-08-06 11:19:37 -05:00
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* For stupid old applications that don't fill in
|
|
|
|
* ut_type correctly.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
2013-08-13 03:22:50 -05:00
|
|
|
else {
|
|
|
|
if (ut.ut_type != DEAD_PROCESS &&
|
2013-08-29 01:46:46 -05:00
|
|
|
ut.ut_user[0] && ut.ut_line[0] &&
|
|
|
|
strcmp(ut.ut_user, "LOGIN") != 0)
|
2013-08-13 03:22:50 -05:00
|
|
|
ut.ut_type = USER_PROCESS;
|
|
|
|
/*
|
|
|
|
* Even worse, applications that write ghost
|
|
|
|
* entries: ut_type set to USER_PROCESS but
|
2013-08-29 01:46:46 -05:00
|
|
|
* empty ut_user...
|
2013-08-13 03:22:50 -05:00
|
|
|
*/
|
2013-08-29 01:46:46 -05:00
|
|
|
if (ut.ut_user[0] == 0)
|
2013-08-13 03:22:50 -05:00
|
|
|
ut.ut_type = DEAD_PROCESS;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clock changes.
|
|
|
|
*/
|
2013-08-29 01:46:46 -05:00
|
|
|
if (strcmp(ut.ut_user, "date") == 0) {
|
2013-08-13 03:22:50 -05:00
|
|
|
if (ut.ut_line[0] == '|')
|
|
|
|
ut.ut_type = OLD_TIME;
|
|
|
|
if (ut.ut_line[0] == '{')
|
|
|
|
ut.ut_type = NEW_TIME;
|
|
|
|
}
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|
2013-08-06 11:19:37 -05:00
|
|
|
#endif
|
2013-08-13 03:22:50 -05:00
|
|
|
switch (ut.ut_type) {
|
2013-08-06 11:19:37 -05:00
|
|
|
case SHUTDOWN_TIME:
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->extended) {
|
2013-08-06 11:19:37 -05:00
|
|
|
strcpy(ut.ut_line, "system down");
|
2013-08-27 07:15:08 -05:00
|
|
|
quit = list(ctl, &ut, lastboot, R_NORMAL);
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
2013-08-29 01:46:46 -05:00
|
|
|
lastdown = lastrch = ut.UL_UT_TIME;
|
2013-08-06 11:19:37 -05:00
|
|
|
down = 1;
|
|
|
|
break;
|
|
|
|
case OLD_TIME:
|
|
|
|
case NEW_TIME:
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->extended) {
|
2013-08-06 11:19:37 -05:00
|
|
|
strcpy(ut.ut_line,
|
|
|
|
ut.ut_type == NEW_TIME ? "new time" :
|
|
|
|
"old time");
|
2013-08-27 07:15:08 -05:00
|
|
|
quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BOOT_TIME:
|
|
|
|
strcpy(ut.ut_line, "system boot");
|
2013-08-27 07:15:08 -05:00
|
|
|
quit = list(ctl, &ut, lastdown, R_REBOOT);
|
2013-08-29 01:46:46 -05:00
|
|
|
lastboot = ut.UL_UT_TIME;
|
2013-08-06 11:19:37 -05:00
|
|
|
down = 1;
|
|
|
|
break;
|
|
|
|
case RUN_LVL:
|
|
|
|
x = ut.ut_pid & 255;
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl->extended) {
|
2013-08-06 11:19:37 -05:00
|
|
|
sprintf(ut.ut_line, "(to lvl %c)", x);
|
2013-08-27 07:15:08 -05:00
|
|
|
quit = list(ctl, &ut, lastrch, R_NORMAL);
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
if (x == '0' || x == '6') {
|
2013-08-29 01:46:46 -05:00
|
|
|
lastdown = ut.UL_UT_TIME;
|
2013-08-06 11:19:37 -05:00
|
|
|
down = 1;
|
|
|
|
ut.ut_type = SHUTDOWN_TIME;
|
|
|
|
}
|
2013-08-29 01:46:46 -05:00
|
|
|
lastrch = ut.UL_UT_TIME;
|
2013-08-06 11:19:37 -05:00
|
|
|
break;
|
2006-12-06 17:25:32 -06:00
|
|
|
|
2013-08-06 11:19:37 -05:00
|
|
|
case USER_PROCESS:
|
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* This was a login - show the first matching
|
|
|
|
* logout record and delete all records with
|
|
|
|
* the same ut_line.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
|
|
|
c = 0;
|
2015-03-13 05:13:26 -05:00
|
|
|
for (p = ulist; p; p = next) {
|
2013-08-06 11:19:37 -05:00
|
|
|
next = p->next;
|
|
|
|
if (strncmp(p->ut.ut_line, ut.ut_line,
|
|
|
|
UT_LINESIZE) == 0) {
|
|
|
|
/* Show it */
|
|
|
|
if (c == 0) {
|
2013-08-29 01:46:46 -05:00
|
|
|
quit = list(ctl, &ut, p->ut.UL_UT_TIME, R_NORMAL);
|
2013-08-06 11:19:37 -05:00
|
|
|
c = 1;
|
|
|
|
}
|
2015-03-13 05:13:26 -05:00
|
|
|
if (p->next)
|
|
|
|
p->next->prev = p->prev;
|
2013-08-06 11:19:37 -05:00
|
|
|
if (p->prev)
|
|
|
|
p->prev->next = p->next;
|
|
|
|
else
|
2015-03-13 05:13:26 -05:00
|
|
|
ulist = p->next;
|
2013-08-06 11:19:37 -05:00
|
|
|
free(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* Not found? Then crashed, down, still
|
|
|
|
* logged in, or missing logout record.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
|
|
|
if (c == 0) {
|
2013-09-02 04:11:38 -05:00
|
|
|
if (!lastboot) {
|
2013-08-06 11:19:37 -05:00
|
|
|
c = R_NOW;
|
|
|
|
/* Is process still alive? */
|
2014-04-23 17:32:11 -05:00
|
|
|
if (is_phantom(ctl, &ut))
|
2013-08-06 11:19:37 -05:00
|
|
|
c = R_PHANTOM;
|
|
|
|
} else
|
|
|
|
c = whydown;
|
2013-08-27 07:15:08 -05:00
|
|
|
quit = list(ctl, &ut, lastboot, c);
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
/* FALLTHRU */
|
|
|
|
|
|
|
|
case DEAD_PROCESS:
|
|
|
|
/*
|
2013-08-13 03:22:50 -05:00
|
|
|
* Just store the data if it is
|
|
|
|
* interesting enough.
|
2013-08-06 11:19:37 -05:00
|
|
|
*/
|
|
|
|
if (ut.ut_line[0] == 0)
|
|
|
|
break;
|
2013-08-12 07:10:42 -05:00
|
|
|
p = xmalloc(sizeof(struct utmplist));
|
2013-08-06 11:19:37 -05:00
|
|
|
memcpy(&p->ut, &ut, sizeof(struct utmp));
|
2015-03-13 05:13:26 -05:00
|
|
|
p->next = ulist;
|
2013-08-06 11:19:37 -05:00
|
|
|
p->prev = NULL;
|
2015-03-13 05:13:26 -05:00
|
|
|
if (ulist)
|
|
|
|
ulist->prev = p;
|
|
|
|
ulist = p;
|
2013-08-06 11:19:37 -05:00
|
|
|
break;
|
|
|
|
|
2013-08-17 13:15:14 -05:00
|
|
|
case EMPTY:
|
|
|
|
case INIT_PROCESS:
|
|
|
|
case LOGIN_PROCESS:
|
|
|
|
case ACCOUNTING:
|
|
|
|
/* ignored ut_types */
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
warnx("unrecogized ut_type: %d", ut.ut_type);
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
2013-08-13 03:22:50 -05:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we saw a shutdown/reboot record we can remove
|
2015-03-13 05:13:26 -05:00
|
|
|
* the entire current ulist.
|
2013-08-13 03:22:50 -05:00
|
|
|
*/
|
|
|
|
if (down) {
|
2013-08-29 01:46:46 -05:00
|
|
|
lastboot = ut.UL_UT_TIME;
|
2013-08-13 03:22:50 -05:00
|
|
|
whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
|
2015-03-13 05:13:26 -05:00
|
|
|
for (p = ulist; p; p = next) {
|
2013-08-13 03:22:50 -05:00
|
|
|
next = p->next;
|
|
|
|
free(p);
|
|
|
|
}
|
2015-03-13 05:13:26 -05:00
|
|
|
ulist = NULL;
|
2013-08-13 03:22:50 -05:00
|
|
|
down = 0;
|
|
|
|
}
|
2013-08-06 11:19:37 -05:00
|
|
|
}
|
|
|
|
|
2015-03-13 05:13:26 -05:00
|
|
|
printf(_("\n%s begins %s"), basename(filename), ctime(&begintime));
|
2013-08-13 03:22:50 -05:00
|
|
|
fclose(fp);
|
2015-03-13 05:13:26 -05:00
|
|
|
|
|
|
|
for (p = ulist; p; p = next) {
|
2013-09-08 11:09:06 -05:00
|
|
|
next = p->next;
|
|
|
|
free(p);
|
|
|
|
}
|
2013-08-17 13:15:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
2013-08-27 07:15:08 -05:00
|
|
|
struct last_control ctl = {
|
|
|
|
.showhost = TRUE,
|
|
|
|
.name_len = LAST_LOGIN_LEN,
|
2013-08-27 07:48:27 -05:00
|
|
|
.time_fmt = LAST_TIMEFTM_SHORT_CTIME,
|
2013-08-27 07:15:08 -05:00
|
|
|
.domain_len = LAST_DOMAIN_LEN
|
|
|
|
};
|
2015-03-13 05:47:00 -05:00
|
|
|
char **files = NULL;
|
|
|
|
size_t i, nfiles = 0;
|
2013-08-27 07:15:08 -05:00
|
|
|
int c;
|
2013-08-26 15:43:05 -05:00
|
|
|
usec_t p;
|
2013-08-17 13:15:20 -05:00
|
|
|
|
2013-08-27 07:48:27 -05:00
|
|
|
enum {
|
|
|
|
OPT_TIME_FORMAT = CHAR_MAX + 1
|
|
|
|
};
|
2013-08-17 13:15:20 -05:00
|
|
|
static const struct option long_opts[] = {
|
|
|
|
{ "limit", required_argument, NULL, 'n' },
|
|
|
|
{ "help", no_argument, NULL, 'h' },
|
|
|
|
{ "file", required_argument, NULL, 'f' },
|
|
|
|
{ "nohostname", no_argument, NULL, 'R' },
|
|
|
|
{ "version", no_argument, NULL, 'V' },
|
|
|
|
{ "hostlast", no_argument, NULL, 'a' },
|
2013-08-26 15:52:54 -05:00
|
|
|
{ "since", required_argument, NULL, 's' },
|
2013-08-17 13:15:20 -05:00
|
|
|
{ "until", required_argument, NULL, 't' },
|
2013-08-17 13:15:21 -05:00
|
|
|
{ "present", required_argument, NULL, 'p' },
|
2013-08-17 13:15:20 -05:00
|
|
|
{ "system", no_argument, NULL, 'x' },
|
|
|
|
{ "dns", no_argument, NULL, 'd' },
|
|
|
|
{ "ip", no_argument, NULL, 'i' },
|
|
|
|
{ "fulltimes", no_argument, NULL, 'F' },
|
|
|
|
{ "fullnames", no_argument, NULL, 'w' },
|
2013-08-27 07:48:27 -05:00
|
|
|
{ "time-format", required_argument, NULL, OPT_TIME_FORMAT },
|
2013-08-17 13:15:20 -05:00
|
|
|
{ NULL, 0, NULL, 0 }
|
|
|
|
};
|
2013-09-02 04:42:44 -05:00
|
|
|
static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */
|
|
|
|
{ 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
|
|
|
|
{ 0 }
|
|
|
|
};
|
|
|
|
int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
|
2013-08-17 13:15:20 -05:00
|
|
|
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
|
|
textdomain(PACKAGE);
|
|
|
|
atexit(close_stdout);
|
2015-10-06 16:18:27 -05:00
|
|
|
/*
|
|
|
|
* Which file do we want to read?
|
|
|
|
*/
|
|
|
|
ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
|
2013-08-17 13:15:20 -05:00
|
|
|
while ((c = getopt_long(argc, argv,
|
2013-08-26 15:52:54 -05:00
|
|
|
"hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) {
|
2013-09-02 04:42:44 -05:00
|
|
|
|
|
|
|
err_exclusive_options(c, long_opts, excl, excl_st);
|
|
|
|
|
2013-08-17 13:15:20 -05:00
|
|
|
switch(c) {
|
|
|
|
case 'h':
|
2015-10-06 16:18:27 -05:00
|
|
|
usage(&ctl, stdout);
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'V':
|
|
|
|
printf(UTIL_LINUX_VERSION);
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
case 'R':
|
2013-09-02 04:11:38 -05:00
|
|
|
ctl.showhost = 0;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'x':
|
2013-09-02 04:11:38 -05:00
|
|
|
ctl.extended = 1;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'n':
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'f':
|
2015-03-13 05:47:00 -05:00
|
|
|
if (!files)
|
|
|
|
files = xmalloc(sizeof(char *) * argc);
|
|
|
|
files[nfiles++] = xstrdup(optarg);
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'd':
|
2013-09-02 04:11:38 -05:00
|
|
|
ctl.usedns = 1;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'i':
|
2013-09-02 04:11:38 -05:00
|
|
|
ctl.useip = 1;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'a':
|
2013-09-02 04:11:38 -05:00
|
|
|
ctl.altlist = 1;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'F':
|
2013-08-27 07:48:27 -05:00
|
|
|
ctl.time_fmt = LAST_TIMEFTM_FULL_CTIME;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
2013-08-17 13:15:21 -05:00
|
|
|
case 'p':
|
2013-08-26 15:43:05 -05:00
|
|
|
if (parse_timestamp(optarg, &p) < 0)
|
2013-08-17 13:15:21 -05:00
|
|
|
errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.present = (time_t) (p / 1000000);
|
2013-08-17 13:15:21 -05:00
|
|
|
break;
|
2013-08-26 15:52:54 -05:00
|
|
|
case 's':
|
|
|
|
if (parse_timestamp(optarg, &p) < 0)
|
|
|
|
errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.since = (time_t) (p / 1000000);
|
2013-08-26 15:52:54 -05:00
|
|
|
break;
|
2013-08-17 13:15:20 -05:00
|
|
|
case 't':
|
2013-08-26 15:43:05 -05:00
|
|
|
if (parse_timestamp(optarg, &p) < 0)
|
2013-08-17 13:15:20 -05:00
|
|
|
errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.until = (time_t) (p / 1000000);
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case 'w':
|
2013-08-27 07:15:08 -05:00
|
|
|
if (ctl.name_len < UT_NAMESIZE)
|
|
|
|
ctl.name_len = UT_NAMESIZE;
|
|
|
|
if (ctl.domain_len < UT_HOSTSIZE)
|
|
|
|
ctl.domain_len = UT_HOSTSIZE;
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
|
|
case '5': case '6': case '7': case '8': case '9':
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
2013-08-27 07:48:27 -05:00
|
|
|
case OPT_TIME_FORMAT:
|
|
|
|
ctl.time_fmt = which_time_format(optarg);
|
|
|
|
break;
|
2013-08-17 13:15:20 -05:00
|
|
|
default:
|
2015-10-06 16:18:27 -05:00
|
|
|
usage(&ctl, stderr);
|
2013-08-17 13:15:20 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind < argc)
|
2013-08-27 07:15:08 -05:00
|
|
|
ctl.show = argv + optind;
|
2013-08-17 13:15:20 -05:00
|
|
|
|
2015-03-13 05:47:00 -05:00
|
|
|
if (!files) {
|
|
|
|
files = xmalloc(sizeof(char *));
|
|
|
|
files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
|
2013-08-17 13:15:20 -05:00
|
|
|
}
|
|
|
|
|
2015-03-13 05:47:00 -05:00
|
|
|
for (i = 0; i < nfiles; i++) {
|
2014-04-23 17:32:11 -05:00
|
|
|
get_boot_time(&ctl.boot_time);
|
2015-03-13 05:47:00 -05:00
|
|
|
process_wtmp_file(&ctl, files[i]);
|
|
|
|
free(files[i]);
|
2013-08-17 13:15:20 -05:00
|
|
|
}
|
2015-03-13 05:47:00 -05:00
|
|
|
free(files);
|
2013-08-13 03:22:50 -05:00
|
|
|
return EXIT_SUCCESS;
|
2006-12-06 17:25:32 -06:00
|
|
|
}
|