Coverage Report

Created: 2025-06-13 06:55

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