root/usr.bin/last/want.c
/*      $NetBSD: want.c,v 1.17 2012/03/15 03:04:05 dholland Exp $       */

/*
 * Copyright (c) 1987, 1993, 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * 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.
 */
static struct utmp *buf;
static time_t seentime;

static void onintr(int);
static int want(struct utmp *, int);
static const char *gethost(struct utmp *, const char *, int);

static const char *
/*ARGSUSED*/
gethost(struct utmp *ut, const char *host, int numeric)
{
#if HAS_UT_SS == 0
        return numeric ? "" : host;
#else
        if (numeric) {
                static char hbuf[512];
                hbuf[0] = '\0';
                (void)sockaddr_snprintf(hbuf, sizeof(hbuf), "%a",
                    (struct sockaddr *)&ut->ut_ss);
                return hbuf;
        } else
                return host;
#endif
}

#define NULTERM(what) \
        if (check ## what) \
                (void)strlcpy(what ## p = what ## buf, bp->ut_ ## what, \
                    sizeof(what ## buf)); \
        else \
                what ## p = bp->ut_ ## what
        
/*
 * wtmp --
 *      read through the wtmp file
 */
static void
wtmp(const char *file, int namesz, int linesz, int hostsz, int numeric)
{
        struct utmp     *bp;            /* current structure */
        TTY     *T;                     /* tty list entry */
        struct stat     stb;            /* stat of file for sz */
        off_t   offset;
        int     wfd;
        char    *ct;
        const char *crmsg;
        size_t  len = sizeof(*buf) * MAXUTMP;
        char namebuf[sizeof(bp->ut_name) + 1], *namep;
        char linebuf[sizeof(bp->ut_line) + 1], *linep;
        char hostbuf[sizeof(bp->ut_host) + 1], *hostp;
        int checkname = namesz > (int)sizeof(bp->ut_name);
        int checkline = linesz > (int)sizeof(bp->ut_line);
        int checkhost = hostsz > (int)sizeof(bp->ut_host);

        if ((buf = malloc(len)) == NULL)
                err(EXIT_FAILURE, "Cannot allocate utmp buffer");

        crmsg = NULL;

        if (!strcmp(file, "-")) {
                wfd = STDIN_FILENO;
                file = "<stdin>";
        } else if ((wfd = open(file, O_RDONLY, 0)) < 0) {
                err(EXIT_FAILURE, "%s", file);
        }

        if (lseek(wfd, 0, SEEK_CUR) < 0) {
                const char *dir;
                char *tfile;
                int tempfd;
                ssize_t tlen;

                if (ESPIPE != errno) {
                        err(EXIT_FAILURE, "lseek");
                }
                dir = getenv("TMPDIR");
                if (asprintf(&tfile, "%s/last.XXXXXX", dir ? dir : _PATH_TMP) == -1)
                        err(EXIT_FAILURE, "asprintf");
                tempfd = mkstemp(tfile);
                if (tempfd < 0) {
                        err(EXIT_FAILURE, "mkstemp");
                }
                unlink(tfile);
                for (;;) {
                        tlen = read(wfd, buf, len);
                        if (tlen < 0) {
                                err(1, "%s: read", file);
                        }
                        if (tlen == 0) {
                                break;
                        }
                        if (write(tempfd, buf, tlen) != tlen) {
                                err(1, "%s: write", tfile);
                        }
                }
                wfd = tempfd;
        }

        if (fstat(wfd, &stb) == -1)
                err(EXIT_FAILURE, "%s: fstat", file);
        if (!S_ISREG(stb.st_mode))
                errx(EXIT_FAILURE, "%s: Not a regular file", file);

        seentime = stb.st_mtime;
        (void)signal(SIGINT, onintr);
        (void)signal(SIGQUIT, onintr);

        offset = stb.st_size;
        /* Ignore trailing garbage or partial record */
        offset -= offset % (off_t) sizeof(*buf);
        
        while (offset >= (off_t) sizeof(*buf)) {
                ssize_t ret, i;
                size_t size;

                size = MIN((off_t)len, offset);
                offset -= size; /* Always a multiple of sizeof(*buf) */
                ret = pread(wfd, buf, size, offset);
                if (ret < 0) {
                        err(EXIT_FAILURE, "%s: pread", file);
                } else if ((size_t) ret < size) {
                        err(EXIT_FAILURE, "%s: Unexpected end of file", file);
                }

                for (i = ret / sizeof(*buf) - 1; i >= 0; i--) {
                        bp = &buf[i];

                        NULTERM(name);
                        NULTERM(line);
                        NULTERM(host);

                        seentime = bp->ut_timefld;

                        /*
                         * if the terminal line is '~', the machine stopped.
                         * see utmp(5) for more info.
                         */
                        if (linep[0] == '~' && !linep[1]) {
                                /* everybody just logged out */
                                for (T = ttylist; T; T = T->next)
                                        T->logout = -bp->ut_timefld;
                                currentout = -bp->ut_timefld;
                                crmsg = strncmp(namep, "shutdown",
                                    namesz) ? "crash" : "shutdown";
                                if (want(bp, NO)) {
                                        ct = fmttime(bp->ut_timefld, fulltime);
                                        printf("%-*.*s  %-*.*s %-*.*s %s\n",
                                            namesz, namesz, namep,
                                            linesz, linesz, linep,
                                            hostsz, hostsz,
                                            gethost(bp, hostp, numeric), ct);
                                        if (maxrec != -1 && !--maxrec)
                                                return;
                                }
                                continue;
                        }
                        /*
                         * if the line is '{' or '|', date got set; see
                         * utmp(5) for more info.
                         */
                        if ((linep[0] == '{' || linep[0] == '|') && !linep[1]) {
                                if (want(bp, NO)) {
                                        ct = fmttime(bp->ut_timefld, fulltime);
                                        printf("%-*.*s  %-*.*s %-*.*s %s\n",
                                            namesz, namesz, namep,
                                            linesz, linesz, linep,
                                            hostsz, hostsz,
                                            gethost(bp, hostp, numeric),
                                            ct);
                                        if (maxrec && !--maxrec)
                                                return;
                                }
                                continue;
                        }
                        /* find associated tty */
                        for (T = ttylist;; T = T->next) {
                                if (!T) {
                                        /* add new one */
                                        T = addtty(linep);
                                        break;
                                }
                                if (!strncmp(T->tty, linep, LINESIZE))
                                        break;
                        }
                        if (TYPE(bp) == SIGNATURE)
                                continue;
                        if (namep[0] && want(bp, YES)) {
                                ct = fmttime(bp->ut_timefld, fulltime);
                                printf("%-*.*s  %-*.*s %-*.*s %s ",
                                    namesz, namesz, namep,
                                    linesz, linesz, linep,
                                    hostsz, hostsz,
                                    gethost(bp, hostp, numeric),
                                    ct);
                                if (!T->logout)
                                        puts("  still logged in");
                                else {
                                        time_t  delta;                  /* time difference */

                                        if (T->logout < 0) {
                                                T->logout = -T->logout;
                                                printf("- %s", crmsg);
                                        }
                                        else
                                                printf("- %s",
                                                    fmttime(T->logout,
                                                    fulltime | TIMEONLY));
                                        delta = T->logout - bp->ut_timefld;
                                        if (delta < SECSPERDAY)
                                                printf("  (%s)\n",
                                                    fmttime(delta,
                                                    fulltime | TIMEONLY | GMT));
                                        else
                                                printf(" (%lld+%s)\n",
                                                    (long long)
                                                    delta / SECSPERDAY,
                                                    fmttime(delta,
                                                    fulltime | TIMEONLY | GMT));
                                }
                                if (maxrec != -1 && !--maxrec)
                                        return;
                        }
                        T->logout = bp->ut_timefld;
                }
        }
        fulltime = 1;   /* show full time */
        crmsg = fmttime(seentime, FULLTIME);
        if ((ct = strrchr(file, '/')) != NULL)
                ct++;
        printf("\n%s begins %s\n", ct ? ct : file, crmsg);
}

/*
 * want --
 *      see if want this entry
 */
static int
want(struct utmp *bp, int check)
{
        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 (!strncasecmp(step->name, bp->ut_host, HOSTSIZE))
                                return (YES);
                        break;
                case TTY_TYPE:
                        if (!strncmp(step->name, bp->ut_line, LINESIZE))
                                return (YES);
                        break;
                case USER_TYPE:
                        if (!strncmp(step->name, bp->ut_name, NAMESIZE))
                                return (YES);
                        break;
        }
        return (NO);
}

/*
 * onintr --
 *      on interrupt, we inform the user how far we've gotten
 */
static void
onintr(int signo)
{
        /* FIXME: None of this is allowed in a signal handler */
        printf("\ninterrupted %s\n", fmttime(seentime, FULLTIME));
        if (signo == SIGINT) {
                (void)raise_default_signal(signo);
                exit(EXIT_FAILURE);
        }
        (void)fflush(stdout);           /* fix required for rsh */
}