Coverage Report

Created: 2025-07-01 07:09

/src/glib/gio/gdbuserror.c
Line
Count
Source (jump to first uncovered line)
1
/* GDBus - GLib D-Bus Library
2
 *
3
 * Copyright (C) 2008-2010 Red Hat, Inc.
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
16
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17
 *
18
 * Author: David Zeuthen <davidz@redhat.com>
19
 */
20
21
#include "config.h"
22
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include "gdbuserror.h"
27
#include "gioenums.h"
28
#include "gioenumtypes.h"
29
#include "gioerror.h"
30
#include "gdbusprivate.h"
31
32
#include "glibintl.h"
33
34
/**
35
 * SECTION:gdbuserror
36
 * @title: GDBusError
37
 * @short_description: Mapping D-Bus errors to and from GError
38
 * @include: gio/gio.h
39
 *
40
 * All facilities that return errors from remote methods (such as
41
 * g_dbus_connection_call_sync()) use #GError to represent both D-Bus
42
 * errors (e.g. errors returned from the other peer) and locally
43
 * in-process generated errors.
44
 *
45
 * To check if a returned #GError is an error from a remote peer, use
46
 * g_dbus_error_is_remote_error(). To get the actual D-Bus error name,
47
 * use g_dbus_error_get_remote_error(). Before presenting an error,
48
 * always use g_dbus_error_strip_remote_error().
49
 *
50
 * In addition, facilities used to return errors to a remote peer also
51
 * use #GError. See g_dbus_method_invocation_return_error() for
52
 * discussion about how the D-Bus error name is set.
53
 *
54
 * Applications can associate a #GError error domain with a set of D-Bus errors in order to
55
 * automatically map from D-Bus errors to #GError and back. This
56
 * is typically done in the function returning the #GQuark for the
57
 * error domain:
58
 * |[<!-- language="C" -->
59
 * // foo-bar-error.h:
60
 *
61
 * #define FOO_BAR_ERROR (foo_bar_error_quark ())
62
 * GQuark foo_bar_error_quark (void);
63
 *
64
 * typedef enum
65
 * {
66
 *   FOO_BAR_ERROR_FAILED,
67
 *   FOO_BAR_ERROR_ANOTHER_ERROR,
68
 *   FOO_BAR_ERROR_SOME_THIRD_ERROR,
69
 *   FOO_BAR_N_ERRORS / *< skip >* /
70
 * } FooBarError;
71
 *
72
 * // foo-bar-error.c:
73
 *
74
 * static const GDBusErrorEntry foo_bar_error_entries[] =
75
 * {
76
 *   {FOO_BAR_ERROR_FAILED,           "org.project.Foo.Bar.Error.Failed"},
77
 *   {FOO_BAR_ERROR_ANOTHER_ERROR,    "org.project.Foo.Bar.Error.AnotherError"},
78
 *   {FOO_BAR_ERROR_SOME_THIRD_ERROR, "org.project.Foo.Bar.Error.SomeThirdError"},
79
 * };
80
 *
81
 * // Ensure that every error code has an associated D-Bus error name
82
 * G_STATIC_ASSERT (G_N_ELEMENTS (foo_bar_error_entries) == FOO_BAR_N_ERRORS);
83
 *
84
 * GQuark
85
 * foo_bar_error_quark (void)
86
 * {
87
 *   static gsize quark = 0;
88
 *   g_dbus_error_register_error_domain ("foo-bar-error-quark",
89
 *                                       &quark,
90
 *                                       foo_bar_error_entries,
91
 *                                       G_N_ELEMENTS (foo_bar_error_entries));
92
 *   return (GQuark) quark;
93
 * }
94
 * ]|
95
 * With this setup, a D-Bus peer can transparently pass e.g. %FOO_BAR_ERROR_ANOTHER_ERROR and
96
 * other peers will see the D-Bus error name org.project.Foo.Bar.Error.AnotherError.
97
 *
98
 * If the other peer is using GDBus, and has registered the association with
99
 * g_dbus_error_register_error_domain() in advance (e.g. by invoking the %FOO_BAR_ERROR quark
100
 * generation itself in the previous example) the peer will see also %FOO_BAR_ERROR_ANOTHER_ERROR instead
101
 * of %G_IO_ERROR_DBUS_ERROR. Note that GDBus clients can still recover
102
 * org.project.Foo.Bar.Error.AnotherError using g_dbus_error_get_remote_error().
103
 *
104
 * Note that the %G_DBUS_ERROR error domain is intended only
105
 * for returning errors from a remote message bus process. Errors
106
 * generated locally in-process by e.g. #GDBusConnection should use the
107
 * %G_IO_ERROR domain.
108
 */
