last_fancy.c 6.2 KB

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