1 /* last - display login history Author: Terrence W. Holm */
3 /* last- Display the user log-in history.
4 * Last(1) searches backwards through the file of log-in
5 * records (/usr/adm/wtmp), displaying the length of
6 * log-in sessions as requested by the options:
8 * Usage: last [-r] [-count] [-f file] [name] [tty] ...
10 * -r Search backwards only until the last reboot
13 * -count Only print out <count> records. Last(1) stops
14 * when either -r or -count is satisfied, or at
15 * the end of the file if neither is given.
17 * -f file Use "file" instead of "/usr/adm/wtmp".
19 * name Print records for the user "name".
21 * tty Print records for the terminal "tty". Actually,
22 * a list of names may be given and all records
23 * that match either the user or tty name are
24 * printed. If no names are given then all records
27 * A sigquit (^\) causes last(1) to display how far it
28 * has gone back in the log-in record file, it then
29 * continues. This is used to check on the progress of
30 * long running searches. A sigint will stop last(1).
32 * Author: Terrence W. Holm May 1988
35 * Fred van Kempen, October 1989
37 * -Adapted to new utmp database.
39 * Fred van Kempen, December 1989
40 * -Adapted to POSIX (MINIX 1.5)
42 * Fred van Kempen, January 1990
45 * Philip Homburg, March 1992
46 * -Include host in output
48 * Kees J. Bot, July 1997
49 * -Approximate system uptime from last reboot record
51 #include <sys/types.h>
66 #define BUFFER_SIZE 4096 /* Room for wtmp records */
67 #define MAX_WTMP_COUNT ( BUFFER_SIZE / sizeof(struct utmp) )
69 #define min( a, b ) ( (a < b) ? a : b )
70 #define max( a, b ) ( (a > b) ? a : b )
73 typedef struct logout
{ /* A logout time record */
74 char line
[12]; /* The terminal name */
75 long time
; /* The logout time */
76 struct logout
*next
; /* Next in linked list */
80 static char *Version
= "@(#) LAST 1.7 (10/24/92)";
83 /* command-line option flags */
84 char boot_limit
= FALSE
; /* stop on latest reboot */
85 char count_limit
= FALSE
; /* stop after print_count */
86 char tell_uptime
= FALSE
; /* tell uptime since last reboot */
88 char *prog
; /* name of this program */
89 int arg_count
; /* used to select specific */
90 char **args
; /* users and ttys */
92 /* global variables */
93 long boot_time
= 0; /* Zero means no reboot yet */
94 char *boot_down
; /* "crash" or "down " flag */
95 logout
*first_link
= NULL
; /* List of logout times */
96 int interrupt
= FALSE
; /* If sigint or sigquit occurs */
98 int main(int argc
, char **argv
);
100 void Sigquit(int sig
);
102 void Process(struct utmp
*wtmp
);
103 int Print_Record(struct utmp
*wtmp
);
104 void Print_Duration(long from
, long to
);
105 void Print_Uptime(void);
106 void Record_Logout_Time(struct utmp
*wtmp
);
108 /* Sigint() and Sigquit() Flag occurrence of an interrupt. */
126 "Usage: last [-r] [-u] [-count] [-f file] [name] [tty] ...\n");
131 /* A log-in record format file contains four types of records.
133 * [1] generated on a system reboot:
135 * line="~", name="reboot", host="", time=date()
138 * [2] generated after a shutdown:
140 * line="~", name="shutdown", host="", time=date()
143 * [3] generated on a successful login(1)
145 * line=ttyname(), name=cuserid(), host=, time=date()
148 * [4] generated by init(8) on a logout
150 * line=ttyname(), name="", host="", time=date()
153 * Note: This version of last(1) does not recognize the '|' and '}' time
154 * change records. Last(1) pairs up line login's and logout's to
155 * generate four types of output lines:
157 * [1] a system reboot or shutdown
159 * reboot ~ Mon May 16 14:16
160 * shutdown ~ Mon May 16 14:15
162 * [2] a login with a matching logout
164 * edwin tty1 Thu May 26 20:05 - 20:32 (00:27)
166 * [3] a login followed by a reboot or shutdown
168 * root tty0 Mon May 16 13:57 - crash (00:19)
169 * root tty1 Mon May 16 13:45 - down (00:30)
171 * [4] a login not followed by a logout or reboot
173 * terry tty0 Thu May 26 21:19 still logged in
182 /* suppress the job number on an "ftp" line */
183 if (!strncmp(wtmp
->ut_line
, "ftp", (size_t)3)) strncpy(wtmp
->ut_line
, "ftp", (size_t)8);
185 if (!strcmp(wtmp
->ut_line
, "~")) {
186 /* A reboot or shutdown record */
187 if (boot_limit
) exit(0);
189 if (Print_Record(wtmp
)) putchar('\n');
190 boot_time
= wtmp
->ut_time
;
192 is_reboot
= !strcmp(wtmp
->ut_name
, "reboot");
201 "%s: no reboot record added to wtmp file on system boot!\n",
209 /* remove any logout records */
210 for (link
= first_link
; link
!= NULL
; link
= next_link
) {
211 next_link
= link
->next
;
215 } else if (wtmp
->ut_name
[0] == '\0') {
216 /* A logout record */
217 Record_Logout_Time(wtmp
);
220 for (link
= first_link
; link
!= NULL
; link
= link
->next
)
221 if (!strncmp(link
->line
, wtmp
->ut_line
, (size_t)8)) {
222 /* found corresponding logout record */
223 if (Print_Record(wtmp
)) {
224 printf("- %.5s ", ctime(&link
->time
) + 11);
225 Print_Duration(wtmp
->ut_time
, link
->time
);
227 /* record login time */
228 link
->time
= wtmp
->ut_time
;
231 /* could not find a logout record for this login tty */
232 if (Print_Record(wtmp
))
233 if (boot_time
== 0) /* still on */
234 printf(" still logged in\n");
235 else { /* system crashed while on */
236 printf("- %s ", boot_down
);
237 Print_Duration(wtmp
->ut_time
, boot_time
);
239 Record_Logout_Time(wtmp
); /* Needed in case of 2
240 * consecutive logins */
245 /* Print_Record(wtmp) If the record was requested, then print out
246 * the user name, terminal, host and time.
248 int Print_Record(wtmp
)
252 char print_flag
= FALSE
;
254 /* just interested in the uptime? */
255 if (tell_uptime
) return(FALSE
);
257 /* check if we have already printed the requested number of records */
258 if (count_limit
&& print_count
== 0) exit(0);
260 for (i
= 0; i
< arg_count
; ++i
)
261 if (!strncmp(args
[i
], wtmp
->ut_name
, sizeof(wtmp
->ut_name
)) ||
262 !strncmp(args
[i
], wtmp
->ut_line
, sizeof(wtmp
->ut_line
)))
265 if (arg_count
== 0 || print_flag
) {
267 printf("%-8.8s %-8.8s %-16.16s %.16s ",
268 wtmp
->ut_name
, wtmp
->ut_line
, wtmp
->ut_host
,
269 ctime(&wtmp
->ut_time
));
271 printf("%-8.8s %-8.8s %.16s ",
272 wtmp
->ut_name
, wtmp
->ut_line
, ctime(&wtmp
->ut_time
));
281 /* Print_Duration(from, to) Calculate and print the days and hh:mm between
282 * the log-in and the log-out.
284 void Print_Duration(from
, to
)
288 long delta
, days
, hours
, minutes
;
290 delta
= max(to
- from
, 0);
291 days
= delta
/ (24L * 60L * 60L);
292 delta
= delta
% (24L * 60L * 60L);
293 hours
= delta
/ (60L * 60L);
294 delta
= delta
% (60L * 60L);
295 minutes
= delta
/ 60L;
298 printf("(%ld+", days
);
302 printf("%02ld:%02ld)\n", hours
, minutes
);
306 /* Print_Uptime() Calculate and print the "uptime" between the last recorded
307 * boot and the current time.
313 double loads
[NLOADS
];
314 char *utmp_file
= _PATH_UTMP
;
322 /* Count the number of active users in the utmp file. */
323 if ((uf
= fopen(utmp_file
, "r")) == NULL
) {
324 fprintf(stderr
, "%s: %s: %s\n", prog
, utmp_file
, strerror(errno
));
329 while (fread(&ut
, sizeof(ut
), 1, uf
) == 1) {
331 if (ut
.ut_type
== USER_PROCESS
) nusers
++;
333 if (ut
.ut_name
[0] != 0 && ut
.ut_line
[0] != 0) nusers
++;
339 now
= time((time_t *) NULL
);
340 tm
= localtime(&now
);
343 up
= now
- boot_time
;
345 printf(" %d:%02d up", tm
->tm_hour
, tm
->tm_min
);
346 if (up
>= 24 * 3600L) {
347 unsigned long days
= up
/ (24 * 3600L);
348 printf(" %lu day%s,", days
, days
== 1 ? "" : "s");
350 printf(" %lu:%02lu,", (up
% (24 * 3600L)) / 3600, (up
% 3600) / 60);
351 printf(" %u user%s", nusers
, nusers
== 1 ? "" : "s");
352 if((nloads
= getloadavg(loads
, NLOADS
)) > 0) {
354 printf(", load averages:");
355 for(i
= 0; i
< nloads
; i
++)
356 printf("%s %.2f", (i
> 0) ? "," : "", loads
[i
]);
362 /* Record_Logout_Time(wtmp) A linked list of "last logout time" is kept.
363 * Each element of the list is for one terminal.
365 void Record_Logout_Time(wtmp
)
370 /* see if the terminal is already in the list */
371 for (link
= first_link
; link
!= NULL
; link
= link
->next
)
372 if (!strncmp(link
->line
, wtmp
->ut_line
, (size_t)8)) {
373 link
->time
= wtmp
->ut_time
;
376 /* allocate a new logout record, for a tty not previously encountered */
377 link
= (logout
*) malloc(sizeof(logout
));
379 fprintf(stderr
, "%s: malloc failure\n", prog
);
382 strncpy(link
->line
, wtmp
->ut_line
, (size_t)8);
383 link
->time
= wtmp
->ut_time
;
384 link
->next
= first_link
;
394 char *wtmp_file
= _PATH_WTMP
;
396 long size
; /* Number of wtmp records in the file */
397 int wtmp_count
; /* How many to read into wtmp_buffer */
398 struct utmp wtmp_buffer
[MAX_WTMP_COUNT
];
400 if ((prog
= strrchr(argv
[0], '/')) == NULL
) prog
= argv
[0]; else prog
++;
405 while (argc
> 0 && *argv
[0] == '-') {
406 if (!strcmp(argv
[0], "-r"))
409 if (!strcmp(argv
[0], "-u"))
411 else if (argc
> 1 && !strcmp(argv
[0], "-f")) {
415 } else if ((print_count
= atoi(argv
[0] + 1)) > 0)
427 if (!strcmp(prog
, "uptime")) tell_uptime
= TRUE
;
429 if ((f
= fopen(wtmp_file
, "r")) == NULL
) {
433 if (fseek(f
, 0L, 2) != 0 || (size
= ftell(f
)) % sizeof(struct utmp
) != 0) {
434 fprintf(stderr
, "%s: invalid wtmp file\n", prog
);
437 if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
) {
438 signal(SIGINT
, Sigint
);
439 signal(SIGQUIT
, Sigquit
);
441 size
/= sizeof(struct utmp
); /* Number of records in wtmp */
443 if (size
== 0) wtmp_buffer
[0].ut_time
= time((time_t *)0);
446 wtmp_count
= (int) min(size
, MAX_WTMP_COUNT
);
447 size
-= (long) wtmp_count
;
449 fseek(f
, size
* sizeof(struct utmp
), 0);
452 if (fread(&wtmp_buffer
[0], sizeof(struct utmp
), (size_t)wtmp_count
, f
)
454 fprintf(stderr
, "%s: read error on wtmp file\n", prog
);
457 while (--wtmp_count
>= 0) {
458 Process(&wtmp_buffer
[wtmp_count
]);
460 printf("\ninterrupted %.16s \n",
461 ctime(&wtmp_buffer
[wtmp_count
].ut_time
));
463 if (interrupt
== SIGINT
) exit(2);
466 signal(SIGQUIT
, Sigquit
);
470 } /* end while(size > 0) */
474 "%s: no reboot record in wtmp file to compute uptime from\n",
479 printf("\nwtmp begins %.16s \n", ctime(&wtmp_buffer
[0].ut_time
));