109
110
static const GDBusErrorEntry g_dbus_error_entries[] =
111
{
112
  {G_DBUS_ERROR_FAILED,                           "org.freedesktop.DBus.Error.Failed"},
113
  {G_DBUS_ERROR_NO_MEMORY,                        "org.freedesktop.DBus.Error.NoMemory"},
114
  {G_DBUS_ERROR_SERVICE_UNKNOWN,                  "org.freedesktop.DBus.Error.ServiceUnknown"},
115
  {G_DBUS_ERROR_NAME_HAS_NO_OWNER,                "org.freedesktop.DBus.Error.NameHasNoOwner"},
116
  {G_DBUS_ERROR_NO_REPLY,                         "org.freedesktop.DBus.Error.NoReply"},
117
  {G_DBUS_ERROR_IO_ERROR,                         "org.freedesktop.DBus.Error.IOError"},
118
  {G_DBUS_ERROR_BAD_ADDRESS,                      "org.freedesktop.DBus.Error.BadAddress"},
119
  {G_DBUS_ERROR_NOT_SUPPORTED,                    "org.freedesktop.DBus.Error.NotSupported"},
120
  {G_DBUS_ERROR_LIMITS_EXCEEDED,                  "org.freedesktop.DBus.Error.LimitsExceeded"},
121
  {G_DBUS_ERROR_ACCESS_DENIED,                    "org.freedesktop.DBus.Error.AccessDenied"},
122
  {G_DBUS_ERROR_AUTH_FAILED,                      "org.freedesktop.DBus.Error.AuthFailed"},
123
  {G_DBUS_ERROR_NO_SERVER,                        "org.freedesktop.DBus.Error.NoServer"},
124
  {G_DBUS_ERROR_TIMEOUT,                          "org.freedesktop.DBus.Error.Timeout"},
125
  {G_DBUS_ERROR_NO_NETWORK,                       "org.freedesktop.DBus.Error.NoNetwork"},
126
  {G_DBUS_ERROR_ADDRESS_IN_USE,                   "org.freedesktop.DBus.Error.AddressInUse"},
127
  {G_DBUS_ERROR_DISCONNECTED,                     "org.freedesktop.DBus.Error.Disconnected"},
128
  {G_DBUS_ERROR_INVALID_ARGS,                     "org.freedesktop.DBus.Error.InvalidArgs"},
129
  {G_DBUS_ERROR_FILE_NOT_FOUND,                   "org.freedesktop.DBus.Error.FileNotFound"},
130
  {G_DBUS_ERROR_FILE_EXISTS,                      "org.freedesktop.DBus.Error.FileExists"},
131
  {G_DBUS_ERROR_UNKNOWN_METHOD,                   "org.freedesktop.DBus.Error.UnknownMethod"},
132
  {G_DBUS_ERROR_TIMED_OUT,                        "org.freedesktop.DBus.Error.TimedOut"},
133
  {G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,             "org.freedesktop.DBus.Error.MatchRuleNotFound"},
134
  {G_DBUS_ERROR_MATCH_RULE_INVALID,               "org.freedesktop.DBus.Error.MatchRuleInvalid"},
135
  {G_DBUS_ERROR_SPAWN_EXEC_FAILED,                "org.freedesktop.DBus.Error.Spawn.ExecFailed"},
136
  {G_DBUS_ERROR_SPAWN_FORK_FAILED,                "org.freedesktop.DBus.Error.Spawn.ForkFailed"},
137
  {G_DBUS_ERROR_SPAWN_CHILD_EXITED,               "org.freedesktop.DBus.Error.Spawn.ChildExited"},
138
  {G_DBUS_ERROR_SPAWN_CHILD_SIGNALED,             "org.freedesktop.DBus.Error.Spawn.ChildSignaled"},
139
  {G_DBUS_ERROR_SPAWN_FAILED,                     "org.freedesktop.DBus.Error.Spawn.Failed"},
140
  {G_DBUS_ERROR_SPAWN_SETUP_FAILED,               "org.freedesktop.DBus.Error.Spawn.FailedToSetup"},
141
  {G_DBUS_ERROR_SPAWN_CONFIG_INVALID,             "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"},
142
  {G_DBUS_ERROR_SPAWN_SERVICE_INVALID,            "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"},
143
  {G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,          "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"},
144
  {G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,        "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"},
145
  {G_DBUS_ERROR_SPAWN_FILE_INVALID,               "org.freedesktop.DBus.Error.Spawn.FileInvalid"},
146
  {G_DBUS_ERROR_SPAWN_NO_MEMORY,                  "org.freedesktop.DBus.Error.Spawn.NoMemory"},
147
  {G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,          "org.freedesktop.DBus.Error.UnixProcessIdUnknown"},
148
  {G_DBUS_ERROR_INVALID_SIGNATURE,                "org.freedesktop.DBus.Error.InvalidSignature"},
149
  {G_DBUS_ERROR_INVALID_FILE_CONTENT,             "org.freedesktop.DBus.Error.InvalidFileContent"},
150
  {G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"},
151
  {G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN,           "org.freedesktop.DBus.Error.AdtAuditDataUnknown"},
152
  {G_DBUS_ERROR_OBJECT_PATH_IN_USE,               "org.freedesktop.DBus.Error.ObjectPathInUse"},
153
  {G_DBUS_ERROR_UNKNOWN_OBJECT,                   "org.freedesktop.DBus.Error.UnknownObject"},
154
  {G_DBUS_ERROR_UNKNOWN_INTERFACE,                "org.freedesktop.DBus.Error.UnknownInterface"},
155
  {G_DBUS_ERROR_UNKNOWN_PROPERTY,                 "org.freedesktop.DBus.Error.UnknownProperty"},
156
  {G_DBUS_ERROR_PROPERTY_READ_ONLY,               "org.freedesktop.DBus.Error.PropertyReadOnly"},
157
};
158
159
GQuark
160
g_dbus_error_quark (void)
161
0
{
162
0
  G_STATIC_ASSERT (G_N_ELEMENTS (g_dbus_error_entries) - 1 == G_DBUS_ERROR_PROPERTY_READ_ONLY);
163
0
  static gsize quark = 0;
164
0
  g_dbus_error_register_error_domain ("g-dbus-error-quark",
165
0
                                      &quark,
166
0
                                      g_dbus_error_entries,
167
0
                                      G_N_ELEMENTS (g_dbus_error_entries));
168
0
  return (GQuark) quark;
169
0
}
170
171
/**
172
 * g_dbus_error_register_error_domain:
173
 * @error_domain_quark_name: The error domain name.
174
 * @quark_volatile: A pointer where to store the #GQuark.
175
 * @entries: (array length=num_entries): A pointer to @num_entries #GDBusErrorEntry struct items.
176
 * @num_entries: Number of items to register.
177
 *
178
 * Helper function for associating a #GError error domain with D-Bus error names.
179
 *
180
 * While @quark_volatile has a `volatile` qualifier, this is a historical
181
 * artifact and the argument passed to it should not be `volatile`.
182
 *
183
 * Since: 2.26
184
 */
185
void
186
g_dbus_error_register_error_domain (const gchar           *error_domain_quark_name,
187
                                    volatile gsize        *quark_volatile,
188
                                    const GDBusErrorEntry *entries,
189
                                    guint                  num_entries)
190
0
{
191
0
  gsize *quark;
192
193
0
  g_return_if_fail (error_domain_quark_name != NULL);
194
0
  g_return_if_fail (quark_volatile != NULL);
195
0
  g_return_if_fail (entries != NULL);
196
0
  g_return_if_fail (num_entries > 0);
197
198
  /* Drop the volatile qualifier, which should never have been on the argument
199
   * in the first place. */
200
0
  quark = (gsize *) quark_volatile;
201
202
0
  if (g_once_init_enter (quark))
203
0
    {
204
0
      guint n;
205
0
      GQuark new_quark;
206
207
0
      new_quark = g_quark_from_static_string (error_domain_quark_name);
208
209
0
      for (n = 0; n < num_entries; n++)
210
0
        {
211
0
          g_warn_if_fail (g_dbus_error_register_error (new_quark,
212
0
                                                       entries[n].error_code,
213
0
                                                       entries[n].dbus_error_name));
214
0
        }
215
0
      g_once_init_leave (quark, new_quark);
216
0
    }
217
0
}
218
219
static gboolean
220
_g_dbus_error_decode_gerror (const gchar *dbus_name,
221
                             GQuark      *out_error_domain,
222
                             gint        *out_error_code)
223
0
{
224
0
  gboolean ret;
225
0
  guint n;
226
0
  GString *s;
227
0
  gchar *domain_quark_string;
228
229
0
  ret = FALSE;
230
0
  s = NULL;
231
232
0
  if (g_str_has_prefix (dbus_name, "org.gtk.GDBus.UnmappedGError.Quark._"))
233
0
    {
234
0
      s = g_string_new (NULL);
235
236
0
      for (n = sizeof "org.gtk.GDBus.UnmappedGError.Quark._" - 1;
237
0
           dbus_name[n] != '.' && dbus_name[n] != '\0';
238
0
           n++)
239
0
        {
240
0
          if (g_ascii_isalnum (dbus_name[n]))
241
0
            {
242
0
              g_string_append_c (s, dbus_name[n]);
243
0
            }
244
0
          else if (dbus_name[n] == '_')
245
0
            {
246
0
              guint nibble_top;
247
0
              guint nibble_bottom;
248
249
0
              n++;
250
251
0
              nibble_top = dbus_name[n];
252
0
              if (nibble_top >= '0' && nibble_top <= '9')
253
0
                nibble_top -= '0';
254
0
              else if (nibble_top >= 'a' && nibble_top <= 'f')
255
0
                nibble_top -= ('a' - 10);
256
0
              else
257
0
                goto not_mapped;
258
259
0
              n++;
260
261
0
              nibble_bottom = dbus_name[n];
262
0
              if (nibble_bottom >= '0' && nibble_bottom <= '9')
263
0
                nibble_bottom -= '0';
264
0
              else if (nibble_bottom >= 'a' && nibble_bottom <= 'f')
265
0
                nibble_bottom -= ('a' - 10);
266
0
              else
267
0
                goto not_mapped;
268
269
0
              g_string_append_c (s, (nibble_top<<4) | nibble_bottom);
270
0
            }
271
0
          else
272
0
            {
273
0
              goto not_mapped;
274
0
            }
275
0
        }
276
277
0
      if (!g_str_has_prefix (dbus_name + n, ".Code"))
278
0
        goto not_mapped;
279
280
0
      domain_quark_string = g_string_free (s, FALSE);
281
0
      s = NULL;
282
283
0
      if (out_error_domain != NULL)
284
0
        *out_error_domain = g_quark_from_string (domain_quark_string);
285
0
      g_free (domain_quark_string);
286
287
0
      if (out_error_code != NULL)
288
0
        *out_error_code = atoi (dbus_name + n + sizeof ".Code" - 1);
289
290
0
      ret = TRUE;
291
0
    }
292
293
0
 not_mapped:
294
295
0
  if (s != NULL)
296
0
    g_string_free (s, TRUE);
297
298
0
  return ret;
299
0
}
300
301
/* ---------------------------------------------------------------------------------------------------- */
302
303
typedef struct
304
{
305
  GQuark error_domain;
306
  gint   error_code;
307
} QuarkCodePair;
308
309
static guint
310
quark_code_pair_hash_func (const QuarkCodePair *pair)
311
1.58k
{
312
1.58k
  gint val;
313
1.58k
  val = pair->error_domain + pair->error_code;
314
1.58k
  return g_int_hash (&val);
315
1.58k
}
316
317
static gboolean
318
quark_code_pair_equal_func (const QuarkCodePair *a,
319
                            const QuarkCodePair *b)
320
0
{
321
0
  return (a->error_domain == b->error_domain) && (a->error_code == b->error_code);
322
0
}
323
324
typedef struct
325
{
326
  QuarkCodePair pair;
327
  gchar *dbus_error_name;
328
} RegisteredError;
329
330
static void
331
registered_error_free (RegisteredError *re)
332
0
{
333
0
  g_free (re->dbus_error_name);
334
0
  g_free (re);
335
0
}
336
337
G_LOCK_DEFINE_STATIC (error_lock);
338
339
/* maps from QuarkCodePair* -> RegisteredError* */
340
static GHashTable *quark_code_pair_to_re = NULL;
341
342
/* maps from gchar* -> RegisteredError* */
343
static GHashTable *dbus_error_name_to_re = NULL;
344
345
/**
346
 * g_dbus_error_register_error:
347
 * @error_domain: A #GQuark for an error domain.
348
 * @error_code: An error code.
349
 * @dbus_error_name: A D-Bus error name.
350
 *
351
 * Creates an association to map between @dbus_error_name and
352
 * #GErrors specified by @error_domain and @error_code.
353
 *
354
 * This is typically done in the routine that returns the #GQuark for
355
 * an error domain.
356
 *
357
 * Returns: %TRUE if the association was created, %FALSE if it already
358
 * exists.
359
 *
360
 * Since: 2.26
361
 */
362
gboolean
363
g_dbus_error_register_error (GQuark       error_domain,
364
                             gint         error_code,
365
                             const gchar *dbus_error_name)
366
792
{
367
792
  gboolean ret;
368
792
  QuarkCodePair pair;
369
792
  RegisteredError *re;
370
371
792
  g_return_val_if_fail (dbus_error_name != NULL, FALSE);
372
373
792
  ret = FALSE;
374
375
792
  G_LOCK (error_lock);
376
377
792
  if (quark_code_pair_to_re == NULL)
378
36
    {
379
36
      g_assert (dbus_error_name_to_re == NULL); /* check invariant */
380
36
      quark_code_pair_to_re = g_hash_table_new ((GHashFunc) quark_code_pair_hash_func,
381
36
                                                (GEqualFunc) quark_code_pair_equal_func);
382
36
      dbus_error_name_to_re = g_hash_table_new_full (g_str_hash,
383
36
                                                     g_str_equal,
384
36
                                                     NULL,
385
36
                                                     (GDestroyNotify) registered_error_free);
386
36
    }
387
388
792
  if (g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name) != NULL)
389
0
    goto out;
390
391
792
  pair.error_domain = error_domain;
392
792
  pair.error_code = error_code;
393
394
792
  if (g_hash_table_lookup (quark_code_pair_to_re, &pair) != NULL)
395
0
    goto out;
396
397
792
  re = g_new0 (RegisteredError, 1);
398
792
  re->pair = pair;
399
792
  re->dbus_error_name = g_strdup (dbus_error_name);
400
401
792
  g_hash_table_insert (quark_code_pair_to_re, &(re->pair), re);
402
792
  g_hash_table_insert (dbus_error_name_to_re, re->dbus_error_name, re);
403
404
792
  ret = TRUE;
405
406
792
 out:
407
792
  G_UNLOCK (error_lock);
408
792
  return ret;
409
792
}
410
411
/**
412
 * g_dbus_error_unregister_error:
413
 * @error_domain: A #GQuark for an error domain.
414
 * @error_code: An error code.
415
 * @dbus_error_name: A D-Bus error name.
416
 *
417
 * Destroys an association previously set up with g_dbus_error_register_error().
418
 *
419
 * Returns: %TRUE if the association was destroyed, %FALSE if it wasn't found.
420
 *
421
 * Since: 2.26
422
 */
