]>
cygwin.com Git - cygwin-apps/cygutils.git/blob - src-gpl/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.79 13-Jun-2001 miquels@cistron.nl
11 * This file is part of the sysvinit suite,
12 * Copyright 1991-2001 Miquel van Smoorenburg.
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version
17 * 2 of the License, or (at your option) any later version.
20 #include <sys/types.h>
22 #include <sys/fcntl.h>
34 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <sys/socket.h>
40 #define RUN_LVL 1 /* The system's runlevel. */
41 #define BOOT_TIME 2 /* Time of system boot. */
42 #define NEW_TIME 3 /* Time after system clock changed. */
43 #define OLD_TIME 4 /* Time when system clock changed. */
46 # define SHUTDOWN_TIME 254
49 char *Version
= "@(#) last 2.79 11-Sep-2000 miquels";
51 #define CHOP_DOMAIN 0 /* Define to chop off local domainname. */
52 #define NEW_UTMP 1 /* Fancy & fast utmp read code. */
53 #define UCHUNKSIZE 16384 /* How much we read at once. */
55 /* Double linked list of struct utmp's */
58 struct utmplist
*next
;
59 struct utmplist
*prev
;
61 struct utmplist
*utmplist
= NULL
;
63 /* Types of listing */
64 #define R_CRASH 1 /* No logout record, system boot in between */
65 #define R_DOWN 2 /* System brought down in decent way */
66 #define R_NORMAL 3 /* Normal */
67 #define R_NOW 4 /* Still logged in */
68 #define R_REBOOT 5 /* Reboot record. */
69 #define R_PHANTOM 6 /* No logout record but session is stale. */
70 #define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */
72 /* Global variables */
73 int maxrecs
= 0; /* Maximum number of records to list. */
74 int recsdone
= 0; /* Number of records listed */
75 int showhost
= 1; /* Show hostname too? */
76 int altlist
= 0; /* Show hostname at the end. */
77 int usedns
= 0; /* Use DNS to lookup the hostname. */
78 int useip
= 0; /* Print IP address in number format */
79 int oldfmt
= 0; /* Use old libc5 format? */
80 char **show
= NULL
; /* What do they want us to show */
81 char *ufile
; /* Filename of this file */
82 time_t lastdate
; /* Last date we've seen */
83 char *progname
; /* Name of this program */
85 char hostname
[256]; /* For gethostbyname() */
86 char *domainname
; /* Our domainname. */
90 * Convert old utmp format to new.
92 void uconv(struct oldutmp
*oldut
, struct utmp
*utn
)
94 memset(utn
, 0, sizeof(struct utmp
));
95 utn
->ut_type
= oldut
->ut_type
;
96 utn
->ut_pid
= oldut
->ut_pid
;
97 utn
->ut_time
= oldut
->ut_oldtime
;
98 utn
->ut_addr
= oldut
->ut_oldaddr
;
99 strncpy(utn
->ut_line
, oldut
->ut_line
, OLD_LINESIZE
);
100 strncpy(utn
->ut_user
, oldut
->ut_user
, OLD_NAMESIZE
);
101 strncpy(utn
->ut_host
, oldut
->ut_host
, OLD_HOSTSIZE
);
106 * Read one utmp entry, return in new format.
107 * Automatically reposition file pointer.
109 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
112 static char buf
[UCHUNKSIZE
];
120 if (quit
== NULL
&& u
!= NULL
) {
125 r
= fread(&uto
, sizeof(uto
), 1, fp
);
128 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
134 * Initialize and position.
136 utsize
= oldfmt
? sizeof(uto
) : sizeof(struct utmp
);
137 fseek(fp
, 0L, SEEK_END
);
141 o
= ((fpos
- 1) / UCHUNKSIZE
) * UCHUNKSIZE
;
142 if (fseek(fp
, o
, SEEK_SET
) < 0) {
143 fprintf(stderr
, "%s: seek failed!\n", progname
);
147 if (fread(buf
, bpos
, 1, fp
) != 1) {
148 fprintf(stderr
, "%s: read failed!\n", progname
);
156 * Read one struct. From the buffer if possible.
161 uconv((struct oldutmp
*)(buf
+ bpos
), u
);
163 memcpy(u
, buf
+ bpos
, sizeof(struct utmp
));
168 * Oops we went "below" the buffer. We should be able to
169 * seek back UCHUNKSIZE bytes.
176 * Copy whatever is left in the buffer.
178 memcpy(tmp
+ (-bpos
), buf
, utsize
+ bpos
);
179 if (fseek(fp
, fpos
, SEEK_SET
) < 0) {
185 * Read another UCHUNKSIZE bytes.
187 if (fread(buf
, UCHUNKSIZE
, 1, fp
) != 1) {
193 * The end of the UCHUNKSIZE byte buffer should be the first
194 * few bytes of the current struct utmp.
196 memcpy(tmp
, buf
+ UCHUNKSIZE
+ bpos
, -bpos
);
200 uconv((struct oldutmp
*)tmp
, u
);
202 memcpy(u
, tmp
, sizeof(struct utmp
));
210 * Read one utmp entry, return in new format.
211 * Automatically reposition file pointer.
213 int uread(FILE *fp
, struct utmp
*u
, int *quit
)
219 r
= oldfmt
? sizeof(struct oldutmp
) : sizeof(struct utmp
);
220 fseek(fp
, -1L * r
, SEEK_END
);
225 r
= fread(u
, sizeof(struct utmp
), 1, fp
);
227 if (fseek(fp
, -2L * sizeof(struct utmp
), SEEK_CUR
) < 0)
232 r
= fread(&uto
, sizeof(struct oldutmp
), 1, fp
);
234 if (fseek(fp
, -2L * sizeof(struct oldutmp
), SEEK_CUR
) < 0)
244 * Try to be smart about the location of the BTMP file
247 #define WTMP_FILE "/var/log/wtmp"
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
, char *org
, unsigned int ip
)
318 * Try to catch illegal IP numbers
320 if (ip
== 0 || (int)ip
== -1 || (ip
>> 24) == 0 || (ip
& 255) == 0) {
321 if (size
> UT_HOSTSIZE
) size
= UT_HOSTSIZE
+1;
322 strncpy(result
, org
, size
- 1);
327 if ((h
= gethostbyaddr((char *)&ip
, 4, AF_INET
)) == NULL
) {
328 strncpy(result
, inet_ntoa(*(struct in_addr
*)&ip
), size
);
332 strncpy(result
, h
->h_name
, size
-1);
339 * Show one line of information on screen
341 int list(struct utmp
*p
, time_t t
, int what
)
348 char utline
[UT_LINESIZE
+1];
351 int mins
, hours
, days
;
355 * uucp and ftp have special-type entries
357 strncpy(utline
, p
->ut_line
, UT_LINESIZE
);
358 utline
[UT_LINESIZE
- 1] = 0;
359 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
361 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
365 * Is this something we wanna show?
368 for (walk
= show
; *walk
; walk
++) {
369 if (strncmp(p
->ut_name
, *walk
, UT_NAMESIZE
) == 0 ||
370 strcmp(utline
, *walk
) == 0 ||
371 (strncmp(utline
, "tty", 3) == 0 &&
372 strcmp(utline
+ 3, *walk
) == 0)) break;
374 if (*walk
== NULL
) return 0;
380 tmp
= (time_t)p
->ut_time
;
381 strcpy(logintime
, ctime(&tmp
));
383 sprintf(logouttime
, "- %s", ctime(&t
) + 11);
385 secs
= t
- p
->ut_time
;
386 mins
= (secs
/ 60) % 60;
387 hours
= (secs
/ 3600) % 24;
390 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
392 sprintf(length
, " (%02d:%02d)", hours
, mins
);
396 sprintf(logouttime
, "- crash");
399 sprintf(logouttime
, "- down ");
403 sprintf(logouttime
, " still");
404 sprintf(length
, "logged in");
408 sprintf(logouttime
, " gone");
409 sprintf(length
, "- no logout");
412 logouttime
[0] = 0; /* Print machine uptime */
423 * Look up host with DNS if needed.
426 dns_lookup(domain
, sizeof(domain
), p
->ut_host
, p
->ut_addr
);
428 in
.s_addr
= p
->ut_addr
;
429 strcpy(domain
, inet_ntoa(in
));
431 strncpy(domain
, p
->ut_host
, UT_HOSTSIZE
);
432 domain
[UT_HOSTSIZE
-1] = 0;
438 * See if this is in our domain.
440 if (!usedns
&& (s
= strchr(p
->ut_host
, '.')) != NULL
&&
441 strcmp(s
+ 1, domainname
) == 0) *s
= 0;
444 snprintf(final
, sizeof(final
),
445 "%-8.8s %-12.12s %-16.16s "
446 "%-16.16s %-7.7s %-12.12s\n",
448 domain
, logintime
, logouttime
, length
);
450 snprintf(final
, sizeof(final
),
451 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
453 logintime
, logouttime
, length
, domain
);
456 snprintf(final
, sizeof(final
),
457 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s\n",
459 logintime
, logouttime
, length
);
462 * Print out "final" string safely.
464 for (s
= final
; *s
; s
++) {
465 if (*s
== '\n' || (*s
>= 32 && (unsigned char)*s
<= 126))
472 if (maxrecs
&& recsdone
>= maxrecs
)
484 fprintf(stderr
, "Usage: %s [-num | -n num] [-f file] "
485 "[-R] [-x] [-o] [username..] [tty..]\n", s
);
489 int main(int argc
, char **argv
)
491 FILE *fp
; /* Filepointer of wtmp file */
493 struct utmp ut
; /* Current utmp entry */
494 struct utmp oldut
; /* Old utmp entry to check for duplicates */
495 struct utmplist
*p
; /* Pointer into utmplist */
496 struct utmplist
*next
;/* Pointer into utmplist */
498 time_t lastboot
= 0; /* Last boottime */
499 time_t lastrch
= 0; /* Last run level change */
500 time_t lastdown
; /* Last downtime */
501 time_t begintime
; /* When wtmp begins */
502 int whydown
= 0; /* Why we went down: crash or shutdown */
504 int c
, x
; /* Scratch */
505 struct stat st
; /* To stat the [uw]tmp file */
506 int quit
= 0; /* Flag */
507 int down
= 0; /* Down flag */
508 int lastb
= 0; /* Is this 'lastb' ? */
509 int extended
= 0; /* Lots of info. */
510 char *altufile
= NULL
;/* Alternate wtmp */
512 progname
= mybasename(argv
[0]);
514 /* Process the arguments. */
515 while((c
= getopt(argc
, argv
, "f:n:Rxadio0123456789")) != EOF
)
524 maxrecs
= atoi(optarg
);
530 if((altufile
= malloc(strlen(optarg
)+1)) == NULL
) {
531 fprintf(stderr
, "%s: out of memory\n",
535 strcpy(altufile
, optarg
);
546 case '0': case '1': case '2': case '3': case '4':
547 case '5': case '6': case '7': case '8': case '9':
548 maxrecs
= 10*maxrecs
+ c
- '0';
554 if (optind
< argc
) show
= argv
+ optind
;
557 * Which file do we want to read?
559 if (strcmp(progname
, "lastb") == 0) {
576 * Find out domainname.
578 * This doesn't work on modern systems, where only a DNS
579 * lookup of the result from hostname() will get you the domainname.
580 * Remember that domainname() is the NIS domainname, not DNS.
581 * So basically this whole piece of code is bullshit.
584 (void) gethostname(hostname
, sizeof(hostname
));
585 if ((domainname
= strchr(hostname
, '.')) != NULL
) domainname
++;
586 if (domainname
== NULL
|| domainname
[0] == 0) {
588 (void) getdomainname(hostname
, sizeof(hostname
));
589 hostname
[sizeof(hostname
) - 1] = 0;
590 domainname
= hostname
;
591 if (strcmp(domainname
, "(none)") == 0 || domainname
[0] == 0)
597 * Install signal handlers
599 signal(SIGINT
, int_handler
);
600 signal(SIGQUIT
, quit_handler
);
605 if ((fp
= fopen(ufile
, "r")) == NULL
) {
607 fprintf(stderr
, "%s: %s: %s\n", progname
, ufile
, strerror(errno
));
608 if (altufile
== NULL
&& x
== ENOENT
)
609 fprintf(stderr
, "Perhaps this file was removed by the "
610 "operator to prevent logging %s info.\n", progname
);
615 * Optimize the buffer size.
617 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
620 * Read first structure to capture the time field
622 if (uread(fp
, &ut
, NULL
) == 1)
623 begintime
= ut
.ut_time
;
625 fstat(fileno(fp
), &st
);
626 begintime
= st
.st_ctime
;
631 * Go to end of file minus one structure
632 * and/or initialize utmp reading code.
634 uread(fp
, NULL
, NULL
);
637 * Read struct after struct backwards from the file.
641 if (uread(fp
, &ut
, &quit
) != 1)
644 if (memcmp(&ut
, &oldut
, sizeof(struct utmp
)) == 0) continue;
645 memcpy(&oldut
, &ut
, sizeof(struct utmp
));
646 lastdate
= ut
.ut_time
;
649 quit
= list(&ut
, ut
.ut_time
, R_NORMAL
);
654 * Set ut_type to the correct type.
656 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
657 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
658 ut
.ut_type
= SHUTDOWN_TIME
;
659 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
660 ut
.ut_type
= BOOT_TIME
;
661 else if (strncmp(ut
.ut_user
, "runlevel", 7) == 0)
662 ut
.ut_type
= RUN_LVL
;
666 * For stupid old applications that don't fill in
670 if (ut
.ut_type
!= DEAD_PROCESS
&&
671 ut
.ut_name
[0] && ut
.ut_line
[0] &&
672 strcmp(ut
.ut_name
, "LOGIN") != 0)
673 ut
.ut_type
= USER_PROCESS
;
675 * Even worse, applications that write ghost
676 * entries: ut_type set to USER_PROCESS but
679 if (ut
.ut_name
[0] == 0)
680 ut
.ut_type
= DEAD_PROCESS
;
685 if (strcmp(ut
.ut_name
, "date") == 0) {
686 if (ut
.ut_line
[0] == '|') ut
.ut_type
= OLD_TIME
;
687 if (ut
.ut_line
[0] == '{') ut
.ut_type
= NEW_TIME
;
692 switch (ut
.ut_type
) {
695 strcpy(ut
.ut_line
, "system down");
696 quit
= list(&ut
, lastdown
, R_NORMAL
);
698 lastdown
= lastrch
= ut
.ut_time
;
705 ut
.ut_type
== NEW_TIME
? "new time" :
707 quit
= list(&ut
, lastdown
, R_TIMECHANGE
);
711 strcpy(ut
.ut_line
, "system boot");
712 quit
= list(&ut
, lastdown
, R_REBOOT
);
718 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
719 quit
= list(&ut
, lastrch
, R_NORMAL
);
721 if (x
== '0' || x
== '6') {
722 lastdown
= ut
.ut_time
;
724 ut
.ut_type
= SHUTDOWN_TIME
;
726 lastrch
= ut
.ut_time
;
731 * This was a login - show the first matching
732 * logout record and delete all records with
736 for (p
= utmplist
; p
; p
= next
) {
738 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
742 quit
= list(&ut
, p
->ut
.ut_time
,
746 if (p
->next
) p
->next
->prev
= p
->prev
;
748 p
->prev
->next
= p
->next
;
755 * Not found? Then crashed, down, still
756 * logged in, or missing logout record.
761 /* Is process still alive? */
763 kill(ut
.ut_pid
, 0) != 0 &&
768 quit
= list(&ut
, lastboot
, c
);
774 * Just store the data if it is
775 * interesting enough.
777 if (ut
.ut_line
[0] == 0)
779 if ((p
= malloc(sizeof(struct utmplist
))) == NULL
) {
780 fprintf(stderr
, "%s: out of memory\n",
784 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
787 if (utmplist
) utmplist
->prev
= p
;
793 * If we saw a shutdown/reboot record we can remove
794 * the entire current utmplist.
797 lastboot
= ut
.ut_time
;
798 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
799 for (p
= utmplist
; p
; p
= next
) {
807 printf("\n%s begins %s", mybasename(ufile
), ctime(&begintime
));
812 * Should we free memory here? Nah. This is not NT :)
This page took 0.190918 seconds and 5 git commands to generate.