1 /* vi: set sw=4 ts=4: */
3 * (sysvinit like) last implementation
5 * Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
7 * Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
13 /* NB: ut_name and ut_user are the same field, use only one name (ut_user)
14 * to reduce confusion */
17 # define SHUTDOWN_TIME 254
20 #define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
21 #define HEADER_LINE "USER", "TTY", \
22 INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
23 #define HEADER_LINE_WIDE "USER", "TTY", \
24 INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
36 LAST_OPT_W
= (1 << 0), /* -W wide */
37 LAST_OPT_f
= (1 << 1), /* -f input file */
38 LAST_OPT_H
= (1 << 2), /* -H header */
41 #define show_wide (option_mask32 & LAST_OPT_W)
43 static void show_entry(struct utmp
*ut
, int state
, time_t dur_secs
)
45 unsigned days
, hours
, mins
;
49 const char *logout_str
;
50 const char *duration_str
;
53 /* manpages say ut_tv.tv_sec *is* time_t,
54 * but some systems have it wrong */
55 tmp
= ut
->ut_tv
.tv_sec
;
56 safe_strncpy(login_time
, ctime(&tmp
), 17);
57 snprintf(logout_time
, 8, "- %s", ctime(&dur_secs
) + 11);
59 dur_secs
= MAX(dur_secs
- (time_t)ut
->ut_tv
.tv_sec
, (time_t)0);
60 /* unsigned int is easier to divide than time_t (which may be signed long) */
62 days
= mins
/ (24*60);
63 mins
= mins
% (24*60);
68 sprintf(duration
, "(%u+%02u:%02u)", days
, hours
, mins
);
70 // sprintf(duration, " (%02u:%02u)", hours, mins);
73 logout_str
= logout_time
;
74 duration_str
= duration
;
79 logout_str
= " still";
80 duration_str
= "logged in";
83 logout_str
= "- down ";
88 logout_str
= "- crash";
92 duration_str
= "- no logout";
99 show_wide
? INET6_ADDRSTRLEN
: INET_ADDRSTRLEN
,
100 show_wide
? INET6_ADDRSTRLEN
: INET_ADDRSTRLEN
,
107 static int get_ut_type(struct utmp
*ut
)
109 if (ut
->ut_line
[0] == '~') {
110 if (strcmp(ut
->ut_user
, "shutdown") == 0) {
111 return SHUTDOWN_TIME
;
113 if (strcmp(ut
->ut_user
, "reboot") == 0) {
116 if (strcmp(ut
->ut_user
, "runlevel") == 0) {
122 if (ut
->ut_user
[0] == 0) {
126 if ((ut
->ut_type
!= DEAD_PROCESS
)
127 && (strcmp(ut
->ut_user
, "LOGIN") != 0)
131 ut
->ut_type
= USER_PROCESS
;
134 if (strcmp(ut
->ut_user
, "date") == 0) {
135 if (ut
->ut_line
[0] == '|') {
138 if (ut
->ut_line
[0] == '{') {
145 static int is_runlevel_shutdown(struct utmp
*ut
)
147 if (((ut
->ut_pid
& 255) == '0') || ((ut
->ut_pid
& 255) == '6')) {
154 int last_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
155 int last_main(int argc UNUSED_PARAM
, char **argv
)
158 const char *filename
= _PATH_WTMP
;
169 opt
= getopt32(argv
, "Wf:" /* "H" */, &filename
);
170 #ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
171 if (opt
& LAST_OPT_H
) {
172 /* Print header line */
173 if (opt
& LAST_OPT_W
) {
174 printf(HEADER_FORMAT
, HEADER_LINE_WIDE
);
176 printf(HEADER_FORMAT
, HEADER_LINE
);
181 file
= xopen(filename
, O_RDONLY
);
183 /* in case the file is empty... */
186 start_time
= st
.st_ctime
;
191 boot_down
= NORMAL
; /* 0 */
194 /* get file size, rounding down to last full record */
195 pos
= xlseek(file
, 0, SEEK_END
) / sizeof(ut
) * sizeof(ut
);
197 pos
-= (off_t
)sizeof(ut
);
199 /* Beyond the beginning of the file boundary =>
200 * the whole file has been read. */
203 xlseek(file
, pos
, SEEK_SET
);
204 xread(file
, &ut
, sizeof(ut
));
205 /* rewritten by each record, eventially will have
206 * first record's ut_tv.tv_sec: */
207 start_time
= ut
.ut_tv
.tv_sec
;
209 switch (get_ut_type(&ut
)) {
211 down_time
= ut
.ut_tv
.tv_sec
;
216 if (is_runlevel_shutdown(&ut
)) {
217 down_time
= ut
.ut_tv
.tv_sec
;
223 strcpy(ut
.ut_line
, "system boot");
224 show_entry(&ut
, REBOOT
, down_time
);
229 if (!ut
.ut_line
[0]) {
233 llist_add_to(&zlist
, memcpy(xmalloc(sizeof(ut
)), &ut
, sizeof(ut
)));
238 if (!ut
.ut_line
[0]) {
245 for (el
= zlist
; el
; el
= next
) {
246 struct utmp
*up
= (struct utmp
*)el
->data
;
248 if (strncmp(up
->ut_line
, ut
.ut_line
, UT_LINESIZE
) == 0) {
250 show_entry(&ut
, NORMAL
, up
->ut_tv
.tv_sec
);
253 llist_unlink(&zlist
, el
);
261 int state
= boot_down
;
263 if (boot_time
== 0) {
265 /* Check if the process is alive */
267 && (kill(ut
.ut_pid
, 0) != 0)
268 && (errno
== ESRCH
)) {
272 show_entry(&ut
, state
, boot_time
);
275 llist_add_to(&zlist
, memcpy(xmalloc(sizeof(ut
)), &ut
, sizeof(ut
)));
281 boot_time
= ut
.ut_tv
.tv_sec
;
282 llist_free(zlist
, free
);
288 if (ENABLE_FEATURE_CLEAN_UP
) {
289 llist_free(zlist
, free
);
292 printf("\nwtmp begins %s", ctime(&start_time
));
294 if (ENABLE_FEATURE_CLEAN_UP
)
296 fflush_stdout_and_exit(EXIT_SUCCESS
);