423
gboolean
424
g_dbus_error_unregister_error (GQuark       error_domain,
425
                               gint         error_code,
426
                               const gchar *dbus_error_name)
427
0
{
428
0
  gboolean ret;
429
0
  RegisteredError *re;
430
0
  guint hash_size;
431
432
0
  g_return_val_if_fail (dbus_error_name != NULL, FALSE);
433
434
0
  ret = FALSE;
435
436
0
  G_LOCK (error_lock);
437
438
0
  if (dbus_error_name_to_re == NULL)
439
0
    {
440
0
      g_assert (quark_code_pair_to_re == NULL); /* check invariant */
441
0
      goto out;
442
0
    }
443
444
0
  re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
445
0
  if (re == NULL)
446
0
    {
447
0
      QuarkCodePair pair;
448
0
      pair.error_domain = error_domain;
449
0
      pair.error_code = error_code;
450
0
      g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &pair) == NULL); /* check invariant */
451
0
      goto out;
452
0
    }
453
454
0
  ret = TRUE;
455
456
0
  g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &(re->pair)) == re); /* check invariant */
457
458
0
  g_warn_if_fail (g_hash_table_remove (quark_code_pair_to_re, &(re->pair)));
459
0
  g_warn_if_fail (g_hash_table_remove (dbus_error_name_to_re, re->dbus_error_name));
