From ffc73820840e41a0045c6712c5ab5b94335c8cc5 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Fri, 06 Jun 2008 19:39:50 +0000 Subject: Prefix the session server stuff with gsm, makes things a lot less confusing. --- (limited to 'src/sugar/gsm-client-xsmp.c') diff --git a/src/sugar/gsm-client-xsmp.c b/src/sugar/gsm-client-xsmp.c new file mode 100644 index 0000000..dcacc6f --- /dev/null +++ b/src/sugar/gsm-client-xsmp.c @@ -0,0 +1,828 @@ +/* client-xsmp.c + * Copyright (C) 2007 Novell, Inc. + * + * 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 + * Lesser 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "gsm-client-xsmp.h" +#include "gsm-session.h" + +/* FIXME */ +#define GsmDesktopFile "_Gsm_DesktopFile" + +static gboolean client_iochannel_watch (GIOChannel *channel, + GIOCondition condition, + gpointer data); +static gboolean client_protocol_timeout (gpointer data); + +static void set_description (GsmClientXSMP *xsmp); + +static const char *xsmp_get_client_id (GsmClient *client); +static pid_t xsmp_get_pid (GsmClient *client); +static char *xsmp_get_desktop_file (GsmClient *client); +static char *xsmp_get_restart_command (GsmClient *client); +static char *xsmp_get_discard_command (GsmClient *client); +static gboolean xsmp_get_autorestart (GsmClient *client); + +static void xsmp_finalize (GObject *object); +static void xsmp_restart (GsmClient *client, + GError **error); +static void xsmp_save_yourself (GsmClient *client, + gboolean save_state); +static void xsmp_save_yourself_phase2 (GsmClient *client); +static void xsmp_interact (GsmClient *client); +static void xsmp_shutdown_cancelled (GsmClient *client); +static void xsmp_die (GsmClient *client); + +G_DEFINE_TYPE (GsmClientXSMP, gsm_client_xsmp, GSM_TYPE_CLIENT) + +static void +gsm_client_xsmp_init (GsmClientXSMP *xsmp) +{ + ; +} + +static void +gsm_client_xsmp_class_init (GsmClientXSMPClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmClientClass *client_class = GSM_CLIENT_CLASS (klass); + + object_class->finalize = xsmp_finalize; + + client_class->get_client_id = xsmp_get_client_id; + client_class->get_pid = xsmp_get_pid; + client_class->get_desktop_file = xsmp_get_desktop_file; + client_class->get_restart_command = xsmp_get_restart_command; + client_class->get_discard_command = xsmp_get_discard_command; + client_class->get_autorestart = xsmp_get_autorestart; + + client_class->restart = xsmp_restart; + client_class->save_yourself = xsmp_save_yourself; + client_class->save_yourself_phase2 = xsmp_save_yourself_phase2; + client_class->interact = xsmp_interact; + client_class->shutdown_cancelled = xsmp_shutdown_cancelled; + client_class->die = xsmp_die; +} + +GsmClientXSMP * +gsm_client_xsmp_new (IceConn ice_conn) +{ + GsmClientXSMP *xsmp; + GIOChannel *channel; + int fd; + + xsmp = g_object_new (GSM_TYPE_CLIENT_XSMP, NULL); + xsmp->props = g_ptr_array_new (); + + xsmp->ice_conn = ice_conn; + xsmp->current_save_yourself = -1; + xsmp->next_save_yourself = -1; + + fd = IceConnectionNumber (ice_conn); + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + + channel = g_io_channel_unix_new (fd); + xsmp->watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR, + client_iochannel_watch, xsmp); + g_io_channel_unref (channel); + + xsmp->protocol_timeout = g_timeout_add (5000, client_protocol_timeout, xsmp); + + set_description (xsmp); + g_debug ("New client '%s'", xsmp->description); + + return xsmp; +} + +static void +xsmp_finalize (GObject *object) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) object; + + g_debug ("xsmp_finalize (%s)", xsmp->description); + + if (xsmp->watch_id) + g_source_remove (xsmp->watch_id); + + if (xsmp->conn) + SmsCleanUp (xsmp->conn); + else + IceCloseConnection (xsmp->ice_conn); + + if (xsmp->protocol_timeout) + g_source_remove (xsmp->protocol_timeout); + + G_OBJECT_CLASS (gsm_client_xsmp_parent_class)->finalize (object); +} + +static gboolean +client_iochannel_watch (GIOChannel *channel, + GIOCondition condition, + gpointer data) +{ + GsmClient *client = data; + GsmClientXSMP *xsmp = data; + + switch (IceProcessMessages (xsmp->ice_conn, NULL, NULL)) + { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: + g_debug ("IceProcessMessagesIOError on '%s'", xsmp->description); + gsm_client_disconnected (client); + return FALSE; + + case IceProcessMessagesConnectionClosed: + g_debug ("IceProcessMessagesConnectionClosed on '%s'", + xsmp->description); + return FALSE; + + default: + g_assert_not_reached (); + } +} + +/* Called if too much time passes between the initial connection and + * the XSMP protocol setup. + */ +static gboolean +client_protocol_timeout (gpointer data) +{ + GsmClient *client = data; + GsmClientXSMP *xsmp = data; + + g_debug ("client_protocol_timeout for client '%s' in ICE status %d", + xsmp->description, IceConnectionStatus (xsmp->ice_conn)); + gsm_client_disconnected (client); + + return FALSE; +} + +static Status +register_client_callback (SmsConn conn, + SmPointer manager_data, + char *previous_id) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + char *id; + + g_debug ("Client '%s' received RegisterClient(%s)", + xsmp->description, + previous_id ? previous_id : "NULL"); + + id = gsm_session_register_client (global_session, client, previous_id); + + if (id == NULL) + { + g_debug (" rejected: invalid previous_id"); + free (previous_id); + return FALSE; + } + + xsmp->id = id; + + set_description (xsmp); + + g_debug ("Sending RegisterClientReply to '%s'", xsmp->description); + + SmsRegisterClientReply (conn, xsmp->id); + + if (!previous_id) + { + /* Send the initial SaveYourself. */ + g_debug ("Sending initial SaveYourself"); + SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False); + xsmp->current_save_yourself = SmSaveLocal; + + free (previous_id); + } + + return TRUE; +} + +static void +do_save_yourself (GsmClientXSMP *xsmp, int save_type) +{ + if (xsmp->next_save_yourself != -1) + { + /* Either we're currently doing a shutdown and there's a checkpoint + * queued after it, or vice versa. Either way, the new SaveYourself + * is redundant. + */ + g_debug (" skipping redundant SaveYourself for '%s'", + xsmp->description); + } + else if (xsmp->current_save_yourself != -1) + { + g_debug (" queuing new SaveYourself for '%s'", + xsmp->description); + xsmp->next_save_yourself = save_type; + } + else + { + xsmp->current_save_yourself = save_type; + + switch (save_type) + { + case SmSaveLocal: + /* Save state */ + SmsSaveYourself (xsmp->conn, SmSaveLocal, FALSE, + SmInteractStyleNone, FALSE); + break; + + default: + /* Logout */ + SmsSaveYourself (xsmp->conn, save_type, TRUE, + SmInteractStyleAny, FALSE); + break; + } + } +} + +static void +save_yourself_request_callback (SmsConn conn, + SmPointer manager_data, + int save_type, + Bool shutdown, + int interact_style, + Bool fast, + Bool global) +{ + GsmClientXSMP *xsmp = manager_data; + + g_debug ("Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)", + xsmp->description, + save_type == SmSaveLocal ? "SmSaveLocal" : + save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", + shutdown ? "Shutdown" : "!Shutdown", + interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : + interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : + "SmInteractStyleNone", fast ? "Fast" : "!Fast", + global ? "Global" : "!Global"); + + /* Examining the g_debug above, you can see that there are a total + * of 72 different combinations of options that this could have been + * called with. However, most of them are stupid. + * + * If @shutdown and @global are both TRUE, that means the caller is + * requesting that a logout message be sent to all clients, so we do + * that. We use @fast to decide whether or not to show a + * confirmation dialog. (This isn't really what @fast is for, but + * the old gnome-session and ksmserver both interpret it that way, + * so we do too.) We ignore @save_type because we pick the correct + * save_type ourselves later based on user prefs, dialog choices, + * etc, and we ignore @interact_style, because clients have not used + * it correctly consistently enough to make it worth honoring. + * + * If @shutdown is TRUE and @global is FALSE, the caller is + * confused, so we ignore the request. + * + * If @shutdown is FALSE and @save_type is SmSaveGlobal or + * SmSaveBoth, then the client wants us to ask some or all open + * applications to save open files to disk, but NOT quit. This is + * silly and so we ignore the request. + * + * If @shutdown is FALSE and @save_type is SmSaveLocal, then the + * client wants us to ask some or all open applications to update + * their current saved state, but not log out. At the moment, the + * code only supports this for the !global case (ie, a client + * requesting that it be allowed to update *its own* saved state, + * but not having everyone else update their saved state). + */ + + if (shutdown && global) + { + g_debug (" initiating shutdown"); +/* gsm_session_initiate_shutdown (global_session, + !fast, + GSM_SESSION_LOGOUT_TYPE_LOGOUT); +*/ + } + else if (!shutdown && !global) + { + g_debug (" initiating checkpoint"); + do_save_yourself (xsmp, SmSaveLocal); + } + else + g_debug (" ignoring"); +} + +static void +xsmp_restart (GsmClient *client, GError **error) +{ + char *restart_cmd = gsm_client_get_restart_command (client); + + g_spawn_command_line_async (restart_cmd, error); + + g_free (restart_cmd); +} + +static void +xsmp_save_yourself (GsmClient *client, gboolean save_state) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *)client; + + g_debug ("xsmp_save_yourself ('%s', %s)", xsmp->description, + save_state ? "True" : "False"); + + do_save_yourself (xsmp, save_state ? SmSaveBoth : SmSaveGlobal); +} + +static void +save_yourself_phase2_request_callback (SmsConn conn, + SmPointer manager_data) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + + g_debug ("Client '%s' received SaveYourselfPhase2Request", + xsmp->description); + + if (xsmp->current_save_yourself == SmSaveLocal) + { + /* WTF? Anyway, if it's checkpointing, it doesn't have to wait + * for anyone else. + */ + SmsSaveYourselfPhase2 (xsmp->conn); + } + else + gsm_client_request_phase2 (client); +} + +static void +xsmp_save_yourself_phase2 (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *)client; + + g_debug ("xsmp_save_yourself_phase2 ('%s')", xsmp->description); + + SmsSaveYourselfPhase2 (xsmp->conn); +} + +static void +interact_request_callback (SmsConn conn, + SmPointer manager_data, + int dialog_type) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + + g_debug ("Client '%s' received InteractRequest(%s)", xsmp->description, + dialog_type == SmInteractStyleAny ? "Any" : "Errors"); + + gsm_client_request_interaction (client); +} + +static void +xsmp_interact (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + + g_debug ("xsmp_interact ('%s')", xsmp->description); + + SmsInteract (xsmp->conn); +} + +static void +interact_done_callback (SmsConn conn, + SmPointer manager_data, + Bool cancel_shutdown) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + + g_debug ("Client '%s' received InteractDone(cancel_shutdown = %s)", + xsmp->description, cancel_shutdown ? "True" : "False"); + + gsm_client_interaction_done (client, cancel_shutdown); +} + +static void +xsmp_shutdown_cancelled (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + + g_debug ("xsmp_shutdown_cancelled ('%s')", xsmp->description); + + SmsShutdownCancelled (xsmp->conn); +} + +static void +xsmp_die (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + + g_debug ("xsmp_die ('%s')", xsmp->description); + + SmsDie (xsmp->conn); +} + +static void +save_yourself_done_callback (SmsConn conn, + SmPointer manager_data, + Bool success) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + + g_debug ("Client '%s' received SaveYourselfDone(success = %s)", + xsmp->description, success ? "True" : "False"); + + if (xsmp->current_save_yourself == SmSaveLocal) + { + xsmp->current_save_yourself = -1; + SmsSaveComplete (xsmp->conn); + gsm_client_saved_state (client); + } + else + { + xsmp->current_save_yourself = -1; + gsm_client_save_yourself_done (client); + } + + if (xsmp->next_save_yourself) + { + int save_type = xsmp->next_save_yourself; + + xsmp->next_save_yourself = -1; + do_save_yourself (xsmp, save_type); + } +} + +static void +close_connection_callback (SmsConn conn, + SmPointer manager_data, + int count, + char **reason_msgs) +{ + GsmClient *client = manager_data; + GsmClientXSMP *xsmp = manager_data; + int i; + + g_debug ("Client '%s' received CloseConnection", xsmp->description); + for (i = 0; i < count; i++) + g_debug (" close reason: '%s'", reason_msgs[i]); + SmFreeReasons (count, reason_msgs); + + gsm_client_disconnected (client); +} + +static void +debug_print_property (SmProp *prop) +{ + GString *tmp; + int i; + + switch (prop->type[0]) + { + case 'C': /* CARD8 */ + g_debug (" %s = %d", prop->name, *(unsigned char *)prop->vals[0].value); + break; + + case 'A': /* ARRAY8 */ + g_debug (" %s = '%s'", prop->name, (char *)prop->vals[0].value); + break; + + case 'L': /* LISTofARRAY8 */ + tmp = g_string_new (NULL); + for (i = 0; i < prop->num_vals; i++) + { + g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length, + (char *)prop->vals[i].value); + } + g_debug (" %s = %s", prop->name, tmp->str); + g_string_free (tmp, TRUE); + break; + + default: + g_debug (" %s = ??? (%s)", prop->name, prop->type); + break; + } +} + +static SmProp * +find_property (GsmClientXSMP *client, const char *name, int *index) +{ + SmProp *prop; + int i; + + for (i = 0; i < client->props->len; i++) + { + prop = client->props->pdata[i]; + + if (!strcmp (prop->name, name)) + { + if (index) + *index = i; + return prop; + } + } + + return NULL; +} + +static void +delete_property (GsmClientXSMP *client, const char *name) +{ + int index; + SmProp *prop; + + prop = find_property (client, name, &index); + if (!prop) + return; + +#if 0 + /* This is wrong anyway; we can't unconditionally run the current + * discard command; if this client corresponds to a GsmAppResumed, + * and the current discard command is identical to the app's + * discard_command, then we don't run the discard command now, + * because that would delete a saved state we may want to resume + * again later. + */ + if (!strcmp (name, SmDiscardCommand)) + gsm_client_run_discard (client); +#endif + + g_ptr_array_remove_index_fast (client->props, index); + SmFreeProperty (prop); +} + +static void +set_properties_callback (SmsConn conn, + SmPointer manager_data, + int num_props, + SmProp **props) +{ + GsmClientXSMP *client = manager_data; + int i; + + g_debug ("Set properties from client '%s'", client->description); + + for (i = 0; i < num_props; i++) + { + delete_property (client, props[i]->name); + g_ptr_array_add (client->props, props[i]); + + debug_print_property (props[i]); + + if (!strcmp (props[i]->name, SmProgram)) + set_description (client); + } + + free (props); + +} + +static void +delete_properties_callback (SmsConn conn, + SmPointer manager_data, + int num_props, + char **prop_names) +{ + GsmClientXSMP *client = manager_data; + int i; + + g_debug ("Delete properties from '%s'", client->description); + + for (i = 0; i < num_props; i++) + { + delete_property (client, prop_names[i]); + + g_debug (" %s", prop_names[i]); + } + + free (prop_names); +} + +static void +get_properties_callback (SmsConn conn, + SmPointer manager_data) +{ + GsmClientXSMP *client = manager_data; + + g_debug ("Get properties request from '%s'", client->description); + + SmsReturnProperties (conn, client->props->len, + (SmProp **)client->props->pdata); +} + +static const char * +xsmp_get_client_id (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + + return xsmp->id; +} + +static pid_t +xsmp_get_pid (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + SmProp *prop = find_property (xsmp, SmProcessID, NULL); + char buf[32]; + + if (!prop || strcmp (prop->type, SmARRAY8) != 0) + return (pid_t)-1; + + /* prop->vals[0].value might not be '\0'-terminated... */ + g_strlcpy (buf, prop->vals[0].value, MIN (prop->vals[0].length, sizeof (buf))); + return (pid_t)strtoul (buf, NULL, 10); +} + +static char * +xsmp_get_desktop_file (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + SmProp *prop = find_property (xsmp, GsmDesktopFile, NULL); + + if (!prop || strcmp (prop->type, SmARRAY8) != 0) + return NULL; + + return g_strndup (prop->vals[0].value, prop->vals[0].length); +} + +static char * +prop_to_command (SmProp *prop) +{ + GString *str; + int i, j; + gboolean need_quotes; + + str = g_string_new (NULL); + for (i = 0; i < prop->num_vals; i++) + { + char *val = prop->vals[i].value; + + need_quotes = FALSE; + for (j = 0; j < prop->vals[i].length; j++) + { + if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j])) + { + need_quotes = TRUE; + break; + } + } + + if (i > 0) + g_string_append_c (str, ' '); + + if (!need_quotes) + { + g_string_append_printf (str, "%.*s", prop->vals[i].length, + (char *)prop->vals[i].value); + } + else + { + g_string_append_c (str, '\''); + while (val < (char *)prop->vals[i].value + prop->vals[i].length) + { + if (*val == '\'') + g_string_append (str, "'\''"); + else + g_string_append_c (str, *val); + val++; + } + g_string_append_c (str, '\''); + } + } + + return g_string_free (str, FALSE); +} + +static char * +xsmp_get_restart_command (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + SmProp *prop = find_property (xsmp, SmRestartCommand, NULL); + + if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) + return NULL; + + return prop_to_command (prop); +} + +static char * +xsmp_get_discard_command (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + SmProp *prop = find_property (xsmp, SmDiscardCommand, NULL); + + if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0) + return NULL; + + return prop_to_command (prop); +} + +static gboolean +xsmp_get_autorestart (GsmClient *client) +{ + GsmClientXSMP *xsmp = (GsmClientXSMP *) client; + SmProp *prop = find_property (xsmp, SmRestartStyleHint, NULL); + + if (!prop || strcmp (prop->type, SmCARD8) != 0) + return FALSE; + + return ((unsigned char *)prop->vals[0].value)[0] == SmRestartImmediately; +} + +static void +set_description (GsmClientXSMP *client) +{ + SmProp *prop = find_property (client, SmProgram, NULL); + + g_free (client->description); + if (prop) + { + client->description = g_strdup_printf ("%p [%.*s %s]", client, + prop->vals[0].length, + (char *)prop->vals[0].value, + client->id); + } + else if (client->id) + client->description = g_strdup_printf ("%p [%s]", client, client->id); + else + client->description = g_strdup_printf ("%p", client); +} + +void +gsm_client_xsmp_connect (GsmClientXSMP *client, SmsConn conn, + unsigned long *mask_ret, SmsCallbacks *callbacks_ret) +{ + client->conn = conn; + + if (client->protocol_timeout) + { + g_source_remove (client->protocol_timeout); + client->protocol_timeout = 0; + } + + g_debug ("Initializing client %s", client->description); + + *mask_ret = 0; + + *mask_ret |= SmsRegisterClientProcMask; + callbacks_ret->register_client.callback = register_client_callback; + callbacks_ret->register_client.manager_data = client; + + *mask_ret |= SmsInteractRequestProcMask; + callbacks_ret->interact_request.callback = interact_request_callback; + callbacks_ret->interact_request.manager_data = client; + + *mask_ret |= SmsInteractDoneProcMask; + callbacks_ret->interact_done.callback = interact_done_callback; + callbacks_ret->interact_done.manager_data = client; + + *mask_ret |= SmsSaveYourselfRequestProcMask; + callbacks_ret->save_yourself_request.callback = save_yourself_request_callback; + callbacks_ret->save_yourself_request.manager_data = client; + + *mask_ret |= SmsSaveYourselfP2RequestProcMask; + callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback; + callbacks_ret->save_yourself_phase2_request.manager_data = client; + + *mask_ret |= SmsSaveYourselfDoneProcMask; + callbacks_ret->save_yourself_done.callback = save_yourself_done_callback; + callbacks_ret->save_yourself_done.manager_data = client; + + *mask_ret |= SmsCloseConnectionProcMask; + callbacks_ret->close_connection.callback = close_connection_callback; + callbacks_ret->close_connection.manager_data = client; + + *mask_ret |= SmsSetPropertiesProcMask; + callbacks_ret->set_properties.callback = set_properties_callback; + callbacks_ret->set_properties.manager_data = client; + + *mask_ret |= SmsDeletePropertiesProcMask; + callbacks_ret->delete_properties.callback = delete_properties_callback; + callbacks_ret->delete_properties.manager_data = client; + + *mask_ret |= SmsGetPropertiesProcMask; + callbacks_ret->get_properties.callback = get_properties_callback; + callbacks_ret->get_properties.manager_data = client; +} -- cgit v0.9.1