]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.bin/last/last.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #ifndef lint
31 static const char copyright[] =
32 "@(#) Copyright (c) 1987, 1993, 1994\n\
33         The Regents of the University of California.  All rights reserved.\n";
34 #endif /* not lint */
35
36 #ifndef lint
37 static const char sccsid[] = "@(#)last.c        8.2 (Berkeley) 4/2/94";
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
41
42 #include <sys/param.h>
43 #include <sys/stat.h>
44
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <langinfo.h>
49 #include <locale.h>
50 #include <paths.h>
51 #include <signal.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <time.h>
56 #include <timeconv.h>
57 #include <unistd.h>
58 #include <utmpx.h>
59 #include <sys/queue.h>
60
61 #define NO      0                               /* false/no */
62 #define YES     1                               /* true/yes */
63 #define ATOI2(ar)       ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
64
65 typedef struct arg {
66         char    *name;                          /* argument */
67 #define HOST_TYPE       -2
68 #define TTY_TYPE        -3
69 #define USER_TYPE       -4
70         int     type;                           /* type of arg */
71         struct arg      *next;                  /* linked list pointer */
72 } ARG;
73 ARG     *arglist;                               /* head of linked list */
74
75 LIST_HEAD(idlisthead, idtab) idlist;
76
77 struct idtab {
78         time_t  logout;                         /* log out time */
79         char    id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
80         LIST_ENTRY(idtab) list;
81 };
82
83 static const    char *crmsg;                    /* cause of last reboot */
84 static time_t   currentout;                     /* current logout value */
85 static long     maxrec;                         /* records to display */
86 static const    char *file = NULL;              /* wtmp file */
87 static int      sflag = 0;                      /* show delta in seconds */
88 static int      width = 5;                      /* show seconds in delta */
89 static int      yflag;                          /* show year */
90 static int      d_first;
91 static int      snapfound = 0;                  /* found snapshot entry? */
92 static time_t   snaptime;                       /* if != 0, we will only
93                                                  * report users logged in
94                                                  * at this snapshot time
95                                                  */
96
97 void     addarg(int, char *);
98 time_t   dateconv(char *);
99 void     doentry(struct utmpx *);
100 void     hostconv(char *);
101 void     printentry(struct utmpx *, struct idtab *);
102 char    *ttyconv(char *);
103 int      want(struct utmpx *);
104 void     usage(void);
105 void     wtmp(void);
106
107 void
108 usage(void)
109 {
110         (void)fprintf(stderr,
111 "usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
112 "            [-n maxrec] [-t tty] [user ...]\n");
113         exit(1);
114 }
115
116 int
117 main(int argc, char *argv[])
118 {
119         int ch;
120         char *p;
121
122         (void) setlocale(LC_TIME, "");
123         d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
124
125         maxrec = -1;
126         snaptime = 0;
127         while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
128                 switch (ch) {
129                 case '0': case '1': case '2': case '3': case '4':
130                 case '5': case '6': case '7': case '8': case '9':
131                         /*
132                          * kludge: last was originally designed to take
133                          * a number after a dash.
134                          */
135                         if (maxrec == -1) {
136                                 p = strchr(argv[optind - 1], ch);
137                                 if (p == NULL)
138                                         p = strchr(argv[optind], ch);
139                                 maxrec = atol(p);
140                                 if (!maxrec)
141                                         exit(0);
142                         }
143                         break;
144                 case 'd':
145                         snaptime = dateconv(optarg);
146                         break;
147                 case 'f':
148                         file = optarg;
149                         break;
150                 case 'h':
151                         hostconv(optarg);
152                         addarg(HOST_TYPE, optarg);
153                         break;
154                 case 'n':
155                         errno = 0;
156                         maxrec = strtol(optarg, &p, 10);
157                         if (p == optarg || *p != '\0' || errno != 0 ||
158                             maxrec <= 0)
159                                 errx(1, "%s: bad line count", optarg);
160                         break;
161                 case 's':
162                         sflag++;        /* Show delta as seconds */
163                         break;
164                 case 't':
165                         addarg(TTY_TYPE, ttyconv(optarg));
166                         break;
167                 case 'w':
168                         width = 8;
169                         break;
170                 case 'y':
171                         yflag++;
172                         break;
173                 case '?':
174                 default:
175                         usage();
176                 }
177
178         if (sflag && width == 8) usage();
179
180         if (argc) {
181                 setlinebuf(stdout);
182                 for (argv += optind; *argv; ++argv) {
183 #define COMPATIBILITY
184 #ifdef  COMPATIBILITY
185                         /* code to allow "last p5" to work */
186                         addarg(TTY_TYPE, ttyconv(*argv));
187 #endif
188                         addarg(USER_TYPE, *argv);
189                 }
190         }
191         wtmp();
192         exit(0);
193 }
194
195 /*
196  * wtmp --
197  *      read through the wtmp file
198  */
199 void
200 wtmp(void)
201 {
202         struct utmpx *buf = NULL;
203         struct utmpx *ut;
204         static unsigned int amount = 0;
205         time_t t;
206         char ct[80];
207         struct tm *tm;
208
209         LIST_INIT(&idlist);
210         (void)time(&t);
211
212         /* Load the last entries from the file. */
213         if (setutxdb(UTXDB_LOG, file) != 0)
214                 err(1, "%s", file);
215         while ((ut = getutxent()) != NULL) {
216                 if (amount % 128 == 0) {
217                         buf = realloc(buf, (amount + 128) * sizeof *ut);
218                         if (buf == NULL)
219                                 err(1, "realloc");
220                 }
221                 memcpy(&buf[amount++], ut, sizeof *ut);
222                 if (t > ut->ut_tv.tv_sec)
223                         t = ut->ut_tv.tv_sec;
224         }
225         endutxent();
226
227         /* Display them in reverse order. */
228         while (amount > 0)
229                 doentry(&buf[--amount]);
230
231         tm = localtime(&t);
232         (void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
233         printf("%s", ct);
234 }
235
236 /*
237  * doentry --
238  *      process a single wtmp entry
239  */
240 void
241 doentry(struct utmpx *bp)
242 {
243         struct idtab    *tt, *ttx;              /* idlist entry */
244
245         /* the machine stopped */
246         if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
247                 /* everybody just logged out */
248                 for (tt = LIST_FIRST(&idlist); tt;) {
249                         LIST_REMOVE(tt, list);
250                         ttx = tt;
251                         tt = LIST_NEXT(tt, list);
252                         free(ttx);
253                 }
254                 currentout = -bp->ut_tv.tv_sec;
255                 crmsg = bp->ut_type != SHUTDOWN_TIME ?
256                     "crash" : "shutdown";
257                 /*
258                  * if we're in snapshot mode, we want to exit if this
259                  * shutdown/reboot appears while we we are tracking the
260                  * active range
261                  */
262                 if (snaptime && snapfound)
263                         exit(0);
264                 /*
265                  * don't print shutdown/reboot entries unless flagged for
266                  */
267                 if (!snaptime && want(bp))
268                         printentry(bp, NULL);
269                 return;
270         }
271         /* date got set */
272         if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
273                 if (want(bp) && !snaptime)
274                         printentry(bp, NULL);
275                 return;
276         }
277
278         if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
279                 return;
280
281         /* find associated identifier */
282         LIST_FOREACH(tt, &idlist, list)
283             if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
284                     break;
285
286         if (tt == NULL) {
287                 /* add new one */
288                 tt = malloc(sizeof(struct idtab));
289                 if (tt == NULL)
290                         errx(1, "malloc failure");
291                 tt->logout = currentout;
292                 memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
293                 LIST_INSERT_HEAD(&idlist, tt, list);
294         }
295
296         /*
297          * print record if not in snapshot mode and wanted
298          * or in snapshot mode and in snapshot range
299          */
300         if (bp->ut_type == USER_PROCESS && (want(bp) ||
301             (bp->ut_tv.tv_sec < snaptime &&
302             (tt->logout > snaptime || tt->logout < 1)))) {
303                 snapfound = 1;
304                 printentry(bp, tt);
305         }
306         tt->logout = bp->ut_tv.tv_sec;
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 utmpx *bp, struct idtab *tt)
318 {
319         char ct[80];
320         struct tm *tm;
321         time_t  delta;                          /* time difference */
322         time_t  t;
323
324         if (maxrec != -1 && !maxrec--)
325                 exit(0);
326         t = bp->ut_tv.tv_sec;
327         tm = localtime(&t);
328         (void) strftime(ct, sizeof(ct), d_first ?
329             (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
330             (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
331         switch (bp->ut_type) {
332         case BOOT_TIME:
333                 printf("%-42s", "boot time");
334                 break;
335         case SHUTDOWN_TIME:
336                 printf("%-42s", "shutdown time");
337                 break;
338         case OLD_TIME:
339                 printf("%-42s", "old time");
340                 break;
341         case NEW_TIME:
342                 printf("%-42s", "new time");
343                 break;
344         case USER_PROCESS:
345                 printf("%-10s %-8s %-22.22s",
346                     bp->ut_user, bp->ut_line, bp->ut_host);
347                 break;
348         }
349         printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
350         if (tt == NULL)
351                 return;
352         if (!tt->logout) {
353                 puts("  still logged in");
354                 return;
355         }
356         if (tt->logout < 0) {
357                 tt->logout = -tt->logout;
358                 printf("- %s", crmsg);
359         } else {
360                 tm = localtime(&tt->logout);
361                 (void) strftime(ct, sizeof(ct), "%R", tm);
362                 printf("- %s", ct);
363         }
364         delta = tt->logout - bp->ut_tv.tv_sec;
365         if (sflag) {
366                 printf("  (%8ld)\n", (long)delta);
367         } else {
368                 tm = gmtime(&delta);
369                 (void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
370                 if (delta < 86400)
371                         printf("  (%s)\n", ct);
372                 else
373                         printf(" (%ld+%s)\n", (long)delta / 86400, ct);
374         }
375 }
376
377 /*
378  * want --
379  *      see if want this entry
380  */
381 int
382 want(struct utmpx *bp)
383 {
384         ARG *step;
385
386         if (snaptime)
387                 return (NO);
388
389         if (!arglist)
390                 return (YES);
391
392         for (step = arglist; step; step = step->next)
393                 switch(step->type) {
394                 case HOST_TYPE:
395                         if (!strcasecmp(step->name, bp->ut_host))
396                                 return (YES);
397                         break;
398                 case TTY_TYPE:
399                         if (!strcmp(step->name, bp->ut_line))
400                                 return (YES);
401                         break;
402                 case USER_TYPE:
403                         if (!strcmp(step->name, bp->ut_user))
404                                 return (YES);
405                         break;
406                 }
407         return (NO);
408 }
409
410 /*
411  * addarg --
412  *      add an entry to a linked list of arguments
413  */
414 void
415 addarg(int type, char *arg)
416 {
417         ARG *cur;
418
419         if ((cur = malloc(sizeof(ARG))) == NULL)
420                 errx(1, "malloc failure");
421         cur->next = arglist;
422         cur->type = type;
423         cur->name = arg;
424         arglist = cur;
425 }
426
427 /*
428  * hostconv --
429  *      convert the hostname to search pattern; if the supplied host name
430  *      has a domain attached that is the same as the current domain, rip
431  *      off the domain suffix since that's what login(1) does.
432  */
433 void
434 hostconv(char *arg)
435 {
436         static int first = 1;
437         static char *hostdot, name[MAXHOSTNAMELEN];
438         char *argdot;
439
440         if (!(argdot = strchr(arg, '.')))
441                 return;
442         if (first) {
443                 first = 0;
444                 if (gethostname(name, sizeof(name)))
445                         err(1, "gethostname");
446                 hostdot = strchr(name, '.');
447         }
448         if (hostdot && !strcasecmp(hostdot, argdot))
449                 *argdot = '\0';
450 }
451
452 /*
453  * ttyconv --
454  *      convert tty to correct name.
455  */
456 char *
457 ttyconv(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(8)) == NULL)
468                         errx(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(char *arg)
490 {
491         time_t timet;
492         struct tm *t;
493         int yearset;
494         char *p;
495
496         /* Start with the current time. */
497         if (time(&timet) < 0)
498                 err(1, "time");
499         if ((t = localtime(&timet)) == NULL)
500                 err(1, "localtime");
501
502         /* [[CC]YY]MMDDhhmm[.SS] */
503         if ((p = strchr(arg, '.')) == NULL)
504                 t->tm_sec = 0;          /* Seconds defaults to 0. */
505         else {
506                 if (strlen(p + 1) != 2)
507                         goto terr;
508                 *p++ = '\0';
509                 t->tm_sec = ATOI2(p);
510         }
511
512         yearset = 0;
513         switch (strlen(arg)) {
514         case 12:                        /* CCYYMMDDhhmm */
515                 t->tm_year = ATOI2(arg);
516                 t->tm_year *= 100;
517                 yearset = 1;
518                 /* FALLTHROUGH */
519         case 10:                        /* YYMMDDhhmm */
520                 if (yearset) {
521                         yearset = ATOI2(arg);
522                         t->tm_year += yearset;
523                 } else {
524                         yearset = ATOI2(arg);
525                         if (yearset < 69)
526                                 t->tm_year = yearset + 2000;
527                         else
528                                 t->tm_year = yearset + 1900;
529                 }
530                 t->tm_year -= 1900;     /* Convert to UNIX time. */
531                 /* FALLTHROUGH */
532         case 8:                         /* MMDDhhmm */
533                 t->tm_mon = ATOI2(arg);
534                 --t->tm_mon;            /* Convert from 01-12 to 00-11 */
535                 t->tm_mday = ATOI2(arg);
536                 t->tm_hour = ATOI2(arg);
537                 t->tm_min = ATOI2(arg);
538                 break;
539         case 4:                         /* hhmm */
540                 t->tm_hour = ATOI2(arg);
541                 t->tm_min = ATOI2(arg);
542                 break;
543         default:
544                 goto terr;
545         }
546         t->tm_isdst = -1;               /* Figure out DST. */
547         timet = mktime(t);
548         if (timet == -1)
549 terr:           errx(1,
550         "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
551         return timet;
552 }