460
461
  /* destroy hashes if empty */
462
0
  hash_size = g_hash_table_size (dbus_error_name_to_re);
463
0
  if (hash_size == 0)
464
0
    {
465
0
      g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == 0); /* check invariant */
466
467
0
      g_hash_table_unref (dbus_error_name_to_re);
468
0
      dbus_error_name_to_re = NULL;
469
0
      g_hash_table_unref (quark_code_pair_to_re);
470
0
      quark_code_pair_to_re = NULL;
471
0
    }
472
0
  else
473
0
    {
474
0
      g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == hash_size); /* check invariant */
475
0
    }
476
477
0
 out:
478
0
  G_UNLOCK (error_lock);
479
0
  return ret;
480
0
}
481
482
/* ---------------------------------------------------------------------------------------------------- */
483
484
/**
485
 * g_dbus_error_is_remote_error:
486
 * @error: A #GError.
487
 *
488
 * Checks if @error represents an error received via D-Bus from a remote peer. If so,
489
 * use g_dbus_error_get_remote_error() to get the name of the error.
490
 *
491
 * Returns: %TRUE if @error represents an error from a remote peer,
492
 * %FALSE otherwise.
493
 *
494
 * Since: 2.26
495
 */
496
gboolean
497
g_dbus_error_is_remote_error (const GError *error)
498
0
{
499
0
  g_return_val_if_fail (error != NULL, FALSE);
500
0
  return g_str_has_prefix (error->message, "GDBus.Error:");
501
0
}
502
503
504
/**
505
 * g_dbus_error_get_remote_error:
506
 * @error: a #GError
507
 *
508
 * Gets the D-Bus error name used for @error, if any.
509
 *
510
 * This function is guaranteed to return a D-Bus error name for all
511
 * #GErrors returned from functions handling remote method calls
512
 * (e.g. g_dbus_connection_call_finish()) unless
513
 * g_dbus_error_strip_remote_error() has been used on @error.
514
 *
515
 * Returns: (nullable) (transfer full): an allocated string or %NULL if the
516
 *     D-Bus error name could not be found. Free with g_free().
517
 *
518
 * Since: 2.26
519
 */
