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