]>
git.wh0rd.org - sysvinit.git/blob - src/last.c
2 * last.c Re-implementation of the 'last' command, this time
3 * for Linux. Yes I know there is BSD last, but I
4 * just felt like writing this. No thanks :-).
5 * Also, this version gives lots more info (especially with -x)
7 * Author: Miquel van Smoorenburg, miquels@cistron.nl
9 * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl
11 * This file is part of the sysvinit suite,
12 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
29 #include <sys/types.h>
31 #include <sys/fcntl.h>
43 #include <netinet/in.h>
45 #include <arpa/inet.h>
49 # define SHUTDOWN_TIME 254
52 char *Version
= "@(#) last 2.85 31-Apr-2004 miquels";
54 #define CHOP_DOMAIN 0 /* Define to chop off local domainname. */
55 #define NEW_UTMP 1 /* Fancy & fast utmp read code. */
56 #define UCHUNKSIZE 16384 /* How much we read at once. */
58 /* Double linked list of struct utmp's */
61 struct utmplist
*next
;
62 struct utmplist
*prev
;
64 struct utmplist
*utmplist
= NULL
;
66 /* Types of listing */
67 #define R_CRASH 1 /* No logout record, system boot in between */
68 #define R_DOWN 2 /* System brought down in decent way */
69 #define R_NORMAL 3 /* Normal */
70 #define R_NOW 4 /* Still logged in */
71 #define R_REBOOT 5 /* Reboot record. */
72 #define R_PHANTOM 6 /* No logout record but session is stale. */
73 #define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */
75 /* Global variables */
76 int maxrecs
= 0; /* Maximum number of records to list. */
77 int recsdone
= 0; /* Number of records listed */
78 int showhost
= 1; /* Show hostname too? */
79 int altlist
= 0; /* Show hostname at the end. */
80 int usedns
= 0; /* Use DNS to lookup the hostname. */
81 int useip
= 0; /* Print IP address in number format */
82 int fulltime
= 0; /* Print full dates and times */
83 int oldfmt
= 0; /* Use old libc5 format? */
84 char **show
= NULL
; /* What do they want us to show */
85 char *ufile
; /* Filename of this file */
86 time_t lastdate
; /* Last date we've seen */
87 char *progname
; /* Name of this program */
89 char hostname
[256]; /* For gethostbyname() */
90 char *domainname
; /* Our domainname. */
94 * Convert old utmp format to new.
96 void uconv(struct oldutmp
*oldut
, struct utmp
*utn
)
98 memset(utn
, 0, sizeof(struct utmp
));
99 utn
->ut_type
= oldut
->ut_type
;
100 utn
->ut_pid
= oldut
->ut_pid
;
101 utn
->ut_time
= oldut
->ut_oldtime
;
102 utn
->ut_addr
= oldut
->ut_oldaddr
;
103 strncpy(utn
->ut_line
, oldut
->ut_line
, OLD_LINESIZE
);
104 strncpy(utn
->ut_user
, oldut
->ut_user
, OLD_NAMESIZE
);
105 strncpy(utn
->ut_host
, oldut
->ut_host
, OLD_HOSTSIZE
);
110 * Read one utmp entry, return in new format.
111 * Automatically reposition file pointer.
113 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
116 static char buf
[UCHUNKSIZE
];
124 if (quit
== NULL
&& u
!= NULL
) {
129 r
= fread(&uto
, sizeof(uto
), 1, fp
);
132 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
138 * Initialize and position.
140 utsize
= oldfmt
? sizeof(uto
) : sizeof(struct utmp
);
141 fseeko(fp
, 0, SEEK_END
);
145 o
= ((fpos
- 1) / UCHUNKSIZE
) * UCHUNKSIZE
;
146 if (fseeko(fp
, o
, SEEK_SET
) < 0) {
147 fprintf(stderr
, "%s: seek failed!\n", progname
);
150 bpos
= (int)(fpos
- o
);
151 if (fread(buf
, bpos
, 1, fp
) != 1) {
152 fprintf(stderr
, "%s: read failed!\n", progname
);
160 * Read one struct. From the buffer if possible.
165 uconv((struct oldutmp
*)(buf
+ bpos
), u
);
167 memcpy(u
, buf
+ bpos
, sizeof(struct utmp
));
172 * Oops we went "below" the buffer. We should be able to
173 * seek back UCHUNKSIZE bytes.
180 * Copy whatever is left in the buffer.
182 memcpy(tmp
+ (-bpos
), buf
, utsize
+ bpos
);
183 if (fseeko(fp
, fpos
, SEEK_SET
) < 0) {
189 * Read another UCHUNKSIZE bytes.
191 if (fread(buf
, UCHUNKSIZE
, 1, fp
) != 1) {
197 * The end of the UCHUNKSIZE byte buffer should be the first
198 * few bytes of the current struct utmp.
200 memcpy(tmp
, buf
+ UCHUNKSIZE
+ bpos
, -bpos
);
204 uconv((struct oldutmp
*)tmp
, u
);
206 memcpy(u
, tmp
, sizeof(struct utmp
));
214 * Read one utmp entry, return in new format.
215 * Automatically reposition file pointer.
217 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
223 r
= oldfmt
? sizeof(struct oldutmp
) : sizeof(struct utmp
);
224 fseek(fp
, -1 * r
, SEEK_END
);
229 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
231 if (fseeko(fp
, -2 * sizeof(struct utmp
), SEEK_CUR
) < 0)
236 r
= fread(&uto
, sizeof(struct oldutmp
), 1, fp
);
238 if (fseeko(fp
, -2 * sizeof(struct oldutmp
), SEEK_CUR
) < 0)
248 * Try to be smart about the location of the BTMP file
251 #define BTMP_FILE getbtmp()
254 static char btmp
[128];
257 strcpy(btmp
, WTMP_FILE
);
258 if ((p
= strrchr(btmp
, '/')) == NULL
)
263 strcat(btmp
, "btmp");
269 * Print a short date.
273 char *s
= ctime(&lastdate
);
283 printf("Interrupted %s\n", showdate());
292 printf("Interrupted %s\n", showdate());
293 signal(SIGQUIT
, quit_handler
);
297 * Get the basename of a filename
299 char *mybasename(char *s
)
303 if ((p
= strrchr(s
, '/')) != NULL
)
311 * Lookup a host with DNS.
313 int dns_lookup(char *result
, int size
, int useip
, int32_t *a
)
315 struct sockaddr_in sin
;
316 struct sockaddr_in6 sin6
;
319 unsigned int topnibble
;
320 unsigned int azero
= 0, sitelocal
= 0;
323 flags
= useip
? NI_NUMERICHOST
: 0;
326 * IPv4 or IPv6 ? We use 2 heuristics:
327 * 1. Current IPv6 range uses 2000-3fff or fec0-feff.
328 * Outside of that is illegal and must be IPv4.
329 * 2. If last 3 bytes are 0, must be IPv4
330 * 3. If IPv6 in IPv4, handle as IPv4
334 if (a
[0] == 0 && a
[1] == 0 && a
[2] == htonl (0xffff))
336 topnibble
= ntohl((unsigned int)a
[0]) >> 28;
338 azero
= ntohl((unsigned int)a
[0]) >> 16;
339 sitelocal
= (azero
>= 0xfec0 && azero
<= 0xfeff) ? 1 : 0;
341 if (((topnibble
< 2 || topnibble
> 3) && (!sitelocal
)) || mapped
||
342 (a
[1] == 0 && a
[2] == 0 && a
[3] == 0)) {
344 sin
.sin_family
= AF_INET
;
346 sin
.sin_addr
.s_addr
= mapped
? a
[3] : a
[0];
347 sa
= (struct sockaddr
*)&sin
;
351 memset(&sin6
, 0, sizeof(sin6
));
352 sin6
.sin6_family
= AF_INET6
;
354 memcpy(sin6
.sin6_addr
.s6_addr
, a
, 16);
355 sa
= (struct sockaddr
*)&sin6
;
356 salen
= sizeof(sin6
);
359 return getnameinfo(sa
, salen
, result
, size
, NULL
, 0, flags
);
363 * Show one line of information on screen
365 int list(struct utmp
*p
, time_t t
, int what
)
372 char utline
[UT_LINESIZE
+1];
375 int mins
, hours
, days
;
379 * uucp and ftp have special-type entries
382 strncat(utline
, p
->ut_line
, UT_LINESIZE
);
383 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
385 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
389 * Is this something we wanna show?
392 for (walk
= show
; *walk
; walk
++) {
393 if (strncmp(p
->ut_name
, *walk
, UT_NAMESIZE
) == 0 ||
394 strcmp(utline
, *walk
) == 0 ||
395 (strncmp(utline
, "tty", 3) == 0 &&
396 strcmp(utline
+ 3, *walk
) == 0)) break;
398 if (*walk
== NULL
) return 0;
404 tmp
= (time_t)p
->ut_time
;
405 strcpy(logintime
, ctime(&tmp
));
407 sprintf(logouttime
, "- %s", ctime(&t
));
410 sprintf(logouttime
, "- %s", ctime(&t
) + 11);
413 secs
= t
- p
->ut_time
;
414 mins
= (secs
/ 60) % 60;
415 hours
= (secs
/ 3600) % 24;
418 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
420 sprintf(length
, " (%02d:%02d)", hours
, mins
);
424 sprintf(logouttime
, "- crash");
427 sprintf(logouttime
, "- down ");
432 sprintf(logouttime
, " still logged in");
434 sprintf(logouttime
, " still");
435 sprintf(length
, "logged in");
441 sprintf(logouttime
, " gone - no logout");
443 sprintf(logouttime
, " gone");
444 sprintf(length
, "- no logout");
458 * Look up host with DNS if needed.
462 r
= dns_lookup(domain
, sizeof(domain
), useip
, p
->ut_addr_v6
);
465 if (len
>= sizeof(domain
)) len
= sizeof(domain
) - 1;
467 strncat(domain
, p
->ut_host
, len
);
473 * See if this is in our domain.
475 if (!usedns
&& (s
= strchr(p
->ut_host
, '.')) != NULL
&&
476 strcmp(s
+ 1, domainname
) == 0) *s
= 0;
479 snprintf(final
, sizeof(final
),
481 "%-8.8s %-12.12s %-16.16s %-24.24s %-26.26s %-12.12s\n" :
482 "%-8.8s %-12.12s %-16.16s %-16.16s %-7.7s %-12.12s\n",
484 domain
, logintime
, logouttime
, length
);
486 snprintf(final
, sizeof(final
),
488 "%-8.8s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
489 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
491 logintime
, logouttime
, length
, domain
);
494 snprintf(final
, sizeof(final
),
496 "%-8.8s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
497 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s\n",
499 logintime
, logouttime
, length
);
502 * Print out "final" string safely.
504 for (s
= final
; *s
; s
++) {
505 if (*s
== '\n' || (*s
>= 32 && (unsigned char)*s
<= 126))
512 if (maxrecs
&& recsdone
>= maxrecs
)
524 fprintf(stderr
, "Usage: %s [-num | -n num] [-f file] "
525 "[-t YYYYMMDDHHMMSS] "
526 "[-R] [-adioxF] [username..] [tty..]\n", s
);
530 time_t parsetm(char *ts
)
535 memset(&tm
, 0, sizeof(tm
));
537 if (sscanf(ts
, "%4d%2d%2d%2d%2d%2d", &u
.tm_year
,
538 &u
.tm_mon
, &u
.tm_mday
, &u
.tm_hour
, &u
.tm_min
,
548 if ((tm
= mktime(&u
)) == (time_t)-1)
552 * Unfortunately mktime() is much more forgiving than
553 * it should be. For example, it'll gladly accept
554 * "30" as a valid month number. This behavior is by
555 * design, but we don't like it, so we want to detect
558 if (u
.tm_year
!= origu
.tm_year
||
559 u
.tm_mon
!= origu
.tm_mon
||
560 u
.tm_mday
!= origu
.tm_mday
||
561 u
.tm_hour
!= origu
.tm_hour
||
562 u
.tm_min
!= origu
.tm_min
||
563 u
.tm_sec
!= origu
.tm_sec
)
569 int main(int argc
, char **argv
)
571 FILE *fp
; /* Filepointer of wtmp file */
573 struct utmp ut
; /* Current utmp entry */
574 struct utmp oldut
; /* Old utmp entry to check for duplicates */
575 struct utmplist
*p
; /* Pointer into utmplist */
576 struct utmplist
*next
;/* Pointer into utmplist */
578 time_t lastboot
= 0; /* Last boottime */
579 time_t lastrch
= 0; /* Last run level change */
580 time_t lastdown
; /* Last downtime */
581 time_t begintime
; /* When wtmp begins */
582 int whydown
= 0; /* Why we went down: crash or shutdown */
584 int c
, x
; /* Scratch */
585 struct stat st
; /* To stat the [uw]tmp file */
586 int quit
= 0; /* Flag */
587 int down
= 0; /* Down flag */
588 int lastb
= 0; /* Is this 'lastb' ? */
589 int extended
= 0; /* Lots of info. */
590 char *altufile
= NULL
;/* Alternate wtmp */
592 time_t until
= 0; /* at what time to stop parsing the file */
594 progname
= mybasename(argv
[0]);
596 /* Process the arguments. */
597 while((c
= getopt(argc
, argv
, "f:n:RxadFiot:0123456789")) != EOF
)
606 maxrecs
= atoi(optarg
);
612 if((altufile
= malloc(strlen(optarg
)+1)) == NULL
) {
613 fprintf(stderr
, "%s: out of memory\n",
617 strcpy(altufile
, optarg
);
632 if ((until
= parsetm(optarg
)) == (time_t)-1) {
633 fprintf(stderr
, "%s: Invalid time value \"%s\"\n",
638 case '0': case '1': case '2': case '3': case '4':
639 case '5': case '6': case '7': case '8': case '9':
640 maxrecs
= 10*maxrecs
+ c
- '0';
646 if (optind
< argc
) show
= argv
+ optind
;
649 * Which file do we want to read?
651 if (strcmp(progname
, "lastb") == 0) {
668 * Find out domainname.
670 * This doesn't work on modern systems, where only a DNS
671 * lookup of the result from hostname() will get you the domainname.
672 * Remember that domainname() is the NIS domainname, not DNS.
673 * So basically this whole piece of code is bullshit.
676 (void) gethostname(hostname
, sizeof(hostname
));
677 if ((domainname
= strchr(hostname
, '.')) != NULL
) domainname
++;
678 if (domainname
== NULL
|| domainname
[0] == 0) {
680 (void) getdomainname(hostname
, sizeof(hostname
));
681 hostname
[sizeof(hostname
) - 1] = 0;
682 domainname
= hostname
;
683 if (strcmp(domainname
, "(none)") == 0 || domainname
[0] == 0)
689 * Install signal handlers
691 signal(SIGINT
, int_handler
);
692 signal(SIGQUIT
, quit_handler
);
697 if ((fp
= fopen(ufile
, "r")) == NULL
) {
699 fprintf(stderr
, "%s: %s: %s\n", progname
, ufile
, strerror(errno
));
700 if (altufile
== NULL
&& x
== ENOENT
)
701 fprintf(stderr
, "Perhaps this file was removed by the "
702 "operator to prevent logging %s info.\n", progname
);
707 * Optimize the buffer size.
709 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
712 * Read first structure to capture the time field
714 if (uread(fp
, &ut
, NULL
) == 1)
715 begintime
= ut
.ut_time
;
717 fstat(fileno(fp
), &st
);
718 begintime
= st
.st_ctime
;
723 * Go to end of file minus one structure
724 * and/or initialize utmp reading code.
726 uread(fp
, NULL
, NULL
);
729 * Read struct after struct backwards from the file.
733 if (uread(fp
, &ut
, &quit
) != 1)
736 if (until
&& until
< ut
.ut_time
)
739 if (memcmp(&ut
, &oldut
, sizeof(struct utmp
)) == 0) continue;
740 memcpy(&oldut
, &ut
, sizeof(struct utmp
));
741 lastdate
= ut
.ut_time
;
744 quit
= list(&ut
, ut
.ut_time
, R_NORMAL
);
749 * Set ut_type to the correct type.
751 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
752 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
753 ut
.ut_type
= SHUTDOWN_TIME
;
754 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
755 ut
.ut_type
= BOOT_TIME
;
756 else if (strncmp(ut
.ut_user
, "runlevel", 8) == 0)
757 ut
.ut_type
= RUN_LVL
;
761 * For stupid old applications that don't fill in
765 if (ut
.ut_type
!= DEAD_PROCESS
&&
766 ut
.ut_name
[0] && ut
.ut_line
[0] &&
767 strcmp(ut
.ut_name
, "LOGIN") != 0)
768 ut
.ut_type
= USER_PROCESS
;
770 * Even worse, applications that write ghost
771 * entries: ut_type set to USER_PROCESS but
774 if (ut
.ut_name
[0] == 0)
775 ut
.ut_type
= DEAD_PROCESS
;
780 if (strcmp(ut
.ut_name
, "date") == 0) {
781 if (ut
.ut_line
[0] == '|') ut
.ut_type
= OLD_TIME
;
782 if (ut
.ut_line
[0] == '{') ut
.ut_type
= NEW_TIME
;
787 switch (ut
.ut_type
) {
790 strcpy(ut
.ut_line
, "system down");
791 quit
= list(&ut
, lastboot
, R_NORMAL
);
793 lastdown
= lastrch
= ut
.ut_time
;
800 ut
.ut_type
== NEW_TIME
? "new time" :
802 quit
= list(&ut
, lastdown
, R_TIMECHANGE
);
806 strcpy(ut
.ut_line
, "system boot");
807 quit
= list(&ut
, lastdown
, R_REBOOT
);
808 lastboot
= ut
.ut_time
;
814 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
815 quit
= list(&ut
, lastrch
, R_NORMAL
);
817 if (x
== '0' || x
== '6') {
818 lastdown
= ut
.ut_time
;
820 ut
.ut_type
= SHUTDOWN_TIME
;
822 lastrch
= ut
.ut_time
;
827 * This was a login - show the first matching
828 * logout record and delete all records with
832 for (p
= utmplist
; p
; p
= next
) {
834 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
838 quit
= list(&ut
, p
->ut
.ut_time
,
842 if (p
->next
) p
->next
->prev
= p
->prev
;
844 p
->prev
->next
= p
->next
;
851 * Not found? Then crashed, down, still
852 * logged in, or missing logout record.
857 /* Is process still alive? */
859 kill(ut
.ut_pid
, 0) != 0 &&
864 quit
= list(&ut
, lastboot
, c
);
870 * Just store the data if it is
871 * interesting enough.
873 if (ut
.ut_line
[0] == 0)
875 if ((p
= malloc(sizeof(struct utmplist
))) == NULL
) {
876 fprintf(stderr
, "%s: out of memory\n",
880 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
883 if (utmplist
) utmplist
->prev
= p
;
889 * If we saw a shutdown/reboot record we can remove
890 * the entire current utmplist.
893 lastboot
= ut
.ut_time
;
894 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
895 for (p
= utmplist
; p
; p
= next
) {
903 printf("\n%s begins %s", mybasename(ufile
), ctime(&begintime
));
908 * Should we free memory here? Nah. This is not NT :)