520
gchar *
521
g_dbus_error_get_remote_error (const GError *error)
522
0
{
523
0
  RegisteredError *re;
524
0
  gchar *ret;
525
526
0
  g_return_val_if_fail (error != NULL, NULL);
527
528
  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
529
0
  _g_dbus_initialize ();
530
531
0
  ret = NULL;
532
533
0
  G_LOCK (error_lock);
534
535
0
  re = NULL;
536
0
  if (quark_code_pair_to_re != NULL)
537
0
    {
538
0
      QuarkCodePair pair;
539
0
      pair.error_domain = error->domain;
540
0
      pair.error_code = error->code;
541
0
      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
542
0
      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
543
0
    }
544
545
0
  if (re != NULL)
546
0
    {
547
0
      ret = g_strdup (re->dbus_error_name);
548
0
    }
549
0
  else
550
0
    {
551
0
      if (g_str_has_prefix (error->message, "GDBus.Error:"))
552
0
        {
553
0
          const gchar *begin;
554
0
          const gchar *end;
555
0
          begin = error->message + sizeof ("GDBus.Error:") -1;
556
0
          end = strstr (begin, ":");
557
0
          if (end != NULL && end[1] == ' ')
558
0
            {
559
0
              ret = g_strndup (begin, end - begin);
560
0
            }
561
0
        }
562
0
    }
563
564
0
  G_UNLOCK (error_lock);
565
566
0
  return ret;
567
0
}
568
569
/* ---------------------------------------------------------------------------------------------------- */
570
571
/**
572
 * g_dbus_error_new_for_dbus_error:
573
 * @dbus_error_name: D-Bus error name.
574
 * @dbus_error_message: D-Bus error message.
575
 *
576
 * Creates a #GError based on the contents of @dbus_error_name and
577
 * @dbus_error_message.
578
 *
579
 * Errors registered with g_dbus_error_register_error() will be looked
580
 * up using @dbus_error_name and if a match is found, the error domain
581
 * and code is used. Applications can use g_dbus_error_get_remote_error()
582
 * to recover @dbus_error_name.
583
 *
584
 * If a match against a registered error is not found and the D-Bus
585
 * error name is in a form as returned by g_dbus_error_encode_gerror()
586
 * the error domain and code encoded in the name is used to
587
 * create the #GError. Also, @dbus_error_name is added to the error message
588
 * such that it can be recovered with g_dbus_error_get_remote_error().
589
 *
590
 * Otherwise, a #GError with the error code %G_IO_ERROR_DBUS_ERROR
591
 * in the #G_IO_ERROR error domain is returned. Also, @dbus_error_name is
592
 * added to the error message such that it can be recovered with
593
 * g_dbus_error_get_remote_error().
594
 *
595
 * In all three cases, @dbus_error_name can always be recovered from the
596
 * returned #GError using the g_dbus_error_get_remote_error() function
597
 * (unless g_dbus_error_strip_remote_error() hasn't been used on the returned error).
598
 *
599
 * This function is typically only used in object mappings to prepare
600
 * #GError instances for applications. Regular applications should not use
601
 * it.
602
 *
603
 * Returns: (transfer full): An allocated #GError. Free with g_error_free().
604
 *
605
 * Since: 2.26
606
 */
