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