1 /*
2  * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
3  * Copyright (C) 2007-2008 Nokia Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include "config.h"
21 
22 #include "telepathy-glib/proxy-subclass.h"
23 #include "telepathy-glib/proxy-internal.h"
24 
25 #define DEBUG_FLAG TP_DEBUG_PROXY
26 #include "telepathy-glib/debug-internal.h"
27 #include <telepathy-glib/util.h>
28 
29 #if 0
30 #define MORE_DEBUG DEBUG
31 #else
32 #define MORE_DEBUG(...) G_STMT_START {} G_STMT_END
33 #endif
34 
35 /**
36  * TpProxyPendingCall:
37  *
38  * Opaque structure representing a pending D-Bus call.
39  *
40  * Since: 0.7.1
41  */
42 
43 struct _TpProxyPendingCall {
44     /* This structure's "reference count" is implicit:
45      * - 1 if D-Bus has us (from creation until _completed)
46      * - 1 if results have come in but we haven't run the callback yet
47      *   (idle_source is nonzero)
48      *
49      * In normal use, its life cycle should go like this:
50      * - Created by tp_proxy_pending_call_v0_new
51      * - Given to dbus-glib by generated code (actual call starts here)
52      * - tp_proxy_pending_call_v0_take_pending_call
53      * - (Phase 1)
54      * - tp_proxy_pending_call_v0_take_results
55      * - Idle handler queued
56      * - (Phase 2)
57      * - tp_proxy_pending_call_v0_completed
58      * - (Phase 3)
59      * - tp_proxy_pending_call_idle_invoke
60      * - tp_proxy_pending_call_free
61      *
62      * although we can't guarantee that idle_invoke won't go off before
63      * completed does, if the dbus-glib implementation changes.
64      *
65      * Exceptional conditions that can occur:
66      * - Weak object dies
67      *   - Reference cleared, otherwise equivalent to explicit cancellation
68      * - Explicitly cancelled
69      *   - All phases: callback invoked if cancel_must_raise, otherwise
70      *     not
71      * - DBusGProxy destroy signal (or _completed before _take_results)
72      *   - Phase 1: error callback queued
73      *   - Phase 2: ignored, we use the results we've already got
74      *   - Phase 3: ignored, we use the results we've already got
75      */
76 
77     /* Always non-NULL */
78     TpProxy *proxy;
79 
80     /* Set to NULL after it's been invoked once, or if cancellation means
81      * it should never be called. Supplied by the generated code */
82     TpProxyInvokeFunc invoke_callback;
83 
84     /* arguments for invoke_callback supplied by _take_results, by
85      * cancellation or by the destroy signal */
86     GError *error /* implicitly initialized */;
87     GValueArray *args;
88 
89     /* user-supplied arguments for invoke_callback */
90     GCallback callback;
91     gpointer user_data;
92     GDestroyNotify destroy;
93     GObject *weak_object;
94 
95     /* Non-NULL until either _completed or destroy, whichever comes first */
96     DBusGProxy *iface_proxy;
97     DBusGProxyCall *pending_call;
98 
99     /* Nonzero if _idle_invoke has been queued (even if it has already
100      * happened), i.e. if results have been taken or the DBusGProxy
101      * was destroyed */
102     guint idle_source;
103 
104     /* If TRUE, invoke the callback even on cancellation */
105     unsigned cancel_must_raise:1;
106 
107     /* If TRUE, the idle_invoke callback has either run or been cancelled */
108     unsigned idle_completed:1;
109     /* If TRUE, dbus-glib no longer holds a reference to us */
110     unsigned dbus_completed:1;
111 
112     /* Marker to indicate that this is, in fact, a valid TpProxyPendingCall */
113     gconstpointer priv;
114 };
115 
116 static const gchar * const pending_call_magic = "TpProxyPendingCall";
117 
118 static void
tp_proxy_pending_call_lost_weak_ref(gpointer data,GObject * dead)119 tp_proxy_pending_call_lost_weak_ref (gpointer data,
120                                      GObject *dead)
121 {
122   TpProxyPendingCall *pc = data;
123 
124   DEBUG ("%p lost weak ref to %p", pc, dead);
125 
126   g_assert (pc->priv == pending_call_magic);
127   g_assert (dead == pc->weak_object);
128 
129   pc->weak_object = NULL;
130 
131   if (!pc->idle_completed)
132     tp_proxy_pending_call_cancel (pc);
133 }
134 
135 static gboolean
tp_proxy_pending_call_idle_invoke(gpointer p)136 tp_proxy_pending_call_idle_invoke (gpointer p)
137 {
138   TpProxyPendingCall *pc = p;
139   TpProxyInvokeFunc invoke = pc->invoke_callback;
140 
141   MORE_DEBUG ("%p", pc);
142 
143   if (invoke == NULL)
144     {
145       /* either already invoked (bug?), or cancelled */
146       return FALSE;
147     }
148 
149   MORE_DEBUG ("%p: invoking user callback", pc);
150 
151   g_assert (pc->proxy != NULL);
152   g_assert (pc->error == NULL || pc->args == NULL);
153   g_assert (!pc->idle_completed);
154 
155   pc->invoke_callback = NULL;
156   invoke (pc->proxy, pc->error, pc->args, pc->callback,
157       pc->user_data, pc->weak_object);
158   pc->error = NULL;
159   pc->args = NULL;
160 
161   /* don't clear pc->idle_source here! tp_proxy_pending_call_v0_completed
162    * compares it to 0 to determine whether to free the object */
163 
164   return FALSE;
165 }
166 
167 static void _tp_proxy_pending_call_idle_completed (gpointer p);
168 
169 static void
_tp_proxy_pending_call_dgproxy_destroy(DBusGProxy * iface_proxy,TpProxyPendingCall * pc)170 _tp_proxy_pending_call_dgproxy_destroy (DBusGProxy *iface_proxy,
171                                        TpProxyPendingCall *pc)
172 {
173   g_assert (iface_proxy != NULL);
174   g_assert (pc != NULL);
175   g_assert (pc->iface_proxy == iface_proxy);
176   g_assert (pc->proxy != NULL);
177 
178   DEBUG ("%p: DBusGProxy %p invalidated", pc, iface_proxy);
179 
180   if (pc->idle_source == 0)
181     {
182       /* we haven't already received and queued a reply, so synthesize
183        * one */
184       g_assert (pc->args == NULL);
185       g_assert (pc->error == NULL);
186 
187       pc->error = g_error_new_literal (TP_DBUS_ERRORS,
188           TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)");
189 
190       pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
191           tp_proxy_pending_call_idle_invoke, pc,
192           _tp_proxy_pending_call_idle_completed);
193     }
194 
195   g_signal_handlers_disconnect_by_func (pc->iface_proxy,
196       _tp_proxy_pending_call_dgproxy_destroy, pc);
197   g_object_unref (pc->iface_proxy);
198   pc->iface_proxy = NULL;
199 }
200 
201 /**
202  * tp_proxy_pending_call_v0_new:
203  * @self: a proxy
204  * @iface: a quark whose string value is the D-Bus interface
205  * @member: the name of the method being called
206  * @iface_proxy: the interface-specific #DBusGProxy for @iface
207  * @invoke_callback: an implementation of #TpProxyInvokeFunc which will
208  *  invoke @callback with appropriate arguments
209  * @callback: a callback to be called when the call completes
210  * @user_data: user-supplied data for the callback
211  * @destroy: user-supplied destructor for the data
212  * @weak_object: if not %NULL, a #GObject which will be weakly referenced by
213  *   the signal connection - if it is destroyed, the pending call will
214  *   automatically be cancelled
215  * @cancel_must_raise: if %TRUE, the @invoke_callback will be run with
216  *  error %TP_DBUS_ERROR_CANCELLED if the call is cancelled by a call to
217  *  tp_proxy_pending_call_cancel() or by destruction of the @weak_object;
218  *  if %FALSE, the @invoke_callback will not be run at all in these cases
219  *
220  * Allocate a new pending call structure. After calling this function, the
221  * caller must start an asynchronous D-Bus call and give the resulting
222  * DBusGProxyCall to the pending call object using
223  * tp_proxy_pending_call_v0_take_pending_call().
224  *
225  * If dbus-glib gets a reply to the call before it's cancelled, the caller
226  * must arrange for tp_proxy_pending_call_v0_take_results() to be called
227  * with the results (the intention is for this to be done immediately
228  * after dbus_g_proxy_end_call in the callback supplied to dbus-glib).
229  *
230  * When dbus-glib discards its reference to the user_data supplied in the
231  * asynchronous D-Bus call (i.e. after the call is cancelled or a reply
232  * arrives), tp_proxy_pending_call_v0_completed must be called (the intention
233  * is for the #TpProxyPendingCall to be the @user_data in the async call,
234  * and for tp_proxy_pending_call_v0_completed to be the #GDestroyNotify
235  * passed to the same async call).
236  *
237  * This function is for use by #TpProxy subclass implementations only, and
238  * should usually only be called from code generated by
239  * tools/glib-client-gen.py.
240  *
241  * Returns: a new pending call structure
242  *
243  * Since: 0.7.1
244  */
245 TpProxyPendingCall *
tp_proxy_pending_call_v0_new(TpProxy * self,GQuark iface,const gchar * member,DBusGProxy * iface_proxy,TpProxyInvokeFunc invoke_callback,GCallback callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object,gboolean cancel_must_raise)246 tp_proxy_pending_call_v0_new (TpProxy *self,
247                               GQuark iface,
248                               const gchar *member,
249                               DBusGProxy *iface_proxy,
250                               TpProxyInvokeFunc invoke_callback,
251                               GCallback callback,
252                               gpointer user_data,
253                               GDestroyNotify destroy,
254                               GObject *weak_object,
255                               gboolean cancel_must_raise)
256 {
257   TpProxyPendingCall *pc;
258 
259   g_return_val_if_fail (invoke_callback != NULL, NULL);
260   g_return_val_if_fail ((gpointer) iface_proxy != (gpointer) self, NULL);
261 
262   pc = g_slice_new0 (TpProxyPendingCall);
263 
264   MORE_DEBUG ("(proxy=%p, if=%s, meth=%s, ic=%p; cb=%p, ud=%p, dn=%p, wo=%p)"
265       " -> %p", self, g_quark_to_string (iface), member, invoke_callback,
266       callback, user_data, destroy, weak_object, pc);
267 
268   pc->proxy = g_object_ref (self);
269   pc->invoke_callback = invoke_callback;
270   pc->callback = callback;
271   pc->user_data = user_data;
272   pc->destroy = destroy;
273   pc->weak_object = weak_object;
274   pc->iface_proxy = g_object_ref (iface_proxy);
275   pc->pending_call = NULL;
276   pc->priv = pending_call_magic;
277   pc->cancel_must_raise = cancel_must_raise;
278 
279   if (weak_object != NULL)
280     g_object_weak_ref (weak_object, tp_proxy_pending_call_lost_weak_ref, pc);
281 
282   g_signal_connect (iface_proxy, "destroy",
283       G_CALLBACK (_tp_proxy_pending_call_dgproxy_destroy), pc);
284 
285   return pc;
286 }
287 
288 /**
289  * tp_proxy_pending_call_cancel:
290  * @pc: a pending call
291  *
292  * Cancel the given pending call. After this function returns, you
293  * must not assume that the pending call remains valid, but you must not
294  * explicitly free it either.
295  *
296  * Since: 0.7.1
297  */
298 void
tp_proxy_pending_call_cancel(TpProxyPendingCall * pc)299 tp_proxy_pending_call_cancel (TpProxyPendingCall *pc)
300 {
301   DEBUG ("%p", pc);
302 
303   g_return_if_fail (pc->priv == pending_call_magic);
304   g_return_if_fail (pc->proxy != NULL);
305   /* If the callback has already run, it's too late to cancel */
306   g_return_if_fail (!pc->idle_completed);
307 
308   if (pc->cancel_must_raise)
309     {
310       if (pc->error != NULL)
311         g_error_free (pc->error);
312 
313       pc->error = g_error_new_literal (TP_DBUS_ERRORS,
314           TP_DBUS_ERROR_CANCELLED, "Re-entrant D-Bus call cancelled");
315 
316       if (pc->args != NULL)
317         {
318           tp_value_array_free (pc->args);
319           pc->args = NULL;
320         }
321     }
322   else
323     {
324       pc->invoke_callback = NULL;
325     }
326 
327   /* If we're calling the callback due to cancellation, we must free the
328    * pending call object afterwards. Otherwise, we must free the pending
329    * call object later anyway, in case this function was called due to
330    * weak refs (like fd.o #14750). */
331   if (pc->idle_source == 0)
332     {
333       pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
334           tp_proxy_pending_call_idle_invoke, pc,
335           _tp_proxy_pending_call_idle_completed);
336     }
337 
338   if (!pc->dbus_completed && pc->pending_call != NULL)
339     {
340       /* Implicitly asserts that iface_proxy is non-NULL */
341       DBusGProxy *iface_proxy = g_object_ref (pc->iface_proxy);
342 
343       dbus_g_proxy_cancel_call (iface_proxy, pc->pending_call);
344       g_object_unref (iface_proxy);
345     }
346 }
347 
348 static void
tp_proxy_pending_call_free(TpProxyPendingCall * pc)349 tp_proxy_pending_call_free (TpProxyPendingCall *pc)
350 {
351   MORE_DEBUG ("%p", pc);
352 
353   g_assert (pc->priv == pending_call_magic);
354 
355   if (pc->destroy != NULL)
356     pc->destroy (pc->user_data);
357 
358   pc->destroy = NULL;
359   pc->user_data = NULL;
360 
361   if (pc->error != NULL)
362     g_error_free (pc->error);
363 
364   pc->error = NULL;
365 
366   if (pc->args != NULL)
367     tp_value_array_free (pc->args);
368 
369   pc->args = NULL;
370 
371   if (pc->weak_object != NULL)
372     g_object_weak_unref (pc->weak_object,
373         tp_proxy_pending_call_lost_weak_ref, pc);
374 
375   if (pc->iface_proxy != NULL)
376     {
377       g_signal_handlers_disconnect_by_func (pc->iface_proxy,
378           _tp_proxy_pending_call_dgproxy_destroy, pc);
379       g_object_unref (pc->iface_proxy);
380       pc->iface_proxy = NULL;
381     }
382 
383   g_assert (pc->proxy != NULL);
384   g_object_unref (pc->proxy);
385   pc->proxy = NULL;
386 
387   g_slice_free (TpProxyPendingCall, pc);
388 }
389 
390 /**
391  * tp_proxy_pending_call_v0_completed:
392  * @p: a #TpProxyPendingCall allocated with tp_proxy_pending_call_v0_new()
393  *
394  * Indicate that dbus-glib has finished with this pending call, and therefore
395  * either tp_proxy_pending_call_v0_take_results() has already been called,
396  * or it will never be called. See tp_proxy_pending_call_v0_new().
397  *
398  * The signature is chosen to match #GDestroyNotify.
399  *
400  * This function is for use by #TpProxy subclass implementations only, and
401  * should usually only be called from code generated by
402  * tools/glib-client-gen.py.
403  *
404  * Since: 0.7.1
405  */
406 void
tp_proxy_pending_call_v0_completed(gpointer p)407 tp_proxy_pending_call_v0_completed (gpointer p)
408 {
409   TpProxyPendingCall *pc = p;
410 
411   MORE_DEBUG ("%p", pc);
412 
413   g_return_if_fail (pc->priv == pending_call_magic);
414   g_return_if_fail (!pc->dbus_completed);
415   g_return_if_fail (pc->proxy != NULL);
416 
417   /* dbus-glib frees its user_data *before* it emits destroy; if we
418    * haven't yet queued the callback, assume that's what's going on. */
419   if (pc->idle_source == 0 && pc->iface_proxy != NULL)
420     {
421       MORE_DEBUG ("Looks like this pending call hasn't finished, assuming "
422           "the DBusGProxy is about to die");
423       /* this causes the pending call to be freed */
424       _tp_proxy_pending_call_dgproxy_destroy (pc->iface_proxy, pc);
425 
426       g_assert (pc->iface_proxy == NULL);
427     }
428 
429   pc->dbus_completed = TRUE;
430 
431   /* If the idle callback has been run already, we can go away */
432   if (pc->idle_completed)
433     tp_proxy_pending_call_free (pc);
434 }
435 
436 /**
437  * tp_proxy_pending_call_v0_take_pending_call:
438  * @pc: A pending call on which this function has not yet been called
439  * @pending_call: The underlying dbus-glib pending call
440  *
441  * Set the underlying pending call to be used by this object.
442  * See also tp_proxy_pending_call_v0_new().
443  *
444  * This function is for use by #TpProxy subclass implementations only, and
445  * should usually only be called from code generated by
446  * tools/glib-client-gen.py.
447  *
448  * Since: 0.7.1
449  */
450 void
tp_proxy_pending_call_v0_take_pending_call(TpProxyPendingCall * pc,DBusGProxyCall * pending_call)451 tp_proxy_pending_call_v0_take_pending_call (TpProxyPendingCall *pc,
452                                             DBusGProxyCall *pending_call)
453 {
454   g_return_if_fail (pc->priv == pending_call_magic);
455   g_return_if_fail (pc->pending_call == NULL);
456   g_return_if_fail (pc->proxy != NULL);
457 
458   pc->pending_call = pending_call;
459 }
460 
461 static void
_tp_proxy_pending_call_idle_completed(gpointer p)462 _tp_proxy_pending_call_idle_completed (gpointer p)
463 {
464   TpProxyPendingCall *pc = p;
465 
466   MORE_DEBUG ("%p", pc);
467 
468   pc->idle_completed = TRUE;
469 
470   if (pc->dbus_completed)
471     tp_proxy_pending_call_free (pc);
472 }
473 
474 /**
475  * tp_proxy_pending_call_v0_take_results:
476  * @pc: A pending call on which this function has not yet been called
477  * @error: %NULL if the call was successful, or an error (whose ownership
478  *  is taken over by the pending call object). Because of dbus-glib
479  *  idiosyncrasies, this must be the error produced by dbus-glib, not a copy.
480  * @args: %NULL if the call failed or had no "out" arguments, or an array
481  *  of "out" arguments (whose ownership is taken over by the pending call
482  *  object)
483  *
484  * Set the "out" arguments (return values) from this pending call.
485  * See also tp_proxy_pending_call_v0_new().
486  *
487  * This function is for use by #TpProxy subclass implementations only, and
488  * should usually only be called from code generated by
489  * tools/glib-client-gen.py.
490  *
491  * Since: 0.7.1
492  */
493 void
tp_proxy_pending_call_v0_take_results(TpProxyPendingCall * pc,GError * error,GValueArray * args)494 tp_proxy_pending_call_v0_take_results (TpProxyPendingCall *pc,
495                                        GError *error,
496                                        GValueArray *args)
497 {
498   g_return_if_fail (pc->proxy != NULL);
499   g_return_if_fail (pc->priv == pending_call_magic);
500   g_return_if_fail (pc->args == NULL);
501   g_return_if_fail (pc->error == NULL);
502   g_return_if_fail (pc->idle_source == 0);
503   g_return_if_fail (error == NULL || args == NULL);
504 
505   MORE_DEBUG ("%p (error: %s)", pc,
506       error == NULL ? "(none)" : error->message);
507 
508   pc->args = args;
509   pc->error = _tp_proxy_take_and_remap_error (pc->proxy, error);
510 
511   /* queue up the actual callback to run after we go back to the event loop */
512   pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
513       tp_proxy_pending_call_idle_invoke, pc,
514       _tp_proxy_pending_call_idle_completed);
515 }
516