607
GError *
608
g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name,
609
                                 const gchar *dbus_error_message)
610
0
{
611
0
  GError *error;
612
0
  RegisteredError *re;
613
614
0
  g_return_val_if_fail (dbus_error_name != NULL, NULL);
615
0
  g_return_val_if_fail (dbus_error_message != NULL, NULL);
616
617
  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
618
0
  _g_dbus_initialize ();
619
620
0
  G_LOCK (error_lock);
621
622
0
  re = NULL;
623
0
  if (dbus_error_name_to_re != NULL)
624
0
    {
625
0
      g_assert (quark_code_pair_to_re != NULL); /* check invariant */
626
0
      re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
627
0
    }
628
629
0
  if (re != NULL)
630
0
    {
631
0
      error = g_error_new (re->pair.error_domain,
632
0
                           re->pair.error_code,
633
0
                           "GDBus.Error:%s: %s",
634
0
                           dbus_error_name,
635
0
                           dbus_error_message);
636
0
    }
637
0
  else
638
0
    {
639
0
      GQuark error_domain = 0;
640
0
      gint error_code = 0;
641
642
0
      if (_g_dbus_error_decode_gerror (dbus_error_name,
643
0
                                       &error_domain,
644
0
                                       &error_code))
645
0
        {
646
0
          error = g_error_new (error_domain,
647
0
                               error_code,
648
0
                               "GDBus.Error:%s: %s",
649
0
                               dbus_error_name,
650
0
                               dbus_error_message);
651
0
        }
652
0
      else
653
0
        {
654
0
          error = g_error_new (G_IO_ERROR,
655
0
                               G_IO_ERROR_DBUS_ERROR,
656
0
                               "GDBus.Error:%s: %s",
657
0
                               dbus_error_name,
658
0
                               dbus_error_message);
659
0
        }
660
0
    }
661
662
0
  G_UNLOCK (error_lock);
663
0
  return error;
664
0
}
665
666
/**
667
 * g_dbus_error_set_dbus_error:
668
 * @error: A pointer to a #GError or %NULL.
669
 * @dbus_error_name: D-Bus error name.
670
 * @dbus_error_message: D-Bus error message.
671
 * @format: (nullable): printf()-style format to prepend to @dbus_error_message or %NULL.
672
 * @...: Arguments for @format.
673
 *
674
 * Does nothing if @error is %NULL. Otherwise sets *@error to
675
 * a new #GError created with g_dbus_error_new_for_dbus_error()
676
 * with @dbus_error_message prepend with @format (unless %NULL).
677
 *
678
 * Since: 2.26
679
 */
