]>
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
;
321 unsigned int topnibble
;
322 unsigned int azero
= 0, sitelocal
= 0;
325 flags
= useip
? NI_NUMERICHOST
: 0;
328 * IPv4 or IPv6 ? We use 2 heuristics:
329 * 1. Current IPv6 range uses 2000-3fff or fec0-feff.
330 * Outside of that is illegal and must be IPv4.
331 * 2. If last 3 bytes are 0, must be IPv4
332 * 3. If IPv6 in IPv4, handle as IPv4
336 if (a
[0] == 0 && a
[1] == 0 && a
[2] == (int32_t)htonl (0xffff))
338 topnibble
= ntohl((unsigned int)a
[0]) >> 28;
340 azero
= ntohl((unsigned int)a
[0]) >> 16;
341 sitelocal
= (azero
>= 0xfec0 && azero
<= 0xfeff) ? 1 : 0;
343 if (((topnibble
< 2 || topnibble
> 3) && (!sitelocal
)) || mapped
||
344 (a
[1] == 0 && a
[2] == 0 && a
[3] == 0)) {
346 sin
.sin_family
= AF_INET
;
348 sin
.sin_addr
.s_addr
= mapped
? a
[3] : a
[0];
349 sa
= (struct sockaddr
*)&sin
;
353 memset(&sin6
, 0, sizeof(sin6
));
354 sin6
.sin6_family
= AF_INET6
;
356 memcpy(sin6
.sin6_addr
.s6_addr
, a
, 16);
357 sa
= (struct sockaddr
*)&sin6
;
358 salen
= sizeof(sin6
);
361 return getnameinfo(sa
, salen
, result
, size
, NULL
, 0, flags
);
365 * Show one line of information on screen
367 int list(struct utmp
*p
, time_t t
, int what
)
374 char utline
[UT_LINESIZE
+1];
377 int mins
, hours
, days
;
381 * uucp and ftp have special-type entries
384 strncat(utline
, p
->ut_line
, UT_LINESIZE
);
385 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
387 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
391 * Is this something we wanna show?
394 for (walk
= show
; *walk
; walk
++) {
395 if (strncmp(p
->ut_name
, *walk
, UT_NAMESIZE
) == 0 ||
396 strcmp(utline
, *walk
) == 0 ||
397 (strncmp(utline
, "tty", 3) == 0 &&
398 strcmp(utline
+ 3, *walk
) == 0)) break;
400 if (*walk
== NULL
) return 0;
406 tmp
= (time_t)p
->ut_time
;
407 strcpy(logintime
, ctime(&tmp
));
409 sprintf(logouttime
, "- %s", ctime(&t
));
412 sprintf(logouttime
, "- %s", ctime(&t
) + 11);
415 secs
= t
- p
->ut_time
;
416 mins
= (secs
/ 60) % 60;
417 hours
= (secs
/ 3600) % 24;
420 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
422 sprintf(length
, " (%02d:%02d)", hours
, mins
);
426 sprintf(logouttime
, "- crash");
429 sprintf(logouttime
, "- down ");
434 sprintf(logouttime
, " still logged in");
436 sprintf(logouttime
, " still");
437 sprintf(length
, "logged in");
443 sprintf(logouttime
, " gone - no logout");
445 sprintf(logouttime
, " gone");
446 sprintf(length
, "- no logout");
460 * Look up host with DNS if needed.
464 r
= dns_lookup(domain
, sizeof(domain
), useip
, p
->ut_addr_v6
);
467 if (len
>= (int)sizeof(domain
)) len
= sizeof(domain
) - 1;
469 strncat(domain
, p
->ut_host
, len
);
475 * See if this is in our domain.
477 if (!usedns
&& (s
= strchr(p
->ut_host
, '.')) != NULL
&&
478 strcmp(s
+ 1, domainname
) == 0) *s
= 0;
481 len
= snprintf(final
, sizeof(final
),
483 "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
484 "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
485 name_len
, p
->ut_name
, utline
,
486 domain_len
, domain
, logintime
, logouttime
, length
);
488 len
= snprintf(final
, sizeof(final
),
490 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
491 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
492 name_len
, p
->ut_name
, utline
,
493 logintime
, logouttime
, length
, domain
);
496 len
= snprintf(final
, sizeof(final
),
498 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
499 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
500 name_len
, p
->ut_name
, utline
,
501 logintime
, logouttime
, length
);
503 #if defined(__GLIBC__)
504 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
505 final
[sizeof(final
)-1] = '\0';
510 * Print out "final" string safely.
512 for (s
= final
; *s
; s
++) {
513 if (*s
== '\n' || (*s
>= 32 && (unsigned char)*s
<= 126))
519 if (len
< 0 || (size_t)len
>= sizeof(final
))
523 if (maxrecs
&& recsdone
>= maxrecs
)
535 fprintf(stderr
, "Usage: %s [-num | -n num] [-f file] "
536 "[-t YYYYMMDDHHMMSS] "
537 "[-R] [-adioxFw] [username..] [tty..]\n", s
);
541 time_t parsetm(char *ts
)
546 memset(&tm
, 0, sizeof(tm
));
548 if (sscanf(ts
, "%4d%2d%2d%2d%2d%2d", &u
.tm_year
,
549 &u
.tm_mon
, &u
.tm_mday
, &u
.tm_hour
, &u
.tm_min
,
559 if ((tm
= mktime(&u
)) == (time_t)-1)
563 * Unfortunately mktime() is much more forgiving than
564 * it should be. For example, it'll gladly accept
565 * "30" as a valid month number. This behavior is by
566 * design, but we don't like it, so we want to detect
569 if (u
.tm_year
!= origu
.tm_year
||
570 u
.tm_mon
!= origu
.tm_mon
||
571 u
.tm_mday
!= origu
.tm_mday
||
572 u
.tm_hour
!= origu
.tm_hour
||
573 u
.tm_min
!= origu
.tm_min
||
574 u
.tm_sec
!= origu
.tm_sec
)
580 int main(int argc
, char **argv
)
582 FILE *fp
; /* Filepointer of wtmp file */
584 struct utmp ut
; /* Current utmp entry */
585 struct utmp oldut
; /* Old utmp entry to check for duplicates */
586 struct utmplist
*p
; /* Pointer into utmplist */
587 struct utmplist
*next
;/* Pointer into utmplist */
589 time_t lastboot
= 0; /* Last boottime */
590 time_t lastrch
= 0; /* Last run level change */
591 time_t lastdown
; /* Last downtime */
592 time_t begintime
; /* When wtmp begins */
593 int whydown
= 0; /* Why we went down: crash or shutdown */
595 int c
, x
; /* Scratch */
596 struct stat st
; /* To stat the [uw]tmp file */
597 int quit
= 0; /* Flag */
598 int down
= 0; /* Down flag */
599 int lastb
= 0; /* Is this 'lastb' ? */
600 int extended
= 0; /* Lots of info. */
601 char *altufile
= NULL
;/* Alternate wtmp */
603 time_t until
= 0; /* at what time to stop parsing the file */
605 progname
= mybasename(argv
[0]);
607 /* Process the arguments. */
608 while((c
= getopt(argc
, argv
, "f:n:RxadFiot:0123456789w")) != EOF
)
617 maxrecs
= atoi(optarg
);
623 if((altufile
= malloc(strlen(optarg
)+1)) == NULL
) {
624 fprintf(stderr
, "%s: out of memory\n",
628 strcpy(altufile
, optarg
);
643 if ((until
= parsetm(optarg
)) == (time_t)-1) {
644 fprintf(stderr
, "%s: Invalid time value \"%s\"\n",
650 if (UT_NAMESIZE
> name_len
)
651 name_len
= UT_NAMESIZE
;
652 if (UT_HOSTSIZE
> domain_len
)
653 domain_len
= UT_HOSTSIZE
;
655 case '0': case '1': case '2': case '3': case '4':
656 case '5': case '6': case '7': case '8': case '9':
657 maxrecs
= 10*maxrecs
+ c
- '0';
663 if (optind
< argc
) show
= argv
+ optind
;
666 * Which file do we want to read?
668 if (strcmp(progname
, "lastb") == 0) {
685 * Find out domainname.
687 * This doesn't work on modern systems, where only a DNS
688 * lookup of the result from hostname() will get you the domainname.
689 * Remember that domainname() is the NIS domainname, not DNS.
690 * So basically this whole piece of code is bullshit.
693 (void) gethostname(hostname
, sizeof(hostname
));
694 if ((domainname
= strchr(hostname
, '.')) != NULL
) domainname
++;
695 if (domainname
== NULL
|| domainname
[0] == 0) {
697 (void) getdomainname(hostname
, sizeof(hostname
));
698 hostname
[sizeof(hostname
) - 1] = 0;
699 domainname
= hostname
;
700 if (strcmp(domainname
, "(none)") == 0 || domainname
[0] == 0)
706 * Install signal handlers
708 signal(SIGINT
, int_handler
);
709 signal(SIGQUIT
, quit_handler
);
714 if ((fp
= fopen(ufile
, "r")) == NULL
) {
716 fprintf(stderr
, "%s: %s: %s\n", progname
, ufile
, strerror(errno
));
717 if (altufile
== NULL
&& x
== ENOENT
)
718 fprintf(stderr
, "Perhaps this file was removed by the "
719 "operator to prevent logging %s info.\n", progname
);
724 * Optimize the buffer size.
726 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
729 * Read first structure to capture the time field
731 if (uread(fp
, &ut
, NULL
) == 1)
732 begintime
= ut
.ut_time
;
734 fstat(fileno(fp
), &st
);
735 begintime
= st
.st_ctime
;
740 * Go to end of file minus one structure
741 * and/or initialize utmp reading code.
743 uread(fp
, NULL
, NULL
);
746 * Read struct after struct backwards from the file.
750 if (uread(fp
, &ut
, &quit
) != 1)
753 if (until
&& until
< ut
.ut_time
)
756 if (memcmp(&ut
, &oldut
, sizeof(struct utmp
)) == 0) continue;
757 memcpy(&oldut
, &ut
, sizeof(struct utmp
));
758 lastdate
= ut
.ut_time
;
761 quit
= list(&ut
, ut
.ut_time
, R_NORMAL
);
766 * Set ut_type to the correct type.
768 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
769 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
770 ut
.ut_type
= SHUTDOWN_TIME
;
771 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
772 ut
.ut_type
= BOOT_TIME
;
773 else if (strncmp(ut
.ut_user
, "runlevel", 8) == 0)
774 ut
.ut_type
= RUN_LVL
;
778 * For stupid old applications that don't fill in
782 if (ut
.ut_type
!= DEAD_PROCESS
&&
783 ut
.ut_name
[0] && ut
.ut_line
[0] &&
784 strcmp(ut
.ut_name
, "LOGIN") != 0)
785 ut
.ut_type
= USER_PROCESS
;
787 * Even worse, applications that write ghost
788 * entries: ut_type set to USER_PROCESS but
791 if (ut
.ut_name
[0] == 0)
792 ut
.ut_type
= DEAD_PROCESS
;
797 if (strcmp(ut
.ut_name
, "date") == 0) {
798 if (ut
.ut_line
[0] == '|') ut
.ut_type
= OLD_TIME
;
799 if (ut
.ut_line
[0] == '{') ut
.ut_type
= NEW_TIME
;
804 switch (ut
.ut_type
) {
807 strcpy(ut
.ut_line
, "system down");
808 quit
= list(&ut
, lastboot
, R_NORMAL
);
810 lastdown
= lastrch
= ut
.ut_time
;
817 ut
.ut_type
== NEW_TIME
? "new time" :
819 quit
= list(&ut
, lastdown
, R_TIMECHANGE
);
823 strcpy(ut
.ut_line
, "system boot");
824 quit
= list(&ut
, lastdown
, R_REBOOT
);
825 lastboot
= ut
.ut_time
;
831 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
832 quit
= list(&ut
, lastrch
, R_NORMAL
);
834 if (x
== '0' || x
== '6') {
835 lastdown
= ut
.ut_time
;
837 ut
.ut_type
= SHUTDOWN_TIME
;
839 lastrch
= ut
.ut_time
;
844 * This was a login - show the first matching
845 * logout record and delete all records with
849 for (p
= utmplist
; p
; p
= next
) {
851 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
855 quit
= list(&ut
, p
->ut
.ut_time
,
859 if (p
->next
) p
->next
->prev
= p
->prev
;
861 p
->prev
->next
= p
->next
;
868 * Not found? Then crashed, down, still
869 * logged in, or missing logout record.
874 /* Is process still alive? */
876 kill(ut
.ut_pid
, 0) != 0 &&
881 quit
= list(&ut
, lastboot
, c
);
887 * Just store the data if it is
888 * interesting enough.
890 if (ut
.ut_line
[0] == 0)
892 if ((p
= malloc(sizeof(struct utmplist
))) == NULL
) {
893 fprintf(stderr
, "%s: out of memory\n",
897 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
900 if (utmplist
) utmplist
->prev
= p
;
906 * If we saw a shutdown/reboot record we can remove
907 * the entire current utmplist.
910 lastboot
= ut
.ut_time
;
911 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
912 for (p
= utmplist
; p
; p
= next
) {
920 printf("\n%s begins %s", mybasename(ufile
), ctime(&begintime
));
925 * Should we free memory here? Nah. This is not NT :)