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