0ca58ca151
Make that change no-op for C/POSIX locale just like for UTF-8 that it superset of US-ASCII. MFC after: 2 weeks X-MFC-With: r351413
629 lines
15 KiB
C
629 lines
15 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 1987, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
* Copyright (c) 2018 Philip Paeps
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
static const char copyright[] =
|
|
"@(#) Copyright (c) 1987, 1993, 1994\n\
|
|
The Regents of the University of California. All rights reserved.\n";
|
|
#endif /* not lint */
|
|
|
|
#ifndef lint
|
|
static const char sccsid[] = "@(#)last.c 8.2 (Berkeley) 4/2/94";
|
|
#endif /* not lint */
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/capsicum.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <capsicum_helpers.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <langinfo.h>
|
|
#include <locale.h>
|
|
#include <paths.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <timeconv.h>
|
|
#include <unistd.h>
|
|
#include <utmpx.h>
|
|
|
|
#include <libxo/xo.h>
|
|
|
|
#define NO 0 /* false/no */
|
|
#define YES 1 /* true/yes */
|
|
#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
|
|
|
|
typedef struct arg {
|
|
char *name; /* argument */
|
|
#define REBOOT_TYPE -1
|
|
#define HOST_TYPE -2
|
|
#define TTY_TYPE -3
|
|
#define USER_TYPE -4
|
|
int type; /* type of arg */
|
|
struct arg *next; /* linked list pointer */
|
|
} ARG;
|
|
static ARG *arglist; /* head of linked list */
|
|
|
|
static SLIST_HEAD(, idtab) idlist;
|
|
|
|
struct idtab {
|
|
time_t logout; /* log out time */
|
|
char id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
|
|
SLIST_ENTRY(idtab) list;
|
|
};
|
|
|
|
static const char *crmsg; /* cause of last reboot */
|
|
static time_t currentout; /* current logout value */
|
|
static long maxrec; /* records to display */
|
|
static const char *file = NULL; /* utx.log file */
|
|
static int noctfix = 0; /* locale is C or UTF-8 */
|
|
static int sflag = 0; /* show delta in seconds */
|
|
static int width = 5; /* show seconds in delta */
|
|
static int yflag; /* show year */
|
|
static int d_first;
|
|
static int snapfound = 0; /* found snapshot entry? */
|
|
static time_t snaptime; /* if != 0, we will only
|
|
* report users logged in
|
|
* at this snapshot time
|
|
*/
|
|
|
|
static void addarg(int, char *);
|
|
static const char *ctf(const char *);
|
|
static time_t dateconv(char *);
|
|
static void doentry(struct utmpx *);
|
|
static void hostconv(char *);
|
|
static void printentry(struct utmpx *, struct idtab *);
|
|
static char *ttyconv(char *);
|
|
static int want(struct utmpx *);
|
|
static void usage(void);
|
|
static void wtmp(void);
|
|
|
|
static const char*
|
|
ctf(const char *fmt) {
|
|
static char buf[31];
|
|
const char *src, *end;
|
|
char *dst;
|
|
|
|
if (noctfix)
|
|
return (fmt);
|
|
|
|
end = buf + sizeof(buf);
|
|
for (src = fmt, dst = buf; dst < end; *dst++ = *src++) {
|
|
if (*src == '\0') {
|
|
*dst = '\0';
|
|
break;
|
|
} else if (*src == '%' && *(src+1) == 's') {
|
|
*dst++ = '%';
|
|
*dst++ = 'h';
|
|
*dst++ = 's';
|
|
strlcpy(dst, src+2, end - dst);
|
|
return (buf);
|
|
}
|
|
}
|
|
return (buf);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
xo_error(
|
|
"usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
|
|
" [-n maxrec] [-t tty] [user ...]\n");
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int ch;
|
|
char *p;
|
|
|
|
(void) setlocale(LC_TIME, "");
|
|
d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
|
|
|
|
(void) setlocale(LC_CTYPE, "");
|
|
p = nl_langinfo(CODESET);
|
|
if (strcmp (p, "UTF-8") == 0 || strcmp (p, "US-ASCII") == 0)
|
|
noctfix = 1;
|
|
|
|
argc = xo_parse_args(argc, argv);
|
|
if (argc < 0)
|
|
exit(1);
|
|
atexit(xo_finish_atexit);
|
|
|
|
maxrec = -1;
|
|
snaptime = 0;
|
|
while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
|
|
switch (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 == -1) {
|
|
p = strchr(argv[optind - 1], ch);
|
|
if (p == NULL)
|
|
p = strchr(argv[optind], ch);
|
|
maxrec = atol(p);
|
|
if (!maxrec)
|
|
exit(0);
|
|
}
|
|
break;
|
|
case 'd':
|
|
snaptime = dateconv(optarg);
|
|
break;
|
|
case 'f':
|
|
file = optarg;
|
|
break;
|
|
case 'h':
|
|
hostconv(optarg);
|
|
addarg(HOST_TYPE, optarg);
|
|
break;
|
|
case 'n':
|
|
errno = 0;
|
|
maxrec = strtol(optarg, &p, 10);
|
|
if (p == optarg || *p != '\0' || errno != 0 ||
|
|
maxrec <= 0)
|
|
xo_errx(1, "%s: bad line count", optarg);
|
|
break;
|
|
case 's':
|
|
sflag++; /* Show delta as seconds */
|
|
break;
|
|
case 't':
|
|
addarg(TTY_TYPE, ttyconv(optarg));
|
|
break;
|
|
case 'w':
|
|
width = 8;
|
|
break;
|
|
case 'y':
|
|
yflag++;
|
|
break;
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
|
|
if (caph_limit_stdio() < 0)
|
|
xo_err(1, "can't limit stdio rights");
|
|
|
|
caph_cache_catpages();
|
|
caph_cache_tzdata();
|
|
|
|
/* Cache UTX database. */
|
|
if (setutxdb(UTXDB_LOG, file) != 0)
|
|
xo_err(1, "%s", file != NULL ? file : "(default utx db)");
|
|
|
|
if (caph_enter() < 0)
|
|
xo_err(1, "cap_enter");
|
|
|
|
if (sflag && width == 8) usage();
|
|
|
|
if (argc) {
|
|
setlinebuf(stdout);
|
|
for (argv += optind; *argv; ++argv) {
|
|
if (strcmp(*argv, "reboot") == 0)
|
|
addarg(REBOOT_TYPE, *argv);
|
|
#define COMPATIBILITY
|
|
#ifdef COMPATIBILITY
|
|
/* code to allow "last p5" to work */
|
|
addarg(TTY_TYPE, ttyconv(*argv));
|
|
#endif
|
|
addarg(USER_TYPE, *argv);
|
|
}
|
|
}
|
|
wtmp();
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* wtmp --
|
|
* read through the utx.log file
|
|
*/
|
|
static void
|
|
wtmp(void)
|
|
{
|
|
struct utmpx *buf = NULL;
|
|
struct utmpx *ut;
|
|
static unsigned int amount = 0;
|
|
time_t t;
|
|
char ct[80];
|
|
struct tm *tm;
|
|
|
|
SLIST_INIT(&idlist);
|
|
(void)time(&t);
|
|
|
|
xo_open_container("last-information");
|
|
|
|
/* Load the last entries from the file. */
|
|
while ((ut = getutxent()) != NULL) {
|
|
if (amount % 128 == 0) {
|
|
buf = realloc(buf, (amount + 128) * sizeof *ut);
|
|
if (buf == NULL)
|
|
xo_err(1, "realloc");
|
|
}
|
|
memcpy(&buf[amount++], ut, sizeof *ut);
|
|
if (t > ut->ut_tv.tv_sec)
|
|
t = ut->ut_tv.tv_sec;
|
|
}
|
|
endutxent();
|
|
|
|
/* Display them in reverse order. */
|
|
xo_open_list("last");
|
|
while (amount > 0)
|
|
doentry(&buf[--amount]);
|
|
xo_close_list("last");
|
|
free(buf);
|
|
tm = localtime(&t);
|
|
(void) strftime(ct, sizeof(ct), "%+", tm);
|
|
xo_emit("\n{:utxdb/%s}", (file == NULL) ? "utx.log" : file);
|
|
xo_attr("seconds", "%lu", (unsigned long) t);
|
|
xo_emit(ctf(" begins {:begins/%s}\n"), ct);
|
|
xo_close_container("last-information");
|
|
}
|
|
|
|
/*
|
|
* doentry --
|
|
* process a single utx.log entry
|
|
*/
|
|
static void
|
|
doentry(struct utmpx *bp)
|
|
{
|
|
struct idtab *tt;
|
|
|
|
/* the machine stopped */
|
|
if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
|
|
/* everybody just logged out */
|
|
while ((tt = SLIST_FIRST(&idlist)) != NULL) {
|
|
SLIST_REMOVE_HEAD(&idlist, list);
|
|
free(tt);
|
|
}
|
|
currentout = -bp->ut_tv.tv_sec;
|
|
crmsg = bp->ut_type != SHUTDOWN_TIME ?
|
|
"crash" : "shutdown";
|
|
/*
|
|
* if we're in snapshot mode, we want to exit if this
|
|
* shutdown/reboot appears while we we are tracking the
|
|
* active range
|
|
*/
|
|
if (snaptime && snapfound)
|
|
exit(0);
|
|
/*
|
|
* don't print shutdown/reboot entries unless flagged for
|
|
*/
|
|
if (!snaptime && want(bp))
|
|
printentry(bp, NULL);
|
|
return;
|
|
}
|
|
/* date got set */
|
|
if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
|
|
if (want(bp) && !snaptime)
|
|
printentry(bp, NULL);
|
|
return;
|
|
}
|
|
|
|
if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
|
|
return;
|
|
|
|
/* find associated identifier */
|
|
SLIST_FOREACH(tt, &idlist, list)
|
|
if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
|
|
break;
|
|
|
|
if (tt == NULL) {
|
|
/* add new one */
|
|
tt = malloc(sizeof(struct idtab));
|
|
if (tt == NULL)
|
|
xo_errx(1, "malloc failure");
|
|
tt->logout = currentout;
|
|
memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
|
|
SLIST_INSERT_HEAD(&idlist, tt, list);
|
|
}
|
|
|
|
/*
|
|
* print record if not in snapshot mode and wanted
|
|
* or in snapshot mode and in snapshot range
|
|
*/
|
|
if (bp->ut_type == USER_PROCESS && (want(bp) ||
|
|
(bp->ut_tv.tv_sec < snaptime &&
|
|
(tt->logout > snaptime || tt->logout < 1)))) {
|
|
snapfound = 1;
|
|
printentry(bp, tt);
|
|
}
|
|
tt->logout = bp->ut_tv.tv_sec;
|
|
}
|
|
|
|
/*
|
|
* printentry --
|
|
* output an entry
|
|
*
|
|
* If `tt' is non-NULL, use it and `crmsg' to print the logout time or
|
|
* logout type (crash/shutdown) as appropriate.
|
|
*/
|
|
static void
|
|
printentry(struct utmpx *bp, struct idtab *tt)
|
|
{
|
|
char ct[80];
|
|
struct tm *tm;
|
|
time_t delta; /* time difference */
|
|
time_t t;
|
|
|
|
if (maxrec != -1 && !maxrec--)
|
|
exit(0);
|
|
xo_open_instance("last");
|
|
t = bp->ut_tv.tv_sec;
|
|
tm = localtime(&t);
|
|
(void) strftime(ct, sizeof(ct), d_first ?
|
|
(yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
|
|
(yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
|
|
switch (bp->ut_type) {
|
|
case BOOT_TIME:
|
|
xo_emit("{:user/%-42s/%s}", "boot time");
|
|
break;
|
|
case SHUTDOWN_TIME:
|
|
xo_emit("{:user/%-42s/%s}", "shutdown time");
|
|
break;
|
|
case OLD_TIME:
|
|
xo_emit("{:user/%-42s/%s}", "old time");
|
|
break;
|
|
case NEW_TIME:
|
|
xo_emit("{:user/%-42s/%s}", "new time");
|
|
break;
|
|
case USER_PROCESS:
|
|
xo_emit("{:user/%-10s/%s} {:tty/%-8s/%s} {:from/%-22.22s/%s}",
|
|
bp->ut_user, bp->ut_line, bp->ut_host);
|
|
break;
|
|
}
|
|
xo_attr("seconds", "%lu", (unsigned long)t);
|
|
xo_emit(ctf(" {:login-time/%s%c/%s}"), ct, tt == NULL ? '\n' : ' ');
|
|
if (tt == NULL)
|
|
goto end;
|
|
if (!tt->logout) {
|
|
xo_emit(" {:logout-time/still logged in}\n");
|
|
goto end;
|
|
}
|
|
if (tt->logout < 0) {
|
|
tt->logout = -tt->logout;
|
|
xo_emit("- {:logout-reason/%s}", crmsg);
|
|
} else {
|
|
tm = localtime(&tt->logout);
|
|
(void) strftime(ct, sizeof(ct), "%R", tm);
|
|
xo_attr("seconds", "%lu", (unsigned long)tt->logout);
|
|
xo_emit(ctf("- {:logout-time/%s}"), ct);
|
|
}
|
|
delta = tt->logout - bp->ut_tv.tv_sec;
|
|
xo_attr("seconds", "%ld", (long)delta);
|
|
if (sflag) {
|
|
xo_emit(" ({:session-length/%8ld})\n", (long)delta);
|
|
} else {
|
|
tm = gmtime(&delta);
|
|
(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
|
|
if (delta < 86400)
|
|
xo_emit(ctf(" ({:session-length/%s})\n"), ct);
|
|
else
|
|
xo_emit(ctf(" ({:session-length/%ld+%s})\n"),
|
|
(long)delta / 86400, ct);
|
|
}
|
|
|
|
end:
|
|
xo_close_instance("last");
|
|
}
|
|
|
|
/*
|
|
* want --
|
|
* see if want this entry
|
|
*/
|
|
static int
|
|
want(struct utmpx *bp)
|
|
{
|
|
ARG *step;
|
|
|
|
if (snaptime)
|
|
return (NO);
|
|
|
|
if (!arglist)
|
|
return (YES);
|
|
|
|
for (step = arglist; step; step = step->next)
|
|
switch(step->type) {
|
|
case REBOOT_TYPE:
|
|
if (bp->ut_type == BOOT_TIME ||
|
|
bp->ut_type == SHUTDOWN_TIME)
|
|
return (YES);
|
|
break;
|
|
case HOST_TYPE:
|
|
if (!strcasecmp(step->name, bp->ut_host))
|
|
return (YES);
|
|
break;
|
|
case TTY_TYPE:
|
|
if (!strcmp(step->name, bp->ut_line))
|
|
return (YES);
|
|
break;
|
|
case USER_TYPE:
|
|
if (!strcmp(step->name, bp->ut_user))
|
|
return (YES);
|
|
break;
|
|
}
|
|
return (NO);
|
|
}
|
|
|
|
/*
|
|
* addarg --
|
|
* add an entry to a linked list of arguments
|
|
*/
|
|
static void
|
|
addarg(int type, char *arg)
|
|
{
|
|
ARG *cur;
|
|
|
|
if ((cur = malloc(sizeof(ARG))) == NULL)
|
|
xo_errx(1, "malloc failure");
|
|
cur->next = arglist;
|
|
cur->type = type;
|
|
cur->name = arg;
|
|
arglist = 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[MAXHOSTNAMELEN];
|
|
char *argdot;
|
|
|
|
if (!(argdot = strchr(arg, '.')))
|
|
return;
|
|
if (first) {
|
|
first = 0;
|
|
if (gethostname(name, sizeof(name)))
|
|
xo_err(1, "gethostname");
|
|
hostdot = strchr(name, '.');
|
|
}
|
|
if (hostdot && !strcasecmp(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" */
|
|
if ((mval = malloc(8)) == NULL)
|
|
xo_errx(1, "malloc failure");
|
|
if (!strcmp(arg, "co"))
|
|
(void)strcpy(mval, "console");
|
|
else {
|
|
(void)strcpy(mval, "tty");
|
|
(void)strcpy(mval + 3, arg);
|
|
}
|
|
return (mval);
|
|
}
|
|
if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
|
|
return (arg + 5);
|
|
return (arg);
|
|
}
|
|
|
|
/*
|
|
* dateconv --
|
|
* Convert the snapshot time in command line given in the format
|
|
* [[CC]YY]MMDDhhmm[.SS]] to a time_t.
|
|
* Derived from atime_arg1() in usr.bin/touch/touch.c
|
|
*/
|
|
static time_t
|
|
dateconv(char *arg)
|
|
{
|
|
time_t timet;
|
|
struct tm *t;
|
|
int yearset;
|
|
char *p;
|
|
|
|
/* Start with the current time. */
|
|
if (time(&timet) < 0)
|
|
xo_err(1, "time");
|
|
if ((t = localtime(&timet)) == NULL)
|
|
xo_err(1, "localtime");
|
|
|
|
/* [[CC]YY]MMDDhhmm[.SS] */
|
|
if ((p = strchr(arg, '.')) == NULL)
|
|
t->tm_sec = 0; /* Seconds defaults to 0. */
|
|
else {
|
|
if (strlen(p + 1) != 2)
|
|
goto terr;
|
|
*p++ = '\0';
|
|
t->tm_sec = ATOI2(p);
|
|
}
|
|
|
|
yearset = 0;
|
|
switch (strlen(arg)) {
|
|
case 12: /* CCYYMMDDhhmm */
|
|
t->tm_year = ATOI2(arg);
|
|
t->tm_year *= 100;
|
|
yearset = 1;
|
|
/* FALLTHROUGH */
|
|
case 10: /* YYMMDDhhmm */
|
|
if (yearset) {
|
|
yearset = ATOI2(arg);
|
|
t->tm_year += yearset;
|
|
} else {
|
|
yearset = ATOI2(arg);
|
|
if (yearset < 69)
|
|
t->tm_year = yearset + 2000;
|
|
else
|
|
t->tm_year = yearset + 1900;
|
|
}
|
|
t->tm_year -= 1900; /* Convert to UNIX time. */
|
|
/* FALLTHROUGH */
|
|
case 8: /* MMDDhhmm */
|
|
t->tm_mon = ATOI2(arg);
|
|
--t->tm_mon; /* Convert from 01-12 to 00-11 */
|
|
t->tm_mday = ATOI2(arg);
|
|
t->tm_hour = ATOI2(arg);
|
|
t->tm_min = ATOI2(arg);
|
|
break;
|
|
case 4: /* hhmm */
|
|
t->tm_hour = ATOI2(arg);
|
|
t->tm_min = ATOI2(arg);
|
|
break;
|
|
default:
|
|
goto terr;
|
|
}
|
|
t->tm_isdst = -1; /* Figure out DST. */
|
|
timet = mktime(t);
|
|
if (timet == -1)
|
|
terr: xo_errx(1,
|
|
"out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
|
|
return timet;
|
|
}
|