Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.bin / last / last.c
1 /*
2  * Copyright (c) 1987, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * $FreeBSD: src/usr.bin/last/last.c,v 1.10.6.11 2002/11/02 23:00:26 mux Exp $
34  */
35
36 #ifndef lint
37 static const char copyright[] =
38 "@(#) Copyright (c) 1987, 1993, 1994\n\
39         The Regents of the University of California.  All rights reserved.\n";
40 #endif /* not lint */
41
42 #ifndef lint
43 static const char sccsid[] = "@(#)last.c        8.2 (Berkeley) 4/2/94";
44 #endif /* not lint */
45
46 #include <sys/param.h>
47 #include <sys/stat.h>
48
49 #include <err.h>
50 #include <fcntl.h>
51 #include <langinfo.h>
52 #include <locale.h>
53 #include <paths.h>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60 #include <utmp.h>
61 #include <sys/queue.h>
62
63 #define NO      0                               /* false/no */
64 #define YES     1                               /* true/yes */
65 #define ATOI2(ar)       ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
66
67 static struct utmp      buf[1024];              /* utmp read buffer */
68
69 typedef struct arg {
70         char    *name;                          /* argument */
71 #define HOST_TYPE       -2
72 #define TTY_TYPE        -3
73 #define USER_TYPE       -4
74         int     type;                           /* type of arg */
75         struct arg      *next;                  /* linked list pointer */
76 } ARG;
77 ARG     *arglist;                               /* head of linked list */
78
79 LIST_HEAD(ttylisthead, ttytab) ttylist;
80
81 struct ttytab {
82         time_t  logout;                         /* log out time */
83         char    tty[UT_LINESIZE + 1];           /* terminal name */
84         LIST_ENTRY(ttytab) list;
85 };
86
87 static const    char *crmsg;                    /* cause of last reboot */
88 static long     currentout,                     /* current logout value */
89                 maxrec;                         /* records to display */
90 static const    char *file = _PATH_WTMP;                /* wtmp file */
91 static int      sflag = 0;                      /* show delta in seconds */
92 static int      width = 5;                      /* show seconds in delta */
93 static int      yflag;                          /* show year */
94 static int      d_first;
95 static int      snapfound = 0;                  /* found snapshot entry? */
96 static time_t   snaptime;                       /* if != 0, we will only
97                                                  * report users logged in
98                                                  * at this snapshot time
99                                                  */
100
101 int      main __P((int, char *[]));
102 void     addarg __P((int, char *));
103 time_t   dateconv __P((char *));
104 void     doentry __P((struct utmp *));
105 void     hostconv __P((char *));
106 void     onintr __P((int));
107 void     printentry __P((struct utmp *, struct ttytab *));
108 char    *ttyconv __P((char *));
109 char    *ttyconv __P((char *));
110 int      want __P((struct utmp *));
111 void     usage __P((void));
112 void     wtmp __P((void));
113
114 void
115 usage(void)
116 {
117         (void)fprintf(stderr,
118 "usage: last [-#] [-y] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
119 "\t[-t tty] [-s|w] [user ...]\n");
120         exit(1);
121 }
122
123 int
124 main(argc, argv)
125         int argc;
126         char *argv[];
127 {
128         int ch;
129         char *p;
130
131         (void) setlocale(LC_TIME, "");
132         d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
133
134         maxrec = -1;
135         snaptime = 0;
136         while ((ch = getopt(argc, argv, "0123456789d:f:h:st:wy")) != -1)
137                 switch (ch) {
138                 case '0': case '1': case '2': case '3': case '4':
139                 case '5': case '6': case '7': case '8': case '9':
140                         /*
141                          * kludge: last was originally designed to take
142                          * a number after a dash.
143                          */
144                         if (maxrec == -1) {
145                                 p = strchr(argv[optind - 1], ch);
146                                 if (p == NULL)
147                                         p = strchr(argv[optind], ch);
148                                 maxrec = atol(p);
149                                 if (!maxrec)
150                                         exit(0);
151                         }
152                         break;
153                 case 'd':
154                         snaptime = dateconv(optarg);
155                         break;
156                 case 'f':
157                         file = optarg;
158                         break;
159                 case 'h':
160                         hostconv(optarg);
161                         addarg(HOST_TYPE, optarg);
162                         break;
163                 case 's':
164                         sflag++;        /* Show delta as seconds */
165                         break;
166                 case 't':
167                         addarg(TTY_TYPE, ttyconv(optarg));
168                         break;
169                 case 'w':
170                         width = 8;
171                         break;
172                 case 'y':
173                         yflag++;
174                         break;
175                 case '?':
176                 default:
177                         usage();
178                 }
179
180         if (sflag && width == 8) usage();
181
182         if (argc) {
183                 setlinebuf(stdout);
184                 for (argv += optind; *argv; ++argv) {
185 #define COMPATIBILITY
186 #ifdef  COMPATIBILITY
187                         /* code to allow "last p5" to work */
188                         addarg(TTY_TYPE, ttyconv(*argv));
189 #endif
190                         addarg(USER_TYPE, *argv);
191                 }
192         }
193         wtmp();
194         exit(0);
195 }
196
197 /*
198  * wtmp --
199  *      read through the wtmp file
200  */
201 void
202 wtmp()
203 {
204         struct utmp     *bp;                    /* current structure */
205         struct stat     stb;                    /* stat of file for size */
206         long    bl;
207         int     bytes, wfd;
208         char ct[80];
209         struct tm *tm;
210
211         LIST_INIT(&ttylist);
212
213         if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
214                 err(1, "%s", file);
215         bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
216
217         (void)time(&buf[0].ut_time);
218         (void)signal(SIGINT, onintr);
219         (void)signal(SIGQUIT, onintr);
220
221         while (--bl >= 0) {
222                 if (lseek(wfd, (off_t)(bl * sizeof(buf)), L_SET) == -1 ||
223                     (bytes = read(wfd, buf, sizeof(buf))) == -1)
224                         err(1, "%s", file);
225                 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp)
226                         doentry(bp);
227         }
228         tm = localtime(&buf[0].ut_time);
229         (void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
230         printf("%s", ct);
231 }
232
233 /*
234  * doentry --
235  *      process a single wtmp entry
236  */
237 void
238 doentry(bp)
239         struct utmp *bp;
240 {
241         struct ttytab   *tt, *ttx;              /* ttylist entry */
242
243         /*
244          * if the terminal line is '~', the machine stopped.
245          * see utmp(5) for more info.
246          */
247         if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
248                 /* everybody just logged out */
249                 for (tt = LIST_FIRST(&ttylist); tt;) {
250                         LIST_REMOVE(tt, list);
251                         ttx = tt;
252                         tt = LIST_NEXT(tt, list);
253                         free(ttx);
254                 }
255                 currentout = -bp->ut_time;
256                 crmsg = strncmp(bp->ut_name, "shutdown", UT_NAMESIZE) ?
257                     "crash" : "shutdown";
258                 /*
259                  * if we're in snapshot mode, we want to exit if this
260                  * shutdown/reboot appears while we we are tracking the
261                  * active range
262                  */
263                 if (snaptime && snapfound)
264                         exit(0);
265                 /*
266                  * don't print shutdown/reboot entries unless flagged for
267                  */
268                 if (!snaptime && want(bp))
269                         printentry(bp, NULL);
270                 return;
271         }
272         /*
273          * if the line is '{' or '|', date got set; see
274          * utmp(5) for more info.
275          */
276         if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') &&
277             !bp->ut_line[1]) {
278                 if (want(bp) && !snaptime)
279                         printentry(bp, NULL);
280                 return;
281         }
282         /* find associated tty */
283         LIST_FOREACH(tt, &ttylist, list)
284             if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE))
285                     break;
286
287         if (tt == NULL) {
288                 /* add new one */
289                 tt = malloc(sizeof(struct ttytab));
290                 if (tt == NULL)
291                         err(1, "malloc failure");
292                 tt->logout = currentout;
293                 strncpy(tt->tty, bp->ut_line, UT_LINESIZE);
294                 LIST_INSERT_HEAD(&ttylist, tt, list);
295         }
296
297         /*
298          * print record if not in snapshot mode and wanted
299          * or in snapshot mode and in snapshot range
300          */
301         if (bp->ut_name[0] && (want(bp) || (bp->ut_time < snaptime &&
302             (tt->logout > snaptime || tt->logout < 1)))) {
303                 snapfound = 1;
304                 /*
305                  * when uucp and ftp log in over a network, the entry in
306                  * the utmp file is the name plus their process id.  See
307                  * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
308                  */
309                 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
310                         bp->ut_line[3] = '\0';
311                 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
312                         bp->ut_line[4] = '\0';
313                 printentry(bp, tt);
314         }
315         tt->logout = bp->ut_time;
316 }
317
318 /*
319  * printentry --
320  *      output an entry
321  *
322  * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
323  * logout type (crash/shutdown) as appropriate.
324  */
325 void
326 printentry(bp, tt)
327         struct utmp *bp;
328         struct ttytab *tt;
329 {
330         char ct[80];
331         struct tm *tm;
332         time_t  delta;                          /* time difference */
333
334         if (maxrec != -1 && !maxrec--)
335                 exit(0);
336         tm = localtime(&bp->ut_time);
337         (void) strftime(ct, sizeof(ct), d_first ?
338             (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
339             (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
340         printf("%-*.*s %-*.*s %-*.*s %s%c",
341             UT_NAMESIZE, UT_NAMESIZE, bp->ut_name,
342             UT_LINESIZE, UT_LINESIZE, bp->ut_line,
343             UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host,
344             ct, tt == NULL ? '\n' : ' ');
345         if (tt == NULL)
346                 return;
347         if (!tt->logout) {
348                 puts("  still logged in");
349                 return;
350         }
351         if (tt->logout < 0) {
352                 tt->logout = -tt->logout;
353                 printf("- %s", crmsg);
354         } else {
355                 tm = localtime(&tt->logout);
356                 (void) strftime(ct, sizeof(ct), "%R", tm);
357                 printf("- %s", ct);
358         }
359         delta = tt->logout - bp->ut_time;
360         if (sflag) {
361                 printf("  (%8ld)\n", (long)delta);
362         } else {
363                 tm = gmtime(&delta);
364                 (void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
365                 if (delta < 86400)
366                         printf("  (%s)\n", ct);
367                 else
368                         printf(" (%ld+%s)\n", (long)delta / 86400, ct);
369         }
370 }
371
372 /*
373  * want --
374  *      see if want this entry
375  */
376 int
377 want(bp)
378         struct utmp *bp;
379 {
380         ARG *step;
381
382         if (snaptime)
383                 return (NO);
384
385         if (!arglist)
386                 return (YES);
387
388         for (step = arglist; step; step = step->next)
389                 switch(step->type) {
390                 case HOST_TYPE:
391                         if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
392                                 return (YES);
393                         break;
394                 case TTY_TYPE:
395                         if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
396                                 return (YES);
397                         break;
398                 case USER_TYPE:
399                         if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
400                                 return (YES);
401                         break;
402                 }
403         return (NO);
404 }
405
406 /*
407  * addarg --
408  *      add an entry to a linked list of arguments
409  */
410 void
411 addarg(type, arg)
412         int type;
413         char *arg;
414 {
415         ARG *cur;
416
417         if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
418                 err(1, "malloc failure");
419         cur->next = arglist;
420         cur->type = type;
421         cur->name = arg;
422         arglist = cur;
423 }
424
425 /*
426  * hostconv --
427  *      convert the hostname to search pattern; if the supplied host name
428  *      has a domain attached that is the same as the current domain, rip
429  *      off the domain suffix since that's what login(1) does.
430  */
431 void
432 hostconv(arg)
433         char *arg;
434 {
435         static int first = 1;
436         static char *hostdot, name[MAXHOSTNAMELEN];
437         char *argdot;
438
439         if (!(argdot = strchr(arg, '.')))
440                 return;
441         if (first) {
442                 first = 0;
443                 if (gethostname(name, sizeof(name)))
444                         err(1, "gethostname");
445                 hostdot = strchr(name, '.');
446         }
447         if (hostdot && !strcasecmp(hostdot, argdot))
448                 *argdot = '\0';
449 }
450
451 /*
452  * ttyconv --
453  *      convert tty to correct name.
454  */
455 char *
456 ttyconv(arg)
457         char *arg;
458 {
459         char *mval;
460
461         /*
462          * kludge -- we assume that all tty's end with
463          * a two character suffix.
464          */
465         if (strlen(arg) == 2) {
466                 /* either 6 for "ttyxx" or 8 for "console" */
467                 if (!(mval = malloc((u_int)8)))
468                         err(1, "malloc failure");
469                 if (!strcmp(arg, "co"))
470                         (void)strcpy(mval, "console");
471                 else {
472                         (void)strcpy(mval, "tty");
473                         (void)strcpy(mval + 3, arg);
474                 }
475                 return (mval);
476         }
477         if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
478                 return (arg + 5);
479         return (arg);
480 }
481
482 /*
483  * dateconv --
484  *      Convert the snapshot time in command line given in the format
485  *      [[CC]YY]MMDDhhmm[.SS]] to a time_t.
486  *      Derived from atime_arg1() in usr.bin/touch/touch.c
487  */
488 time_t
489 dateconv(arg)
490         char *arg;
491 {
492         time_t timet;
493         struct tm *t;
494         int yearset;
495         char *p;
496
497         /* Start with the current time. */
498         if (time(&timet) < 0)
499                 err(1, "time");
500         if ((t = localtime(&timet)) == NULL)
501                 err(1, "localtime");
502
503         /* [[CC]YY]MMDDhhmm[.SS] */
504         if ((p = strchr(arg, '.')) == NULL)
505                 t->tm_sec = 0;          /* Seconds defaults to 0. */
506         else {
507                 if (strlen(p + 1) != 2)
508                         goto terr;
509                 *p++ = '\0';
510                 t->tm_sec = ATOI2(p);
511         }
512
513         yearset = 0;
514         switch (strlen(arg)) {
515         case 12:                        /* CCYYMMDDhhmm */
516                 t->tm_year = ATOI2(arg);
517                 t->tm_year *= 100;
518                 yearset = 1;
519                 /* FALLTHOUGH */
520         case 10:                        /* YYMMDDhhmm */
521                 if (yearset) {
522                         yearset = ATOI2(arg);
523                         t->tm_year += yearset;
524                 } else {
525                         yearset = ATOI2(arg);
526                         if (yearset < 69)
527                                 t->tm_year = yearset + 2000;
528                         else
529                                 t->tm_year = yearset + 1900;
530                 }
531                 t->tm_year -= 1900;     /* Convert to UNIX time. */
532                 /* FALLTHROUGH */
533         case 8:                         /* MMDDhhmm */
534                 t->tm_mon = ATOI2(arg);
535                 --t->tm_mon;            /* Convert from 01-12 to 00-11 */
536                 t->tm_mday = ATOI2(arg);
537                 t->tm_hour = ATOI2(arg);
538                 t->tm_min = ATOI2(arg);
539                 break;
540         case 4:                         /* hhmm */
541                 t->tm_hour = ATOI2(arg);
542                 t->tm_min = ATOI2(arg);
543                 break;
544         default:
545                 goto terr;
546         }
547         t->tm_isdst = -1;               /* Figure out DST. */
548         timet = mktime(t);
549         if (timet == -1)
550 terr:           errx(1,
551         "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
552         return timet;
553 }
554
555
556 /*
557  * onintr --
558  *      on interrupt, we inform the user how far we've gotten
559  */
560 void
561 onintr(signo)
562         int signo;
563 {
564         char ct[80];
565         struct tm *tm;
566
567         tm = localtime(&buf[0].ut_time);
568         (void) strftime(ct, sizeof(ct),
569                         d_first ? "%a %e %b %R" : "%a %b %e %R",
570                         tm);
571         printf("\ninterrupted %s\n", ct);
572         if (signo == SIGINT)
573                 exit(1);
574         (void)fflush(stdout);                   /* fix required for rsh */
575 }