680
void
681
g_dbus_error_set_dbus_error (GError      **error,
682
                             const gchar  *dbus_error_name,
683
                             const gchar  *dbus_error_message,
684
                             const gchar  *format,
685
                             ...)
686
0
{
687
0
  g_return_if_fail (error == NULL || *error == NULL);
688
0
  g_return_if_fail (dbus_error_name != NULL);
689
0
  g_return_if_fail (dbus_error_message != NULL);
690
691
0
  if (error == NULL)
692
0
    return;
693
694
0
  if (format == NULL)
695
0
    {
696
0
      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
697
0
    }
698
0
  else
699
0
    {
700
0
      va_list var_args;
701
0
      va_start (var_args, format);
702
0
      g_dbus_error_set_dbus_error_valist (error,
703
0
                                          dbus_error_name,
704
0
                                          dbus_error_message,
705
0
                                          format,
706
0
                                          var_args);
707
0
      va_end (var_args);
708
0
    }
709
0
}
710
711
/**
712
 * g_dbus_error_set_dbus_error_valist:
713
 * @error: A pointer to a #GError or %NULL.
714
 * @dbus_error_name: D-Bus error name.
715
 * @dbus_error_message: D-Bus error message.
716
 * @format: (nullable): printf()-style format to prepend to @dbus_error_message or %NULL.
717
 * @var_args: Arguments for @format.
718
 *
719
 * Like g_dbus_error_set_dbus_error() but intended for language bindings.
720
 *
721
 * Since: 2.26
722
 */
723
void
724
g_dbus_error_set_dbus_error_valist (GError      **error,
725
                                    const gchar  *dbus_error_name,
726
                                    const gchar  *dbus_error_message,
727
                                    const gchar  *format,
728
                                    va_list       var_args)
729
0
{
730
0
  g_return_if_fail (error == NULL || *error == NULL);
731
0
  g_return_if_fail (dbus_error_name != NULL);
732
0
  g_return_if_fail (dbus_error_message != NULL);
733
734
0
  if (error == NULL)
735
0
    return;
736
737
0
  if (format != NULL)
738
0
    {
739
0
      gchar *message;
740
0
      gchar *s;
741
0
      message = g_strdup_vprintf (format, var_args);
742
0
      s = g_strdup_printf ("%s: %s", message, dbus_error_message);
743
0
      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, s);
744
0
      g_free (s);
745
0
      g_free (message);
746
0
    }
747
0
  else
748
0
    {
749
0
      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
750
0
    }
751
0
}
752
753
/**
754
 * g_dbus_error_strip_remote_error:
755
 * @error: A #GError.
756
 *
757
 * Looks for extra information in the error message used to recover
758
 * the D-Bus error name and strips it if found. If stripped, the
759
 * message field in @error will correspond exactly to what was
760
 * received on the wire.
761
 *
762
 * This is typically used when presenting errors to the end user.
763
 *
764
 * Returns: %TRUE if information was stripped, %FALSE otherwise.
765
 *
766
 * Since: 2.26
767
 */
