diff options
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/ck-job.c | 392 | ||||
-rw-r--r-- | src/ck-job.h | 76 | ||||
-rw-r--r-- | src/ck-manager.c | 278 | ||||
-rw-r--r-- | tools/linux/ck-collect-session-info.c | 11 |
5 files changed, 681 insertions, 78 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1835ecd..5f18cc7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -58,6 +58,8 @@ console_kit_daemon_SOURCES = \ ck-manager.c \ ck-vt-monitor.h \ ck-vt-monitor.c \ + ck-job.h \ + ck-job.c \ ck-seat.h \ ck-seat.c \ ck-session.h \ diff --git a/src/ck-job.c b/src/ck-job.c new file mode 100644 index 0000000..44e9a57 --- /dev/null +++ b/src/ck-job.c @@ -0,0 +1,392 @@ +/* -*- 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 <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include "ck-job.h" +#include "ck-debug.h" +#include "ck-marshal.h" + +#define CK_JOB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CK_TYPE_JOB, CkJobPrivate)) + +struct CkJobPrivate +{ + guint err_watch_id; + guint out_watch_id; + + char *command; + GString *stdout; + GString *stderr; + GPid child_pid; + +}; + +enum { + COMPLETED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void ck_job_class_init (CkJobClass *klass); +static void ck_job_init (CkJob *job); +static void ck_job_finalize (GObject *object); + +G_DEFINE_TYPE (CkJob, ck_job, G_TYPE_OBJECT) + +GQuark +ck_job_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("ck_job_error"); + } + + return ret; +} + +static int +wait_on_child (int pid) +{ + int status; + + wait_again: + if (waitpid (pid, &status, 0) < 0) { + if (errno == EINTR) { + goto wait_again; + } else if (errno == ECHILD) { + ; /* do nothing, child already reaped */ + } else { + ck_debug ("waitpid () should not fail"); + } + } + + return status; +} + +static int +wait_on_job (CkJob *job) +{ + int status; + + status = wait_on_child (job->priv->child_pid); + + g_spawn_close_pid (job->priv->child_pid); + job->priv->child_pid = 0; + + return WEXITSTATUS(status); +} + +static void +maybe_complete_job (CkJob *job) +{ + if (job->priv->out_watch_id == 0 + && job->priv->err_watch_id == 0) { + int status; + + status = wait_on_job (job); + + ck_debug ("Emitting completed"); + g_signal_emit (job, signals [COMPLETED], 0, status); + } +} + +static gboolean +error_watch (GIOChannel *source, + GIOCondition condition, + CkJob *job) +{ + gboolean finished = FALSE; + + if (condition & G_IO_IN) { + GIOStatus status; + GError *error = NULL; + char *line; + + line = NULL; + status = g_io_channel_read_line (source, &line, NULL, NULL, &error); + + switch (status) { + case G_IO_STATUS_NORMAL: + ck_debug ("command error output: %s", line); + g_string_append (job->priv->stderr, line); + break; + case G_IO_STATUS_EOF: + finished = TRUE; + break; + case G_IO_STATUS_ERROR: + finished = TRUE; + ck_debug ("Error reading from child: %s\n", error->message); + break; + case G_IO_STATUS_AGAIN: + default: + break; + } + g_free (line); + } else if (condition & G_IO_HUP) { + finished = TRUE; + } + + if (finished) { + job->priv->err_watch_id = 0; + maybe_complete_job (job); + return FALSE; + } + + return TRUE; +} + +static gboolean +out_watch (GIOChannel *source, + GIOCondition condition, + CkJob *job) +{ + gboolean finished = FALSE; + + if (condition & G_IO_IN) { + GIOStatus status; + GError *error = NULL; + char *line; + + line = NULL; + status = g_io_channel_read_line (source, &line, NULL, NULL, &error); + + switch (status) { + case G_IO_STATUS_NORMAL: + ck_debug ("command output: %s", line); + g_string_append (job->priv->stdout, line); + break; + case G_IO_STATUS_EOF: + finished = TRUE; + break; + case G_IO_STATUS_ERROR: + finished = TRUE; + ck_debug ("Error reading from child: %s\n", error->message); + break; + case G_IO_STATUS_AGAIN: + default: + break; + } + g_free (line); + } else if (condition & G_IO_HUP) { + finished = TRUE; + } + + if (finished) { + job->priv->out_watch_id = 0; + maybe_complete_job (job); + return FALSE; + } + + return TRUE; +} + +gboolean +ck_job_execute (CkJob *job, + GError **error) +{ + GError *local_error; + gboolean res; + GIOChannel *channel; + int standard_output; + int standard_error; + int argc; + char **argv; + + ck_debug ("Executing %s", job->priv->command); + local_error = NULL; + if (! g_shell_parse_argv (job->priv->command, &argc, &argv, &local_error)) { + ck_debug ("Could not parse command: %s", local_error->message); + g_propagate_error (error, local_error); + return FALSE; + } + + local_error = NULL; + res = g_spawn_async_with_pipes (NULL, + argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &job->priv->child_pid, + NULL, + &standard_output, + &standard_error, + &local_error); + + g_strfreev (argv); + if (! res) { + ck_debug ("Could not start command '%s': %s", + job->priv->command, + local_error->message); + g_propagate_error (error, local_error); + return FALSE; + } + + /* output channel */ + channel = g_io_channel_unix_new (standard_output); + g_io_channel_set_close_on_unref (channel, TRUE); + g_io_channel_set_flags (channel, + g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, + NULL); + job->priv->out_watch_id = g_io_add_watch (channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc)out_watch, + job); + g_io_channel_unref (channel); + + /* error channel */ + channel = g_io_channel_unix_new (standard_error); + g_io_channel_set_close_on_unref (channel, TRUE); + g_io_channel_set_flags (channel, + g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, + NULL); + job->priv->err_watch_id = g_io_add_watch (channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc)error_watch, + job); + g_io_channel_unref (channel); + + return res; +} + +gboolean +ck_job_get_stdout (CkJob *job, + char **stdout) +{ + if (stdout != NULL) { + *stdout = g_strdup (job->priv->stdout->str); + } + return TRUE; +} + +gboolean +ck_job_set_command (CkJob *job, + const char *command) +{ + g_free (job->priv->command); + job->priv->command = g_strdup (command); + return TRUE; +} + +gboolean +ck_job_get_command (CkJob *job, + char **command) +{ + if (command != NULL) { + *command = g_strdup (job->priv->command); + } + + return TRUE; +} + +static void +ck_job_class_init (CkJobClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = ck_job_finalize; + + signals [COMPLETED] = + g_signal_new ("completed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CkJobClass, completed), + NULL, + NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, G_TYPE_INT); + + g_type_class_add_private (klass, sizeof (CkJobPrivate)); +} + +static void +ck_job_init (CkJob *job) +{ + job->priv = CK_JOB_GET_PRIVATE (job); + + job->priv->stderr = g_string_new (NULL); + job->priv->stdout = g_string_new (NULL); +} + +gboolean +ck_job_cancel (CkJob *job) +{ + if (job->priv->child_pid > 0) { + kill (job->priv->child_pid, SIGTERM); + wait_on_job (job); + return TRUE; + } + + return FALSE; +} + +static void +ck_job_finalize (GObject *object) +{ + CkJob *job; + + ck_debug ("Finalizing job"); + + g_return_if_fail (object != NULL); + g_return_if_fail (CK_IS_JOB (object)); + + job = CK_JOB (object); + + g_return_if_fail (job->priv != NULL); + + ck_job_cancel (job); + + if (job->priv->out_watch_id > 0) { + g_source_remove (job->priv->out_watch_id); + } + if (job->priv->err_watch_id > 0) { + g_source_remove (job->priv->err_watch_id); + } + g_free (job->priv->command); + g_string_free (job->priv->stdout, TRUE); + g_string_free (job->priv->stderr, TRUE); + + G_OBJECT_CLASS (ck_job_parent_class)->finalize (object); +} + +CkJob * +ck_job_new (void) +{ + GObject *object; + + object = g_object_new (CK_TYPE_JOB, NULL); + + return CK_JOB (object); +} diff --git a/src/ck-job.h b/src/ck-job.h new file mode 100644 index 0000000..a12a262 --- /dev/null +++ b/src/ck-job.h @@ -0,0 +1,76 @@ +/* -*- 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. + * + */ + +#ifndef __CK_JOB_H +#define __CK_JOB_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define CK_TYPE_JOB (ck_job_get_type ()) +#define CK_JOB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CK_TYPE_JOB, CkJob)) +#define CK_JOB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CK_TYPE_JOB, CkJobClass)) +#define CK_IS_JOB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CK_TYPE_JOB)) +#define CK_IS_JOB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CK_TYPE_JOB)) +#define CK_JOB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CK_TYPE_JOB, CkJobClass)) + +typedef struct CkJobPrivate CkJobPrivate; + +typedef struct +{ + GObject parent; + CkJobPrivate *priv; +} CkJob; + +typedef struct +{ + GObjectClass parent_class; + + void (* completed) (CkJob *job, + int status); +} CkJobClass; + +typedef enum +{ + CK_JOB_ERROR_GENERAL +} CkJobError; + +#define CK_JOB_ERROR ck_job_error_quark () + +GQuark ck_job_error_quark (void); +GType ck_job_get_type (void); +CkJob * ck_job_new (void); + +gboolean ck_job_set_command (CkJob *job, + const char *command); +gboolean ck_job_get_command (CkJob *job, + char **command); +gboolean ck_job_execute (CkJob *job, + GError **error); +gboolean ck_job_get_stdout (CkJob *job, + char **std_output); +gboolean ck_job_get_stderr (CkJob *job, + char **std_error); +gboolean ck_job_cancel (CkJob *job); + +G_END_DECLS + +#endif /* __CK_JOB_H */ diff --git a/src/ck-manager.c b/src/ck-manager.c index 080d86e..7ea6315 100644 --- a/src/ck-manager.c +++ b/src/ck-manager.c @@ -40,6 +40,7 @@ #include "ck-manager-glue.h" #include "ck-seat.h" #include "ck-session.h" +#include "ck-job.h" #include "ck-marshal.h" #include "ck-debug.h" @@ -75,12 +76,14 @@ struct CkManagerPrivate typedef struct { + int refcount; + gboolean cancelled; uid_t uid; pid_t pid; char *service_name; char *ssid; char *cookie; - GHashTable *pending_ids; + GList *pending_jobs; } LeaderInfo; enum { @@ -100,38 +103,65 @@ static gpointer manager_object = NULL; G_DEFINE_TYPE (CkManager, ck_manager, G_TYPE_OBJECT) -static LeaderInfo * -leader_info_copy (LeaderInfo *info) +static void +remove_pending_job (CkJob *job) { - LeaderInfo *new; - - new = g_new0 (LeaderInfo, 1); + if (job != NULL) { + char *command; - new->uid = info->uid; - new->pid = info->pid; - new->service_name = g_strdup (info->service_name); - new->ssid = g_strdup (info->ssid); - new->cookie = g_strdup (info->cookie); + command = NULL; + ck_job_get_command (job, &command); + ck_debug ("Removing pending job: %s", command); + g_free (command); - if (info->pending_ids != NULL) { - new->pending_ids = g_hash_table_ref (info->pending_ids); + ck_job_cancel (job); + g_object_unref (job); } - - return new; } static void -leader_info_free (LeaderInfo *info) +_leader_info_free (LeaderInfo *info) { + ck_debug ("Freeing leader info: %s", info->ssid); + g_free (info->ssid); + info->ssid = NULL; g_free (info->cookie); + info->cookie = NULL; g_free (info->service_name); + info->service_name = NULL; - if (info->pending_ids != NULL) { - g_hash_table_unref (info->pending_ids); + g_free (info); +} + +static void +leader_info_cancel (LeaderInfo *info) +{ + if (info->pending_jobs != NULL) { + g_list_foreach (info->pending_jobs, (GFunc)remove_pending_job, NULL); + g_list_free (info->pending_jobs); + info->pending_jobs = NULL; } - g_free (info); + info->cancelled = TRUE; +} + +static void +leader_info_unref (LeaderInfo *info) +{ + /* Probably should use some kind of atomic op here */ + info->refcount -= 1; + if (info->refcount == 0) { + _leader_info_free (info); + } +} + +static LeaderInfo * +leader_info_ref (LeaderInfo *info) +{ + info->refcount += 1; + + return info; } GQuark @@ -366,7 +396,7 @@ manager_set_system_idle_hint (CkManager *manager, /* FIXME: can we get a time from the dbus message? */ g_get_current_time (&manager->priv->system_idle_since_hint); - ck_debug ("Emitting system-idle-changed"); + ck_debug ("Emitting system-idle-hint-changed: %d", idle_hint); g_signal_emit (manager, signals [SYSTEM_IDLE_HINT_CHANGED], 0, idle_hint); } @@ -541,15 +571,18 @@ verify_and_open_session_for_leader_info (CkManager *manager, } static void -add_param_int (GPtrArray *parameters, - const char *key, - int value) +add_param_int (GPtrArray *parameters, + const char *key, + const char *value) { GValue val = { 0, }; GValue param_val = { 0, }; + int num; + + num = atoi (value); g_value_init (&val, G_TYPE_INT); - g_value_set_int (&val, value); + g_value_set_int (&val, num); g_value_init (¶m_val, CK_TYPE_PARAMETER_STRUCT); g_value_take_boxed (¶m_val, dbus_g_type_specialized_construct (CK_TYPE_PARAMETER_STRUCT)); @@ -561,15 +594,22 @@ add_param_int (GPtrArray *parameters, } static void -add_param_boolean (GPtrArray *parameters, - const char *key, - gboolean value) +add_param_boolean (GPtrArray *parameters, + const char *key, + const char *value) { - GValue val = { 0, }; - GValue param_val = { 0, }; + GValue val = { 0, }; + GValue param_val = { 0, }; + gboolean b; + + if (value != NULL && strcmp (value, "true") == 0) { + b = TRUE; + } else { + b = FALSE; + } g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, value); + g_value_set_boolean (&val, b); g_value_init (¶m_val, CK_TYPE_PARAMETER_STRUCT); g_value_take_boxed (¶m_val, dbus_g_type_specialized_construct (CK_TYPE_PARAMETER_STRUCT)); @@ -581,9 +621,9 @@ add_param_boolean (GPtrArray *parameters, } static void -add_param_string (GPtrArray *parameters, - const char *key, - const char *value) +add_param_string (GPtrArray *parameters, + const char *key, + const char *value) { GValue val = { 0, }; GValue param_val = { 0, }; @@ -602,23 +642,135 @@ add_param_string (GPtrArray *parameters, g_ptr_array_add (parameters, g_value_get_boxed (¶m_val)); } +typedef void (* CkAddParamFunc) (GPtrArray *arr, + const char *key, + const char *value); + +static struct { + char *key; + CkAddParamFunc func; +} parse_ops[] = { + { "display-device", add_param_string }, + { "x11-display", add_param_string }, + { "host-name", add_param_string }, + { "session-type", add_param_string }, + { "is-local", add_param_boolean }, + { "user", add_param_int }, +}; + +static GPtrArray * +parse_output (const char *output) +{ + GPtrArray *parameters; + char **lines; + int i; + int j; + + lines = g_strsplit (output, "\n", -1); + if (lines == NULL) { + return NULL; + } + + parameters = g_ptr_array_sized_new (10); + + for (i = 0; lines[i] != NULL; i++) { + char **vals; + + vals = g_strsplit (lines[i], " = ", 2); + if (vals == NULL || vals[0] == NULL) { + continue; + } + + for (j = 0; j < G_N_ELEMENTS (parse_ops); j++) { + if (strcmp (vals[0], parse_ops[j].key) == 0) { + parse_ops[j].func (parameters, vals[0], vals[1]); + break; + } + } + g_strfreev (vals); + } + + return parameters; +} + +typedef struct { + CkManager *manager; + LeaderInfo *leader_info; + DBusGMethodInvocation *context; +} JobData; + +static void +job_data_free (JobData *data) +{ + leader_info_unref (data->leader_info); + g_free (data); +} + +static void +job_completed (CkJob *job, + int status, + JobData *data) +{ + ck_debug ("Job status: %d", status); + if (status == 0) { + char *output; + GPtrArray *parameters; + + output = NULL; + ck_job_get_stdout (job, &output); + ck_debug ("Job output: %s", output); + + parameters = parse_output (output); + g_free (output); + + verify_and_open_session_for_leader_info (data->manager, + data->leader_info, + parameters, + data->context); + + g_ptr_array_free (parameters, TRUE); + + } + + /* remove job from queue */ + data->leader_info->pending_jobs = g_list_remove (data->leader_info->pending_jobs, job); + + g_signal_handlers_disconnect_by_func (job, job_completed, data); + g_object_unref (job); +} + static void generate_session_for_leader_info (CkManager *manager, LeaderInfo *leader_info, DBusGMethodInvocation *context) { - GPtrArray *parameters; GError *local_error; - proc_stat_t *stat; - char *cmd; - char *x11_display; - char *tty; + char *command; gboolean res; - - /* FIXME: callout to a helper tool to generate all parameters */ + CkJob *job; + JobData *data; + + command = g_strdup_printf ("%s --uid %u --pid %u", + LIBEXECDIR "/ck-collect-session-info", + leader_info->uid, + leader_info->pid); + job = ck_job_new (); + ck_job_set_command (job, command); + g_free (command); + + data = g_new0 (JobData, 1); + data->manager = manager; + data->leader_info = leader_info_ref (leader_info); + data->context = context; + g_signal_connect_data (job, + "completed", + G_CALLBACK (job_completed), + data, + (GClosureNotify)job_data_free, + 0); local_error = NULL; - res = proc_stat_new_for_pid (leader_info->pid, &stat, &local_error); + res = ck_job_execute (job, &local_error); if (! res) { GError *error; error = g_error_new (CK_MANAGER_ERROR, @@ -632,33 +784,13 @@ generate_session_for_leader_info (CkManager *manager, g_error_free (local_error); } + g_object_unref (job); + return; } - tty = proc_stat_get_tty (stat); - cmd = proc_stat_get_cmd (stat); - x11_display = NULL; - proc_stat_free (stat); - - parameters = g_ptr_array_sized_new (10); - /* FIXME: What should this be? */ - add_param_boolean (parameters, "is-local", TRUE); - add_param_int (parameters, "user", leader_info->uid); - add_param_string (parameters, "x11-display", x11_display); - add_param_string (parameters, "display-device", tty); - /* FIXME: this isn't really a reliable value to use */ - add_param_string (parameters, "session-type", cmd); - - g_free (x11_display); - g_free (cmd); - g_free (tty); - - verify_and_open_session_for_leader_info (manager, - leader_info, - parameters, - context); - - g_ptr_array_free (parameters, TRUE); + /* Add job to queue */ + leader_info->pending_jobs = g_list_prepend (leader_info->pending_jobs, job); } static gboolean @@ -701,7 +833,9 @@ create_session_for_sender (CkManager *manager, leader_info->cookie = g_strdup (cookie); /* need to store the leader info first so the pending request can be revoked */ - g_hash_table_insert (manager->priv->leaders, g_strdup (leader_info->cookie), leader_info); + g_hash_table_insert (manager->priv->leaders, + g_strdup (leader_info->cookie), + leader_info_ref (leader_info)); if (parameters == NULL) { generate_session_for_leader_info (manager, @@ -967,13 +1101,6 @@ ck_manager_open_session_with_parameters (CkManager *manager, } static gboolean -cancel_pending_actions_for_cookie (CkManager *manager, - const char *cookie) -{ - return TRUE; -} - -static gboolean remove_session_for_cookie (CkManager *manager, const char *cookie, GError **error) @@ -1150,9 +1277,8 @@ remove_leader_for_connection (const char *cookie, g_assert (data->service_name != NULL); if (strcmp (info->service_name, data->service_name) == 0) { - cancel_pending_actions_for_cookie (data->manager, cookie); remove_session_for_cookie (data->manager, cookie, NULL); - + leader_info_cancel (info); return TRUE; } @@ -1371,7 +1497,7 @@ ck_manager_init (CkManager *manager) manager->priv->leaders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) leader_info_free); + (GDestroyNotify) leader_info_unref); create_seats (manager); } diff --git a/tools/linux/ck-collect-session-info.c b/tools/linux/ck-collect-session-info.c index 2979da0..66ecd26 100644 --- a/tools/linux/ck-collect-session-info.c +++ b/tools/linux/ck-collect-session-info.c @@ -308,6 +308,7 @@ fill_session_info (SessionInfo *si) static void print_session_info (SessionInfo *si) { + printf ("user = %u\n", si->uid); if (si->display_device != NULL) { printf ("display-device = %s\n", si->display_device); } @@ -362,6 +363,12 @@ main (int argc, { NULL } }; + /* For now at least restrict this to root */ + if (getuid () != 0) { + g_warning ("You must be root to run this program"); + exit (1); + } + context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); error = NULL; @@ -374,7 +381,7 @@ main (int argc, exit (1); } - if (user_id < 500) { + if (user_id < 0) { g_warning ("Invalid UID"); exit (1); } @@ -386,5 +393,5 @@ main (int argc, ret = collect_session_info (user_id, process_id); - return ret; + return ret != TRUE; } |