]>
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>
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 name_len
= 8; /* Default print 8 characters of name */
84 int domain_len
= 16; /* Default print 16 characters of domain */
85 int oldfmt
= 0; /* Use old libc5 format? */
86 char **show
= NULL
; /* What do they want us to show */
87 char *ufile
; /* Filename of this file */
88 time_t lastdate
; /* Last date we've seen */
89 char *progname
; /* Name of this program */
91 char hostname
[256]; /* For gethostbyname() */
92 char *domainname
; /* Our domainname. */
96 * Convert old utmp format to new.
98 void uconv(struct oldutmp
*oldut
, struct utmp
*utn
)
100 memset(utn
, 0, sizeof(struct utmp
));
101 utn
->ut_type
= oldut
->ut_type
;
102 utn
->ut_pid
= oldut
->ut_pid
;
103 utn
->ut_time
= oldut
->ut_oldtime
;
104 utn
->ut_addr
= oldut
->ut_oldaddr
;
105 strncpy(utn
->ut_line
, oldut
->ut_line
, OLD_LINESIZE
);
106 strncpy(utn
->ut_user
, oldut
->ut_user
, OLD_NAMESIZE
);
107 strncpy(utn
->ut_host
, oldut
->ut_host
, OLD_HOSTSIZE
);
112 * Read one utmp entry, return in new format.
113 * Automatically reposition file pointer.
115 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
118 static char buf
[UCHUNKSIZE
];
126 if (quit
== NULL
&& u
!= NULL
) {
131 r
= fread(&uto
, sizeof(uto
), 1, fp
);
134 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
140 * Initialize and position.
142 utsize
= oldfmt
? sizeof(uto
) : sizeof(struct utmp
);
143 fseeko(fp
, 0, SEEK_END
);
147 o
= ((fpos
- 1) / UCHUNKSIZE
) * UCHUNKSIZE
;
148 if (fseeko(fp
, o
, SEEK_SET
) < 0) {
149 fprintf(stderr
, "%s: seek failed!\n", progname
);
152 bpos
= (int)(fpos
- o
);
153 if (fread(buf
, bpos
, 1, fp
) != 1) {
154 fprintf(stderr
, "%s: read failed!\n", progname
);
162 * Read one struct. From the buffer if possible.
167 uconv((struct oldutmp
*)(buf
+ bpos
), u
);
169 memcpy(u
, buf
+ bpos
, sizeof(struct utmp
));
174 * Oops we went "below" the buffer. We should be able to
175 * seek back UCHUNKSIZE bytes.
182 * Copy whatever is left in the buffer.
184 memcpy(tmp
+ (-bpos
), buf
, utsize
+ bpos
);
185 if (fseeko(fp
, fpos
, SEEK_SET
) < 0) {
191 * Read another UCHUNKSIZE bytes.
193 if (fread(buf
, UCHUNKSIZE
, 1, fp
) != 1) {
199 * The end of the UCHUNKSIZE byte buffer should be the first
200 * few bytes of the current struct utmp.
202 memcpy(tmp
, buf
+ UCHUNKSIZE
+ bpos
, -bpos
);
206 uconv((struct oldutmp
*)tmp
, u
);
208 memcpy(u
, tmp
, sizeof(struct utmp
));
216 * Read one utmp entry, return in new format.
217 * Automatically reposition file pointer.
219 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
225 r
= oldfmt
? sizeof(struct oldutmp
) : sizeof(struct utmp
);
226 fseek(fp
, -1 * r
, SEEK_END
);
231 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
233 if (fseeko(fp
, -2 * sizeof(struct utmp
), SEEK_CUR
) < 0)
238 r
= fread(&uto
, sizeof(struct oldutmp
), 1, fp
);
240 if (fseeko(fp
, -2 * sizeof(struct oldutmp
), SEEK_CUR
) < 0)
250 * Try to be smart about the location of the BTMP file
253 #define BTMP_FILE getbtmp()
256 static char btmp
[128];
259 strcpy(btmp
, WTMP_FILE
);
260 if ((p
= strrchr(btmp
, '/')) == NULL
)
265 strcat(btmp
, "btmp");
271 * Print a short date.
275 char *s
= ctime(&lastdate
);
285 printf("Interrupted %s\n", showdate());
294 printf("Interrupted %s\n", showdate());
295 signal(SIGQUIT
, quit_handler
);
299 * Get the basename of a filename
301 char *mybasename(char *s
)
305 if ((p
= strrchr(s
, '/')) != NULL
)
313 * Lookup a host with DNS.
315 int dns_lookup(char *result
, int size
, int useip
, int32_t *a
)
317 struct sockaddr_in sin
;
318 struct sockaddr_in6 sin6
;
323 flags
= useip
? NI_NUMERICHOST
: 0;
327 * 1. If last 3 4bytes are 0, must be IPv4
328 * 2. If IPv6 in IPv4, handle as IPv4
329 * 3. Anything else is IPv6
333 if (a
[0] == 0 && a
[1] == 0 && a
[2] == (int32_t)htonl (0xffff))
336 if (mapped
|| (a
[1] == 0 && a
[2] == 0 && a
[3] == 0)) {
338 sin
.sin_family
= AF_INET
;
340 sin
.sin_addr
.s_addr
= mapped
? a
[3] : a
[0];
341 sa
= (struct sockaddr
*)&sin
;
345 memset(&sin6
, 0, sizeof(sin6
));
346 sin6
.sin6_family
= AF_INET6
;
348 memcpy(sin6
.sin6_addr
.s6_addr
, a
, 16);
349 sa
= (struct sockaddr
*)&sin6
;
350 salen
= sizeof(sin6
);
353 return getnameinfo(sa
, salen
, result
, size
, NULL
, 0, flags
);
357 * Show one line of information on screen
359 int list(struct utmp
*p
, time_t t
, int what
)
366 char utline
[UT_LINESIZE
+1];
369 int mins
, hours
, days
;
373 * uucp and ftp have special-type entries
376 strncat(utline
, p
->ut_line
, UT_LINESIZE
);
377 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
379 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
383 * Is this something we wanna show?
386 for (walk
= show
; *walk
; walk
++) {
387 if (strncmp(p
->ut_name
, *walk
, UT_NAMESIZE
) == 0 ||
388 strcmp(utline
, *walk
) == 0 ||
389 (strncmp(utline
, "tty", 3) == 0 &&
390 strcmp(utline
+ 3, *walk
) == 0)) break;
392 if (*walk
== NULL
) return 0;
398 tmp
= (time_t)p
->ut_time
;
399 strncpy(logintime
, ctime(&tmp
), sizeof(logintime
));
400 logintime
[sizeof(logintime
)-1] = 0; /* enforce null termination */
402 sprintf(logouttime
, "- %s", ctime(&t
));
405 sprintf(logouttime
, "- %s", ctime(&t
) + 11);
408 secs
= t
- p
->ut_time
;
409 mins
= (secs
/ 60) % 60;
410 hours
= (secs
/ 3600) % 24;
413 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
415 sprintf(length
, " (%02d:%02d)", hours
, mins
);
419 sprintf(logouttime
, "- crash");
422 sprintf(logouttime
, "- down ");
427 sprintf(logouttime
, " still logged in");
429 sprintf(logouttime
, " still");
430 sprintf(length
, "logged in");
436 sprintf(logouttime
, " gone - no logout");
438 sprintf(logouttime
, " gone");
439 sprintf(length
, "- no logout");
453 * Look up host with DNS if needed.
457 r
= dns_lookup(domain
, sizeof(domain
), useip
, p
->ut_addr_v6
);
460 if (len
>= (int)sizeof(domain
)) len
= sizeof(domain
) - 1;
462 strncat(domain
, p
->ut_host
, len
);
468 * See if this is in our domain.
470 if (!usedns
&& (s
= strchr(p
->ut_host
, '.')) != NULL
&&
471 strcmp(s
+ 1, domainname
) == 0) *s
= 0;
474 len
= snprintf(final
, sizeof(final
),
476 "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
477 "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
478 name_len
, p
->ut_name
, utline
,
479 domain_len
, domain
, logintime
, logouttime
, length
);
481 len
= snprintf(final
, sizeof(final
),
483 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
484 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
485 name_len
, p
->ut_name
, utline
,
486 logintime
, logouttime
, length
, domain
);
489 len
= snprintf(final
, sizeof(final
),
491 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
492 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
493 name_len
, p
->ut_name
, utline
,
494 logintime
, logouttime
, length
);
496 #if defined(__GLIBC__)
497 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
498 final
[sizeof(final
)-1] = '\0';
503 * Print out "final" string safely.
505 for (s
= final
; *s
; s
++) {
506 if (*s
== '\n' || (*s
>= 32 && (unsigned char)*s
<= 126))
512 if (len
< 0 || (size_t)len
>= sizeof(final
))
516 if (maxrecs
&& recsdone
>= maxrecs
)
528 fprintf(stderr
, "Usage: %s [-num | -n num] [-f file] "
529 "[-t YYYYMMDDHHMMSS] "
530 "[-R] [-adioxFw] [username..] [tty..]\n", s
);
534 time_t parsetm(char *ts
)
539 memset(&tm
, 0, sizeof(tm
));
541 if (sscanf(ts
, "%4d%2d%2d%2d%2d%2d", &u
.tm_year
,
542 &u
.tm_mon
, &u
.tm_mday
, &u
.tm_hour
, &u
.tm_min
,
552 if ((tm
= mktime(&u
)) == (time_t)-1)
556 * Unfortunately mktime() is much more forgiving than
557 * it should be. For example, it'll gladly accept
558 * "30" as a valid month number. This behavior is by
559 * design, but we don't like it, so we want to detect
562 if (u
.tm_year
!= origu
.tm_year
||
563 u
.tm_mon
!= origu
.tm_mon
||
564 u
.tm_mday
!= origu
.tm_mday
||
565 u
.tm_hour
!= origu
.tm_hour
||
566 u
.tm_min
!= origu
.tm_min
||
567 u
.tm_sec
!= origu
.tm_sec
)
573 int main(int argc
, char **argv
)
575 FILE *fp
; /* Filepointer of wtmp file */
577 struct utmp ut
; /* Current utmp entry */
578 struct utmp oldut
; /* Old utmp entry to check for duplicates */
579 struct utmplist
*p
; /* Pointer into utmplist */
580 struct utmplist
*next
;/* Pointer into utmplist */
582 time_t lastboot
= 0; /* Last boottime */
583 time_t lastrch
= 0; /* Last run level change */
584 time_t lastdown
; /* Last downtime */
585 time_t begintime
; /* When wtmp begins */
586 int whydown
= 0; /* Why we went down: crash or shutdown */
588 int c
, x
; /* Scratch */
589 struct stat st
; /* To stat the [uw]tmp file */
590 int quit
= 0; /* Flag */
591 int down
= 0; /* Down flag */
592 int lastb
= 0; /* Is this 'lastb' ? */
593 int extended
= 0; /* Lots of info. */
594 char *altufile
= NULL
;/* Alternate wtmp */
596 time_t until
= 0; /* at what time to stop parsing the file */
598 progname
= mybasename(argv
[0]);
600 /* Process the arguments. */
601 while((c
= getopt(argc
, argv
, "f:n:RxadFiot:0123456789w")) != EOF
)
610 maxrecs
= atoi(optarg
);
616 if((altufile
= malloc(strlen(optarg
)+1)) == NULL
) {
617 fprintf(stderr
, "%s: out of memory\n",
621 strcpy(altufile
, optarg
);
636 if ((until
= parsetm(optarg
)) == (time_t)-1) {
637 fprintf(stderr
, "%s: Invalid time value \"%s\"\n",
643 if (UT_NAMESIZE
> name_len
)
644 name_len
= UT_NAMESIZE
;
645 if (UT_HOSTSIZE
> domain_len
)
646 domain_len
= UT_HOSTSIZE
;
648 case '0': case '1': case '2': case '3': case '4':
649 case '5': case '6': case '7': case '8': case '9':
650 maxrecs
= 10*maxrecs
+ c
- '0';
656 if (optind
< argc
) show
= argv
+ optind
;
659 * Which file do we want to read?
661 if (strcmp(progname
, "lastb") == 0) {
678 * Find out domainname.
680 * This doesn't work on modern systems, where only a DNS
681 * lookup of the result from hostname() will get you the domainname.
682 * Remember that domainname() is the NIS domainname, not DNS.
683 * So basically this whole piece of code is bullshit.
686 (void) gethostname(hostname
, sizeof(hostname
));
687 if ((domainname
= strchr(hostname
, '.')) != NULL
) domainname
++;
688 if (domainname
== NULL
|| domainname
[0] == 0) {
690 (void) getdomainname(hostname
, sizeof(hostname
));
691 hostname
[sizeof(hostname
) - 1] = 0;
692 domainname
= hostname
;
693 if (strcmp(domainname
, "(none)") == 0 || domainname
[0] == 0)
699 * Install signal handlers
701 signal(SIGINT
, int_handler
);
702 signal(SIGQUIT
, quit_handler
);
707 if ((fp
= fopen(ufile
, "r")) == NULL
) {
709 fprintf(stderr
, "%s: %s: %s\n", progname
, ufile
, strerror(errno
));
710 if (altufile
== NULL
&& x
== ENOENT
)
711 fprintf(stderr
, "Perhaps this file was removed by the "
712 "operator to prevent logging %s info.\n", progname
);
717 * Optimize the buffer size.
719 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
722 * Read first structure to capture the time field
724 if (uread(fp
, &ut
, NULL
) == 1)
725 begintime
= ut
.ut_time
;
727 fstat(fileno(fp
), &st
);
728 begintime
= st
.st_ctime
;
733 * Go to end of file minus one structure
734 * and/or initialize utmp reading code.
736 uread(fp
, NULL
, NULL
);
739 * Read struct after struct backwards from the file.
743 if (uread(fp
, &ut
, &quit
) != 1)
746 if (until
&& until
< ut
.ut_time
)
749 if (memcmp(&ut
, &oldut
, sizeof(struct utmp
)) == 0) continue;
750 memcpy(&oldut
, &ut
, sizeof(struct utmp
));
751 lastdate
= ut
.ut_time
;
754 quit
= list(&ut
, ut
.ut_time
, R_NORMAL
);
759 * Set ut_type to the correct type.
761 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
762 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
763 ut
.ut_type
= SHUTDOWN_TIME
;
764 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
765 ut
.ut_type
= BOOT_TIME
;
766 else if (strncmp(ut
.ut_user
, "runlevel", 8) == 0)
767 ut
.ut_type
= RUN_LVL
;
771 * For stupid old applications that don't fill in
775 if (ut
.ut_type
!= DEAD_PROCESS
&&
776 ut
.ut_name
[0] && ut
.ut_line
[0] &&
777 strcmp(ut
.ut_name
, "LOGIN") != 0)
778 ut
.ut_type
= USER_PROCESS
;
780 * Even worse, applications that write ghost
781 * entries: ut_type set to USER_PROCESS but
784 if (ut
.ut_name
[0] == 0)
785 ut
.ut_type
= DEAD_PROCESS
;
790 if (strcmp(ut
.ut_name
, "date") == 0) {
791 if (ut
.ut_line
[0] == '|') ut
.ut_type
= OLD_TIME
;
792 if (ut
.ut_line
[0] == '{') ut
.ut_type
= NEW_TIME
;
797 switch (ut
.ut_type
) {
800 strcpy(ut
.ut_line
, "system down");
801 quit
= list(&ut
, lastboot
, R_NORMAL
);
803 lastdown
= lastrch
= ut
.ut_time
;
810 ut
.ut_type
== NEW_TIME
? "new time" :
812 quit
= list(&ut
, lastdown
, R_TIMECHANGE
);
816 strcpy(ut
.ut_line
, "system boot");
817 quit
= list(&ut
, lastdown
, R_REBOOT
);
818 lastboot
= ut
.ut_time
;
824 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
825 quit
= list(&ut
, lastrch
, R_NORMAL
);
827 if (x
== '0' || x
== '6') {
828 lastdown
= ut
.ut_time
;
830 ut
.ut_type
= SHUTDOWN_TIME
;
832 lastrch
= ut
.ut_time
;
837 * This was a login - show the first matching
838 * logout record and delete all records with
842 for (p
= utmplist
; p
; p
= next
) {
844 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
848 quit
= list(&ut
, p
->ut
.ut_time
,
852 if (p
->next
) p
->next
->prev
= p
->prev
;
854 p
->prev
->next
= p
->next
;
861 * Not found? Then crashed, down, still
862 * logged in, or missing logout record.
867 /* Is process still alive? */
869 kill(ut
.ut_pid
, 0) != 0 &&
874 quit
= list(&ut
, lastboot
, c
);
880 * Just store the data if it is
881 * interesting enough.
883 if (ut
.ut_line
[0] == 0)
885 if ((p
= malloc(sizeof(struct utmplist
))) == NULL
) {
886 fprintf(stderr
, "%s: out of memory\n",
890 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
893 if (utmplist
) utmplist
->prev
= p
;
899 * If we saw a shutdown/reboot record we can remove
900 * the entire current utmplist.
903 lastboot
= ut
.ut_time
;
904 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
905 for (p
= utmplist
; p
; p
= next
) {
913 printf("\n%s begins %s", mybasename(ufile
), ctime(&begintime
));
918 * Should we free memory here? Nah. This is not NT :)