768
gboolean
769
g_dbus_error_strip_remote_error (GError *error)
770
0
{
771
0
  gboolean ret;
772
773
0
  g_return_val_if_fail (error != NULL, FALSE);
774
775
0
  ret = FALSE;
776
777
0
  if (g_str_has_prefix (error->message, "GDBus.Error:"))
778
0
    {
779
0
      const gchar *begin;
780
0
      const gchar *end;
781
0
      gchar *new_message;
782
783
0
      begin = error->message + sizeof ("GDBus.Error:") -1;
784
0
      end = strstr (begin, ":");
785
0
      if (end != NULL && end[1] == ' ')
786
0
        {
787
0
          new_message = g_strdup (end + 2);
788
0
          g_free (error->message);
789
0
          error->message = new_message;
790
0
          ret = TRUE;
791
0
        }
792
0
    }
793
794
0
  return ret;
795
0
}
796
797
/**
798
 * g_dbus_error_encode_gerror:
799
 * @error: A #GError.
800
 *
801
 * Creates a D-Bus error name to use for @error. If @error matches
802
 * a registered error (cf. g_dbus_error_register_error()), the corresponding
803
 * D-Bus error name will be returned.
804
 *
805
 * Otherwise the a name of the form
806
 * `org.gtk.GDBus.UnmappedGError.Quark._ESCAPED_QUARK_NAME.Code_ERROR_CODE`
807
 * will be used. This allows other GDBus applications to map the error
808
 * on the wire back to a #GError using g_dbus_error_new_for_dbus_error().
809
 *
810
 * This function is typically only used in object mappings to put a
811
 * #GError on the wire. Regular applications should not use it.
812
 *
813
 * Returns: (transfer full) (not nullable): A D-Bus error name (never %NULL).
814
 *     Free with g_free().
815
 *
816
 * Since: 2.26
817
 */
818
gchar *
819
g_dbus_error_encode_gerror (const GError *error)
820
0
{
821
0
  RegisteredError *re;
822
0
  gchar *error_name;
823
824
0
  g_return_val_if_fail (error != NULL, NULL);
825
826
  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
827
0
  _g_dbus_initialize ();
828
829
0
  error_name = NULL;
830
831
0
  G_LOCK (error_lock);
832
0
  re = NULL;
833
0
  if (quark_code_pair_to_re != NULL)
834
0
    {
835
0
      QuarkCodePair pair;
836
0
      pair.error_domain = error->domain;
837
0
      pair.error_code = error->code;
838
0
      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
839
0
      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
840
0
    }
841
0
  if (re != NULL)
842
0
    {
843
0
      error_name = g_strdup (re->dbus_error_name);
844
0
      G_UNLOCK (error_lock);
845
0
    }
846
0
  else
847
0
    {
848
0
      const gchar *domain_as_string;
849
0
      GString *s;
850
0
      guint n;
851
852
0
      G_UNLOCK (error_lock);
853
854
      /* We can't make a lot of assumptions about what domain_as_string
855
       * looks like and D-Bus is extremely picky about error names so
856
       * hex-encode it for transport across the wire.
857
       */
858
0
      domain_as_string = g_quark_to_string (error->domain);
859
860
      /* 0 is not a domain; neither are non-quark integers */
861
0
      g_return_val_if_fail (domain_as_string != NULL, NULL);
862
863
0
      s = g_string_new ("org.gtk.GDBus.UnmappedGError.Quark._");
864
0
      for (n = 0; domain_as_string[n] != 0; n++)
865
0
        {
866
0
          gint c = domain_as_string[n];
867
0
          if (g_ascii_isalnum (c))
868
0
            {
869
0
              g_string_append_c (s, c);
870
0
            }
871
0
          else
872
0
            {
873
0
              guint nibble_top;
874
0
              guint nibble_bottom;
875
0
              g_string_append_c (s, '_');
876
0
              nibble_top = ((int) domain_as_string[n]) >> 4;
877
0
              nibble_bottom = ((int) domain_as_string[n]) & 0x0f;
878
0
              if (nibble_top < 10)
879
0
                nibble_top += '0';
880
0
              else
881
0
                nibble_top += 'a' - 10;
882
0
              if (nibble_bottom < 10)
883
0
                nibble_bottom += '0';
884
0
              else
885
0
                nibble_bottom += 'a' - 10;
886
0
              g_string_append_c (s, nibble_top);
887
0
              g_string_append_c (s, nibble_bottom);
888
0
            }
889
0
        }
890
0
      g_string_append_printf (s, ".Code%d", error->code);
891
0
      error_name = g_string_free (s, FALSE);
892
0
    }
893
894
0
  return error_name;
895
0
}