1 | /* last - display login history Author: Terrence W. Holm */
|
---|
2 |
|
---|
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:
|
---|
7 | *
|
---|
8 | * Usage: last [-r] [-count] [-f file] [name] [tty] ...
|
---|
9 | *
|
---|
10 | * -r Search backwards only until the last reboot
|
---|
11 | * record.
|
---|
12 | *
|
---|
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.
|
---|
16 | *
|
---|
17 | * -f file Use "file" instead of "/usr/adm/wtmp".
|
---|
18 | *
|
---|
19 | * name Print records for the user "name".
|
---|
20 | *
|
---|
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
|
---|
25 | * are displayed.
|
---|
26 | *
|
---|
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).
|
---|
31 | *
|
---|
32 | * Author: Terrence W. Holm May 1988
|
---|
33 | *
|
---|
34 | * Revision:
|
---|
35 | * Fred van Kempen, October 1989
|
---|
36 | * -Adapted to MSS.
|
---|
37 | * -Adapted to new utmp database.
|
---|
38 | *
|
---|
39 | * Fred van Kempen, December 1989
|
---|
40 | * -Adapted to POSIX (MINIX 1.5)
|
---|
41 | *
|
---|
42 | * Fred van Kempen, January 1990
|
---|
43 | * -Final edit for 1.5
|
---|
44 | *
|
---|
45 | * Philip Homburg, March 1992
|
---|
46 | * -Include host in output
|
---|
47 | *
|
---|
48 | * Kees J. Bot, July 1997
|
---|
49 | * -Approximate system uptime from last reboot record
|
---|
50 | */
|
---|
51 | #include <sys/types.h>
|
---|
52 | #include <signal.h>
|
---|
53 | #include <string.h>
|
---|
54 | #include <utmp.h>
|
---|
55 | #include <time.h>
|
---|
56 | #include <stdlib.h>
|
---|
57 | #include <stdio.h>
|
---|
58 | #include <errno.h>
|
---|
59 |
|
---|
60 | #include <minix/paths.h>
|
---|
61 |
|
---|
62 | #define FALSE 0
|
---|
63 | #define TRUE 1
|
---|
64 | #define RLOGIN 1
|
---|
65 |
|
---|
66 | #define BUFFER_SIZE 4096 /* Room for wtmp records */
|
---|
67 | #define MAX_WTMP_COUNT ( BUFFER_SIZE / sizeof(struct utmp) )
|
---|
68 |
|
---|
69 | #define min( a, b ) ( (a < b) ? a : b )
|
---|
70 | #define max( a, b ) ( (a > b) ? a : b )
|
---|
71 |
|
---|
72 |
|
---|
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 */
|
---|
77 | } logout;
|
---|
78 |
|
---|
79 |
|
---|
80 | static char *Version = "@(#) LAST 1.7 (10/24/92)";
|
---|
81 |
|
---|
82 |
|
---|
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 */
|
---|
87 | int print_count;
|
---|
88 | char *prog; /* name of this program */
|
---|
89 | int arg_count; /* used to select specific */
|
---|
90 | char **args; /* users and ttys */
|
---|
91 |
|
---|
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 */
|
---|
97 |
|
---|
98 | _PROTOTYPE(int main, (int argc, char **argv));
|
---|
99 | _PROTOTYPE(void Sigint, (int sig));
|
---|
100 | _PROTOTYPE(void Sigquit, (int sig));
|
---|
101 | _PROTOTYPE(void usage, (void));
|
---|
102 | _PROTOTYPE(void Process, (struct utmp *wtmp));
|
---|
103 | _PROTOTYPE(int Print_Record, (struct utmp *wtmp));
|
---|
104 | _PROTOTYPE(void Print_Duration, (long from, long to));
|
---|
105 | _PROTOTYPE(void Print_Uptime, (void));
|
---|
106 | _PROTOTYPE(void Record_Logout_Time, (struct utmp *wtmp));
|
---|
107 |
|
---|
108 | /* Sigint() and Sigquit() Flag occurrence of an interrupt. */
|
---|
109 | void Sigint(sig)
|
---|
110 | int sig;
|
---|
111 | {
|
---|
112 | interrupt = SIGINT;
|
---|
113 | }
|
---|
114 |
|
---|
115 |
|
---|
116 | void Sigquit(sig)
|
---|
117 | int sig;
|
---|
118 | {
|
---|
119 | interrupt = SIGQUIT;
|
---|
120 | }
|
---|
121 |
|
---|
122 |
|
---|
123 | void usage()
|
---|
124 | {
|
---|
125 | fprintf(stderr,
|
---|
126 | "Usage: last [-r] [-u] [-count] [-f file] [name] [tty] ...\n");
|
---|
127 | exit(-1);
|
---|
128 | }
|
---|
129 |
|
---|
130 |
|
---|
131 | /* A log-in record format file contains four types of records.
|
---|
132 | *
|
---|
133 | * [1] generated on a system reboot:
|
---|
134 | *
|
---|
135 | * line="~", name="reboot", host="", time=date()
|
---|
136 | *
|
---|
137 | *
|
---|
138 | * [2] generated after a shutdown:
|
---|
139 | *
|
---|
140 | * line="~", name="shutdown", host="", time=date()
|
---|
141 | *
|
---|
142 | *
|
---|
143 | * [3] generated on a successful login(1)
|
---|
144 | *
|
---|
145 | * line=ttyname(), name=cuserid(), host=, time=date()
|
---|
146 | *
|
---|
147 | *
|
---|
148 | * [4] generated by init(8) on a logout
|
---|
149 | *
|
---|
150 | * line=ttyname(), name="", host="", time=date()
|
---|
151 | *
|
---|
152 | *
|
---|
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:
|
---|
156 | *
|
---|
157 | * [1] a system reboot or shutdown
|
---|
158 | *
|
---|
159 | * reboot ~ Mon May 16 14:16
|
---|
160 | * shutdown ~ Mon May 16 14:15
|
---|
161 | *
|
---|
162 | * [2] a login with a matching logout
|
---|
163 | *
|
---|
164 | * edwin tty1 Thu May 26 20:05 - 20:32 (00:27)
|
---|
165 | *
|
---|
166 | * [3] a login followed by a reboot or shutdown
|
---|
167 | *
|
---|
168 | * root tty0 Mon May 16 13:57 - crash (00:19)
|
---|
169 | * root tty1 Mon May 16 13:45 - down (00:30)
|
---|
170 | *
|
---|
171 | * [4] a login not followed by a logout or reboot
|
---|
172 | *
|
---|
173 | * terry tty0 Thu May 26 21:19 still logged in
|
---|
174 | */
|
---|
175 | void Process(wtmp)
|
---|
176 | struct utmp *wtmp;
|
---|
177 | {
|
---|
178 | logout *link;
|
---|
179 | logout *next_link;
|
---|
180 | char is_reboot;
|
---|
181 |
|
---|
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);
|
---|
184 |
|
---|
185 | if (!strcmp(wtmp->ut_line, "~")) {
|
---|
186 | /* A reboot or shutdown record */
|
---|
187 | if (boot_limit) exit(0);
|
---|
188 |
|
---|
189 | if (Print_Record(wtmp)) putchar('\n');
|
---|
190 | boot_time = wtmp->ut_time;
|
---|
191 |
|
---|
192 | is_reboot = !strcmp(wtmp->ut_name, "reboot");
|
---|
193 | if (is_reboot)
|
---|
194 | boot_down = "crash";
|
---|
195 | else
|
---|
196 | boot_down = "down ";
|
---|
197 |
|
---|
198 | if (tell_uptime) {
|
---|
199 | if (!is_reboot) {
|
---|
200 | fprintf(stderr,
|
---|
201 | "%s: no reboot record added to wtmp file on system boot!\n",
|
---|
202 | prog);
|
---|
203 | exit(1);
|
---|
204 | }
|
---|
205 | Print_Uptime();
|
---|
206 | exit(0);
|
---|
207 | }
|
---|
208 |
|
---|
209 | /* remove any logout records */
|
---|
210 | for (link = first_link; link != NULL; link = next_link) {
|
---|
211 | next_link = link->next;
|
---|
212 | free(link);
|
---|
213 | }
|
---|
214 | first_link = NULL;
|
---|
215 | } else if (wtmp->ut_name[0] == '\0') {
|
---|
216 | /* A logout record */
|
---|
217 | Record_Logout_Time(wtmp);
|
---|
218 | } else {
|
---|
219 | /* A login record */
|
---|
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);
|
---|
226 | }
|
---|
227 | /* record login time */
|
---|
228 | link->time = wtmp->ut_time;
|
---|
229 | return;
|
---|
230 | }
|
---|
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);
|
---|
238 | }
|
---|
239 | Record_Logout_Time(wtmp); /* Needed in case of 2
|
---|
240 | * consecutive logins */
|
---|
241 | }
|
---|
242 | }
|
---|
243 |
|
---|
244 |
|
---|
245 | /* Print_Record(wtmp) If the record was requested, then print out
|
---|
246 | * the user name, terminal, host and time.
|
---|
247 | */
|
---|
248 | int Print_Record(wtmp)
|
---|
249 | struct utmp *wtmp;
|
---|
250 | {
|
---|
251 | int i;
|
---|
252 | char print_flag = FALSE;
|
---|
253 |
|
---|
254 | /* just interested in the uptime? */
|
---|
255 | if (tell_uptime) return(FALSE);
|
---|
256 |
|
---|
257 | /* check if we have already printed the requested number of records */
|
---|
258 | if (count_limit && print_count == 0) exit(0);
|
---|
259 |
|
---|
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)))
|
---|
263 | print_flag = TRUE;
|
---|
264 |
|
---|
265 | if (arg_count == 0 || print_flag) {
|
---|
266 | #ifdef RLOGIN
|
---|
267 | printf("%-8.8s %-8.8s %-16.16s %.16s ",
|
---|
268 | wtmp->ut_name, wtmp->ut_line, wtmp->ut_host,
|
---|
269 | ctime(&wtmp->ut_time));
|
---|
270 | #else
|
---|
271 | printf("%-8.8s %-8.8s %.16s ",
|
---|
272 | wtmp->ut_name, wtmp->ut_line, ctime(&wtmp->ut_time));
|
---|
273 | #endif
|
---|
274 | --print_count;
|
---|
275 | return(TRUE);
|
---|
276 | }
|
---|
277 | return(FALSE);
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | /* Print_Duration(from, to) Calculate and print the days and hh:mm between
|
---|
282 | * the log-in and the log-out.
|
---|
283 | */
|
---|
284 | void Print_Duration(from, to)
|
---|
285 | long from;
|
---|
286 | long to;
|
---|
287 | {
|
---|
288 | long delta, days, hours, minutes;
|
---|
289 |
|
---|
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;
|
---|
296 |
|
---|
297 | if (days > 0)
|
---|
298 | printf("(%ld+", days);
|
---|
299 | else
|
---|
300 | printf(" (");
|
---|
301 |
|
---|
302 | printf("%02ld:%02ld)\n", hours, minutes);
|
---|
303 | }
|
---|
304 |
|
---|
305 |
|
---|
306 | /* Print_Uptime() Calculate and print the "uptime" between the last recorded
|
---|
307 | * boot and the current time.
|
---|
308 | */
|
---|
309 | void Print_Uptime()
|
---|
310 | {
|
---|
311 | #define NLOADS 3
|
---|
312 | int nloads;
|
---|
313 | double loads[NLOADS];
|
---|
314 | char *utmp_file = _PATH_UTMP;
|
---|
315 | unsigned nusers;
|
---|
316 | struct utmp ut;
|
---|
317 | FILE *uf;
|
---|
318 | time_t now;
|
---|
319 | struct tm *tm;
|
---|
320 | unsigned long up;
|
---|
321 |
|
---|
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));
|
---|
325 | exit(1);
|
---|
326 | }
|
---|
327 |
|
---|
328 | nusers = 0;
|
---|
329 | while (fread(&ut, sizeof(ut), 1, uf) == 1) {
|
---|
330 | #ifdef USER_PROCESS
|
---|
331 | if (ut.ut_type == USER_PROCESS) nusers++;
|
---|
332 | #else
|
---|
333 | if (ut.ut_name[0] != 0 && ut.ut_line[0] != 0) nusers++;
|
---|
334 | #endif
|
---|
335 | }
|
---|
336 | fclose(uf);
|
---|
337 |
|
---|
338 | /* Current time. */
|
---|
339 | now = time((time_t *) NULL);
|
---|
340 | tm = localtime(&now);
|
---|
341 |
|
---|
342 | /* Uptime. */
|
---|
343 | up = now - boot_time;
|
---|
344 |
|
---|
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");
|
---|
349 | }
|
---|
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) {
|
---|
353 | int i;
|
---|
354 | printf(", load averages:");
|
---|
355 | for(i = 0; i < nloads; i++)
|
---|
356 | printf("%s %.2f", (i > 0) ? "," : "", loads[i]);
|
---|
357 | }
|
---|
358 | printf("\n");
|
---|
359 | }
|
---|
360 |
|
---|
361 |
|
---|
362 | /* Record_Logout_Time(wtmp) A linked list of "last logout time" is kept.
|
---|
363 | * Each element of the list is for one terminal.
|
---|
364 | */
|
---|
365 | void Record_Logout_Time(wtmp)
|
---|
366 | struct utmp *wtmp;
|
---|
367 | {
|
---|
368 | logout *link;
|
---|
369 |
|
---|
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;
|
---|
374 | return;
|
---|
375 | }
|
---|
376 | /* allocate a new logout record, for a tty not previously encountered */
|
---|
377 | link = (logout *) malloc(sizeof(logout));
|
---|
378 | if (link == NULL) {
|
---|
379 | fprintf(stderr, "%s: malloc failure\n", prog);
|
---|
380 | exit(1);
|
---|
381 | }
|
---|
382 | strncpy(link->line, wtmp->ut_line, (size_t)8);
|
---|
383 | link->time = wtmp->ut_time;
|
---|
384 | link->next = first_link;
|
---|
385 |
|
---|
386 | first_link = link;
|
---|
387 | }
|
---|
388 |
|
---|
389 |
|
---|
390 | int main(argc, argv)
|
---|
391 | int argc;
|
---|
392 | char *argv[];
|
---|
393 | {
|
---|
394 | char *wtmp_file = _PATH_WTMP;
|
---|
395 | FILE *f;
|
---|
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];
|
---|
399 |
|
---|
400 | if ((prog = strrchr(argv[0], '/')) == NULL) prog = argv[0]; else prog++;
|
---|
401 |
|
---|
402 | --argc;
|
---|
403 | ++argv;
|
---|
404 |
|
---|
405 | while (argc > 0 && *argv[0] == '-') {
|
---|
406 | if (!strcmp(argv[0], "-r"))
|
---|
407 | boot_limit = TRUE;
|
---|
408 | else
|
---|
409 | if (!strcmp(argv[0], "-u"))
|
---|
410 | tell_uptime = TRUE;
|
---|
411 | else if (argc > 1 && !strcmp(argv[0], "-f")) {
|
---|
412 | wtmp_file = argv[1];
|
---|
413 | --argc;
|
---|
414 | ++argv;
|
---|
415 | } else if ((print_count = atoi(argv[0] + 1)) > 0)
|
---|
416 | count_limit = TRUE;
|
---|
417 | else
|
---|
418 | usage();
|
---|
419 |
|
---|
420 | --argc;
|
---|
421 | ++argv;
|
---|
422 | }
|
---|
423 |
|
---|
424 | arg_count = argc;
|
---|
425 | args = argv;
|
---|
426 |
|
---|
427 | if (!strcmp(prog, "uptime")) tell_uptime = TRUE;
|
---|
428 |
|
---|
429 | if ((f = fopen(wtmp_file, "r")) == NULL) {
|
---|
430 | perror(wtmp_file);
|
---|
431 | exit(1);
|
---|
432 | }
|
---|
433 | if (fseek(f, 0L, 2) != 0 || (size = ftell(f)) % sizeof(struct utmp) != 0) {
|
---|
434 | fprintf(stderr, "%s: invalid wtmp file\n", prog);
|
---|
435 | exit(1);
|
---|
436 | }
|
---|
437 | if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
|
---|
438 | signal(SIGINT, Sigint);
|
---|
439 | signal(SIGQUIT, Sigquit);
|
---|
440 | }
|
---|
441 | size /= sizeof(struct utmp); /* Number of records in wtmp */
|
---|
442 |
|
---|
443 | if (size == 0) wtmp_buffer[0].ut_time = time((time_t *)0);
|
---|
444 |
|
---|
445 | while (size > 0) {
|
---|
446 | wtmp_count = (int) min(size, MAX_WTMP_COUNT);
|
---|
447 | size -= (long) wtmp_count;
|
---|
448 |
|
---|
449 | fseek(f, size * sizeof(struct utmp), 0);
|
---|
450 |
|
---|
451 |
|
---|
452 | if (fread(&wtmp_buffer[0], sizeof(struct utmp), (size_t)wtmp_count, f)
|
---|
453 | != wtmp_count) {
|
---|
454 | fprintf(stderr, "%s: read error on wtmp file\n", prog);
|
---|
455 | exit(1);
|
---|
456 | }
|
---|
457 | while (--wtmp_count >= 0) {
|
---|
458 | Process(&wtmp_buffer[wtmp_count]);
|
---|
459 | if (interrupt) {
|
---|
460 | printf("\ninterrupted %.16s \n",
|
---|
461 | ctime(&wtmp_buffer[wtmp_count].ut_time));
|
---|
462 |
|
---|
463 | if (interrupt == SIGINT) exit(2);
|
---|
464 |
|
---|
465 | interrupt = FALSE;
|
---|
466 | signal(SIGQUIT, Sigquit);
|
---|
467 | }
|
---|
468 | }
|
---|
469 |
|
---|
470 | } /* end while(size > 0) */
|
---|
471 |
|
---|
472 | if (tell_uptime) {
|
---|
473 | fprintf(stderr,
|
---|
474 | "%s: no reboot record in wtmp file to compute uptime from\n",
|
---|
475 | prog);
|
---|
476 | return(1);
|
---|
477 | }
|
---|
478 |
|
---|
479 | printf("\nwtmp begins %.16s \n", ctime(&wtmp_buffer[0].ut_time));
|
---|
480 | return(0);
|
---|
481 | }
|
---|