Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | /* vi: set sw=4 ts=4: */
/*
* (sysvinit like) last implementation
*
* Copyright (C) 2008 by Patricia Muscalu <patricia.muscalu@axis.com>
*
* Licensed under the GPLv2 or later, see the file LICENSE in this tarball.
*/
#include "libbb.h"
#include <utmp.h>
/* NB: ut_name and ut_user are the same field, use only one name (ut_user)
* to reduce confusion */
#ifndef SHUTDOWN_TIME
# define SHUTDOWN_TIME 254
#endif
#define HEADER_FORMAT "%-8.8s %-12.12s %-*.*s %-16.16s %-7.7s %s\n"
#define HEADER_LINE "USER", "TTY", \
INET_ADDRSTRLEN, INET_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
#define HEADER_LINE_WIDE "USER", "TTY", \
INET6_ADDRSTRLEN, INET6_ADDRSTRLEN, "HOST", "LOGIN", " TIME", ""
enum {
NORMAL,
LOGGED,
DOWN,
REBOOT,
CRASH,
GONE
};
enum {
LAST_OPT_W = (1 << 0), /* -W wide */
LAST_OPT_f = (1 << 1), /* -f input file */
LAST_OPT_H = (1 << 2), /* -H header */
};
#define show_wide (option_mask32 & LAST_OPT_W)
static void show_entry(struct utmp *ut, int state, time_t dur_secs)
{
unsigned days, hours, mins;
char duration[32];
char login_time[17];
char logout_time[8];
const char *logout_str;
const char *duration_str;
time_t tmp;
/* manpages say ut_tv.tv_sec *is* time_t,
* but some systems have it wrong */
tmp = ut->ut_tv.tv_sec;
safe_strncpy(login_time, ctime(&tmp), 17);
snprintf(logout_time, 8, "- %s", ctime(&dur_secs) + 11);
dur_secs = MAX(dur_secs - (time_t)ut->ut_tv.tv_sec, (time_t)0);
/* unsigned int is easier to divide than time_t (which may be signed long) */
mins = dur_secs / 60;
days = mins / (24*60);
mins = mins % (24*60);
hours = mins / 60;
mins = mins % 60;
// if (days) {
sprintf(duration, "(%u+%02u:%02u)", days, hours, mins);
// } else {
// sprintf(duration, " (%02u:%02u)", hours, mins);
// }
logout_str = logout_time;
duration_str = duration;
switch (state) {
case NORMAL:
break;
case LOGGED:
logout_str = " still";
duration_str = "logged in";
break;
case DOWN:
logout_str = "- down ";
break;
case REBOOT:
break;
case CRASH:
logout_str = "- crash";
break;
case GONE:
logout_str = " gone";
duration_str = "- no logout";
break;
}
printf(HEADER_FORMAT,
ut->ut_user,
ut->ut_line,
show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
show_wide ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN,
ut->ut_host,
login_time,
logout_str,
duration_str);
}
static int get_ut_type(struct utmp *ut)
{
if (ut->ut_line[0] == '~') {
if (strcmp(ut->ut_user, "shutdown") == 0) {
return SHUTDOWN_TIME;
}
if (strcmp(ut->ut_user, "reboot") == 0) {
return BOOT_TIME;
}
if (strcmp(ut->ut_user, "runlevel") == 0) {
return RUN_LVL;
}
return ut->ut_type;
}
if (ut->ut_user[0] == 0) {
return DEAD_PROCESS;
}
if ((ut->ut_type != DEAD_PROCESS)
&& (strcmp(ut->ut_user, "LOGIN") != 0)
&& ut->ut_user[0]
&& ut->ut_line[0]
) {
ut->ut_type = USER_PROCESS;
}
if (strcmp(ut->ut_user, "date") == 0) {
if (ut->ut_line[0] == '|') {
return OLD_TIME;
}
if (ut->ut_line[0] == '{') {
return NEW_TIME;
}
}
return ut->ut_type;
}
static int is_runlevel_shutdown(struct utmp *ut)
{
if (((ut->ut_pid & 255) == '0') || ((ut->ut_pid & 255) == '6')) {
return 1;
}
return 0;
}
int last_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int last_main(int argc ATTRIBUTE_UNUSED, char **argv)
{
struct utmp ut;
const char *filename = _PATH_WTMP;
llist_t *zlist;
off_t pos;
time_t start_time;
time_t boot_time;
time_t down_time;
int file;
unsigned opt;
smallint going_down;
smallint boot_down;
opt = getopt32(argv, "Wf:" /* "H" */, &filename);
#ifdef BUT_UTIL_LINUX_LAST_HAS_NO_SUCH_OPT
if (opt & LAST_OPT_H) {
/* Print header line */
if (opt & LAST_OPT_W) {
printf(HEADER_FORMAT, HEADER_LINE_WIDE);
} else {
printf(HEADER_FORMAT, HEADER_LINE);
}
}
#endif
file = xopen(filename, O_RDONLY);
{
/* in case the file is empty... */
struct stat st;
fstat(file, &st);
start_time = st.st_ctime;
}
time(&down_time);
going_down = 0;
boot_down = NORMAL; /* 0 */
zlist = NULL;
boot_time = 0;
/* get file size, rounding down to last full record */
pos = xlseek(file, 0, SEEK_END) / sizeof(ut) * sizeof(ut);
for (;;) {
pos -= (off_t)sizeof(ut);
if (pos < 0) {
/* Beyond the beginning of the file boundary =>
* the whole file has been read. */
break;
}
xlseek(file, pos, SEEK_SET);
xread(file, &ut, sizeof(ut));
/* rewritten by each record, eventially will have
* first record's ut_tv.tv_sec: */
start_time = ut.ut_tv.tv_sec;
switch (get_ut_type(&ut)) {
case SHUTDOWN_TIME:
down_time = ut.ut_tv.tv_sec;
boot_down = DOWN;
going_down = 1;
break;
case RUN_LVL:
if (is_runlevel_shutdown(&ut)) {
down_time = ut.ut_tv.tv_sec;
going_down = 1;
boot_down = DOWN;
}
break;
case BOOT_TIME:
strcpy(ut.ut_line, "system boot");
show_entry(&ut, REBOOT, down_time);
boot_down = CRASH;
going_down = 1;
break;
case DEAD_PROCESS:
if (!ut.ut_line[0]) {
break;
}
/* add_entry */
llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
break;
case USER_PROCESS: {
int show;
if (!ut.ut_line[0]) {
break;
}
/* find_entry */
show = 1;
{
llist_t *el, *next;
for (el = zlist; el; el = next) {
struct utmp *up = (struct utmp *)el->data;
next = el->link;
if (strncmp(up->ut_line, ut.ut_line, UT_LINESIZE) == 0) {
if (show) {
show_entry(&ut, NORMAL, up->ut_tv.tv_sec);
show = 0;
}
llist_unlink(&zlist, el);
free(el->data);
free(el);
}
}
}
if (show) {
int state = boot_down;
if (boot_time == 0) {
state = LOGGED;
/* Check if the process is alive */
if ((ut.ut_pid > 0)
&& (kill(ut.ut_pid, 0) != 0)
&& (errno == ESRCH)) {
state = GONE;
}
}
show_entry(&ut, state, boot_time);
}
/* add_entry */
llist_add_to(&zlist, memcpy(xmalloc(sizeof(ut)), &ut, sizeof(ut)));
break;
}
}
if (going_down) {
boot_time = ut.ut_tv.tv_sec;
llist_free(zlist, free);
zlist = NULL;
going_down = 0;
}
}
if (ENABLE_FEATURE_CLEAN_UP) {
llist_free(zlist, free);
}
printf("\nwtmp begins %s", ctime(&start_time));
if (ENABLE_FEATURE_CLEAN_UP)
close(file);
fflush_stdout_and_exit(EXIT_SUCCESS);
}
|