1
/*********************************************************
2
* Copyright (C) 2011 VMware, Inc. All rights reserved.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License as published
6
* by the Free Software Foundation version 2.1 and no later version.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10
* or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
11
* License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program; if not, write to the Free Software Foundation, Inc.,
15
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17
*********************************************************/
22
* Support for X session management using the, well, X Session Management
23
* Library (libSM) with a little help from the Inter-Client Exchange Library
24
* (libICE). This allows vmusr to receive X session lifecycle events and clean
25
* up appropriately upon session termination. For now, cleanup upon shutdown
26
* is the only activity we're interested in.
28
* A custom event source is used to bind libICE connections with GLib's main
29
* loop and dispatch messages. As this is the first (and hopefully only)
30
* libICE client, all ICE interaction is handled here (as opposed to in a
31
* provider application).
33
* This should work with any session manager implementing XSMP, covering all
34
* of our supported desktop environments.
36
* This plugin also maps the XSM callbacks to GLib signals. See desktopevents.h
39
* @todo Scrutinize libICE error handling. I/O errors should be handled here,
40
* but errors handled by libICE's default may exit().
44
/* Include first. Sets G_LOG_DOMAIN. */
45
#include "desktopEventsInt.h"
48
#include "vmware/tools/desktopevents.h"
49
#include "sessionMgrSignals.h"
57
#include <X11/SM/SMlib.h>
58
#include <X11/ICE/ICElib.h>
62
* Identifier used for SessionMgr private data in DesktopEvents private hash
65
#define DE_FEATURE_KEY "sessionMgr"
68
static void InitSignals(ToolsAppCtx* ctx);
69
static void InitSMProperties(SmcConn smcCnx);
73
* libICE integration declarations.
82
static gboolean ICEDispatch(GIOChannel *chn, GIOCondition cond, gpointer cbData);
83
static void ICEIOErrorHandler(IceConn iceCnx);
84
static void ICEWatch(IceConn iceCnx, IcePointer clientData, Bool opening,
85
IcePointer *watchData);
93
static void SMDieCb(SmcConn smcCnx, SmPointer cbData);
94
static void SMSaveYourselfCb(SmcConn smcCnx, SmPointer cbData, int saveType,
95
Bool shutdown, int interactStyle, Bool fast);
96
static void SMSaveCompleteCb(SmcConn smcCnx, SmPointer cbData);
97
static void SMShutdownCancelledCb(SmcConn smcCnx, SmPointer cbData);
101
******************************************************************************
102
* SessionMgr_Init -- */ /**
104
* Register custom ICE connection source and sign up with the session manager.
106
* @param[in] ctx Application context.
107
* @param[in] pdata Plugin data.
109
* @retval TRUE Always "succeeds". Session management is optional.
111
******************************************************************************
115
SessionMgr_Init(ToolsAppCtx *ctx,
116
ToolsPluginData *pdata)
118
SmcCallbacks smCallbacks;
119
unsigned long cbMask =
120
SmcSaveYourselfProcMask
122
| SmcSaveCompleteProcMask
123
| SmcShutdownCancelledProcMask;
126
char *clientID = NULL;
128
IceSetIOErrorHandler(ICEIOErrorHandler);
129
IceAddConnectionWatch(ICEWatch, pdata);
131
memset(&smCallbacks, 0, sizeof smCallbacks);
132
smCallbacks.save_yourself.callback = &SMSaveYourselfCb;
133
smCallbacks.save_yourself.client_data = pdata;
134
smCallbacks.save_complete.callback = &SMSaveCompleteCb;
135
smCallbacks.save_complete.client_data = pdata;
136
smCallbacks.shutdown_cancelled.callback = &SMShutdownCancelledCb;
137
smCallbacks.shutdown_cancelled.client_data = pdata;
138
smCallbacks.die.callback = &SMDieCb;
139
smCallbacks.die.client_data = pdata;
142
SmcOpenConnection(NULL, NULL, SmProtoMajor, SmProtoMinor, cbMask,
143
&smCallbacks, NULL, &clientID, sizeof errorBuf, errorBuf);
144
if (smcCnx != NULL) {
146
InitSMProperties(smcCnx);
147
g_hash_table_insert(pdata->_private, DE_FEATURE_KEY, smcCnx);
148
g_debug("Registered with session manager as %s\n", clientID);
151
g_message("Failed to register with session manager.\n");
152
g_message("SmcOpenConnection: %s\n", errorBuf);
153
IceRemoveConnectionWatch(ICEWatch, pdata);
161
******************************************************************************
162
* SessionMgr_Shutdown -- */ /**
164
* Shuts down XSM and ICE interfaces and frees other resources.
166
* @param[in] ctx Application context.
167
* @param[in] pdata Plugin data.
169
******************************************************************************
173
SessionMgr_Shutdown(ToolsAppCtx *ctx,
174
ToolsPluginData *pdata)
176
GHashTable *desktopData = pdata->_private;
177
SmcConn smcCnx = g_hash_table_lookup(desktopData, DE_FEATURE_KEY);
178
if (smcCnx != NULL) {
179
SmcCloseConnection(smcCnx, 0, NULL);
180
IceRemoveConnectionWatch(ICEWatch, pdata);
181
g_hash_table_remove(desktopData, DE_FEATURE_KEY);
187
******************************************************************************
188
* InitSignals -- */ /**
190
* Creates new signals for XSM events.
192
* @param[in] ctx ToolsAppCtx*: Application context.
194
******************************************************************************
198
InitSignals(ToolsAppCtx *ctx)
200
/* SmcCallbacks::save_yourself */
201
g_signal_new(TOOLS_CORE_SIG_XSM_SAVE_YOURSELF,
202
G_OBJECT_TYPE(ctx->serviceObj),
207
g_cclosure_user_marshal_VOID__POINTER_INT_BOOLEAN_INT_BOOLEAN,
216
/* SmcCallbacks::die */
217
g_signal_new(TOOLS_CORE_SIG_XSM_DIE,
218
G_OBJECT_TYPE(ctx->serviceObj),
223
g_cclosure_marshal_VOID__POINTER,
228
/* SmcCallbacks::save_complete */
229
g_signal_new(TOOLS_CORE_SIG_XSM_SAVE_COMPLETE,
230
G_OBJECT_TYPE(ctx->serviceObj),
235
g_cclosure_marshal_VOID__POINTER,
240
/* SmcCallbacks::shutdown_cancelled */
241
g_signal_new(TOOLS_CORE_SIG_XSM_SHUTDOWN_CANCELLED,
242
G_OBJECT_TYPE(ctx->serviceObj),
247
g_cclosure_marshal_VOID__POINTER,
255
******************************************************************************
256
* InitSMProperties -- */ /**
258
* Tell the session manager a little bit about ourself.
260
* The most important property to us is SmRestartStyleHint, where we hint to
261
* the session manager that it shouldn't attempt to restore the vmusr container
262
* as part of a session. (Instead, that job is handled by our XDG autostart
265
* The other properties are set only because SMlib docs claim they're
266
* mandatory. Dummy values are used where possible.
268
* @param[in] smcCnx SM client connection.
270
******************************************************************************
274
InitSMProperties(SmcConn smcCnx)
280
PROP_ID_RESTART_STYLE,
283
static uint8 restartHint = SmRestartNever;
284
static SmPropValue values[] = {
285
{ sizeof "/bin/false" - 1, "/bin/false" },
286
{ sizeof "vmware-user" - 1, "vmware-user" },
287
{ sizeof "/bin/false" - 1, "/bin/false" },
288
{ sizeof restartHint, &restartHint },
291
static SmProp properties[] = {
292
{ SmCloneCommand, SmLISTofARRAY8, 1, &values[PROP_ID_CLONE_CMD] },
293
{ SmProgram, SmARRAY8, 1, &values[PROP_ID_PROGRAM] },
294
{ SmRestartCommand, SmLISTofARRAY8, 1, &values[PROP_ID_RESTART_CMD] },
295
{ SmRestartStyleHint, SmCARD8, 1, &values[PROP_ID_RESTART_STYLE] },
296
{ SmUserID, SmARRAY8, 1, &values[PROP_ID_USER_ID] },
298
static SmProp *propp[] = {
299
&properties[0], &properties[1], &properties[2], &properties[3],
303
struct passwd *pw = getpwuid(getuid());
304
values[PROP_ID_USER_ID].length = strlen(pw->pw_name);
305
values[PROP_ID_USER_ID].value = pw->pw_name;
307
SmcSetProperties(smcCnx, ARRAYSIZE(propp), (SmProp**)propp);
312
******************************************************************************
313
* BEGIN libICE stuff.
318
* A note on source reference counting:
320
* There are two entities that maintain references on ICEWatchCtx's GSource.
321
* - GLib's GMainContext, and
322
* - libICE's IceConn (via ICEWatch's watchData).
324
* A source's initial reference created in ICEWatch may be considered as
325
* transferred to libICE until ICEWatch is again called upon connection close.
326
* The second reference comes from attaching the source to the GLib main loop.
331
******************************************************************************
332
* ICEIOErrorHandler -- */ /**
334
* Handler for libICE I/O errors.
336
* Does nothing but replaces libICE's default handler which would've caused us
339
* @param[in] iceCnx Opaque ICE connection descriptor.
341
******************************************************************************
345
ICEIOErrorHandler(IceConn iceCnx)
347
g_message("%s: %s\n", __func__, strerror(errno));
352
******************************************************************************
353
* ICEDispatch -- */ /**
355
* GSource dispatch routine. Calls IceProcessMessages on the ICE connection.
357
* @param[in] chn GIOChannel event source
358
* @param[in] cond condition satisfied (ignored)
359
* @param[in] cbData (ICEWatchCtx*) Channel context.
361
* @retval TRUE Underlying iceCnx is still valid, so do not kill this source.
362
* @retval FALSE Underlying iceCnx was destroyed, so remove this source.
364
******************************************************************************
368
ICEDispatch(GIOChannel *chn,
372
IceProcessMessagesStatus status;
373
ICEWatchCtx *watchCtx = cbData;
377
* We ignore the error conditions here and let IceProcessMessages return
378
* an IceProcessMessagesIOError, resulting in a single, shared error code
382
status = IceProcessMessages(watchCtx->iceCnx, NULL, NULL);
384
case IceProcessMessagesSuccess:
386
case IceProcessMessagesIOError:
387
IceCloseConnection(watchCtx->iceCnx); // Let ICEWatch kill the source.
389
case IceProcessMessagesConnectionClosed:
391
* iceCnx was invalidated, so we won't see another call to ICEWatch,
392
* so we'll return FALSE and let GLib destroy the source for us.
394
watchCtx->iceCnx = NULL;
395
g_source_unref(watchCtx->iceSource);
404
******************************************************************************
407
* libICE connection watching callback.
409
* Creates or removes GLib event sources upon ICE connection creation/
413
* "If opening is True the client should set the *watch_data pointer to
414
* any data it may need to save until the connection is closed and the
415
* watch procedure is invoked again with opening set to False."
417
* @param[in] iceCnx Opaque ICE connection descriptor.
418
* @parma[in] cbData (ToolsPluginData*) plugin data
419
* @param[in] opening True if creating a connection, False if closing.
420
* @param[in] watchData See above. New source will be stored here.
422
******************************************************************************
426
ICEWatch(IceConn iceCnx,
429
IcePointer *watchData)
431
ToolsPluginData *pdata = cbData;
434
ctx = g_hash_table_lookup(pdata->_private, DE_PRIVATE_CTX);
438
GIOChannel *iceChannel;
440
ICEWatchCtx *watchCtx;
441
GError *error = NULL;
443
iceChannel = g_io_channel_unix_new(IceConnectionNumber(iceCnx));
444
if (g_io_channel_set_encoding(iceChannel, NULL, &error) != G_IO_STATUS_NORMAL) {
445
g_warning("%s: g_io_channel_set_encoding: %s\n", __func__, error->message);
446
g_clear_error(&error);
447
g_io_channel_unref(iceChannel);
450
g_io_channel_set_buffered(iceChannel, FALSE);
452
iceSource = g_io_create_watch(iceChannel, G_IO_IN|G_IO_HUP|G_IO_ERR);
453
g_io_channel_unref(iceChannel); // Ownership transferred to iceSource.
455
watchCtx = g_new(ICEWatchCtx, 1);
456
watchCtx->iceSource = iceSource;
457
watchCtx->iceCnx = iceCnx;
458
*watchData = watchCtx;
460
VMTOOLSAPP_ATTACH_SOURCE(ctx, iceSource, &ICEDispatch, watchCtx, NULL);
462
ICEWatchCtx *watchCtx = *watchData;
464
watchCtx->iceCnx = NULL;
465
if (watchCtx->iceSource) {
466
g_source_destroy(watchCtx->iceSource);
467
g_source_unref(watchCtx->iceSource);
478
******************************************************************************
483
******************************************************************************
489
******************************************************************************
492
* Callback for a XSM "Die" event.
494
* Instructs the main loop to quit. We "acknowledge" the callback by closing
495
* the connection in our shutdown handler.
497
* @param[in] smcCnx Opaque XSM connection object.
498
* @param[in] cbData (ToolsPluginData*) Plugin data.
500
******************************************************************************
504
SMDieCb(SmcConn smcCnx,
507
ToolsPluginData *pdata = cbData;
512
ctx = g_hash_table_lookup(pdata->_private, DE_PRIVATE_CTX);
515
g_message("Session manager says our time is up. Exiting.\n");
516
g_signal_emit_by_name(ctx->serviceObj, TOOLS_CORE_SIG_XSM_DIE, ctx);
517
g_main_loop_quit(ctx->mainLoop);
522
******************************************************************************
523
* SMSaveYourselfCb -- */ /**
525
* Callback for XSM "SaveYourself" event.
527
* This event is sent to all XSM clients either to checkpoint a session
528
* or in advance of a (cancellable) session shutdown event. If we needed
529
* time to persist state, now would be the time to do it. Since we don't,
530
* however, this is nearly a no-op -- we only acknowledge the manager.
532
* @param[in] smcCnx Opaque XSM connection object.
533
* @param[in] cbData (ToolsPluginData*) Plugin data.
534
* @param[in] saveType Refer to SMlib.xml.
535
* @param[in] shutdown Checkpoint or shutdown?
536
* @param[in] interactStyle May interact with user?
537
* @param[in] fast Shutdown as quickly as possible.
539
* @todo Consider whether it'd make sense to unregister capabilities and pause
540
* other plugins if shutdown == True until either we receive a 'die' or
541
* 'shutdown cancelled' event.
543
******************************************************************************
547
SMSaveYourselfCb(SmcConn smcCnx,
554
ToolsPluginData *pdata = cbData;
559
ctx = g_hash_table_lookup(pdata->_private, DE_PRIVATE_CTX);
562
g_signal_emit_by_name(ctx->serviceObj, TOOLS_CORE_SIG_XSM_SAVE_YOURSELF,
563
ctx, saveType, shutdown, interactStyle, fast);
564
SmcSaveYourselfDone(smcCnx, True);
569
******************************************************************************
570
* SMSaveCompleteCb -- */ /**
572
* Callback for XSM "SaveComplete" event.
574
* State has been checkpointed. Application may resume normal operations.
577
* @param[in] smcCnx Opaque XSM connection object.
578
* @param[in] cbData (ToolsPluginData*) Plugin data.
580
******************************************************************************
584
SMSaveCompleteCb(SmcConn smcCnx,
587
ToolsPluginData *pdata = cbData;
592
ctx = g_hash_table_lookup(pdata->_private, DE_PRIVATE_CTX);
595
g_signal_emit_by_name(ctx->serviceObj, TOOLS_CORE_SIG_XSM_SAVE_COMPLETE, ctx);
600
******************************************************************************
601
* SMShutdownCancelledCb -- */ /**
603
* Callback for XSM "ShutdownCancelled" event.
605
* User cancelled shutdown. May resume normal operations. Again, total no-op.
607
* @param[in] smcCnx Opaque XSM connection object.
608
* @param[in] cbData (ToolsPluginData*) Plugin data.
610
******************************************************************************
614
SMShutdownCancelledCb(SmcConn smcCnx,
617
ToolsPluginData *pdata = cbData;
622
ctx = g_hash_table_lookup(pdata->_private, DE_PRIVATE_CTX);
625
g_signal_emit_by_name(ctx->serviceObj, TOOLS_CORE_SIG_XSM_SHUTDOWN_CANCELLED,
632
******************************************************************************