/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ck-log-event.h" typedef enum { REPORT_TYPE_SUMMARY = 0, REPORT_TYPE_LAST, REPORT_TYPE_LAST_COMPAT, REPORT_TYPE_FREQUENT, REPORT_TYPE_LOG, } ReportType; /* same record types as sysvinit last */ typedef enum { RECORD_STATUS_CRASH = 1, /* No logout record, system boot in between */ RECORD_STATUS_DOWN, /* System brought down in decent way */ RECORD_STATUS_NORMAL, /* Normal */ RECORD_STATUS_NOW, /* Still logged in */ RECORD_STATUS_REBOOT, /* Reboot record. */ RECORD_STATUS_PHANTOM, /* No logout record but session is stale. */ RECORD_STATUS_TIMECHANGE, /* NEW_TIME or OLD_TIME */ } RecordStatus; #define DEFAULT_LOG_FILENAME LOCALSTATEDIR "/log/ConsoleKit/history" #define MAX_LINE_LEN 2048 #define USERNAME_MAX "24" static GList *all_events = NULL; static CkLogEvent * parse_event_line (const char *line) { GString *str; CkLogEvent *event; str = g_string_new (line); event = ck_log_event_new_from_string (str); g_string_free (str, TRUE); return event; } static gboolean process_log_gzstream (gzFile fstream, GTimeVal *since) { char line[MAX_LINE_LEN]; gboolean hit_since; GList *events; events = NULL; hit_since = FALSE; while (gzgets (fstream, line, sizeof (line)) != Z_NULL) { CkLogEvent *event; if (strlen (line) == sizeof (line) - 1) { g_warning ("Log line truncated"); } event = parse_event_line (line); if (event == NULL) { continue; } if (since == NULL || event->timestamp.tv_sec >= since->tv_sec) { events = g_list_prepend (events, event); } else { hit_since = TRUE; } } all_events = g_list_concat (all_events, events); return !hit_since; } static gboolean process_log_stream (FILE *fstream, GTimeVal *since) { char line[MAX_LINE_LEN]; gboolean hit_since; GList *events; events = NULL; hit_since = FALSE; while (fgets (line, sizeof (line), fstream) != NULL) { CkLogEvent *event; if (strlen (line) == sizeof (line) - 1) { g_warning ("Log line truncated"); } event = parse_event_line (line); if (event == NULL) { continue; } if (since == NULL || event->timestamp.tv_sec >= since->tv_sec) { events = g_list_prepend (events, event); } else { hit_since = TRUE; } } all_events = g_list_concat (all_events, events); return !hit_since; } static gboolean process_log_file (const char *filename, GTimeVal *since) { gboolean ret; if (g_str_has_suffix (filename, ".gz")) { gzFile f; f = gzopen (filename, "r"); if (f == NULL) { int errnum; const char *errmsg; errmsg = gzerror (f, &errnum); if (errnum == Z_ERRNO) { errmsg = g_strerror (errno); } g_warning ("Error opening %s (%s)\n", filename, errmsg); return FALSE; } ret = process_log_gzstream (f, since); gzclose (f); } else { FILE *f; f = g_fopen (filename, "r"); if (f == NULL) { g_warning ("Error opening %s (%s)\n", filename, g_strerror (errno)); return FALSE; } ret = process_log_stream (f, since); fclose (f); } return ret; } static GList * get_log_file_list (void) { int num; GList *files; /* always try the primary file */ files = NULL; files = g_list_prepend (files, g_strdup (DEFAULT_LOG_FILENAME)); num = 1; while (1) { char *filename; filename = g_strdup_printf ("%s.%d", DEFAULT_LOG_FILENAME, num); if (g_access (filename, R_OK) != 0) { char *filename_gz; /* check for .gz */ filename_gz = g_strdup_printf ("%s.gz", filename); g_free (filename); if (g_access (filename_gz, R_OK) != 0) { g_free (filename_gz); break; } filename = filename_gz; } num++; files = g_list_prepend (files, filename); }; /* Return the list in reverse time order, newest first */ files = g_list_reverse (files); return files; } static gboolean process_logs (GTimeVal *since) { gboolean ret; GList *files; GList *l; ret = FALSE; files = get_log_file_list (); for (l = files; l != NULL; l = l->next) { gboolean res; char *filename; filename = l->data; res = process_log_file (filename, since); if (! res) { goto out; } } ret = TRUE; out: g_list_foreach (files, (GFunc)g_free, NULL); g_list_free (files); return ret; } static void generate_report_summary (int uid, const char *seat, const char *session_type) { } static CkLogEvent * find_first_matching_remove_event (GList *events, CkLogSeatSessionAddedEvent *event) { CkLogEvent *revent; GList *l; revent = NULL; for (l = events; l != NULL; l = l->next) { CkLogEventType etype; etype = ((CkLogEvent *)l->data)->type; /* skip all non removal events */ if (! (etype == CK_LOG_EVENT_SEAT_SESSION_REMOVED || etype == CK_LOG_EVENT_SYSTEM_START || etype == CK_LOG_EVENT_SYSTEM_STOP || etype == CK_LOG_EVENT_SYSTEM_RESTART)) { continue; } if (etype == CK_LOG_EVENT_SEAT_SESSION_REMOVED) { CkLogSeatSessionRemovedEvent *e; e = l->data; if (e->session_id != NULL && event->session_id != NULL && strcmp (e->session_id, event->session_id) == 0) { revent = (CkLogEvent *)l->data; break; } } else { revent = (CkLogEvent *)l->data; break; } } return revent; } static CkLogEvent * find_first_matching_system_stop_event (GList *events, CkLogSeatSessionAddedEvent *event) { CkLogEvent *revent; GList *l; revent = NULL; for (l = events; l != NULL; l = l->next) { CkLogEventType etype; etype = ((CkLogEvent *)l->data)->type; /* skip all non removal events */ if (! (etype == CK_LOG_EVENT_SYSTEM_STOP || etype == CK_LOG_EVENT_SYSTEM_RESTART)) { continue; } revent = (CkLogEvent *)l->data; break; } return revent; } static char * get_user_name_for_uid (int uid) { struct passwd *pwent; char *name; name = NULL; pwent = getpwuid (uid); if (pwent != NULL) { name = g_strdup (pwent->pw_name); } return name; } static int get_uid_for_username (const char *username) { struct passwd *pwent; int uid; g_assert (username != NULL); uid = -1; pwent = getpwnam (username); if (pwent != NULL) { uid = pwent->pw_uid; } return uid; } static char * get_utline_for_event (CkLogEvent *event) { char *utline; utline = NULL; switch (event->type) { case CK_LOG_EVENT_SEAT_SESSION_ADDED: { CkLogSeatSessionAddedEvent *e; e = (CkLogSeatSessionAddedEvent *)event; if (e->session_x11_display_device != NULL && e->session_x11_display_device[0] != '\0') { if (g_str_has_prefix (e->session_x11_display_device, "/dev/")) { utline = g_strdup (e->session_x11_display_device + 5); } else { utline = g_strdup (e->session_x11_display_device); } } else { if (g_str_has_prefix (e->session_display_device, "/dev/")) { utline = g_strdup (e->session_display_device + 5); } else { utline = g_strdup (e->session_display_device); } } } break; case CK_LOG_EVENT_SYSTEM_START: utline = g_strdup ("system boot"); break; default: g_assert_not_reached (); } return utline; } static char * get_user_name_for_event (CkLogEvent *event) { char *username; username = NULL; switch (event->type) { case CK_LOG_EVENT_SEAT_SESSION_ADDED: username = get_user_name_for_uid (((CkLogSeatSessionAddedEvent *)event)->session_unix_user); break; case CK_LOG_EVENT_SYSTEM_START: username = g_strdup ("reboot"); break; default: g_assert_not_reached (); } return username; } static char * get_host_for_event (CkLogEvent *event) { char *name; name = NULL; switch (event->type) { case CK_LOG_EVENT_SEAT_SESSION_ADDED: name = g_strdup (((CkLogSeatSessionAddedEvent *)event)->session_remote_host_name); if (name == NULL) { /* If not set then use the display value */ name = g_strdup (((CkLogSeatSessionAddedEvent *)event)->session_x11_display); } break; case CK_LOG_EVENT_SYSTEM_START: name = g_strdup (((CkLogSystemStartEvent *)event)->kernel_release); break; default: g_assert_not_reached (); } return name; } static RecordStatus get_event_record_status (CkLogEvent *remove_event) { RecordStatus status; status = RECORD_STATUS_NOW; if (remove_event == NULL) { goto out; } if (remove_event->type == CK_LOG_EVENT_SEAT_SESSION_REMOVED) { status = RECORD_STATUS_NORMAL; } else if (remove_event->type == CK_LOG_EVENT_SYSTEM_START) { status = RECORD_STATUS_CRASH; } else if (remove_event->type == CK_LOG_EVENT_SYSTEM_STOP) { status = RECORD_STATUS_DOWN; } else if (remove_event->type == CK_LOG_EVENT_SYSTEM_RESTART) { status = RECORD_STATUS_DOWN; } out: return status; } static char * get_duration (CkLogEvent *event, CkLogEvent *remove_event) { time_t secs; int mins; int hours; int days; char *duration; if (remove_event != NULL) { secs = remove_event->timestamp.tv_sec - event->timestamp.tv_sec; } else { GTimeVal now; g_get_current_time (&now); secs = now.tv_sec - event->timestamp.tv_sec; } mins = (secs / 60) % 60; hours = (secs / 3600) % 24; days = secs / 86400; if (days > 0) { duration = g_strdup_printf ("(%d+%02d:%02d)", days, hours, mins); } else { duration = g_strdup_printf (" (%02d:%02d)", hours, mins); } return duration; } static void print_last_report_record (GList *list, CkLogEvent *event, gboolean legacy_compat) { GString *str; char *username; char *utline; char *host; char *addedtime; char *removedtime; char *duration; char *session_type; char *session_id; char *seat_id; CkLogSeatSessionAddedEvent *e = NULL; CkLogEvent *remove_event; RecordStatus status; time_t added_t, removed_t; if (event->type != CK_LOG_EVENT_SEAT_SESSION_ADDED && event->type != CK_LOG_EVENT_SYSTEM_START) { return; } remove_event = NULL; if (event->type == CK_LOG_EVENT_SEAT_SESSION_ADDED) { e = (CkLogSeatSessionAddedEvent *)event; remove_event = find_first_matching_remove_event (list->next, e); status = get_event_record_status (remove_event); session_type = e->session_type; session_id = e->session_id; seat_id = e->seat_id; } else { status = RECORD_STATUS_REBOOT; remove_event = find_first_matching_system_stop_event (list->next, NULL); session_type = ""; session_id = ""; seat_id = ""; } str = g_string_new (NULL); username = get_user_name_for_event (event); utline = get_utline_for_event (event); host = get_host_for_event (event); added_t = event->timestamp.tv_sec; addedtime = g_strndup (ctime (&added_t), 16); if (legacy_compat) { g_string_printf (str, "%-8.8s %-12.12s %-16.16s %-16.16s", username, utline != NULL ? utline : "", host != NULL ? host : "", addedtime); } else { g_string_printf (str, "%-"USERNAME_MAX"."USERNAME_MAX"s %12s %-10.10s %-7.7s %-12.12s %-28.28s %-16.16s", username, session_type, session_id, seat_id, utline, host != NULL ? host : "", addedtime); } g_free (username); g_free (addedtime); g_free (utline); g_free (host); removedtime = NULL; duration = NULL; switch (status) { case RECORD_STATUS_CRASH: duration = get_duration (event, remove_event); removedtime = g_strdup ("- crash"); break; case RECORD_STATUS_DOWN: duration = get_duration (event, remove_event); removedtime = g_strdup ("- down "); break; case RECORD_STATUS_NOW: duration = g_strdup ("logged in"); removedtime = g_strdup (" still"); break; case RECORD_STATUS_PHANTOM: duration = g_strdup ("- no logout"); removedtime = g_strdup (" gone"); break; case RECORD_STATUS_REBOOT: duration = get_duration (event, remove_event); removedtime = g_strdup (""); break; case RECORD_STATUS_TIMECHANGE: duration = g_strdup (""); removedtime = g_strdup (""); break; case RECORD_STATUS_NORMAL: duration = get_duration (event, remove_event); removed_t = remove_event->timestamp.tv_sec; removedtime = g_strdup_printf ("- %s", ctime (&removed_t) + 11); removedtime[7] = 0; break; default: g_assert_not_reached (); break; } g_string_append_printf (str, " %-7.7s %-12.12s", removedtime, duration); g_print ("%s\n", str->str); g_string_free (str, TRUE); g_free (removedtime); g_free (duration); } static void generate_report_last (int uid, const char *seat, const char *session_type) { GList *oldest; CkLogEvent *oldest_event; GList *l; time_t oldest_e; /* print events in reverse time order */ for (l = g_list_last (all_events); l != NULL; l = l->prev) { CkLogEvent *event; event = l->data; if (event->type == CK_LOG_EVENT_SEAT_SESSION_ADDED) { CkLogSeatSessionAddedEvent *e; e = (CkLogSeatSessionAddedEvent *)event; if (uid >= 0 && e->session_unix_user != (guint)uid) { continue; } if (seat != NULL && e->seat_id != NULL && strcmp (e->seat_id, seat) != 0) { continue; } if (session_type != NULL && e->session_type != NULL && strcmp (e->session_type, session_type) != 0) { continue; } } print_last_report_record (l, event, FALSE); } oldest = g_list_first (all_events); if (oldest != NULL) { oldest_event = oldest->data; oldest_e = oldest_event->timestamp.tv_sec; g_print ("\nLog begins %s", ctime (&oldest_e)); } } static void generate_report_last_compat (int uid, const char *seat, const char *session_type) { GList *oldest; CkLogEvent *oldest_event; GList *l; time_t oldest_e; /* print events in reverse time order */ for (l = g_list_last (all_events); l != NULL; l = l->prev) { CkLogEvent *event; event = l->data; if (event->type == CK_LOG_EVENT_SEAT_SESSION_ADDED) { CkLogSeatSessionAddedEvent *e; e = (CkLogSeatSessionAddedEvent *)event; if (uid >= 0 && e->session_unix_user != (guint)uid) { continue; } if (seat != NULL && e->seat_id != NULL && strcmp (e->seat_id, seat) != 0) { continue; } if (session_type != NULL && e->session_type != NULL && strcmp (e->session_type, session_type) != 0) { continue; } } print_last_report_record (l, event, TRUE); } oldest = g_list_first (all_events); if (oldest != NULL) { oldest_event = oldest->data; oldest_e = oldest_event->timestamp.tv_sec; g_print ("\nLog begins %s", ctime (&oldest_e)); } } typedef struct { int uid; guint count; } CountData; static void listify_counts (gpointer key, gpointer val, GList **list) { CountData *count_data; count_data = g_new0 (CountData, 1); count_data->uid = GPOINTER_TO_INT (key); count_data->count = GPOINTER_TO_UINT (val); *list = g_list_prepend (*list, count_data); } static int counts_compare (CountData *a, CountData *b) { if (a->count < b->count) { return 1; } else if (a->count > b->count) { return -1; } else { return 0; } } static void generate_report_frequent (int uid, const char *seat, const char *session_type) { GHashTable *counts; GList *l; GList *user_counts; /* FIXME: we can probably do this more efficiently */ counts = g_hash_table_new (NULL, NULL); for (l = g_list_first (all_events); l != NULL; l = l->next) { CkLogEvent *event; CkLogSeatSessionAddedEvent *e; guint count; gpointer val; event = l->data; if (event->type != CK_LOG_EVENT_SEAT_SESSION_ADDED) { continue; } e = (CkLogSeatSessionAddedEvent *)event; if (uid >= 0 && e->session_unix_user != (guint)uid) { continue; } if (seat != NULL && e->seat_id != NULL && strcmp (e->seat_id, seat) != 0) { continue; } if (session_type != NULL && e->session_type != NULL && strcmp (e->session_type, session_type) != 0) { continue; } val = g_hash_table_lookup (counts, GINT_TO_POINTER (e->session_unix_user)); if (val != NULL) { count = GPOINTER_TO_INT (val); } else { count = 0; } g_hash_table_insert (counts, GINT_TO_POINTER (e->session_unix_user), GUINT_TO_POINTER (count + 1)); } user_counts = NULL; g_hash_table_foreach (counts, (GHFunc)listify_counts, &user_counts); g_hash_table_destroy (counts); if (user_counts == NULL) { return; } user_counts = g_list_sort (user_counts, (GCompareFunc)counts_compare); while (user_counts != NULL) { CountData *data; char *username; data = user_counts->data; username = get_user_name_for_uid (data->uid); g_print ("%-"USERNAME_MAX"s %u\n", username, data->count); g_free (data); user_counts = g_list_delete_link (user_counts, user_counts); g_free (username); } g_list_free (user_counts); } static void generate_report_log (int uid, const char *seat, const char *session_type) { GList *l; for (l = all_events; l != NULL; l = l->next) { CkLogEvent *event; GString *str; event = l->data; str = g_string_new (NULL); ck_log_event_to_string (event, str); g_print ("%s\n", str->str); g_string_free (str, TRUE); } } static void generate_report (int report_type, int uid, const char *seat, const char *session_type) { all_events = g_list_reverse (all_events); switch (report_type) { case REPORT_TYPE_SUMMARY: generate_report_summary (uid, seat, session_type); break; case REPORT_TYPE_LAST: generate_report_last (uid, seat, session_type); break; case REPORT_TYPE_LAST_COMPAT: generate_report_last_compat (uid, seat, session_type); break; case REPORT_TYPE_FREQUENT: generate_report_frequent (uid, seat, session_type); break; case REPORT_TYPE_LOG: generate_report_log (uid, seat, session_type); break; default: g_assert_not_reached (); break; } } static void free_events (void) { /* FIXME: */ } int main (int argc, char **argv) { GOptionContext *context; gboolean retval; GError *error = NULL; int report_type; int uid; GTimeVal timestamp; gboolean use_since; static gboolean do_version = FALSE; static gboolean report_last_compat = FALSE; static gboolean report_last = FALSE; static gboolean report_frequent = FALSE; static gboolean report_log = FALSE; static char *username = NULL; static char *seat = NULL; static char *session_type = NULL; static char *since = NULL; static GOptionEntry entries [] = { { "version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &do_version, N_("Version of this application"), NULL }, { "frequent", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &report_frequent, N_("Show listing of frequent users"), NULL }, { "last", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &report_last, N_("Show listing of last logged in users"), NULL }, { "last-compat", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &report_last_compat, N_("Show 'last' compatible listing of last logged in users"), NULL }, { "log", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &report_log, N_("Show full event log"), NULL }, { "seat", 's', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &seat, N_("Show entries for the specified seat"), N_("SEAT") }, { "session-type", 't', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &session_type, N_("Show entries for the specified session type"), N_("TYPE") }, { "user", 'u', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &username, N_("Show entries for the specified user"), N_("NAME") }, { "since", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &since, N_("Show entries since the specified time (ISO 8601 format)"), N_("DATETIME") }, { NULL } }; /* Setup for i18n */ setlocale(LC_ALL, ""); #ifdef ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); retval = g_option_context_parse (context, &argc, &argv, &error); g_option_context_free (context); if (! retval) { g_warning ("%s", error->message); g_error_free (error); exit (1); } if (do_version) { g_print ("%s %s\n", argv [0], VERSION); exit (1); } use_since = FALSE; if (since != NULL) { use_since = g_time_val_from_iso8601 (since, ×tamp); if (! use_since) { g_warning ("Invalid ISO 8601 time value"); exit (1); } } if (report_last_compat) { report_type = REPORT_TYPE_LAST_COMPAT; } else if (report_last) { report_type = REPORT_TYPE_LAST; } else if (report_frequent) { report_type = REPORT_TYPE_FREQUENT; } else if (report_log) { report_type = REPORT_TYPE_LOG; } else { report_type = REPORT_TYPE_SUMMARY; } if (username != NULL) { uid = get_uid_for_username (username); if (uid == -1) { g_warning ("Unknown username: %s", username); exit (1); } } else { uid = -1; } if (use_since) { process_logs (×tamp); } else { process_logs (NULL); } generate_report (report_type, uid, seat, session_type); free_events (); return 0; }