]>
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 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 strcpy(logintime
, ctime(&tmp
));
401 sprintf(logouttime
, "- %s", ctime(&t
));
404 sprintf(logouttime
, "- %s", ctime(&t
) + 11);
407 secs
= t
- p
->ut_time
;
408 mins
= (secs
/ 60) % 60;
409 hours
= (secs
/ 3600) % 24;
412 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
414 sprintf(length
, " (%02d:%02d)", hours
, mins
);
418 sprintf(logouttime
, "- crash");
421 sprintf(logouttime
, "- down ");
426 sprintf(logouttime
, " still logged in");
428 sprintf(logouttime
, " still");
429 sprintf(length
, "logged in");
435 sprintf(logouttime
, " gone - no logout");
437 sprintf(logouttime
, " gone");
438 sprintf(length
, "- no logout");
452 * Look up host with DNS if needed.
456 r
= dns_lookup(domain
, sizeof(domain
), useip
, p
->ut_addr_v6
);
459 if (len
>= (int)sizeof(domain
)) len
= sizeof(domain
) - 1;
461 strncat(domain
, p
->ut_host
, len
);
467 * See if this is in our domain.
469 if (!usedns
&& (s
= strchr(p
->ut_host
, '.')) != NULL
&&
470 strcmp(s
+ 1, domainname
) == 0) *s
= 0;
473 len
= snprintf(final
, sizeof(final
),
475 "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
476 "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
477 name_len
, p
->ut_name
, utline
,
478 domain_len
, domain
, logintime
, logouttime
, length
);
480 len
= snprintf(final
, sizeof(final
),
482 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
483 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
484 name_len
, p
->ut_name
, utline
,
485 logintime
, logouttime
, length
, domain
);
488 len
= snprintf(final
, sizeof(final
),
490 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
491 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
492 name_len
, p
->ut_name
, utline
,
493 logintime
, logouttime
, length
);
495 #if defined(__GLIBC__)
496 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
497 final
[sizeof(final
)-1] = '\0';
502 * Print out "final" string safely.
504 for (s
= final
; *s
; s
++) {
505 if (*s
== '\n' || (*s
>= 32 && (unsigned char)*s
<= 126))
511 if (len
< 0 || (size_t)len
>= sizeof(final
))
515 if (maxrecs
&& recsdone
>= maxrecs
)
527 fprintf(stderr
, "Usage: %s [-num | -n num] [-f file] "
528 "[-t YYYYMMDDHHMMSS] "
529 "[-R] [-adioxFw] [username..] [tty..]\n", s
);
533 time_t parsetm(char *ts
)
538 memset(&tm
, 0, sizeof(tm
));
540 if (sscanf(ts
, "%4d%2d%2d%2d%2d%2d", &u
.tm_year
,
541 &u
.tm_mon
, &u
.tm_mday
, &u
.tm_hour
, &u
.tm_min
,
551 if ((tm
= mktime(&u
)) == (time_t)-1)
555 * Unfortunately mktime() is much more forgiving than
556 * it should be. For example, it'll gladly accept
557 * "30" as a valid month number. This behavior is by
558 * design, but we don't like it, so we want to detect
561 if (u
.tm_year
!= origu
.tm_year
||
562 u
.tm_mon
!= origu
.tm_mon
||
563 u
.tm_mday
!= origu
.tm_mday
||
564 u
.tm_hour
!= origu
.tm_hour
||
565 u
.tm_min
!= origu
.tm_min
||
566 u
.tm_sec
!= origu
.tm_sec
)
572 int main(int argc
, char **argv
)
574 FILE *fp
; /* Filepointer of wtmp file */
576 struct utmp ut
; /* Current utmp entry */
577 struct utmp oldut
; /* Old utmp entry to check for duplicates */
578 struct utmplist
*p
; /* Pointer into utmplist */
579 struct utmplist
*next
;/* Pointer into utmplist */
581 time_t lastboot
= 0; /* Last boottime */
582 time_t lastrch
= 0; /* Last run level change */
583 time_t lastdown
; /* Last downtime */
584 time_t begintime
; /* When wtmp begins */
585 int whydown
= 0; /* Why we went down: crash or shutdown */
587 int c
, x
; /* Scratch */
588 struct stat st
; /* To stat the [uw]tmp file */
589 int quit
= 0; /* Flag */
590 int down
= 0; /* Down flag */
591 int lastb
= 0; /* Is this 'lastb' ? */
592 int extended
= 0; /* Lots of info. */
593 char *altufile
= NULL
;/* Alternate wtmp */
595 time_t until
= 0; /* at what time to stop parsing the file */
597 progname
= mybasename(argv
[0]);
599 /* Process the arguments. */
600 while((c
= getopt(argc
, argv
, "f:n:RxadFiot:0123456789w")) != EOF
)
609 maxrecs
= atoi(optarg
);
615 if((altufile
= malloc(strlen(optarg
)+1)) == NULL
) {
616 fprintf(stderr
, "%s: out of memory\n",
620 strcpy(altufile
, optarg
);
635 if ((until
= parsetm(optarg
)) == (time_t)-1) {
636 fprintf(stderr
, "%s: Invalid time value \"%s\"\n",
642 if (UT_NAMESIZE
> name_len
)
643 name_len
= UT_NAMESIZE
;
644 if (UT_HOSTSIZE
> domain_len
)
645 domain_len
= UT_HOSTSIZE
;
647 case '0': case '1': case '2': case '3': case '4':
648 case '5': case '6': case '7': case '8': case '9':
649 maxrecs
= 10*maxrecs
+ c
- '0';
655 if (optind
< argc
) show
= argv
+ optind
;
658 * Which file do we want to read?
660 if (strcmp(progname
, "lastb") == 0) {
677 * Find out domainname.
679 * This doesn't work on modern systems, where only a DNS
680 * lookup of the result from hostname() will get you the domainname.
681 * Remember that domainname() is the NIS domainname, not DNS.
682 * So basically this whole piece of code is bullshit.
685 (void) gethostname(hostname
, sizeof(hostname
));
686 if ((domainname
= strchr(hostname
, '.')) != NULL
) domainname
++;
687 if (domainname
== NULL
|| domainname
[0] == 0) {
689 (void) getdomainname(hostname
, sizeof(hostname
));
690 hostname
[sizeof(hostname
) - 1] = 0;
691 domainname
= hostname
;
692 if (strcmp(domainname
, "(none)") == 0 || domainname
[0] == 0)
698 * Install signal handlers
700 signal(SIGINT
, int_handler
);
701 signal(SIGQUIT
, quit_handler
);
706 if ((fp
= fopen(ufile
, "r")) == NULL
) {
708 fprintf(stderr
, "%s: %s: %s\n", progname
, ufile
, strerror(errno
));
709 if (altufile
== NULL
&& x
== ENOENT
)
710 fprintf(stderr
, "Perhaps this file was removed by the "
711 "operator to prevent logging %s info.\n", progname
);
716 * Optimize the buffer size.
718 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
721 * Read first structure to capture the time field
723 if (uread(fp
, &ut
, NULL
) == 1)
724 begintime
= ut
.ut_time
;
726 fstat(fileno(fp
), &st
);
727 begintime
= st
.st_ctime
;
732 * Go to end of file minus one structure
733 * and/or initialize utmp reading code.
735 uread(fp
, NULL
, NULL
);
738 * Read struct after struct backwards from the file.
742 if (uread(fp
, &ut
, &quit
) != 1)
745 if (until
&& until
< ut
.ut_time
)
748 if (memcmp(&ut
, &oldut
, sizeof(struct utmp
)) == 0) continue;
749 memcpy(&oldut
, &ut
, sizeof(struct utmp
));
750 lastdate
= ut
.ut_time
;
753 quit
= list(&ut
, ut
.ut_time
, R_NORMAL
);
758 * Set ut_type to the correct type.
760 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
761 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
762 ut
.ut_type
= SHUTDOWN_TIME
;
763 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
764 ut
.ut_type
= BOOT_TIME
;
765 else if (strncmp(ut
.ut_user
, "runlevel", 8) == 0)
766 ut
.ut_type
= RUN_LVL
;
770 * For stupid old applications that don't fill in
774 if (ut
.ut_type
!= DEAD_PROCESS
&&
775 ut
.ut_name
[0] && ut
.ut_line
[0] &&
776 strcmp(ut
.ut_name
, "LOGIN") != 0)
777 ut
.ut_type
= USER_PROCESS
;
779 * Even worse, applications that write ghost
780 * entries: ut_type set to USER_PROCESS but
783 if (ut
.ut_name
[0] == 0)
784 ut
.ut_type
= DEAD_PROCESS
;
789 if (strcmp(ut
.ut_name
, "date") == 0) {
790 if (ut
.ut_line
[0] == '|') ut
.ut_type
= OLD_TIME
;
791 if (ut
.ut_line
[0] == '{') ut
.ut_type
= NEW_TIME
;
796 switch (ut
.ut_type
) {
799 strcpy(ut
.ut_line
, "system down");
800 quit
= list(&ut
, lastboot
, R_NORMAL
);
802 lastdown
= lastrch
= ut
.ut_time
;
809 ut
.ut_type
== NEW_TIME
? "new time" :
811 quit
= list(&ut
, lastdown
, R_TIMECHANGE
);
815 strcpy(ut
.ut_line
, "system boot");
816 quit
= list(&ut
, lastdown
, R_REBOOT
);
817 lastboot
= ut
.ut_time
;
823 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
824 quit
= list(&ut
, lastrch
, R_NORMAL
);
826 if (x
== '0' || x
== '6') {
827 lastdown
= ut
.ut_time
;
829 ut
.ut_type
= SHUTDOWN_TIME
;
831 lastrch
= ut
.ut_time
;
836 * This was a login - show the first matching
837 * logout record and delete all records with
841 for (p
= utmplist
; p
; p
= next
) {
843 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
847 quit
= list(&ut
, p
->ut
.ut_time
,
851 if (p
->next
) p
->next
->prev
= p
->prev
;
853 p
->prev
->next
= p
->next
;
860 * Not found? Then crashed, down, still
861 * logged in, or missing logout record.
866 /* Is process still alive? */
868 kill(ut
.ut_pid
, 0) != 0 &&
873 quit
= list(&ut
, lastboot
, c
);
879 * Just store the data if it is
880 * interesting enough.
882 if (ut
.ut_line
[0] == 0)
884 if ((p
= malloc(sizeof(struct utmplist
))) == NULL
) {
885 fprintf(stderr
, "%s: out of memory\n",
889 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
892 if (utmplist
) utmplist
->prev
= p
;
898 * If we saw a shutdown/reboot record we can remove
899 * the entire current utmplist.
902 lastboot
= ut
.ut_time
;
903 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
904 for (p
= utmplist
; p
; p
= next
) {
912 printf("\n%s begins %s", mybasename(ufile
), ctime(&begintime
));
917 * Should we free memory here? Nah. This is not NT :)