diff options
Diffstat (limited to 'tools/ck-history.c')
-rw-r--r-- | tools/ck-history.c | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/tools/ck-history.c b/tools/ck-history.c new file mode 100644 index 0000000..716c3ef --- /dev/null +++ b/tools/ck-history.c @@ -0,0 +1,866 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <string.h> +#include <errno.h> + +#include <locale.h> +#include <zlib.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> + +#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 + +static GList *all_events = NULL; + +static gboolean +process_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); + + if (event != NULL) { + all_events = g_list_prepend (all_events, event); + } + + return TRUE; +} + +static gboolean +process_log_gzstream (gzFile *fstream) +{ + char line[MAX_LINE_LEN]; + + while (gzgets (fstream, line, sizeof (line)) != Z_NULL) { + if (strlen (line) == sizeof (line) - 1) { + g_warning ("Log line truncated"); + } + + process_event_line (line); + } + + return TRUE; +} + +static gboolean +process_log_stream (FILE *fstream) +{ + char line[MAX_LINE_LEN]; + + while (fgets (line, sizeof (line), fstream) != NULL) { + if (strlen (line) == sizeof (line) - 1) { + g_warning ("Log line truncated"); + } + + process_event_line (line); + } + + return TRUE; +} + +static gboolean +process_log_file (const char *filename) +{ + 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); + 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); + 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 files; +} + +static gboolean +process_logs (void) +{ + 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); + 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 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 != NULL && e->session_x11_display[0] != '\0') { + utline = g_strdup (e->session_x11_display); + } 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 *username; + + username = NULL; + + switch (event->type) { + case CK_LOG_EVENT_SEAT_SESSION_ADDED: + username = g_strdup (((CkLogSeatSessionAddedEvent *)event)->session_remote_host_name); + break; + case CK_LOG_EVENT_SYSTEM_START: + /* FIXME: get kernel version */ + username = g_strdup (""); + break; + default: + g_assert_not_reached (); + } + + return username; +} + +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; + + secs = remove_event->timestamp.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; + CkLogEvent *remove_event; + RecordStatus status; + + 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, 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; + + 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); + + addedtime = g_strndup (ctime (&event->timestamp.tv_sec), 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, + "%-8.8s %12s %-10.10s %-7.7s %-12.12s %-16.16s %-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: + removedtime = g_strdup (""); + break; + case RECORD_STATUS_TIMECHANGE: + removedtime = g_strdup (""); + break; + case RECORD_STATUS_NORMAL: + duration = get_duration (event, remove_event); + removedtime = g_strdup_printf ("- %s", ctime (&remove_event->timestamp.tv_sec) + 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; + + /* 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 != 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; + g_print ("\nLog begins %s", ctime (&oldest_event->timestamp.tv_sec)); + } +} + +static void +generate_report_last_compat (int uid, + const char *seat, + const char *session_type) +{ + GList *oldest; + CkLogEvent *oldest_event; + GList *l; + + /* 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 != 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; + g_print ("\nLog begins %s", ctime (&oldest_event->timestamp.tv_sec)); + } +} + +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 != 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 ("%-8.8s %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; + 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 GOptionEntry entries [] = { + { "version", 'V', 0, G_OPTION_ARG_NONE, &do_version, N_("Version of this application"), NULL }, + { "frequent", 0, 0, G_OPTION_ARG_NONE, &report_frequent, N_("Show listing of frequent users"), NULL }, + { "last", 0, 0, G_OPTION_ARG_NONE, &report_last, N_("Show listing of last logged in users"), NULL }, + { "last-compat", 0, 0, G_OPTION_ARG_NONE, &report_last_compat, N_("Show 'last' compatible listing of last logged in users"), NULL }, + { "log", 0, 0, G_OPTION_ARG_NONE, &report_log, N_("Show full event log"), NULL }, + { "seat", 's', 0, G_OPTION_ARG_STRING, &seat, N_("Show entries for the specified seat"), N_("SEAT") }, + { "session-type", 't', 0, G_OPTION_ARG_STRING, &session_type, N_("Show entries for the specified session type"), N_("TYPE") }, + { "user", 'u', 0, G_OPTION_ARG_STRING, &username, N_("Show entries for the specified user"), N_("NAME") }, + { NULL } + }; + + 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); + } + + 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; + } + + process_logs (); + generate_report (report_type, uid, seat, session_type); + free_events (); + + return 0; +} |