diff options
Diffstat (limited to 'gio')
159 files changed, 7943 insertions, 1531 deletions
diff --git a/gio/fam/meson.build b/gio/fam/meson.build index 8019fe5c4..e3f57037c 100644 --- a/gio/fam/meson.build +++ b/gio/fam/meson.build @@ -34,3 +34,9 @@ module = shared_module('giofam', 'gfamfilemonitor.c', if not meson.is_cross_build() meson.add_install_script('../gio-querymodules-wrapper.py', gio_querymodules.full_path(), glib_giomodulesdir) endif + +if meson.version().version_compare('>=0.58') + env = environment() + env.prepend('GIO_EXTRA_MODULES', meson.current_build_dir()) + meson.add_devenv(env) +endif
\ No newline at end of file diff --git a/gio/gappinfo.c b/gio/gappinfo.c index eff18da83..3f0332825 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -326,7 +326,12 @@ g_app_info_set_as_default_for_type (GAppInfo *appinfo, iface = G_APP_INFO_GET_IFACE (appinfo); - return (* iface->set_as_default_for_type) (appinfo, content_type, error); + if (iface->set_as_default_for_type) + return (* iface->set_as_default_for_type) (appinfo, content_type, error); + + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Setting default applications not supported yet")); + return FALSE; } /** @@ -354,7 +359,12 @@ g_app_info_set_as_last_used_for_type (GAppInfo *appinfo, iface = G_APP_INFO_GET_IFACE (appinfo); - return (* iface->set_as_last_used_for_type) (appinfo, content_type, error); + if (iface->set_as_last_used_for_type) + return (* iface->set_as_last_used_for_type) (appinfo, content_type, error); + + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Setting application as last used for type not supported yet")); + return FALSE; } /** @@ -1103,6 +1113,7 @@ g_app_info_delete (GAppInfo *appinfo) enum { LAUNCH_FAILED, + LAUNCH_STARTED, LAUNCHED, LAST_SIGNAL }; @@ -1147,7 +1158,7 @@ g_app_launch_context_class_init (GAppLaunchContextClass *klass) * @context: the object emitting the signal * @startup_notify_id: the startup notification id for the failed launch * - * The ::launch-failed signal is emitted when a #GAppInfo launch + * The #GAppLaunchContext::launch-failed signal is emitted when a #GAppInfo launch * fails. The startup notification id is provided, so that the launcher * can cancel the startup notification. * @@ -1161,16 +1172,55 @@ g_app_launch_context_class_init (GAppLaunchContextClass *klass) G_TYPE_NONE, 1, G_TYPE_STRING); /** + * GAppLaunchContext::launch-started: + * @context: the object emitting the signal + * @info: the #GAppInfo that is about to be launched + * @platform_data: (nullable): additional platform-specific data for this launch + * + * The #GAppLaunchContext::launch-started signal is emitted when a #GAppInfo is + * about to be launched. If non-null the @platform_data is an + * GVariant dictionary mapping strings to variants (ie `a{sv}`), which + * contains additional, platform-specific data about this launch. On + * UNIX, at least the `startup-notification-id` keys will be + * present. + * + * The value of the `startup-notification-id` key (type `s`) is a startup + * notification ID corresponding to the format from the [startup-notification + * specification](https://specifications.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt). + * It allows tracking the progress of the launchee through startup. + * + * It is guaranteed that this signal is followed by either a #GAppLaunchContext::launched or + * #GAppLaunchContext::launch-failed signal. + * + * Since: 2.72 + */ + signals[LAUNCH_STARTED] = g_signal_new (I_("launch-started"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GAppLaunchContextClass, launch_started), + NULL, NULL, + _g_cclosure_marshal_VOID__OBJECT_VARIANT, + G_TYPE_NONE, 2, + G_TYPE_APP_INFO, G_TYPE_VARIANT); + g_signal_set_va_marshaller (signals[LAUNCH_STARTED], + G_TYPE_FROM_CLASS (klass), + _g_cclosure_marshal_VOID__OBJECT_VARIANTv); + + /** * GAppLaunchContext::launched: * @context: the object emitting the signal * @info: the #GAppInfo that was just launched * @platform_data: additional platform-specific data for this launch * - * The ::launched signal is emitted when a #GAppInfo is successfully + * The #GAppLaunchContext::launched signal is emitted when a #GAppInfo is successfully * launched. The @platform_data is an GVariant dictionary mapping - * strings to variants (ie a{sv}), which contains additional, + * strings to variants (ie `a{sv}`), which contains additional, * platform-specific data about this launch. On UNIX, at least the - * "pid" and "startup-notification-id" keys will be present. + * `pid` and `startup-notification-id` keys will be present. + * + * Since 2.72 the `pid` may be 0 if the process id wasn't known (for + * example if the process was launched via D-Bus). The `pid` may not be + * set at all in subsequent releases. * * Since: 2.36 */ diff --git a/gio/gappinfo.h b/gio/gappinfo.h index d26d048a5..ad3068e31 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -293,12 +293,14 @@ struct _GAppLaunchContextClass void (* launched) (GAppLaunchContext *context, GAppInfo *info, GVariant *platform_data); + void (* launch_started) (GAppLaunchContext *context, + GAppInfo *info, + GVariant *platform_data); /* Padding for future expansion */ void (*_g_reserved1) (void); void (*_g_reserved2) (void); void (*_g_reserved3) (void); - void (*_g_reserved4) (void); }; GLIB_AVAILABLE_IN_ALL diff --git a/gio/gapplicationcommandline.c b/gio/gapplicationcommandline.c index 741aa97f1..d7be108bf 100644 --- a/gio/gapplicationcommandline.c +++ b/gio/gapplicationcommandline.c @@ -106,7 +106,7 @@ * The complete example can be found here: * [gapplication-example-cmdline.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gapplication-example-cmdline.c) * - * In more complicated cases, the handling of the comandline can be + * In more complicated cases, the handling of the commandline can be * split between the launcher and the primary instance. * |[<!-- language="C" --> * static gboolean @@ -119,6 +119,12 @@ * * argv = *arguments; * + * if (argv[0] == NULL) + * { + * *exit_status = 0; + * return FALSE; + * } + * * i = 1; * while (argv[i]) * { diff --git a/gio/gcontenttype-win32.c b/gio/gcontenttype-win32.c index 2f07fa36e..9b6f69ece 100644 --- a/gio/gcontenttype-win32.c +++ b/gio/gcontenttype-win32.c @@ -59,7 +59,7 @@ get_registry_classes_key (const char *subdir, if (key_type == REG_EXPAND_SZ) { wchar_t dummy[1]; - int len = ExpandEnvironmentStringsW (wc_temp, dummy, 1); + DWORD len = ExpandEnvironmentStringsW (wc_temp, dummy, 1); if (len > 0) { wchar_t *wc_temp_expanded = g_new (wchar_t, len); @@ -128,7 +128,8 @@ g_content_type_is_a (const gchar *type, const gchar *supertype) { gboolean res; - char *value_utf8; + char *perceived_type; + char *perceived_supertype; g_return_val_if_fail (type != NULL, FALSE); g_return_val_if_fail (supertype != NULL, FALSE); @@ -136,12 +137,15 @@ g_content_type_is_a (const gchar *type, if (g_content_type_equals (type, supertype)) return TRUE; - res = FALSE; - value_utf8 = get_registry_classes_key (type, L"PerceivedType"); - if (value_utf8 && strcmp (value_utf8, supertype) == 0) - res = TRUE; - g_free (value_utf8); - + perceived_type = get_registry_classes_key (type, L"PerceivedType"); + perceived_supertype = get_registry_classes_key (supertype, L"PerceivedType"); + + res = perceived_type && perceived_supertype && + strcmp (perceived_type, perceived_supertype) == 0; + + g_free (perceived_type); + g_free (perceived_supertype); + return res; } @@ -342,7 +346,8 @@ g_content_type_from_mime_type (const gchar *mime_type) content_type = get_registry_classes_key (key, L"Extension"); g_free (key); - return content_type; + + return content_type ? g_steal_pointer (&content_type) : g_strdup ("*"); } gchar * @@ -354,6 +359,7 @@ g_content_type_guess (const gchar *filename, char *basename; char *type; char *dot; + size_t i; type = NULL; @@ -366,11 +372,21 @@ g_content_type_guess (const gchar *filename, if (filename) { - basename = g_path_get_basename (filename); - dot = strrchr (basename, '.'); - if (dot) - type = g_strdup (dot); - g_free (basename); + i = strlen (filename); + if (i > 0 && filename[i - 1] == G_DIR_SEPARATOR) + { + type = g_strdup ("inode/directory"); + if (result_uncertain) + *result_uncertain = TRUE; + } + else + { + basename = g_path_get_basename (filename); + dot = strrchr (basename, '.'); + if (dot) + type = g_strdup (dot); + g_free (basename); + } } if (type) diff --git a/gio/gcontenttype.c b/gio/gcontenttype.c index 83075fcb6..3c9522bc6 100644 --- a/gio/gcontenttype.c +++ b/gio/gcontenttype.c @@ -32,6 +32,7 @@ #include "gfileenumerator.h" #include "gfileinfo.h" #include "glibintl.h" +#include "glib-private.h" /** @@ -57,7 +58,12 @@ static void tree_magic_schedule_reload (void); -/* We lock this mutex whenever we modify global state in this module. */ +/* We lock this mutex whenever we modify global state in this module. + * Taking and releasing this lock should always be associated with a pair of + * g_begin_ignore_leaks()/g_end_ignore_leaks() calls, as any call into xdgmime + * could trigger xdg_mime_init(), which makes a number of one-time allocations + * which GLib can never free as it doesn’t know when is suitable to call + * xdg_mime_shutdown(). */ G_LOCK_DEFINE_STATIC (gio_xdgmime); gsize @@ -66,7 +72,9 @@ _g_unix_content_type_get_sniff_len (void) gsize size; G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); size = xdg_mime_get_max_buffer_extents (); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return size; @@ -78,7 +86,9 @@ _g_unix_content_type_unalias (const gchar *type) gchar *res; G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); res = g_strdup (xdg_mime_unalias_mime_type (type)); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return res; @@ -95,6 +105,7 @@ _g_unix_content_type_get_parents (const gchar *type) array = g_ptr_array_new (); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); umime = xdg_mime_unalias_mime_type (type); @@ -106,6 +117,7 @@ _g_unix_content_type_get_parents (const gchar *type) free (parents); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); g_ptr_array_add (array, NULL); @@ -233,7 +245,9 @@ g_content_type_equals (const gchar *type1, g_return_val_if_fail (type2 != NULL, FALSE); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); res = xdg_mime_mime_type_equal (type1, type2); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return res; @@ -259,7 +273,9 @@ g_content_type_is_a (const gchar *type, g_return_val_if_fail (supertype != NULL, FALSE); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); res = xdg_mime_mime_type_subclass (type, supertype); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return res; @@ -472,7 +488,9 @@ g_content_type_get_description (const gchar *type) g_return_val_if_fail (type != NULL, NULL); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); type = xdg_mime_unalias_mime_type (type); + g_end_ignore_leaks (); if (type_comment_cache == NULL) type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); @@ -528,7 +546,9 @@ g_content_type_get_icon_internal (const gchar *type, g_return_val_if_fail (type != NULL, NULL); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); xdg_icon = xdg_mime_get_icon (type); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); if (xdg_icon) @@ -619,7 +639,9 @@ g_content_type_get_generic_icon_name (const gchar *type) g_return_val_if_fail (type != NULL, NULL); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); xdg_icon_name = xdg_mime_get_generic_icon (type); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); if (!xdg_icon_name) @@ -703,8 +725,10 @@ g_content_type_from_mime_type (const gchar *mime_type) g_return_val_if_fail (mime_type != NULL, NULL); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); /* mime type and content type are same on unixes */ umime = g_strdup (xdg_mime_unalias_mime_type (mime_type)); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return umime; @@ -712,7 +736,7 @@ g_content_type_from_mime_type (const gchar *mime_type) /** * g_content_type_guess: - * @filename: (nullable): a string, or %NULL + * @filename: (nullable) (type filename): a path, or %NULL * @data: (nullable) (array length=data_size): a stream of data, or %NULL * @data_size: the size of @data * @result_uncertain: (out) (optional): return location for the certainty @@ -751,11 +775,12 @@ g_content_type_guess (const gchar *filename, g_return_val_if_fail (data_size != (gsize) -1, g_strdup (XDG_MIME_TYPE_UNKNOWN)); G_LOCK (gio_xdgmime); + g_begin_ignore_leaks (); if (filename) { i = strlen (filename); - if (filename[i - 1] == '/') + if (i > 0 && filename[i - 1] == '/') { name_mimetypes[0] = "inode/directory"; name_mimetypes[1] = NULL; @@ -775,6 +800,7 @@ g_content_type_guess (const gchar *filename, if (n_name_mimetypes == 1) { gchar *s = g_strdup (name_mimetypes[0]); + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return s; } @@ -843,6 +869,7 @@ g_content_type_guess (const gchar *filename, } } + g_end_ignore_leaks (); G_UNLOCK (gio_xdgmime); return mimetype; @@ -997,6 +1024,8 @@ parse_header (gchar *line) line[len - 1] = 0; s = strchr (line, ':'); + if (s == NULL) + return NULL; match = g_slice_new0 (TreeMatch); match->priority = atoi (line + 1); @@ -1025,9 +1054,13 @@ parse_match_line (gchar *line, { *depth = atoi (line); s = strchr (line, '>'); + if (s == NULL) + goto handle_error; } s += 2; p = strchr (s, '"'); + if (p == NULL) + goto handle_error; *p = 0; matchlet->path = g_strdup (s); @@ -1058,6 +1091,10 @@ parse_match_line (gchar *line, g_strfreev (parts); return matchlet; + +handle_error: + g_slice_free (TreeMatchlet, matchlet); + return NULL; } static gint @@ -1119,7 +1156,7 @@ read_tree_magic_from_directory (const gchar *prefix) gchar *text; gsize len; gchar **lines; - gint i; + gsize i; TreeMatch *match; TreeMatchlet *matchlet; gint depth; @@ -1134,14 +1171,18 @@ read_tree_magic_from_directory (const gchar *prefix) match = NULL; for (i = 0; lines[i] && lines[i][0]; i++) { - if (lines[i][0] == '[') + if (lines[i][0] == '[' && (match = parse_header (lines[i])) != NULL) { - match = parse_header (lines[i]); insert_match (match); } else if (match != NULL) { matchlet = parse_match_line (lines[i], &depth); + if (matchlet == NULL) + { + g_warning ("%s: body corrupt; skipping", filename); + break; + } insert_matchlet (match, matchlet, depth); } else diff --git a/gio/gcredentials.c b/gio/gcredentials.c index ebbc2cc5b..17378e881 100644 --- a/gio/gcredentials.c +++ b/gio/gcredentials.c @@ -72,6 +72,9 @@ * On Solaris (including OpenSolaris and its derivatives), the native * credential type is a `ucred_t`. This corresponds to * %G_CREDENTIALS_TYPE_SOLARIS_UCRED. + * + * Since GLib 2.72, on Windows, the native credentials may contain the PID of a + * process. This corresponds to %G_CREDENTIALS_TYPE_WIN32_PID. */ /** @@ -91,6 +94,7 @@ struct _GCredentials struct ucred native; #elif G_CREDENTIALS_USE_APPLE_XUCRED struct xucred native; + pid_t pid; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED struct cmsgcred native; #elif G_CREDENTIALS_USE_NETBSD_UNPCBID @@ -99,6 +103,8 @@ struct _GCredentials struct sockpeercred native; #elif G_CREDENTIALS_USE_SOLARIS_UCRED ucred_t *native; +#elif G_CREDENTIALS_USE_WIN32_PID + DWORD native; #else #ifdef __GNUC__ #pragma GCC diagnostic push @@ -170,6 +176,8 @@ g_credentials_init (GCredentials *credentials) * For now we fill it with -1 (meaning "no data"). */ for (i = 1; i < NGROUPS; i++) credentials->native.cr_groups[i] = -1; + + credentials->pid = -1; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED memset (&credentials->native, 0, sizeof (struct cmsgcred)); credentials->native.cmcred_pid = getpid (); @@ -185,6 +193,8 @@ g_credentials_init (GCredentials *credentials) credentials->native.gid = getegid (); #elif G_CREDENTIALS_USE_SOLARIS_UCRED credentials->native = ucred_get (P_MYID); +#elif G_CREDENTIALS_USE_WIN32_PID + credentials->native = GetCurrentProcessId (); #endif } @@ -290,6 +300,8 @@ g_credentials_to_string (GCredentials *credentials) if (ret->str[ret->len - 1] == ',') ret->str[ret->len - 1] = '\0'; } +#elif G_CREDENTIALS_USE_WIN32_PID + g_string_append_printf (ret, "win32-pid:pid=%lu", credentials->native); #else g_string_append (ret, "unknown"); #endif @@ -569,8 +581,7 @@ g_credentials_get_unix_user (GCredentials *credentials, * * This operation can fail if #GCredentials is not supported on the * OS or if the native credentials type does not contain information - * about the UNIX process ID (for example this is the case for - * %G_CREDENTIALS_TYPE_APPLE_XUCRED). + * about the UNIX process ID. * * Returns: The UNIX process ID, or `-1` if @error is set. * @@ -598,13 +609,21 @@ g_credentials_get_unix_pid (GCredentials *credentials, ret = credentials->native.pid; #elif G_CREDENTIALS_USE_SOLARIS_UCRED ret = ucred_getpid (credentials->native); +#elif G_CREDENTIALS_USE_WIN32_PID + ret = credentials->native; +#else + +#if G_CREDENTIALS_USE_APPLE_XUCRED + ret = credentials->pid; #else - /* this case includes G_CREDENTIALS_USE_APPLE_XUCRED */ ret = -1; - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - _("GCredentials does not contain a process ID on this OS")); +#endif + + if (ret == -1) + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("GCredentials does not contain a process ID on this OS")); #endif return ret; @@ -671,4 +690,16 @@ g_credentials_set_unix_user (GCredentials *credentials, return ret; } +#ifdef __APPLE__ +void +_g_credentials_set_local_peerid (GCredentials *credentials, + pid_t pid) +{ + g_return_if_fail (G_IS_CREDENTIALS (credentials)); + g_return_if_fail (pid >= 0); + + credentials->pid = pid; +} +#endif /* __APPLE__ */ + #endif /* G_OS_UNIX */ diff --git a/gio/gcredentialsprivate.h b/gio/gcredentialsprivate.h index 13d3bf327..c09f9eceb 100644 --- a/gio/gcredentialsprivate.h +++ b/gio/gcredentialsprivate.h @@ -40,6 +40,7 @@ #undef G_CREDENTIALS_USE_OPENBSD_SOCKPEERCRED #undef G_CREDENTIALS_USE_SOLARIS_UCRED #undef G_CREDENTIALS_USE_APPLE_XUCRED +#undef G_CREDENTIALS_USE_WIN32_PID /* * G_CREDENTIALS_NATIVE_TYPE: @@ -168,6 +169,17 @@ #define G_CREDENTIALS_SPOOFING_SUPPORTED 1 #define G_CREDENTIALS_HAS_PID 0 +void _g_credentials_set_local_peerid (GCredentials *credentials, + pid_t pid); + +#elif defined(_WIN32) +#define G_CREDENTIALS_SUPPORTED 1 +#define G_CREDENTIALS_USE_WIN32_PID 1 +#define G_CREDENTIALS_NATIVE_TYPE G_CREDENTIALS_TYPE_WIN32_PID +#define G_CREDENTIALS_NATIVE_SIZE (sizeof (DWORD)) +#define G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED 1 +#define G_CREDENTIALS_HAS_PID 1 + #endif #endif /* __G_CREDENTIALS_PRIVATE_H__ */ diff --git a/gio/gdbus-2.0/codegen/.flake8 b/gio/gdbus-2.0/codegen/.flake8 new file mode 100644 index 000000000..9450a2832 --- /dev/null +++ b/gio/gdbus-2.0/codegen/.flake8 @@ -0,0 +1,4 @@ +[flake8] +# We are generating long lines through templates +max-line-length = 120 +exclude = __pycache__ diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py index 9d28cb77d..d8d9a8521 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -2101,7 +2101,7 @@ class CodeGenerator: " *\n" " * Signal emitted when a remote caller is invoking the %s.%s() D-Bus method.\n" " *\n" - " * If a signal handler returns %%TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call %s_complete_%s() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %%G_DBUS_ERROR_UNKNOWN_METHOD error is returned.\n" + " * If a signal handler returns %%TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call %s_complete_%s() or e.g. g_dbus_method_invocation_return_error() on it) and no other signal handlers will run. If no signal handler handles the invocation, the %%G_DBUS_ERROR_UNKNOWN_METHOD error is returned.\n" " *\n" " * Returns: %%G_DBUS_METHOD_INVOCATION_HANDLED or %%TRUE if the invocation was handled, %%G_DBUS_METHOD_INVOCATION_UNHANDLED or %%FALSE to let other signal handlers run.\n" % (i.name, m.name, i.name_lower, m.name_lower), diff --git a/gio/gdbus-2.0/codegen/codegen_docbook.py b/gio/gdbus-2.0/codegen/codegen_docbook.py index 4b69e2927..b7280e306 100644 --- a/gio/gdbus-2.0/codegen/codegen_docbook.py +++ b/gio/gdbus-2.0/codegen/codegen_docbook.py @@ -25,10 +25,6 @@ from os import path from . import utils -# Disable line length warnings as wrapping the Docbook templates would be hard -# flake8: noqa: E501 - - # ---------------------------------------------------------------------------------------------------- @@ -345,9 +341,17 @@ class DocbookCodeGenerator: def expand_paras(self, s, expandParamsAndConstants): s = self.expand(s, expandParamsAndConstants).strip() - if not s.startswith("<para"): - s = "<para>%s</para>" % s - return s + res = [] + if not s.startswith("<para>"): + res.append("<para>") + for line in s.split("\n"): + line = line.strip() + if not line: + line = "</para><para>" + res.append(line) + if not s.endswith("</para>"): + res.append("</para>") + return "\n".join(res) def generate_expand_dicts(self): self.expand_member_dict = {} diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index 238d7dd12..194800c78 100644 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -30,6 +30,7 @@ from . import dbustypes from . import parser from . import codegen from . import codegen_docbook +from . import codegen_rst from .utils import print_error, print_warning @@ -212,6 +213,11 @@ def codegen_main(): help="Generate Docbook in OUTFILES-org.Project.IFace.xml", ) arg_parser.add_argument( + "--generate-rst", + metavar="OUTFILES", + help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst", + ) + arg_parser.add_argument( "--pragma-once", action="store_true", help='Use "pragma once" as the inclusion guard', @@ -287,10 +293,12 @@ def codegen_main(): ) if ( - args.generate_c_code is not None or args.generate_docbook is not None + args.generate_c_code is not None + or args.generate_docbook is not None + or args.generate_rst is not None ) and args.output is not None: print_error( - "Using --generate-c-code or --generate-docbook and " + "Using --generate-c-code or --generate-docbook or --generate-rst and " "--output at the same time is not allowed" ) @@ -420,6 +428,11 @@ def codegen_main(): if docbook: docbook_gen.generate(docbook, args.output_directory) + rst = args.generate_rst + rst_gen = codegen_rst.RstCodeGenerator(all_ifaces) + if rst: + rst_gen.generate(rst, args.output_directory) + if args.header: with open(h_file, "w") as outfile: gen = codegen.HeaderCodeGenerator( diff --git a/gio/gdbus-2.0/codegen/codegen_rst.py b/gio/gdbus-2.0/codegen/codegen_rst.py new file mode 100644 index 000000000..51da2d572 --- /dev/null +++ b/gio/gdbus-2.0/codegen/codegen_rst.py @@ -0,0 +1,332 @@ +# SPDX-FileCopyrightText: 2022 Emmanuele Bassi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +import re + +from . import utils + +# Disable line length warnings as wrapping the templates would be hard +# flake8: noqa: E501 + + +class RstCodeGenerator: + """Generates documentation in reStructuredText format.""" + + def __init__(self, ifaces): + self.ifaces = ifaces + self._generate_expand_dicts() + + def _expand(self, s, expandParamsAndConstants): + """Expands parameters and constant literals.""" + res = [] + for line in s.split("\n"): + line = line.strip() + if line == "": + res.append("") + continue + for key in self._expand_member_dict_keys: + line = line.replace(key, self._expand_member_dict[key]) + for key in self._expand_iface_dict_keys: + line = line.replace(key, self._expand_iface_dict[key]) + if expandParamsAndConstants: + # replace @foo with ``foo`` + line = re.sub( + "@[a-zA-Z0-9_]*", + lambda m: "``" + m.group(0)[1:] + "``", + line, + ) + # replace e.g. %TRUE with ``TRUE`` + line = re.sub( + "%[a-zA-Z0-9_]*", + lambda m: "``" + m.group(0)[1:] + "``", + line, + ) + res.append(line) + return "\n".join(res) + + def _generate_expand_dicts(self): + """Generates the dictionaries used to expand gtk-doc sigils.""" + self._expand_member_dict = {} + self._expand_iface_dict = {} + for i in self.ifaces: + key = f"#{i.name}" + value = f"`{i.name}`_" + self._expand_iface_dict[key] = value + + for m in i.methods: + key = "%s.%s()" % (i.name, m.name) + value = f"`{i.name}.{m.name}`_" + self._expand_member_dict[key] = value + + for s in i.signals: + key = "#%s::%s" % (i.name, s.name) + value = f"`{i.name}::{s.name}`_" + self._expand_member_dict[key] = value + + for p in i.properties: + key = "#%s:%s" % (i.name, p.name) + value = f"`{i.name}:{p.name}`_" + self._expand_member_dict[key] = value + + # Make sure to expand the keys in reverse order so e.g. #org.foo.Iface:MediaCompat + # is evaluated before #org.foo.Iface:Media ... + self._expand_member_dict_keys = sorted( + self._expand_member_dict.keys(), reverse=True + ) + self._expand_iface_dict_keys = sorted( + self._expand_iface_dict.keys(), reverse=True + ) + + def _generate_header(self, iface): + """Generates the header and preamble of the document.""" + header_len = len(iface.name) + res = [ + f".. _{iface.name}:", + "", + "=" * header_len, + iface.name, + "=" * header_len, + "", + "-----------", + "Description", + "-----------", + "", + f".. _{iface.name} Description:", + "", + iface.doc_string_brief.strip(), + "", + self._expand(iface.doc_string, True), + "", + ] + if iface.since: + res += [ + f"Interface available since: {iface.since}.", + "", + ] + if iface.deprecated: + res += [ + ".. warning::", + "", + " This interface is deprecated.", + "", + "", + ] + res += [""] + return "\n".join(res) + + def _generate_section(self, title, name): + """Generates a section with the given title.""" + res = [ + "-" * len(title), + title, + "-" * len(title), + "", + f".. {name} {title}:", + "", + "", + ] + return "\n".join(res) + + def _generate_properties(self, iface): + """Generates the properties section.""" + res = [] + for p in iface.properties: + title = f"{iface.name}:{p.name}" + if p.readable and p.writable: + access = "readwrite" + elif p.writable: + access = "writable" + else: + access = "readable" + res += [ + title, + "^" * len(title), + "", + "::", + "", + f" {p.name} {access} {p.signature}", + "", + "", + self._expand(p.doc_string, True), + "", + ] + if p.since: + res += [ + f"Property available since: {p.since}.", + "", + ] + if p.deprecated: + res += [ + ".. warning::", + "", + " This property is deprecated.", + "", + "", + ] + res += [""] + return "\n".join(res) + + def _generate_method_signature(self, method): + """Generates the method signature as a code block.""" + res = [ + "::", + "", + ] + n_in_args = len(method.in_args) + n_out_args = len(method.out_args) + if n_in_args == 0 and n_out_args == 0: + res += [ + f" {method.name} ()", + ] + else: + res += [ + f" {method.name} (", + ] + for idx, arg in enumerate(method.in_args): + if idx == n_in_args - 1 and n_out_args == 0: + res += [ + f" IN {arg.name} {arg.signature}", + ] + else: + res += [ + f" IN {arg.name} {arg.signature},", + ] + for idx, arg in enumerate(method.out_args): + if idx == n_out_args - 1: + res += [ + f" OUT {arg.name} {arg.signature}", + ] + else: + res += [ + f" OUT {arg.name} {arg.signature},", + ] + res += [ + " )", + "", + ] + res += [""] + return "\n".join(res) + + def _generate_methods(self, iface): + """Generates the methods section.""" + res = [] + for m in iface.methods: + title = f"{iface.name}.{m.name}" + res += [ + title, + "^" * len(title), + "", + self._generate_method_signature(m), + "", + self._expand(m.doc_string, True), + "", + ] + for a in m.in_args: + arg_desc = self._expand(a.doc_string, True) + res += [ + f"{a.name}", + f" {arg_desc}", + "", + ] + res += [""] + if m.since: + res += [ + f"Method available since: {m.since}.", + "", + ] + if m.deprecated: + res += [ + ".. warning::", + "", + " This method is deprecated.", + "", + "", + ] + res += [""] + return "\n".join(res) + + def _generate_signal_signature(self, signal): + """Generates the signal signature.""" + res = [ + "::", + "", + ] + n_args = len(signal.args) + if n_args == 0: + res += [ + f" {signal.name} ()", + ] + else: + res += [ + f" {signal.name} (", + ] + for idx, arg in enumerate(signal.args): + if idx == n_args - 1: + res += [ + f" {arg.name} {arg.signature}", + ] + else: + res += [ + f" {arg.name} {arg.signature},", + ] + res += [ + " )", + "", + ] + res += [""] + return "\n".join(res) + + def _generate_signals(self, iface): + """Generates the signals section.""" + res = [] + for s in iface.signals: + title = f"{iface.name}::{s.name}" + res += [ + title, + "^" * len(title), + "", + self._generate_signal_signature(s), + "", + self._expand(s.doc_string, True), + "", + ] + for a in s.args: + arg_desc = self._expand(a.doc_string, True) + res += [ + f"{a.name}", + f" {arg_desc}", + "", + ] + res += [""] + if s.since: + res += [ + f"Signal available since: {s.since}.", + "", + ] + if s.deprecated: + res += [ + ".. warning::", + "", + " This signal is deprecated.", + "", + "", + ] + res += [""] + return "\n".join(res) + + def generate(self, rst, outdir): + """Generates the reStructuredText file for each interface.""" + for i in self.ifaces: + with open(os.path.join(outdir, f"{rst}-{i.name}.rst"), "w") as outfile: + outfile.write(self._generate_header(i)) + if len(i.properties) > 0: + outfile.write(self._generate_section("Properties", i.name)) + outfile.write(self._generate_properties(i)) + if len(i.methods) > 0: + outfile.write(self._generate_section("Methods", i.name)) + outfile.write(self._generate_methods(i)) + if len(i.signals) > 0: + outfile.write(self._generate_section("Signals", i.name)) + outfile.write(self._generate_signals(i)) diff --git a/gio/gdbus-2.0/codegen/meson.build b/gio/gdbus-2.0/codegen/meson.build index c0caf0e50..bf25cdaeb 100644 --- a/gio/gdbus-2.0/codegen/meson.build +++ b/gio/gdbus-2.0/codegen/meson.build @@ -3,6 +3,7 @@ gdbus_codegen_files = [ 'codegen.py', 'codegen_main.py', 'codegen_docbook.py', + 'codegen_rst.py', 'dbustypes.py', 'parser.py', 'utils.py', diff --git a/gio/gdbus-2.0/codegen/parser.py b/gio/gdbus-2.0/codegen/parser.py index 45226d540..cf8ea5229 100644 --- a/gio/gdbus-2.0/codegen/parser.py +++ b/gio/gdbus-2.0/codegen/parser.py @@ -85,7 +85,7 @@ class DBusXMLParser: symbol = line[0:colon_index] rest_of_line = line[colon_index + 2 :].strip() if len(rest_of_line) > 0: - body += "<para>" + rest_of_line + "</para>" + body += f"{rest_of_line}\n" comment_state = DBusXMLParser.COMMENT_STATE_PARAMS elif comment_state == DBusXMLParser.COMMENT_STATE_PARAMS: if line.startswith("@"): @@ -93,9 +93,9 @@ class DBusXMLParser: if colon_index == -1: comment_state = DBusXMLParser.COMMENT_STATE_BODY if not in_para: - body += "<para>" + body += "\n" in_para = True - body += orig_line + "\n" + body += f"{orig_line}\n" else: param = line[1:colon_index] docs = line[colon_index + 2 :] @@ -104,21 +104,20 @@ class DBusXMLParser: comment_state = DBusXMLParser.COMMENT_STATE_BODY if len(line) > 0: if not in_para: - body += "<para>" + body += "\n" in_para = True body += orig_line + "\n" elif comment_state == DBusXMLParser.COMMENT_STATE_BODY: if len(line) > 0: if not in_para: - body += "<para>" in_para = True body += orig_line + "\n" else: if in_para: - body += "</para>" + body += "\n" in_para = False if in_para: - body += "</para>" + body += "\n" if symbol != "": self.doc_comment_last_symbol = symbol diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c index f44253804..476056b9f 100644 --- a/gio/gdbus-tool.c +++ b/gio/gdbus-tool.c @@ -107,7 +107,7 @@ usage (gint *argc, gchar **argv[], gboolean use_stdout) g_option_context_set_help_enabled (o, FALSE); /* Ignore parsing result */ g_option_context_parse (o, argc, argv, NULL); - program_name = g_path_get_basename ((*argv)[0]); + program_name = (*argc > 0) ? g_path_get_basename ((*argv)[0]) : g_strdup ("gdbus-tool"); s = g_strdup_printf (_("Commands:\n" " help Shows this information\n" " introspect Introspect a remote object\n" @@ -141,6 +141,7 @@ modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command) * 2. save old argv[0] and restore later */ + g_assert (*argc > 1); g_assert (g_strcmp0 ((*argv)[1], command) == 0); remove_arg (1, argc, argv); @@ -887,6 +888,7 @@ static gchar *opt_call_dest = NULL; static gchar *opt_call_object_path = NULL; static gchar *opt_call_method = NULL; static gint opt_call_timeout = -1; +static gboolean opt_call_interactive = FALSE; static const GOptionEntry call_entries[] = { @@ -894,6 +896,7 @@ static const GOptionEntry call_entries[] = { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_call_object_path, N_("Object path to invoke method on"), NULL}, { "method", 'm', 0, G_OPTION_ARG_STRING, &opt_call_method, N_("Method and interface name"), NULL}, { "timeout", 't', 0, G_OPTION_ARG_INT, &opt_call_timeout, N_("Timeout in seconds"), NULL}, + { "interactive", 'i', 0, G_OPTION_ARG_NONE, &opt_call_interactive, N_("Allow interactive authorization"), NULL}, G_OPTION_ENTRY_NULL }; @@ -925,6 +928,7 @@ handle_call (gint *argc, gboolean skip_dashes; guint parm; guint n; + GDBusCallFlags flags; ret = FALSE; c = NULL; @@ -1204,6 +1208,11 @@ handle_call (gint *argc, if (parameters != NULL) parameters = g_variant_ref_sink (parameters); + + flags = G_DBUS_CALL_FLAGS_NONE; + if (opt_call_interactive) + flags |= G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION; + #ifdef G_OS_UNIX result = g_dbus_connection_call_with_unix_fd_list_sync (c, opt_call_dest, @@ -1212,7 +1221,7 @@ handle_call (gint *argc, method_name, parameters, NULL, - G_DBUS_CALL_FLAGS_NONE, + flags, opt_call_timeout > 0 ? opt_call_timeout * 1000 : opt_call_timeout, fd_list, NULL, @@ -1226,7 +1235,7 @@ handle_call (gint *argc, method_name, parameters, NULL, - G_DBUS_CALL_FLAGS_NONE, + flags, opt_call_timeout > 0 ? opt_call_timeout * 1000 : opt_call_timeout, NULL, &error); diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c index 48c766682..48fdef2f4 100644 --- a/gio/gdbusaddress.c +++ b/gio/gdbusaddress.c @@ -40,12 +40,12 @@ #include "gdbusprivate.h" #include "gstdio.h" -#ifdef G_OS_UNIX +#ifdef HAVE_UNISTD_H #include <unistd.h> +#endif #include <sys/stat.h> #include <sys/types.h> #include <gio/gunixsocketaddress.h> -#endif #ifdef G_OS_WIN32 #include <windows.h> @@ -66,6 +66,9 @@ * * TCP D-Bus connections are supported, but accessing them via a proxy is * currently not supported. + * + * Since GLib 2.72, `unix:` addresses are supported on Windows with `AF_UNIX` + * support (Windows 10). */ static gchar *get_session_address_platform_specific (GError **error); @@ -571,11 +574,7 @@ g_dbus_address_connect (const gchar *address_entry, ret = NULL; nonce_file = NULL; - if (FALSE) - { - } -#ifdef G_OS_UNIX - else if (g_strcmp0 (transport_name, "unix") == 0) + if (g_strcmp0 (transport_name, "unix") == 0) { const gchar *path; const gchar *abstract; @@ -605,7 +604,6 @@ g_dbus_address_connect (const gchar *address_entry, g_assert_not_reached (); } } -#endif else if (g_strcmp0 (transport_name, "tcp") == 0 || g_strcmp0 (transport_name, "nonce-tcp") == 0) { const gchar *s; @@ -1098,7 +1096,7 @@ get_session_address_dbus_launch (GError **error) if (GLIB_PRIVATE_CALL (g_check_setuid) ()) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Cannot spawn a message bus when setuid")); + _("Cannot spawn a message bus when AT_SECURE is set")); goto out; } diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c index 182c57278..b3f21175b 100644 --- a/gio/gdbusauthmechanismexternal.c +++ b/gio/gdbusauthmechanismexternal.c @@ -29,6 +29,10 @@ #include "glibintl.h" +#ifdef G_OS_WIN32 +#include "gwin32sid.h" +#endif + struct _GDBusAuthMechanismExternalPrivate { gboolean is_client; @@ -124,11 +128,17 @@ static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism) { g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), FALSE); + +#if defined(G_OS_WIN32) + /* all that is required is current process SID */ + return TRUE; +#else /* This mechanism is only available if credentials has been exchanged */ if (_g_dbus_auth_mechanism_get_credentials (mechanism) != NULL) return TRUE; else return FALSE; +#endif } static gint @@ -329,32 +339,39 @@ mechanism_client_initiate (GDBusAuthMechanism *mechanism, { GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); gchar *initial_response = NULL; +#if defined(G_OS_UNIX) GCredentials *credentials; +#endif g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); m->priv->is_client = TRUE; - m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; *out_initial_response_len = 0; + /* return the uid */ +#if defined(G_OS_UNIX) credentials = _g_dbus_auth_mechanism_get_credentials (mechanism); g_assert (credentials != NULL); - /* return the uid */ -#if defined(G_OS_UNIX) initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user (credentials, NULL)); - *out_initial_response_len = strlen (initial_response); #elif defined(G_OS_WIN32) + initial_response = _g_win32_current_process_sid_string (NULL); +#else #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic warning "-Wcpp" #warning Dont know how to send credentials on this OS. The EXTERNAL D-Bus authentication mechanism will not work. #pragma GCC diagnostic pop #endif - m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; #endif + if (initial_response) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; + *out_initial_response_len = strlen (initial_response); + } return initial_response; } diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c index 066ef1ab7..ed5aa3f96 100644 --- a/gio/gdbusauthmechanismsha1.c +++ b/gio/gdbusauthmechanismsha1.c @@ -32,11 +32,13 @@ #endif #ifdef G_OS_WIN32 #include <io.h> +#include "gwin32sid.h" #endif #include "gdbusauthmechanismsha1.h" #include "gcredentials.h" #include "gdbuserror.h" +#include "glocalfileinfo.h" #include "gioenumtypes.h" #include "gioerror.h" #include "gdbusprivate.h" @@ -508,6 +510,7 @@ _log (const gchar *message, * and was created successfully) */ static gint create_lock_exclusive (const gchar *lock_path, + gint64 *mtime_nsec, GError **error) { int errsv; @@ -517,6 +520,16 @@ create_lock_exclusive (const gchar *lock_path, errsv = errno; if (ret < 0) { + GLocalFileStat stat_buf; + + /* Get the modification time to distinguish between the lock being stale + * or highly contested. */ + if (mtime_nsec != NULL && + g_local_file_stat (lock_path, G_LOCAL_FILE_STAT_FIELD_MTIME, G_LOCAL_FILE_STAT_FIELD_ALL, &stat_buf) == 0) + *mtime_nsec = _g_stat_mtime (&stat_buf) * G_USEC_PER_SEC * 1000 + _g_stat_mtim_nsec (&stat_buf); + else if (mtime_nsec != NULL) + *mtime_nsec = 0; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), @@ -537,6 +550,7 @@ keyring_acquire_lock (const gchar *path, gint ret; guint num_tries; int errsv; + gint64 lock_mtime_nsec = 0, lock_mtime_nsec_prev = 0; /* Total possible sleep period = max_tries * timeout_usec = 0.5s */ const guint max_tries = 50; @@ -564,13 +578,21 @@ keyring_acquire_lock (const gchar *path, for (num_tries = 0; num_tries < max_tries; num_tries++) { + lock_mtime_nsec_prev = lock_mtime_nsec; + /* Ignore the error until the final call. */ - ret = create_lock_exclusive (lock, NULL); + ret = create_lock_exclusive (lock, &lock_mtime_nsec, NULL); if (ret >= 0) break; /* sleep 10ms, then try again */ g_usleep (timeout_usec); + + /* If the mtime of the lock file changed, don’t count the retry, as it + * seems like there’s contention between processes for the lock file, + * rather than a stale lock file from a crashed process. */ + if (num_tries > 0 && lock_mtime_nsec != lock_mtime_nsec_prev) + num_tries--; } if (num_tries == max_tries) @@ -593,7 +615,7 @@ keyring_acquire_lock (const gchar *path, _log ("Deleted stale lock file '%s'", lock); /* Try one last time to create it, now that we've deleted the stale one */ - ret = create_lock_exclusive (lock, error); + ret = create_lock_exclusive (lock, NULL, error); if (ret < 0) goto out; } @@ -991,9 +1013,12 @@ mechanism_server_initiate (GDBusAuthMechanism *mechanism, } #elif defined(G_OS_WIN32) gchar *sid; - sid = _g_dbus_win32_get_user_sid (); + + sid = _g_win32_current_process_sid_string (NULL); + if (g_strcmp0 (initial_response, sid) == 0) m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; + g_free (sid); #else #error Please implement for your OS @@ -1143,20 +1168,25 @@ mechanism_client_initiate (GDBusAuthMechanism *mechanism, g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); m->priv->is_client = TRUE; - m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; *out_initial_response_len = 0; #ifdef G_OS_UNIX initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ()); - *out_initial_response_len = strlen (initial_response); #elif defined (G_OS_WIN32) - initial_response = _g_dbus_win32_get_user_sid (); - *out_initial_response_len = strlen (initial_response); + initial_response = _g_win32_current_process_sid_string (NULL); #else #error Please implement for your OS #endif - g_assert (initial_response != NULL); + if (initial_response) + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; + *out_initial_response_len = strlen (initial_response); + } + else + { + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + } return initial_response; } diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index 73b5b309a..1159c2db4 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -282,22 +282,6 @@ call_destroy_notify (GMainContext *context, /* ---------------------------------------------------------------------------------------------------- */ -static gboolean -_g_strv_has_string (const gchar* const *haystack, - const gchar *needle) -{ - guint n; - - for (n = 0; haystack != NULL && haystack[n] != NULL; n++) - { - if (g_strcmp0 (haystack[n], needle) == 0) - return TRUE; - } - return FALSE; -} - -/* ---------------------------------------------------------------------------------------------------- */ - #ifdef G_OS_WIN32 #define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE) #else @@ -5268,7 +5252,7 @@ obj_message_func (GDBusConnection *connection, * #GVariant of incorrect type. * * If an existing callback is already registered at @object_path and - * @interface_name, then @error is set to #G_IO_ERROR_EXISTS. + * @interface_name, then @error is set to %G_IO_ERROR_EXISTS. * * GDBus automatically implements the standard D-Bus interfaces * org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable @@ -6544,7 +6528,7 @@ handle_subtree_introspect (GDBusConnection *connection, /* Assert existence of object if we are not dynamic */ if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) && - !_g_strv_has_string ((const gchar * const *) children, requested_node)) + !g_strv_contains ((const gchar * const *) children, requested_node)) goto out; } else @@ -6675,7 +6659,7 @@ handle_subtree_method_invocation (GDBusConnection *connection, es->object_path, es->user_data); - exists = _g_strv_has_string ((const gchar * const *) children, requested_node); + exists = g_strv_contains ((const gchar * const *) children, requested_node); g_strfreev (children); if (!exists) @@ -6930,7 +6914,7 @@ subtree_message_func (GDBusConnection *connection, * * When handling remote calls into any node in the subtree, first the * @enumerate function is used to check if the node exists. If the node exists - * or the #G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set + * or the %G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set * the @introspection function is used to check if the node supports the * requested method. If so, the @dispatch function is used to determine * where to dispatch the call. The collected #GDBusInterfaceVTable and @@ -6942,7 +6926,7 @@ subtree_message_func (GDBusConnection *connection, * of the thread you are calling this method from. * * If an existing subtree is already registered at @object_path or - * then @error is set to #G_IO_ERROR_EXISTS. + * then @error is set to %G_IO_ERROR_EXISTS. * * Note that it is valid to register regular objects (using * g_dbus_connection_register_object()) in a subtree registered with @@ -7450,7 +7434,9 @@ _g_bus_forget_singleton (GBusType bus_type) * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the * event that you need a private message bus connection, use * g_dbus_address_get_for_bus_sync() and - * g_dbus_connection_new_for_address(). + * g_dbus_connection_new_for_address() with + * G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT and + * G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION flags. * * Note that the returned #GDBusConnection object will (usually) have * the #GDBusConnection:exit-on-close property set to %TRUE. @@ -7569,7 +7555,9 @@ g_bus_get (GBusType bus_type, * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the * event that you need a private message bus connection, use * g_dbus_address_get_for_bus_sync() and - * g_dbus_connection_new_for_address(). + * g_dbus_connection_new_for_address() with + * G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT and + * G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION flags. * * Note that the returned #GDBusConnection object will (usually) have * the #GDBusConnection:exit-on-close property set to %TRUE. diff --git a/gio/gdbuserror.c b/gio/gdbuserror.c index b3ea28f5a..4cc542c7e 100644 --- a/gio/gdbuserror.c +++ b/gio/gdbuserror.c @@ -588,7 +588,7 @@ g_dbus_error_get_remote_error (const GError *error) * such that it can be recovered with g_dbus_error_get_remote_error(). * * Otherwise, a #GError with the error code %G_IO_ERROR_DBUS_ERROR - * in the #G_IO_ERROR error domain is returned. Also, @dbus_error_name is + * in the %G_IO_ERROR error domain is returned. Also, @dbus_error_name is * added to the error message such that it can be recovered with * g_dbus_error_get_remote_error(). * diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c index 3415ed613..ecef6cd3c 100644 --- a/gio/gdbusmessage.c +++ b/gio/gdbusmessage.c @@ -51,6 +51,7 @@ #include "gseekable.h" #include "gioerror.h" #include "gdbusprivate.h" +#include "gutilsprivate.h" #ifdef G_OS_UNIX #include "gunixfdlist.h" @@ -257,17 +258,6 @@ g_memory_buffer_read_uint64 (GMemoryBuffer *mbuf, #define MIN_ARRAY_SIZE 128 -static gsize -g_nearest_pow (gsize num) -{ - gsize n = 1; - - while (n < num && n > 0) - n <<= 1; - - return n; -} - static void array_resize (GMemoryBuffer *mbuf, gsize size) diff --git a/gio/gdbusmethodinvocation.c b/gio/gdbusmethodinvocation.c index c22e19ef0..705af079f 100644 --- a/gio/gdbusmethodinvocation.c +++ b/gio/gdbusmethodinvocation.c @@ -397,14 +397,7 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); if (g_dbus_message_get_flags (invocation->message) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED) - { - if (parameters != NULL) - { - g_variant_ref_sink (parameters); - g_variant_unref (parameters); - } - goto out; - } + goto out; if (parameters == NULL) parameters = g_variant_new_tuple (NULL, 0); @@ -420,7 +413,7 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio { gchar *type_string = g_variant_type_dup_string (type); - g_warning ("Type of return value is incorrect: expected '%s', got '%s''", + g_warning ("Type of return value is incorrect: expected '%s', got '%s'", type_string, g_variant_get_type_string (parameters)); g_variant_type_free (type); g_free (type_string); @@ -431,7 +424,9 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio /* property_info is only non-NULL if set that way from * GDBusConnection, so this must be the case of async property - * handling on either 'Get', 'Set' or 'GetAll'. + * handling on either 'Get' or 'Set'. + * + * property_info is NULL for 'GetAll'. */ if (invocation->property_info != NULL) { @@ -461,21 +456,6 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio g_variant_unref (nested); } - else if (g_str_equal (invocation->method_name, "GetAll")) - { - if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) - { - g_warning ("Type of return value for property 'GetAll' call should be '(a{sv})' but got '%s'", - g_variant_get_type_string (parameters)); - goto out; - } - - /* Could iterate the list of properties and make sure that all - * of them are actually on the interface and with the correct - * types, but let's not do that for now... - */ - } - else if (g_str_equal (invocation->method_name, "Set")) { if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE_UNIT)) @@ -489,6 +469,21 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio else g_assert_not_reached (); } + else if (g_str_equal (invocation->interface_name, "org.freedesktop.DBus.Properties") && + g_str_equal (invocation->method_name, "GetAll")) + { + if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) + { + g_warning ("Type of return value for property 'GetAll' call should be '(a{sv})' but got '%s'", + g_variant_get_type_string (parameters)); + goto out; + } + + /* Could iterate the list of properties and make sure that all + * of them are actually on the interface and with the correct + * types, but let's not do that for now... + */ + } if (G_UNLIKELY (_g_dbus_debug_return ())) { @@ -508,7 +503,7 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio } reply = g_dbus_message_new_method_reply (invocation->message); - g_dbus_message_set_body (reply, parameters); + g_dbus_message_set_body (reply, g_steal_pointer (¶meters)); #ifdef G_OS_UNIX if (fd_list != NULL) @@ -525,6 +520,12 @@ g_dbus_method_invocation_return_value_internal (GDBusMethodInvocation *invocatio g_object_unref (reply); out: + if (parameters != NULL) + { + g_variant_ref_sink (parameters); + g_variant_unref (parameters); + } + g_object_unref (invocation); } diff --git a/gio/gdbusobjectmanagerclient.c b/gio/gdbusobjectmanagerclient.c index 04c55995d..0d6f5e65c 100644 --- a/gio/gdbusobjectmanagerclient.c +++ b/gio/gdbusobjectmanagerclient.c @@ -1689,11 +1689,11 @@ remove_interfaces (GDBusObjectManagerClient *manager, op = g_hash_table_lookup (manager->priv->map_object_path_to_object_proxy, object_path); if (op == NULL) { - g_warning ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists", - G_STRLOC, - object_path); + g_debug ("%s: Processing InterfaceRemoved signal for path %s but no object proxy exists", + G_STRLOC, + object_path); g_mutex_unlock (&manager->priv->lock); - goto out; + return; } interfaces = g_dbus_object_get_interfaces (G_DBUS_OBJECT (op)); @@ -1730,8 +1730,6 @@ remove_interfaces (GDBusObjectManagerClient *manager, g_object_unref (op); } g_object_unref (manager); - out: - ; } static void diff --git a/gio/gdbusobjectmanagerserver.c b/gio/gdbusobjectmanagerserver.c index a68594765..0b875be30 100644 --- a/gio/gdbusobjectmanagerserver.c +++ b/gio/gdbusobjectmanagerserver.c @@ -457,6 +457,34 @@ registration_data_free (RegistrationData *data) g_free (data); } +/* Validate whether an object path is valid as a child of the manager. According + * to the specification: + * https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager + * this means that: + * > All returned object paths are children of the object path implementing this + * > interface, i.e. their object paths start with the ObjectManager's object + * > path plus '/' + * + * For example, if the manager is at `/org/gnome/Example`, children will be + * `/org/gnome/Example/(.+)`. + * + * It is permissible (but not encouraged) for the manager to be at `/`. If so, + * children will be `/(.+)`. + */ +static gboolean +is_valid_child_object_path (GDBusObjectManagerServer *manager, + const gchar *child_object_path) +{ + /* Historically GDBus accepted @child_object_paths at `/` if the @manager + * itself is also at `/". This is not spec-compliant, but making GDBus enforce + * the spec more strictly would be an incompatible change. + * + * See https://gitlab.gnome.org/GNOME/glib/-/issues/2500 */ + g_warn_if_fail (!g_str_equal (child_object_path, manager->priv->object_path_ending_in_slash)); + + return g_str_has_prefix (child_object_path, manager->priv->object_path_ending_in_slash); +} + /* ---------------------------------------------------------------------------------------------------- */ static void @@ -471,7 +499,7 @@ g_dbus_object_manager_server_export_unlocked (GDBusObjectManagerServer *manager g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager)); g_return_if_fail (G_IS_DBUS_OBJECT (object)); - g_return_if_fail (g_str_has_prefix (object_path, manager->priv->object_path_ending_in_slash)); + g_return_if_fail (is_valid_child_object_path (manager, object_path)); interface_names = g_ptr_array_new (); @@ -574,7 +602,7 @@ g_dbus_object_manager_server_export_uniquely (GDBusObjectManagerServer *manager, g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager)); g_return_if_fail (G_IS_DBUS_OBJECT (object)); - g_return_if_fail (g_str_has_prefix (orig_object_path, manager->priv->object_path_ending_in_slash)); + g_return_if_fail (is_valid_child_object_path (manager, orig_object_path)); g_mutex_lock (&manager->priv->lock); @@ -650,7 +678,7 @@ g_dbus_object_manager_server_unexport_unlocked (GDBusObjectManagerServer *manag g_return_val_if_fail (G_IS_DBUS_OBJECT_MANAGER_SERVER (manager), FALSE); g_return_val_if_fail (g_variant_is_object_path (object_path), FALSE); - g_return_val_if_fail (g_str_has_prefix (object_path, manager->priv->object_path_ending_in_slash), FALSE); + g_return_val_if_fail (is_valid_child_object_path (manager, object_path), FALSE); ret = FALSE; diff --git a/gio/gdbusprivate.c b/gio/gdbusprivate.c index fc58aea06..0b8630ab2 100644 --- a/gio/gdbusprivate.c +++ b/gio/gdbusprivate.c @@ -23,27 +23,28 @@ #include <stdlib.h> #include <string.h> -#include "giotypes.h" -#include "gioenumtypes.h" -#include "gsocket.h" #include "gdbusauthobserver.h" -#include "gdbusprivate.h" -#include "gdbusmessage.h" #include "gdbusconnection.h" -#include "gdbusproxy.h" +#include "gdbusdaemon.h" #include "gdbuserror.h" #include "gdbusintrospection.h" -#include "gdbusdaemon.h" -#include "giomodule-priv.h" -#include "gtask.h" +#include "gdbusmessage.h" +#include "gdbusprivate.h" +#include "gdbusproxy.h" #include "ginputstream.h" -#include "gmemoryinputstream.h" +#include "gioenumtypes.h" +#include "giomodule-priv.h" #include "giostream.h" +#include "giotypes.h" +#include "glib-private.h" #include "glib/gstdio.h" +#include "gmemoryinputstream.h" +#include "gsocket.h" #include "gsocketaddress.h" -#include "gsocketcontrolmessage.h" #include "gsocketconnection.h" +#include "gsocketcontrolmessage.h" #include "gsocketoutputstream.h" +#include "gtask.h" #ifdef G_OS_UNIX #include "gunixfdmessage.h" @@ -55,6 +56,7 @@ #include <windows.h> #include <io.h> #include <conio.h> +#include "gwin32sid.h" #endif #include "glibintl.h" @@ -2010,69 +2012,6 @@ _g_dbus_compute_complete_signature (GDBusArgInfo **args) #ifdef G_OS_WIN32 -extern BOOL WINAPI ConvertSidToStringSidA (PSID Sid, LPSTR *StringSid); - -gchar * -_g_dbus_win32_get_user_sid (void) -{ - HANDLE h; - TOKEN_USER *user; - DWORD token_information_len; - PSID psid; - gchar *sid; - gchar *ret; - - ret = NULL; - user = NULL; - h = INVALID_HANDLE_VALUE; - - if (!OpenProcessToken (GetCurrentProcess (), TOKEN_QUERY, &h)) - { - g_warning ("OpenProcessToken failed with error code %d", (gint) GetLastError ()); - goto out; - } - - /* Get length of buffer */ - token_information_len = 0; - if (!GetTokenInformation (h, TokenUser, NULL, 0, &token_information_len)) - { - if (GetLastError () != ERROR_INSUFFICIENT_BUFFER) - { - g_warning ("GetTokenInformation() failed with error code %d", (gint) GetLastError ()); - goto out; - } - } - user = g_malloc (token_information_len); - if (!GetTokenInformation (h, TokenUser, user, token_information_len, &token_information_len)) - { - g_warning ("GetTokenInformation() failed with error code %d", (gint) GetLastError ()); - goto out; - } - - psid = user->User.Sid; - if (!IsValidSid (psid)) - { - g_warning ("Invalid SID"); - goto out; - } - - if (!ConvertSidToStringSidA (psid, &sid)) - { - g_warning ("Invalid SID"); - goto out; - } - - ret = g_strdup (sid); - LocalFree (sid); - -out: - g_free (user); - if (h != INVALID_HANDLE_VALUE) - CloseHandle (h); - return ret; -} - - #define DBUS_DAEMON_ADDRESS_INFO "DBusDaemonAddressInfo" #define DBUS_DAEMON_MUTEX "DBusDaemonMutex" #define UNIQUE_DBUS_INIT_MUTEX "UniqueDBusInitMutex" @@ -2337,6 +2276,26 @@ g_win32_run_session_bus (void* hwnd, void* hinst, const char* cmdline, int cmdsh static gboolean autolaunch_binary_absent = FALSE; +static wchar_t * +find_dbus_process_path (void) +{ + wchar_t *dbus_path; + gchar *exe_path = GLIB_PRIVATE_CALL (g_win32_find_helper_executable_path) ("gdbus.exe", _g_io_win32_get_module ()); + dbus_path = g_utf8_to_utf16 (exe_path, -1, NULL, NULL, NULL); + g_free (exe_path); + + if (dbus_path == NULL) + return NULL; + + if (GetFileAttributesW (dbus_path) == INVALID_FILE_ATTRIBUTES) + { + g_free (dbus_path); + return NULL; + } + + return dbus_path; +} + gchar * _g_dbus_win32_get_session_address_dbus_launch (GError **error) { @@ -2354,61 +2313,53 @@ _g_dbus_win32_get_session_address_dbus_launch (GError **error) if (address == NULL && !autolaunch_binary_absent) { - wchar_t gio_path[MAX_PATH + 2] = { 0 }; - int gio_path_len = GetModuleFileNameW (_g_io_win32_get_module (), gio_path, MAX_PATH + 1); - - /* The <= MAX_PATH check prevents truncated path usage */ - if (gio_path_len > 0 && gio_path_len <= MAX_PATH) - { - PROCESS_INFORMATION pi = { 0 }; - STARTUPINFOW si = { 0 }; - BOOL res = FALSE; - wchar_t exe_path[MAX_PATH + 100] = { 0 }; - /* calculate index of first char of dll file name inside full path */ - int gio_name_index = gio_path_len; - for (; gio_name_index > 0; --gio_name_index) - { - wchar_t prev_char = gio_path[gio_name_index - 1]; - if (prev_char == L'\\' || prev_char == L'/') - break; - } - gio_path[gio_name_index] = L'\0'; - wcscpy (exe_path, gio_path); - wcscat (exe_path, L"\\gdbus.exe"); - - if (GetFileAttributesW (exe_path) == INVALID_FILE_ATTRIBUTES) - { - /* warning won't be raised another time - * since autolaunch_binary_absent would be already set. - */ - autolaunch_binary_absent = TRUE; - g_warning ("win32 session dbus binary not found: %S", exe_path ); - } - else - { - wchar_t args[MAX_PATH*2 + 100] = { 0 }; - wcscpy (args, L"\""); - wcscat (args, exe_path); - wcscat (args, L"\" "); + wchar_t *dbus_path = find_dbus_process_path (); + if (dbus_path == NULL) + { + /* warning won't be raised another time + * since autolaunch_binary_absent would be already set. + */ + autolaunch_binary_absent = TRUE; + g_warning ("win32 session dbus binary not found"); + } + else + { + PROCESS_INFORMATION pi = { 0 }; + STARTUPINFOW si = { 0 }; + BOOL res = FALSE; + wchar_t args[MAX_PATH * 2 + 100] = { 0 }; + wchar_t working_dir[MAX_PATH + 2] = { 0 }; + wchar_t *p; + + wcscpy (working_dir, dbus_path); + p = wcsrchr (working_dir, L'\\'); + if (p != NULL) + *p = L'\0'; + + wcscpy (args, L"\""); + wcscat (args, dbus_path); + wcscat (args, L"\" "); #define _L_PREFIX_FOR_EXPANDED(arg) L##arg #define _L_PREFIX(arg) _L_PREFIX_FOR_EXPANDED (arg) - wcscat (args, _L_PREFIX (_GDBUS_ARG_WIN32_RUN_SESSION_BUS)); + wcscat (args, _L_PREFIX (_GDBUS_ARG_WIN32_RUN_SESSION_BUS)); #undef _L_PREFIX #undef _L_PREFIX_FOR_EXPANDED - res = CreateProcessW (exe_path, args, - 0, 0, FALSE, - NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | DETACHED_PROCESS, - 0, gio_path, - &si, &pi); - } - if (res) - { - address = read_shm (DBUS_DAEMON_ADDRESS_INFO); - if (address == NULL) - g_warning ("%S dbus binary failed to launch bus, maybe incompatible version", exe_path ); - } - } + res = CreateProcessW (dbus_path, args, + 0, 0, FALSE, + NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | DETACHED_PROCESS, + 0, working_dir, + &si, &pi); + + if (res) + { + address = read_shm (DBUS_DAEMON_ADDRESS_INFO); + if (address == NULL) + g_warning ("%S dbus binary failed to launch bus, maybe incompatible version", dbus_path); + } + + g_free (dbus_path); + } } release_mutex (autolaunch_mutex); diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c index 7a2289bd4..c095bc5fa 100644 --- a/gio/gdbusproxy.c +++ b/gio/gdbusproxy.c @@ -593,11 +593,15 @@ g_dbus_proxy_class_init (GDBusProxyClass *klass) * * Emitted when a signal from the remote object and interface that @proxy is for, has been received. * + * Since 2.72 this signal supports detailed connections. You can connect to + * the detailed signal `g-signal::x` in order to receive callbacks only when + * signal `x` is received from the remote object. + * * Since: 2.26 */ signals[SIGNAL_SIGNAL] = g_signal_new (I_("g-signal"), G_TYPE_DBUS_PROXY, - G_SIGNAL_RUN_LAST | G_SIGNAL_MUST_COLLECT, + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED | G_SIGNAL_MUST_COLLECT, G_STRUCT_OFFSET (GDBusProxyClass, g_signal), NULL, NULL, @@ -890,7 +894,7 @@ on_signal_received (GDBusConnection *connection, g_signal_emit (proxy, signals[SIGNAL_SIGNAL], - 0, + g_quark_try_string (signal_name), sender_name, signal_name, parameters); @@ -1694,6 +1698,10 @@ static void async_initable_init_first (GAsyncInitable *initable) { GDBusProxy *proxy = G_DBUS_PROXY (initable); + GDBusSignalFlags signal_flags = G_DBUS_SIGNAL_FLAGS_NONE; + + if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_NO_MATCH_RULE) + signal_flags |= G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE; if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)) { @@ -1705,7 +1713,7 @@ async_initable_init_first (GAsyncInitable *initable) "PropertiesChanged", proxy->priv->object_path, proxy->priv->interface_name, - G_DBUS_SIGNAL_FLAGS_NONE, + signal_flags, on_properties_changed, weak_ref_new (G_OBJECT (proxy)), (GDestroyNotify) weak_ref_free); @@ -1721,7 +1729,7 @@ async_initable_init_first (GAsyncInitable *initable) NULL, /* member */ proxy->priv->object_path, NULL, /* arg0 */ - G_DBUS_SIGNAL_FLAGS_NONE, + signal_flags, on_signal_received, weak_ref_new (G_OBJECT (proxy)), (GDestroyNotify) weak_ref_free); @@ -1737,7 +1745,7 @@ async_initable_init_first (GAsyncInitable *initable) "NameOwnerChanged", /* signal name */ "/org/freedesktop/DBus", /* path */ proxy->priv->name, /* arg0 */ - G_DBUS_SIGNAL_FLAGS_NONE, + signal_flags, on_name_owner_changed, weak_ref_new (G_OBJECT (proxy)), (GDestroyNotify) weak_ref_free); diff --git a/gio/gdbusutils.c b/gio/gdbusutils.c index f12e86204..112c24e22 100644 --- a/gio/gdbusutils.c +++ b/gio/gdbusutils.c @@ -537,23 +537,23 @@ g_dbus_gvariant_to_gvalue (GVariant *value, * * The conversion is using the following rules: * - * - #G_TYPE_STRING: 's', 'o', 'g' or 'ay' - * - #G_TYPE_STRV: 'as', 'ao' or 'aay' - * - #G_TYPE_BOOLEAN: 'b' - * - #G_TYPE_UCHAR: 'y' - * - #G_TYPE_INT: 'i', 'n' - * - #G_TYPE_UINT: 'u', 'q' - * - #G_TYPE_INT64 'x' - * - #G_TYPE_UINT64: 't' - * - #G_TYPE_DOUBLE: 'd' - * - #G_TYPE_VARIANT: Any #GVariantType - * - * This can fail if e.g. @gvalue is of type #G_TYPE_STRING and @type - * is ['i'][G-VARIANT-TYPE-INT32:CAPS]. It will also fail for any #GType - * (including e.g. #G_TYPE_OBJECT and #G_TYPE_BOXED derived-types) not + * - `G_TYPE_STRING`: 's', 'o', 'g' or 'ay' + * - `G_TYPE_STRV`: 'as', 'ao' or 'aay' + * - `G_TYPE_BOOLEAN`: 'b' + * - `G_TYPE_UCHAR`: 'y' + * - `G_TYPE_INT`: 'i', 'n' + * - `G_TYPE_UINT`: 'u', 'q' + * - `G_TYPE_INT64`: 'x' + * - `G_TYPE_UINT64`: 't' + * - `G_TYPE_DOUBLE`: 'd' + * - `G_TYPE_VARIANT`: Any #GVariantType + * + * This can fail if e.g. @gvalue is of type %G_TYPE_STRING and @type + * is 'i', i.e. %G_VARIANT_TYPE_INT32. It will also fail for any #GType + * (including e.g. %G_TYPE_OBJECT and %G_TYPE_BOXED derived-types) not * in the table above. * - * Note that if @gvalue is of type #G_TYPE_VARIANT and its value is + * Note that if @gvalue is of type %G_TYPE_VARIANT and its value is * %NULL, the empty #GVariant instance (never %NULL) for @type is * returned (e.g. 0 for scalar types, the empty string for string types, * '/' for object path types, the empty array for any array type and so on). diff --git a/gio/gdebugcontroller.c b/gio/gdebugcontroller.c new file mode 100644 index 000000000..c61561621 --- /dev/null +++ b/gio/gdebugcontroller.c @@ -0,0 +1,119 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2021 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include "glib.h" +#include "glibintl.h" + +#include "gdebugcontroller.h" +#include "ginitable.h" +#include "giomodule-priv.h" + +/** + * SECTION:gdebugcontroller + * @title: GDebugController + * @short_description: Debugging controller + * @include: gio/gio.h + * + * #GDebugController is an interface to expose control of debugging features and + * debug output. + * + * It is implemented on Linux using #GDebugControllerDBus, which exposes a D-Bus + * interface to allow authenticated peers to control debug features in this + * process. + * + * Whether debug output is enabled is exposed as + * #GDebugController:debug-enabled. This controls g_log_set_debug_enabled() by + * default. Application code may connect to the #GObject::notify signal for it + * to control other parts of its debug infrastructure as necessary. + * + * If your application or service is using the default GLib log writer function, + * creating one of the built-in implementations of #GDebugController should be + * all that’s needed to dynamically enable or disable debug output. + * + * Since: 2.72 + */ + +G_DEFINE_INTERFACE_WITH_CODE (GDebugController, g_debug_controller, G_TYPE_OBJECT, + g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_INITABLE)) + +static void +g_debug_controller_default_init (GDebugControllerInterface *iface) +{ + /** + * GDebugController:debug-enabled: + * + * %TRUE if debug output should be exposed (for example by forwarding it to + * the journal), %FALSE otherwise. + * + * Since: 2.72 + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("debug-enabled", + "Debug Enabled", + "Whether to expose debug output", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY)); +} + +/** + * g_debug_controller_get_debug_enabled: + * @self: a #GDebugController + * + * Get the value of #GDebugController:debug-enabled. + * + * Returns: %TRUE if debug output should be exposed, %FALSE otherwise + * Since: 2.72 + */ +gboolean +g_debug_controller_get_debug_enabled (GDebugController *self) +{ + gboolean enabled; + + g_return_val_if_fail (G_IS_DEBUG_CONTROLLER (self), FALSE); + + g_object_get (G_OBJECT (self), + "debug-enabled", &enabled, + NULL); + + return enabled; +} + +/** + * g_debug_controller_set_debug_enabled: + * @self: a #GDebugController + * @debug_enabled: %TRUE if debug output should be exposed, %FALSE otherwise + * + * Set the value of #GDebugController:debug-enabled. + * + * Since: 2.72 + */ +void +g_debug_controller_set_debug_enabled (GDebugController *self, + gboolean debug_enabled) +{ + g_return_if_fail (G_IS_DEBUG_CONTROLLER (self)); + + g_object_set (G_OBJECT (self), + "debug-enabled", debug_enabled, + NULL); +} diff --git a/gio/gdebugcontroller.h b/gio/gdebugcontroller.h new file mode 100644 index 000000000..ca3a2d29d --- /dev/null +++ b/gio/gdebugcontroller.h @@ -0,0 +1,79 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2021 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __G_DEBUG_CONTROLLER_H__ +#define __G_DEBUG_CONTROLLER_H__ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only <gio/gio.h> can be included directly." +#endif + +#include <gio/giotypes.h> + +G_BEGIN_DECLS + +/** + * G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME: + * + * Extension point for debug control functionality. + * See [Extending GIO][extending-gio]. + * + * Since: 2.72 + */ +#define G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME "gio-debug-controller" + +/** + * GDebugController: + * + * #GDebugController is an interface to expose control of debugging features and + * debug output. + * + * Since: 2.72 + */ +#define G_TYPE_DEBUG_CONTROLLER (g_debug_controller_get_type ()) +GLIB_AVAILABLE_IN_2_72 +G_DECLARE_INTERFACE(GDebugController, g_debug_controller, g, debug_controller, GObject) + +#define G_DEBUG_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DEBUG_CONTROLLER, GDebugController)) +#define G_IS_DEBUG_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DEBUG_CONTROLLER)) +#define G_DEBUG_CONTROLLER_GET_INTERFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_DEBUG_CONTROLLER, GDebugControllerInterface)) + +/** + * GDebugControllerInterface: + * @g_iface: The parent interface. + * + * The virtual function table for #GDebugController. + * + * Since: 2.72 + */ +struct _GDebugControllerInterface { + /*< private >*/ + GTypeInterface g_iface; +}; + +GLIB_AVAILABLE_IN_2_72 +gboolean g_debug_controller_get_debug_enabled (GDebugController *self); +GLIB_AVAILABLE_IN_2_72 +void g_debug_controller_set_debug_enabled (GDebugController *self, + gboolean debug_enabled); + +G_END_DECLS + +#endif /* __G_DEBUG_CONTROLLER_H__ */ diff --git a/gio/gdebugcontrollerdbus.c b/gio/gdebugcontrollerdbus.c new file mode 100644 index 000000000..3c0ee007e --- /dev/null +++ b/gio/gdebugcontrollerdbus.c @@ -0,0 +1,709 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2021 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include <gio/gio.h> +#include "gdebugcontroller.h" +#include "gdebugcontrollerdbus.h" +#include "giomodule-priv.h" +#include "gi18n.h" +#include "gio/gdbusprivate.h" +#include "gio/gmarshal-internal.h" + +/** + * SECTION:gdebugcontrollerdbus + * @title: GDebugControllerDBus + * @short_description: Debugging controller D-Bus implementation + * @include: gio/gio.h + * + * #GDebugControllerDBus is an implementation of #GDebugController which exposes + * debug settings as a D-Bus object. + * + * It is a #GInitable object, and will register an object at + * `/org/gtk/Debugging` on the bus given as + * #GDebugControllerDBus:connection once it’s initialized. The object will be + * unregistered when the last reference to the #GDebugControllerDBus is dropped. + * + * This D-Bus object can be used by remote processes to enable or disable debug + * output in this process. Remote processes calling + * `org.gtk.Debugging.SetDebugEnabled()` will affect the value of + * #GDebugController:debug-enabled and, by default, g_log_get_debug_enabled(). + * default. + * + * By default, all processes will be able to call `SetDebugEnabled()`. If this + * process is privileged, or might expose sensitive information in its debug + * output, you may want to restrict the ability to enable debug output to + * privileged users or processes. + * + * One option is to install a D-Bus security policy which restricts access to + * `SetDebugEnabled()`, installing something like the following in + * `$datadir/dbus-1/system.d/`: + * |[<!-- language="XML" --> + * <?xml version="1.0"?> <!--*-nxml-*--> + * <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + * "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + * <busconfig> + * <policy user="root"> + * <allow send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/> + * </policy> + * <policy context="default"> + * <deny send_destination="com.example.MyService" send_interface="org.gtk.Debugging"/> + * </policy> + * </busconfig> + * ]| + * + * This will prevent the `SetDebugEnabled()` method from being called by all + * except root. It will not prevent the `DebugEnabled` property from being read, + * as it’s accessed through the `org.freedesktop.DBus.Properties` interface. + * + * Another option is to use polkit to allow or deny requests on a case-by-case + * basis, allowing for the possibility of dynamic authorisation. To do this, + * connect to the #GDebugControllerDBus::authorize signal and query polkit in + * it: + * |[<!-- language="C" --> + * g_autoptr(GError) child_error = NULL; + * g_autoptr(GDBusConnection) connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + * gulong debug_controller_authorize_id = 0; + * + * // Set up the debug controller. + * debug_controller = G_DEBUG_CONTROLLER (g_debug_controller_dbus_new (priv->connection, NULL, &child_error)); + * if (debug_controller == NULL) + * { + * g_error ("Could not register debug controller on bus: %s"), + * child_error->message); + * } + * + * debug_controller_authorize_id = g_signal_connect (debug_controller, + * "authorize", + * G_CALLBACK (debug_controller_authorize_cb), + * self); + * + * static gboolean + * debug_controller_authorize_cb (GDebugControllerDBus *debug_controller, + * GDBusMethodInvocation *invocation, + * gpointer user_data) + * { + * g_autoptr(PolkitAuthority) authority = NULL; + * g_autoptr(PolkitSubject) subject = NULL; + * g_autoptr(PolkitAuthorizationResult) auth_result = NULL; + * g_autoptr(GError) local_error = NULL; + * GDBusMessage *message; + * GDBusMessageFlags message_flags; + * PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; + * + * message = g_dbus_method_invocation_get_message (invocation); + * message_flags = g_dbus_message_get_flags (message); + * + * authority = polkit_authority_get_sync (NULL, &local_error); + * if (authority == NULL) + * { + * g_warning ("Failed to get polkit authority: %s", local_error->message); + * return FALSE; + * } + * + * if (message_flags & G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) + * flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; + * + * subject = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation)); + * + * auth_result = polkit_authority_check_authorization_sync (authority, + * subject, + * "com.example.MyService.set-debug-enabled", + * NULL, + * flags, + * NULL, + * &local_error); + * if (auth_result == NULL) + * { + * g_warning ("Failed to get check polkit authorization: %s", local_error->message); + * return FALSE; + * } + * + * return polkit_authorization_result_get_is_authorized (auth_result); + * } + * ]| + * + * Since: 2.72 + */ + +static const gchar org_gtk_Debugging_xml[] = + "<node>" + "<interface name='org.gtk.Debugging'>" + "<property name='DebugEnabled' type='b' access='read'/>" + "<method name='SetDebugEnabled'>" + "<arg type='b' name='debug-enabled' direction='in'/>" + "</method>" + "</interface>" + "</node>"; + +static GDBusInterfaceInfo *org_gtk_Debugging; + +#define G_DEBUG_CONTROLLER_DBUS_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) + +static void g_debug_controller_dbus_iface_init (GDebugControllerInterface *iface); +static void g_debug_controller_dbus_initable_iface_init (GInitableIface *iface); +static gboolean g_debug_controller_dbus_authorize_default (GDebugControllerDBus *self, + GDBusMethodInvocation *invocation); + +typedef enum +{ + PROP_CONNECTION = 1, + /* Overrides: */ + PROP_DEBUG_ENABLED, +} GDebugControllerDBusProperty; + +static GParamSpec *props[PROP_CONNECTION + 1] = { NULL, }; + +typedef enum +{ + SIGNAL_AUTHORIZE, +} GDebugControllerDBusSignal; + +static guint signals[SIGNAL_AUTHORIZE + 1] = {0}; + +typedef struct +{ + GObject parent_instance; + + GCancellable *cancellable; /* (owned) */ + GDBusConnection *connection; /* (owned) */ + guint object_id; + GPtrArray *pending_authorize_tasks; /* (element-type GWeakRef) (owned) (nullable) */ + + gboolean debug_enabled; +} GDebugControllerDBusPrivate; + +G_DEFINE_TYPE_WITH_CODE (GDebugControllerDBus, g_debug_controller_dbus, G_TYPE_OBJECT, + G_ADD_PRIVATE (GDebugControllerDBus) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_debug_controller_dbus_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_DEBUG_CONTROLLER, + g_debug_controller_dbus_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME, + g_define_type_id, + "dbus", + 30)) + +static void +g_debug_controller_dbus_init (GDebugControllerDBus *self) +{ + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + priv->cancellable = g_cancellable_new (); +} + +static void +set_debug_enabled (GDebugControllerDBus *self, + gboolean debug_enabled) +{ + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + if (g_cancellable_is_cancelled (priv->cancellable)) + return; + + if (debug_enabled != priv->debug_enabled) + { + GVariantBuilder builder; + + priv->debug_enabled = debug_enabled; + + /* Change the default log writer’s behaviour in GLib. */ + g_log_set_debug_enabled (debug_enabled); + + /* Notify internally and externally of the property change. */ + g_object_notify (G_OBJECT (self), "debug-enabled"); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "DebugEnabled", g_variant_new_boolean (priv->debug_enabled)); + + g_dbus_connection_emit_signal (priv->connection, + NULL, + "/org/gtk/Debugging", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + "org.gtk.Debugging", + &builder, + NULL), + NULL); + + g_debug ("Debug output %s", debug_enabled ? "enabled" : "disabled"); + } +} + +/* Called in the #GMainContext which was default when the #GDebugControllerDBus + * was initialised. */ +static GVariant * +dbus_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GDebugControllerDBus *self = user_data; + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + if (g_str_equal (property_name, "DebugEnabled")) + return g_variant_new_boolean (priv->debug_enabled); + + g_assert_not_reached (); + + return NULL; +} + +static GWeakRef * +weak_ref_new (GObject *obj) +{ + GWeakRef *weak_ref = g_new0 (GWeakRef, 1); + + g_weak_ref_init (weak_ref, obj); + + return g_steal_pointer (&weak_ref); +} + +static void +weak_ref_free (GWeakRef *weak_ref) +{ + g_weak_ref_clear (weak_ref); + g_free (weak_ref); +} + +/* Called in the #GMainContext which was default when the #GDebugControllerDBus + * was initialised. */ +static void +garbage_collect_weak_refs (GDebugControllerDBus *self) +{ + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + guint i; + + if (priv->pending_authorize_tasks == NULL) + return; + + /* Iterate in reverse order so that if we remove an element the hole won’t be + * filled by an element we haven’t checked yet. */ + for (i = priv->pending_authorize_tasks->len; i > 0; i--) + { + GWeakRef *weak_ref = g_ptr_array_index (priv->pending_authorize_tasks, i - 1); + GObject *obj = g_weak_ref_get (weak_ref); + + if (obj == NULL) + g_ptr_array_remove_index_fast (priv->pending_authorize_tasks, i - 1); + else + g_object_unref (obj); + } + + /* Don’t need to keep the array around any more if it’s empty. */ + if (priv->pending_authorize_tasks->len == 0) + g_clear_pointer (&priv->pending_authorize_tasks, g_ptr_array_unref); +} + +/* Called in a worker thread. */ +static void +authorize_task_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (source_object); + GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (task_data); + gboolean authorized = TRUE; + + g_signal_emit (self, signals[SIGNAL_AUTHORIZE], 0, invocation, &authorized); + + g_task_return_boolean (task, authorized); +} + +/* Called in the #GMainContext which was default when the #GDebugControllerDBus + * was initialised. */ +static void +authorize_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); + GDebugControllerDBusPrivate *priv G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; + GTask *task = G_TASK (result); + GDBusMethodInvocation *invocation = g_task_get_task_data (task); + GVariant *parameters = g_dbus_method_invocation_get_parameters (invocation); + gboolean enabled = FALSE; + gboolean authorized; + + priv = g_debug_controller_dbus_get_instance_private (self); + authorized = g_task_propagate_boolean (task, NULL); + + if (!authorized) + { + GError *local_error = g_error_new (G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, + _("Not authorized to change debug settings")); + g_dbus_method_invocation_take_error (invocation, g_steal_pointer (&local_error)); + } + else + { + /* Update the property value. */ + g_variant_get (parameters, "(b)", &enabled); + set_debug_enabled (self, enabled); + + g_dbus_method_invocation_return_value (invocation, NULL); + } + + /* The GTask will stay alive for a bit longer as the worker thread is + * potentially still in the process of dropping its reference to it. */ + g_assert (priv->pending_authorize_tasks != NULL && priv->pending_authorize_tasks->len > 0); +} + +/* Called in the #GMainContext which was default when the #GDebugControllerDBus + * was initialised. */ +static void +dbus_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GDebugControllerDBus *self = user_data; + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + GDebugControllerDBusClass *klass = G_DEBUG_CONTROLLER_DBUS_GET_CLASS (self); + + /* Only on the org.gtk.Debugging interface */ + if (g_str_equal (method_name, "SetDebugEnabled")) + { + GTask *task = NULL; + + task = g_task_new (self, priv->cancellable, authorize_cb, NULL); + g_task_set_source_tag (task, dbus_method_call); + g_task_set_task_data (task, g_object_ref (invocation), (GDestroyNotify) g_object_unref); + + /* Track the pending #GTask with a weak ref as its final strong ref could + * be dropped from this thread or an arbitrary #GTask worker thread. The + * weak refs will be evaluated in g_debug_controller_dbus_stop(). */ + if (priv->pending_authorize_tasks == NULL) + priv->pending_authorize_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) weak_ref_free); + g_ptr_array_add (priv->pending_authorize_tasks, weak_ref_new (G_OBJECT (task))); + + /* Take the opportunity to clean up a bit. */ + garbage_collect_weak_refs (self); + + /* Check the calling peer is authorised to change the debug mode. So that + * the signal handler can block on checking polkit authorisation (which + * definitely involves D-Bus calls, and might involve user interaction), + * emit the #GDebugControllerDBus::authorize signal in a worker thread, so + * that handlers can synchronously block it. This is similar to how + * #GDBusInterfaceSkeleton::g-authorize-method works. + * + * If no signal handlers are connected, don’t bother running the worker + * thread, and just return a default value of %FALSE. Fail closed. */ + if (g_signal_has_handler_pending (self, signals[SIGNAL_AUTHORIZE], 0, FALSE) || + klass->authorize != g_debug_controller_dbus_authorize_default) + g_task_run_in_thread (task, authorize_task_cb); + else + g_task_return_boolean (task, FALSE); + + g_clear_object (&task); + } + else + g_assert_not_reached (); +} + +static gboolean +g_debug_controller_dbus_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (initable); + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + static const GDBusInterfaceVTable vtable = { + dbus_method_call, + dbus_get_property, + NULL /* set_property */, + { 0 } + }; + + if (org_gtk_Debugging == NULL) + { + GError *local_error = NULL; + GDBusNodeInfo *info; + + info = g_dbus_node_info_new_for_xml (org_gtk_Debugging_xml, &local_error); + if G_UNLIKELY (info == NULL) + g_error ("%s", local_error->message); + org_gtk_Debugging = g_dbus_node_info_lookup_interface (info, "org.gtk.Debugging"); + g_assert (org_gtk_Debugging != NULL); + g_dbus_interface_info_ref (org_gtk_Debugging); + g_dbus_node_info_unref (info); + } + + priv->object_id = g_dbus_connection_register_object (priv->connection, + "/org/gtk/Debugging", + org_gtk_Debugging, + &vtable, self, NULL, error); + if (priv->object_id == 0) + return FALSE; + + return TRUE; +} + +static void +g_debug_controller_dbus_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + switch ((GDebugControllerDBusProperty) prop_id) + { + case PROP_CONNECTION: + g_value_set_object (value, priv->connection); + break; + case PROP_DEBUG_ENABLED: + g_value_set_boolean (value, priv->debug_enabled); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_debug_controller_dbus_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + switch ((GDebugControllerDBusProperty) prop_id) + { + case PROP_CONNECTION: + /* Construct only */ + g_assert (priv->connection == NULL); + priv->connection = g_value_dup_object (value); + break; + case PROP_DEBUG_ENABLED: + set_debug_enabled (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +g_debug_controller_dbus_dispose (GObject *object) +{ + GDebugControllerDBus *self = G_DEBUG_CONTROLLER_DBUS (object); + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + g_debug_controller_dbus_stop (self); + g_assert (priv->pending_authorize_tasks == NULL); + g_clear_object (&priv->connection); + g_clear_object (&priv->cancellable); + + G_OBJECT_CLASS (g_debug_controller_dbus_parent_class)->dispose (object); +} + +static gboolean +g_debug_controller_dbus_authorize_default (GDebugControllerDBus *self, + GDBusMethodInvocation *invocation) +{ + return TRUE; +} + +static void +g_debug_controller_dbus_class_init (GDebugControllerDBusClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = g_debug_controller_dbus_get_property; + gobject_class->set_property = g_debug_controller_dbus_set_property; + gobject_class->dispose = g_debug_controller_dbus_dispose; + + klass->authorize = g_debug_controller_dbus_authorize_default; + + /** + * GDebugControllerDBus:connection: + * + * The D-Bus connection to expose the debugging interface on. + * + * Typically this will be the same connection (to the system or session bus) + * which the rest of the application or service’s D-Bus objects are registered + * on. + * + * Since: 2.72 + */ + props[PROP_CONNECTION] = + g_param_spec_object ("connection", "D-Bus Connection", + "The D-Bus connection to expose the debugging interface on.", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, G_N_ELEMENTS (props), props); + + g_object_class_override_property (gobject_class, PROP_DEBUG_ENABLED, "debug-enabled"); + + /** + * GDebugControllerDBus::authorize: + * @controller: The #GDebugControllerDBus emitting the signal. + * @invocation: A #GDBusMethodInvocation. + * + * Emitted when a D-Bus peer is trying to change the debug settings and used + * to determine if that is authorized. + * + * This signal is emitted in a dedicated worker thread, so handlers are + * allowed to perform blocking I/O. This means that, for example, it is + * appropriate to call `polkit_authority_check_authorization_sync()` to check + * authorization using polkit. + * + * If %FALSE is returned then no further handlers are run and the request to + * change the debug settings is rejected. + * + * Otherwise, if %TRUE is returned, signal emission continues. If no handlers + * return %FALSE, then the debug settings are allowed to be changed. + * + * Signal handlers must not modify @invocation, or cause it to return a value. + * + * The default class handler just returns %TRUE. + * + * Returns: %TRUE if the call is authorized, %FALSE otherwise. + * + * Since: 2.72 + */ + signals[SIGNAL_AUTHORIZE] = + g_signal_new ("authorize", + G_TYPE_DEBUG_CONTROLLER_DBUS, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GDebugControllerDBusClass, authorize), + _g_signal_accumulator_false_handled, + NULL, + _g_cclosure_marshal_BOOLEAN__OBJECT, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_METHOD_INVOCATION); + g_signal_set_va_marshaller (signals[SIGNAL_AUTHORIZE], + G_TYPE_FROM_CLASS (klass), + _g_cclosure_marshal_BOOLEAN__OBJECTv); +} + +static void +g_debug_controller_dbus_iface_init (GDebugControllerInterface *iface) +{ +} + +static void +g_debug_controller_dbus_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_debug_controller_dbus_initable_init; +} + +/** + * g_debug_controller_dbus_new: + * @connection: a #GDBusConnection to register the debug object on + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Create a new #GDebugControllerDBus and synchronously initialize it. + * + * Initializing the object will export the debug object on @connection. The + * object will remain registered until the last reference to the + * #GDebugControllerDBus is dropped. + * + * Initialization may fail if registering the object on @connection fails. + * + * Returns: (nullable) (transfer full): a new #GDebugControllerDBus, or %NULL + * on failure + * Since: 2.72 + */ +GDebugControllerDBus * +g_debug_controller_dbus_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return g_initable_new (G_TYPE_DEBUG_CONTROLLER_DBUS, + cancellable, + error, + "connection", connection, + NULL); +} + +/** + * g_debug_controller_dbus_stop: + * @self: a #GDebugControllerDBus + * + * Stop the debug controller, unregistering its object from the bus. + * + * Any pending method calls to the object will complete successfully, but new + * ones will return an error. This method will block until all pending + * #GDebugControllerDBus::authorize signals have been handled. This is expected + * to not take long, as it will just be waiting for threads to join. If any + * #GDebugControllerDBus::authorize signal handlers are still executing in other + * threads, this will block until after they have returned. + * + * This method will be called automatically when the final reference to the + * #GDebugControllerDBus is dropped. You may want to call it explicitly to know + * when the controller has been fully removed from the bus, or to break + * reference count cycles. + * + * Calling this method from within a #GDebugControllerDBus::authorize signal + * handler will cause a deadlock and must not be done. + * + * Since: 2.72 + */ +void +g_debug_controller_dbus_stop (GDebugControllerDBus *self) +{ + GDebugControllerDBusPrivate *priv = g_debug_controller_dbus_get_instance_private (self); + + g_cancellable_cancel (priv->cancellable); + + if (priv->object_id != 0) + { + g_dbus_connection_unregister_object (priv->connection, priv->object_id); + priv->object_id = 0; + } + + /* Wait for any pending authorize tasks to finish. These will just be waiting + * for threads to join at this point, as the D-Bus object has been + * unregistered and the cancellable cancelled. + * + * The loop will never terminate if g_debug_controller_dbus_stop() is + * called from within an ::authorize callback. + * + * See discussion in https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2486 */ + while (priv->pending_authorize_tasks != NULL) + { + garbage_collect_weak_refs (self); + g_thread_yield (); + } +} diff --git a/gio/gdebugcontrollerdbus.h b/gio/gdebugcontrollerdbus.h new file mode 100644 index 000000000..5e54bbfa1 --- /dev/null +++ b/gio/gdebugcontrollerdbus.h @@ -0,0 +1,69 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2021 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __G_DEBUG_CONTROLLER_DBUS_H__ +#define __G_DEBUG_CONTROLLER_DBUS_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +/** + * GDebugControllerDBus: + * + * #GDebugControllerDBus is an implementation of #GDebugController over D-Bus. + * + * Since: 2.72 + */ +#define G_TYPE_DEBUG_CONTROLLER_DBUS (g_debug_controller_dbus_get_type ()) +GLIB_AVAILABLE_IN_2_72 +G_DECLARE_DERIVABLE_TYPE (GDebugControllerDBus, g_debug_controller_dbus, G, DEBUG_CONTROLLER_DBUS, GObject) + +/** + * GDebugControllerDBusClass: + * @parent_class: The parent class. + * @authorize: Default handler for the #GDebugControllerDBus::authorize signal. + * + * The virtual function table for #GDebugControllerDBus. + * + * Since: 2.72 + */ +struct _GDebugControllerDBusClass +{ + GObjectClass parent_class; + + gboolean (*authorize) (GDebugControllerDBus *controller, + GDBusMethodInvocation *invocation); + + gpointer padding[12]; +}; + +GLIB_AVAILABLE_IN_2_72 +GDebugControllerDBus *g_debug_controller_dbus_new (GDBusConnection *connection, + GCancellable *cancellable, + GError **error); + +GLIB_AVAILABLE_IN_2_72 +void g_debug_controller_dbus_stop (GDebugControllerDBus *self); + +G_END_DECLS + +#endif /* __G_DEBUG_CONTROLLER_DBUS_H__ */ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 229e62143..60d6debb2 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -89,6 +89,7 @@ enum { static void g_desktop_app_info_iface_init (GAppInfoIface *iface); static gboolean g_desktop_app_info_ensure_saved (GDesktopAppInfo *info, GError **error); +static gboolean g_desktop_app_info_load_file (GDesktopAppInfo *self); /** * GDesktopAppInfo: @@ -1003,6 +1004,19 @@ desktop_file_dir_unindexed_init (DesktopFileDir *dir) } static GDesktopAppInfo * +g_desktop_app_info_new_from_filename_unlocked (const char *filename) +{ + GDesktopAppInfo *info = NULL; + + info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL); + + if (!g_desktop_app_info_load_file (info)) + g_clear_object (&info); + + return info; +} + +static GDesktopAppInfo * desktop_file_dir_unindexed_get_app (DesktopFileDir *dir, const gchar *desktop_id) { @@ -1013,7 +1027,7 @@ desktop_file_dir_unindexed_get_app (DesktopFileDir *dir, if (!filename) return NULL; - return g_desktop_app_info_new_from_filename (filename); + return g_desktop_app_info_new_from_filename_unlocked (filename); } static void @@ -1033,7 +1047,7 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir, if (desktop_file_dir_app_name_is_masked (dir, app_name)) continue; - add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename (filename)); + add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename_unlocked (filename)); } } @@ -1754,6 +1768,56 @@ binary_from_exec (const char *exec) return g_strndup (start, p - start); } +/*< internal > + * g_desktop_app_info_get_desktop_id_for_filename + * @self: #GDesktopAppInfo to get desktop id of + * + * Tries to find the desktop ID for a particular `.desktop` filename, as per the + * [Desktop Entry Specification](https://specifications.freedesktop.org/desktop- + * entry-spec/desktop-entry-spec-latest.html#desktop-file-id). + * + * Returns: desktop id or basename if filename is unknown. + */ +static char * +g_desktop_app_info_get_desktop_id_for_filename (GDesktopAppInfo *self) +{ + guint i; + gchar *desktop_id = NULL; + + g_return_val_if_fail (self->filename != NULL, NULL); + + for (i = 0; i < desktop_file_dirs->len; i++) + { + DesktopFileDir *dir = g_ptr_array_index (desktop_file_dirs, i); + GHashTable *app_names; + GHashTableIter iter; + gpointer key, value; + + app_names = dir->app_names; + + if (!app_names) + continue; + + g_hash_table_iter_init (&iter, app_names); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (!strcmp (value, self->filename)) + { + desktop_id = g_strdup (key); + break; + } + } + + if (desktop_id) + break; + } + + if (!desktop_id) + desktop_id = g_path_get_basename (self->filename); + + return g_steal_pointer (&desktop_id); +} + static gboolean g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info, GKeyFile *key_file) @@ -1816,6 +1880,10 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info, else { char *t; + + /* Since @exec is not an empty string, there must be at least one + * argument, so dereferencing argv[0] should return non-NULL. */ + g_assert (argc > 0); t = g_find_program_in_path (argv[0]); g_strfreev (argv); @@ -1912,6 +1980,9 @@ g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info, g_free (basename); } + if (info->filename) + info->desktop_id = g_desktop_app_info_get_desktop_id_for_filename (info); + info->keyfile = g_key_file_ref (key_file); return TRUE; @@ -1925,8 +1996,6 @@ g_desktop_app_info_load_file (GDesktopAppInfo *self) g_return_val_if_fail (self->filename != NULL, FALSE); - self->desktop_id = g_path_get_basename (self->filename); - key_file = g_key_file_new (); if (g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, NULL)) @@ -1953,11 +2022,14 @@ g_desktop_app_info_new_from_keyfile (GKeyFile *key_file) info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL); info->filename = NULL; + + desktop_file_dirs_lock (); + if (!g_desktop_app_info_load_from_keyfile (info, key_file)) - { - g_object_unref (info); - return NULL; - } + g_clear_object (&info); + + desktop_file_dirs_unlock (); + return info; } @@ -1975,12 +2047,12 @@ g_desktop_app_info_new_from_filename (const char *filename) { GDesktopAppInfo *info = NULL; - info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL); - if (!g_desktop_app_info_load_file (info)) - { - g_object_unref (info); - return NULL; - } + desktop_file_dirs_lock (); + + info = g_desktop_app_info_new_from_filename_unlocked (filename); + + desktop_file_dirs_unlock (); + return info; } @@ -2233,7 +2305,7 @@ g_desktop_app_info_get_generic_name (GDesktopAppInfo *info) * * Gets the value of the NoDisplay key, which helps determine if the * application info should be shown in menus. See - * #G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show(). + * %G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show(). * * Returns: The value of the NoDisplay key * @@ -2731,6 +2803,26 @@ notify_desktop_launch (GDBusConnection *session_bus, g_object_unref (msg); } +static void +emit_launch_started (GAppLaunchContext *context, + GDesktopAppInfo *info, + const gchar *startup_id) +{ + GVariantBuilder builder; + GVariant *platform_data = NULL; + + if (startup_id) + { + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + g_variant_builder_add (&builder, "{sv}", + "startup-notification-id", + g_variant_new_string (startup_id)); + platform_data = g_variant_ref_sink (g_variant_builder_end (&builder)); + } + g_signal_emit_by_name (context, "launch-started", info, platform_data); + g_clear_pointer (&platform_data, g_variant_unref); +} + #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH) static gboolean @@ -2826,6 +2918,8 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, } g_list_free_full (launched_files, g_object_unref); + + emit_launch_started (launch_context, info, sn_id); } /* Wrap the @argv in a command which will set the @@ -2959,6 +3053,64 @@ g_desktop_app_info_make_platform_data (GDesktopAppInfo *info, return g_variant_builder_end (&builder); } +typedef struct +{ + GDesktopAppInfo *info; /* (owned) */ + GAppLaunchContext *launch_context; /* (owned) (nullable) */ + GAsyncReadyCallback callback; + gchar *startup_id; /* (owned) */ + gpointer user_data; +} LaunchUrisWithDBusData; + +static void +launch_uris_with_dbus_data_free (LaunchUrisWithDBusData *data) +{ + g_clear_object (&data->info); + g_clear_object (&data->launch_context); + g_free (data->startup_id); + + g_free (data); +} + +static void +launch_uris_with_dbus_signal_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + LaunchUrisWithDBusData *data = user_data; + GVariantBuilder builder; + + if (data->launch_context) + { + if (g_task_had_error (G_TASK (result))) + g_app_launch_context_launch_failed (data->launch_context, data->startup_id); + else + { + GVariant *platform_data; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); + /* the docs guarantee `pid` will be set, but we can’t + * easily know it for a D-Bus process, so set it to zero */ + g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (0)); + if (data->startup_id) + g_variant_builder_add (&builder, "{sv}", + "startup-notification-id", + g_variant_new_string (data->startup_id)); + platform_data = g_variant_ref_sink (g_variant_builder_end (&builder)); + g_signal_emit_by_name (data->launch_context, + "launched", + data->info, + platform_data); + g_variant_unref (platform_data); + } + } + + if (data->callback) + data->callback (object, result, data->user_data); + + launch_uris_with_dbus_data_free (data); +} + static void launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, @@ -2968,8 +3120,11 @@ launch_uris_with_dbus (GDesktopAppInfo *info, GAsyncReadyCallback callback, gpointer user_data) { + GVariant *platform_data; GVariantBuilder builder; + GVariantDict dict; gchar *object_path; + LaunchUrisWithDBusData *data; g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); @@ -2983,14 +3138,29 @@ launch_uris_with_dbus (GDesktopAppInfo *info, g_variant_builder_close (&builder); } - g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context)); + platform_data = g_desktop_app_info_make_platform_data (info, uris, launch_context); + g_variant_builder_add_value (&builder, platform_data); object_path = object_path_from_appid (info->app_id); + + data = g_new0 (LaunchUrisWithDBusData, 1); + data->info = g_object_ref (info); + data->callback = callback; + data->user_data = user_data; + data->launch_context = launch_context ? g_object_ref (launch_context) : NULL; + g_variant_dict_init (&dict, platform_data); + g_variant_dict_lookup (&dict, "desktop-startup-id", "s", &data->startup_id); + + if (launch_context) + emit_launch_started (launch_context, info, data->startup_id); + g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application", uris ? "Open" : "Activate", g_variant_builder_end (&builder), NULL, G_DBUS_CALL_FLAGS_NONE, -1, - cancellable, callback, user_data); + cancellable, launch_uris_with_dbus_signal_cb, g_steal_pointer (&data)); g_free (object_path); + + g_variant_dict_clear (&dict); } static gboolean diff --git a/gio/gdrive.c b/gio/gdrive.c index 32bdae879..c6c68dda9 100644 --- a/gio/gdrive.c +++ b/gio/gdrive.c @@ -613,7 +613,7 @@ g_drive_poll_for_media_finish (GDrive *drive, * * Gets the identifier of the given kind for @drive. The only * identifier currently available is - * #G_DRIVE_IDENTIFIER_KIND_UNIX_DEVICE. + * %G_DRIVE_IDENTIFIER_KIND_UNIX_DEVICE. * * Returns: (nullable) (transfer full): a newly allocated string containing the * requested identifier, or %NULL if the #GDrive diff --git a/gio/gdtlsconnection.c b/gio/gdtlsconnection.c index 880d87d2c..1fd035c57 100644 --- a/gio/gdtlsconnection.c +++ b/gio/gdtlsconnection.c @@ -118,6 +118,19 @@ g_dtls_connection_default_init (GDtlsConnectionInterface *iface) * If no certificate database is set, then the default database will be * used. See g_tls_backend_get_default_database(). * + * When using a non-default database, #GDtlsConnection must fall back to using + * the #GTlsDatabase to perform certificate verification using + * g_tls_database_verify_chain(), which means certificate verification will + * not be able to make use of TLS session context. This may be less secure. + * For example, if you create your own #GTlsDatabase that just wraps the + * default #GTlsDatabase, you might expect that you have not changed anything, + * but this is not true because you may have altered the behavior of + * #GDtlsConnection by causing it to use g_tls_database_verify_chain(). See the + * documentation of g_tls_database_verify_chain() for more details on specific + * security checks that may not be performed. Accordingly, setting a + * non-default database is discouraged except for specialty applications with + * unusual security requirements. + * * Since: 2.48 */ g_object_interface_install_property (iface, @@ -223,6 +236,14 @@ g_dtls_connection_default_init (GDtlsConnectionInterface *iface) * #GDtlsConnection::accept-certificate overrode the default * behavior. * + * GLib guarantees that if certificate verification fails, at least + * one error will be set, but it does not guarantee that all possible + * errors will be set. Accordingly, you may not safely decide to + * ignore any particular type of error. For example, it would be + * incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if you want to allow + * expired certificates, because this could potentially be the only + * error flag set even if other problems exist with the certificate. + * * Since: 2.48 */ g_object_interface_install_property (iface, @@ -314,6 +335,15 @@ g_dtls_connection_default_init (GDtlsConnectionInterface *iface) * signal handler. Otherwise, if no handler accepts the certificate, * the handshake will fail with %G_TLS_ERROR_BAD_CERTIFICATE. * + * GLib guarantees that if certificate verification fails, this signal + * will be emitted with at least one error will be set in @errors, but + * it does not guarantee that all possible errors will be set. + * Accordingly, you may not safely decide to ignore any particular + * type of error. For example, it would be incorrect to ignore + * %G_TLS_CERTIFICATE_EXPIRED if you want to allow expired + * certificates, because this could potentially be the only error flag + * set even if other problems exist with the certificate. + * * For a server-side connection, @peer_cert is the certificate * presented by the client, if this was requested via the server's * #GDtlsServerConnection:authentication_mode. On the server side, @@ -372,6 +402,9 @@ g_dtls_connection_default_init (GDtlsConnectionInterface *iface) * client-side connections, unless that bit is not set in * #GDtlsClientConnection:validation-flags). * + * There are nonintuitive security implications when using a non-default + * database. See #GDtlsConnection:database for details. + * * Since: 2.48 */ void diff --git a/gio/gfile.c b/gio/gfile.c index d7feca6b9..1810e3682 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -247,6 +247,18 @@ static void g_file_real_trash_async (GFile static gboolean g_file_real_trash_finish (GFile *file, GAsyncResult *res, GError **error); +static void g_file_real_move_async (GFile *source, + GFile *destination, + GFileCopyFlags flags, + int io_priority, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean g_file_real_move_finish (GFile *file, + GAsyncResult *result, + GError **error); static void g_file_real_make_directory_async (GFile *file, int io_priority, GCancellable *cancellable, @@ -381,6 +393,8 @@ g_file_default_init (GFileIface *iface) iface->delete_file_finish = g_file_real_delete_finish; iface->trash_async = g_file_real_trash_async; iface->trash_finish = g_file_real_trash_finish; + iface->move_async = g_file_real_move_async; + iface->move_finish = g_file_real_move_finish; iface->make_directory_async = g_file_real_make_directory_async; iface->make_directory_finish = g_file_real_make_directory_finish; iface->open_readwrite_async = g_file_real_open_readwrite_async; @@ -441,8 +455,8 @@ g_file_is_native (GFile *file) * This call does no blocking I/O. * * Returns: %TRUE if #GFile's backend supports the - * given URI scheme, %FALSE if URI scheme is %NULL, - * not supported, or #GFile is invalid. + * given URI scheme, %FALSE if URI scheme is %NULL, + * not supported, or #GFile is invalid. */ gboolean g_file_has_uri_scheme (GFile *file, @@ -476,8 +490,8 @@ g_file_has_uri_scheme (GFile *file, * This call does no blocking I/O. * * Returns: (nullable): a string containing the URI scheme for the given - * #GFile or %NULL if the #GFile was constructed with an invalid URI. The - * returned string should be freed with g_free() when no longer needed. + * #GFile or %NULL if the #GFile was constructed with an invalid URI. The + * returned string should be freed with g_free() when no longer needed. */ char * g_file_get_uri_scheme (GFile *file) @@ -511,8 +525,8 @@ g_file_get_uri_scheme (GFile *file) * This call does no blocking I/O. * * Returns: (type filename) (nullable): string containing the #GFile's - * base name, or %NULL if given #GFile is invalid. The returned string - * should be freed with g_free() when no longer needed. + * base name, or %NULL if given #GFile is invalid. The returned string + * should be freed with g_free() when no longer needed. */ char * g_file_get_basename (GFile *file) @@ -536,8 +550,8 @@ g_file_get_basename (GFile *file) * This call does no blocking I/O. * * Returns: (type filename) (nullable): string containing the #GFile's path, - * or %NULL if no such path exists. The returned string should be freed - * with g_free() when no longer needed. + * or %NULL if no such path exists. The returned string should be freed + * with g_free() when no longer needed. */ char * g_file_get_path (GFile *file) @@ -609,7 +623,7 @@ file_peek_path_generic (GFile *file) * This call does no blocking I/O. * * Returns: (type filename) (nullable): string containing the #GFile's path, - * or %NULL if no such path exists. The returned string is owned by @file. + * or %NULL if no such path exists. The returned string is owned by @file. * Since: 2.56 */ const char * @@ -629,9 +643,9 @@ g_file_peek_path (GFile *file) * This call does no blocking I/O. * * Returns: a string containing the #GFile's URI. If the #GFile was constructed - * with an invalid URI, an invalid URI is returned. - * The returned string should be freed with g_free() - * when no longer needed. + * with an invalid URI, an invalid URI is returned. + * The returned string should be freed with g_free() + * when no longer needed. */ char * g_file_get_uri (GFile *file) @@ -665,8 +679,8 @@ g_file_get_uri (GFile *file) * This call does no blocking I/O. * * Returns: a string containing the #GFile's parse name. - * The returned string should be freed with g_free() - * when no longer needed. + * The returned string should be freed with g_free() + * when no longer needed. */ char * g_file_get_parse_name (GFile *file) @@ -696,7 +710,7 @@ g_file_get_parse_name (GFile *file) * This call does no blocking I/O. * * Returns: (transfer full): a new #GFile that is a duplicate - * of the given #GFile. + * of the given #GFile. */ GFile * g_file_dup (GFile *file) @@ -720,9 +734,9 @@ g_file_dup (GFile *file) * * Virtual: hash * Returns: 0 if @file is not a valid #GFile, otherwise an - * integer that can be used as hash value for the #GFile. - * This function is intended for easily hashing a #GFile to - * add to a #GHashTable or similar data structure. + * integer that can be used as hash value for the #GFile. + * This function is intended for easily hashing a #GFile to + * add to a #GHashTable or similar data structure. */ guint g_file_hash (gconstpointer file) @@ -783,8 +797,8 @@ g_file_equal (GFile *file1, * This call does no blocking I/O. * * Returns: (nullable) (transfer full): a #GFile structure to the - * parent of the given #GFile or %NULL if there is no parent. Free - * the returned object with g_object_unref(). + * parent of the given #GFile or %NULL if there is no parent. Free + * the returned object with g_object_unref(). */ GFile * g_file_get_parent (GFile *file) @@ -810,7 +824,7 @@ g_file_get_parent (GFile *file) * if @file is an immediate child of @parent. * * Returns: %TRUE if @file is an immediate child of @parent (or any parent in - * the case that @parent is %NULL). + * the case that @parent is %NULL). * * Since: 2.24 */ @@ -855,7 +869,7 @@ g_file_has_parent (GFile *file, * This call does no blocking I/O. * * Returns: (transfer full): a #GFile to a child specified by @name. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFile * g_file_get_child (GFile *file, @@ -863,6 +877,7 @@ g_file_get_child (GFile *file, { g_return_val_if_fail (G_IS_FILE (file), NULL); g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (!g_path_is_absolute (name), NULL); return g_file_resolve_relative_path (file, name); } @@ -883,8 +898,8 @@ g_file_get_child (GFile *file, * This call does no blocking I/O. * * Returns: (transfer full): a #GFile to the specified child, or - * %NULL if the display name couldn't be converted. - * Free the returned object with g_object_unref(). + * %NULL if the display name couldn't be converted. + * Free the returned object with g_object_unref(). */ GFile * g_file_get_child_for_display_name (GFile *file, @@ -923,7 +938,7 @@ g_file_get_child_for_display_name (GFile *file, * * Virtual: prefix_matches * Returns: %TRUE if the @file's parent, grandparent, etc is @prefix, - * %FALSE otherwise. + * %FALSE otherwise. */ gboolean g_file_has_prefix (GFile *file, @@ -955,9 +970,9 @@ g_file_has_prefix (GFile *file, * This call does no blocking I/O. * * Returns: (type filename) (nullable): string with the relative path from - * @descendant to @parent, or %NULL if @descendant doesn't have @parent as - * prefix. The returned string should be freed with g_free() when - * no longer needed. + * @descendant to @parent, or %NULL if @descendant doesn't have @parent as + * prefix. The returned string should be freed with g_free() when + * no longer needed. */ char * g_file_get_relative_path (GFile *parent, @@ -985,9 +1000,10 @@ g_file_get_relative_path (GFile *parent, * * This call does no blocking I/O. * - * Returns: (transfer full): #GFile to the resolved path. - * %NULL if @relative_path is %NULL or if @file is invalid. - * Free the returned object with g_object_unref(). + * If the @relative_path is an absolute path name, the resolution + * is done absolutely (without taking @file path as base). + * + * Returns: (transfer full): a #GFile for the resolved path. */ GFile * g_file_resolve_relative_path (GFile *file, @@ -1009,7 +1025,7 @@ g_file_resolve_relative_path (GFile *file, * @attributes: an attribute query string * @flags: a set of #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: #GError for error reporting * * Gets the requested information about the files in a directory. @@ -1025,7 +1041,9 @@ g_file_resolve_relative_path (GFile *file, * "standard::*" means all attributes in the standard namespace. * An example attribute query be "standard::*,owner::user". * The standard attributes are available as defines, like - * #G_FILE_ATTRIBUTE_STANDARD_NAME. + * %G_FILE_ATTRIBUTE_STANDARD_NAME. %G_FILE_ATTRIBUTE_STANDARD_NAME should + * always be specified if you plan to call g_file_enumerator_get_child() or + * g_file_enumerator_iterate() on the returned enumerator. * * If @cancellable is not %NULL, then the operation can be cancelled * by triggering the cancellable object from another thread. If the @@ -1037,7 +1055,7 @@ g_file_resolve_relative_path (GFile *file, * error will be returned. Other errors are possible too. * * Returns: (transfer full): A #GFileEnumerator if successful, - * %NULL on error. Free the returned object with g_object_unref(). + * %NULL on error. Free the returned object with g_object_unref(). */ GFileEnumerator * g_file_enumerate_children (GFile *file, @@ -1074,9 +1092,9 @@ g_file_enumerate_children (GFile *file, * @flags: a set of #GFileQueryInfoFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call when the - * request is satisfied + * request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously gets the requested information about the files @@ -1123,8 +1141,8 @@ g_file_enumerate_children_async (GFile *file, * See g_file_enumerate_children_async(). * * Returns: (transfer full): a #GFileEnumerator or %NULL - * if an error occurred. - * Free the returned object with g_object_unref(). + * if an error occurred. + * Free the returned object with g_object_unref(). */ GFileEnumerator * g_file_enumerate_children_finish (GFile *file, @@ -1147,7 +1165,7 @@ g_file_enumerate_children_finish (GFile *file, * g_file_query_exists: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * * Utility function to check if a particular file exists. This is * implemented using g_file_query_info() and as such does blocking I/O. @@ -1173,7 +1191,7 @@ g_file_enumerate_children_finish (GFile *file, * that can happen due to races when you execute the operation. * * Returns: %TRUE if the file exists (and can be detected without error), - * %FALSE otherwise (or if cancelled). + * %FALSE otherwise (or if cancelled). */ gboolean g_file_query_exists (GFile *file, @@ -1199,7 +1217,7 @@ g_file_query_exists (GFile *file, * @file: input #GFile * @flags: a set of #GFileQueryInfoFlags passed to g_file_query_info() * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * * Utility function to inspect the #GFileType of a file. This is * implemented using g_file_query_info() and as such does blocking I/O. @@ -1207,8 +1225,8 @@ g_file_query_exists (GFile *file, * The primary use case of this method is to check if a file is * a regular file, directory, or symlink. * - * Returns: The #GFileType of the file and #G_FILE_TYPE_UNKNOWN - * if the file does not exist + * Returns: The #GFileType of the file and %G_FILE_TYPE_UNKNOWN + * if the file does not exist * * Since: 2.18 */ @@ -1240,7 +1258,7 @@ g_file_query_file_type (GFile *file, * @attributes: an attribute query string * @flags: a set of #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError * * Gets the requested information about specified @file. @@ -1256,7 +1274,7 @@ g_file_query_file_type (GFile *file, * "standard::*" means all attributes in the standard namespace. * An example attribute query be "standard::*,owner::user". * The standard attributes are available as defines, like - * #G_FILE_ATTRIBUTE_STANDARD_NAME. + * %G_FILE_ATTRIBUTE_STANDARD_NAME. * * If @cancellable is not %NULL, then the operation can be cancelled * by triggering the cancellable object from another thread. If the @@ -1265,7 +1283,7 @@ g_file_query_file_type (GFile *file, * * For symlinks, normally the information about the target of the * symlink is returned, rather than information about the symlink - * itself. However if you pass #G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS + * itself. However if you pass %G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS * in @flags the information about the symlink itself will be returned. * Also, for symlinks that point to non-existing files the information * about the symlink itself will be returned. @@ -1275,7 +1293,7 @@ g_file_query_file_type (GFile *file, * filesystem the file is on. * * Returns: (transfer full): a #GFileInfo for the given @file, or %NULL - * on error. Free the returned object with g_object_unref(). + * on error. Free the returned object with g_object_unref(). */ GFileInfo * g_file_query_info (GFile *file, @@ -1311,9 +1329,9 @@ g_file_query_info (GFile *file, * @flags: a set of #GFileQueryInfoFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call when the - * request is satisfied + * request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously gets the requested information about specified @file. @@ -1359,8 +1377,8 @@ g_file_query_info_async (GFile *file, * See g_file_query_info_async(). * * Returns: (transfer full): #GFileInfo for given @file - * or %NULL on error. Free the returned object with - * g_object_unref(). + * or %NULL on error. Free the returned object with + * g_object_unref(). */ GFileInfo * g_file_query_info_finish (GFile *file, @@ -1384,7 +1402,7 @@ g_file_query_info_finish (GFile *file, * @file: input #GFile * @attributes: an attribute query string * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError * * Similar to g_file_query_info(), but obtains information @@ -1400,9 +1418,9 @@ g_file_query_info_finish (GFile *file, * attributes, and a wildcard like "filesystem::*" means all attributes * in the filesystem namespace. The standard namespace for filesystem * attributes is "filesystem". Common attributes of interest are - * #G_FILE_ATTRIBUTE_FILESYSTEM_SIZE (the total size of the filesystem - * in bytes), #G_FILE_ATTRIBUTE_FILESYSTEM_FREE (number of bytes available), - * and #G_FILE_ATTRIBUTE_FILESYSTEM_TYPE (type of the filesystem). + * %G_FILE_ATTRIBUTE_FILESYSTEM_SIZE (the total size of the filesystem + * in bytes), %G_FILE_ATTRIBUTE_FILESYSTEM_FREE (number of bytes available), + * and %G_FILE_ATTRIBUTE_FILESYSTEM_TYPE (type of the filesystem). * * If @cancellable is not %NULL, then the operation can be cancelled * by triggering the cancellable object from another thread. If the @@ -1414,7 +1432,7 @@ g_file_query_info_finish (GFile *file, * kind of filesystem the file is on. * * Returns: (transfer full): a #GFileInfo or %NULL if there was an error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileInfo * g_file_query_filesystem_info (GFile *file, @@ -1448,9 +1466,9 @@ g_file_query_filesystem_info (GFile *file, * @attributes: an attribute query string * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously gets the requested information about the filesystem @@ -1496,8 +1514,8 @@ g_file_query_filesystem_info_async (GFile *file, * See g_file_query_filesystem_info_async(). * * Returns: (transfer full): #GFileInfo for given @file - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). */ GFileInfo * g_file_query_filesystem_info_finish (GFile *file, @@ -1520,7 +1538,7 @@ g_file_query_filesystem_info_finish (GFile *file, * g_file_find_enclosing_mount: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError * * Gets a #GMount for the #GFile. @@ -1534,8 +1552,8 @@ g_file_query_filesystem_info_finish (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: (transfer full): a #GMount where the @file is located - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). */ GMount * g_file_find_enclosing_mount (GFile *file, @@ -1570,9 +1588,9 @@ g_file_find_enclosing_mount (GFile *file, * @file: a #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously gets the mount for the file. @@ -1613,7 +1631,7 @@ g_file_find_enclosing_mount_async (GFile *file, * See g_file_find_enclosing_mount_async(). * * Returns: (transfer full): #GMount for given @file or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GMount * g_file_find_enclosing_mount_finish (GFile *file, @@ -1653,7 +1671,7 @@ g_file_find_enclosing_mount_finish (GFile *file, * * Virtual: read_fn * Returns: (transfer full): #GFileInputStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileInputStream * g_file_read (GFile *file, @@ -1685,14 +1703,14 @@ g_file_read (GFile *file, * @file: input #GFile * @flags: a set of #GFileCreateFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Gets an output stream for appending data to the file. * If the file doesn't already exist it is created. * * By default files created are generally readable by everyone, - * but if you pass #G_FILE_CREATE_PRIVATE in @flags the file + * but if you pass %G_FILE_CREATE_PRIVATE in @flags the file * will be made readable only to the current user, to the level that * is supported on the target filesystem. * @@ -1707,7 +1725,7 @@ g_file_read (GFile *file, * possible too, and depend on what kind of filesystem the file is on. * * Returns: (transfer full): a #GFileOutputStream, or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_append_to (GFile *file, @@ -1740,14 +1758,14 @@ g_file_append_to (GFile *file, * @file: input #GFile * @flags: a set of #GFileCreateFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Creates a new file and returns an output stream for writing to it. * The file must not already exist. * * By default files created are generally readable by everyone, - * but if you pass #G_FILE_CREATE_PRIVATE in @flags the file + * but if you pass %G_FILE_CREATE_PRIVATE in @flags the file * will be made readable only to the current user, to the level * that is supported on the target filesystem. * @@ -1764,8 +1782,8 @@ g_file_append_to (GFile *file, * of filesystem the file is on. * * Returns: (transfer full): a #GFileOutputStream for the newly created - * file, or %NULL on error. - * Free the returned object with g_object_unref(). + * file, or %NULL on error. + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_create (GFile *file, @@ -1797,11 +1815,11 @@ g_file_create (GFile *file, * g_file_replace: * @file: input #GFile * @etag: (nullable): an optional [entity tag][gfile-etag] - * for the current #GFile, or #NULL to ignore + * for the current #GFile, or #NULL to ignore * @make_backup: %TRUE if a backup should be created * @flags: a set of #GFileCreateFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Returns an output stream for overwriting the file, possibly @@ -1815,7 +1833,7 @@ g_file_create (GFile *file, * the destination when the stream is closed. * * By default files created are generally readable by everyone, - * but if you pass #G_FILE_CREATE_PRIVATE in @flags the file + * but if you pass %G_FILE_CREATE_PRIVATE in @flags the file * will be made readable only to the current user, to the level that * is supported on the target filesystem. * @@ -1847,7 +1865,7 @@ g_file_create (GFile *file, * possible too, and depend on what kind of filesystem the file is on. * * Returns: (transfer full): a #GFileOutputStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_replace (GFile *file, @@ -1905,7 +1923,7 @@ g_file_replace (GFile *file, * for reading or writing. * * Returns: (transfer full): #GFileIOStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -1939,14 +1957,14 @@ g_file_open_readwrite (GFile *file, * @file: a #GFile * @flags: a set of #GFileCreateFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: return location for a #GError, or %NULL * * Creates a new file and returns a stream for reading and * writing to it. The file must not already exist. * * By default files created are generally readable by everyone, - * but if you pass #G_FILE_CREATE_PRIVATE in @flags the file + * but if you pass %G_FILE_CREATE_PRIVATE in @flags the file * will be made readable only to the current user, to the level * that is supported on the target filesystem. * @@ -1967,8 +1985,8 @@ g_file_open_readwrite (GFile *file, * streaming, rather than just opening for reading or writing. * * Returns: (transfer full): a #GFileIOStream for the newly created - * file, or %NULL on error. - * Free the returned object with g_object_unref(). + * file, or %NULL on error. + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -2002,11 +2020,11 @@ g_file_create_readwrite (GFile *file, * g_file_replace_readwrite: * @file: a #GFile * @etag: (nullable): an optional [entity tag][gfile-etag] - * for the current #GFile, or #NULL to ignore + * for the current #GFile, or #NULL to ignore * @make_backup: %TRUE if a backup should be created * @flags: a set of #GFileCreateFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: return location for a #GError, or %NULL * * Returns an output stream for overwriting the file in readwrite mode, @@ -2021,7 +2039,7 @@ g_file_create_readwrite (GFile *file, * rather than just opening for reading or writing. * * Returns: (transfer full): a #GFileIOStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -2058,9 +2076,9 @@ g_file_replace_readwrite (GFile *file, * @file: input #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously opens @file for reading. @@ -2101,7 +2119,7 @@ g_file_read_async (GFile *file, * g_file_read_async(). * * Returns: (transfer full): a #GFileInputStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileInputStream * g_file_read_finish (GFile *file, @@ -2126,9 +2144,9 @@ g_file_read_finish (GFile *file, * @flags: a set of #GFileCreateFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously opens @file for appending. @@ -2171,8 +2189,8 @@ g_file_append_to_async (GFile *file, * g_file_append_to_async(). * * Returns: (transfer full): a valid #GFileOutputStream - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_append_to_finish (GFile *file, @@ -2197,9 +2215,9 @@ g_file_append_to_finish (GFile *file, * @flags: a set of #GFileCreateFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously creates a new file and returns an output stream @@ -2243,7 +2261,7 @@ g_file_create_async (GFile *file, * g_file_create_async(). * * Returns: (transfer full): a #GFileOutputStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_create_finish (GFile *file, @@ -2266,14 +2284,14 @@ g_file_create_finish (GFile *file, * g_file_replace_async: * @file: input #GFile * @etag: (nullable): an [entity tag][gfile-etag] for the current #GFile, - * or %NULL to ignore + * or %NULL to ignore * @make_backup: %TRUE if a backup should be created * @flags: a set of #GFileCreateFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously overwrites the file, replacing the contents, @@ -2321,7 +2339,7 @@ g_file_replace_async (GFile *file, * g_file_replace_async(). * * Returns: (transfer full): a #GFileOutputStream, or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFileOutputStream * g_file_replace_finish (GFile *file, @@ -2345,9 +2363,9 @@ g_file_replace_finish (GFile *file, * @file: input #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously opens @file for reading and writing. @@ -2390,7 +2408,7 @@ g_file_open_readwrite_async (GFile *file, * g_file_open_readwrite_async(). * * Returns: (transfer full): a #GFileIOStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -2417,9 +2435,9 @@ g_file_open_readwrite_finish (GFile *file, * @flags: a set of #GFileCreateFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously creates a new file and returns a stream @@ -2465,7 +2483,7 @@ g_file_create_readwrite_async (GFile *file, * g_file_create_readwrite_async(). * * Returns: (transfer full): a #GFileIOStream or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -2490,14 +2508,14 @@ g_file_create_readwrite_finish (GFile *file, * g_file_replace_readwrite_async: * @file: input #GFile * @etag: (nullable): an [entity tag][gfile-etag] for the current #GFile, - * or %NULL to ignore + * or %NULL to ignore * @make_backup: %TRUE if a backup should be created * @flags: a set of #GFileCreateFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously overwrites the file in read-write mode, @@ -2548,7 +2566,7 @@ g_file_replace_readwrite_async (GFile *file, * g_file_replace_readwrite_async(). * * Returns: (transfer full): a #GFileIOStream, or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.22 */ @@ -2718,7 +2736,7 @@ should_copy (GFileAttributeInfo *info, * @file: a #GFile to copy attributes to * @flags: a set of #GFileCopyFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, %NULL to ignore * * Prepares the file attribute query string for copying to @file. @@ -2732,7 +2750,7 @@ should_copy (GFileAttributeInfo *info, * stages (e.g., for recursive move of a directory). * * Returns: an attribute query string for g_file_query_info(), - * or %NULL if an error occurs. + * or %NULL if an error occurs. * * Since: 2.68 */ @@ -2826,7 +2844,7 @@ g_file_build_attribute_list_for_copy (GFile *file, * @destination: a #GFile to copy attributes to * @flags: a set of #GFileCopyFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, %NULL to ignore * * Copies the file attributes from @source to @destination. @@ -2834,12 +2852,12 @@ g_file_build_attribute_list_for_copy (GFile *file, * Normally only a subset of the file attributes are copied, * those that are copies in a normal file copy operation * (which for instance does not include e.g. owner). However - * if #G_FILE_COPY_ALL_METADATA is specified in @flags, then + * if %G_FILE_COPY_ALL_METADATA is specified in @flags, then * all the metadata that is possible to copy is copied. This * is useful when implementing move by copy + delete source. * * Returns: %TRUE if the attributes were copied successfully, - * %FALSE otherwise. + * %FALSE otherwise. */ gboolean g_file_copy_attributes (GFile *source, @@ -3428,23 +3446,23 @@ file_copy_fallback (GFile *source, * @destination: destination #GFile * @flags: set of #GFileCopyFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @progress_callback: (nullable) (scope call): function to callback with - * progress information, or %NULL if progress information is not needed + * progress information, or %NULL if progress information is not needed * @progress_callback_data: (closure): user data to pass to @progress_callback * @error: #GError to set on error, or %NULL * * Copies the file @source to the location specified by @destination. * Can not handle recursive copies of directories. * - * If the flag #G_FILE_COPY_OVERWRITE is specified an already + * If the flag %G_FILE_COPY_OVERWRITE is specified an already * existing @destination file is overwritten. * - * If the flag #G_FILE_COPY_NOFOLLOW_SYMLINKS is specified then symlinks + * If the flag %G_FILE_COPY_NOFOLLOW_SYMLINKS is specified then symlinks * will be copied as symlinks, otherwise the target of the * @source symlink will be copied. * - * If the flag #G_FILE_COPY_ALL_METADATA is specified then all the metadata + * If the flag %G_FILE_COPY_ALL_METADATA is specified then all the metadata * that is possible to copy is copied, not just the default subset (which, * for instance, does not include the owner, see #GFileInfo). * @@ -3461,7 +3479,7 @@ file_copy_fallback (GFile *source, * If the @source file does not exist, then the %G_IO_ERROR_NOT_FOUND error * is returned, independent on the status of the @destination. * - * If #G_FILE_COPY_OVERWRITE is not specified and the target exists, then + * If %G_FILE_COPY_OVERWRITE is not specified and the target exists, then * the error %G_IO_ERROR_EXISTS is returned. * * If trying to overwrite a file over a directory, the %G_IO_ERROR_IS_DIRECTORY @@ -3469,7 +3487,7 @@ file_copy_fallback (GFile *source, * %G_IO_ERROR_WOULD_MERGE error is returned. * * If the source is a directory and the target does not exist, or - * #G_FILE_COPY_OVERWRITE is specified and the target is a file, then the + * %G_FILE_COPY_OVERWRITE is specified and the target is a file, then the * %G_IO_ERROR_WOULD_RECURSE error is returned. * * If you are interested in copying the #GFile object itself (not the on-disk @@ -3557,9 +3575,9 @@ g_file_copy (GFile *source, * @flags: set of #GFileCopyFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @progress_callback: (nullable) (scope notified): function to callback with progress - * information, or %NULL if progress information is not needed + * information, or %NULL if progress information is not needed * @progress_callback_data: (closure progress_callback) (nullable): user data to pass to @progress_callback * @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied * @user_data: (closure callback): the data to pass to callback function @@ -3636,11 +3654,11 @@ g_file_copy_finish (GFile *file, * @destination: #GFile pointing to the destination location * @flags: set of #GFileCopyFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @progress_callback: (nullable) (scope call): #GFileProgressCallback - * function for updates + * function for updates * @progress_callback_data: (closure): gpointer to user data for - * the callback function + * the callback function * @error: #GError for returning error conditions, or %NULL * * Tries to move the file or directory @source to the location specified @@ -3649,7 +3667,7 @@ g_file_copy_finish (GFile *file, * implementation may support moving directories (for instance on moves * inside the same filesystem), but the fallback code does not. * - * If the flag #G_FILE_COPY_OVERWRITE is specified an already + * If the flag %G_FILE_COPY_OVERWRITE is specified an already * existing @destination file is overwritten. * * If @cancellable is not %NULL, then the operation can be cancelled by @@ -3665,7 +3683,7 @@ g_file_copy_finish (GFile *file, * If the @source file does not exist, then the %G_IO_ERROR_NOT_FOUND * error is returned, independent on the status of the @destination. * - * If #G_FILE_COPY_OVERWRITE is not specified and the target exists, + * If %G_FILE_COPY_OVERWRITE is not specified and the target exists, * then the error %G_IO_ERROR_EXISTS is returned. * * If trying to overwrite a file over a directory, the %G_IO_ERROR_IS_DIRECTORY @@ -3673,7 +3691,7 @@ g_file_copy_finish (GFile *file, * %G_IO_ERROR_WOULD_MERGE error is returned. * * If the source is a directory and the target does not exist, or - * #G_FILE_COPY_OVERWRITE is specified and the target is a file, then + * %G_FILE_COPY_OVERWRITE is specified and the target is a file, then * the %G_IO_ERROR_WOULD_RECURSE error may be returned (if the native * move operation isn't available). * @@ -3765,10 +3783,95 @@ g_file_move (GFile *source, } /** + * g_file_move_async: + * @source: #GFile pointing to the source location + * @destination: #GFile pointing to the destination location + * @flags: set of #GFileCopyFlags + * @io_priority: the [I/O priority][io-priority] of the request + * @cancellable: (nullable): optional #GCancellable object, + * %NULL to ignore + * @progress_callback: (nullable) (scope call): #GFileProgressCallback + * function for updates + * @progress_callback_data: (closure): gpointer to user data for + * the callback function + * @callback: a #GAsyncReadyCallback to call + * when the request is satisfied + * @user_data: the data to pass to callback function + * + * Asynchronously moves a file @source to the location of @destination. For details of the behaviour, see g_file_move(). + * + * If @progress_callback is not %NULL, then that function that will be called + * just like in g_file_move(). The callback will run in the default main context + * of the thread calling g_file_move_async() — the same context as @callback is + * run in. + * + * When the operation is finished, @callback will be called. You can then call + * g_file_move_finish() to get the result of the operation. + * + * Since: 2.72 + */ +void +g_file_move_async (GFile *source, + GFile *destination, + GFileCopyFlags flags, + int io_priority, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFileIface *iface; + + g_return_if_fail (G_IS_FILE (source)); + g_return_if_fail (G_IS_FILE (destination)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + iface = G_FILE_GET_IFACE (source); + (* iface->move_async) (source, + destination, + flags, + io_priority, + cancellable, + progress_callback, + progress_callback_data, + callback, + user_data); +} + +/** + * g_file_move_finish: + * @file: input source #GFile + * @result: a #GAsyncResult + * @error: a #GError, or %NULL + * + * Finishes an asynchronous file movement, started with + * g_file_move_async(). + * + * Returns: %TRUE on successful file move, %FALSE otherwise. + * + * Since: 2.72 + */ +gboolean +g_file_move_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + iface = G_FILE_GET_IFACE (file); + return (* iface->move_finish) (file, result, error); +} + +/** * g_file_make_directory: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Creates a directory. Note that this will only create a child directory @@ -3818,9 +3921,9 @@ g_file_make_directory (GFile *file, * @file: input #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: the data to pass to callback function * * Asynchronously creates a directory. @@ -3878,7 +3981,7 @@ g_file_make_directory_finish (GFile *file, * g_file_make_directory_with_parents: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Creates a directory and any parent directories that may not @@ -4000,9 +4103,9 @@ g_file_make_directory_with_parents (GFile *file, * g_file_make_symbolic_link: * @file: a #GFile with the name of the symlink to create * @symlink_value: (type filename): a string with the path for the target - * of the new symlink + * of the new symlink * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError * * Creates a symbolic link named @file which contains the string @@ -4053,7 +4156,7 @@ g_file_make_symbolic_link (GFile *file, * g_file_delete: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Deletes a file. If the @file is a directory, it will only be @@ -4111,9 +4214,9 @@ g_file_delete (GFile *file, * @file: input #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: the data to pass to callback function * * Asynchronously delete a file. If the @file is a directory, it will @@ -4175,7 +4278,7 @@ g_file_delete_finish (GFile *file, * g_file_trash: * @file: #GFile to send to trash * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sends @file to the "Trashcan", if possible. This is similar to @@ -4222,9 +4325,9 @@ g_file_trash (GFile *file, * @file: input #GFile * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: the data to pass to callback function * * Asynchronously sends @file to the Trash location, if possible. @@ -4283,7 +4386,7 @@ g_file_trash_finish (GFile *file, * @file: input #GFile * @display_name: a string * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Renames @file to the specified display name. @@ -4292,7 +4395,7 @@ g_file_trash_finish (GFile *file, * for the target filesystem if possible and the @file is renamed to this. * * If you want to implement a rename operation in the user interface the - * edit name (#G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME) should be used as the + * edit name (%G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME) should be used as the * initial value in the rename widget, and then the result after editing * should be passed to g_file_set_display_name(). * @@ -4303,8 +4406,8 @@ g_file_trash_finish (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: (transfer full): a #GFile specifying what @file was renamed to, - * or %NULL if there was an error. - * Free the returned object with g_object_unref(). + * or %NULL if there was an error. + * Free the returned object with g_object_unref(). */ GFile * g_file_set_display_name (GFile *file, @@ -4340,9 +4443,9 @@ g_file_set_display_name (GFile *file, * @display_name: a string * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously sets the display name for a given #GFile. @@ -4386,7 +4489,7 @@ g_file_set_display_name_async (GFile *file, * g_file_set_display_name_async(). * * Returns: (transfer full): a #GFile or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFile * g_file_set_display_name_finish (GFile *file, @@ -4409,7 +4512,7 @@ g_file_set_display_name_finish (GFile *file, * g_file_query_settable_attributes: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Obtain the list of settable attributes for the file. @@ -4423,9 +4526,9 @@ g_file_set_display_name_finish (GFile *file, * triggering the cancellable object from another thread. If the operation * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * - * Returns: a #GFileAttributeInfoList describing the settable attributes. - * When you are done with it, release it with - * g_file_attribute_info_list_unref() + * Returns: (transfer full): a #GFileAttributeInfoList describing the settable attributes. + * When you are done with it, release it with + * g_file_attribute_info_list_unref() */ GFileAttributeInfoList * g_file_query_settable_attributes (GFile *file, @@ -4467,7 +4570,7 @@ g_file_query_settable_attributes (GFile *file, * g_file_query_writable_namespaces: * @file: input #GFile * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Obtain the list of attribute namespaces where new attributes @@ -4478,9 +4581,9 @@ g_file_query_settable_attributes (GFile *file, * triggering the cancellable object from another thread. If the operation * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * - * Returns: a #GFileAttributeInfoList describing the writable namespaces. - * When you are done with it, release it with - * g_file_attribute_info_list_unref() + * Returns: (transfer full): a #GFileAttributeInfoList describing the writable namespaces. + * When you are done with it, release it with + * g_file_attribute_info_list_unref() */ GFileAttributeInfoList * g_file_query_writable_namespaces (GFile *file, @@ -4529,10 +4632,10 @@ g_file_query_writable_namespaces (GFile *file, * @attribute: a string containing the attribute's name * @type: The type of the attribute * @value_p: (nullable): a pointer to the value (or the pointer - * itself if the type is a pointer type) + * itself if the type is a pointer type) * @flags: a set of #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets an attribute in the file with attribute name @attribute to @value_p. @@ -4582,7 +4685,7 @@ g_file_set_attribute (GFile *file, * @info: a #GFileInfo * @flags: #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Tries to set all attributes in the #GFileInfo on the target @@ -4674,7 +4777,7 @@ g_file_real_set_attributes_from_info (GFile *file, * @flags: a #GFileQueryInfoFlags * @io_priority: the [I/O priority][io-priority] of the request * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async): a #GAsyncReadyCallback * @user_data: (closure): a #gpointer * @@ -4747,7 +4850,7 @@ g_file_set_attributes_finish (GFile *file, * @value: a string containing the attribute's value * @flags: #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_STRING to @value. @@ -4779,7 +4882,7 @@ g_file_set_attribute_string (GFile *file, * @value: a string containing the attribute's new value * @flags: a #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_BYTE_STRING to @value. @@ -4791,7 +4894,7 @@ g_file_set_attribute_string (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: %TRUE if the @attribute was successfully set to @value - * in the @file, %FALSE otherwise. + * in the @file, %FALSE otherwise. */ gboolean g_file_set_attribute_byte_string (GFile *file, @@ -4813,7 +4916,7 @@ g_file_set_attribute_byte_string (GFile *file, * @value: a #guint32 containing the attribute's new value * @flags: a #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_UINT32 to @value. @@ -4824,7 +4927,7 @@ g_file_set_attribute_byte_string (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: %TRUE if the @attribute was successfully set to @value - * in the @file, %FALSE otherwise. + * in the @file, %FALSE otherwise. */ gboolean g_file_set_attribute_uint32 (GFile *file, @@ -4846,7 +4949,7 @@ g_file_set_attribute_uint32 (GFile *file, * @value: a #gint32 containing the attribute's new value * @flags: a #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_INT32 to @value. @@ -4857,7 +4960,7 @@ g_file_set_attribute_uint32 (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: %TRUE if the @attribute was successfully set to @value - * in the @file, %FALSE otherwise. + * in the @file, %FALSE otherwise. */ gboolean g_file_set_attribute_int32 (GFile *file, @@ -4879,7 +4982,7 @@ g_file_set_attribute_int32 (GFile *file, * @value: a #guint64 containing the attribute's new value * @flags: a #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_UINT64 to @value. @@ -4890,7 +4993,7 @@ g_file_set_attribute_int32 (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: %TRUE if the @attribute was successfully set to @value - * in the @file, %FALSE otherwise. + * in the @file, %FALSE otherwise. */ gboolean g_file_set_attribute_uint64 (GFile *file, @@ -4912,7 +5015,7 @@ g_file_set_attribute_uint64 (GFile *file, * @value: a #guint64 containing the attribute's new value * @flags: a #GFileQueryInfoFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Sets @attribute of type %G_FILE_ATTRIBUTE_TYPE_INT64 to @value. @@ -4942,11 +5045,11 @@ g_file_set_attribute_int64 (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @mount_operation: (nullable): a #GMountOperation, - * or %NULL to avoid user interaction + * or %NULL to avoid user interaction * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async) (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: (closure): the data to pass to callback function * * Mounts a file of type G_FILE_TYPE_MOUNTABLE. @@ -5004,7 +5107,7 @@ g_file_mount_mountable (GFile *file, * with g_file_mount_mountable(). * * Returns: (transfer full): a #GFile or %NULL on error. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFile * g_file_mount_mountable_finish (GFile *file, @@ -5030,9 +5133,9 @@ g_file_mount_mountable_finish (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async) (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: (closure): the data to pass to callback function * * Unmounts a file of type G_FILE_TYPE_MOUNTABLE. @@ -5088,10 +5191,10 @@ g_file_unmount_mountable (GFile *file, * with g_file_unmount_mountable(). * * Returns: %TRUE if the operation finished successfully. - * %FALSE otherwise. + * %FALSE otherwise. * * Deprecated: 2.22: Use g_file_unmount_mountable_with_operation_finish() - * instead. + * instead. */ gboolean g_file_unmount_mountable_finish (GFile *file, @@ -5117,14 +5220,14 @@ g_file_unmount_mountable_finish (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @mount_operation: (nullable): a #GMountOperation, - * or %NULL to avoid user interaction + * or %NULL to avoid user interaction * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async) (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: (closure): the data to pass to callback function * - * Unmounts a file of type #G_FILE_TYPE_MOUNTABLE. + * Unmounts a file of type %G_FILE_TYPE_MOUNTABLE. * * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation @@ -5187,7 +5290,7 @@ g_file_unmount_mountable_with_operation (GFile *file, * with g_file_unmount_mountable_with_operation(). * * Returns: %TRUE if the operation finished successfully. - * %FALSE otherwise. + * %FALSE otherwise. * * Since: 2.22 */ @@ -5218,9 +5321,9 @@ g_file_unmount_mountable_with_operation_finish (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async) (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: (closure): the data to pass to callback function * * Starts an asynchronous eject on a mountable. @@ -5273,10 +5376,10 @@ g_file_eject_mountable (GFile *file, * g_file_eject_mountable(). * * Returns: %TRUE if the @file was ejected successfully. - * %FALSE otherwise. + * %FALSE otherwise. * * Deprecated: 2.22: Use g_file_eject_mountable_with_operation_finish() - * instead. + * instead. */ gboolean g_file_eject_mountable_finish (GFile *file, @@ -5302,11 +5405,11 @@ g_file_eject_mountable_finish (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @mount_operation: (nullable): a #GMountOperation, - * or %NULL to avoid user interaction + * or %NULL to avoid user interaction * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (scope async) (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: (closure): the data to pass to callback function * * Starts an asynchronous eject on a mountable. @@ -5368,7 +5471,7 @@ g_file_eject_mountable_with_operation (GFile *file, * g_file_eject_mountable_with_operation(). * * Returns: %TRUE if the @file was ejected successfully. - * %FALSE otherwise. + * %FALSE otherwise. * * Since: 2.22 */ @@ -5399,7 +5502,7 @@ g_file_eject_mountable_with_operation_finish (GFile *file, * @file: input #GFile * @flags: a set of #GFileMonitorFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Obtains a directory monitor for the given file. @@ -5417,8 +5520,8 @@ g_file_eject_mountable_with_operation_finish (GFile *file, * * Virtual: monitor_dir * Returns: (transfer full): a #GFileMonitor for the given @file, - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). */ GFileMonitor * g_file_monitor_directory (GFile *file, @@ -5452,7 +5555,7 @@ g_file_monitor_directory (GFile *file, * @file: input #GFile * @flags: a set of #GFileMonitorFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Obtains a file monitor for the given file. If no file notification @@ -5471,8 +5574,8 @@ g_file_monitor_directory (GFile *file, * backend and/or filesystem type. * * Returns: (transfer full): a #GFileMonitor for the given @file, - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). */ GFileMonitor * g_file_monitor_file (GFile *file, @@ -5507,7 +5610,7 @@ g_file_monitor_file (GFile *file, * @file: input #GFile * @flags: a set of #GFileMonitorFlags * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @error: a #GError, or %NULL * * Obtains a file or directory monitor for the given file, @@ -5518,8 +5621,8 @@ g_file_monitor_file (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: (transfer full): a #GFileMonitor for the given @file, - * or %NULL on error. - * Free the returned object with g_object_unref(). + * or %NULL on error. + * Free the returned object with g_object_unref(). * * Since: 2.18 */ @@ -5998,6 +6101,125 @@ g_file_real_trash_finish (GFile *file, return g_task_propagate_boolean (G_TASK (res), error); } + +typedef struct { + GFile *source; /* (owned) */ + GFile *destination; /* (owned) */ + GFileCopyFlags flags; + GFileProgressCallback progress_cb; + gpointer progress_cb_data; +} MoveAsyncData; + +static void +move_async_data_free (MoveAsyncData *data) +{ + g_object_unref (data->source); + g_object_unref (data->destination); + g_slice_free (MoveAsyncData, data); +} + +typedef struct { + MoveAsyncData *data; /* (unowned) */ + goffset current_num_bytes; + goffset total_num_bytes; +} MoveProgressData; + +static gboolean +move_async_progress_in_main (gpointer user_data) +{ + MoveProgressData *progress = user_data; + MoveAsyncData *data = progress->data; + + data->progress_cb (progress->current_num_bytes, + progress->total_num_bytes, + data->progress_cb_data); + + return G_SOURCE_REMOVE; +} + +static void +move_async_progress_callback (goffset current_num_bytes, + goffset total_num_bytes, + gpointer user_data) +{ + GTask *task = user_data; + MoveAsyncData *data = g_task_get_task_data (task); + MoveProgressData *progress; + + progress = g_new0 (MoveProgressData, 1); + progress->data = data; + progress->current_num_bytes = current_num_bytes; + progress->total_num_bytes = total_num_bytes; + + g_main_context_invoke_full (g_task_get_context (task), + g_task_get_priority (task), + move_async_progress_in_main, + g_steal_pointer (&progress), + g_free); +} + +static void +move_async_thread (GTask *task, + gpointer source, + gpointer task_data, + GCancellable *cancellable) +{ + MoveAsyncData *data = task_data; + gboolean result; + GError *error = NULL; + + result = g_file_move (data->source, + data->destination, + data->flags, + cancellable, + (data->progress_cb != NULL) ? move_async_progress_callback : NULL, + task, + &error); + if (result) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, g_steal_pointer (&error)); +} + +static void +g_file_real_move_async (GFile *source, + GFile *destination, + GFileCopyFlags flags, + int io_priority, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + MoveAsyncData *data; + + data = g_slice_new0 (MoveAsyncData); + data->source = g_object_ref (source); + data->destination = g_object_ref (destination); + data->flags = flags; + data->progress_cb = progress_callback; + data->progress_cb_data = progress_callback_data; + + task = g_task_new (source, cancellable, callback, user_data); + g_task_set_source_tag (task, g_file_real_move_async); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) move_async_data_free); + g_task_set_priority (task, io_priority); + g_task_run_in_thread (task, move_async_thread); + g_object_unref (task); +} + +static gboolean +g_file_real_move_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, file), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + static void make_directory_async_thread (GTask *task, gpointer object, @@ -6397,12 +6619,12 @@ typedef struct { CopyAsyncData *data; goffset current_num_bytes; goffset total_num_bytes; -} ProgressData; +} CopyProgressData; static gboolean copy_async_progress_in_main (gpointer user_data) { - ProgressData *progress = user_data; + CopyProgressData *progress = user_data; CopyAsyncData *data = progress->data; data->progress_cb (progress->current_num_bytes, @@ -6419,9 +6641,9 @@ copy_async_progress_callback (goffset current_num_bytes, { GTask *task = user_data; CopyAsyncData *data = g_task_get_task_data (task); - ProgressData *progress; + CopyProgressData *progress; - progress = g_new (ProgressData, 1); + progress = g_new (CopyProgressData, 1); progress->data = data; progress->current_num_bytes = current_num_bytes; progress->total_num_bytes = total_num_bytes; @@ -6503,7 +6725,7 @@ g_file_real_copy_finish (GFile *file, /** * g_file_new_for_path: * @path: (type filename): a string containing a relative or absolute path. - * The string must be encoded in the glib filename encoding. + * The string must be encoded in the glib filename encoding. * * Constructs a #GFile for a given path. This operation never * fails, but the returned object might not support any I/O @@ -6530,7 +6752,7 @@ g_file_new_for_path (const char *path) * not supported. * * Returns: (transfer full): a new #GFile for the given @uri. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFile * g_file_new_for_uri (const char *uri) @@ -6559,7 +6781,7 @@ g_file_new_for_uri (const char *uri) * a temporary file could not be created. * * Returns: (transfer full): a new #GFile. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). * * Since: 2.32 */ @@ -6720,7 +6942,7 @@ new_for_cmdline_arg (const gchar *arg, * #GOptionContext arguments of type %G_OPTION_ARG_FILENAME. * * Returns: (transfer full): a new #GFile. - * Free the returned object with g_object_unref(). + * Free the returned object with g_object_unref(). */ GFile * g_file_new_for_commandline_arg (const char *arg) @@ -6766,11 +6988,11 @@ g_file_new_for_commandline_arg_and_cwd (const gchar *arg, * @location: input #GFile * @flags: flags affecting the operation * @mount_operation: (nullable): a #GMountOperation - * or %NULL to avoid user interaction + * or %NULL to avoid user interaction * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: the data to pass to callback function * * Starts a @mount_operation, mounting the volume that contains @@ -6820,8 +7042,8 @@ g_file_mount_enclosing_volume (GFile *location, * Finishes a mount operation started by g_file_mount_enclosing_volume(). * * Returns: %TRUE if successful. If an error has occurred, - * this function will return %FALSE and set @error - * appropriately if present. + * this function will return %FALSE and set @error + * appropriately if present. */ gboolean g_file_mount_enclosing_volume_finish (GFile *location, @@ -6861,8 +7083,8 @@ g_file_mount_enclosing_volume_finish (GFile *location, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: (transfer full): a #GAppInfo if the handle was found, - * %NULL if there were errors. - * When you are done with it, release it with g_object_unref() + * %NULL if there were errors. + * When you are done with it, release it with g_object_unref() */ GAppInfo * g_file_query_default_handler (GFile *file, @@ -7032,8 +7254,8 @@ g_file_query_default_handler_async (GFile *file, * Finishes a g_file_query_default_handler_async() operation. * * Returns: (transfer full): a #GAppInfo if the handle was found, - * %NULL if there were errors. - * When you are done with it, release it with g_object_unref() + * %NULL if there were errors. + * When you are done with it, release it with g_object_unref() * * Since: 2.60 */ @@ -7056,9 +7278,9 @@ g_file_query_default_handler_finish (GFile *file, * @cancellable: optional #GCancellable object, %NULL to ignore * @contents: (out) (transfer full) (element-type guint8) (array length=length): a location to place the contents of the file * @length: (out) (optional): a location to place the length of the contents of the file, - * or %NULL if the length is not needed + * or %NULL if the length is not needed * @etag_out: (out) (optional) (nullable): a location to place the current entity tag for the file, - * or %NULL if the entity tag is not needed + * or %NULL if the entity tag is not needed * @error: a #GError, or %NULL * * Loads the content of the file into memory. The data is always @@ -7071,7 +7293,7 @@ g_file_query_default_handler_finish (GFile *file, * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * * Returns: %TRUE if the @file's contents were successfully loaded. - * %FALSE if there were errors. + * %FALSE if there were errors. */ gboolean g_file_load_contents (GFile *file, @@ -7294,10 +7516,10 @@ load_contents_open_callback (GObject *obj, * @file: input #GFile * @cancellable: optional #GCancellable object, %NULL to ignore * @read_more_callback: (scope call) (closure user_data): a - * #GFileReadMoreCallback to receive partial data - * and to specify whether further data should be read + * #GFileReadMoreCallback to receive partial data + * and to specify whether further data should be read * @callback: (scope async) (closure user_data): a #GAsyncReadyCallback to call - * when the request is satisfied + * when the request is satisfied * @user_data: the data to pass to the callback functions * * Reads the partial contents of a file. A #GFileReadMoreCallback should @@ -7344,9 +7566,9 @@ g_file_load_partial_contents_async (GFile *file, * @res: a #GAsyncResult * @contents: (out) (transfer full) (element-type guint8) (array length=length): a location to place the contents of the file * @length: (out) (optional): a location to place the length of the contents of the file, - * or %NULL if the length is not needed + * or %NULL if the length is not needed * @etag_out: (out) (optional) (nullable): a location to place the current entity tag for the file, - * or %NULL if the entity tag is not needed + * or %NULL if the entity tag is not needed * @error: a #GError, or %NULL * * Finishes an asynchronous partial load operation that was started @@ -7356,7 +7578,7 @@ g_file_load_partial_contents_async (GFile *file, * needed. * * Returns: %TRUE if the load was successful. If %FALSE and @error is - * present, it will be set appropriately. + * present, it will be set appropriately. */ gboolean g_file_load_partial_contents_finish (GFile *file, @@ -7442,9 +7664,9 @@ g_file_load_contents_async (GFile *file, * @res: a #GAsyncResult * @contents: (out) (transfer full) (element-type guint8) (array length=length): a location to place the contents of the file * @length: (out) (optional): a location to place the length of the contents of the file, - * or %NULL if the length is not needed + * or %NULL if the length is not needed * @etag_out: (out) (optional) (nullable): a location to place the current entity tag for the file, - * or %NULL if the entity tag is not needed + * or %NULL if the entity tag is not needed * @error: a #GError, or %NULL * * Finishes an asynchronous load of the @file's contents. @@ -7454,7 +7676,7 @@ g_file_load_contents_async (GFile *file, * set to the new entity tag for the @file. * * Returns: %TRUE if the load was successful. If %FALSE and @error is - * present, it will be set appropriately. + * present, it will be set appropriately. */ gboolean g_file_load_contents_finish (GFile *file, @@ -7478,12 +7700,12 @@ g_file_load_contents_finish (GFile *file, * @contents: (element-type guint8) (array length=length): a string containing the new contents for @file * @length: the length of @contents in bytes * @etag: (nullable): the old [entity-tag][gfile-etag] for the document, - * or %NULL + * or %NULL * @make_backup: %TRUE if a backup should be created * @flags: a set of #GFileCreateFlags * @new_etag: (out) (optional) (nullable): a location to a new [entity tag][gfile-etag] - * for the document. This should be freed with g_free() when no longer - * needed, or %NULL + * for the document. This should be freed with g_free() when no longer + * needed, or %NULL * @cancellable: optional #GCancellable object, %NULL to ignore * @error: a #GError, or %NULL * @@ -7505,7 +7727,7 @@ g_file_load_contents_finish (GFile *file, * changed the next time it is saved over. * * Returns: %TRUE if successful. If an error has occurred, this function - * will return %FALSE and set @error appropriately if present. + * will return %FALSE and set @error appropriately if present. */ gboolean g_file_replace_contents (GFile *file, @@ -7790,8 +8012,8 @@ g_file_replace_contents_bytes_async (GFile *file, * @file: input #GFile * @res: a #GAsyncResult * @new_etag: (out) (optional) (nullable): a location of a new [entity tag][gfile-etag] - * for the document. This should be freed with g_free() when it is no - * longer needed, or %NULL + * for the document. This should be freed with g_free() when it is no + * longer needed, or %NULL * @error: a #GError, or %NULL * * Finishes an asynchronous replace of the given @file. See @@ -8015,7 +8237,7 @@ g_file_real_measure_disk_usage_finish (GFile *file, * callback will be invoked. * * Returns: %TRUE if successful, with the out parameters set. - * %FALSE otherwise, with @error set. + * %FALSE otherwise, with @error set. * * Since: 2.38 **/ @@ -8090,7 +8312,7 @@ g_file_measure_disk_usage_async (GFile *file, * more information. * * Returns: %TRUE if successful, with the out parameters set. - * %FALSE otherwise, with @error set. + * %FALSE otherwise, with @error set. * * Since: 2.38 **/ @@ -8117,7 +8339,7 @@ g_file_measure_disk_usage_finish (GFile *file, * @callback: (nullable): a #GAsyncReadyCallback to call when the request is satisfied, or %NULL * @user_data: the data to pass to callback function * - * Starts a file of type #G_FILE_TYPE_MOUNTABLE. + * Starts a file of type %G_FILE_TYPE_MOUNTABLE. * Using @start_operation, you can request callbacks when, for instance, * passwords are needed during authentication. * @@ -8202,14 +8424,14 @@ g_file_start_mountable_finish (GFile *file, * @file: input #GFile * @flags: flags affecting the operation * @mount_operation: (nullable): a #GMountOperation, - * or %NULL to avoid user interaction. + * or %NULL to avoid user interaction. * @cancellable: (nullable): optional #GCancellable object, - * %NULL to ignore + * %NULL to ignore * @callback: (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: the data to pass to callback function * - * Stops a file of type #G_FILE_TYPE_MOUNTABLE. + * Stops a file of type %G_FILE_TYPE_MOUNTABLE. * * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation @@ -8264,7 +8486,7 @@ g_file_stop_mountable (GFile *file, * with g_file_stop_mountable(). * * Returns: %TRUE if the operation finished successfully. - * %FALSE otherwise. + * %FALSE otherwise. * * Since: 2.22 */ @@ -8292,10 +8514,10 @@ g_file_stop_mountable_finish (GFile *file, * @file: input #GFile * @cancellable: optional #GCancellable object, %NULL to ignore * @callback: (nullable): a #GAsyncReadyCallback to call - * when the request is satisfied, or %NULL + * when the request is satisfied, or %NULL * @user_data: the data to pass to callback function * - * Polls a file of type #G_FILE_TYPE_MOUNTABLE. + * Polls a file of type %G_FILE_TYPE_MOUNTABLE. * * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation @@ -8398,7 +8620,7 @@ g_file_supports_thread_contexts (GFile *file) * @file: a #GFile * @cancellable: (nullable): a #GCancellable or %NULL * @etag_out: (out) (nullable) (optional): a location to place the current - * entity tag for the file, or %NULL if the entity tag is not needed + * entity tag for the file, or %NULL if the entity tag is not needed * @error: a location for a #GError or %NULL * * Loads the contents of @file and returns it as #GBytes. @@ -8485,7 +8707,7 @@ g_file_load_bytes_cb (GObject *object, * @file: a #GFile * @cancellable: (nullable): a #GCancellable or %NULL * @callback: (scope async): a #GAsyncReadyCallback to call when the - * request is satisfied + * request is satisfied * @user_data: (closure): the data to pass to callback function * * Asynchronously loads the contents of @file as #GBytes. @@ -8543,7 +8765,7 @@ g_file_load_bytes_async (GFile *file, * @file: a #GFile * @result: a #GAsyncResult provided to the callback * @etag_out: (out) (nullable) (optional): a location to place the current - * entity tag for the file, or %NULL if the entity tag is not needed + * entity tag for the file, or %NULL if the entity tag is not needed * @error: a location for a #GError, or %NULL * * Completes an asynchronous request to g_file_load_bytes_async(). diff --git a/gio/gfile.h b/gio/gfile.h index 4cff1a372..3a324cf9d 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -121,8 +121,8 @@ typedef struct _GFileIface GFileIface; * @copy_async: Asynchronously copies a file. * @copy_finish: Finishes an asynchronous copy operation. * @move: Moves a file. - * @_move_async: Asynchronously moves a file. - * @_move_finish: Finishes an asynchronous move operation. + * @move_async: Asynchronously moves a file. Since: 2.72 + * @move_finish: Finishes an asynchronous move operation. Since: 2.72 * @mount_mountable: Mounts a mountable object. * @mount_mountable_finish: Finishes a mounting operation. * @unmount_mountable: Unmounts a mountable object. @@ -424,8 +424,18 @@ struct _GFileIface GFileProgressCallback progress_callback, gpointer progress_callback_data, GError **error); - void (* _move_async) (void); - void (* _move_finish) (void); + void (* move_async) (GFile *source, + GFile *destination, + GFileCopyFlags flags, + int io_priority, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* move_finish) (GFile *file, + GAsyncResult *result, + GError **error); void (* mount_mountable) (GFile *file, GMountMountFlags flags, @@ -926,6 +936,20 @@ gboolean g_file_move (GFile GFileProgressCallback progress_callback, gpointer progress_callback_data, GError **error); +GLIB_AVAILABLE_IN_2_72 +void g_file_move_async (GFile *source, + GFile *destination, + GFileCopyFlags flags, + int io_priority, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_72 +gboolean g_file_move_finish (GFile *file, + GAsyncResult *result, + GError **error); GLIB_AVAILABLE_IN_ALL gboolean g_file_make_directory (GFile *file, GCancellable *cancellable, diff --git a/gio/gfileenumerator.c b/gio/gfileenumerator.c index 1f9bc24eb..e0ed9720e 100644 --- a/gio/gfileenumerator.c +++ b/gio/gfileenumerator.c @@ -662,7 +662,10 @@ g_file_enumerator_iterate (GFileEnumerator *direnum, const char *name = g_file_info_get_name (ret_info); if (G_UNLIKELY (name == NULL)) - g_warning ("g_file_enumerator_iterate() created without standard::name"); + { + g_critical ("g_file_enumerator_iterate() created without standard::name"); + g_return_val_if_reached (FALSE); + } else { *out_child = g_file_get_child (g_file_enumerator_get_container (direnum), name); @@ -718,6 +721,9 @@ g_file_enumerator_get_container (GFileEnumerator *enumerator) * directory of @enumerator. This function is primarily intended to be used * inside loops with g_file_enumerator_next_file(). * + * To use this, %G_FILE_ATTRIBUTE_STANDARD_NAME must have been listed in the + * attributes list used when creating the #GFileEnumerator. + * * This is a convenience method that's equivalent to: * |[<!-- language="C" --> * gchar *name = g_file_info_get_name (info); @@ -733,10 +739,20 @@ GFile * g_file_enumerator_get_child (GFileEnumerator *enumerator, GFileInfo *info) { + const gchar *name; + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL); + g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + + name = g_file_info_get_name (info); + + if (G_UNLIKELY (name == NULL)) + { + g_critical ("GFileEnumerator created without standard::name"); + g_return_val_if_reached (NULL); + } - return g_file_get_child (enumerator->priv->container, - g_file_info_get_name (info)); + return g_file_get_child (enumerator->priv->container, name); } static void diff --git a/gio/gfilemonitor.c b/gio/gfilemonitor.c index 9b9c0ea2d..89c89d203 100644 --- a/gio/gfilemonitor.c +++ b/gio/gfilemonitor.c @@ -172,7 +172,7 @@ g_file_monitor_class_init (GFileMonitorClass *klass) * that the %G_FILE_MONITOR_WATCH_MOVES flag is not in use. * * If using the deprecated flag %G_FILE_MONITOR_SEND_MOVED flag and @event_type is - * #G_FILE_MONITOR_EVENT_MOVED, @file will be set to a #GFile containing the + * %G_FILE_MONITOR_EVENT_MOVED, @file will be set to a #GFile containing the * old path, and @other_file will be set to a #GFile containing the new path. * * In all the other cases, @other_file will be set to #NULL. diff --git a/gio/ginputstream.c b/gio/ginputstream.c index a40c7d9c4..8fda2693c 100644 --- a/gio/ginputstream.c +++ b/gio/ginputstream.c @@ -429,8 +429,8 @@ g_input_stream_real_skip (GInputStream *stream, end = g_seekable_tell (seekable); g_assert (start >= 0); g_assert (end >= start); - if ((guint64) start > (G_MAXSIZE - count) || - (start + count) > (guint64) end) + if (start > (goffset) (G_MAXOFFSET - count) || + (goffset) (start + count) > end) { stream->priv->pending = TRUE; return end - start; diff --git a/gio/gio-querymodules.c b/gio/gio-querymodules.c index cbeb9758e..aff194b73 100644 --- a/gio/gio-querymodules.c +++ b/gio/gio-querymodules.c @@ -161,7 +161,7 @@ main (gint argc, { int i; - if (argc == 1) + if (argc <= 1) { g_print ("Usage: gio-querymodules <directory1> [<directory2> ...]\n"); g_print ("Will update giomodule.cache in the listed directories\n"); diff --git a/gio/gio-tool-set.c b/gio/gio-tool-set.c index 4dbe1214f..c2a9431f6 100644 --- a/gio/gio-tool-set.c +++ b/gio/gio-tool-set.c @@ -76,12 +76,14 @@ handle_set (int argc, char *argv[], gboolean do_help) const char *attribute; GFileAttributeType type; gpointer value; + gpointer value_allocated = NULL; gboolean b; guint32 uint32; gint32 int32; guint64 uint64; gint64 int64; gchar *param; + int retval = 0; g_set_prgname ("gio set"); @@ -147,7 +149,7 @@ handle_set (int argc, char *argv[], gboolean do_help) value = argv[3]; break; case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING: - value = hex_unescape (argv[3]); + value = value_allocated = hex_unescape (argv[3]); break; case G_FILE_ATTRIBUTE_TYPE_BOOLEAN: b = g_ascii_strcasecmp (argv[3], "true") == 0; @@ -194,11 +196,11 @@ handle_set (int argc, char *argv[], gboolean do_help) { print_error ("%s", error->message); g_error_free (error); - g_object_unref (file); - return 1; + retval = 1; } + g_clear_pointer (&value_allocated, g_free); g_object_unref (file); - return 0; + return retval; } @@ -69,6 +69,8 @@ #include <gio/gdbusproxy.h> #include <gio/gdbusserver.h> #include <gio/gdbusutils.h> +#include <gio/gdebugcontroller.h> +#include <gio/gdebugcontrollerdbus.h> #include <gio/gdrive.h> #include <gio/gdtlsclientconnection.h> #include <gio/gdtlsconnection.h> diff --git a/gio/gioenums.h b/gio/gioenums.h index d81ada416..efc430152 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -392,7 +392,7 @@ typedef enum { * * Indicates a hint from the file system whether files should be * previewed in a file manager. Returned as the value of the key - * #G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW. + * %G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW. **/ typedef enum { G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS = 0, @@ -522,7 +522,7 @@ typedef enum { * } * ]| * but should instead treat all unrecognized error codes the same as - * #G_IO_ERROR_FAILED. + * %G_IO_ERROR_FAILED. * * See also #GPollableReturn for a cheaper way of returning * %G_IO_ERROR_WOULD_BLOCK to callers without allocating a #GError. @@ -973,7 +973,7 @@ typedef enum * @G_BUS_NAME_OWNER_FLAGS_NONE: No flags set. * @G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT: Allow another message bus connection to claim the name. * @G_BUS_NAME_OWNER_FLAGS_REPLACE: If another message bus connection owns the name and have - * specified #G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then take the name from the other connection. + * specified %G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then take the name from the other connection. * @G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE: If another message bus connection owns the name, immediately * return an error from g_bus_own_name() rather than entering the waiting queue for that name. (Since 2.54) * @@ -1021,6 +1021,9 @@ typedef enum * do not ask the bus to launch an owner during proxy initialization, but allow it to be * autostarted by a method call. This flag is only meaningful in proxies for well-known names, * and only if %G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START is not also specified. + * @G_DBUS_PROXY_FLAGS_NO_MATCH_RULE: Don't actually send the AddMatch D-Bus + * call for this signal subscription. This gives you more control + * over which match rules you add (but you must add them manually). (Since: 2.72) * * Flags used when constructing an instance of a #GDBusProxy derived class. * @@ -1033,7 +1036,8 @@ typedef enum G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = (1<<2), G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = (1<<3), - G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION = (1<<4) + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION = (1<<4), + G_DBUS_PROXY_FLAGS_NO_MATCH_RULE GLIB_AVAILABLE_ENUMERATOR_IN_2_72 = (1<<5) } GDBusProxyFlags; /** @@ -1437,6 +1441,7 @@ typedef enum * @G_CREDENTIALS_TYPE_SOLARIS_UCRED: The native credentials type is a `ucred_t`. Added in 2.40. * @G_CREDENTIALS_TYPE_NETBSD_UNPCBID: The native credentials type is a `struct unpcbid`. Added in 2.42. * @G_CREDENTIALS_TYPE_APPLE_XUCRED: The native credentials type is a `struct xucred`. Added in 2.66. + * @G_CREDENTIALS_TYPE_WIN32_PID: The native credentials type is a PID `DWORD`. Added in 2.72. * * Enumeration describing different kinds of native credential types. * @@ -1451,6 +1456,7 @@ typedef enum G_CREDENTIALS_TYPE_SOLARIS_UCRED, G_CREDENTIALS_TYPE_NETBSD_UNPCBID, G_CREDENTIALS_TYPE_APPLE_XUCRED, + G_CREDENTIALS_TYPE_WIN32_PID, } GCredentialsType; /** @@ -1548,6 +1554,8 @@ typedef enum * @G_TLS_ERROR_INAPPROPRIATE_FALLBACK: The TLS handshake failed * because the client sent the fallback SCSV, indicating a protocol * downgrade attack. Since: 2.60 + * @G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD: The certificate failed + * to load because a password was incorrect. Since: 2.72 * * An error code used with %G_TLS_ERROR in a #GError returned from a * TLS-related routine. @@ -1562,7 +1570,8 @@ typedef enum { G_TLS_ERROR_HANDSHAKE, G_TLS_ERROR_CERTIFICATE_REQUIRED, G_TLS_ERROR_EOF, - G_TLS_ERROR_INAPPROPRIATE_FALLBACK + G_TLS_ERROR_INAPPROPRIATE_FALLBACK, + G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD } GTlsError; /** @@ -1584,10 +1593,16 @@ typedef enum { * flags * * A set of flags describing TLS certification validation. This can be - * used to set which validation steps to perform (eg, with - * g_tls_client_connection_set_validation_flags()), or to describe why - * a particular certificate was rejected (eg, in - * #GTlsConnection::accept-certificate). + * used to describe why a particular certificate was rejected (for + * example, in #GTlsConnection::accept-certificate). + * + * GLib guarantees that if certificate verification fails, at least one + * flag will be set, but it does not guarantee that all possible flags + * will be set. Accordingly, you may not safely decide to ignore any + * particular type of error. For example, it would be incorrect to mask + * %G_TLS_CERTIFICATE_EXPIRED if you want to allow expired certificates, + * because this could potentially be the only error flag set even if + * other problems exist with the certificate. * * Since: 2.28 */ @@ -1973,6 +1988,9 @@ typedef enum /*< flags >*/ { * file descriptors of their parent, unless those descriptors have * been explicitly marked as close-on-exec. This flag has no effect * over the "standard" file descriptors (stdin, stdout, stderr). + * @G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP: if path searching is + * needed when spawning the subprocess, use the `PATH` in the launcher + * environment. (Since: 2.72) * * Flags to define the behaviour of a #GSubprocess. * @@ -1995,7 +2013,8 @@ typedef enum { G_SUBPROCESS_FLAGS_STDERR_PIPE = (1u << 4), G_SUBPROCESS_FLAGS_STDERR_SILENCE = (1u << 5), G_SUBPROCESS_FLAGS_STDERR_MERGE = (1u << 6), - G_SUBPROCESS_FLAGS_INHERIT_FDS = (1u << 7) + G_SUBPROCESS_FLAGS_INHERIT_FDS = (1u << 7), + G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP = (1u << 8) } GSubprocessFlags; /** diff --git a/gio/giomodule.c b/gio/giomodule.c index d34037a45..2a043ccd9 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -45,6 +45,8 @@ #include "gnotificationbackend.h" #include "ginitable.h" #include "gnetworkmonitor.h" +#include "gdebugcontroller.h" +#include "gdebugcontrollerdbus.h" #include "gmemorymonitor.h" #include "gmemorymonitorportal.h" #include "gmemorymonitordbus.h" @@ -68,6 +70,10 @@ #include <AvailabilityMacros.h> #endif +#define __GLIB_H_INSIDE__ +#include "gconstructor.h" +#undef __GLIB_H_INSIDE__ + /** * SECTION:giomodule * @short_description: Loadable GIO Modules @@ -1078,8 +1084,10 @@ extern GType _g_network_monitor_netlink_get_type (void); extern GType _g_network_monitor_nm_get_type (void); #endif +extern GType g_debug_controller_dbus_get_type (void); extern GType g_memory_monitor_dbus_get_type (void); extern GType g_memory_monitor_portal_get_type (void); +extern GType g_memory_monitor_win32_get_type (void); extern GType g_power_profile_monitor_dbus_get_type (void); #ifdef G_OS_UNIX @@ -1102,7 +1110,7 @@ extern GType _g_win32_network_monitor_get_type (void); static HMODULE gio_dll = NULL; -#ifdef DLL_EXPORT +#ifndef GLIB_STATIC_COMPILATION BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, @@ -1122,8 +1130,40 @@ DllMain (HINSTANCE hinstDLL, return TRUE; } +#elif defined(G_HAS_CONSTRUCTORS) /* && G_PLATFORM_WIN32 && GLIB_STATIC_COMPILATION */ +extern void glib_win32_init (void); +extern void gobject_win32_init (void); + +#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(giomodule_init_ctor) #endif +G_DEFINE_CONSTRUCTOR (giomodule_init_ctor) + +static void +giomodule_init_ctor (void) +{ + /* When built dynamically, module initialization is done through DllMain + * function which is called when the dynamic library is loaded by the glib + * module AFTER loading gobject. So, in dynamic configuration glib and + * gobject are always initialized BEFORE gio. + * + * When built statically, initialization mechanism relies on hooking + * functions to the CRT section directly at compilation time. As we don't + * control how each compilation unit will be built and in which order, we + * obtain the same kind of issue as the "static initialization order fiasco". + * In this case, we must ensure explicitly that glib and gobject are always + * well initialized BEFORE gio. + */ + glib_win32_init (); + gobject_win32_init (); + gio_win32_appinfo_init (FALSE); +} + +#else /* G_PLATFORM_WIN32 && GLIB_STATIC_COMPILATION && !G_HAS_CONSTRUCTORS */ +#error Your platform/compiler is missing constructor support +#endif /* GLIB_STATIC_COMPILATION */ + void * _g_io_win32_get_module (void) { @@ -1135,7 +1175,7 @@ _g_io_win32_get_module (void) return gio_dll; } -#endif +#endif /* G_PLATFORM_WIN32 */ void _g_io_modules_ensure_extension_points_registered (void) @@ -1189,6 +1229,9 @@ _g_io_modules_ensure_extension_points_registered (void) ep = g_io_extension_point_register (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (ep, G_TYPE_NOTIFICATION_BACKEND); + ep = g_io_extension_point_register (G_DEBUG_CONTROLLER_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (ep, G_TYPE_DEBUG_CONTROLLER); + ep = g_io_extension_point_register (G_MEMORY_MONITOR_EXTENSION_POINT_NAME); g_io_extension_point_set_required_type (ep, G_TYPE_MEMORY_MONITOR); @@ -1300,6 +1343,7 @@ _g_io_modules_ensure_loaded (void) #endif #ifdef G_OS_UNIX g_type_ensure (_g_unix_volume_monitor_get_type ()); + g_type_ensure (g_debug_controller_dbus_get_type ()); g_type_ensure (g_fdo_notification_backend_get_type ()); g_type_ensure (g_gtk_notification_backend_get_type ()); g_type_ensure (g_portal_notification_backend_get_type ()); @@ -1315,6 +1359,7 @@ _g_io_modules_ensure_loaded (void) #ifdef G_OS_WIN32 g_type_ensure (g_win32_notification_backend_get_type ()); g_type_ensure (_g_winhttp_vfs_get_type ()); + g_type_ensure (g_memory_monitor_win32_get_type ()); #endif g_type_ensure (_g_local_vfs_get_type ()); g_type_ensure (_g_dummy_proxy_resolver_get_type ()); @@ -1429,7 +1474,7 @@ g_io_extension_point_set_required_type (GIOExtensionPoint *extension_point, * Gets the required type for @extension_point. * * Returns: the #GType that all implementations must have, - * or #G_TYPE_INVALID if the extension point has no required type + * or %G_TYPE_INVALID if the extension point has no required type */ GType g_io_extension_point_get_required_type (GIOExtensionPoint *extension_point) diff --git a/gio/giomodule.h b/gio/giomodule.h index e94b8099d..4457c498e 100644 --- a/gio/giomodule.h +++ b/gio/giomodule.h @@ -104,7 +104,7 @@ GTypeClass* g_io_extension_ref_class (GIOExtension /* API for the modules to implement */ /** - * g_io_module_load: + * g_io_module_load: (skip) * @module: a #GIOModule. * * Required API for GIO modules to implement. @@ -125,7 +125,7 @@ GLIB_AVAILABLE_IN_ALL void g_io_module_load (GIOModule *module); /** - * g_io_module_unload: + * g_io_module_unload: (skip) * @module: a #GIOModule. * * Required API for GIO modules to implement. diff --git a/gio/giowin32-afunix.h b/gio/giowin32-afunix.h new file mode 100644 index 000000000..0e8e27f08 --- /dev/null +++ b/gio/giowin32-afunix.h @@ -0,0 +1,40 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GIOWIN32_AFUNIX_H_ +#define GIOWIN32_AFUNIX_H_ + +#ifdef HAVE_AFUNIX_H +#include <afunix.h> +#else + +/* + * Fallback definitions of things we need in afunix.h, if not available from the + * used Windows SDK or MinGW headers. + */ +#define UNIX_PATH_MAX 108 + +typedef struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +} SOCKADDR_UN, *PSOCKADDR_UN; + +#define SIO_AF_UNIX_GETPEERPID _WSAIOR(IOC_VENDOR, 256) +#endif + +#endif /* GIOWIN32_AFUNIX_H_*/ diff --git a/gio/glib-compile-resources.c b/gio/glib-compile-resources.c index ac95801f4..82f19563a 100644 --- a/gio/glib-compile-resources.c +++ b/gio/glib-compile-resources.c @@ -710,6 +710,86 @@ escape_makefile_string (const char *string) return g_string_free (str, FALSE); } +typedef enum { + COMPILER_GCC, + COMPILER_CLANG, + COMPILER_MSVC, + COMPILER_UNKNOWN +} CompilerType; + +/* Get the compiler id from the platform, environment, or command line + * + * Keep compiler IDs consistent with https://mesonbuild.com/Reference-tables.html#compiler-ids + * for simplicity + */ +static CompilerType +get_compiler_id (const char *compiler) +{ + char *base, *ext_p; + CompilerType compiler_type; + + if (compiler == NULL) + { +#ifdef G_OS_UNIX + const char *compiler_env = g_getenv ("CC"); + +# ifdef __APPLE__ + if (compiler_env == NULL || *compiler_env == '\0') + compiler = "clang"; + else + compiler = compiler_env; +# elif __linux__ + if (compiler_env == NULL || *compiler_env == '\0') + compiler = "gcc"; + else + compiler = compiler_env; +# else + if (compiler_env == NULL || *compiler_env == '\0') + compiler = "unknown"; + else + compiler = compiler_env; +# endif +#endif + +#ifdef G_OS_WIN32 + if (g_getenv ("MSYSTEM") != NULL) + { + const char *compiler_env = g_getenv ("CC"); + + if (compiler_env == NULL || *compiler_env == '\0') + compiler = "gcc"; + else + compiler = compiler_env; + } + else + compiler = "msvc"; +#endif + } + + base = g_path_get_basename (compiler); + ext_p = strrchr (base, '.'); + if (ext_p != NULL) + { + gsize offset = ext_p - base; + base[offset] = '\0'; + } + + compiler = base; + + if (g_strcmp0 (compiler, "gcc") == 0) + compiler_type = COMPILER_GCC; + else if (g_strcmp0 (compiler, "clang") == 0) + compiler_type = COMPILER_CLANG; + else if (g_strcmp0 (compiler, "msvc") == 0) + compiler_type = COMPILER_MSVC; + else + compiler_type = COMPILER_UNKNOWN; + + g_free (base); + + return compiler_type; +} + int main (int argc, char **argv) { @@ -732,6 +812,8 @@ main (int argc, char **argv) char *c_name = NULL; char *c_name_no_underscores; const char *linkage = "extern"; + char *compiler = NULL; + CompilerType compiler_type = COMPILER_GCC; GOptionContext *context; GOptionEntry entries[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &show_version_and_exit, N_("Show program version and exit"), NULL }, @@ -747,6 +829,7 @@ main (int argc, char **argv) { "internal", 0, 0, G_OPTION_ARG_NONE, &internal, N_("Don’t export functions; declare them G_GNUC_INTERNAL"), NULL }, { "external-data", 0, 0, G_OPTION_ARG_NONE, &external_data, N_("Don’t embed resource data in the C file; assume it's linked externally instead"), NULL }, { "c-name", 0, 0, G_OPTION_ARG_STRING, &c_name, N_("C identifier name used for the generated source code"), NULL }, + { "compiler", 'C', 0, G_OPTION_ARG_STRING, &compiler, N_("The target C compiler (default: the CC environment variable)"), NULL }, G_OPTION_ENTRY_NULL }; @@ -802,6 +885,9 @@ main (int argc, char **argv) if (internal) linkage = "G_GNUC_INTERNAL"; + compiler_type = get_compiler_id (compiler); + g_free (compiler); + srcfile = argv[1]; xmllint = g_strdup (g_getenv ("XMLLINT")); @@ -1105,40 +1191,41 @@ main (int argc, char **argv) } else { - /* For Visual Studio builds: Avoid surpassing the 65535-character limit for a string, GitLab issue #1580 */ - g_fprintf (file, "#ifdef _MSC_VER\n"); - g_fprintf (file, - "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;} %s_resource_data = { {\n", - data_size + 1 /* nul terminator */, c_name); - - for (i = 0; i < data_size; i++) + if (compiler_type == COMPILER_MSVC || compiler_type == COMPILER_UNKNOWN) { - if (i % 16 == 0) - g_fprintf (file, " "); - g_fprintf (file, "0%3.3o", (int)data[i]); - if (i != data_size - 1) - g_fprintf (file, ", "); - if (i % 16 == 15 || i == data_size - 1) - g_fprintf (file, "\n"); - } + /* For Visual Studio builds: Avoid surpassing the 65535-character limit for a string, GitLab issue #1580 */ + g_fprintf (file, + "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;} %s_resource_data = { {\n", + data_size + 1 /* nul terminator */, c_name); - g_fprintf (file, "} };\n"); - - /* For other compilers, use the long string approach */ - g_fprintf (file, "#else /* _MSC_VER */\n"); - g_fprintf (file, - "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;} %s_resource_data = {\n \"", - data_size + 1 /* nul terminator */, c_name); + for (i = 0; i < data_size; i++) + { + if (i % 16 == 0) + g_fprintf (file, " "); + g_fprintf (file, "0%3.3o", (int)data[i]); + if (i != data_size - 1) + g_fprintf (file, ", "); + if (i % 16 == 15 || i == data_size - 1) + g_fprintf (file, "\n"); + } - for (i = 0; i < data_size; i++) - { - g_fprintf (file, "\\%3.3o", (int)data[i]); - if (i % 16 == 15) - g_fprintf (file, "\"\n \""); + g_fprintf (file, "} };\n"); } + else + { + g_fprintf (file, + "static const SECTION union { const guint8 data[%"G_GSIZE_FORMAT"]; const double alignment; void * const ptr;} %s_resource_data = {\n \"", + data_size + 1 /* nul terminator */, c_name); - g_fprintf (file, "\" };\n"); - g_fprintf (file, "#endif /* !_MSC_VER */\n"); + for (i = 0; i < data_size; i++) + { + g_fprintf (file, "\\%3.3o", (int)data[i]); + if (i % 16 == 15) + g_fprintf (file, "\"\n \""); + } + + g_fprintf (file, "\" };\n"); + } } g_fprintf (file, diff --git a/gio/glib-compile-schemas.c b/gio/glib-compile-schemas.c index 7e1152f6b..83184e1e8 100644 --- a/gio/glib-compile-schemas.c +++ b/gio/glib-compile-schemas.c @@ -1038,7 +1038,7 @@ schema_state_add_override (SchemaState *state, GError **error) { SchemaState *parent; - KeyState *original; + KeyState *original = NULL; if (state->extends == NULL) { @@ -1386,14 +1386,14 @@ start_element (GMarkupParseContext *context, } else if (strcmp (element_name, "override") == 0) { - const gchar *name, *l10n, *context; + const gchar *name, *l10n, *str_context; - if (COLLECT (STRING, "name", &name, - OPTIONAL | STRING, "l10n", &l10n, - OPTIONAL | STRING, "context", &context)) + if (COLLECT (STRING, "name", &name, + OPTIONAL | STRING, "l10n", &l10n, + OPTIONAL | STRING, "context", &str_context)) schema_state_add_override (state->schema_state, &state->key_state, &state->string, - name, l10n, context, error); + name, l10n, str_context, error); return; } } @@ -1403,11 +1403,11 @@ start_element (GMarkupParseContext *context, { if (strcmp (element_name, "default") == 0) { - const gchar *l10n, *context; - if (COLLECT (STRING | OPTIONAL, "l10n", &l10n, - STRING | OPTIONAL, "context", &context)) + const gchar *l10n, *str_context; + if (COLLECT (STRING | OPTIONAL, "l10n", &l10n, + STRING | OPTIONAL, "context", &str_context)) state->string = key_state_start_default (state->key_state, - l10n, context, error); + l10n, str_context, error); return; } diff --git a/gio/glistmodel.c b/gio/glistmodel.c index eb797caaf..0411353c2 100644 --- a/gio/glistmodel.c +++ b/gio/glistmodel.c @@ -135,7 +135,7 @@ g_list_model_default_init (GListModelInterface *iface) * from @list. At @position, @removed items were removed and @added * items were added in their place. * - * Note: If @removed != @added, the positions of all later items + * Note: If `removed != added`, the positions of all later items * in the model change. * * Since: 2.44 @@ -157,9 +157,11 @@ g_list_model_default_init (GListModelInterface *iface) * g_list_model_get_item_type: * @list: a #GListModel * - * Gets the type of the items in @list. All items returned from - * g_list_model_get_type() are of that type or a subtype, or are an - * implementation of that interface. + * Gets the type of the items in @list. + * + * All items returned from g_list_model_get_item() are of the type + * returned by this function, or a subtype, or if the type is an + * interface, they are an implementation of that interface. * * The item type of a #GListModel can not change during the life of the * model. @@ -203,11 +205,15 @@ g_list_model_get_n_items (GListModel *list) * @list: a #GListModel * @position: the position of the item to fetch * - * Get the item at @position. If @position is greater than the number of - * items in @list, %NULL is returned. + * Get the item at @position. + * + * If @position is greater than the number of items in @list, %NULL is + * returned. * * %NULL is never returned for an index that is smaller than the length - * of the list. See g_list_model_get_n_items(). + * of the list. + * + * See also: g_list_model_get_n_items() * * Returns: (transfer full) (nullable): the item at @position. * @@ -227,11 +233,18 @@ g_list_model_get_item (GListModel *list, * @list: a #GListModel * @position: the position of the item to fetch * - * Get the item at @position. If @position is greater than the number of - * items in @list, %NULL is returned. + * Get the item at @position. + * + * If @position is greater than the number of items in @list, %NULL is + * returned. * * %NULL is never returned for an index that is smaller than the length - * of the list. See g_list_model_get_n_items(). + * of the list. + * + * This function is meant to be used by language bindings in place + * of g_list_model_get_item(). + * + * See also: g_list_model_get_n_items() * * Returns: (transfer full) (nullable): the object at @position. * diff --git a/gio/glocalfileinfo.h b/gio/glocalfileinfo.h index e04e921f2..f73804516 100644 --- a/gio/glocalfileinfo.h +++ b/gio/glocalfileinfo.h @@ -297,7 +297,7 @@ inline static guint32 _g_stat_nlink (const GLocalFileStat *buf) { return b #endif inline static dev_t _g_stat_dev (const GLocalFileStat *buf) { return buf->st_dev; } inline static ino_t _g_stat_ino (const GLocalFileStat *buf) { return buf->st_ino; } -inline static off_t _g_stat_size (const GLocalFileStat *buf) { return buf->st_size; } +inline static goffset _g_stat_size (const GLocalFileStat *buf) { return buf->st_size; } #ifndef G_OS_WIN32 inline static uid_t _g_stat_uid (const GLocalFileStat *buf) { return buf->st_uid; } @@ -317,11 +317,19 @@ inline static blkcnt_t _g_stat_blocks (const GLocalFileStat *buf) { return b inline static time_t _g_stat_atime (const GLocalFileStat *buf) { return buf->st_atime; } inline static time_t _g_stat_ctime (const GLocalFileStat *buf) { return buf->st_ctime; } inline static time_t _g_stat_mtime (const GLocalFileStat *buf) { return buf->st_mtime; } +#else +inline static time_t _g_stat_atime (const GLocalFileStat *buf) { return buf->st_atim.tv_sec; } +inline static time_t _g_stat_ctime (const GLocalFileStat *buf) { return buf->st_ctim.tv_sec; } +inline static time_t _g_stat_mtime (const GLocalFileStat *buf) { return buf->st_mtim.tv_sec; } #endif -#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC +#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) || defined(G_OS_WIN32) inline static guint32 _g_stat_atim_nsec (const GLocalFileStat *buf) { return buf->st_atim.tv_nsec; } inline static guint32 _g_stat_ctim_nsec (const GLocalFileStat *buf) { return buf->st_ctim.tv_nsec; } inline static guint32 _g_stat_mtim_nsec (const GLocalFileStat *buf) { return buf->st_mtim.tv_nsec; } +#else +inline static guint32 _g_stat_atim_nsec (const GLocalFileStat *buf) { return 0; } +inline static guint32 _g_stat_ctim_nsec (const GLocalFileStat *buf) { return 0; } +inline static guint32 _g_stat_mtim_nsec (const GLocalFileStat *buf) { return 0; } #endif #endif /* !HAVE_STATX */ diff --git a/gio/glocalfilemonitor.c b/gio/glocalfilemonitor.c index 7e922561a..68afd7b51 100644 --- a/gio/glocalfilemonitor.c +++ b/gio/glocalfilemonitor.c @@ -348,7 +348,6 @@ g_file_monitor_source_handle_event (GFileMonitorSource *fms, gint64 event_time) { gboolean interesting = TRUE; - GFileMonitor *instance = NULL; g_assert (!child || is_basename (child)); g_assert (!rename_to || is_basename (rename_to)); @@ -359,9 +358,24 @@ g_file_monitor_source_handle_event (GFileMonitorSource *fms, g_mutex_lock (&fms->lock); - /* monitor is already gone -- don't bother */ - instance = g_weak_ref_get (&fms->instance_ref); - if (instance == NULL) + /* NOTE: + * + * We process events even if the file monitor has already been disposed. + * The reason is that we must not take a reference to the instance here as + * destroying it from the event handling thread will lead to a deadlock when + * taking the lock in _ih_sub_cancel. + * + * This results in seemingly-unbounded growth of the `event_queue` with the + * calls to `g_file_monitor_source_queue_event()`. However, each of those sets + * the ready time on the #GSource, which means that it will be dispatched in + * a subsequent iteration of the #GMainContext it’s attached to. At that + * point, `g_file_monitor_source_dispatch()` will return %FALSE, and this will + * trigger finalisation of the source. That will clear the `event_queue`. + * + * If the source is no longer attached, this will return early to prevent + * unbounded queueing. + */ + if (g_source_is_destroyed ((GSource *) fms)) { g_mutex_unlock (&fms->lock); return TRUE; @@ -406,7 +420,7 @@ g_file_monitor_source_handle_event (GFileMonitorSource *fms, g_assert (!other && rename_to); if (fms->flags & (G_FILE_MONITOR_WATCH_MOVES | G_FILE_MONITOR_SEND_MOVED)) { - GFile *other; + GFile *other_file; const gchar *dirname; gchar *allocated_dirname = NULL; GFileMonitorEvent event; @@ -421,11 +435,11 @@ g_file_monitor_source_handle_event (GFileMonitorSource *fms, dirname = allocated_dirname; } - other = g_local_file_new_from_dirname_and_basename (dirname, rename_to); + other_file = g_local_file_new_from_dirname_and_basename (dirname, rename_to); g_file_monitor_source_file_changes_done (fms, rename_to); - g_file_monitor_source_send_event (fms, event, child, other); + g_file_monitor_source_send_event (fms, event, child, other_file); - g_object_unref (other); + g_object_unref (other_file); g_free (allocated_dirname); } else @@ -452,7 +466,6 @@ g_file_monitor_source_handle_event (GFileMonitorSource *fms, g_file_monitor_source_update_ready_time (fms); g_mutex_unlock (&fms->lock); - g_clear_object (&instance); return interesting; } @@ -599,9 +612,9 @@ g_file_monitor_source_dispose (GFileMonitorSource *fms) g_file_monitor_source_update_ready_time (fms); - g_mutex_unlock (&fms->lock); - g_source_destroy ((GSource *) fms); + + g_mutex_unlock (&fms->lock); } static void diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c index 71a992668..5d6a48840 100644 --- a/gio/glocalfileoutputstream.c +++ b/gio/glocalfileoutputstream.c @@ -855,7 +855,7 @@ handle_overwrite_open (const char *filename, int open_flags; int res; int mode; - int errsv; + int errsv = 0; gboolean replace_destination_set = (flags & G_FILE_CREATE_REPLACE_DESTINATION); mode = mode_from_flags_or_info (flags, reference_info); @@ -1179,7 +1179,7 @@ handle_overwrite_open (const char *filename, /* Seek back to the start of the file after the backup copy */ if (lseek (fd, 0, SEEK_SET) == -1) { - int errsv = errno; + errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), @@ -1195,7 +1195,7 @@ handle_overwrite_open (const char *filename, if (g_unlink (filename) != 0) { - int errsv = errno; + errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), @@ -1211,8 +1211,10 @@ handle_overwrite_open (const char *filename, fd = g_open (filename, open_flags, mode); if (fd == -1) { - int errsv = errno; - char *display_name = g_filename_display_name (filename); + char *display_name; + errsv = errno; + display_name = g_filename_display_name (filename); + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), _("Error opening file “%s”: %s"), @@ -1230,7 +1232,7 @@ handle_overwrite_open (const char *filename, if (ftruncate (fd, 0) == -1) #endif { - int errsv = errno; + errsv = errno; g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), diff --git a/gio/gmemorymonitorwin32.c b/gio/gmemorymonitorwin32.c new file mode 100644 index 000000000..c0e09a5bf --- /dev/null +++ b/gio/gmemorymonitorwin32.c @@ -0,0 +1,261 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gmemorymonitor.h" +#include "gioerror.h" +#include "ginitable.h" +#include "giomodule-priv.h" +#include "glibintl.h" +#include "glib/gstdio.h" +#include "gcancellable.h" + +#include <windows.h> + +#define G_TYPE_MEMORY_MONITOR_WIN32 (g_memory_monitor_win32_get_type ()) +G_DECLARE_FINAL_TYPE (GMemoryMonitorWin32, g_memory_monitor_win32, G, MEMORY_MONITOR_WIN32, GObject) + +#define G_MEMORY_MONITOR_WIN32_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) + +static void g_memory_monitor_win32_iface_init (GMemoryMonitorInterface *iface); +static void g_memory_monitor_win32_initable_iface_init (GInitableIface *iface); + +struct _GMemoryMonitorWin32 +{ + GObject parent_instance; + + HANDLE event; + HANDLE mem; + HANDLE thread; +}; + +G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorWin32, g_memory_monitor_win32, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + g_memory_monitor_win32_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR, + g_memory_monitor_win32_iface_init) + _g_io_modules_ensure_extension_points_registered (); + g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME, + g_define_type_id, + "win32", + 30)) + +static void +g_memory_monitor_win32_init (GMemoryMonitorWin32 *win32) +{ +} + +static gboolean +watch_handler (gpointer user_data) +{ + GMemoryMonitorWin32 *win32 = user_data; + + g_signal_emit_by_name (win32, "low-memory-warning", + G_MEMORY_MONITOR_WARNING_LEVEL_LOW); + + return G_SOURCE_REMOVE; +} + +/* Thread which watches for win32 memory resource events */ +static DWORD WINAPI +watch_thread_function (LPVOID parameter) +{ + GWeakRef *weak_ref = parameter; + GMemoryMonitorWin32 *win32 = NULL; + HANDLE handles[2] = { 0, }; + DWORD result; + BOOL low_memory_state; + + win32 = g_weak_ref_get (weak_ref); + if (!win32) + goto end; + + if (!DuplicateHandle (GetCurrentProcess (), + win32->event, + GetCurrentProcess (), + &handles[0], + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) + { + gchar *emsg; + + emsg = g_win32_error_message (GetLastError ()); + g_debug ("DuplicateHandle failed: %s", emsg); + g_free (emsg); + goto end; + } + + if (!DuplicateHandle (GetCurrentProcess (), + win32->mem, + GetCurrentProcess (), + &handles[1], + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) + { + gchar *emsg; + + emsg = g_win32_error_message (GetLastError ()); + g_debug ("DuplicateHandle failed: %s", emsg); + g_free (emsg); + goto end; + } + + g_clear_object (&win32); + + while (1) + { + if (!QueryMemoryResourceNotification (handles[1], &low_memory_state)) + { + gchar *emsg; + + emsg = g_win32_error_message (GetLastError ()); + g_debug ("QueryMemoryResourceNotification failed: %s", emsg); + g_free (emsg); + break; + } + + win32 = g_weak_ref_get (weak_ref); + if (!win32) + break; + + if (low_memory_state) + { + g_idle_add_full (G_PRIORITY_DEFAULT, + watch_handler, + g_steal_pointer (&win32), + g_object_unref); + /* throttle a bit the loop */ + g_usleep (G_USEC_PER_SEC); + continue; + } + + g_clear_object (&win32); + + result = WaitForMultipleObjects (G_N_ELEMENTS (handles), handles, FALSE, INFINITE); + switch (result) + { + case WAIT_OBJECT_0 + 1: + continue; + + case WAIT_FAILED: + { + gchar *emsg; + + emsg = g_win32_error_message (GetLastError ()); + g_debug ("WaitForMultipleObjects failed: %s", emsg); + g_free (emsg); + } + G_GNUC_FALLTHROUGH; + default: + goto end; + } + } + +end: + if (handles[0]) + CloseHandle (handles[0]); + if (handles[1]) + CloseHandle (handles[1]); + g_clear_object (&win32); + g_weak_ref_clear (weak_ref); + g_free (weak_ref); + return 0; +} + +static gboolean +g_memory_monitor_win32_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GMemoryMonitorWin32 *win32 = G_MEMORY_MONITOR_WIN32 (initable); + GWeakRef *weak_ref = NULL; + + win32->event = CreateEvent (NULL, FALSE, FALSE, NULL); + if (win32->event == NULL) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to create event"); + return FALSE; + } + + win32->mem = CreateMemoryResourceNotification (LowMemoryResourceNotification); + if (win32->mem == NULL) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to create resource notification handle"); + return FALSE; + } + + weak_ref = g_new0 (GWeakRef, 1); + g_weak_ref_init (weak_ref, win32); + /* Use CreateThread (not GThread) with a small stack to make it more lightweight. */ + win32->thread = CreateThread (NULL, 1024, watch_thread_function, weak_ref, 0, NULL); + if (win32->thread == NULL) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to create memory resource notification thread"); + g_weak_ref_clear (weak_ref); + g_free (weak_ref); + return FALSE; + } + + return TRUE; +} + +static void +g_memory_monitor_win32_finalize (GObject *object) +{ + GMemoryMonitorWin32 *win32 = G_MEMORY_MONITOR_WIN32 (object); + + if (win32->thread) + { + SetEvent (win32->event); + WaitForSingleObject (win32->thread, INFINITE); + CloseHandle (win32->thread); + } + + if (win32->event) + CloseHandle (win32->event); + + if (win32->mem) + CloseHandle (win32->mem); + + G_OBJECT_CLASS (g_memory_monitor_win32_parent_class)->finalize (object); +} + +static void +g_memory_monitor_win32_class_init (GMemoryMonitorWin32Class *nl_class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (nl_class); + + gobject_class->finalize = g_memory_monitor_win32_finalize; +} + +static void +g_memory_monitor_win32_iface_init (GMemoryMonitorInterface *monitor_iface) +{ +} + +static void +g_memory_monitor_win32_initable_iface_init (GInitableIface *iface) +{ + iface->init = g_memory_monitor_win32_initable_init; +} diff --git a/gio/gmemoryoutputstream.c b/gio/gmemoryoutputstream.c index b120ccedc..6a410ebb5 100644 --- a/gio/gmemoryoutputstream.c +++ b/gio/gmemoryoutputstream.c @@ -29,6 +29,7 @@ #include "gioerror.h" #include "string.h" #include "glibintl.h" +#include "gutilsprivate.h" /** @@ -596,17 +597,6 @@ array_resize (GMemoryOutputStream *ostream, return TRUE; } -static gsize -g_nearest_pow (gsize num) -{ - gsize n = 1; - - while (n < num && n > 0) - n <<= 1; - - return n; -} - static gssize g_memory_output_stream_write (GOutputStream *stream, const void *buffer, diff --git a/gio/gnetworkservice.c b/gio/gnetworkservice.c index 2b8571e9b..f5ba9d848 100644 --- a/gio/gnetworkservice.c +++ b/gio/gnetworkservice.c @@ -445,7 +445,7 @@ g_network_service_address_enumerator_next (GSocketAddressEnumerator *enumerator { if (srv_enum->addr_enum == NULL && srv_enum->t) { - GError *error = NULL; + GError *my_error = NULL; gchar *uri; gchar *hostname; GSocketConnectable *addr; @@ -477,15 +477,15 @@ g_network_service_address_enumerator_next (GSocketAddressEnumerator *enumerator addr = g_network_address_parse_uri (uri, g_srv_target_get_port (target), - &error); + &my_error); g_free (uri); if (addr == NULL) { if (srv_enum->error == NULL) - srv_enum->error = error; + srv_enum->error = my_error; else - g_error_free (error); + g_error_free (my_error); continue; } @@ -498,18 +498,18 @@ g_network_service_address_enumerator_next (GSocketAddressEnumerator *enumerator if (srv_enum->addr_enum) { - GError *error = NULL; + GError *my_error = NULL; ret = g_socket_address_enumerator_next (srv_enum->addr_enum, cancellable, - &error); + &my_error); - if (error) + if (my_error) { if (srv_enum->error == NULL) - srv_enum->error = error; + srv_enum->error = my_error; else - g_error_free (error); + g_error_free (my_error); } if (!ret) diff --git a/gio/gopenuriportal.c b/gio/gopenuriportal.c index 6ef8f037c..2f527d828 100644 --- a/gio/gopenuriportal.c +++ b/gio/gopenuriportal.c @@ -108,10 +108,10 @@ g_openuri_portal_open_uri (const char *uri, errsv = errno; if (fd == -1) { - g_free (path); - g_variant_builder_clear (&opt_builder); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), "Failed to open '%s'", path); + g_free (path); + g_variant_builder_clear (&opt_builder); return FALSE; } diff --git a/gio/goutputstream.c b/gio/goutputstream.c index 8e48803be..3547b8f12 100644 --- a/gio/goutputstream.c +++ b/gio/goutputstream.c @@ -293,7 +293,7 @@ g_output_stream_write_all (GOutputStream *stream, gssize res; g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); - g_return_val_if_fail (buffer != NULL, FALSE); + g_return_val_if_fail (buffer != NULL || count == 0, FALSE); _bytes_written = 0; while (_bytes_written < count) diff --git a/gio/gpowerprofilemonitor.c b/gio/gpowerprofilemonitor.c index f5028b3e8..00bdc94eb 100644 --- a/gio/gpowerprofilemonitor.c +++ b/gio/gpowerprofilemonitor.c @@ -41,10 +41,10 @@ * some systems). * * When in “Low Power” mode, it is recommended that applications: - * - disabling automatic downloads + * - disable automatic downloads; * - reduce the rate of refresh from online sources such as calendar or - * email synchronisation - * - if the application has expensive visual effects, reduce them + * email synchronisation; + * - reduce the use of expensive visual effects. * * It is also likely that OS components providing services to applications will * lower their own background activity, for the sake of the system. diff --git a/gio/gproxyaddressenumerator.c b/gio/gproxyaddressenumerator.c index 654baade5..de932ff91 100644 --- a/gio/gproxyaddressenumerator.c +++ b/gio/gproxyaddressenumerator.c @@ -25,6 +25,7 @@ #include "gasyncresult.h" #include "ginetaddress.h" +#include "gioerror.h" #include "glibintl.h" #include "gnetworkaddress.h" #include "gnetworkingprivate.h" @@ -87,6 +88,20 @@ struct _GProxyAddressEnumeratorPrivate gboolean supports_hostname; GList *next_dest_ip; GError *last_error; + + /* ever_enumerated is TRUE after we've returned a result for the first time + * via g_proxy_address_enumerator_next() or _next_async(). If FALSE, we have + * never returned yet, and should return an error if returning NULL because + * it does not make sense for a proxy resolver to return NULL except on error. + * (Whereas a DNS resolver would return NULL with no error to indicate "no + * results", a proxy resolver would want to return "direct://" instead, so + * NULL without error does not make sense for us.) + * + * But if ever_enumerated is TRUE, then we must not report any further errors + * (except for G_IO_ERROR_CANCELLED), because this is an API contract of + * GSocketAddressEnumerator. + */ + gboolean ever_enumerated; }; G_DEFINE_TYPE_WITH_PRIVATE (GProxyAddressEnumerator, g_proxy_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR) @@ -171,8 +186,9 @@ g_proxy_address_enumerator_next (GSocketAddressEnumerator *enumerator, GSocketAddress *result = NULL; GError *first_error = NULL; - if (priv->proxies == NULL) + if (!priv->ever_enumerated) { + g_assert (priv->proxies == NULL); priv->proxies = g_proxy_resolver_lookup (priv->proxy_resolver, priv->dest_uri, cancellable, @@ -180,7 +196,10 @@ g_proxy_address_enumerator_next (GSocketAddressEnumerator *enumerator, priv->next_proxy = priv->proxies; if (priv->proxies == NULL) - return NULL; + { + priv->ever_enumerated = TRUE; + return NULL; + } } while (result == NULL && (*priv->next_proxy || priv->addr_enum)) @@ -294,29 +313,37 @@ g_proxy_address_enumerator_next (GSocketAddressEnumerator *enumerator, } } - if (result == NULL && first_error) + if (result == NULL && first_error && (!priv->ever_enumerated || g_error_matches (first_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))) g_propagate_error (error, first_error); else if (first_error) g_error_free (first_error); - return result; -} + if (result == NULL && error != NULL && *error == NULL && !priv->ever_enumerated) + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unspecified proxy lookup failure"); + priv->ever_enumerated = TRUE; + return result; +} static void complete_async (GTask *task) { GProxyAddressEnumeratorPrivate *priv = g_task_get_task_data (task); - if (priv->last_error) + if (priv->last_error && (!priv->ever_enumerated || g_error_matches (priv->last_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))) { g_task_return_error (task, priv->last_error); priv->last_error = NULL; } + else if (!priv->ever_enumerated) + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Unspecified proxy lookup failure"); else g_task_return_pointer (task, NULL, NULL); + priv->ever_enumerated = TRUE; + + g_clear_error (&priv->last_error); g_object_unref (task); } @@ -388,6 +415,7 @@ return_result (GTask *task) } } + priv->ever_enumerated = TRUE; g_task_return_pointer (task, result, g_object_unref); g_object_unref (task); } diff --git a/gio/gregistrysettingsbackend.c b/gio/gregistrysettingsbackend.c index bae066a0a..a7171a21f 100644 --- a/gio/gregistrysettingsbackend.c +++ b/gio/gregistrysettingsbackend.c @@ -1200,7 +1200,7 @@ g_registry_backend_get_writable (GSettingsBackend *backend, GRegistryBackend *self = G_REGISTRY_BACKEND (backend); gchar *path_name; gunichar2 *path_namew; - gchar *value_name; + gchar *value_name = NULL; HKEY hpath; LONG result; @@ -1495,14 +1495,14 @@ registry_cache_update (GRegistryBackend *self, child_item->readable = TRUE; if (changed && event != NULL) { - gchar *item; + gchar *item_path; if (partial_key_name == NULL) - item = g_strdup (buffer); + item_path = g_strdup (buffer); else - item = g_build_path ("/", partial_key_name, buffer, NULL); + item_path = g_build_path ("/", partial_key_name, buffer, NULL); - g_ptr_array_add (event->items, item); + g_ptr_array_add (event->items, item_path); } g_free (buffer); diff --git a/gio/gresolver.c b/gio/gresolver.c index cd5445a65..90b057cbf 100644 --- a/gio/gresolver.c +++ b/gio/gresolver.c @@ -64,7 +64,8 @@ static guint signals[LAST_SIGNAL] = { 0 }; struct _GResolverPrivate { #ifdef G_OS_UNIX - time_t resolv_conf_timestamp; + GMutex mutex; + time_t resolv_conf_timestamp; /* protected by @mutex */ #else int dummy; #endif @@ -149,8 +150,24 @@ g_resolver_real_lookup_service_finish (GResolver *resolver, } static void +g_resolver_finalize (GObject *object) +{ +#ifdef G_OS_UNIX + GResolver *resolver = G_RESOLVER (object); + + g_mutex_clear (&resolver->priv->mutex); +#endif + + G_OBJECT_CLASS (g_resolver_parent_class)->finalize (object); +} + +static void g_resolver_class_init (GResolverClass *resolver_class) { + GObjectClass *object_class = G_OBJECT_CLASS (resolver_class); + + object_class->finalize = g_resolver_finalize; + /* Automatically pass these over to the lookup_records methods */ resolver_class->lookup_service = g_resolver_real_lookup_service; resolver_class->lookup_service_async = g_resolver_real_lookup_service_async; @@ -185,6 +202,8 @@ g_resolver_init (GResolver *resolver) #ifdef G_OS_UNIX if (stat (_PATH_RESCONF, &st) == 0) resolver->priv->resolv_conf_timestamp = st.st_mtime; + + g_mutex_init (&resolver->priv->mutex); #endif } @@ -242,27 +261,23 @@ g_resolver_set_default (GResolver *resolver) G_UNLOCK (default_resolver); } -/* Bionic has res_init() but it's not in any header */ -#ifdef __BIONIC__ -int res_init (void); -#endif - static void -g_resolver_maybe_reload (GResolver *resolver) +maybe_emit_reload (GResolver *resolver) { #ifdef G_OS_UNIX struct stat st; if (stat (_PATH_RESCONF, &st) == 0) { + g_mutex_lock (&resolver->priv->mutex); if (st.st_mtime != resolver->priv->resolv_conf_timestamp) { resolver->priv->resolv_conf_timestamp = st.st_mtime; -#ifdef HAVE_RES_INIT - res_init (); -#endif + g_mutex_unlock (&resolver->priv->mutex); g_signal_emit (resolver, signals[RELOAD], 0); } + else + g_mutex_unlock (&resolver->priv->mutex); } #endif } @@ -444,7 +459,7 @@ lookup_by_name_real (GResolver *resolver, return NULL; } - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) { @@ -530,7 +545,7 @@ g_resolver_lookup_by_name (GResolver *resolver, * * This differs from g_resolver_lookup_by_name() in that you can modify * the lookup behavior with @flags. For example this can be used to limit - * results with #G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY. + * results with %G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY. * * Returns: (element-type GInetAddress) (transfer full): a non-empty #GList * of #GInetAddress, or %NULL on error. You @@ -602,7 +617,7 @@ lookup_by_name_async_real (GResolver *resolver, return; } - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); if (flags != G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) { @@ -839,7 +854,7 @@ g_resolver_lookup_by_address (GResolver *resolver, g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); g_return_val_if_fail (G_IS_INET_ADDRESS (address), NULL); - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); return G_RESOLVER_GET_CLASS (resolver)-> lookup_by_address (resolver, address, cancellable, error); } @@ -868,7 +883,7 @@ g_resolver_lookup_by_address_async (GResolver *resolver, g_return_if_fail (G_IS_RESOLVER (resolver)); g_return_if_fail (G_IS_INET_ADDRESS (address)); - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_by_address_async (resolver, address, cancellable, callback, user_data); } @@ -985,7 +1000,7 @@ g_resolver_lookup_service (GResolver *resolver, return NULL; } - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); targets = G_RESOLVER_GET_CLASS (resolver)-> lookup_service (resolver, rrname, cancellable, error); @@ -1037,7 +1052,7 @@ g_resolver_lookup_service_async (GResolver *resolver, return; } - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_service_async (resolver, rrname, cancellable, callback, user_data); @@ -1136,7 +1151,7 @@ g_resolver_lookup_records (GResolver *resolver, g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); g_return_val_if_fail (rrname != NULL, NULL); - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); records = G_RESOLVER_GET_CLASS (resolver)-> lookup_records (resolver, rrname, record_type, cancellable, error); @@ -1170,7 +1185,7 @@ g_resolver_lookup_records_async (GResolver *resolver, g_return_if_fail (G_IS_RESOLVER (resolver)); g_return_if_fail (rrname != NULL); - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_records_async (resolver, rrname, record_type, cancellable, callback, user_data); } @@ -1210,15 +1225,21 @@ g_resolver_lookup_records_finish (GResolver *resolver, guint64 g_resolver_get_serial (GResolver *resolver) { + guint64 result; + g_return_val_if_fail (G_IS_RESOLVER (resolver), 0); - g_resolver_maybe_reload (resolver); + maybe_emit_reload (resolver); #ifdef G_OS_UNIX - return (guint64) resolver->priv->resolv_conf_timestamp; + g_mutex_lock (&resolver->priv->mutex); + result = resolver->priv->resolv_conf_timestamp; + g_mutex_unlock (&resolver->priv->mutex); #else - return 1; + result = 1; #endif + + return result; } /** diff --git a/gio/gresource-tool.c b/gio/gresource-tool.c index aa718974e..7ebdddaf0 100644 --- a/gio/gresource-tool.c +++ b/gio/gresource-tool.c @@ -180,7 +180,7 @@ elf_foreach_resource_section (Elf *elf, SectionCallback callback, gpointer data) { - int ret; + int ret G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; size_t shstrndx, shnum; size_t scnidx; Elf_Scn *scn; @@ -477,8 +477,8 @@ static gint cmd_help (gboolean requested, const gchar *command) { - const gchar *description; - const gchar *synopsis; + const gchar *description = NULL; + const gchar *synopsis = NULL; gchar *option; GString *string; diff --git a/gio/gresource.c b/gio/gresource.c index 53933f9d2..45ca92b1f 100644 --- a/gio/gresource.c +++ b/gio/gresource.c @@ -346,7 +346,7 @@ g_resource_find_overlay (const gchar *path, if (envvar != NULL) { gchar **parts; - gint i, j; + gint j; parts = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0); @@ -414,7 +414,7 @@ g_resource_find_overlay (const gchar *path, /* We go out of the way to avoid malloc() in the normal case * where the environment variable is not set. */ - static const gchar * const empty_strv[0 + 1]; + static const gchar *const empty_strv[0 + 1] = { 0 }; result = empty_strv; } diff --git a/gio/gresourcefile.c b/gio/gresourcefile.c index 429e9ef49..35dffebe9 100644 --- a/gio/gresourcefile.c +++ b/gio/gresourcefile.c @@ -447,8 +447,8 @@ g_resource_file_query_info (GFile *file, GFileInfo *info; GFileAttributeMatcher *matcher; gboolean res; - gsize size; - guint32 resource_flags; + gsize size = 0; + guint32 resource_flags = 0; char **children; gboolean is_dir; char *base; diff --git a/gio/gsettings-tool.c b/gio/gsettings-tool.c index 7559a6b1b..9352b70f5 100644 --- a/gio/gsettings-tool.c +++ b/gio/gsettings-tool.c @@ -112,11 +112,19 @@ check_path (const gchar *path) return TRUE; } +static int +qsort_cmp (const void *a, + const void *b) +{ + return g_strcmp0 (*(gchar* const*)a, *(gchar* const*)b); +} + static void output_list (gchar **list) { gint i; + qsort (list, g_strv_length (list), sizeof (gchar*), qsort_cmp); for (i = 0; list[i]; i++) g_print ("%s\n", list[i]); } @@ -195,6 +203,7 @@ gsettings_list_children (void) gint i; children = g_settings_list_children (global_settings); + qsort (children, g_strv_length (children), sizeof (gchar*), qsort_cmp); for (i = 0; children[i]; i++) { gsize len = strlen (children[i]); @@ -239,6 +248,7 @@ enumerate (GSettings *settings) g_object_get (settings, "settings-schema", &schema, NULL); keys = g_settings_schema_list_keys (schema); + qsort (keys, g_strv_length (keys), sizeof (gchar*), qsort_cmp); for (i = 0; keys[i]; i++) { GVariant *value; @@ -263,6 +273,7 @@ list_recursively (GSettings *settings) enumerate (settings); children = g_settings_list_children (settings); + qsort (children, g_strv_length (children), sizeof (gchar*), qsort_cmp); for (i = 0; children[i]; i++) { gboolean will_see_elsewhere = FALSE; @@ -306,6 +317,7 @@ gsettings_list_recursively (void) gint i; g_settings_schema_source_list_schemas (global_schema_source, TRUE, &schemas, NULL); + qsort (schemas, g_strv_length (schemas), sizeof (gchar*), qsort_cmp); for (i = 0; schemas[i]; i++) { @@ -558,8 +570,8 @@ static int gsettings_help (gboolean requested, const gchar *command) { - const gchar *description; - const gchar *synopsis; + const gchar *description = NULL; + const gchar *synopsis = NULL; GString *string; string = g_string_new (NULL); diff --git a/gio/gsettings.c b/gio/gsettings.c index 9130dafd5..21ae2ff10 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -248,7 +248,7 @@ * looks for a boolean property with the name "sensitivity" and * automatically binds it to the writability of the bound setting. * If this 'magic' gets in the way, it can be suppressed with the - * #G_SETTINGS_BIND_NO_SENSITIVITY flag. + * %G_SETTINGS_BIND_NO_SENSITIVITY flag. * * ## Relocatable schemas # {#gsettings-relocatable} * @@ -406,12 +406,12 @@ g_settings_real_writable_change_event (GSettings *settings, for (i = 0; i < n_keys; i++) { - const gchar *key = g_quark_to_string (keys[i]); + const gchar *key_name = g_quark_to_string (keys[i]); - if (g_str_has_suffix (key, "/")) + if (g_str_has_suffix (key_name, "/")) continue; - g_signal_emit (settings, g_settings_signals[SIGNAL_WRITABLE_CHANGED], keys[i], key); + g_signal_emit (settings, g_settings_signals[SIGNAL_WRITABLE_CHANGED], keys[i], key_name); } return FALSE; @@ -970,7 +970,7 @@ g_settings_class_init (GSettingsClass *class) * call to g_settings_new(). The new #GSettings will hold a reference * on the context. See g_main_context_push_thread_default(). * - * Returns: a new #GSettings object + * Returns: (not nullable) (transfer full): a new #GSettings object * * Since: 2.26 */ @@ -1018,7 +1018,7 @@ path_is_valid (const gchar *path) * begins and ends with '/' and does not contain two consecutive '/' * characters. * - * Returns: a new #GSettings object + * Returns: (not nullable) (transfer full): a new #GSettings object * * Since: 2.26 */ @@ -1049,7 +1049,7 @@ g_settings_new_with_path (const gchar *schema_id, * the system to get a settings object that modifies the system default * settings instead of the settings for this user. * - * Returns: a new #GSettings object + * Returns: (not nullable) (transfer full): a new #GSettings object * * Since: 2.26 */ @@ -1078,7 +1078,7 @@ g_settings_new_with_backend (const gchar *schema_id, * This is a mix of g_settings_new_with_backend() and * g_settings_new_with_path(). * - * Returns: a new #GSettings object + * Returns: (not nullable) (transfer full): a new #GSettings object * * Since: 2.26 */ @@ -1128,7 +1128,7 @@ g_settings_new_with_backend_and_path (const gchar *schema_id, * @path is non-%NULL and not equal to the path that the schema does * have. * - * Returns: a new #GSettings object + * Returns: (not nullable) (transfer full): a new #GSettings object * * Since: 2.32 */ @@ -1205,7 +1205,7 @@ g_settings_read_from_backend (GSettings *settings, * It is a programmer error to give a @key that isn't contained in the * schema for @settings. * - * Returns: a new #GVariant + * Returns: (not nullable) (transfer full): a new #GVariant * * Since: 2.26 */ @@ -1725,7 +1725,7 @@ g_settings_set (GSettings *settings, * what is returned by this function. %NULL is valid; it is returned * just as any other value would be. * - * Returns: (transfer full): the result, which may be %NULL + * Returns: (nullable) (transfer full): the result, which may be %NULL **/ gpointer g_settings_get_mapped (GSettings *settings, @@ -1792,7 +1792,7 @@ g_settings_get_mapped (GSettings *settings, * It is a programmer error to give a @key that isn't specified as * having a string type in the schema for @settings. * - * Returns: a newly-allocated string + * Returns: (not nullable) (transfer full): a newly-allocated string * * Since: 2.26 */ @@ -2186,7 +2186,7 @@ g_settings_set_boolean (GSettings *settings, * It is a programmer error to give a @key that isn't specified as * having an array of strings type in the schema for @settings. * - * Returns: (array zero-terminated=1) (transfer full): a + * Returns: (array zero-terminated=1) (not nullable) (transfer full): a * newly-allocated, %NULL-terminated array of strings, the value that * is stored at @key in @settings. * @@ -2471,8 +2471,8 @@ g_settings_get_child (GSettings *settings, * You should free the return value with g_strfreev() when you are done * with it. * - * Returns: (transfer full) (element-type utf8): a list of the keys on - * @settings, in no defined order + * Returns: (not nullable) (transfer full) (element-type utf8): a list + * of the keys on @settings, in no defined order * Deprecated: 2.46: Use g_settings_schema_list_keys() instead. */ gchar ** @@ -2497,8 +2497,8 @@ g_settings_list_keys (GSettings *settings) * You should free the return value with g_strfreev() when you are done * with it. * - * Returns: (transfer full) (element-type utf8): a list of the children on - * @settings, in no defined order + * Returns: (not nullable) (transfer full) (element-type utf8): a list of the children + * on @settings, in no defined order */ gchar ** g_settings_list_children (GSettings *settings) @@ -3382,7 +3382,7 @@ g_settings_action_enabled_changed (GSettings *settings, * activations take the new value for the key (which must have the * correct type). * - * Returns: (transfer full): a new #GAction + * Returns: (not nullable) (transfer full): a new #GAction * * Since: 2.32 **/ diff --git a/gio/gsettings.h b/gio/gsettings.h index d3d5a076f..cb35d288e 100644 --- a/gio/gsettings.h +++ b/gio/gsettings.h @@ -284,7 +284,7 @@ typedef gboolean (*GSettingsGetMapping) (GVarian * @G_SETTINGS_BIND_SET: Update the setting when the #GObject property changes. * It is an error to use this flag if the property is not readable. * @G_SETTINGS_BIND_NO_SENSITIVITY: Do not try to bind a "sensitivity" property to the writability of the setting - * @G_SETTINGS_BIND_GET_NO_CHANGES: When set in addition to #G_SETTINGS_BIND_GET, set the #GObject property + * @G_SETTINGS_BIND_GET_NO_CHANGES: When set in addition to %G_SETTINGS_BIND_GET, set the #GObject property * value initially from the setting, but do not listen for changes of the setting * @G_SETTINGS_BIND_INVERT_BOOLEAN: When passed to g_settings_bind(), uses a pair of mapping functions that invert * the boolean value when mapping between the setting and the property. The setting and property must both diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c index b124bc7ec..a1a23cc56 100644 --- a/gio/gsettingsbackend.c +++ b/gio/gsettingsbackend.c @@ -60,7 +60,7 @@ static gboolean g_settings_has_backend; * non-strictly-typed data that is stored in a hierarchy. To implement * an alternative storage backend for #GSettings, you need to implement * the #GSettingsBackend interface and then make it implement the - * extension point #G_SETTINGS_BACKEND_EXTENSION_POINT_NAME. + * extension point %G_SETTINGS_BACKEND_EXTENSION_POINT_NAME. * * The interface defines methods for reading and writing values, a * method for determining if writing of certain values will fail @@ -703,7 +703,7 @@ g_settings_backend_changed_tree (GSettingsBackend *backend, * backend (ie: the one that the backend would contain if * g_settings_reset() were called). * - * Returns: the value that was read, or %NULL + * Returns: (nullable) (transfer full): the value that was read, or %NULL */ GVariant * g_settings_backend_read (GSettingsBackend *backend, @@ -741,7 +741,7 @@ g_settings_backend_read (GSettingsBackend *backend, * value for themselves, then this will return %NULL (even if the * sysadmin has provided a default value). * - * Returns: the value that was read, or %NULL + * Returns: (nullable) (transfer full): the value that was read, or %NULL */ GVariant * g_settings_backend_read_user_value (GSettingsBackend *backend, @@ -1041,7 +1041,8 @@ g_settings_backend_get_default (void) * If this is not implemented in the backend, then a %TRUE * #GSimplePermission is returned. * - * Returns: a non-%NULL #GPermission. Free with g_object_unref() + * Returns: (not nullable) (transfer full): a non-%NULL #GPermission. + * Free with g_object_unref() */ GPermission * g_settings_backend_get_permission (GSettingsBackend *backend, diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 3b5ba0d56..ef4ec1799 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -202,7 +202,7 @@ static GSettingsSchemaSource *schema_sources; * * Increase the reference count of @source, returning a new reference. * - * Returns: a new reference to @source + * Returns: (transfer full) (not nullable): a new reference to @source * * Since: 2.32 **/ @@ -869,9 +869,9 @@ ensure_schema_lists (void) * * Deprecated. * - * Returns: (element-type utf8) (transfer none): a list of #GSettings - * schemas that are available, in no defined order. The list must not be - * modified or freed. + * Returns: (element-type utf8) (transfer none) (not nullable): a list of + * #GSettings schemas that are available, in no defined order. The list + * must not be modified or freed. * * Since: 2.26 * @@ -893,9 +893,9 @@ g_settings_list_schemas (void) * * Deprecated. * - * Returns: (element-type utf8) (transfer none): a list of relocatable - * #GSettings schemas that are available, in no defined order. The list must - * not be modified or freed. + * Returns: (element-type utf8) (transfer none) (not nullable): a list of + * relocatable #GSettings schemas that are available, in no defined order. + * The list must not be modified or freed. * * Since: 2.28 * @@ -915,7 +915,7 @@ g_settings_list_relocatable_schemas (void) * * Increase the reference count of @schema, returning a new reference. * - * Returns: a new reference to @schema + * Returns: (transfer full) (not nullable): a new reference to @schema * * Since: 2.32 **/ @@ -1066,8 +1066,8 @@ g_settings_schema_has_key (GSettingsSchema *schema, * You should free the return value with g_strfreev() when you are done * with it. * - * Returns: (transfer full) (element-type utf8): a list of the children on - * @settings, in no defined order + * Returns: (not nullable) (transfer full) (element-type utf8): a list of + * the children on @settings, in no defined order * * Since: 2.44 */ @@ -1111,8 +1111,8 @@ g_settings_schema_list_children (GSettingsSchema *schema) * (since you should already know what keys are in your schema). This * function is intended for introspection reasons. * - * Returns: (transfer full) (element-type utf8): a list of the keys on - * @schema, in no defined order + * Returns: (not nullable) (transfer full) (element-type utf8): a list + * of the keys on @schema, in no defined order * * Since: 2.46 */ @@ -1250,7 +1250,7 @@ g_settings_schema_list (GSettingsSchema *schema, * * Get the ID of @schema. * - * Returns: (transfer none): the ID + * Returns: (not nullable) (transfer none): the ID **/ const gchar * g_settings_schema_get_id (GSettingsSchema *schema) @@ -1581,7 +1581,7 @@ G_DEFINE_BOXED_TYPE (GSettingsSchemaKey, g_settings_schema_key, g_settings_schem * * Increase the reference count of @key, returning a new reference. * - * Returns: a new reference to @key + * Returns: (not nullable) (transfer full): a new reference to @key * * Since: 2.40 **/ @@ -1626,7 +1626,7 @@ g_settings_schema_key_unref (GSettingsSchemaKey *key) * It is a programmer error to request a key that does not exist. See * g_settings_schema_list_keys(). * - * Returns: (transfer full): the #GSettingsSchemaKey for @name + * Returns: (not nullable) (transfer full): the #GSettingsSchemaKey for @name * * Since: 2.40 **/ @@ -1652,7 +1652,7 @@ g_settings_schema_get_key (GSettingsSchema *schema, * * Gets the name of @key. * - * Returns: the name of @key. + * Returns: (not nullable) (transfer none): the name of @key. * * Since: 2.44 */ @@ -1682,7 +1682,7 @@ g_settings_schema_key_get_name (GSettingsSchemaKey *key) * function has to parse all of the source XML files in the schema * directory. * - * Returns: (nullable): the summary for @key, or %NULL + * Returns: (nullable) (transfer none): the summary for @key, or %NULL * * Since: 2.34 **/ @@ -1717,7 +1717,7 @@ g_settings_schema_key_get_summary (GSettingsSchemaKey *key) * function has to parse all of the source XML files in the schema * directory. * - * Returns: (nullable): the description for @key, or %NULL + * Returns: (nullable) (transfer none): the description for @key, or %NULL * * Since: 2.34 **/ @@ -1739,7 +1739,7 @@ g_settings_schema_key_get_description (GSettingsSchemaKey *key) * * Gets the #GVariantType of @key. * - * Returns: (transfer none): the type of @key + * Returns: (not nullable) (transfer none): the type of @key * * Since: 2.40 **/ @@ -1760,7 +1760,7 @@ g_settings_schema_key_get_value_type (GSettingsSchemaKey *key) * Note that this is the default value according to the schema. System * administrator defaults and lockdown are not visible via this API. * - * Returns: (transfer full): the default value for the key + * Returns: (not nullable) (transfer full): the default value for the key * * Since: 2.40 **/ @@ -1823,7 +1823,7 @@ g_settings_schema_key_get_default_value (GSettingsSchemaKey *key) * You should free the returned value with g_variant_unref() when it is * no longer needed. * - * Returns: (transfer full): a #GVariant describing the range + * Returns: (not nullable) (transfer full): a #GVariant describing the range * * Since: 2.40 **/ diff --git a/gio/gsimpleasyncresult.c b/gio/gsimpleasyncresult.c index c971bdfcd..7fd9b43b5 100644 --- a/gio/gsimpleasyncresult.c +++ b/gio/gsimpleasyncresult.c @@ -705,7 +705,7 @@ g_simple_async_result_take_error (GSimpleAsyncResult *simple, /** * g_simple_async_result_set_error_va: (skip) * @simple: a #GSimpleAsyncResult. - * @domain: a #GQuark (usually #G_IO_ERROR). + * @domain: a #GQuark (usually %G_IO_ERROR). * @code: an error code. * @format: a formatted error reporting string. * @args: va_list of arguments. @@ -735,7 +735,7 @@ g_simple_async_result_set_error_va (GSimpleAsyncResult *simple, /** * g_simple_async_result_set_error: (skip) * @simple: a #GSimpleAsyncResult. - * @domain: a #GQuark (usually #G_IO_ERROR). + * @domain: a #GQuark (usually %G_IO_ERROR). * @code: an error code. * @format: a formatted error reporting string. * @...: a list of variables to fill in @format. @@ -1007,7 +1007,7 @@ g_simple_async_result_is_valid (GAsyncResult *result, * @object: (nullable): a #GObject, or %NULL. * @callback: a #GAsyncReadyCallback. * @user_data: user data passed to @callback. - * @domain: a #GQuark containing the error domain (usually #G_IO_ERROR). + * @domain: a #GQuark containing the error domain (usually %G_IO_ERROR). * @code: a specific error code. * @format: a formatted error reporting string. * @...: a list of variables to fill in @format. diff --git a/gio/gsimpleproxyresolver.c b/gio/gsimpleproxyresolver.c index 2d6009975..f19d56743 100644 --- a/gio/gsimpleproxyresolver.c +++ b/gio/gsimpleproxyresolver.c @@ -493,7 +493,7 @@ g_simple_proxy_resolver_iface_init (GProxyResolverInterface *iface) * g_simple_proxy_resolver_new: * @default_proxy: (nullable): the default proxy to use, eg * "socks://192.168.1.1" - * @ignore_hosts: (nullable): an optional list of hosts/IP addresses + * @ignore_hosts: (array zero-terminated=1) (nullable): an optional list of hosts/IP addresses * to not use a proxy for. * * Creates a new #GSimpleProxyResolver. See @@ -544,7 +544,7 @@ g_simple_proxy_resolver_set_default_proxy (GSimpleProxyResolver *resolver, /** * g_simple_proxy_resolver_set_ignore_hosts: * @resolver: a #GSimpleProxyResolver - * @ignore_hosts: %NULL-terminated list of hosts/IP addresses + * @ignore_hosts: (array zero-terminated=1): %NULL-terminated list of hosts/IP addresses * to not use a proxy for * * Sets the list of ignored hosts. diff --git a/gio/gsocket.c b/gio/gsocket.c index d13e2cab7..be5b96adf 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -76,6 +76,10 @@ #include "glibintl.h" #include "gioprivate.h" +#ifdef G_OS_WIN32 +#include "giowin32-afunix.h" +#endif + /** * SECTION:gsocket * @short_description: Low-level socket object @@ -3138,7 +3142,7 @@ g_socket_get_available_bytes (GSocket *socket) * systems add internal header size to the reported size, making it * unusable for this function. */ avail = recv (socket->priv->fd, buf, bufsize, MSG_PEEK); - if (avail == -1) + if ((gint) avail == -1) { int errsv = get_socket_errno (); #ifdef G_OS_WIN32 @@ -3997,7 +4001,10 @@ socket_source_dispatch (GSource *source, gboolean ret; #ifdef G_OS_WIN32 - events = update_condition (socket_source->socket); + if ((socket_source->pollfd.revents & G_IO_NVAL) != 0) + events = G_IO_NVAL; + else + events = update_condition (socket_source->socket); #else if (g_socket_is_closed (socket_source->socket)) { @@ -4570,8 +4577,7 @@ G_STMT_START { \ _msg->msg_control = NULL; \ else \ { \ - _msg->msg_control = g_alloca (_msg->msg_controllen); \ - memset (_msg->msg_control, '\0', _msg->msg_controllen); \ + _msg->msg_control = g_alloca0 (_msg->msg_controllen); \ } \ \ cmsg = CMSG_FIRSTHDR (_msg); \ @@ -5275,7 +5281,7 @@ g_socket_send_messages_with_timeout (GSocket *socket, #else { gssize result; - gint i; + guint i; gint64 wait_timeout; wait_timeout = timeout_us; @@ -5305,7 +5311,11 @@ g_socket_send_messages_with_timeout (GSocket *socket, #endif } - result = pollable_result == G_POLLABLE_RETURN_OK ? bytes_written : -1; + if (G_MAXSSIZE > bytes_written && + pollable_result == G_POLLABLE_RETURN_OK) + result = (gssize) bytes_written; + else + result = -1; /* check if we've timed out or how much time to wait at most */ if (timeout_us > 0) @@ -6052,10 +6062,22 @@ g_socket_get_credentials (GSocket *socket, { if (cred.cr_version == XUCRED_VERSION) { + pid_t pid; + socklen_t optlen = sizeof (pid); + ret = g_credentials_new (); g_credentials_set_native (ret, G_CREDENTIALS_NATIVE_TYPE, &cred); + +#ifdef LOCAL_PEERPID + if (getsockopt (socket->priv->fd, + SOL_LOCAL, + LOCAL_PEERPID, + &pid, + &optlen) == 0) + _g_credentials_set_local_peerid (ret, pid); +#endif } else { @@ -6113,6 +6135,23 @@ g_socket_get_credentials (GSocket *socket, ucred_free (ucred); } } +#elif G_CREDENTIALS_USE_WIN32_PID + { + DWORD peerid, drc; + + if (WSAIoctl (socket->priv->fd, SIO_AF_UNIX_GETPEERPID, + NULL, 0U, + &peerid, sizeof(peerid), + /* Windows bug: always 0 https://github.com/microsoft/WSL/issues/4676 */ + &drc, + NULL, NULL) == 0) + { + ret = g_credentials_new (); + g_credentials_set_native (ret, + G_CREDENTIALS_TYPE_WIN32_PID, + &peerid); + } + } #else #error "G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED is set but this is no code for this platform" #endif diff --git a/gio/gsocketaddress.c b/gio/gsocketaddress.c index 2b7e83ccf..41dbe66ad 100644 --- a/gio/gsocketaddress.c +++ b/gio/gsocketaddress.c @@ -35,8 +35,10 @@ #include "glibintl.h" #include "gioenumtypes.h" -#ifdef G_OS_UNIX #include "gunixsocketaddress.h" + +#ifdef G_OS_WIN32 +#include "giowin32-afunix.h" #endif @@ -265,7 +267,6 @@ g_socket_address_new_from_native (gpointer native, return sockaddr; } -#ifdef G_OS_UNIX if (family == AF_UNIX) { struct sockaddr_un *addr = (struct sockaddr_un *) native; @@ -299,7 +300,6 @@ g_socket_address_new_from_native (gpointer native, else return g_unix_socket_address_new (addr->sun_path); } -#endif return g_native_socket_address_new (native, len); } diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index 62b1afbcd..cd5aa074a 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -289,7 +289,9 @@ g_socket_client_get_property (GObject *object, break; case PROP_TLS_VALIDATION_FLAGS: +G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_value_set_flags (value, g_socket_client_get_tls_validation_flags (client)); +G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_PROXY_RESOLVER: @@ -340,7 +342,9 @@ g_socket_client_set_property (GObject *object, break; case PROP_TLS_VALIDATION_FLAGS: +G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_socket_client_set_tls_validation_flags (client, g_value_get_flags (value)); +G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_PROXY_RESOLVER: @@ -679,9 +683,15 @@ g_socket_client_set_tls (GSocketClient *client, * Gets the TLS validation flags used creating TLS connections via * @client. * + * This function does not work as originally designed and is impossible + * to use correctly. See #GSocketClient:tls-validation-flags for more + * information. + * * Returns: the TLS validation flags * * Since: 2.28 + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. */ GTlsCertificateFlags g_socket_client_get_tls_validation_flags (GSocketClient *client) @@ -697,7 +707,13 @@ g_socket_client_get_tls_validation_flags (GSocketClient *client) * Sets the TLS validation flags used when creating TLS connections * via @client. The default value is %G_TLS_CERTIFICATE_VALIDATE_ALL. * + * This function does not work as originally designed and is impossible + * to use correctly. See #GSocketClient:tls-validation-flags for more + * information. + * * Since: 2.28 + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. */ void g_socket_client_set_tls_validation_flags (GSocketClient *client, @@ -916,6 +932,29 @@ g_socket_client_class_init (GSocketClientClass *class) G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GSocketClient:tls-validation-flags: + * + * The TLS validation flags used when creating TLS connections. The + * default value is %G_TLS_CERTIFICATE_VALIDATE_ALL. + * + * GLib guarantees that if certificate verification fails, at least one + * flag will be set, but it does not guarantee that all possible flags + * will be set. Accordingly, you may not safely decide to ignore any + * particular type of error. For example, it would be incorrect to mask + * %G_TLS_CERTIFICATE_EXPIRED if you want to allow expired certificates, + * because this could potentially be the only error flag set even if + * other problems exist with the certificate. Therefore, there is no + * safe way to use this property. This is not a horrible problem, + * though, because you should not be attempting to ignore validation + * errors anyway. If you really must ignore TLS certificate errors, + * connect to the #GSocketClient::event signal, wait for it to be + * emitted with %G_SOCKET_CLIENT_TLS_HANDSHAKING, and use that to + * connect to #GTlsConnection::accept-certificate. + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. + */ g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS, g_param_spec_flags ("tls-validation-flags", P_("TLS validation flags"), @@ -924,7 +963,8 @@ g_socket_client_class_init (GSocketClientClass *class) G_TLS_CERTIFICATE_VALIDATE_ALL, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); + G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); /** * GSocketClient:proxy-resolver: @@ -1209,8 +1249,10 @@ g_socket_client_connect (GSocketClient *client, if (tlsconn) { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn), client->priv->tls_validation_flags); +G_GNUC_END_IGNORE_DEPRECATIONS g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKING, connectable, connection); if (g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), cancellable, &error_info->tmp_error)) @@ -1422,6 +1464,8 @@ typedef struct GSocketConnectable *connectable; GSocketAddressEnumerator *enumerator; GCancellable *enumeration_cancellable; + GCancellable *enumeration_parent_cancellable; /* (nullable) (owned) */ + gulong enumeration_cancelled_id; GSList *connection_attempts; GSList *successful_connections; @@ -1441,7 +1485,12 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data) data->task = NULL; g_clear_object (&data->connectable); g_clear_object (&data->enumerator); + + g_cancellable_disconnect (data->enumeration_parent_cancellable, data->enumeration_cancelled_id); + g_clear_object (&data->enumeration_parent_cancellable); + data->enumeration_cancelled_id = 0; g_clear_object (&data->enumeration_cancellable); + g_slist_free_full (data->connection_attempts, connection_attempt_unref); g_slist_free_full (data->successful_connections, connection_attempt_unref); @@ -1459,6 +1508,7 @@ typedef struct GSocketClientAsyncConnectData *data; /* unowned */ GSource *timeout_source; GCancellable *cancellable; + gulong cancelled_id; grefcount ref; } ConnectionAttempt; @@ -1486,6 +1536,8 @@ connection_attempt_unref (gpointer pointer) g_clear_object (&attempt->address); g_clear_object (&attempt->socket); g_clear_object (&attempt->connection); + g_cancellable_disconnect (g_task_get_cancellable (attempt->data->task), attempt->cancelled_id); + attempt->cancelled_id = 0; g_clear_object (&attempt->cancellable); g_clear_object (&attempt->proxy_addr); if (attempt->timeout_source) @@ -1635,8 +1687,10 @@ g_socket_client_tls_handshake (ConnectionAttempt *attempt) &data->error_info->tmp_error); if (tlsconn) { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn), data->client->priv->tls_validation_flags); +G_GNUC_END_IGNORE_DEPRECATIONS g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_TLS_HANDSHAKING, data->connectable, G_IO_STREAM (tlsconn)); g_tls_connection_handshake_async (G_TLS_CONNECTION (tlsconn), G_PRIORITY_DEFAULT, @@ -1977,8 +2031,9 @@ g_socket_client_enumerator_callback (GObject *object, data->connection_attempts = g_slist_append (data->connection_attempts, attempt); if (g_task_get_cancellable (data->task)) - g_cancellable_connect (g_task_get_cancellable (data->task), G_CALLBACK (on_connection_cancelled), - g_object_ref (attempt->cancellable), g_object_unref); + attempt->cancelled_id = + g_cancellable_connect (g_task_get_cancellable (data->task), G_CALLBACK (on_connection_cancelled), + g_object_ref (attempt->cancellable), g_object_unref); g_socket_connection_set_cached_remote_address ((GSocketConnection *)attempt->connection, address); g_debug ("GSocketClient: Starting TCP connection attempt"); @@ -2083,8 +2138,12 @@ g_socket_client_connect_async (GSocketClient *client, data->enumeration_cancellable = g_cancellable_new (); if (cancellable) - g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled), - g_object_ref (data->enumeration_cancellable), g_object_unref); + { + data->enumeration_parent_cancellable = g_object_ref (cancellable); + data->enumeration_cancelled_id = + g_cancellable_connect (cancellable, G_CALLBACK (on_connection_cancelled), + g_object_ref (data->enumeration_cancellable), g_object_unref); + } enumerator_next_async (data, FALSE); } diff --git a/gio/gsocketclient.h b/gio/gsocketclient.h index f0153450d..8f86ce89f 100644 --- a/gio/gsocketclient.h +++ b/gio/gsocketclient.h @@ -110,9 +110,9 @@ gboolean g_socket_client_get_tls (GSocket GLIB_AVAILABLE_IN_2_28 void g_socket_client_set_tls (GSocketClient *client, gboolean tls); -GLIB_AVAILABLE_IN_2_28 +GLIB_DEPRECATED_IN_2_72 GTlsCertificateFlags g_socket_client_get_tls_validation_flags (GSocketClient *client); -GLIB_AVAILABLE_IN_2_28 +GLIB_DEPRECATED_IN_2_72 void g_socket_client_set_tls_validation_flags (GSocketClient *client, GTlsCertificateFlags flags); GLIB_AVAILABLE_IN_2_36 diff --git a/gio/gsocketconnection.c b/gio/gsocketconnection.c index 37d5d330c..64fe9753b 100644 --- a/gio/gsocketconnection.c +++ b/gio/gsocketconnection.c @@ -615,9 +615,7 @@ g_socket_connection_factory_register_type (GType g_type, static void init_builtin_types (void) { -#ifndef G_OS_WIN32 g_type_ensure (G_TYPE_UNIX_CONNECTION); -#endif g_type_ensure (G_TYPE_TCP_CONNECTION); } diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c index c0f4f8db6..523c80bfc 100644 --- a/gio/gsubprocess.c +++ b/gio/gsubprocess.c @@ -61,7 +61,10 @@ * As a matter of principle, #GSubprocess has no API that accepts * shell-style space-separated strings. It will, however, match the * typical shell behaviour of searching the PATH for executables that do - * not contain a directory separator in their name. + * not contain a directory separator in their name. By default, the `PATH` + * of the current process is used. You can specify + * %G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP to use the `PATH` of the + * launcher environment instead. * * #GSubprocess attempts to have a very simple API for most uses (ie: * spawning a subprocess with arguments and support for most typical @@ -380,7 +383,7 @@ initable_init (GInitable *initable, /* argv0 has no '/' in it? We better do a PATH lookup. */ if (strchr (self->argv[0], G_DIR_SEPARATOR) == NULL) { - if (self->launcher && self->launcher->path_from_envp) + if (self->launcher && self->launcher->flags & G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP) spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP; else spawn_flags |= G_SPAWN_SEARCH_PATH; diff --git a/gio/gsubprocesslauncher-private.h b/gio/gsubprocesslauncher-private.h index d6fe0d784..8bd1b2844 100644 --- a/gio/gsubprocesslauncher-private.h +++ b/gio/gsubprocesslauncher-private.h @@ -28,7 +28,6 @@ struct _GSubprocessLauncher GObject parent; GSubprocessFlags flags; - gboolean path_from_envp; char **envp; char *cwd; diff --git a/gio/gtask.c b/gio/gtask.c index a767e1909..365f200ad 100644 --- a/gio/gtask.c +++ b/gio/gtask.c @@ -58,6 +58,10 @@ * use g_task_propagate_pointer() or the like to extract the * return value. * + * Using #GTask requires the thread-default #GMainContext from when the + * #GTask was constructed to be running at least until the task has completed + * and its data has been freed. + * * Here is an example for using GTask as a GAsyncResult: * |[<!-- language="C" --> * typedef struct { @@ -977,13 +981,19 @@ g_task_set_return_on_cancel (GTask *task, * @task: the #GTask * @source_tag: an opaque pointer indicating the source of this task * - * Sets @task's source tag. You can use this to tag a task return + * Sets @task's source tag. + * + * You can use this to tag a task return * value with a particular pointer (usually a pointer to the function * doing the tagging) and then later check it using * g_task_get_source_tag() (or g_async_result_is_tagged()) in the * task's "finish" function, to figure out if the response came from a * particular place. * + * A macro wrapper around this function will automatically set the + * task’s name to the string form of @source_tag if it’s not already + * set, for convenience. + * * Since: 2.36 */ void @@ -1010,7 +1020,8 @@ void * name of the #GSource used for idle completion of the task. * * This function may only be called before the @task is first used in a thread - * other than the one it was constructed in. + * other than the one it was constructed in. It is called automatically by + * g_task_set_source_tag() if not called already. * * Since: 2.60 */ @@ -1246,7 +1257,6 @@ g_task_return (GTask *task, GTaskReturnType type) { GSource *source; - gchar *source_name = NULL; if (type != G_TASK_RETURN_FROM_THREAD) task->ever_returned = TRUE; @@ -1295,10 +1305,22 @@ g_task_return (GTask *task, /* Otherwise, complete in the next iteration */ source = g_idle_source_new (); - source_name = g_strdup_printf ("[gio] %s complete_in_idle_cb", - (task->name != NULL) ? task->name : "(unnamed)"); - g_source_set_name (source, source_name); - g_free (source_name); + + /* Note: in case the task name is NULL we set it as a const string instead + * of going through the concat path which is more expensive and may show in the + * profiler if g_task_return is called very often + */ + if (task->name == NULL) + g_source_set_static_name (source, "[gio] (unnamed) complete_in_idle_cb"); + else + { + gchar *source_name; + + source_name = g_strconcat ("[gio] ", task->name, " complete_in_idle_cb", NULL); + g_source_set_name (source, source_name); + g_free (source_name); + } + g_task_attach_source (task, source, complete_in_idle_cb); g_source_unref (source); } @@ -1994,7 +2016,7 @@ value_free (gpointer value) * * Sets @task's result to @result (by copying it) and completes the task. * - * If @result is %NULL then a #GValue of type #G_TYPE_POINTER + * If @result is %NULL then a #GValue of type %G_TYPE_POINTER * with a value of %NULL will be used for the result. * * This is a very generic low-level method intended primarily for use diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c index 93794b5b3..aeeb40e9b 100644 --- a/gio/gthreadedresolver.c +++ b/gio/gthreadedresolver.c @@ -529,18 +529,56 @@ typedef enum __ns_type { #endif /* __BIONIC__ */ +/* Wrapper around dn_expand() which does associated length checks and returns + * errors as #GError. */ +static gboolean +expand_name (const gchar *rrname, + const guint8 *answer, + const guint8 *end, + const guint8 **p, + gchar *namebuf, + gsize namebuf_len, + GError **error) +{ + int expand_result; + + expand_result = dn_expand (answer, end, *p, namebuf, namebuf_len); + if (expand_result < 0 || end - *p < expand_result) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), rrname); + return FALSE; + } + + *p += expand_result; + + return TRUE; +} + static GVariant * -parse_res_srv (guchar *answer, - guchar *end, - guchar **p) +parse_res_srv (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) { gchar namebuf[1024]; guint16 priority, weight, port; + if (end - *p < 6) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), "SRV"); + return NULL; + } + GETSHORT (priority, *p); GETSHORT (weight, *p); GETSHORT (port, *p); - *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); + + if (!expand_name ("SRV", answer, end, p, namebuf, sizeof (namebuf), error)) + return NULL; return g_variant_new ("(qqqs)", priority, @@ -550,16 +588,28 @@ parse_res_srv (guchar *answer, } static GVariant * -parse_res_soa (guchar *answer, - guchar *end, - guchar **p) +parse_res_soa (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) { gchar mnamebuf[1024]; gchar rnamebuf[1024]; guint32 serial, refresh, retry, expire, ttl; - *p += dn_expand (answer, end, *p, mnamebuf, sizeof (mnamebuf)); - *p += dn_expand (answer, end, *p, rnamebuf, sizeof (rnamebuf)); + if (!expand_name ("SOA", answer, end, p, mnamebuf, sizeof (mnamebuf), error)) + return NULL; + + if (!expand_name ("SOA", answer, end, p, rnamebuf, sizeof (rnamebuf), error)) + return NULL; + + if (end - *p < 20) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), "SOA"); + return NULL; + } GETLONG (serial, *p); GETLONG (refresh, *p); @@ -578,28 +628,40 @@ parse_res_soa (guchar *answer, } static GVariant * -parse_res_ns (guchar *answer, - guchar *end, - guchar **p) +parse_res_ns (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) { gchar namebuf[1024]; - *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); + if (!expand_name ("NS", answer, end, p, namebuf, sizeof (namebuf), error)) + return NULL; return g_variant_new ("(s)", namebuf); } static GVariant * -parse_res_mx (guchar *answer, - guchar *end, - guchar **p) +parse_res_mx (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) { gchar namebuf[1024]; guint16 preference; + if (end - *p < 2) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), "MX"); + return NULL; + } + GETSHORT (preference, *p); - *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); + if (!expand_name ("MX", answer, end, p, namebuf, sizeof (namebuf), error)) + return NULL; return g_variant_new ("(qs)", preference, @@ -607,21 +669,37 @@ parse_res_mx (guchar *answer, } static GVariant * -parse_res_txt (guchar *answer, - guchar *end, - guchar **p) +parse_res_txt (const guint8 *answer, + const guint8 *end, + const guint8 **p, + GError **error) { GVariant *record; GPtrArray *array; - guchar *at = *p; + const guint8 *at = *p; gsize len; + if (end - *p == 0) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), "TXT"); + return NULL; + } + array = g_ptr_array_new_with_free_func (g_free); while (at < end) { len = *(at++); if (len > (gsize) (end - at)) - break; + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */ + _("Error parsing DNS %s record: malformed DNS packet"), "TXT"); + g_ptr_array_free (array, TRUE); + return NULL; + } + g_ptr_array_add (array, g_strndup ((gchar *)at, len)); at += len; } @@ -633,7 +711,7 @@ parse_res_txt (guchar *answer, return record; } -static gint +gint g_resolver_record_type_to_rrtype (GResolverRecordType type) { switch (type) @@ -652,21 +730,23 @@ g_resolver_record_type_to_rrtype (GResolverRecordType type) g_return_val_if_reached (-1); } -static GList * +GList * g_resolver_records_from_res_query (const gchar *rrname, gint rrtype, - guchar *answer, - gint len, + const guint8 *answer, + gssize len, gint herr, GError **error) { - gint count; + uint16_t count; gchar namebuf[1024]; - guchar *end, *p; + const guint8 *end, *p; guint16 type, qclass, rdlength; - HEADER *header; + const HEADER *header; GList *records; GVariant *record; + gsize len_unsigned; + GError *parsing_error = NULL; if (len <= 0) { @@ -689,18 +769,44 @@ g_resolver_records_from_res_query (const gchar *rrname, return NULL; } + /* We know len ≥ 0 now. */ + len_unsigned = (gsize) len; + + if (len_unsigned < sizeof (HEADER)) + { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the first placeholder is a domain name, the + * second is an error message */ + _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet")); + return NULL; + } + records = NULL; header = (HEADER *)answer; p = answer + sizeof (HEADER); - end = answer + len; + end = answer + len_unsigned; /* Skip query */ count = ntohs (header->qdcount); while (count-- && p < end) { - p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); - p += 4; + int expand_result; + + expand_result = dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + if (expand_result < 0 || end - p < expand_result + 4) + { + /* Not possible to recover parsing as the length of the rest of the + * record is unknown or is too short. */ + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the first placeholder is a domain name, the + * second is an error message */ + _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet")); + return NULL; + } + + p += expand_result; + p += 4; /* skip TYPE and CLASS */ /* To silence gcc warnings */ namebuf[0] = namebuf[1]; @@ -710,12 +816,35 @@ g_resolver_records_from_res_query (const gchar *rrname, count = ntohs (header->ancount); while (count-- && p < end) { - p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + int expand_result; + + expand_result = dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + if (expand_result < 0 || end - p < expand_result + 10) + { + /* Not possible to recover parsing as the length of the rest of the + * record is unknown or is too short. */ + g_set_error (&parsing_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the first placeholder is a domain name, the + * second is an error message */ + _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet")); + break; + } + + p += expand_result; GETSHORT (type, p); GETSHORT (qclass, p); p += 4; /* ignore the ttl (type=long) value */ GETSHORT (rdlength, p); + if (end - p < rdlength) + { + g_set_error (&parsing_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + /* Translators: the first placeholder is a domain name, the + * second is an error message */ + _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet")); + break; + } + if (type != rrtype || qclass != C_IN) { p += rdlength; @@ -725,31 +854,40 @@ g_resolver_records_from_res_query (const gchar *rrname, switch (rrtype) { case T_SRV: - record = parse_res_srv (answer, end, &p); + record = parse_res_srv (answer, p + rdlength, &p, &parsing_error); break; case T_MX: - record = parse_res_mx (answer, end, &p); + record = parse_res_mx (answer, p + rdlength, &p, &parsing_error); break; case T_SOA: - record = parse_res_soa (answer, end, &p); + record = parse_res_soa (answer, p + rdlength, &p, &parsing_error); break; case T_NS: - record = parse_res_ns (answer, end, &p); + record = parse_res_ns (answer, p + rdlength, &p, &parsing_error); break; case T_TXT: - record = parse_res_txt (answer, p + rdlength, &p); + record = parse_res_txt (answer, p + rdlength, &p, &parsing_error); break; default: - g_warn_if_reached (); + g_debug ("Unrecognised DNS record type %u", rrtype); record = NULL; break; } if (record != NULL) records = g_list_prepend (records, record); + + if (parsing_error != NULL) + break; } - if (records == NULL) + if (parsing_error != NULL) + { + g_propagate_prefixed_error (error, parsing_error, _("Failed to parse DNS response for “%s”: "), rrname); + g_list_free_full (records, (GDestroyNotify)g_variant_unref); + return NULL; + } + else if (records == NULL) { g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND, _("No DNS record of the requested type for “%s”"), rrname); diff --git a/gio/gthreadedresolver.h b/gio/gthreadedresolver.h index 5900d6a14..8d2ca19bf 100644 --- a/gio/gthreadedresolver.h +++ b/gio/gthreadedresolver.h @@ -42,6 +42,19 @@ typedef struct { GLIB_AVAILABLE_IN_ALL GType g_threaded_resolver_get_type (void) G_GNUC_CONST; +/* Used for a private test API */ +#ifdef G_OS_UNIX +GLIB_AVAILABLE_IN_ALL +GList *g_resolver_records_from_res_query (const gchar *rrname, + gint rrtype, + const guint8 *answer, + gssize len, + gint herr, + GError **error); +GLIB_AVAILABLE_IN_ALL +gint g_resolver_record_type_to_rrtype (GResolverRecordType type); +#endif + G_END_DECLS #endif /* __G_RESOLVER_H__ */ diff --git a/gio/gtlscertificate.c b/gio/gtlscertificate.c index 2c238120c..d0a326b27 100644 --- a/gio/gtlscertificate.c +++ b/gio/gtlscertificate.c @@ -50,7 +50,11 @@ * Since: 2.28 */ -G_DEFINE_ABSTRACT_TYPE (GTlsCertificate, g_tls_certificate, G_TYPE_OBJECT) +struct _GTlsCertificatePrivate { + gboolean pkcs12_properties_not_overridden; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GTlsCertificate, g_tls_certificate, G_TYPE_OBJECT) enum { @@ -69,6 +73,8 @@ enum PROP_ISSUER_NAME, PROP_DNS_NAMES, PROP_IP_ADDRESSES, + PROP_PKCS12_DATA, + PROP_PASSWORD, }; static void @@ -84,11 +90,11 @@ g_tls_certificate_get_property (GObject *object, { switch (prop_id) { + /* Subclasses must override these properties but this allows older backends to not fatally error */ case PROP_PRIVATE_KEY: case PROP_PRIVATE_KEY_PEM: case PROP_PKCS11_URI: case PROP_PRIVATE_KEY_PKCS11_URI: - /* Subclasses must override this property but this allows older backends to not fatally error */ g_value_set_static_string (value, NULL); break; default: @@ -102,11 +108,19 @@ g_tls_certificate_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { + GTlsCertificate *cert = (GTlsCertificate*)object; + GTlsCertificatePrivate *priv = g_tls_certificate_get_instance_private (cert); + switch (prop_id) { case PROP_PKCS11_URI: case PROP_PRIVATE_KEY_PKCS11_URI: - /* Subclasses must override this property but this allows older backends to not fatally error */ + /* Subclasses must override these properties but this allows older backends to not fatally error. */ + break; + case PROP_PKCS12_DATA: + case PROP_PASSWORD: + /* We don't error on setting these properties however we track that they were not overridden. */ + priv->pkcs12_properties_not_overridden = TRUE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -122,6 +136,39 @@ g_tls_certificate_class_init (GTlsCertificateClass *class) gobject_class->get_property = g_tls_certificate_get_property; /** + * GTlsCertificate:pkcs12-data: (nullable) + * + * The PKCS #12 formatted data used to construct the object. + * + * See also: g_tls_certificate_new_from_pkcs12() + * + * Since: 2.72 + */ + g_object_class_install_property (gobject_class, PROP_PKCS12_DATA, + g_param_spec_boxed ("pkcs12-data", + P_("PKCS #12 data"), + P_("The PKCS #12 data used for construction"), + G_TYPE_BYTE_ARRAY, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GTlsCertificate:password: (nullable) + * + * An optional password used when constructed with GTlsCertificate:pkcs12-data. + * + * Since: 2.72 + */ + g_object_class_install_property (gobject_class, PROP_PASSWORD, + g_param_spec_string ("password", + P_("Password"), + P_("Password used when constructing from bytes"), + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + /** * GTlsCertificate:certificate: * * The DER (binary) encoded representation of the certificate. @@ -684,23 +731,138 @@ g_tls_certificate_new_from_pem (const gchar *data, } /** - * g_tls_certificate_new_from_file: - * @file: (type filename): file containing a PEM-encoded certificate to import + * g_tls_certificate_new_from_pkcs12: + * @data: (array length=length): DER-encoded PKCS #12 format certificate data + * @length: the length of @data + * @password: (nullable): optional password for encrypted certificate data * @error: #GError for error reporting, or %NULL to ignore. * - * Creates a #GTlsCertificate from the PEM-encoded data in @file. The - * returned certificate will be the first certificate found in @file. As - * of GLib 2.44, if @file contains more certificates it will try to load - * a certificate chain. All certificates will be verified in the order - * found (top-level certificate should be the last one in the file) and - * the #GTlsCertificate:issuer property of each certificate will be set - * accordingly if the verification succeeds. If any certificate in the - * chain cannot be verified, the first certificate in the file will - * still be returned. + * Creates a #GTlsCertificate from the data in @data. It must contain + * a certificate and matching private key. + * + * If extra certificates are included they will be verified as a chain + * and the #GTlsCertificate:issuer property will be set. + * All other data will be ignored. + * + * You can pass as single password for all of the data which will be + * used both for the PKCS #12 container as well as encrypted + * private keys. If decryption fails it will error with + * %G_TLS_ERROR_BAD_CERTIFICATE_PASSWORD. + * + * This constructor requires support in the current #GTlsBackend. + * If support is missing it will error with + * %G_IO_ERROR_NOT_SUPPORTED. + * + * Other parsing failures will error with %G_TLS_ERROR_BAD_CERTIFICATE. + * + * Returns: the new certificate, or %NULL if @data is invalid + * + * Since: 2.72 + */ +GTlsCertificate * +g_tls_certificate_new_from_pkcs12 (const guint8 *data, + gsize length, + const gchar *password, + GError **error) +{ + GObject *cert; + GTlsBackend *backend; + GByteArray *bytes; + + g_return_val_if_fail (data != NULL || length == 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + backend = g_tls_backend_get_default (); + + bytes = g_byte_array_new (); + g_byte_array_append (bytes, data, length); + + cert = g_initable_new (g_tls_backend_get_certificate_type (backend), + NULL, error, + "pkcs12-data", bytes, + "password", password, + NULL); + + g_byte_array_unref (bytes); + + if (cert) + { + GTlsCertificatePrivate *priv = g_tls_certificate_get_instance_private (G_TLS_CERTIFICATE (cert)); + + if (priv->pkcs12_properties_not_overridden) + { + g_clear_object (&cert); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("The current TLS backend does not support PKCS #12")); + return NULL; + } + } + + return G_TLS_CERTIFICATE (cert); +} + +/** + * g_tls_certificate_new_from_file_with_password: + * @file: (type filename): file containing a certificate to import + * @password: (not nullable): password for PKCS #12 files + * @error: #GError for error reporting, or %NULL to ignore + * + * Creates a #GTlsCertificate from the data in @file. * * If @file cannot be read or parsed, the function will return %NULL and - * set @error. Otherwise, this behaves like - * g_tls_certificate_new_from_pem(). + * set @error. + * + * Any unknown file types will error with %G_IO_ERROR_NOT_SUPPORTED. + * Currently only `.p12` and `.pfx` files are supported. + * See g_tls_certificate_new_from_pkcs12() for more details. + * + * Returns: the new certificate, or %NULL on error + * + * Since: 2.72 + */ +GTlsCertificate * +g_tls_certificate_new_from_file_with_password (const gchar *file, + const gchar *password, + GError **error) +{ + GTlsCertificate *cert; + gchar *contents; + gsize length; + + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (password != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!g_str_has_suffix (file, ".p12") && !g_str_has_suffix (file, ".pfx")) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "The file type of \"%s\" is unknown. Only .p12 and .pfx files are supported currently.", file); + return NULL; + } + + if (!g_file_get_contents (file, &contents, &length, error)) + return NULL; + + cert = g_tls_certificate_new_from_pkcs12 ((guint8 *)contents, length, password, error); + + g_free (contents); + return cert; +} + +/** + * g_tls_certificate_new_from_file: + * @file: (type filename): file containing a certificate to import + * @error: #GError for error reporting, or %NULL to ignore + * + * Creates a #GTlsCertificate from the data in @file. + * + * As of 2.72, if the filename ends in `.p12` or `.pfx` the data is loaded by + * g_tls_certificate_new_from_pkcs12() otherwise it is loaded by + * g_tls_certificate_new_from_pem(). See those functions for + * exact details. + * + * If @file cannot be read or parsed, the function will return %NULL and + * set @error. * * Returns: the new certificate, or %NULL on error * @@ -708,16 +870,23 @@ g_tls_certificate_new_from_pem (const gchar *data, */ GTlsCertificate * g_tls_certificate_new_from_file (const gchar *file, - GError **error) + GError **error) { GTlsCertificate *cert; gchar *contents; gsize length; + g_return_val_if_fail (file != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + if (!g_file_get_contents (file, &contents, &length, error)) return NULL; - cert = g_tls_certificate_new_from_pem (contents, length, error); + if (g_str_has_suffix (file, ".p12") || g_str_has_suffix (file, ".pfx")) + cert = g_tls_certificate_new_from_pkcs12 ((guint8 *)contents, length, NULL, error); + else + cert = g_tls_certificate_new_from_pem (contents, length, error); + g_free (contents); return cert; } @@ -959,13 +1128,18 @@ g_tls_certificate_get_issuer (GTlsCertificate *cert) * @trusted_ca is %NULL, that bit will never be set in the return * value. * - * (All other #GTlsCertificateFlags values will always be set or unset - * as appropriate.) + * GLib guarantees that if certificate verification fails, at least one + * error will be set in the return value, but it does not guarantee + * that all possible errors will be set. Accordingly, you may not safely + * decide to ignore any particular type of error. For example, it would + * be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if you want to allow + * expired certificates, because this could potentially be the only + * error flag set even if other problems exist with the certificate. * * Because TLS session context is not used, #GTlsCertificate may not * perform as many checks on the certificates as #GTlsConnection would. - * For example, certificate constraints cannot be honored, and some - * revocation checks cannot be performed. The best way to verify TLS + * For example, certificate constraints may not be honored, and + * revocation checks may not be performed. The best way to verify TLS * certificates used by a TLS connection is to let #GTlsConnection * handle the verification. * diff --git a/gio/gtlscertificate.h b/gio/gtlscertificate.h index 3b92b97fc..52e678bcb 100644 --- a/gio/gtlscertificate.h +++ b/gio/gtlscertificate.h @@ -63,7 +63,15 @@ GLIB_AVAILABLE_IN_ALL GTlsCertificate *g_tls_certificate_new_from_pem (const gchar *data, gssize length, GError **error); - +GLIB_AVAILABLE_IN_2_72 +GTlsCertificate *g_tls_certificate_new_from_pkcs12 (const guint8 *data, + gsize length, + const gchar *password, + GError **error); +GLIB_AVAILABLE_IN_2_72 +GTlsCertificate *g_tls_certificate_new_from_file_with_password (const gchar *file, + const gchar *password, + GError **error); GLIB_AVAILABLE_IN_ALL GTlsCertificate *g_tls_certificate_new_from_file (const gchar *file, GError **error); diff --git a/gio/gtlsclientconnection.c b/gio/gtlsclientconnection.c index d0a740f4f..63dd6bee7 100644 --- a/gio/gtlsclientconnection.c +++ b/gio/gtlsclientconnection.c @@ -59,7 +59,21 @@ g_tls_client_connection_default_init (GTlsClientConnectionInterface *iface) * ways indicated here will be rejected unless the application * overrides the default via #GTlsConnection::accept-certificate. * + * GLib guarantees that if certificate verification fails, at least one + * flag will be set, but it does not guarantee that all possible flags + * will be set. Accordingly, you may not safely decide to ignore any + * particular type of error. For example, it would be incorrect to mask + * %G_TLS_CERTIFICATE_EXPIRED if you want to allow expired certificates, + * because this could potentially be the only error flag set even if + * other problems exist with the certificate. Therefore, there is no + * safe way to use this property. This is not a horrible problem, + * though, because you should not be attempting to ignore validation + * errors anyway. If you really must ignore TLS certificate errors, + * connect to #GTlsConnection::accept-certificate. + * * Since: 2.28 + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. */ g_object_interface_install_property (iface, g_param_spec_flags ("validation-flags", @@ -69,7 +83,8 @@ g_tls_client_connection_default_init (GTlsClientConnectionInterface *iface) G_TLS_CERTIFICATE_VALIDATE_ALL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | - G_PARAM_STATIC_STRINGS)); + G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); /** * GTlsClientConnection:server-identity: @@ -183,9 +198,15 @@ g_tls_client_connection_new (GIOStream *base_io_stream, * * Gets @conn's validation flags * + * This function does not work as originally designed and is impossible + * to use correctly. See #GTlsClientConnection:validation-flags for more + * information. + * * Returns: the validation flags * * Since: 2.28 + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. */ GTlsCertificateFlags g_tls_client_connection_get_validation_flags (GTlsClientConnection *conn) @@ -207,7 +228,13 @@ g_tls_client_connection_get_validation_flags (GTlsClientConnection *conn) * checks performed when validating a server certificate. By default, * %G_TLS_CERTIFICATE_VALIDATE_ALL is used. * + * This function does not work as originally designed and is impossible + * to use correctly. See #GTlsClientConnection:validation-flags for more + * information. + * * Since: 2.28 + * + * Deprecated: 2.72: Do not attempt to ignore validation errors. */ void g_tls_client_connection_set_validation_flags (GTlsClientConnection *conn, diff --git a/gio/gtlsclientconnection.h b/gio/gtlsclientconnection.h index 29dbafcf4..f592fa808 100644 --- a/gio/gtlsclientconnection.h +++ b/gio/gtlsclientconnection.h @@ -59,9 +59,9 @@ GIOStream * g_tls_client_connection_new (GIOStream GSocketConnectable *server_identity, GError **error); -GLIB_AVAILABLE_IN_ALL +GLIB_DEPRECATED_IN_2_72 GTlsCertificateFlags g_tls_client_connection_get_validation_flags (GTlsClientConnection *conn); -GLIB_AVAILABLE_IN_ALL +GLIB_DEPRECATED_IN_2_72 void g_tls_client_connection_set_validation_flags (GTlsClientConnection *conn, GTlsCertificateFlags flags); GLIB_AVAILABLE_IN_ALL diff --git a/gio/gtlsconnection.c b/gio/gtlsconnection.c index 0239489b7..f930ebabe 100644 --- a/gio/gtlsconnection.c +++ b/gio/gtlsconnection.c @@ -143,6 +143,19 @@ g_tls_connection_class_init (GTlsConnectionClass *klass) * If no certificate database is set, then the default database will be * used. See g_tls_backend_get_default_database(). * + * When using a non-default database, #GTlsConnection must fall back to using + * the #GTlsDatabase to perform certificate verification using + * g_tls_database_verify_chain(), which means certificate verification will + * not be able to make use of TLS session context. This may be less secure. + * For example, if you create your own #GTlsDatabase that just wraps the + * default #GTlsDatabase, you might expect that you have not changed anything, + * but this is not true because you may have altered the behavior of + * #GTlsConnection by causing it to use g_tls_database_verify_chain(). See the + * documentation of g_tls_database_verify_chain() for more details on specific + * security checks that may not be performed. Accordingly, setting a + * non-default database is discouraged except for specialty applications with + * unusual security requirements. + * * Since: 2.30 */ g_object_class_install_property (gobject_class, PROP_DATABASE, @@ -248,6 +261,14 @@ g_tls_connection_class_init (GTlsConnectionClass *klass) * #GTlsConnection::accept-certificate overrode the default * behavior. * + * GLib guarantees that if certificate verification fails, at least + * one error will be set, but it does not guarantee that all possible + * errors will be set. Accordingly, you may not safely decide to + * ignore any particular type of error. For example, it would be + * incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if you want to allow + * expired certificates, because this could potentially be the only + * error flag set even if other problems exist with the certificate. + * * Since: 2.28 */ g_object_class_install_property (gobject_class, PROP_PEER_CERTIFICATE_ERRORS, @@ -339,6 +360,15 @@ g_tls_connection_class_init (GTlsConnectionClass *klass) * signal handler. Otherwise, if no handler accepts the certificate, * the handshake will fail with %G_TLS_ERROR_BAD_CERTIFICATE. * + * GLib guarantees that if certificate verification fails, this signal + * will be emitted with at least one error will be set in @errors, but + * it does not guarantee that all possible errors will be set. + * Accordingly, you may not safely decide to ignore any particular + * type of error. For example, it would be incorrect to ignore + * %G_TLS_CERTIFICATE_EXPIRED if you want to allow expired + * certificates, because this could potentially be the only error flag + * set even if other problems exist with the certificate. + * * For a server-side connection, @peer_cert is the certificate * presented by the client, if this was requested via the server's * #GTlsServerConnection:authentication_mode. On the server side, @@ -470,6 +500,9 @@ g_tls_connection_get_use_system_certdb (GTlsConnection *conn) * client-side connections, unless that bit is not set in * #GTlsClientConnection:validation-flags). * + * There are nonintuitive security implications when using a non-default + * database. See #GTlsConnection:database for details. + * * Since: 2.30 */ void @@ -655,6 +688,8 @@ g_tls_connection_get_peer_certificate (GTlsConnection *conn) * certificate, after the handshake has completed or failed. (It is * not set during the emission of #GTlsConnection::accept-certificate.) * + * See #GTlsConnection:peer-certificate-errors for more information. + * * Returns: @conn's peer's certificate errors * * Since: 2.28 diff --git a/gio/gtlsdatabase.c b/gio/gtlsdatabase.c index 2e5a264e9..7027b121c 100644 --- a/gio/gtlsdatabase.c +++ b/gio/gtlsdatabase.c @@ -469,7 +469,7 @@ g_tls_database_class_init (GTlsDatabaseClass *klass) * certificate in the chain by its #GTlsCertificate:issuer property. * * @purpose describes the purpose (or usage) for which the certificate - * is being used. Typically @purpose will be set to #G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER + * is being used. Typically @purpose will be set to %G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER * which means that the certificate is being used to authenticate a server * (and we are acting as the client). * @@ -485,13 +485,21 @@ g_tls_database_class_init (GTlsDatabaseClass *klass) * used. * * If @chain is found to be valid, then the return value will be 0. If - * @chain is found to be invalid, then the return value will indicate - * the problems found. If the function is unable to determine whether - * @chain is valid or not (eg, because @cancellable is triggered - * before it completes) then the return value will be - * %G_TLS_CERTIFICATE_GENERIC_ERROR and @error will be set - * accordingly. @error is not set when @chain is successfully analyzed - * but found to be invalid. + * @chain is found to be invalid, then the return value will indicate at + * least one problem found. If the function is unable to determine + * whether @chain is valid (for example, because @cancellable is + * triggered before it completes) then the return value will be + * %G_TLS_CERTIFICATE_GENERIC_ERROR and @error will be set accordingly. + * @error is not set when @chain is successfully analyzed but found to + * be invalid. + * + * GLib guarantees that if certificate verification fails, at least one + * error will be set in the return value, but it does not guarantee + * that all possible errors will be set. Accordingly, you may not safely + * decide to ignore any particular type of error. For example, it would + * be incorrect to mask %G_TLS_CERTIFICATE_EXPIRED if you want to allow + * expired certificates, because this could potentially be the only + * error flag set even if other problems exist with the certificate. * * Prior to GLib 2.48, GLib's default TLS backend modified @chain to * represent the certification path built by #GTlsDatabase during @@ -503,14 +511,14 @@ g_tls_database_class_init (GTlsDatabaseClass *klass) * * Because TLS session context is not used, #GTlsDatabase may not * perform as many checks on the certificates as #GTlsConnection would. - * For example, certificate constraints cannot be honored, and some - * revocation checks cannot be performed. The best way to verify TLS + * For example, certificate constraints may not be honored, and + * revocation checks may not be performed. The best way to verify TLS * certificates used by a TLS connection is to let #GTlsConnection * handle the verification. * * The TLS backend may attempt to look up and add missing certificates - * to the chain. Since GLib 2.70, this may involve HTTP requests to - * download missing certificates. + * to the chain. This may involve HTTP requests to download missing + * certificates. * * This function can block. Use g_tls_database_verify_chain_async() to * perform the verification operation asynchronously. diff --git a/gio/gtrashportal.c b/gio/gtrashportal.c index 03c933297..2f739600c 100644 --- a/gio/gtrashportal.c +++ b/gio/gtrashportal.c @@ -89,8 +89,12 @@ g_trash_portal_trash_file (GFile *file, fd = g_open (path, O_RDWR | O_CLOEXEC | O_NOFOLLOW); if (fd == -1 && errno == EISDIR) - /* If it is a directory, fall back to O_PATH */ - fd = g_open (path, O_PATH | O_CLOEXEC | O_RDONLY | O_NOFOLLOW); + /* If it is a directory, fall back to O_PATH. + * Remove O_NOFOLLOW since + * a) we know it is a directory, not a symlink, and + * b) the portal reject this combination + */ + fd = g_open (path, O_PATH | O_CLOEXEC | O_RDONLY); errsv = errno; diff --git a/gio/gunixconnection.c b/gio/gunixconnection.c index e9e2f75f0..e89aba6ca 100644 --- a/gio/gunixconnection.c +++ b/gio/gunixconnection.c @@ -24,7 +24,9 @@ #include <errno.h> #include <string.h> +#ifdef HAVE_UNISTD_H #include <unistd.h> +#endif /** * SECTION:gunixconnection @@ -39,9 +41,12 @@ * It contains functions to do some of the UNIX socket specific * functionality like passing file descriptors. * - * Note that `<gio/gunixconnection.h>` belongs to the UNIX-specific - * GIO interfaces, thus you have to use the `gio-unix-2.0.pc` - * pkg-config file when using it. + * Since GLib 2.72, #GUnixConnection is available on all platforms. It requires + * underlying system support (such as Windows 10 with `AF_UNIX`) at run time. + * + * Before GLib 2.72, `<gio/gunixconnection.h>` belonged to the UNIX-specific GIO + * interfaces, thus you had to use the `gio-unix-2.0.pc` pkg-config file when + * using it. This is no longer necessary since GLib 2.72. * * Since: 2.22 */ @@ -86,6 +91,7 @@ g_unix_connection_send_fd (GUnixConnection *connection, GCancellable *cancellable, GError **error) { +#ifdef G_OS_UNIX GSocketControlMessage *scm; GSocket *socket; @@ -114,6 +120,11 @@ g_unix_connection_send_fd (GUnixConnection *connection, g_object_unref (scm); return TRUE; +#else + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Sending FD is not supported")); + return FALSE; +#endif } /** @@ -139,6 +150,7 @@ g_unix_connection_receive_fd (GUnixConnection *connection, GCancellable *cancellable, GError **error) { +#ifdef G_OS_UNIX GSocketControlMessage **scms; gint *fds, nfd, fd, nscm; GUnixFDMessage *fdmsg; @@ -221,6 +233,11 @@ g_unix_connection_receive_fd (GUnixConnection *connection, } return fd; +#else + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Receiving FD is not supported")); + return -1; +#endif } static void diff --git a/gio/gunixcredentialsmessage.c b/gio/gunixcredentialsmessage.c index 9e5c7d32e..e8ac5a73c 100644 --- a/gio/gunixcredentialsmessage.c +++ b/gio/gunixcredentialsmessage.c @@ -31,6 +31,14 @@ * g_unix_connection_receive_credentials(). To receive credentials of * a foreign process connected to a socket, use * g_socket_get_credentials(). + * + * Since GLib 2.72, #GUnixCredentialMessage is available on all platforms. It + * requires underlying system support (such as Windows 10 with `AF_UNIX`) at run + * time. + * + * Before GLib 2.72, `<gio/gunixcredentialsmessage.h>` belonged to the UNIX-specific + * GIO interfaces, thus you had to use the `gio-unix-2.0.pc` pkg-config file + * when using it. This is no longer necessary since GLib 2.72. */ #include "config.h" @@ -40,7 +48,9 @@ #include <fcntl.h> #include <errno.h> #include <string.h> +#ifdef HAVE_UNISTD_H #include <unistd.h> +#endif #include "gunixcredentialsmessage.h" #include "gcredentials.h" diff --git a/gio/gunixmounts.c b/gio/gunixmounts.c index e0c8f6778..9c8ef5d66 100644 --- a/gio/gunixmounts.c +++ b/gio/gunixmounts.c @@ -1062,7 +1062,6 @@ _g_get_unix_mount_points (void) if ((mount_fstype != NULL && g_strcmp0 ("supermount", mount_fstype) == 0) || ((userspace_flags & MNT_MS_USER) && (g_strstr_len (mount_options, -1, "user_xattr") == NULL)) || - (g_strstr_len (mount_options, -1, "pamconsole") == NULL) || (userspace_flags & MNT_MS_USERS) || (userspace_flags & MNT_MS_OWNER)) { @@ -1158,7 +1157,6 @@ _g_get_unix_mount_points (void) #ifdef HAVE_HASMNTOPT || (hasmntopt (mntent, "user") != NULL && hasmntopt (mntent, "user") != hasmntopt (mntent, "user_xattr")) - || hasmntopt (mntent, "pamconsole") != NULL || hasmntopt (mntent, "users") != NULL || hasmntopt (mntent, "owner") != NULL #endif @@ -1231,7 +1229,6 @@ _g_get_unix_mount_points (void) #ifdef HAVE_HASMNTOPT || (hasmntopt (&mntent, "user") != NULL && hasmntopt (&mntent, "user") != hasmntopt (&mntent, "user_xattr")) - || hasmntopt (&mntent, "pamconsole") != NULL || hasmntopt (&mntent, "users") != NULL || hasmntopt (&mntent, "owner") != NULL #endif @@ -1669,6 +1666,14 @@ g_unix_mount_for (const char *file_path, return entry; } +static gpointer +copy_mount_point_cb (gconstpointer src, + gpointer data) +{ + GUnixMountPoint *src_mount_point = (GUnixMountPoint *) src; + return g_unix_mount_point_copy (src_mount_point); +} + /** * g_unix_mount_points_get: * @time_read: (out) (optional): guint64 to contain a timestamp. @@ -1684,10 +1689,29 @@ g_unix_mount_for (const char *file_path, GList * g_unix_mount_points_get (guint64 *time_read) { + static GList *mnt_pts_last = NULL; + static guint64 time_read_last = 0; + GList *mnt_pts = NULL; + guint64 time_read_now; + G_LOCK_DEFINE_STATIC (unix_mount_points); + + G_LOCK (unix_mount_points); + + time_read_now = get_mount_points_timestamp (); + if (time_read_now != time_read_last || mnt_pts_last == NULL) + { + time_read_last = time_read_now; + g_list_free_full (mnt_pts_last, (GDestroyNotify) g_unix_mount_point_free); + mnt_pts_last = _g_get_unix_mount_points (); + } + mnt_pts = g_list_copy_deep (mnt_pts_last, copy_mount_point_cb, NULL); + + G_UNLOCK (unix_mount_points); + if (time_read) - *time_read = get_mount_points_timestamp (); + *time_read = time_read_now; - return _g_get_unix_mount_points (); + return mnt_pts; } /** diff --git a/gio/gunixsocketaddress.c b/gio/gunixsocketaddress.c index 69204e9b9..f80e8cc88 100644 --- a/gio/gunixsocketaddress.c +++ b/gio/gunixsocketaddress.c @@ -28,6 +28,9 @@ #include "glibintl.h" #include "gnetworking.h" +#ifdef G_OS_WIN32 +#include "giowin32-afunix.h" +#endif /** * SECTION:gunixsocketaddress @@ -45,9 +48,13 @@ * errors. You can use g_unix_socket_address_abstract_names_supported() * to see if abstract names are supported. * - * Note that `<gio/gunixsocketaddress.h>` belongs to the UNIX-specific GIO - * interfaces, thus you have to use the `gio-unix-2.0.pc` pkg-config file - * when using it. + * Since GLib 2.72, #GUnixSocketAddress is available on all platforms. It + * requires underlying system support (such as Windows 10 with `AF_UNIX`) at + * run time. + * + * Before GLib 2.72, `<gio/gunixsocketaddress.h>` belonged to the UNIX-specific + * GIO interfaces, thus you had to use the `gio-unix-2.0.pc` pkg-config file + * when using it. This is no longer necessary since GLib 2.72. */ /** diff --git a/gio/gvolume.c b/gio/gvolume.c index 0e14ddaf6..cb6d34f8b 100644 --- a/gio/gvolume.c +++ b/gio/gvolume.c @@ -66,13 +66,13 @@ * different kinds of identifiers, such as Hal UDIs, filesystem labels, * traditional Unix devices (e.g. `/dev/sda2`), UUIDs. GIO uses predefined * strings as names for the different kinds of identifiers: - * #G_VOLUME_IDENTIFIER_KIND_UUID, #G_VOLUME_IDENTIFIER_KIND_LABEL, etc. + * %G_VOLUME_IDENTIFIER_KIND_UUID, %G_VOLUME_IDENTIFIER_KIND_LABEL, etc. * Use g_volume_get_identifier() to obtain an identifier for a volume. * * - * Note that #G_VOLUME_IDENTIFIER_KIND_HAL_UDI will only be available + * Note that %G_VOLUME_IDENTIFIER_KIND_HAL_UDI will only be available * when the gvfs hal volume monitor is in use. Other volume monitors - * will generally be able to provide the #G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE + * will generally be able to provide the %G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE * identifier, which can be used to obtain a hal device by means of * libhal_manager_find_device_string_match(). */ diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index cafd053b4..26ca6eff2 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -3359,7 +3359,7 @@ uwp_package_cb (gpointer user_data, GPtrArray *supported_extgroups, GPtrArray *supported_protocols) { - gint i, i_verb, i_ext; + guint i, i_verb, i_ext; gint extensions_considered; GWin32AppInfoApplication *app; gchar *app_user_model_id_u8; @@ -3459,7 +3459,7 @@ uwp_package_cb (gpointer user_data, */ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &ext)) { - gint i_hverb; + guint i_hverb; if (!ext) continue; @@ -3476,8 +3476,8 @@ uwp_package_cb (gpointer user_data, } if (app->verbs->len == 0 && extensions_considered > 0) - g_warning ("Unexpectedly, UWP app `%S' (AUMId `%s') supports %d extensions but has no verbs", - full_package_name, app_user_model_id_u8, extensions_considered); + g_debug ("Unexpectedly, UWP app `%S' (AUMId `%s') supports %d extensions but has no verbs", + full_package_name, app_user_model_id_u8, extensions_considered); for (i = 0; i < supported_protocols->len; i++) { @@ -3537,7 +3537,7 @@ uwp_package_cb (gpointer user_data, while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &url)) { - gint i_hverb; + guint i_hverb; if (!url) continue; @@ -4788,7 +4788,7 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, if (apppath) { gchar **p; - gint p_index; + gsize p_index; for (p = envp, p_index = 0; p[0]; p++, p_index++) if ((p[0][0] == 'p' || p[0][0] == 'P') && @@ -4856,7 +4856,13 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, GVariant *platform_data; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); - g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gint32) pid)); + /* pid handles are never bigger than 2^24 as per + * https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects, + * so truncating to `int32` is valid. + * The gsize cast is to silence a compiler warning + * about conversion from pointer to integer of + * different size. */ + g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 ((gsize) pid)); platform_data = g_variant_ref_sink (g_variant_builder_end (&builder)); g_signal_emit_by_name (launch_context, "launched", info, platform_data); @@ -5088,6 +5094,15 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, } static gboolean +g_win32_app_info_should_show (GAppInfo *appinfo) +{ + /* FIXME: This is a placeholder implementation to avoid crashes + * for now. It can be made more specific to @appinfo in future. */ + + return TRUE; +} + +static gboolean g_win32_app_info_launch (GAppInfo *appinfo, GList *files, GAppLaunchContext *launch_context, @@ -5223,7 +5238,7 @@ g_win32_app_info_iface_init (GAppInfoIface *iface) iface->supports_uris = g_win32_app_info_supports_uris; iface->supports_files = g_win32_app_info_supports_files; iface->launch_uris = g_win32_app_info_launch_uris; -/* iface->should_show = g_win32_app_info_should_show;*/ + iface->should_show = g_win32_app_info_should_show; /* iface->set_as_default_for_type = g_win32_app_info_set_as_default_for_type;*/ /* iface->set_as_default_for_extension = g_win32_app_info_set_as_default_for_extension;*/ /* iface->add_supports_type = g_win32_app_info_add_supports_type;*/ diff --git a/gio/gwin32packageparser.c b/gio/gwin32packageparser.c index ee05bb1dd..58b99947c 100755 --- a/gio/gwin32packageparser.c +++ b/gio/gwin32packageparser.c @@ -96,7 +96,7 @@ static BOOL WIN32_FROM_HRESULT (HRESULT hresult, DWORD *win32_error_code) { - if ((hresult & 0xFFFF0000) == MAKE_HRESULT (SEVERITY_ERROR, FACILITY_WIN32, 0) || + if ((hresult & (HRESULT) 0xFFFF0000) == MAKE_HRESULT (SEVERITY_ERROR, FACILITY_WIN32, 0) || hresult == S_OK) { *win32_error_code = HRESULT_CODE (hresult); @@ -815,4 +815,4 @@ xml_parser_iteration (struct _xml_sax_state *sax, } return TRUE; -}
\ No newline at end of file +} diff --git a/gio/gwin32sid.c b/gio/gwin32sid.c new file mode 100644 index 000000000..6112cf171 --- /dev/null +++ b/gio/gwin32sid.c @@ -0,0 +1,234 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2018 Руслан Ижбулатов + * Copyright (C) 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Руслан Ижбулатов <lrn1986@gmail.com> + */ + +#include "config.h" + +#include "gwin32sid.h" +#include "gioerror.h" + +#include <sddl.h> + +/** + * _g_win32_sid_replace: (skip) + * @dest: A pointer to a SID storage + * @src: Existing SID + * @error: return location for a #GError, or %NULL + * + * Creates a copy of the @src SID and puts that into @dest, after freeing + * existing SID in @dest (if any). + * + * The @src SID must be valid (use IsValidSid() to ensure that). + * + * Returns: TRUE on success, FALSE otherwise + */ +static gboolean +_g_win32_sid_replace (SID **dest, + SID *src, + GError **error) +{ + DWORD sid_len; + SID *new_sid; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (dest && *dest == NULL, FALSE); + + sid_len = GetLengthSid (src); + new_sid = g_malloc (sid_len); + + if (!CopySid (sid_len, new_sid, src)) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to copy SID"); + + g_free (new_sid); + return FALSE; + } + else + { + g_free (*dest); + *dest = g_steal_pointer (&new_sid); + + return TRUE; + } +} + +/** + * _g_win32_token_get_sid: (skip) + * @token: A handle of an access token + * @error: return location for a #GError, or %NULL + * + * Gets user SID of the @token and returns a copy of that SID. + * + * Returns: A newly-allocated SID, or NULL in case of an error. + * Free the returned SID with g_free(). + */ +static SID * +_g_win32_token_get_sid (HANDLE token, + GError **error) +{ + TOKEN_USER *token_user = NULL; + DWORD n; + PSID psid; + SID *result = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!GetTokenInformation (token, TokenUser, NULL, 0, &n) + && GetLastError () != ERROR_INSUFFICIENT_BUFFER) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to GetTokenInformation"); + + return NULL; + } + + token_user = g_alloca (n); + + if (!GetTokenInformation (token, TokenUser, token_user, n, &n)) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to GetTokenInformation"); + + return NULL; + } + + psid = token_user->User.Sid; + + if (!IsValidSid (psid)) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Invalid SID token"); + + return NULL; + } + + _g_win32_sid_replace (&result, psid, error); + + return result; +} + +/** + * _g_win32_process_get_access_token_sid: (skip) + * @process_id: Identifier of a process to get an access token of + * (use 0 to get a token of the current process) + * @error: return location for a #GError, or %NULL + * + * Opens the process identified by @process_id and opens its token, + * then retrieves SID of the token user and returns a copy of that SID. + * + * Returns: A newly-allocated SID, or NULL in case of an error. + * Free the returned SID with g_free(). + */ +SID * +_g_win32_process_get_access_token_sid (DWORD process_id, + GError **error) +{ + HANDLE process_handle; + HANDLE process_token; + SID *result = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (process_id == 0) + process_handle = GetCurrentProcess (); + else + process_handle = OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id); + + if (process_handle == NULL) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "%s failed", process_id == 0 ? "GetCurrentProcess" : "OpenProcess"); + + return NULL; + } + + if (!OpenProcessToken (process_handle, TOKEN_QUERY, &process_token)) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "OpenProcessToken failed"); + + CloseHandle (process_handle); + return NULL; + } + + result = _g_win32_token_get_sid (process_token, error); + + CloseHandle (process_token); + CloseHandle (process_handle); + + return result; +} + +/** + * _g_win32_sid_to_string: (skip) + * @sid: a SID. + * @error: return location for a #GError, or %NULL + * + * Convert a SID to its string form. + * + * Returns: A newly-allocated string, or NULL in case of an error. + */ +gchar * +_g_win32_sid_to_string (SID *sid, GError **error) +{ + gchar *tmp, *ret; + + g_return_val_if_fail (sid != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!ConvertSidToStringSidA (sid, &tmp)) + { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to ConvertSidToString"); + + return NULL; + } + + ret = g_strdup (tmp); + LocalFree (tmp); + return ret; +} + +/** + * _g_win32_current_process_sid_string: (skip) + * @error: return location for a #GError, or %NULL + * + * Get the current process SID, as a string. + * + * Returns: A newly-allocated string, or NULL in case of an error. + */ +gchar * +_g_win32_current_process_sid_string (GError **error) +{ + SID *sid; + gchar *ret; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + sid = _g_win32_process_get_access_token_sid (0, error); + if (!sid) + return NULL; + + ret = _g_win32_sid_to_string (sid, error); + g_free (sid); + return ret; +} diff --git a/gio/gwin32sid.h b/gio/gwin32sid.h new file mode 100644 index 000000000..84c037799 --- /dev/null +++ b/gio/gwin32sid.h @@ -0,0 +1,40 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2018 Руслан Ижбулатов + * Copyright (C) 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Руслан Ижбулатов <lrn1986@gmail.com> + */ + +#ifndef __G_WIN32_SID_H__ +#define __G_WIN32_SID_H__ + +#include <glib.h> +#include <windows.h> + +G_BEGIN_DECLS + +SID * _g_win32_process_get_access_token_sid (DWORD process_id, + GError **error); + +gchar * _g_win32_sid_to_string (SID *sid, + GError **error); + +gchar * _g_win32_current_process_sid_string (GError **error); + +G_END_DECLS + +#endif /* __G_WIN32_SID_H__ */ diff --git a/gio/meson.build b/gio/meson.build index 89c6dc6f8..9019104a4 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -74,15 +74,12 @@ if host_system != 'windows' endif endif - # res_init() - if cc.links('''#include <sys/types.h> - #include <netinet/in.h> - #include <arpa/nameser.h> - #include <resolv.h> + # dn_comp() + if cc.links('''#include <resolv.h> int main (int argc, char ** argv) { - return res_init(); - }''', args : network_args, name : 'res_init()') - glib_conf.set('HAVE_RES_INIT', 1) + return dn_comp(NULL, NULL, 0, NULL, NULL) == -1; + } ''', args : network_args, name : 'dn_comp()') + glib_conf.set('HAVE_DN_COMP', 1) endif # res_nclose() @@ -341,12 +338,6 @@ local_sources = files( platform_deps = [] internal_deps = [] -# TODO: internal_objects is a workaround for -# <https://github.com/mesonbuild/meson/issues/3934> and -# <https://github.com/mesonbuild/meson/issues/3937>. When we can depend -# on a meson version where those are fixed, revert the commit that -# introduced this workaround. -internal_objects = [] appinfo_sources = [] contenttype_sources = [] portal_sources = [] @@ -363,13 +354,10 @@ if host_system != 'windows' unix_sources = files( 'gfiledescriptorbased.c', 'giounix-private.c', - 'gunixconnection.c', - 'gunixcredentialsmessage.c', 'gunixfdlist.c', 'gunixfdmessage.c', 'gunixmount.c', 'gunixmounts.c', - 'gunixsocketaddress.c', 'gunixvolume.c', 'gunixvolumemonitor.c', 'gunixinputstream.c', @@ -393,20 +381,19 @@ if host_system != 'windows' gio_unix_include_headers = files( 'gfiledescriptorbased.h', - 'gunixconnection.h', - 'gunixcredentialsmessage.h', 'gunixmounts.h', 'gunixfdlist.h', 'gunixfdmessage.h', 'gunixinputstream.h', 'gunixoutputstream.h', - 'gunixsocketaddress.h', ) if glib_have_cocoa settings_sources += files('gnextstepsettingsbackend.m') contenttype_sources += files('gosxcontenttype.m') appinfo_sources += files('gosxappinfo.m') + framework_dep = dependency('appleframeworks', modules : ['Foundation', 'CoreFoundation', 'AppKit']) + platform_deps += [framework_dep] if glib_have_os_x_9_or_later unix_sources += files('gcocoanotificationbackend.m') endif @@ -419,7 +406,6 @@ if host_system != 'windows' subdir('xdgmime') internal_deps += [xdgmime_lib] - internal_objects += [xdgmime_lib.extract_all_objects(recursive: false)] install_headers(gio_unix_include_headers, subdir : 'gio-unix-2.0/gio') @@ -439,6 +425,7 @@ else platform_deps += uwp_gio_deps win32_sources += files( + 'gmemorymonitorwin32.c', 'gwin32registrykey.c', 'gwin32mount.c', 'gwin32volumemonitor.c', @@ -449,6 +436,8 @@ else 'gwin32networkmonitor.c', 'gwin32networkmonitor.h', 'gwin32notificationbackend.c', + 'gwin32sid.c', + 'gwin32sid.h', ) gio_win_rc = configure_file( @@ -484,6 +473,8 @@ gio_sources = files( 'gdatagrambased.c', 'gdatainputstream.c', 'gdataoutputstream.c', + 'gdebugcontroller.c', + 'gdebugcontrollerdbus.c', 'gdrive.c', 'gdummyfile.c', 'gdummyproxyresolver.c', @@ -582,6 +573,9 @@ gio_sources = files( 'gdtlsclientconnection.c', 'gdtlsserverconnection.c', 'gunionvolumemonitor.c', + 'gunixconnection.c', + 'gunixcredentialsmessage.c', + 'gunixsocketaddress.c', 'gvfs.c', 'gvolume.c', 'gvolumemonitor.c', @@ -631,6 +625,8 @@ gio_headers = files( 'gdatagrambased.h', 'gdatainputstream.h', 'gdataoutputstream.h', + 'gdebugcontroller.h', + 'gdebugcontrollerdbus.h', 'gdrive.h', 'gemblem.h', 'gemblemedicon.h', @@ -717,6 +713,9 @@ gio_headers = files( 'gdtlsconnection.h', 'gdtlsclientconnection.h', 'gdtlsserverconnection.h', + 'gunixconnection.h', + 'gunixcredentialsmessage.h', + 'gunixsocketaddress.h', 'gvfs.h', 'gvolume.h', 'gvolumemonitor.h', @@ -758,20 +757,17 @@ gioenumtypes_dep = declare_dependency(sources : [gioenumtypes_h, glib_enumtypes_ if glib_conf.has('HAVE_SYS_INOTIFY_H') and have_func_inotify_init1 subdir('inotify') internal_deps += [ inotify_lib ] - internal_objects += [inotify_lib.extract_all_objects(recursive: false)] endif # kevent if have_func_kqueue and have_func_kevent subdir('kqueue') internal_deps += [ kqueue_lib ] - internal_objects += [kqueue_lib.extract_all_objects(recursive: false)] endif if host_system == 'windows' subdir('win32') internal_deps += [ giowin32_lib ] - internal_objects += [giowin32_lib.extract_all_objects(recursive: false)] endif if have_bash @@ -807,13 +803,13 @@ endif libgio = library('gio-2.0', gioenumtypes_h, gioenumtypes_c, gnetworking_h, gio_sources, gio_dtrace_hdr, gio_dtrace_obj, - objects : internal_objects, version : library_version, soversion : soversion, darwin_versions : darwin_versions, install : true, include_directories : [configinc, gioinc], # '$(gio_win32_res_ldflag)', + link_with: internal_deps, dependencies : [libz_dep, libdl_dep, libmount_dep, libglib_dep, libgobject_dep, libgmodule_dep, selinux_dep, xattr_dep, platform_deps, network_libs, libsysprof_capture_dep], @@ -835,8 +831,17 @@ libgio_dep = declare_dependency(link_with : libgio, dependencies : [libgmodule_dep, libgobject_dep, gioenumtypes_dep], include_directories : [gioinc]) +# Work around variables kwarg requiring Meson 0.56 +if meson.version().version_compare('>=0.56.0') + libgio_dep = declare_dependency(dependencies: libgio_dep, + variables: [ + 'schemasdir=' + join_paths(glib_datadir, schemas_subdir), + 'giomoduledir=' + glib_giomodulesdir, + ] + ) +endif + pkg.generate(libgio, - libraries_private : [osx_ldflags], requires : ['glib-2.0', 'gobject-2.0'], variables : ['datadir=' + join_paths('${prefix}', get_option('datadir')), 'schemasdir=' + join_paths('${datadir}', schemas_subdir), @@ -872,7 +877,7 @@ if host_system == 'windows' description : 'Windows specific headers for glib I/O library', ) if meson.version().version_compare('>=0.54.0') - meson.override_dependency('gio-win32-2.0', libgio_dep) + meson.override_dependency('gio-windows-2.0', libgio_dep) endif else pkg.generate(requires : ['gobject-2.0', 'gio-2.0'], diff --git a/gio/tests/actions.c b/gio/tests/actions.c index 91fc08074..a41e989bd 100644 --- a/gio/tests/actions.c +++ b/gio/tests/actions.c @@ -115,33 +115,20 @@ test_name (void) } static gboolean -strv_has_string (gchar **haystack, - const gchar *needle) -{ - guint n; - - for (n = 0; haystack != NULL && haystack[n] != NULL; n++) - { - if (g_strcmp0 (haystack[n], needle) == 0) - return TRUE; - } - return FALSE; -} - -static gboolean -strv_strv_cmp (gchar **a, gchar **b) +strv_strv_cmp (const gchar * const *a, + const gchar * const *b) { guint n; for (n = 0; a[n] != NULL; n++) { - if (!strv_has_string (b, a[n])) + if (!g_strv_contains (b, a[n])) return FALSE; } for (n = 0; b[n] != NULL; n++) { - if (!strv_has_string (a, b[n])) + if (!g_strv_contains (a, b[n])) return FALSE; } @@ -149,7 +136,7 @@ strv_strv_cmp (gchar **a, gchar **b) } static gboolean -strv_set_equal (gchar **strv, ...) +strv_set_equal (const gchar * const *strv, ...) { guint count; va_list list; @@ -164,7 +151,7 @@ strv_set_equal (gchar **strv, ...) str = va_arg (list, const gchar *); if (str == NULL) break; - if (!strv_has_string (strv, str)) + if (!g_strv_contains (strv, str)) { res = FALSE; break; @@ -215,7 +202,7 @@ test_simple_group (void) g_assert_false (g_action_group_has_action (G_ACTION_GROUP (group), "baz")); actions = g_action_group_list_actions (G_ACTION_GROUP (group)); g_assert_cmpint (g_strv_length (actions), ==, 2); - g_assert_true (strv_set_equal (actions, "foo", "bar", NULL)); + g_assert_true (strv_set_equal ((const gchar * const *) actions, "foo", "bar", NULL)); g_strfreev (actions); g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "foo")); g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar")); @@ -275,10 +262,12 @@ test_stateful (void) if (g_test_undefined ()) { + GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123)); g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*assertion*g_variant_is_of_type*failed*"); - g_simple_action_set_state (action, g_variant_new_int32 (123)); + g_simple_action_set_state (action, new_state); g_test_assert_expected_messages (); + g_variant_unref (new_state); } g_simple_action_set_state (action, g_variant_new_string ("hello")); @@ -292,10 +281,12 @@ test_stateful (void) if (g_test_undefined ()) { + GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123)); g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*assertion*!= NULL*failed*"); - g_simple_action_set_state (action, g_variant_new_int32 (123)); + g_simple_action_set_state (action, new_state); g_test_assert_expected_messages (); + g_variant_unref (new_state); } g_object_unref (action); @@ -536,6 +527,8 @@ count_activation (const gchar *action) count = GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action)); count++; g_hash_table_insert (activation_counts, (gpointer)action, GINT_TO_POINTER (count)); + + g_main_context_wakeup (NULL); } static gint @@ -593,7 +586,7 @@ compare_action_groups (GActionGroup *a, GActionGroup *b) alist = g_action_group_list_actions (a); blist = g_action_group_list_actions (b); - equal = strv_strv_cmp (alist, blist); + equal = strv_strv_cmp ((const gchar * const *) alist, (const gchar * const *) blist); for (i = 0; equal && alist[i]; i++) { @@ -743,6 +736,41 @@ call_describe (gpointer user_data) G_GNUC_BEGIN_IGNORE_DEPRECATIONS static void +action_added_removed_cb (GActionGroup *action_group, + char *action_name, + gpointer user_data) +{ + guint *counter = user_data; + + *counter = *counter + 1; + g_main_context_wakeup (NULL); +} + +static void +action_enabled_changed_cb (GActionGroup *action_group, + char *action_name, + gboolean enabled, + gpointer user_data) +{ + guint *counter = user_data; + + *counter = *counter + 1; + g_main_context_wakeup (NULL); +} + +static void +action_state_changed_cb (GActionGroup *action_group, + char *action_name, + GVariant *value, + gpointer user_data) +{ + guint *counter = user_data; + + *counter = *counter + 1; + g_main_context_wakeup (NULL); +} + +static void test_dbus_export (void) { GDBusConnection *bus; @@ -754,6 +782,8 @@ test_dbus_export (void) GVariant *v; guint id; gchar **actions; + guint n_actions_added = 0, n_actions_enabled_changed = 0, n_actions_removed = 0, n_actions_state_changed = 0; + gulong added_signal_id, enabled_changed_signal_id, removed_signal_id, state_changed_signal_id; loop = g_main_loop_new (NULL, FALSE); @@ -770,13 +800,19 @@ test_dbus_export (void) g_assert_no_error (error); proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/"); + added_signal_id = g_signal_connect (proxy, "action-added", G_CALLBACK (action_added_removed_cb), &n_actions_added); + enabled_changed_signal_id = g_signal_connect (proxy, "action-enabled-changed", G_CALLBACK (action_enabled_changed_cb), &n_actions_enabled_changed); + removed_signal_id = g_signal_connect (proxy, "action-removed", G_CALLBACK (action_added_removed_cb), &n_actions_removed); + state_changed_signal_id = g_signal_connect (proxy, "action-state-changed", G_CALLBACK (action_state_changed_cb), &n_actions_state_changed); actions = g_action_group_list_actions (G_ACTION_GROUP (proxy)); g_assert_cmpint (g_strv_length (actions), ==, 0); g_strfreev (actions); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + /* Actions are queried from the bus asynchronously after the first + * list_actions() call. Wait for the expected signals then check again. */ + while (n_actions_added < G_N_ELEMENTS (exported_entries)) + g_main_context_iteration (NULL, TRUE); actions = g_action_group_list_actions (G_ACTION_GROUP (proxy)); g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries)); @@ -795,54 +831,56 @@ test_dbus_export (void) g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); /* test that various changes get propagated from group to proxy */ + n_actions_added = 0; action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE)); g_simple_action_group_insert (group, G_ACTION (action)); g_object_unref (action); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_added == 0) + g_main_context_iteration (NULL, TRUE); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "cut")); g_simple_action_set_enabled (action, FALSE); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_enabled_changed == 0) + g_main_context_iteration (NULL, TRUE); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "bold")); g_simple_action_set_state (action, g_variant_new_boolean (FALSE)); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_state_changed == 0) + g_main_context_iteration (NULL, TRUE); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); g_simple_action_group_remove (group, "italic"); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_removed == 0) + g_main_context_iteration (NULL, TRUE); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); /* test that activations and state changes propagate the other way */ - + n_actions_state_changed = 0; g_assert_cmpint (activation_count ("copy"), ==, 0); g_action_group_activate_action (G_ACTION_GROUP (proxy), "copy", NULL); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (activation_count ("copy") == 0) + g_main_context_iteration (NULL, TRUE); g_assert_cmpint (activation_count ("copy"), ==, 1); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); + n_actions_state_changed = 0; g_assert_cmpint (activation_count ("bold"), ==, 0); g_action_group_activate_action (G_ACTION_GROUP (proxy), "bold", NULL); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_state_changed == 0) + g_main_context_iteration (NULL, TRUE); g_assert_cmpint (activation_count ("bold"), ==, 1); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); @@ -850,10 +888,11 @@ test_dbus_export (void) g_assert_true (g_variant_get_boolean (v)); g_variant_unref (v); + n_actions_state_changed = 0; g_action_group_change_action_state (G_ACTION_GROUP (proxy), "bold", g_variant_new_boolean (FALSE)); - g_timeout_add (100, stop_loop, loop); - g_main_loop_run (loop); + while (n_actions_state_changed == 0) + g_main_context_iteration (NULL, TRUE); g_assert_cmpint (activation_count ("bold"), ==, 1); g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy))); @@ -863,6 +902,10 @@ test_dbus_export (void) g_dbus_connection_unexport_action_group (bus, id); + g_signal_handler_disconnect (proxy, added_signal_id); + g_signal_handler_disconnect (proxy, enabled_changed_signal_id); + g_signal_handler_disconnect (proxy, removed_signal_id); + g_signal_handler_disconnect (proxy, state_changed_signal_id); g_object_unref (proxy); g_object_unref (group); g_main_loop_unref (loop); diff --git a/gio/tests/appmonitor.c b/gio/tests/appmonitor.c index 725d91e5c..9db8c4dea 100644 --- a/gio/tests/appmonitor.c +++ b/gio/tests/appmonitor.c @@ -78,6 +78,11 @@ test_app_monitor (Fixture *fixture, GAppInfoMonitor *monitor; GMainLoop *loop; +#ifdef G_OS_WIN32 + g_test_skip (".desktop monitor on win32"); + return; +#endif + app_path = g_build_filename (fixture->applications_dir, "app.desktop", NULL); /* FIXME: this shouldn't be required */ diff --git a/gio/tests/cert-tests/key-cert-password-123.p12 b/gio/tests/cert-tests/key-cert-password-123.p12 Binary files differnew file mode 100644 index 000000000..4da265fd6 --- /dev/null +++ b/gio/tests/cert-tests/key-cert-password-123.p12 diff --git a/gio/tests/codegen.py b/gio/tests/codegen.py index d3a09bae7..e76c6d243 100644 --- a/gio/tests/codegen.py +++ b/gio/tests/codegen.py @@ -27,10 +27,10 @@ import subprocess import sys import tempfile import unittest +import xml.etree.ElementTree as ET import taptestrunner - # Disable line length warnings as wrapping the C code templates would be hard # flake8: noqa: E501 @@ -38,6 +38,10 @@ import taptestrunner Result = collections.namedtuple("Result", ("info", "out", "err", "subs")) +def on_win32(): + return sys.platform.find('win') != -1 + + class TestCodegen(unittest.TestCase): """Integration test for running gdbus-codegen. @@ -55,7 +59,7 @@ class TestCodegen(unittest.TestCase): cwd = "" def setUp(self): - self.timeout_seconds = 10 # seconds per test + self.timeout_seconds = 100 # seconds per test self.tmpdir = tempfile.TemporaryDirectory() self.cwd = os.getcwd() os.chdir(self.tmpdir.name) @@ -281,6 +285,7 @@ class TestCodegen(unittest.TestCase): with self.assertRaises(subprocess.CalledProcessError): self.runCodegen() + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_empty_interface_header(self): """Test generating a header with an empty interface file.""" result = self.runCodegenWithInterface("", "--output", "/dev/stdout", "--header") @@ -304,6 +309,7 @@ G_END_DECLS result.out.strip(), ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_empty_interface_body(self): """Test generating a body with an empty interface file.""" result = self.runCodegenWithInterface("", "--output", "/dev/stdout", "--body") @@ -323,6 +329,7 @@ G_END_DECLS result.out.strip(), ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_reproducible(self): """Test builds are reproducible regardless of file ordering.""" xml_contents1 = """ @@ -382,6 +389,47 @@ G_END_DECLS # The output should be the same. self.assertEqual(result1.out, result2.out) + def test_generate_docbook(self): + """Test the basic functionality of the docbook generator.""" + xml_contents = """ + <node> + <interface name="org.project.Bar.Frobnicator"> + <method name="RandomMethod"/> + </interface> + </node> + """ + res = self.runCodegenWithInterface( + xml_contents, + "--generate-docbook", + "test", + ) + self.assertEqual("", res.err) + self.assertEqual("", res.out) + with open("test-org.project.Bar.Frobnicator.xml", "r") as f: + xml_data = f.readlines() + self.assertTrue(len(xml_data) != 0) + + def test_generate_rst(self): + """Test the basic functionality of the rst generator.""" + xml_contents = """ + <node> + <interface name="org.project.Bar.Frobnicator"> + <method name="RandomMethod"/> + </interface> + </node> + """ + res = self.runCodegenWithInterface( + xml_contents, + "--generate-rst", + "test", + ) + self.assertEqual("", res.err) + self.assertEqual("", res.out) + with open("test-org.project.Bar.Frobnicator.rst", "r") as f: + rst = f.readlines() + self.assertTrue(len(rst) != 0) + + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_invalid(self): """Test running with an invalid --glib-min-required.""" with self.assertRaises(subprocess.CalledProcessError): @@ -394,6 +442,7 @@ G_END_DECLS "hello mum", ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_too_low(self): """Test running with a --glib-min-required which is too low (and hence probably a typo).""" @@ -402,6 +451,7 @@ G_END_DECLS "", "--output", "/dev/stdout", "--body", "--glib-min-required", "2.6" ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_major_only(self): """Test running with a --glib-min-required which contains only a major version.""" result = self.runCodegenWithInterface( @@ -417,6 +467,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_min_required_with_micro(self): """Test running with a --glib-min-required which contains a micro version.""" result = self.runCodegenWithInterface( @@ -425,6 +476,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_too_low(self): """Test running with a --glib-max-allowed which is too low (and hence probably a typo).""" @@ -433,6 +485,7 @@ G_END_DECLS "", "--output", "/dev/stdout", "--body", "--glib-max-allowed", "2.6" ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_major_only(self): """Test running with a --glib-max-allowed which contains only a major version.""" result = self.runCodegenWithInterface( @@ -441,6 +494,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_with_micro(self): """Test running with a --glib-max-allowed which contains a micro version.""" result = self.runCodegenWithInterface( @@ -449,6 +503,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_unstable(self): """Test running with a --glib-max-allowed which is unstable. It should be rounded up to the next stable version number, and hence should not @@ -466,6 +521,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertNotEqual("", result.out.strip()) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_glib_max_allowed_less_than_min_required(self): """Test running with a --glib-max-allowed which is less than --glib-min-required.""" @@ -481,6 +537,7 @@ G_END_DECLS "2.64", ) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_unix_fd_types_and_annotations(self): """Test an interface with `h` arguments, no annotation, and GLib < 2.64. @@ -539,6 +596,7 @@ G_END_DECLS self.assertEqual("", result.err) self.assertEqual(result.out.strip().count("GUnixFDList"), 18) + @unittest.skipIf(on_win32(), "requires /dev/stdout") def test_call_flags_and_timeout_method_args(self): """Test that generated method call functions have @call_flags and @timeout_msec args if and only if GLib >= 2.64. @@ -585,6 +643,40 @@ G_END_DECLS self.assertEqual(result.out.strip().count("GDBusCallFlags call_flags,"), 2) self.assertEqual(result.out.strip().count("gint timeout_msec,"), 2) + def test_generate_valid_docbook(self): + """Test the basic functionality of the docbook generator.""" + xml_contents = """ + <node> + <interface name="org.project.Bar.Frobnicator"> + <!-- Resize: + @size: New partition size in bytes, 0 for maximal size. + @options: Options. + @since 2.7.2 + + Resizes the partition. + + The partition will not change its position but might be slightly bigger + than requested due to sector counts and alignment (e.g. 1MiB). + If the requested size can't be allocated it results in an error. + The maximal size can automatically be set by using 0 as size. + --> + <method name="Resize"> + <arg name="size" direction="in" type="t"/> + <arg name="options" direction="in" type="a{sv}"/> + </method> + </interface> + </node> + """ + res = self.runCodegenWithInterface( + xml_contents, + "--generate-docbook", + "test", + ) + self.assertEqual("", res.err) + self.assertEqual("", res.out) + with open("test-org.project.Bar.Frobnicator.xml", "r") as f: + self.assertTrue(ET.parse(f) is not None) + if __name__ == "__main__": unittest.main(testRunner=taptestrunner.TAPTestRunner()) diff --git a/gio/tests/contenttype.c b/gio/tests/contenttype.c index db34f1da8..6cfd366ff 100644 --- a/gio/tests/contenttype.c +++ b/gio/tests/contenttype.c @@ -1,8 +1,6 @@ #include <gio/gio.h> #include <string.h> -#include "glib/glib-private.h" - #define g_assert_content_type_equals(s1, s2) \ do { \ const char *__s1 = (s1), *__s2 = (s2); \ @@ -18,9 +16,6 @@ static void test_guess (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *res; gchar *expected; gchar *existing_directory; @@ -35,7 +30,7 @@ test_guess (void) existing_directory = (gchar *) g_getenv ("SYSTEMROOT"); if (existing_directory) - existing_directory = g_strdup_printf ("%s/", existing_directory); + existing_directory = g_strdup_printf ("%s" G_DIR_SEPARATOR_S, existing_directory); #else existing_directory = g_strdup ("/etc/"); #endif @@ -61,7 +56,8 @@ test_guess (void) g_free (res); g_free (expected); - /* Sadly OSX just doesn't have as large and robust of a mime type database as Linux */ + /* Sadly win32 & OSX just don't have as large and robust of a mime type database as Linux */ +#ifndef G_OS_WIN32 #ifndef __APPLE__ res = g_content_type_guess ("foo", data, sizeof (data) - 1, &uncertain); expected = g_content_type_from_mime_type ("text/plain"); @@ -115,7 +111,7 @@ test_guess (void) g_assert_false (uncertain); g_free (res); g_free (expected); -#endif +#endif /* __APPLE__ */ res = g_content_type_guess (NULL, (guchar *)"%!PS-Adobe-2.0 EPSF-1.2", 23, &uncertain); expected = g_content_type_from_mime_type ("image/x-eps"); @@ -131,15 +127,12 @@ test_guess (void) g_assert_false (uncertain); g_free (res); g_free (expected); -#endif +#endif /* G_OS_WIN32 */ } static void test_unknown (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *unknown; gchar *str; @@ -149,15 +142,11 @@ test_unknown (void) g_assert_cmpstr (str, ==, "application/octet-stream"); g_free (str); g_free (unknown); -#endif } static void test_subtype (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *plain; gchar *xml; @@ -169,7 +158,6 @@ test_subtype (void) g_free (plain); g_free (xml); -#endif } static gint @@ -183,10 +171,6 @@ find_mime (gconstpointer a, gconstpointer b) static void test_list (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); - (void) find_mime; -#else GList *types; gchar *plain; gchar *xml; @@ -211,17 +195,20 @@ test_list (void) g_free (plain); g_free (xml); -#endif } static void test_executable (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *type; +#ifdef G_OS_WIN32 + type = g_content_type_from_mime_type ("application/vnd.microsoft.portable-executable"); + /* FIXME: the MIME is not in the default `MIME\Database\Content Type` registry. + * g_assert_true (g_content_type_can_be_executable (type)); + */ + g_free (type); +#else type = g_content_type_from_mime_type ("application/x-executable"); g_assert_true (g_content_type_can_be_executable (type)); g_free (type); @@ -229,19 +216,15 @@ test_executable (void) type = g_content_type_from_mime_type ("text/plain"); g_assert_true (g_content_type_can_be_executable (type)); g_free (type); - +#endif type = g_content_type_from_mime_type ("image/png"); g_assert_false (g_content_type_can_be_executable (type)); g_free (type); -#endif } static void test_description (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *type; gchar *desc; @@ -251,15 +234,11 @@ test_description (void) g_free (desc); g_free (type); -#endif } static void test_icon (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gchar *type; GIcon *icon; @@ -274,7 +253,9 @@ test_icon (void) #ifdef __APPLE__ g_assert_true (g_strv_contains (names, "text-*")); #else +#ifndef G_OS_WIN32 g_assert_true (g_strv_contains (names, "text-plain")); +#endif g_assert_true (g_strv_contains (names, "text-x-generic")); #endif } @@ -289,22 +270,23 @@ test_icon (void) const gchar *const *names; names = g_themed_icon_get_names (G_THEMED_ICON (icon)); +#ifdef G_OS_WIN32 + g_assert_true (g_strv_contains (names, "text-x-generic")); +#else g_assert_true (g_strv_contains (names, "application-rtf")); #ifndef __APPLE__ g_assert_true (g_strv_contains (names, "x-office-document")); #endif +#endif } g_object_unref (icon); g_free (type); -#endif } static void test_symbolic_icon (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#elif !defined(G_OS_WIN32) +#ifndef G_OS_WIN32 gchar *type; GIcon *icon; @@ -352,9 +334,6 @@ test_symbolic_icon (void) static void test_tree (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else const gchar *tests[] = { "x-content/image-dcf", "x-content/unix-software", @@ -365,8 +344,8 @@ test_tree (void) gchar **types; gsize i; -#ifdef __APPLE__ - g_test_skip ("The OSX backend does not implement g_content_type_guess_for_tree()"); +#if defined(__APPLE__) || defined(G_OS_WIN32) + g_test_skip ("The OSX & Windows backends do not implement g_content_type_guess_for_tree()"); return; #endif @@ -379,15 +358,11 @@ test_tree (void) g_strfreev (types); g_object_unref (file); } -#endif } static void test_type_is_a_special_case (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else gboolean res; g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=782311"); @@ -395,19 +370,15 @@ test_type_is_a_special_case (void) /* Everything but the inode type is application/octet-stream */ res = g_content_type_is_a ("inode/directory", "application/octet-stream"); g_assert_false (res); -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(G_OS_WIN32) res = g_content_type_is_a ("anything", "application/octet-stream"); g_assert_true (res); #endif -#endif } static void test_guess_svg_from_data (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#else const gchar svgfilecontent[] = "<svg xmlns=\"http://www.w3.org/2000/svg\"\ xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\ <rect x=\"10\" y=\"10\" height=\"100\" width=\"100\"\n\ @@ -426,15 +397,12 @@ test_guess_svg_from_data (void) #endif g_assert_false (uncertain); g_free (res); -#endif } static void test_mime_from_content (void) { -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_incomplete ("FIXME: Leaks xdgmime internal data, see glib#2310"); -#elif defined(__APPLE__) +#ifdef __APPLE__ gchar *mime_type; mime_type = g_content_type_get_mime_type ("com.microsoft.bmp"); g_assert_cmpstr (mime_type, ==, "image/bmp"); diff --git a/gio/tests/converter-stream.c b/gio/tests/converter-stream.c index cf1853ba0..31399a78e 100644 --- a/gio/tests/converter-stream.c +++ b/gio/tests/converter-stream.c @@ -310,9 +310,9 @@ test_expander (void) G_CONVERTER_INPUT_AT_END, &n_read, &n_written, NULL); - g_assert (cres == G_CONVERTER_FINISHED); - g_assert (n_read == 11); - g_assert (n_written == 41030); + g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED); + g_assert_cmpuint (n_read, ==, 11); + g_assert_cmpuint (n_written, ==, 41030); g_converter_reset (expander); @@ -320,9 +320,9 @@ test_expander (void) sizeof (unexpanded_data), NULL); cstream = g_converter_input_stream_new (mem, expander); - g_assert (g_converter_input_stream_get_converter (G_CONVERTER_INPUT_STREAM (cstream)) == expander); + g_assert_true (g_converter_input_stream_get_converter (G_CONVERTER_INPUT_STREAM (cstream)) == expander); g_object_get (cstream, "converter", &converter, NULL); - g_assert (converter == expander); + g_assert_true (converter == expander); g_object_unref (converter); g_object_unref (mem); @@ -334,7 +334,7 @@ test_expander (void) res = g_input_stream_read (cstream, ptr, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) break; ptr += res; @@ -347,9 +347,9 @@ test_expander (void) mem_out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); cstream_out = g_converter_output_stream_new (mem_out, expander); - g_assert (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (cstream_out)) == expander); + g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (cstream_out)) == expander); g_object_get (cstream_out, "converter", &converter, NULL); - g_assert (converter == expander); + g_assert_true (converter == expander); g_object_unref (converter); g_object_unref (mem_out); @@ -359,13 +359,13 @@ test_expander (void) res = g_output_stream_write (cstream_out, unexpanded_data + i, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) { - g_assert (i == sizeof(unexpanded_data) -1); + g_assert_cmpuint (i, ==, sizeof(unexpanded_data) -1); break; } - g_assert (res == 1); + g_assert_cmpint (res, ==, 1); } g_output_stream_close (cstream_out, NULL, NULL); @@ -402,9 +402,9 @@ test_compressor (void) expanded, 100*1000, G_CONVERTER_INPUT_AT_END, &n_read, &expanded_size, NULL); - g_assert (cres == G_CONVERTER_FINISHED); - g_assert (n_read == 11); - g_assert (expanded_size == 41030); + g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED); + g_assert_cmpuint (n_read, ==, 11); + g_assert_cmpuint (expanded_size, ==, 41030); compressor = g_compressor_converter_new (); @@ -424,7 +424,7 @@ test_compressor (void) res = g_input_stream_read (cstream, ptr, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) break; ptr += res; @@ -448,13 +448,13 @@ test_compressor (void) res = g_output_stream_write (cstream_out, expanded + i, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) { - g_assert (i == expanded_size -1); + g_assert_cmpuint (i, ==, expanded_size -1); break; } - g_assert (res == 1); + g_assert_cmpint (res, ==, 1); } g_output_stream_close (cstream_out, NULL, NULL); @@ -485,15 +485,15 @@ test_compressor (void) res = g_input_stream_read (cstream, ptr, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) break; ptr += res; total_read += res; } - g_assert (total_read == 1); - g_assert (*converted == 5); + g_assert_cmpuint (total_read, ==, 1); + g_assert_cmpuint (*converted, ==, 5); g_object_unref (cstream); @@ -511,16 +511,16 @@ test_compressor (void) res = g_input_stream_read (cstream, ptr, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) break; ptr += res; total_read += res; } - g_assert (total_read == 2); - g_assert (converted[0] == 5); - g_assert (converted[1] == 5); + g_assert_cmpuint (total_read, ==, 2); + g_assert_cmpuint (converted[0], ==, 5); + g_assert_cmpuint (converted[1], ==, 5); g_object_unref (cstream); @@ -547,13 +547,13 @@ test_compressor (void) break; } - g_assert (res != 0); + g_assert_cmpint (res, !=, 0); ptr += res; total_read += res; } - g_assert (total_read == 1); - g_assert (converted[0] == 5); + g_assert_cmpuint (total_read, ==, 1); + g_assert_cmpuint (converted[0], ==, 5); g_object_unref (cstream); @@ -710,7 +710,7 @@ test_converter_leftover (void) converted + total_read, LEFTOVER_BUFSIZE - total_read, NULL, &error); - g_assert (res >= 0); + g_assert_cmpint (res, >=, 0); if (res == 0) break; total_read += res; @@ -763,10 +763,10 @@ test_roundtrip (gconstpointer data) g_file_info_set_name (info, "foo"); g_object_set (compressor, "file-info", info, NULL); info2 = g_zlib_compressor_get_file_info (G_ZLIB_COMPRESSOR (compressor)); - g_assert (info == info2); + g_assert_true (info == info2); g_object_unref (info); costream1 = g_converter_output_stream_new (ostream1, compressor); - g_assert (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor); + g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor); g_output_stream_splice (costream1, istream0, 0, NULL, &error); g_assert_no_error (error); @@ -829,7 +829,7 @@ test_charset (gconstpointer data) conv = (GConverter *)g_charset_converter_new (test->charset_out, test->charset_in, NULL); g_object_get (conv, "use-fallback", &fallback, NULL); - g_assert (!fallback); + g_assert_false (fallback); in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL); in2 = g_converter_input_stream_new (in, conv); @@ -862,7 +862,7 @@ test_charset (gconstpointer data) g_converter_reset (conv); - g_assert (!g_charset_converter_get_use_fallback (G_CHARSET_CONVERTER (conv))); + g_assert_false (g_charset_converter_get_use_fallback (G_CHARSET_CONVERTER (conv))); g_charset_converter_set_use_fallback (G_CHARSET_CONVERTER (conv), TRUE); in = g_memory_input_stream_new_from_data (test->text_in, -1, NULL); @@ -981,9 +981,9 @@ test_converter_pollable (void) expanded, 100*1000, G_CONVERTER_INPUT_AT_END, &n_read, &expanded_size, NULL); - g_assert (cres == G_CONVERTER_FINISHED); - g_assert (n_read == 11); - g_assert (expanded_size == 41030); + g_assert_cmpint (cres, ==, G_CONVERTER_FINISHED); + g_assert_cmpuint (n_read, ==, 11); + g_assert_cmpuint (expanded_size, ==, 41030); expanded_end = expanded + expanded_size; make_socketpair (&left, &right); @@ -995,7 +995,7 @@ test_converter_pollable (void) cstream = g_converter_input_stream_new (g_io_stream_get_input_stream (left), compressor); pollable_in = G_POLLABLE_INPUT_STREAM (cstream); - g_assert (g_pollable_input_stream_can_poll (pollable_in)); + g_assert_true (g_pollable_input_stream_can_poll (pollable_in)); socket_out = g_io_stream_get_output_stream (right); @@ -1017,15 +1017,16 @@ test_converter_pollable (void) } else if (socket_out) { + g_output_stream_close (socket_out, NULL, NULL); g_object_unref (right); socket_out = NULL; } /* Wait a few ticks to check for the pipe to propagate the - * write. Finesses the race condition in the following test, - * where is_readable fails because the write hasn't propagated, - * but the read then succeeds because it has. */ - g_usleep (80L); + * write. We can’t wait on a GSource as that might affect the stream under + * test, so just poll. */ + while (!g_pollable_input_stream_is_readable (pollable_in)) + g_usleep (80L); is_readable = g_pollable_input_stream_is_readable (pollable_in); res = g_pollable_input_stream_read_nonblocking (pollable_in, @@ -1038,7 +1039,10 @@ test_converter_pollable (void) /* After closing the write end, we can't get WOULD_BLOCK any more */ if (!socket_out) - g_assert_cmpint (res, !=, -1); + { + g_assert_no_error (error); + g_assert_cmpint (res, !=, -1); + } if (res == -1) { @@ -1071,8 +1075,8 @@ test_converter_pollable (void) cstream_out = g_converter_output_stream_new (mem_out, compressor); g_object_unref (mem_out); pollable_out = G_POLLABLE_OUTPUT_STREAM (cstream_out); - g_assert (g_pollable_output_stream_can_poll (pollable_out)); - g_assert (g_pollable_output_stream_is_writable (pollable_out)); + g_assert_true (g_pollable_output_stream_can_poll (pollable_out)); + g_assert_true (g_pollable_output_stream_is_writable (pollable_out)); for (i = 0; i < expanded_size; i++) { @@ -1080,13 +1084,13 @@ test_converter_pollable (void) res = g_pollable_output_stream_write_nonblocking (pollable_out, expanded + i, 1, NULL, &error); - g_assert (res != -1); + g_assert_cmpint (res, !=, -1); if (res == 0) { - g_assert (i == expanded_size -1); + g_assert_cmpuint (i, ==, expanded_size -1); break; } - g_assert (res == 1); + g_assert_cmpint (res, ==, 1); } g_output_stream_close (cstream_out, NULL, NULL); @@ -1127,7 +1131,7 @@ test_truncation (gconstpointer data) ostream1 = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); compressor = G_CONVERTER (g_zlib_compressor_new (test->format, -1)); costream1 = g_converter_output_stream_new (ostream1, compressor); - g_assert (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor); + g_assert_true (g_converter_output_stream_get_converter (G_CONVERTER_OUTPUT_STREAM (costream1)) == compressor); g_output_stream_splice (costream1, istream0, 0, NULL, &error); g_assert_no_error (error); diff --git a/gio/tests/credentials.c b/gio/tests/credentials.c index 2b0f1f787..070019f1c 100644 --- a/gio/tests/credentials.c +++ b/gio/tests/credentials.c @@ -25,6 +25,28 @@ #include <gio/gio.h> #include <gio/gcredentialsprivate.h> +#ifdef G_OS_WIN32 + +static void +test_basic (void) +{ + GCredentials *creds = g_credentials_new (); + gchar *stringified; + DWORD *pid; + + stringified = g_credentials_to_string (creds); + g_test_message ("%s", stringified); + g_free (stringified); + + pid = g_credentials_get_native (creds, + G_CREDENTIALS_TYPE_WIN32_PID); + g_assert_cmpuint (*pid, ==, GetCurrentProcessId ()); + + g_object_unref (creds); +} + +#else + static void test_basic (void) { @@ -177,6 +199,8 @@ test_basic (void) g_object_unref (other); } +#endif /* !G_OS_WIN32 */ + int main (int argc, char *argv[]) diff --git a/gio/tests/cxx.cpp b/gio/tests/cxx.cpp new file mode 100644 index 000000000..1f28d0b27 --- /dev/null +++ b/gio/tests/cxx.cpp @@ -0,0 +1,26 @@ +/* Copyright (C) 2001 Sebastian Wilhelmi <wilhelmi@google.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +/* A trivial C++ program to be compiled in C++ mode, which + * smoketests that the GIO headers are valid C++ headers. */ + +#include <gio/gio.h> + +int +main () +{ + return 0; +} diff --git a/gio/tests/debugcontroller.c b/gio/tests/debugcontroller.c new file mode 100644 index 000000000..c20acd659 --- /dev/null +++ b/gio/tests/debugcontroller.c @@ -0,0 +1,396 @@ +/* GLib testing framework examples and tests + * + * Copyright © 2022 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Author: Philip Withnall <pwithnall@endlessos.org> + */ + +#include <gio/gio.h> +#include <locale.h> + + +static void +test_dbus_basic (void) +{ + GTestDBus *bus; + GDBusConnection *connection = NULL, *connection2 = NULL; + GDebugControllerDBus *controller = NULL; + gboolean old_value; + gboolean debug_enabled; + GError *local_error = NULL; + + g_test_summary ("Smoketest for construction and setting of a #GDebugControllerDBus."); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller); + g_assert_true (G_IS_DEBUG_CONTROLLER_DBUS (controller)); + + /* Try enabling and disabling debug output from within the process. */ + old_value = g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)); + + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), TRUE); + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller))); + + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), FALSE); + g_assert_false (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller))); + + /* Reset the debug state and check using g_object_get(), to exercise that. */ + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), old_value); + + g_object_get (G_OBJECT (controller), + "debug-enabled", &debug_enabled, + "connection", &connection2, + NULL); + g_assert_true (debug_enabled == old_value); + g_assert_true (connection2 == connection); + g_clear_object (&connection2); + + g_debug_controller_dbus_stop (controller); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller); + g_clear_object (&connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +static void +test_dbus_duplicate (void) +{ + GTestDBus *bus; + GDBusConnection *connection = NULL; + GDebugControllerDBus *controller1 = NULL, *controller2 = NULL; + GError *local_error = NULL; + + g_test_summary ("Test that creating a second #GDebugControllerDBus on the same D-Bus connection fails."); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller1 = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller1); + + /* And try creating a second one. */ + controller2 = g_debug_controller_dbus_new (connection, NULL, &local_error); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS); + g_assert_null (controller2); + g_clear_error (&local_error); + + g_debug_controller_dbus_stop (controller1); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller1); + g_clear_object (&connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + g_assert_null (*result_out); + *result_out = g_object_ref (result); + + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static gboolean +authorize_false_cb (GDebugControllerDBus *debug_controller, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + return FALSE; +} + +static gboolean +authorize_true_cb (GDebugControllerDBus *debug_controller, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + return TRUE; +} + +static void +notify_debug_enabled_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + guint *notify_count_out = user_data; + + *notify_count_out = *notify_count_out + 1; +} + +static void +properties_changed_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint *properties_changed_count_out = user_data; + + *properties_changed_count_out = *properties_changed_count_out + 1; + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static void +test_dbus_properties (void) +{ + GTestDBus *bus; + GDBusConnection *controller_connection = NULL; + GDBusConnection *remote_connection = NULL; + GDebugControllerDBus *controller = NULL; + gboolean old_value; + GAsyncResult *result = NULL; + GVariant *reply = NULL; + GVariant *debug_enabled_variant = NULL; + gboolean debug_enabled; + GError *local_error = NULL; + gulong handler_id; + gulong notify_id; + guint notify_count = 0; + guint properties_changed_id; + guint properties_changed_count = 0; + + g_test_summary ("Test getting and setting properties on a #GDebugControllerDBus."); + + /* Set up a test session bus and connection. Set up a separate second + * connection to simulate a remote peer. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + controller_connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + + remote_connection = g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (bus), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, + NULL, + &local_error); + g_assert_no_error (local_error); + + /* Create a controller for this process. */ + controller = g_debug_controller_dbus_new (controller_connection, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (controller); + g_assert_true (G_IS_DEBUG_CONTROLLER_DBUS (controller)); + + old_value = g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)); + notify_id = g_signal_connect (controller, "notify::debug-enabled", G_CALLBACK (notify_debug_enabled_cb), ¬ify_count); + + properties_changed_id = g_dbus_connection_signal_subscribe (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + "/org/gtk/Debugging", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + properties_changed_cb, + &properties_changed_count, + NULL); + + /* Get the debug status remotely. */ + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", "org.gtk.Debugging", "DebugEnabled"), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + g_assert_no_error (local_error); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_no_error (local_error); + g_clear_object (&result); + + g_variant_get (reply, "(v)", &debug_enabled_variant); + debug_enabled = g_variant_get_boolean (debug_enabled_variant); + g_assert_true (debug_enabled == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + /* Set the debug status remotely. The first attempt should fail due to no + * authorisation handler being connected. The second should fail due to the + * now-connected handler returning %FALSE. The third attempt should + * succeed. */ + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); + g_clear_object (&result); + g_clear_error (&local_error); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + /* Attach an authorisation handler and try again. */ + handler_id = g_signal_connect (controller, "authorize", G_CALLBACK (authorize_false_cb), NULL); + + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED); + g_clear_object (&result); + g_clear_error (&local_error); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 0); + g_assert_cmpuint (properties_changed_count, ==, 0); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + g_signal_handler_disconnect (controller, handler_id); + handler_id = 0; + + /* Attach another signal handler which will grant access, and try again. */ + handler_id = g_signal_connect (controller, "authorize", G_CALLBACK (authorize_true_cb), NULL); + + g_dbus_connection_call (remote_connection, + g_dbus_connection_get_unique_name (controller_connection), + "/org/gtk/Debugging", + "org.gtk.Debugging", + "SetDebugEnabled", + g_variant_new ("(b)", !old_value), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + async_result_cb, + &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + reply = g_dbus_connection_call_finish (remote_connection, result, &local_error); + g_assert_no_error (local_error); + g_clear_object (&result); + + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == !old_value); + g_assert_cmpuint (notify_count, ==, 1); + g_assert_cmpuint (properties_changed_count, ==, 1); + + g_clear_pointer (&debug_enabled_variant, g_variant_unref); + g_clear_pointer (&reply, g_variant_unref); + + g_signal_handler_disconnect (controller, handler_id); + handler_id = 0; + + /* Set the debug status locally. */ + g_debug_controller_set_debug_enabled (G_DEBUG_CONTROLLER (controller), old_value); + g_assert_true (g_debug_controller_get_debug_enabled (G_DEBUG_CONTROLLER (controller)) == old_value); + g_assert_cmpuint (notify_count, ==, 2); + + while (properties_changed_count != 2) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpuint (properties_changed_count, ==, 2); + + g_signal_handler_disconnect (controller, notify_id); + notify_id = 0; + + g_dbus_connection_signal_unsubscribe (remote_connection, properties_changed_id); + properties_changed_id = 0; + + g_debug_controller_dbus_stop (controller); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_finalize_object (controller); + g_clear_object (&controller_connection); + g_clear_object (&remote_connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +int +main (int argc, + char *argv[]) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/debug-controller/dbus/basic", test_dbus_basic); + g_test_add_func ("/debug-controller/dbus/duplicate", test_dbus_duplicate); + g_test_add_func ("/debug-controller/dbus/properties", test_dbus_properties); + + return g_test_run (); +} diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index 15dcd8f1c..4a5de6017 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -728,6 +728,20 @@ test_show_in (void) assert_shown ("invalid-desktop.desktop", FALSE, "../invalid/desktop:../invalid/desktop"); } +static void +on_launch_started (GAppLaunchContext *context, GAppInfo *info, GVariant *platform_data, gpointer data) +{ + gboolean *invoked = data; + + g_assert_true (G_IS_APP_LAUNCH_CONTEXT (context)); + g_assert_true (G_IS_APP_INFO (info)); + /* Our default context doesn't fill in any platform data */ + g_assert_null (platform_data); + + g_assert_false (*invoked); + *invoked = TRUE; +} + /* Test g_desktop_app_info_launch_uris_as_manager() and * g_desktop_app_info_launch_uris_as_manager_with_fds() */ @@ -738,6 +752,8 @@ test_launch_as_manager (void) GError *error = NULL; gboolean retval; const gchar *path; + gboolean invoked = FALSE; + GAppLaunchContext *context; if (g_getenv ("DISPLAY") == NULL || g_getenv ("DISPLAY")[0] == '\0') { @@ -754,23 +770,43 @@ test_launch_as_manager (void) return; } - retval = g_desktop_app_info_launch_uris_as_manager (appinfo, NULL, NULL, 0, + context = g_app_launch_context_new (); + g_signal_connect (context, "launch-started", + G_CALLBACK (on_launch_started), + &invoked); + retval = g_desktop_app_info_launch_uris_as_manager (appinfo, NULL, context, 0, NULL, NULL, NULL, NULL, &error); g_assert_no_error (error); g_assert_true (retval); + g_assert_true (invoked); + invoked = FALSE; retval = g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo, - NULL, NULL, 0, + NULL, context, 0, NULL, NULL, NULL, NULL, -1, -1, -1, &error); g_assert_no_error (error); g_assert_true (retval); + g_assert_true (invoked); g_object_unref (appinfo); + g_assert_finalize_object (context); +} + +/* Test if Desktop-File Id is correctly formed */ +static void +test_id (void) +{ + gchar *result; + + result = run_apps ("default-for-type", "application/vnd.kde.okular-archive", + TRUE, FALSE, NULL, NULL, NULL); + g_assert_cmpstr (result, ==, "kde4-okular.desktop\n"); + g_free (result); } int @@ -794,6 +830,7 @@ main (int argc, g_test_add_func ("/desktop-app-info/implements", test_implements); g_test_add_func ("/desktop-app-info/show-in", test_show_in); g_test_add_func ("/desktop-app-info/launch-as-manager", test_launch_as_manager); + g_test_add_func ("/desktop-app-info/id", test_id); return g_test_run (); } diff --git a/gio/tests/file.c b/gio/tests/file.c index 2f1b7b310..a849e83cf 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -3006,6 +3006,111 @@ test_build_attribute_list_for_copy (void) g_clear_object (&tmpfile); } +typedef struct +{ + GError *error; + gboolean done; + gboolean res; +} MoveAsyncData; + +static void +test_move_async_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + MoveAsyncData *data = user_data; + GError *error = NULL; + + data->res = g_file_move_finish (file, result, &error); + data->error = error; + data->done = TRUE; +} + +typedef struct +{ + goffset total_num_bytes; +} MoveAsyncProgressData; + +static void +test_move_async_progress_cb (goffset current_num_bytes, + goffset total_num_bytes, + gpointer user_data) +{ + MoveAsyncProgressData *data = user_data; + data->total_num_bytes = total_num_bytes; +} + +/* Test that move_async() moves the file correctly */ +static void +test_move_async (void) +{ + MoveAsyncData data = { 0 }; + MoveAsyncProgressData progress_data = { 0 }; + GFile *source; + GFileIOStream *iostream; + GOutputStream *ostream; + GFile *destination; + gchar *destination_path; + GError *error = NULL; + gboolean res; + const guint8 buffer[] = {1, 2, 3, 4, 5}; + + source = g_file_new_tmp ("g_file_move_XXXXXX", &iostream, NULL); + + destination_path = g_build_path (G_DIR_SEPARATOR_S, g_get_tmp_dir (), "g_file_move_target", NULL); + destination = g_file_new_for_path (destination_path); + + g_assert_nonnull (source); + g_assert_nonnull (iostream); + + res = g_file_query_exists (source, NULL); + g_assert_true (res); + res = g_file_query_exists (destination, NULL); + g_assert_false (res); + + // Write a known amount of bytes to the file, so we can test the progress callback against it + ostream = g_io_stream_get_output_stream (G_IO_STREAM (iostream)); + g_output_stream_write (ostream, buffer, sizeof (buffer), NULL, &error); + g_assert_no_error (error); + + g_file_move_async (source, + destination, + G_FILE_COPY_NONE, + 0, + NULL, + test_move_async_progress_cb, + &progress_data, + test_move_async_cb, + &data); + + while (!data.done) + g_main_context_iteration (NULL, TRUE); + + g_assert_no_error (data.error); + g_assert_true (data.res); + g_assert_cmpuint (progress_data.total_num_bytes, ==, sizeof (buffer)); + + res = g_file_query_exists (source, NULL); + g_assert_false (res); + res = g_file_query_exists (destination, NULL); + g_assert_true (res); + + res = g_io_stream_close (G_IO_STREAM (iostream), NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + g_object_unref (iostream); + + res = g_file_delete (destination, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + g_object_unref (source); + g_object_unref (destination); + + g_free (destination_path); +} + int main (int argc, char *argv[]) { @@ -3049,6 +3154,7 @@ main (int argc, char *argv[]) g_test_add_func ("/file/writev/async_all-to-big-vectors", test_writev_async_all_too_big_vectors); g_test_add_func ("/file/writev/async_all-cancellation", test_writev_async_all_cancellation); g_test_add_func ("/file/build-attribute-list-for-copy", test_build_attribute_list_for_copy); + g_test_add_func ("/file/move_async", test_move_async); return g_test_run (); } diff --git a/gio/tests/g-file-info-filesystem-readonly.c b/gio/tests/g-file-info-filesystem-readonly.c index 9b94c3220..ddf99da1e 100644 --- a/gio/tests/g-file-info-filesystem-readonly.c +++ b/gio/tests/g-file-info-filesystem-readonly.c @@ -135,6 +135,13 @@ test_filesystem_readonly (gconstpointer with_mount_monitor) if (! g_file_set_contents (file_in_mount, "Example", -1, NULL)) { g_test_skip ("Failed to create file needed to proceed further with the test"); + + g_free (dir_mountpoint); + g_free (file_in_mount); + g_free (dir_to_mount); + g_free (curdir); + g_free (fusermount); + g_free (bindfs); return; } @@ -147,8 +154,18 @@ test_filesystem_readonly (gconstpointer with_mount_monitor) { gchar *skip_message = g_strdup_printf ("Failed to run bindfs to set up test: %s", error->message); g_test_skip (skip_message); + g_free (skip_message); g_clear_error (&error); + + g_clear_object (&mount_monitor); + g_free (dir_mountpoint); + g_free (file_in_mount); + g_free (dir_to_mount); + g_free (curdir); + g_free (fusermount); + g_free (bindfs); + return; } @@ -170,6 +187,18 @@ test_filesystem_readonly (gconstpointer with_mount_monitor) if (! g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) { g_test_skip ("Failed to create readonly file needed to proceed further with the test"); + + g_clear_object (&file_info); + g_clear_object (&mounted_file); + g_free (file_in_mountpoint); + g_clear_object (&mount_monitor); + g_free (dir_mountpoint); + g_free (file_in_mount); + g_free (dir_to_mount); + g_free (curdir); + g_free (fusermount); + g_free (bindfs); + return; } diff --git a/gio/tests/g-file-info.c b/gio/tests/g-file-info.c index fd0c64b55..59411c3a8 100644 --- a/gio/tests/g-file-info.c +++ b/gio/tests/g-file-info.c @@ -654,7 +654,11 @@ test_internal_enhanced_stdio (void) g_assert_cmpuint (alsize_ps, <, 0x40000000); g_assert_cmpuint (size_ps, >, G_GUINT64_CONSTANT (0xFFFFFFFF)); g_assert_cmpuint (statbuf_ps.st_size, >, 0); +#if defined(_WIN64) + g_assert_cmpuint (statbuf_ps.st_size, ==, G_GUINT64_CONSTANT (0x10000000f)); +#else g_assert_cmpuint (statbuf_ps.st_size, <=, 0xFFFFFFFF); +#endif g_object_unref (fi_ps); g_object_unref (gf_ps); diff --git a/gio/tests/gdbus-auth.c b/gio/tests/gdbus-auth.c index 18288f36d..eabfdd331 100644 --- a/gio/tests/gdbus-auth.c +++ b/gio/tests/gdbus-auth.c @@ -167,6 +167,7 @@ test_auth_mechanism (const gchar *allowed_client_mechanism, GMainLoop *loop; GThread *client_thread; TestAuthData data; + guint timeout_id; server = server_new_for_mechanism (allowed_server_mechanism); @@ -177,7 +178,7 @@ test_auth_mechanism (const gchar *allowed_client_mechanism, G_CALLBACK (test_auth_on_new_connection), loop); - g_timeout_add_seconds (5, test_auth_on_timeout, NULL); + timeout_id = g_timeout_add_seconds (5, test_auth_on_timeout, NULL); data.allowed_client_mechanism = allowed_client_mechanism; data.allowed_server_mechanism = allowed_server_mechanism; @@ -195,6 +196,7 @@ test_auth_mechanism (const gchar *allowed_client_mechanism, g_dbus_server_stop (server); g_thread_join (client_thread); + g_source_remove (timeout_id); while (g_main_context_iteration (NULL, FALSE)); g_main_loop_unref (loop); diff --git a/gio/tests/gdbus-connection-flush.c b/gio/tests/gdbus-connection-flush.c index 8c925825a..c1647651e 100644 --- a/gio/tests/gdbus-connection-flush.c +++ b/gio/tests/gdbus-connection-flush.c @@ -179,8 +179,8 @@ setup_client_cb (GObject *source, f->client_conn = g_dbus_connection_new_finish (res, &f->error); g_assert_no_error (f->error); - g_assert (G_IS_DBUS_CONNECTION (f->client_conn)); - g_assert (f->client_conn == G_DBUS_CONNECTION (source)); + g_assert_true (G_IS_DBUS_CONNECTION (f->client_conn)); + g_assert_true (f->client_conn == G_DBUS_CONNECTION (source)); } static void @@ -192,8 +192,8 @@ setup_server_cb (GObject *source, f->server_conn = g_dbus_connection_new_finish (res, &f->error); g_assert_no_error (f->error); - g_assert (G_IS_DBUS_CONNECTION (f->server_conn)); - g_assert (f->server_conn == G_DBUS_CONNECTION (source)); + g_assert_true (G_IS_DBUS_CONNECTION (f->server_conn)); + g_assert_true (f->server_conn == G_DBUS_CONNECTION (source)); } static void @@ -206,21 +206,21 @@ setup (Fixture *f, ok = test_pipe (&f->server_istream, &f->client_real_ostream, &f->error); g_assert_no_error (f->error); - g_assert (G_IS_OUTPUT_STREAM (f->client_real_ostream)); - g_assert (G_IS_INPUT_STREAM (f->server_istream)); - g_assert (ok); + g_assert_true (G_IS_OUTPUT_STREAM (f->client_real_ostream)); + g_assert_true (G_IS_INPUT_STREAM (f->server_istream)); + g_assert_true (ok); f->client_ostream = g_object_new (MY_TYPE_OUTPUT_STREAM, "base-stream", f->client_real_ostream, "close-base-stream", TRUE, NULL); - g_assert (G_IS_OUTPUT_STREAM (f->client_ostream)); + g_assert_true (G_IS_OUTPUT_STREAM (f->client_ostream)); ok = test_pipe (&f->client_istream, &f->server_ostream, &f->error); g_assert_no_error (f->error); - g_assert (G_IS_OUTPUT_STREAM (f->server_ostream)); - g_assert (G_IS_INPUT_STREAM (f->client_istream)); - g_assert (ok); + g_assert_true (G_IS_OUTPUT_STREAM (f->server_ostream)); + g_assert_true (G_IS_INPUT_STREAM (f->client_istream)); + g_assert_true (ok); f->client_stream = test_io_stream_new (f->client_istream, f->client_ostream); f->server_stream = test_io_stream_new (f->server_istream, f->server_ostream); @@ -244,13 +244,13 @@ flush_cb (GObject *source, Fixture *f = user_data; gboolean ok; - g_assert (G_IS_DBUS_CONNECTION (source)); - g_assert (G_IS_DBUS_CONNECTION (f->client_conn)); + g_assert_true (G_IS_DBUS_CONNECTION (source)); + g_assert_true (G_IS_DBUS_CONNECTION (f->client_conn)); g_assert_cmpuint ((guintptr) f->client_conn, ==, (guintptr) G_DBUS_CONNECTION (source)); ok = g_dbus_connection_flush_finish (f->client_conn, res, &f->error); g_assert_no_error (f->error); - g_assert (ok); + g_assert_true (ok); f->flushed = TRUE; } @@ -270,7 +270,7 @@ test_flush_busy (Fixture *f, "com.example.Foo", "SomeSignal", NULL, &f->error); g_assert_no_error (f->error); - g_assert (ok); + g_assert_true (ok); /* wait for at least part of the message to have started writing - * the write will block indefinitely in the worker thread @@ -318,7 +318,7 @@ test_flush_idle (Fixture *f, "com.example.Foo", "SomeSignal", NULL, &f->error); g_assert_no_error (f->error); - g_assert (ok); + g_assert_true (ok); /* wait for at least part of the message to have been written */ do { @@ -368,10 +368,7 @@ main (int argc, { gint ret; - /* FIXME: Add debug for https://gitlab.gnome.org/GNOME/glib/issues/1929 */ - g_setenv ("G_DBUS_DEBUG", "authentication", TRUE); - - g_test_init (&argc, &argv, NULL); + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); g_test_add ("/gdbus/connection/flush/busy", Fixture, NULL, setup, test_flush_busy, teardown); diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c index 0410a880a..396b1a40a 100644 --- a/gio/tests/gdbus-connection.c +++ b/gio/tests/gdbus-connection.c @@ -416,7 +416,7 @@ test_connection_send (void) /* * Check that we never actually send a message if the GCancellable - * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED + * is already cancelled - i.e. we should get G_IO_ERROR_CANCELLED * when the actual connection is not up. */ ca = g_cancellable_new (); diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index 4cdc24492..f66b1f74c 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -602,20 +602,6 @@ on_subtree_unregistered (gpointer user_data) data->num_unregistered_subtree_calls++; } -static gboolean -_g_strv_has_string (const gchar* const * haystack, - const gchar *needle) -{ - guint n; - - for (n = 0; haystack != NULL && haystack[n] != NULL; n++) - { - if (g_strcmp0 (haystack[n], needle) == 0) - return TRUE; - } - return FALSE; -} - /* -------------------- */ static gchar ** @@ -1306,11 +1292,11 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/boss"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 5); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1p1")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "worker1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "worker1p1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "worker2")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "interns")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "executives")); g_strfreev (nodes); /* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */ g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5); @@ -1322,7 +1308,7 @@ test_object_registration (void) */ nodes = get_nodes_at (c, "/foo/boss/executives"); g_assert (nodes != NULL); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "non_subtree_object")); g_assert_cmpint (g_strv_length (nodes), ==, 1); g_strfreev (nodes); g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0); @@ -1332,11 +1318,11 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/boss/executives"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 5); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "vp0")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "vp1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "evp0")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "evp1")); /* check that /foo/boss/executives/non_subtree_object is not handled by the * subtree handlers - we can do this because objects from subtree handlers * has exactly one interface and non_subtree_object has two @@ -1358,13 +1344,13 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/boss/executives"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 7); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1")); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "vp0")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "vp1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "vp2")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "evp0")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "evp1")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "evp2")); g_strfreev (nodes); /* This is to check that a bug (rather, class of bugs) in gdbusconnection.c's @@ -1401,7 +1387,7 @@ test_object_registration (void) nodes = get_nodes_at (c, "/foo/boss/executives"); g_assert (nodes != NULL); g_assert_cmpint (g_strv_length (nodes), ==, 1); - g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object")); + g_assert (g_strv_contains ((const gchar* const *) nodes, "non_subtree_object")); g_strfreev (nodes); g_assert (g_dbus_connection_unregister_object (c, boss_foo_reg_id)); diff --git a/gio/tests/gdbus-method-invocation.c b/gio/tests/gdbus-method-invocation.c new file mode 100644 index 000000000..45fb67757 --- /dev/null +++ b/gio/tests/gdbus-method-invocation.c @@ -0,0 +1,406 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2022 Endless OS Foundation, LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include <gio/gio.h> +#include <gio/gunixfdlist.h> +#include <string.h> +#include <unistd.h> + +#include "gdbus-tests.h" + +static const GDBusArgInfo foo_get_fds_in_args = +{ + -1, + "type", + "s", + NULL +}; +static const GDBusArgInfo * const foo_get_fds_in_arg_pointers[] = {&foo_get_fds_in_args, NULL}; + +static const GDBusArgInfo foo_get_fds_out_args = +{ + -1, + "some_fd", + "h", + NULL +}; +static const GDBusArgInfo * const foo_get_fds_out_arg_pointers[] = {&foo_get_fds_out_args, NULL}; + +static const GDBusMethodInfo foo_method_info_wrong_return_type = +{ + -1, + "WrongReturnType", + NULL, /* in args */ + NULL, /* out args */ + NULL /* annotations */ +}; +static const GDBusMethodInfo foo_method_info_close_before_returning = +{ + -1, + "CloseBeforeReturning", + NULL, /* in args */ + NULL, /* out args */ + NULL /* annotations */ +}; +static const GDBusMethodInfo foo_method_info_get_fds = +{ + -1, + "GetFDs", + (GDBusArgInfo **) foo_get_fds_in_arg_pointers, + (GDBusArgInfo **) foo_get_fds_out_arg_pointers, + NULL /* annotations */ +}; +static const GDBusMethodInfo foo_method_info_return_error = +{ + -1, + "ReturnError", + NULL, /* in args */ + NULL, /* out args */ + NULL /* annotations */ +}; +static const GDBusMethodInfo * const foo_method_info_pointers[] = { + &foo_method_info_wrong_return_type, + &foo_method_info_close_before_returning, + &foo_method_info_get_fds, + &foo_method_info_return_error, + NULL +}; + +static const GDBusPropertyInfo foo_property_info[] = +{ + { + -1, + "InvalidType", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, + NULL + }, + { + -1, + "InvalidTypeNull", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, + NULL + }, + { + -1, + "InvalidValueType", + "s", + G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, + NULL + }, +}; +static const GDBusPropertyInfo * const foo_property_info_pointers[] = +{ + &foo_property_info[0], + &foo_property_info[1], + &foo_property_info[2], + NULL +}; + +static const GDBusInterfaceInfo foo_interface_info = +{ + -1, + "org.example.Foo", + (GDBusMethodInfo **) &foo_method_info_pointers, + NULL, /* signals */ + (GDBusPropertyInfo **) &foo_property_info_pointers, + NULL, /* annotations */ +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_method_invocation_return_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + gboolean no_reply = g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; + + if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && + g_str_equal (method_name, "Get")) + { + const gchar *iface_name, *prop_name; + + g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + + /* Do different things depending on the property name. */ + if (g_str_equal (prop_name, "InvalidType")) + { + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Type of return value for property 'Get' call should be '(v)' but got '(s)'"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "this type is invalid")); + } + else if (g_str_equal (prop_name, "InvalidTypeNull")) + { + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Type of return value for property 'Get' call should be '(v)' but got '()'"); + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_str_equal (prop_name, "InvalidValueType")) + { + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Value returned from property 'Get' call for 'InvalidValueType' should be 's' but is 'u'"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_uint32 (123))); + } + else + { + g_assert_not_reached (); + } + + g_test_assert_expected_messages (); + } + else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && + g_str_equal (method_name, "Set")) + { + const gchar *iface_name, *prop_name; + GVariant *value; + + g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + + if (g_str_equal (prop_name, "InvalidType")) + { + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Type of return value for property 'Set' call should be '()' but got '(s)'"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be unit")); + } + else + { + g_assert_not_reached (); + } + + g_test_assert_expected_messages (); + g_variant_unref (value); + } + else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && + g_str_equal (method_name, "GetAll")) + { + const gchar *iface_name; + + g_variant_get (parameters, "(&s)", &iface_name); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Type of return value for property 'GetAll' call should be '(a{sv})' but got '(s)'"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type")); + } + else if (g_str_equal (interface_name, "org.example.Foo") && + g_str_equal (method_name, "WrongReturnType")) + { + if (!no_reply) + g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, + "Type of return value is incorrect: expected '()', got '(s)'"); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type")); + } + else if (g_str_equal (interface_name, "org.example.Foo") && + g_str_equal (method_name, "CloseBeforeReturning")) + { + g_dbus_connection_close (connection, NULL, NULL, NULL); + + g_dbus_method_invocation_return_value (invocation, NULL); + } + else if (g_str_equal (interface_name, "org.example.Foo") && + g_str_equal (method_name, "GetFDs")) + { + const gchar *action; + GUnixFDList *list = NULL; + GError *local_error = NULL; + + g_variant_get (parameters, "(&s)", &action); + + list = g_unix_fd_list_new (); + g_unix_fd_list_append (list, 1, &local_error); + g_assert_no_error (local_error); + + if (g_str_equal (action, "WrongNumber")) + { + g_unix_fd_list_append (list, 1, &local_error); + g_assert_no_error (local_error); + } + + if (g_str_equal (action, "Valid") || + g_str_equal (action, "WrongNumber")) + g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)"), list); + else + g_assert_not_reached (); + + g_object_unref (list); + } + else if (g_str_equal (interface_name, "org.example.Foo") && + g_str_equal (method_name, "ReturnError")) + { + g_dbus_method_invocation_return_dbus_error (invocation, "org.example.Foo", "SomeError"); + } + else + g_assert_not_reached (); +} + +static void +ensure_result_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (source); + GVariant *reply; + guint *n_outstanding_calls = user_data; + + reply = g_dbus_connection_call_finish (connection, result, NULL); + + /* We don’t care what the reply is. */ + g_clear_pointer (&reply, g_variant_unref); + + g_assert_cmpint (*n_outstanding_calls, >, 0); + *n_outstanding_calls = *n_outstanding_calls - 1; +} + +static void +test_method_invocation_return (void) +{ + GDBusConnection *connection = NULL; + GError *local_error = NULL; + guint registration_id; + const GDBusInterfaceVTable vtable = { + test_method_invocation_return_method_call, NULL, NULL, { 0 } + }; + guint n_outstanding_calls = 0; + + g_test_summary ("Test calling g_dbus_method_invocation_return_*() in various ways"); + + /* Connect to the bus. */ + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (connection); + + /* Don’t exit the test when the server closes the connection in + * CloseBeforeReturning(). */ + g_dbus_connection_set_exit_on_close (connection, FALSE); + + /* Register an object which we can call methods on. */ + registration_id = g_dbus_connection_register_object (connection, + "/foo", + (GDBusInterfaceInfo *) &foo_interface_info, + &vtable, NULL, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_cmpint (registration_id, !=, 0); + + /* Test a variety of error cases */ + { + const struct + { + const gchar *interface_name; + const gchar *method_name; + const gchar *parameters_string; + gboolean tests_undefined_behaviour; + } + calls[] = + { + { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidType')", TRUE }, + { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidTypeNull')", TRUE }, + { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidValueType')", TRUE }, + { "org.freedesktop.DBus.Properties", "Set", "('org.example.Foo', 'InvalidType', <'irrelevant'>)", TRUE }, + { "org.freedesktop.DBus.Properties", "GetAll", "('org.example.Foo',)", TRUE }, + { "org.example.Foo", "WrongReturnType", "()", TRUE }, + { "org.example.Foo", "GetFDs", "('Valid',)", FALSE }, + { "org.example.Foo", "GetFDs", "('WrongNumber',)", TRUE }, + { "org.example.Foo", "ReturnError", "()", FALSE }, + { "org.example.Foo", "CloseBeforeReturning", "()", FALSE }, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (calls); i++) + { + if (calls[i].tests_undefined_behaviour && !g_test_undefined ()) + { + g_test_message ("Skipping %s.%s", calls[i].interface_name, calls[i].method_name); + continue; + } + else + { + g_test_message ("Calling %s.%s", calls[i].interface_name, calls[i].method_name); + } + + /* Call twice, once expecting a result and once not. Do the call which + * doesn’t expect a result first; message ordering should ensure that + * it’s completed by the time the second call completes, so we don’t + * have to account for it separately. + * + * That’s good, because the only way to get g_dbus_connection_call() + * to set %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED is to not provide + * a callback function. */ + n_outstanding_calls++; + + g_dbus_connection_call (connection, + g_dbus_connection_get_unique_name (connection), + "/foo", + calls[i].interface_name, + calls[i].method_name, + g_variant_new_parsed (calls[i].parameters_string), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, /* no callback */ + NULL); + + g_dbus_connection_call (connection, + g_dbus_connection_get_unique_name (connection), + "/foo", + calls[i].interface_name, + calls[i].method_name, + g_variant_new_parsed (calls[i].parameters_string), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + ensure_result_cb, + &n_outstanding_calls); + } + } + + /* Wait until all the calls are complete. */ + while (n_outstanding_calls > 0) + g_main_context_iteration (NULL, TRUE); + + g_dbus_connection_unregister_object (connection, registration_id); + g_object_unref (connection); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + g_test_add_func ("/gdbus/method-invocation/return", test_method_invocation_return); + + return session_bus_run (); +} diff --git a/gio/tests/gdbus-non-socket.c b/gio/tests/gdbus-non-socket.c index 911aff262..7ddb55bfb 100644 --- a/gio/tests/gdbus-non-socket.c +++ b/gio/tests/gdbus-non-socket.c @@ -293,7 +293,7 @@ main (int argc, { gint ret; - g_test_init (&argc, &argv, NULL); + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); g_test_add_func ("/gdbus/non-socket", test_non_socket); diff --git a/gio/tests/gdbus-object-manager-example/meson.build b/gio/tests/gdbus-object-manager-example/meson.build index f9c3bce26..ce0335e11 100644 --- a/gio/tests/gdbus-object-manager-example/meson.build +++ b/gio/tests/gdbus-object-manager-example/meson.build @@ -17,6 +17,22 @@ gdbus_example_objectmanager_generated = custom_target('objectmanager-gen', '--symbol-decorator-define', 'HAVE_CONFIG_H', '@INPUT@']) +gdbus_example_objectmanager_rst_gen = custom_target('objectmanager-rst-gen', + input: gdbus_example_objectmanager_xml, + output: [ + 'objectmanager-rst-gen-org.gtk.GDBus.Example.ObjectManager.Animal.rst', + 'objectmanager-rst-gen-org.gtk.GDBus.Example.ObjectManager.Cat.rst', + ], + command: [ + python, + gdbus_codegen, + '--interface-prefix', 'org.gtk.GDBus.Example.ObjectManager.', + '--generate-rst', 'objectmanager-rst-gen', + '--output-directory', '@OUTDIR@', + '@INPUT@', + ], +) + libgdbus_example_objectmanager = library('gdbus-example-objectmanager', gdbus_example_objectmanager_generated, c_args : test_c_args, @@ -25,6 +41,9 @@ libgdbus_example_objectmanager = library('gdbus-example-objectmanager', install_dir : installed_tests_execdir) libgdbus_example_objectmanager_dep = declare_dependency( - sources : gdbus_example_objectmanager_generated[0], + sources : [ + gdbus_example_objectmanager_generated[0], + gdbus_example_objectmanager_rst_gen[0], + ], link_with : libgdbus_example_objectmanager, dependencies : [libgio_dep]) diff --git a/gio/tests/gdbus-proxy.c b/gio/tests/gdbus-proxy.c index 7e619c2ac..eed75acf4 100644 --- a/gio/tests/gdbus-proxy.c +++ b/gio/tests/gdbus-proxy.c @@ -701,7 +701,6 @@ test_basic (GDBusProxy *proxy) connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); g_assert_true (g_dbus_proxy_get_connection (proxy) == connection); - g_assert_cmpint (g_dbus_proxy_get_flags (proxy), ==, G_DBUS_PROXY_FLAGS_NONE); g_assert_null (g_dbus_proxy_get_interface_info (proxy)); g_assert_cmpstr (g_dbus_proxy_get_name (proxy), ==, "com.example.TestService"); g_assert_cmpstr (g_dbus_proxy_get_object_path (proxy), ==, "/com/example/TestObject"); @@ -720,7 +719,7 @@ test_basic (GDBusProxy *proxy) g_assert_true (conn == connection); g_assert_null (info); - g_assert_cmpint (flags, ==, G_DBUS_PROXY_FLAGS_NONE); + g_assert_cmpint (flags, ==, g_dbus_proxy_get_flags (proxy)); g_assert_cmpstr (name, ==, "com.example.TestService"); g_assert_cmpstr (path, ==, "/com/example/TestObject"); g_assert_cmpstr (interface, ==, "com.example.Frob"); @@ -735,6 +734,16 @@ test_basic (GDBusProxy *proxy) } static void +name_disappeared_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + gboolean *name_disappeared = user_data; + *name_disappeared = TRUE; + g_main_context_wakeup (NULL); +} + +static void kill_test_service (GDBusConnection *connection) { #ifdef G_OS_UNIX @@ -742,6 +751,8 @@ kill_test_service (GDBusConnection *connection) GVariant *ret; GError *error = NULL; const gchar *name = "com.example.TestService"; + guint watch_id; + gboolean name_disappeared = FALSE; ret = g_dbus_connection_call_sync (connection, "org.freedesktop.DBus", @@ -756,14 +767,25 @@ kill_test_service (GDBusConnection *connection) &error); g_variant_get (ret, "(u)", &pid); g_variant_unref (ret); + + /* Watch the name and wait until it’s disappeared. */ + watch_id = g_bus_watch_name_on_connection (connection, name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, name_disappeared_cb, + &name_disappeared, NULL); kill (pid, SIGTERM); + + while (!name_disappeared) + g_main_context_iteration (NULL, TRUE); + + g_bus_unwatch_name (watch_id); #else g_warning ("Can't kill com.example.TestService"); #endif } static void -test_proxy (void) +test_proxy_with_flags (GDBusProxyFlags flags) { GDBusProxy *proxy; GDBusConnection *connection; @@ -777,7 +799,7 @@ test_proxy (void) g_assert_no_error (error); error = NULL; proxy = g_dbus_proxy_new_sync (connection, - G_DBUS_PROXY_FLAGS_NONE, + flags, NULL, /* GDBusInterfaceInfo */ "com.example.TestService", /* name */ "/com/example/TestObject", /* object path */ @@ -799,8 +821,6 @@ test_proxy (void) kill_test_service (connection); - _g_assert_property_notify (proxy, "g-name-owner"); - owner = g_dbus_proxy_get_name_owner (proxy); g_assert_null (owner); g_free (owner); @@ -809,6 +829,12 @@ test_proxy (void) g_object_unref (connection); } +static void +test_proxy (void) +{ + test_proxy_with_flags (G_DBUS_PROXY_FLAGS_NONE); +} + /* ---------------------------------------------------------------------------------------------------- */ static void @@ -930,6 +956,58 @@ test_wellknown_noauto (void) g_source_remove (id); } +typedef enum { + ADD_MATCH, + REMOVE_MATCH, +} AddOrRemove; + +static void +add_or_remove_match_rule (GDBusConnection *connection, + AddOrRemove add_or_remove, + GVariant *match_rule) +{ + GDBusMessage *message = NULL; + GError *error = NULL; + + message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ + "/org/freedesktop/DBus", /* path */ + "org.freedesktop.DBus", /* interface */ + (add_or_remove == ADD_MATCH) ? "AddMatch" : "RemoveMatch"); + g_dbus_message_set_body (message, match_rule); + g_dbus_connection_send_message (connection, + message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + NULL, + &error); + g_assert_no_error (error); + g_clear_object (&message); +} + +static void +test_proxy_no_match_rule (void) +{ + GDBusConnection *connection = NULL; + GVariant *match_rule = NULL; + + g_test_summary ("Test that G_DBUS_PROXY_FLAGS_NO_MATCH_RULE works"); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/1109"); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + + /* Add a custom match rule which matches everything. */ + match_rule = g_variant_ref_sink (g_variant_new ("(s)", "type='signal'")); + add_or_remove_match_rule (connection, ADD_MATCH, match_rule); + + /* Run the tests. */ + test_proxy_with_flags (G_DBUS_PROXY_FLAGS_NO_MATCH_RULE); + + /* Remove the match rule again. */ + add_or_remove_match_rule (connection, REMOVE_MATCH, match_rule); + + g_clear_pointer (&match_rule, g_variant_unref); + g_clear_object (&connection); +} + int main (int argc, char *argv[]) @@ -950,6 +1028,7 @@ main (int argc, g_test_add_func ("/gdbus/proxy/no-properties", test_no_properties); g_test_add_func ("/gdbus/proxy/wellknown-noauto", test_wellknown_noauto); g_test_add_func ("/gdbus/proxy/async", test_async); + g_test_add_func ("/gdbus/proxy/no-match-rule", test_proxy_no_match_rule); ret = session_bus_run(); diff --git a/gio/tests/gdbus-test-codegen.c b/gio/tests/gdbus-test-codegen.c index 0681e7ec8..22628c078 100644 --- a/gio/tests/gdbus-test-codegen.c +++ b/gio/tests/gdbus-test-codegen.c @@ -23,8 +23,6 @@ #include <string.h> #include <stdio.h> -#include "glib/glib-private.h" - #include "gdbus-tests.h" #if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 @@ -869,7 +867,7 @@ check_bar_proxy (FooiGenBar *proxy, "s", "a string", "o", "/a/path", "g", "asig", - "ay", g_variant_new_parsed ("[byte 0x65, 0x67]"), + "ay", "eg", "as", array_of_strings, "ao", array_of_objpaths, "ag", g_variant_new_parsed ("[@g 'ass', 'git']"), @@ -1304,18 +1302,6 @@ static gpointer check_proxies_in_thread (gpointer user_data) { GMainLoop *loop = user_data; -#ifdef _GLIB_ADDRESS_SANITIZER - - /* Silence "Not available before 2.38" when using old API */ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - g_test_incomplete ("FIXME: Leaks a GWeakRef, see glib#2312"); - G_GNUC_END_IGNORE_DEPRECATIONS - - (void) check_thread_proxies; - (void) check_authorize_proxy; - (void) check_bat_proxy; - (void) check_bar_proxy; -#else GMainContext *thread_context; GMainLoop *thread_loop; GError *error; @@ -1382,9 +1368,14 @@ check_proxies_in_thread (gpointer user_data) g_object_unref (thread_proxy_1); g_object_unref (thread_proxy_2); + /* Wait for the proxy signals to all be unsubscribed. */ + while (g_main_context_iteration (thread_context, FALSE)) + { + /* Nothing needs to be done here */ + } + g_main_loop_unref (thread_loop); g_main_context_unref (thread_context); -#endif /* this breaks out of the loop in main() (below) */ g_main_loop_quit (loop); diff --git a/gio/tests/gdbus-threading.c b/gio/tests/gdbus-threading.c index 4519fda91..23dc1fde3 100644 --- a/gio/tests/gdbus-threading.c +++ b/gio/tests/gdbus-threading.c @@ -48,8 +48,9 @@ timeout_cb (gpointer user_data) * unrefs complete first. This is typically used on the shared connection, to * ensure it’s in a correct state before beginning the next test. */ static void -assert_connection_has_one_ref (GDBusConnection *connection, - GMainContext *context) +(assert_connection_has_one_ref) (GDBusConnection *connection, + GMainContext *context, + const gchar *calling_function) { GSource *timeout_source = NULL; TimeoutData data = { context, FALSE }; @@ -63,7 +64,8 @@ assert_connection_has_one_ref (GDBusConnection *connection, while (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1 && !data.timed_out) { - g_debug ("refcount of %p is not right, sleeping", connection); + g_debug ("refcount of %p is not right (%u rather than 1) in %s(), sleeping", + connection, g_atomic_int_get (&G_OBJECT (connection)->ref_count), calling_function); g_main_context_iteration (NULL, TRUE); } @@ -71,9 +73,14 @@ assert_connection_has_one_ref (GDBusConnection *connection, g_source_unref (timeout_source); if (g_atomic_int_get (&G_OBJECT (connection)->ref_count) != 1) - g_error ("connection %p had too many refs", connection); + g_error ("connection %p had too many refs (%u rather than 1) in %s()", + connection, g_atomic_int_get (&G_OBJECT (connection)->ref_count), calling_function); } +/* Macro wrapper to add in the calling function name */ +#define assert_connection_has_one_ref(connection, context) \ + (assert_connection_has_one_ref) (connection, context, G_STRFUNC) + /* ---------------------------------------------------------------------------------------------------- */ /* Ensure that signal and method replies are delivered in the right thread */ /* ---------------------------------------------------------------------------------------------------- */ @@ -176,7 +183,7 @@ test_delivery_in_thread_func (gpointer _data) /* * Check that we never actually send a message if the GCancellable - * is already cancelled - i.e. we should get #G_IO_ERROR_CANCELLED + * is already cancelled - i.e. we should get G_IO_ERROR_CANCELLED * when the actual connection is not up. */ ca = g_cancellable_new (); diff --git a/gio/tests/gio-du.c b/gio/tests/gio-du.c index 58d1797d2..253a21f70 100644 --- a/gio/tests/gio-du.c +++ b/gio/tests/gio-du.c @@ -79,7 +79,6 @@ main (int argc, char **argv) #ifdef G_OS_WIN32 argv = g_win32_get_command_line (); - argc = g_strv_length (argv); #endif setlocale (LC_ALL, ""); diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index dad1623b7..35d958e4d 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -2365,21 +2365,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS } static gboolean -strv_has_string (gchar **haystack, - const gchar *needle) -{ - guint n; - - for (n = 0; haystack != NULL && haystack[n] != NULL; n++) - { - if (g_strcmp0 (haystack[n], needle) == 0) - return TRUE; - } - return FALSE; -} - -static gboolean -strv_set_equal (gchar **strv, ...) +strv_set_equal (const gchar * const *strv, ...) { gsize count; va_list list; @@ -2394,7 +2380,7 @@ strv_set_equal (gchar **strv, ...) str = va_arg (list, const gchar *); if (str == NULL) break; - if (!strv_has_string (strv, str)) + if (!g_strv_contains (strv, str)) { res = FALSE; break; @@ -2422,8 +2408,8 @@ test_list_items (void) children = g_settings_list_children (settings); keys = g_settings_schema_list_keys (schema); - g_assert_true (strv_set_equal (children, "basic-types", "complex-types", "localized", NULL)); - g_assert_true (strv_set_equal (keys, "greeting", "farewell", NULL)); + g_assert_true (strv_set_equal ((const gchar * const *) children, "basic-types", "complex-types", "localized", NULL)); + g_assert_true (strv_set_equal ((const gchar * const *) keys, "greeting", "farewell", NULL)); g_strfreev (children); g_strfreev (keys); @@ -2443,13 +2429,13 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS schemas = g_settings_list_schemas (); G_GNUC_END_IGNORE_DEPRECATIONS - g_assert_true (strv_set_equal ((gchar **)relocs, + g_assert_true (strv_set_equal (relocs, "org.gtk.test.no-path", "org.gtk.test.extends.base", "org.gtk.test.extends.extended", NULL)); - g_assert_true (strv_set_equal ((gchar **)schemas, + g_assert_true (strv_set_equal (schemas, "org.gtk.test", "org.gtk.test.basic-types", "org.gtk.test.complex-types", @@ -2658,7 +2644,7 @@ test_schema_list_keys (void) keys = g_settings_schema_list_keys (schema); - g_assert_true (strv_set_equal ((gchar **)keys, + g_assert_true (strv_set_equal ((const gchar * const *) keys, "greeting", "farewell", NULL)); @@ -2969,7 +2955,7 @@ test_extended_schema (void) settings = g_settings_new_with_path ("org.gtk.test.extends.extended", "/test/extendes/"); g_object_get (settings, "settings-schema", &schema, NULL); keys = g_settings_schema_list_keys (schema); - g_assert_true (strv_set_equal (keys, "int32", "string", "another-int32", NULL)); + g_assert_true (strv_set_equal ((const gchar * const *) keys, "int32", "string", "another-int32", NULL)); g_strfreev (keys); g_object_unref (settings); g_settings_schema_unref (schema); diff --git a/gio/tests/gsubprocess-testprog.c b/gio/tests/gsubprocess-testprog.c index da3cf8d00..eee759dcd 100644 --- a/gio/tests/gsubprocess-testprog.c +++ b/gio/tests/gsubprocess-testprog.c @@ -5,8 +5,6 @@ #include <errno.h> #ifdef G_OS_UNIX #include <unistd.h> -#include <gio/gunixinputstream.h> -#include <gio/gunixoutputstream.h> #else #include <io.h> #endif @@ -151,6 +149,55 @@ write_to_fds (int argc, char **argv) } static int +read_from_fd (int argc, char **argv) +{ + int fd; + const char expected_result[] = "Yay success!"; + guint8 buf[sizeof (expected_result) + 1]; + gsize bytes_read; + FILE *f; + + if (argc != 3) + { + g_print ("Usage: %s read-from-fd FD\n", argv[0]); + return 1; + } + + fd = atoi (argv[2]); + if (fd == 0) + { + g_warning ("Argument \"%s\" does not look like a valid nonzero file descriptor", argv[2]); + return 1; + } + + f = fdopen (fd, "r"); + if (f == NULL) + { + g_warning ("Failed to open fd %d: %s", fd, g_strerror (errno)); + return 1; + } + + bytes_read = fread (buf, 1, sizeof (buf), f); + if (bytes_read != sizeof (expected_result)) + { + g_warning ("Read %zu bytes, but expected %zu", bytes_read, sizeof (expected_result)); + return 1; + } + + if (memcmp (expected_result, buf, sizeof (expected_result)) != 0) + { + buf[sizeof (expected_result)] = '\0'; + g_warning ("Expected \"%s\" but read \"%s\"", expected_result, (char *)buf); + return 1; + } + + if (fclose (f) == -1) + g_assert_not_reached (); + + return 0; +} + +static int env_mode (int argc, char **argv) { char **env; @@ -242,6 +289,8 @@ main (int argc, char **argv) return sleep_forever_mode (argc, argv); else if (strcmp (mode, "write-to-fds") == 0) return write_to_fds (argc, argv); + else if (strcmp (mode, "read-from-fd") == 0) + return read_from_fd (argc, argv); else if (strcmp (mode, "env") == 0) return env_mode (argc, argv); else if (strcmp (mode, "cwd") == 0) diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c index 084b77df3..fc5d4624e 100644 --- a/gio/tests/gsubprocess.c +++ b/gio/tests/gsubprocess.c @@ -5,6 +5,7 @@ #include <sys/wait.h> #include <glib-unix.h> #include <gio/gunixinputstream.h> +#include <gio/gunixoutputstream.h> #include <gio/gfiledescriptorbased.h> #include <unistd.h> #include <fcntl.h> @@ -26,6 +27,14 @@ #define SPLICELEN (TOTAL_HELLOS * strlen (HELLO_WORLD)) #endif + + +#ifdef G_OS_WIN32 +#define TESTPROG "gsubprocess-testprog.exe" +#else +#define TESTPROG "gsubprocess-testprog" +#endif + static GPtrArray * get_test_subprocess_args (const char *mode, ...) G_GNUC_NULL_TERMINATED; @@ -36,19 +45,12 @@ get_test_subprocess_args (const char *mode, { GPtrArray *ret; char *path; - const char *binname; va_list args; gpointer arg; ret = g_ptr_array_new_with_free_func (g_free); -#ifdef G_OS_WIN32 - binname = "gsubprocess-testprog.exe"; -#else - binname = "gsubprocess-testprog"; -#endif - - path = g_test_build_filename (G_TEST_BUILT, binname, NULL); + path = g_test_build_filename (G_TEST_BUILT, TESTPROG, NULL); g_ptr_array_add (ret, path); g_ptr_array_add (ret, g_strdup (mode)); @@ -167,6 +169,31 @@ test_search_path (void) g_object_unref (proc); } + +static void +test_search_path_from_envp (void) +{ + GError *local_error = NULL; + GError **error = &local_error; + GSubprocessLauncher *launcher; + GSubprocess *proc; + const char *path; + + path = g_test_get_dir (G_TEST_BUILT); + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP); + g_subprocess_launcher_setenv (launcher, "PATH", path, TRUE); + + proc = g_subprocess_launcher_spawn (launcher, error, TESTPROG, "exit1", NULL); + g_assert_no_error (local_error); + g_object_unref (launcher); + + g_subprocess_wait_check (proc, NULL, error); + g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); + g_clear_error (error); + + g_object_unref (proc); +} #endif static void @@ -1697,7 +1724,8 @@ test_child_setup (void) } static void -test_pass_fd (void) +do_test_pass_fd (GSubprocessFlags flags, + GSpawnChildSetupFunc child_setup) { GError *local_error = NULL; GError **error = &local_error; @@ -1722,9 +1750,11 @@ test_pass_fd (void) needdup_fd_str = g_strdup_printf ("%d", needdup_pipefds[1] + 1); args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + launcher = g_subprocess_launcher_new (flags); g_subprocess_launcher_take_fd (launcher, basic_pipefds[1], basic_pipefds[1]); g_subprocess_launcher_take_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1); + if (child_setup != NULL) + g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); @@ -1754,6 +1784,206 @@ test_pass_fd (void) g_object_unref (proc); } +static void +test_pass_fd (void) +{ + do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, NULL); +} + +static void +empty_child_setup (gpointer user_data) +{ +} + +static void +test_pass_fd_empty_child_setup (void) +{ + /* Using a child setup function forces gspawn to use fork/exec + * rather than posix_spawn. + */ + do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, empty_child_setup); +} + +static void +test_pass_fd_inherit_fds (void) +{ + /* Try to test the optimized posix_spawn codepath instead of + * fork/exec. Currently this requires using INHERIT_FDS since gspawn's + * posix_spawn codepath does not currently handle closing + * non-inherited fds. Note that using INHERIT_FDS means our testing of + * g_subprocess_launcher_take_fd() is less-comprehensive than when + * using G_SUBPROCESS_FLAGS_NONE. + */ + do_test_pass_fd (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL); +} + +static void +do_test_fd_conflation (GSubprocessFlags flags, + GSpawnChildSetupFunc child_setup, + gboolean test_child_err_report_fd) +{ + char success_message[] = "Yay success!"; + GError *error = NULL; + GOutputStream *output_stream; + GSubprocessLauncher *launcher; + GSubprocess *proc; + GPtrArray *args; + int unused_pipefds[2]; + int pipefds[2]; + int fd_to_pass_to_child; + gsize bytes_written; + gboolean success; + char *fd_str; + + /* This test must run in a new process because it is extremely sensitive to + * order of opened fds. + */ + if (!g_test_subprocess ()) + { + g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); + g_test_trap_assert_passed (); + return; + } + + g_unix_open_pipe (unused_pipefds, FD_CLOEXEC, &error); + g_assert_no_error (error); + + g_unix_open_pipe (pipefds, FD_CLOEXEC, &error); + g_assert_no_error (error); + + /* The fds should be sequential since we are in a new process. */ + g_assert_cmpint (unused_pipefds[0] /* 3 */, ==, unused_pipefds[1] - 1); + g_assert_cmpint (unused_pipefds[1] /* 4 */, ==, pipefds[0] - 1); + g_assert_cmpint (pipefds[0] /* 5 */, ==, pipefds[1] /* 6 */ - 1); + + /* Because GSubprocess allows arbitrary remapping of fds, it has to be careful + * to avoid fd conflation issues, e.g. it should properly handle 5 -> 4 and + * 4 -> 5 at the same time. GIO previously attempted to handle this by naively + * dup'ing the source fds, but this was not good enough because it was + * possible that the dup'ed result could still conflict with one of the target + * fds. For example: + * + * source_fd 5 -> target_fd 9, source_fd 3 -> target_fd 7 + * + * dup(5) -> dup returns 8 + * dup(3) -> dup returns 9 + * + * After dup'ing, we wind up with: 8 -> 9, 9 -> 7. That means that after we + * dup2(8, 9), we have clobbered fd 9 before we dup2(9, 7). The end result is + * we have remapped 5 -> 9 as expected, but then remapped 5 -> 7 instead of + * 3 -> 7 as the application intended. + * + * This issue has been fixed in the simplest way possible, by passing a + * minimum fd value when using F_DUPFD_CLOEXEC that is higher than any of the + * target fds, to guarantee all source fds are different than all target fds, + * eliminating any possibility of conflation. + * + * Anyway, that is why we have the unused_pipefds here. We need to open fds in + * a certain order in order to trick older GSubprocess into conflating the + * fds. The primary goal of this test is to ensure this particular conflation + * issue is not reintroduced. See glib#2503. + * + * This test also has an alternate mode of operation where it instead tests + * for conflation with gspawn's child_err_report_fd, glib#2506. + * + * Be aware this test is necessarily extremely fragile. To reproduce these + * bugs, it relies on internals of gspawn and gmain that will likely change + * in the future, eventually causing this test to no longer test the bugs + * it was originally designed to test. That is OK! If the test fails, at + * least you know *something* is wrong. + */ + if (test_child_err_report_fd) + fd_to_pass_to_child = pipefds[1] + 2 /* 8 */; + else + fd_to_pass_to_child = pipefds[1] + 3 /* 9 */; + + launcher = g_subprocess_launcher_new (flags); + g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, fd_to_pass_to_child); + g_subprocess_launcher_take_fd (launcher, unused_pipefds[0] /* 3 */, pipefds[1] + 1 /* 7 */); + if (child_setup != NULL) + g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL); + fd_str = g_strdup_printf ("%d", fd_to_pass_to_child); + args = get_test_subprocess_args ("read-from-fd", fd_str, NULL); + proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, &error); + g_assert_no_error (error); + g_assert_nonnull (proc); + g_ptr_array_free (args, TRUE); + g_object_unref (launcher); + g_free (fd_str); + + /* Close the read ends of the pipes. */ + close (unused_pipefds[0]); + close (pipefds[0]); + + /* Also close the write end of the unused pipe. */ + close (unused_pipefds[1]); + + /* If doing our normal test: + * + * So now pipefds[0] should be inherited into the subprocess as + * pipefds[1] + 2, and unused_pipefds[0] should be inherited as + * pipefds[1] + 1. We will write to pipefds[1] and the subprocess will verify + * that it reads the expected data. But older broken GIO will accidentally + * clobber pipefds[1] + 2 with pipefds[1] + 1! This will cause the subprocess + * to hang trying to read from the wrong pipe. + * + * If testing conflation with child_err_report_fd: + * + * We are actually already done. The real test succeeded if we made it this + * far without hanging while spawning the child. But let's continue with our + * write and read anyway, to ensure things are good. + */ + output_stream = g_unix_output_stream_new (pipefds[1], TRUE); + success = g_output_stream_write_all (output_stream, + success_message, sizeof (success_message), + &bytes_written, + NULL, + &error); + g_assert_no_error (error); + g_assert_cmpint (bytes_written, ==, sizeof (success_message)); + g_assert_true (success); + g_object_unref (output_stream); + + success = g_subprocess_wait_check (proc, NULL, &error); + g_assert_no_error (error); + g_object_unref (proc); +} + +static void +test_fd_conflation (void) +{ + do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL, FALSE); +} + +static void +test_fd_conflation_empty_child_setup (void) +{ + /* Using a child setup function forces gspawn to use fork/exec + * rather than posix_spawn. + */ + do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, FALSE); +} + +static void +test_fd_conflation_inherit_fds (void) +{ + /* Try to test the optimized posix_spawn codepath instead of + * fork/exec. Currently this requires using INHERIT_FDS since gspawn's + * posix_spawn codepath does not currently handle closing + * non-inherited fds. + */ + do_test_fd_conflation (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL, FALSE); +} + +static void +test_fd_conflation_child_err_report_fd (void) +{ + /* Using a child setup function forces gspawn to use fork/exec + * rather than posix_spawn. + */ + do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, TRUE); +} + #endif static void @@ -1819,6 +2049,7 @@ main (int argc, char **argv) g_test_add_func ("/gsubprocess/noop-stdin-inherit", test_noop_stdin_inherit); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/search-path", test_search_path); + g_test_add_func ("/gsubprocess/search-path-from-envp", test_search_path_from_envp); g_test_add_func ("/gsubprocess/signal", test_signal); #endif g_test_add_func ("/gsubprocess/exit1", test_exit1); @@ -1890,7 +2121,13 @@ main (int argc, char **argv) g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file); g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd); g_test_add_func ("/gsubprocess/child-setup", test_child_setup); - g_test_add_func ("/gsubprocess/pass-fd", test_pass_fd); + g_test_add_func ("/gsubprocess/pass-fd/basic", test_pass_fd); + g_test_add_func ("/gsubprocess/pass-fd/empty-child-setup", test_pass_fd_empty_child_setup); + g_test_add_func ("/gsubprocess/pass-fd/inherit-fds", test_pass_fd_inherit_fds); + g_test_add_func ("/gsubprocess/fd-conflation/basic", test_fd_conflation); + g_test_add_func ("/gsubprocess/fd-conflation/empty-child-setup", test_fd_conflation_empty_child_setup); + g_test_add_func ("/gsubprocess/fd-conflation/inherit-fds", test_fd_conflation_inherit_fds); + g_test_add_func ("/gsubprocess/fd-conflation/child-err-report-fd", test_fd_conflation_child_err_report_fd); #endif g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment); diff --git a/gio/tests/memory-monitor.c b/gio/tests/memory-monitor.c index 06eabefa2..bef6d0f2e 100644 --- a/gio/tests/memory-monitor.c +++ b/gio/tests/memory-monitor.c @@ -18,21 +18,6 @@ #include <gio/gio.h> -static const char * -get_level_string (GMemoryMonitorWarningLevel level) -{ - GEnumClass *eclass; - GEnumValue *value; - - eclass = G_ENUM_CLASS (g_type_class_peek (G_TYPE_MEMORY_MONITOR_WARNING_LEVEL)); - value = g_enum_get_value (eclass, level); - - if (value == NULL) - return "unknown"; - - return value->value_nick; -} - static void test_dup_default (void) { @@ -47,10 +32,9 @@ static void warning_cb (GMemoryMonitor *m, GMemoryMonitorWarningLevel level) { - const char *str; - - str = get_level_string (level); - g_debug ("Warning level: %s (%d)", str , level); + char *str = g_enum_to_string (G_TYPE_MEMORY_MONITOR_WARNING_LEVEL, level); + g_message ("Warning level: %s (%d)", str , level); + g_free (str); } static void diff --git a/gio/tests/memory-output-stream.c b/gio/tests/memory-output-stream.c index b448516df..ec1644ed8 100644 --- a/gio/tests/memory-output-stream.c +++ b/gio/tests/memory-output-stream.c @@ -300,6 +300,25 @@ test_write_bytes (void) g_bytes_unref (bytes2); } +static void +test_write_null (void) +{ + GOutputStream *mo; + GError *error = NULL; + + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2471"); + + mo = g_memory_output_stream_new_resizable (); + g_output_stream_write_all (mo, NULL, 0, NULL, NULL, &error); + g_assert_no_error (error); + + g_assert_cmpint (0, ==, g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mo))); + + g_output_stream_close (mo, NULL, &error); + g_assert_no_error (error); + g_object_unref (mo); +} + /* Test that writev() works on #GMemoryOutputStream with a non-empty set of vectors. This * covers the default writev() implementation around write(). */ static void @@ -437,6 +456,7 @@ main (int argc, g_test_add_func ("/memory-output-stream/get-data-size", test_data_size); g_test_add_func ("/memory-output-stream/properties", test_properties); g_test_add_func ("/memory-output-stream/write-bytes", test_write_bytes); + g_test_add_func ("/memory-output-stream/write-null", test_write_null); g_test_add_func ("/memory-output-stream/writev", test_writev); g_test_add_func ("/memory-output-stream/writev_nonblocking", test_writev_nonblocking); g_test_add_func ("/memory-output-stream/steal_as_bytes", test_steal_as_bytes); diff --git a/gio/tests/meson.build b/gio/tests/meson.build index b563e8dde..3ed23a5f2 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -42,25 +42,42 @@ giotypefuncs_inc = custom_target( # Test programs buildable on all platforms gio_tests = { - 'appmonitor' : {}, + 'appmonitor' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, 'async-close-output-stream' : {}, 'async-splice-output-stream' : {}, 'buffered-input-stream' : {}, 'buffered-output-stream' : {}, 'cancellable' : {}, 'contexts' : {}, - 'contenttype' : {}, + 'contenttype' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 / https://gitlab.gnome.org/GNOME/glib/-/issues/1251 + 'should_fail' : host_system == 'darwin', + }, 'converter-stream' : {}, 'credentials' : {}, + 'cxx' : { + 'source' : ['cxx.cpp'], + }, 'data-input-stream' : {}, 'data-output-stream' : {}, - 'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]}, 'fileattributematcher' : {}, 'filter-streams' : {}, - 'giomodule' : {}, - 'gsubprocess' : {}, + 'giomodule' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, + 'gsubprocess' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, 'g-file' : {}, - 'g-file-info' : {}, + 'g-file-info' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, 'g-icon' : {}, 'gdbus-addresses' : {}, 'gdbus-message' : {}, @@ -81,7 +98,10 @@ gio_tests = { 'simple-async-result' : {}, 'simple-proxy' : {}, 'sleepy-stream' : {}, - 'socket' : {}, + 'socket' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, 'socket-listener' : {}, 'socket-service' : {}, 'srvtarget' : {}, @@ -95,7 +115,10 @@ gio_tests = { 'tls-interaction' : {'extra_sources' : ['gtesttlsbackend.c']}, 'tls-database' : {'extra_sources' : ['gtesttlsbackend.c']}, 'tls-bindings' : {'extra_sources' : ['gtesttlsbackend.c']}, - 'gdbus-address-get-session' : {}, + 'gdbus-address-get-session' : { + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', + }, 'win32-appinfo' : {}, } @@ -148,16 +171,27 @@ else } endif +have_dbus_daemon = find_program('dbus-daemon', required : false).found() +if have_dbus_daemon + gio_tests += { + 'debugcontroller' : {}, + 'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]}, + } +endif + # Test programs buildable on UNIX only if host_machine.system() != 'windows' gio_tests += { 'file' : {}, 'gdbus-peer' : { 'dependencies' : [libgdbus_example_objectmanager_dep], - 'install_rpath' : installed_tests_execdir + 'install_rpath' : installed_tests_execdir, + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', }, 'gdbus-peer-object-manager' : {}, 'live-g-file' : {}, + 'resolver-parsing' : {'dependencies' : [network_libs]}, 'socket-address' : {}, 'stream-rw_all' : {}, 'unix-fd' : {}, @@ -219,8 +253,6 @@ if host_machine.system() != 'windows' } endif - # Test programs that need to bring up a session bus (requires dbus-daemon) - have_dbus_daemon = find_program('dbus-daemon', required : false).found() if have_dbus_daemon annotate_args = [ '--annotate', 'org.project.Bar', 'Key1', 'Value1', @@ -306,6 +338,7 @@ if host_machine.system() != 'windows' 'suite' : ['slow'], }, 'gdbus-introspection' : {'extra_sources' : extra_sources}, + 'gdbus-method-invocation' : {'extra_sources' : extra_sources}, 'gdbus-names' : {'extra_sources' : extra_sources}, 'gdbus-proxy' : {'extra_sources' : extra_sources}, 'gdbus-proxy-threads' : { @@ -572,12 +605,16 @@ endif if not meson.is_cross_build() or meson.has_exe_wrapper() + compiler_type = '--compiler=@0@'.format(cc.get_id()) + plugin_resources_c = custom_target('plugin-resources.c', input : 'test4.gresource.xml', output : 'plugin-resources.c', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), + '--internal', '--generate-source', '--c-name', '_g_plugin', '@INPUT@']) @@ -600,9 +637,11 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : 'test.gresource.xml', output : 'test.gresource', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), '--sourcedir=' + meson.current_build_dir(), + '--internal', '@INPUT@'], install_dir : installed_tests_execdir, install : installed_tests_enabled) @@ -611,8 +650,10 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : 'test3.gresource.xml', output : 'test_resources2.c', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), + '--internal', '--generate', '--c-name', '_g_test2', '--manual-register', @@ -622,8 +663,10 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : 'test3.gresource.xml', output : 'test_resources2.h', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), + '--internal', '--generate', '--c-name', '_g_test2', '--manual-register', @@ -634,9 +677,11 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() depends : big_test_resource, output : 'test_resources.c', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), '--sourcedir=' + meson.current_build_dir(), + '--internal', '--generate-source', '--c-name', '_g_test1', '@INPUT@']) @@ -645,9 +690,11 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : '111_digit_test.gresource.xml', output : 'digit_test_resources.c', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), '--sourcedir=' + meson.current_build_dir(), + '--internal', '--generate-source', '--manual-register', '@INPUT@']) @@ -656,8 +703,10 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : '111_digit_test.gresource.xml', output : 'digit_test_resources.h', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), + '--internal', '--generate', '--manual-register', '@INPUT@']) @@ -700,9 +749,11 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : 'test5.gresource.xml', output : 'test5.gresource', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), '--sourcedir=' + meson.current_build_dir(), + '--internal', '@INPUT@'], install_dir : installed_tests_execdir, install : installed_tests_enabled) @@ -712,9 +763,11 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : 'test5.gresource.xml', output : 'test_resources_binary.c', command : [glib_compile_resources, + compiler_type, '--target=@OUTPUT@', '--sourcedir=' + meson.current_source_dir(), '--sourcedir=' + meson.current_build_dir(), + '--internal', '--generate-source', '--external-data', '--c-name', '_g_binary_test1', @@ -725,17 +778,24 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() input : test_gresource_binary, output : 'test_resources.o', command : [ld, + '-z', 'noexecstack', '-r', '-b','binary', '@INPUT@', '-o','@OUTPUT@']) # Rename symbol to match the one in the C file + if cc.get_id() == 'gcc' and host_system == 'windows' + underscore = '_' + else + underscore = '' + endif test_resources_binary2 = custom_target('test_resources2.o', input : test_resources_binary, output : 'test_resources2.o', command : [objcopy, - '--add-symbol','_g_binary_test1_resource_data=.data:0', + '--strip-all', + '--add-symbol', underscore + '_g_binary_test1_resource_data=.data:0', '@INPUT@', '@OUTPUT@']) @@ -748,6 +808,8 @@ if not meson.is_cross_build() or meson.has_exe_wrapper() gio_tests += { 'resources' : { 'extra_sources' : resources_extra_sources, + # FIXME: https://gitlab.gnome.org/GNOME/glib/-/issues/1392 + 'should_fail' : host_system == 'darwin', }, } endif @@ -801,6 +863,7 @@ foreach test_name, extra_args : gio_tests suite : suite, is_parallel : extra_args.get('is_parallel', true), depends : extra_args.get('depends', []), + should_fail : extra_args.get('should_fail', false), ) endforeach diff --git a/gio/tests/mock-resolver.c b/gio/tests/mock-resolver.c index 3a7532134..397be2792 100644 --- a/gio/tests/mock-resolver.c +++ b/gio/tests/mock-resolver.c @@ -87,18 +87,50 @@ mock_resolver_set_ipv6_error (MockResolver *self, GError *error) self->ipv6_error = g_error_copy (error); } +static gboolean lookup_by_name_cb (gpointer user_data); + +/* Core of the implementation of `lookup_by_name()` in the mock resolver. + * + * It creates a #GSource which will become ready with the resolver results. It + * will become ready either after a timeout, or as an idle callback. This + * simulates doing some actual network-based resolution work. + * + * A previous implementation of this did the work in a thread, but that made it + * hard to synchronise the timeouts with the #GResolver failure timeouts in the + * calling thread, as spawning a worker thread could be subject to non-trivial + * delays. */ static void -do_lookup_by_name (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +do_lookup_by_name (MockResolver *self, + GTask *task, + GResolverNameLookupFlags flags) { - MockResolver *self = source_object; - GResolverNameLookupFlags flags = GPOINTER_TO_UINT(task_data); + GSource *source = NULL; + + g_task_set_task_data (task, GINT_TO_POINTER (flags), NULL); + + if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) + source = g_timeout_source_new (self->ipv4_delay_ms); + else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) + source = g_timeout_source_new (self->ipv6_delay_ms); + else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) + source = g_idle_source_new (); + else + g_assert_not_reached (); + + g_source_set_callback (source, lookup_by_name_cb, g_object_ref (task), g_object_unref); + g_source_attach (source, g_main_context_get_thread_default ()); + g_source_unref (source); +} + +static gboolean +lookup_by_name_cb (gpointer user_data) +{ + GTask *task = G_TASK (user_data); + MockResolver *self = g_task_get_source_object (task); + GResolverNameLookupFlags flags = GPOINTER_TO_INT (g_task_get_task_data (task)); if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) { - g_usleep (self->ipv4_delay_ms * 1000); if (self->ipv4_error) g_task_return_error (task, g_error_copy (self->ipv4_error)); else @@ -106,7 +138,6 @@ do_lookup_by_name (GTask *task, } else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) { - g_usleep (self->ipv6_delay_ms * 1000); if (self->ipv6_error) g_task_return_error (task, g_error_copy (self->ipv6_error)); else @@ -120,6 +151,8 @@ do_lookup_by_name (GTask *task, } else g_assert_not_reached (); + + return G_SOURCE_REMOVE; } static void @@ -130,27 +163,65 @@ lookup_by_name_with_flags_async (GResolver *resolver, GAsyncReadyCallback callback, gpointer user_data) { - GTask *task = g_task_new (resolver, cancellable, callback, user_data); - g_task_set_task_data (task, GUINT_TO_POINTER(flags), NULL); - g_task_run_in_thread (task, do_lookup_by_name); + MockResolver *self = MOCK_RESOLVER (resolver); + GTask *task = NULL; + + task = g_task_new (resolver, cancellable, callback, user_data); + g_task_set_source_tag (task, lookup_by_name_with_flags_async); + + do_lookup_by_name (self, task, flags); + g_object_unref (task); } +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + g_assert (*result_out == NULL); + *result_out = g_object_ref (result); + + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + static GList * -lookup_by_name (GResolver *resolver, - const gchar *hostname, - GCancellable *cancellable, +lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, GError **error) { + MockResolver *self = MOCK_RESOLVER (resolver); + GMainContext *context = NULL; GList *result = NULL; - GTask *task = g_task_new (resolver, cancellable, NULL, NULL); - g_task_set_task_data (task, GUINT_TO_POINTER (G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT), NULL); - g_task_run_in_thread_sync (task, do_lookup_by_name); - result = g_task_propagate_pointer (task, error); - g_object_unref (task); - return result; -} + GAsyncResult *async_result = NULL; + GTask *task = NULL; + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + task = g_task_new (resolver, cancellable, async_result_cb, &async_result); + g_task_set_source_tag (task, lookup_by_name); + + /* Set up the resolution job. */ + do_lookup_by_name (self, task, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); + + /* Wait for it to complete synchronously. */ + while (async_result == NULL) + g_main_context_iteration (context, TRUE); + + result = g_task_propagate_pointer (G_TASK (async_result), error); + g_object_unref (async_result); + + g_assert_finalize_object (task); + + g_main_context_pop_thread_default (context); + g_main_context_unref (context); + + return g_steal_pointer (&result); +} static GList * lookup_by_name_with_flags_finish (GResolver *resolver, diff --git a/gio/tests/modules/symbol-visibility.h b/gio/tests/modules/symbol-visibility.h index f9f8826ce..e83894444 100644 --- a/gio/tests/modules/symbol-visibility.h +++ b/gio/tests/modules/symbol-visibility.h @@ -3,11 +3,15 @@ /* This is the same check that's done in configure to create config.h */ #ifdef _WIN32 -# ifdef _MSC_VER -# define GLIB_TEST_EXPORT_SYMBOL __declspec(dllexport) extern -# else -# define GLIB_TEST_EXPORT_SYMBOL __attribute__((visibility("default"))) __declspec(dllexport) extern -# endif +#ifdef GLIB_STATIC_COMPILATION +#define GLIB_TEST_EXPORT_SYMBOL extern +#else +#ifdef _MSC_VER +#define GLIB_TEST_EXPORT_SYMBOL __declspec(dllexport) extern +#else +#define GLIB_TEST_EXPORT_SYMBOL __attribute__ ((visibility ("default"))) __declspec(dllexport) extern +#endif +#endif /* Matches GCC and Clang */ #elif defined(__GNUC__) && (__GNUC__ >= 4) # define GLIB_TEST_EXPORT_SYMBOL __attribute__((visibility("default"))) extern diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 946a8f25f..da4d7f7e0 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -20,7 +20,7 @@ test_basic (void) g_object_get (address, "hostname", &hostname, "port", &port, "scheme", &scheme, NULL); g_assert_cmpstr (hostname, ==, "www.gnome.org"); g_assert_cmpint (port, ==, 8080); - g_assert (scheme == NULL); + g_assert_null (scheme); g_free (hostname); g_object_unref (address); @@ -284,8 +284,8 @@ test_scope_id (GSocketConnectable *addr) saddr = g_socket_address_enumerator_next (addr_enum, NULL, &error); g_assert_no_error (error); - g_assert (saddr != NULL); - g_assert (G_IS_INET_SOCKET_ADDRESS (saddr)); + g_assert_nonnull (saddr); + g_assert_true (G_IS_INET_SOCKET_ADDRESS (saddr)); isaddr = G_INET_SOCKET_ADDRESS (saddr); g_assert_cmpint (g_inet_socket_address_get_scope_id (isaddr), ==, SCOPE_ID_TEST_INDEX); @@ -299,7 +299,7 @@ test_scope_id (GSocketConnectable *addr) g_object_unref (saddr); saddr = g_socket_address_enumerator_next (addr_enum, NULL, &error); g_assert_no_error (error); - g_assert (saddr == NULL); + g_assert_null (saddr); g_object_unref (addr_enum); #else @@ -377,7 +377,7 @@ assert_socket_address_matches (GSocketAddress *a, GInetSocketAddress *sa; gchar *str; /* owned */ - g_assert (G_IS_INET_SOCKET_ADDRESS (a)); + g_assert_true (G_IS_INET_SOCKET_ADDRESS (a)); sa = G_INET_SOCKET_ADDRESS (a); g_assert_cmpint (g_inet_socket_address_get_port (sa), ==, expected_port); @@ -593,7 +593,7 @@ got_addr (GObject *source_object, } else { - g_assert (G_IS_INET_SOCKET_ADDRESS (a)); + g_assert_true (G_IS_INET_SOCKET_ADDRESS (a)); data->addrs = g_list_prepend (data->addrs, a); if (!data->delay_ms) @@ -768,16 +768,22 @@ sort_socket_addresses (gconstpointer a, gconstpointer b) static void assert_list_matches_expected (GList *result, GList *expected) { + GList *result_copy = NULL; + g_assert_cmpint (g_list_length (result), ==, g_list_length (expected)); - /* Sort by ipv4 first which matches the expected list */ - result = g_list_sort (result, sort_socket_addresses); + /* Sort by ipv4 first which matches the expected list. Do this on a copy of + * @result to avoid modifying the original. */ + result_copy = g_list_copy (result); + result = result_copy = g_list_sort (result_copy, sort_socket_addresses); for (; result != NULL; result = result->next, expected = expected->next) { GInetAddress *address = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (result->data)); g_assert_true (g_inet_address_equal (address, expected->data)); } + + g_list_free (result_copy); } typedef struct { @@ -855,6 +861,8 @@ test_happy_eyeballs_basic (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -879,6 +887,7 @@ test_happy_eyeballs_parallel (HappyEyeballsFixture *fixture, /* Run again to ensure the cache from the previous one is correct */ + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); data.addrs = NULL; g_object_unref (enumerator2); @@ -887,7 +896,9 @@ test_happy_eyeballs_parallel (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + g_object_unref (enumerator2); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -905,6 +916,8 @@ test_happy_eyeballs_slow_ipv4 (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -922,6 +935,8 @@ test_happy_eyeballs_slow_ipv6 (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -939,6 +954,8 @@ test_happy_eyeballs_very_slow_ipv6 (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -958,6 +975,8 @@ test_happy_eyeballs_slow_connection_and_ipv4 (HappyEyeballsFixture *fixture, g_main_loop_run (fixture->loop); assert_list_matches_expected (data.addrs, fixture->input_all_results); + + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); } static void @@ -979,6 +998,7 @@ test_happy_eyeballs_ipv6_error_ipv4_first (HappyEyeballsFixture *fixture, assert_list_matches_expected (data.addrs, fixture->input_ipv4_results); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); g_error_free (ipv6_error); } @@ -1001,6 +1021,7 @@ test_happy_eyeballs_ipv6_error_ipv6_first (HappyEyeballsFixture *fixture, assert_list_matches_expected (data.addrs, fixture->input_ipv4_results); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); g_error_free (ipv6_error); } @@ -1026,6 +1047,7 @@ test_happy_eyeballs_ipv6_error_ipv4_very_slow (HappyEyeballsFixture *fixture, assert_list_matches_expected (data.addrs, fixture->input_ipv4_results); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); g_error_free (ipv6_error); } @@ -1048,6 +1070,7 @@ test_happy_eyeballs_ipv4_error_ipv4_first (HappyEyeballsFixture *fixture, assert_list_matches_expected (data.addrs, fixture->input_ipv6_results); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); g_error_free (ipv4_error); } @@ -1070,6 +1093,7 @@ test_happy_eyeballs_ipv4_error_ipv6_first (HappyEyeballsFixture *fixture, assert_list_matches_expected (data.addrs, fixture->input_ipv6_results); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); g_error_free (ipv4_error); } diff --git a/gio/tests/proxy-test.c b/gio/tests/proxy-test.c index eec4bf7ca..d4b71a432 100644 --- a/gio/tests/proxy-test.c +++ b/gio/tests/proxy-test.c @@ -41,9 +41,14 @@ * connects to @server_addr anyway). * * The default GProxyResolver (GTestProxyResolver) looks at its URI - * and returns [ "direct://" ] for "simple://" URIs, and [ - * proxy_a.uri, proxy_b.uri ] for other URIs. The other GProxyResolver - * (GTestAltProxyResolver) always returns [ proxy_a.uri ]. + * and returns [ "direct://" ] for "simple://" URIs, and + * [ proxy_a.uri, proxy_b.uri ] for most other URIs. It can also return + * invalid results for other URIs (empty://, invalid://, + * invalid-then-simple://, and simple-then-invalid://) to test error + * handling. + * + * The other GProxyResolver (GTestAltProxyResolver) always returns + * [ proxy_a.uri ]. */ typedef struct { @@ -134,6 +139,28 @@ g_test_proxy_resolver_lookup (GProxyResolver *resolver, proxies[0] = g_strdup ("direct://"); proxies[1] = NULL; } + else if (g_str_has_prefix (uri, "empty://")) + { + proxies[0] = g_strdup (""); + proxies[1] = NULL; + } + else if (g_str_has_prefix (uri, "invalid://")) + { + proxies[0] = g_strdup ("😼"); + proxies[1] = NULL; + } + else if (g_str_has_prefix (uri, "invalid-then-simple://")) + { + proxies[0] = g_strdup ("😼"); + proxies[1] = g_strdup ("direct://"); + proxies[2] = NULL; + } + else if (g_str_has_prefix (uri, "simple-then-invalid://")) + { + proxies[0] = g_strdup ("direct://"); + proxies[1] = g_strdup ("😼"); + proxies[2] = NULL; + } else { /* Proxy A can only deal with "alpha://" URIs, not @@ -824,11 +851,8 @@ static void teardown_test (gpointer fixture, gconstpointer user_data) { - if (last_proxies) - { - g_strfreev (last_proxies); - last_proxies = NULL; - } + g_clear_pointer (&last_proxies, g_strfreev); + g_clear_error (&proxy_a.last_error); g_clear_error (&proxy_b.last_error); } @@ -1092,6 +1116,118 @@ test_multiple_async (gpointer fixture, } static void +test_invalid_uris_sync (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + gchar *uri; + GError *error = NULL; + + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2597"); + + /* The empty:// URI causes the proxy resolver to return an empty string. */ + uri = g_strdup_printf ("empty://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_assert_null (conn); + g_clear_error (&error); + g_clear_pointer (&last_proxies, g_strfreev); + + /* The invalid:// URI causes the proxy resolver to return a cat emoji. */ + uri = g_strdup_printf ("invalid://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_assert_null (conn); + g_clear_error (&error); + g_clear_pointer (&last_proxies, g_strfreev); + + /* If the proxy resolver returns an invalid URI before a valid URI, + * we should succeed. + */ + uri = g_strdup_printf ("invalid-then-simple://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_no_error (error); + do_echo_test (conn); + g_object_unref (conn); + g_clear_pointer (&last_proxies, g_strfreev); + + /* If the proxy resolver returns a valid URI before an invalid URI, + * we should succeed. + */ + uri = g_strdup_printf ("simple-then-invalid://127.0.0.1:%u", server.server_port); + conn = g_socket_client_connect_to_uri (client, uri, 0, NULL, &error); + g_free (uri); + g_assert_no_error (error); + do_echo_test (conn); + g_object_unref (conn); + g_clear_pointer (&last_proxies, g_strfreev); +} + +static void +test_invalid_uris_async (gpointer fixture, + gconstpointer user_data) +{ + GSocketConnection *conn; + GError *error = NULL; + gchar *uri; + + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2597"); + + /* The empty:// URI causes the proxy resolver to return an empty string. */ + uri = g_strdup_printf ("empty://127.0.0.1:%u", server.server_port); + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_error, &error); + g_free (uri); + while (error == NULL) + g_main_context_iteration (NULL, TRUE); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + g_clear_pointer (&last_proxies, g_strfreev); + + /* The invalid:// URI causes the proxy resolver to return a cat emoji. */ + uri = g_strdup_printf ("invalid://127.0.0.1:%u", server.server_port); + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_error, &error); + g_free (uri); + while (error == NULL) + g_main_context_iteration (NULL, TRUE); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + g_clear_pointer (&last_proxies, g_strfreev); + + /* If the proxy resolver returns an invalid URI before a valid URI, + * we should succeed. + */ + uri = g_strdup_printf ("invalid-then-simple://127.0.0.1:%u", server.server_port); + conn = NULL; + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + g_free (uri); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + do_echo_test (conn); + g_object_unref (conn); + g_clear_pointer (&last_proxies, g_strfreev); + + /* If the proxy resolver returns a valid URI before an invalid URI, + * we should succeed. + */ + uri = g_strdup_printf ("simple-then-invalid://127.0.0.1:%u", server.server_port); + conn = NULL; + g_socket_client_connect_to_uri_async (client, uri, 0, NULL, + async_got_conn, &conn); + g_free (uri); + while (conn == NULL) + g_main_context_iteration (NULL, TRUE); + do_echo_test (conn); + g_object_unref (conn); + g_clear_pointer (&last_proxies, g_strfreev); +} + +static void test_dns (gpointer fixture, gconstpointer user_data) { @@ -1370,6 +1506,8 @@ main (int argc, g_test_add_vtable ("/proxy/single_async", 0, NULL, setup_test, test_single_async, teardown_test); g_test_add_vtable ("/proxy/multiple_sync", 0, NULL, setup_test, test_multiple_sync, teardown_test); g_test_add_vtable ("/proxy/multiple_async", 0, NULL, setup_test, test_multiple_async, teardown_test); + g_test_add_vtable ("/proxy/invalid-uris-sync", 0, NULL, setup_test, test_invalid_uris_sync, teardown_test); + g_test_add_vtable ("/proxy/invalid-uris-async", 0, NULL, setup_test, test_invalid_uris_async, teardown_test); g_test_add_vtable ("/proxy/dns", 0, NULL, setup_test, test_dns, teardown_test); g_test_add_vtable ("/proxy/override", 0, NULL, setup_test, test_override, teardown_test); g_test_add_func ("/proxy/enumerator-ports", test_proxy_enumerator_ports); diff --git a/gio/tests/resolver-parsing.c b/gio/tests/resolver-parsing.c new file mode 100644 index 000000000..d9cf05244 --- /dev/null +++ b/gio/tests/resolver-parsing.c @@ -0,0 +1,879 @@ +/* + * Copyright (c) 2021 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: Patrick Griffis <pgriffis@igalia.com> + */ + +#include "config.h" + +#include <glib.h> +#include <gio/gnetworking.h> + +#define GIO_COMPILATION +#include "gthreadedresolver.h" +#undef GIO_COMPILATION + +#ifdef HAVE_DN_COMP +static void +dns_builder_add_uint8 (GByteArray *builder, + guint8 value) +{ + g_byte_array_append (builder, &value, 1); +} + +static void +dns_builder_add_uint16 (GByteArray *builder, + guint16 value) +{ + dns_builder_add_uint8 (builder, (value >> 8) & 0xFF); + dns_builder_add_uint8 (builder, (value) & 0xFF); +} + +static void +dns_builder_add_uint32 (GByteArray *builder, + guint32 value) +{ + dns_builder_add_uint8 (builder, (value >> 24) & 0xFF); + dns_builder_add_uint8 (builder, (value >> 16) & 0xFF); + dns_builder_add_uint8 (builder, (value >> 8) & 0xFF); + dns_builder_add_uint8 (builder, (value) & 0xFF); +} + +static void +dns_builder_add_length_prefixed_string (GByteArray *builder, + const char *string) +{ + guint8 length; + + g_assert (strlen (string) <= G_MAXUINT8); + + length = (guint8) strlen (string); + dns_builder_add_uint8 (builder, length); + + /* Don't include trailing NUL */ + g_byte_array_append (builder, (const guchar *)string, length); +} + +static void +dns_builder_add_domain (GByteArray *builder, + const char *string) +{ + int ret; + guchar buffer[256]; + + ret = dn_comp (string, buffer, sizeof (buffer), NULL, NULL); + g_assert (ret != -1); + + g_byte_array_append (builder, buffer, ret); +} + +/* Append an invalid domain name to the DNS response. This is implemented by + * appending a single label followed by a pointer back to that label. This is + * invalid regardless of any other context in the response as its expansion is + * infinite. + * + * See https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4 + * + * In order to create a pointer to the label, the label’s final offset in the + * DNS response must be known. The current length of @builder, plus @offset, is + * used for this. Hence, @offset is the additional offset (in bytes) to add, and + * typically corresponds to the length of the parent #GByteArray that @builder + * will eventually be added to. Potentially plus 2 bytes for the rdlength, as + * per dns_builder_add_answer_data(). */ +static void +dns_builder_add_invalid_domain (GByteArray *builder, + gsize offset) +{ + offset += builder->len; + g_assert ((offset & 0xc0) == 0); + + dns_builder_add_uint8 (builder, 1); + dns_builder_add_uint8 (builder, 'f'); + dns_builder_add_uint8 (builder, 0xc0 | offset); +} + +static void +dns_builder_add_answer_data (GByteArray *builder, + GByteArray *answer) +{ + dns_builder_add_uint16 (builder, answer->len); /* rdlength */ + g_byte_array_append (builder, answer->data, answer->len); +} + +static GByteArray * +dns_header (void) +{ + GByteArray *answer = g_byte_array_sized_new (2046); + + /* Start with a header, we ignore everything except ancount. + https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.1 */ + dns_builder_add_uint16 (answer, 0); /* ID */ + dns_builder_add_uint16 (answer, 0); /* |QR| Opcode |AA|TC|RD|RA| Z | RCODE | */ + dns_builder_add_uint16 (answer, 0); /* QDCOUNT */ + dns_builder_add_uint16 (answer, 1); /* ANCOUNT (1 answer) */ + dns_builder_add_uint16 (answer, 0); /* NSCOUNT */ + dns_builder_add_uint16 (answer, 0); /* ARCOUNT */ + + return g_steal_pointer (&answer); +} + +static void +assert_query_fails (const gchar *rrname, + GResolverRecordType record_type, + GByteArray *answer) +{ + GList *records = NULL; + GError *local_error = NULL; + + records = g_resolver_records_from_res_query (rrname, + g_resolver_record_type_to_rrtype (record_type), + answer->data, + answer->len, + 0, + &local_error); + + g_assert_error (local_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL); + g_assert_null (records); + g_clear_error (&local_error); +} + +static void +assert_query_succeeds (const gchar *rrname, + GResolverRecordType record_type, + GByteArray *answer, + const gchar *expected_answer_variant_str) +{ + GList *records = NULL; + GVariant *answer_variant, *expected_answer_variant = NULL; + GError *local_error = NULL; + + records = g_resolver_records_from_res_query (rrname, + g_resolver_record_type_to_rrtype (record_type), + answer->data, + answer->len, + 0, + &local_error); + + g_assert_no_error (local_error); + g_assert_nonnull (records); + + /* Test the results. */ + answer_variant = records->data; + expected_answer_variant = g_variant_new_parsed (expected_answer_variant_str); + g_assert_cmpvariant (answer_variant, expected_answer_variant); + + g_variant_unref (expected_answer_variant); + g_list_free_full (records, (GDestroyNotify) g_variant_unref); +} +#endif /* HAVE_DN_COMP */ + +static void +test_invalid_header (void) +{ + const struct + { + const guint8 *answer; + gsize answer_len; + GResolverError expected_error_code; + } + vectors[] = + { + /* No answer: */ + { (const guint8 *) "", 0, G_RESOLVER_ERROR_NOT_FOUND }, + /* Definitely too short to be a valid header: */ + { (const guint8 *) "\x20", 1, G_RESOLVER_ERROR_INTERNAL }, + /* One byte too short to be a valid header: */ + { (const guint8 *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 11, G_RESOLVER_ERROR_INTERNAL }, + /* Valid header indicating no answers: */ + { (const guint8 *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 12, G_RESOLVER_ERROR_NOT_FOUND }, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (vectors); i++) + { + GList *records = NULL; + GError *local_error = NULL; + + records = g_resolver_records_from_res_query ("example.org", + g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_NS), + vectors[i].answer, + vectors[i].answer_len, + 0, + &local_error); + + g_assert_error (local_error, G_RESOLVER_ERROR, (gint) vectors[i].expected_error_code); + g_assert_null (records); + g_clear_error (&local_error); + } +} + +static void +test_unknown_record_type (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL; + GList *records = NULL; + GError *local_error = NULL; + const guint type_id = 20; /* ISDN, not supported anywhere */ + + /* An answer with an unsupported type chosen from + * https://en.wikipedia.org/wiki/List_of_DNS_record_types#[1]_Obsolete_record_types */ + answer = dns_header (); + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, type_id); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + dns_builder_add_uint16 (answer, 0); /* rdlength */ + + records = g_resolver_records_from_res_query ("example.org", + type_id, + answer->data, + answer->len, + 0, + &local_error); + + g_assert_error (local_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND); + g_assert_null (records); + g_clear_error (&local_error); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_mx_valid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *mx_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_MX)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* MX rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.9 */ + mx_rdata = g_byte_array_new (); + dns_builder_add_uint16 (mx_rdata, 0); /* preference */ + dns_builder_add_domain (mx_rdata, "mail.example.org"); + dns_builder_add_answer_data (answer, mx_rdata); + g_byte_array_unref (mx_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_MX, answer, + "(@q 0, 'mail.example.org')"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_mx_invalid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *mx_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_MX)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* MX rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.9 + * + * Use an invalid domain to trigger parsing failure. */ + mx_rdata = g_byte_array_new (); + dns_builder_add_uint16 (mx_rdata, 0); /* preference */ + dns_builder_add_invalid_domain (mx_rdata, answer->len + 2); + dns_builder_add_answer_data (answer, mx_rdata); + g_byte_array_unref (mx_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_MX, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_mx_invalid_too_short (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *mx_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_MX)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* MX rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.9 + * + * Miss out the domain field to trigger failure */ + mx_rdata = g_byte_array_new (); + dns_builder_add_uint16 (mx_rdata, 0); /* preference */ + /* missing domain field */ + dns_builder_add_answer_data (answer, mx_rdata); + g_byte_array_unref (mx_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_MX, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_mx_invalid_too_short2 (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *mx_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_MX)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* MX rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.9 + * + * Miss out all fields to trigger failure */ + mx_rdata = g_byte_array_new (); + /* missing preference and domain fields */ + dns_builder_add_answer_data (answer, mx_rdata); + g_byte_array_unref (mx_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_MX, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_ns_valid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *ns_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_NS)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* NS rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.11 */ + ns_rdata = g_byte_array_new (); + dns_builder_add_domain (ns_rdata, "ns.example.org"); + dns_builder_add_answer_data (answer, ns_rdata); + g_byte_array_unref (ns_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_NS, answer, + "('ns.example.org',)"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_ns_invalid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *ns_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_NS)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* NS rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.11 + * + * Use an invalid domain to trigger parsing failure. */ + ns_rdata = g_byte_array_new (); + dns_builder_add_invalid_domain (ns_rdata, answer->len + 2); + dns_builder_add_answer_data (answer, ns_rdata); + g_byte_array_unref (ns_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_NS, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_soa_valid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *soa_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SOA)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SOA rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13 */ + soa_rdata = g_byte_array_new (); + dns_builder_add_domain (soa_rdata, "mname.example.org"); + dns_builder_add_domain (soa_rdata, "rname.example.org"); + dns_builder_add_uint32 (soa_rdata, 0); /* serial */ + dns_builder_add_uint32 (soa_rdata, 0); /* refresh */ + dns_builder_add_uint32 (soa_rdata, 0); /* retry */ + dns_builder_add_uint32 (soa_rdata, 0); /* expire */ + dns_builder_add_uint32 (soa_rdata, 0); /* minimum */ + dns_builder_add_answer_data (answer, soa_rdata); + g_byte_array_unref (soa_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_SOA, answer, + "('mname.example.org', 'rname.example.org', @u 0, @u 0, @u 0, @u 0, @u 0)"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_soa_invalid_mname (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *soa_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SOA)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SOA rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13 + * + * Use an invalid domain to trigger parsing failure. */ + soa_rdata = g_byte_array_new (); + dns_builder_add_invalid_domain (soa_rdata, answer->len + 2); /* mname */ + dns_builder_add_domain (soa_rdata, "rname.example.org"); + dns_builder_add_uint32 (soa_rdata, 0); /* serial */ + dns_builder_add_uint32 (soa_rdata, 0); /* refresh */ + dns_builder_add_uint32 (soa_rdata, 0); /* retry */ + dns_builder_add_uint32 (soa_rdata, 0); /* expire */ + dns_builder_add_uint32 (soa_rdata, 0); /* minimum */ + dns_builder_add_answer_data (answer, soa_rdata); + g_byte_array_unref (soa_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SOA, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_soa_invalid_rname (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *soa_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SOA)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SOA rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13 + * + * Use an invalid domain to trigger parsing failure. */ + soa_rdata = g_byte_array_new (); + dns_builder_add_domain (soa_rdata, "mname.example.org"); + dns_builder_add_invalid_domain (soa_rdata, answer->len + 2); /* rname */ + dns_builder_add_uint32 (soa_rdata, 0); /* serial */ + dns_builder_add_uint32 (soa_rdata, 0); /* refresh */ + dns_builder_add_uint32 (soa_rdata, 0); /* retry */ + dns_builder_add_uint32 (soa_rdata, 0); /* expire */ + dns_builder_add_uint32 (soa_rdata, 0); /* minimum */ + dns_builder_add_answer_data (answer, soa_rdata); + g_byte_array_unref (soa_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SOA, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_soa_invalid_too_short (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *soa_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SOA)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SOA rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13 + * + * Miss out one of the fields to trigger a failure. */ + soa_rdata = g_byte_array_new (); + dns_builder_add_domain (soa_rdata, "mname.example.org"); + dns_builder_add_domain (soa_rdata, "rname.example.org"); + dns_builder_add_uint32 (soa_rdata, 0); /* serial */ + dns_builder_add_uint32 (soa_rdata, 0); /* refresh */ + dns_builder_add_uint32 (soa_rdata, 0); /* retry */ + dns_builder_add_uint32 (soa_rdata, 0); /* expire */ + /* missing minimum field */ + dns_builder_add_answer_data (answer, soa_rdata); + g_byte_array_unref (soa_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SOA, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_txt_valid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *txt_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_TXT)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* TXT rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14 */ + txt_rdata = g_byte_array_new (); + dns_builder_add_length_prefixed_string (txt_rdata, "some test content"); + dns_builder_add_answer_data (answer, txt_rdata); + g_byte_array_unref (txt_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_TXT, answer, + "(['some test content'],)"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_txt_valid_multiple_strings (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *txt_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_TXT)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* TXT rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14 */ + txt_rdata = g_byte_array_new (); + dns_builder_add_length_prefixed_string (txt_rdata, "some test content"); + dns_builder_add_length_prefixed_string (txt_rdata, "more test content"); + dns_builder_add_answer_data (answer, txt_rdata); + g_byte_array_unref (txt_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_TXT, answer, + "(['some test content', 'more test content'],)"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_txt_invalid_empty (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *txt_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_TXT)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* TXT rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14 + * + * Provide zero character strings (i.e. an empty rdata section) to trigger + * failure. */ + txt_rdata = g_byte_array_new (); + dns_builder_add_answer_data (answer, txt_rdata); + g_byte_array_unref (txt_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_TXT, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_txt_invalid_overflow (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *txt_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_TXT)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* TXT rdata, https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14 + * + * Use a character string whose length exceeds the remaining length in the + * answer record, to trigger failure. */ + txt_rdata = g_byte_array_new (); + dns_builder_add_uint8 (txt_rdata, 10); /* length, but no content */ + dns_builder_add_answer_data (answer, txt_rdata); + g_byte_array_unref (txt_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_TXT, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_srv_valid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *srv_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SRV)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SRV rdata, https://datatracker.ietf.org/doc/html/rfc2782 */ + srv_rdata = g_byte_array_new (); + dns_builder_add_uint16 (srv_rdata, 0); /* priority */ + dns_builder_add_uint16 (srv_rdata, 0); /* weight */ + dns_builder_add_uint16 (srv_rdata, 0); /* port */ + dns_builder_add_domain (srv_rdata, "target.example.org"); + dns_builder_add_answer_data (answer, srv_rdata); + g_byte_array_unref (srv_rdata); + + assert_query_succeeds ("example.org", G_RESOLVER_RECORD_SRV, answer, + "(@q 0, @q 0, @q 0, 'target.example.org')"); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_srv_invalid (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *srv_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SRV)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SRV rdata, https://datatracker.ietf.org/doc/html/rfc2782 + * + * Use an invalid domain to trigger parsing failure. */ + srv_rdata = g_byte_array_new (); + dns_builder_add_uint16 (srv_rdata, 0); /* priority */ + dns_builder_add_uint16 (srv_rdata, 0); /* weight */ + dns_builder_add_uint16 (srv_rdata, 0); /* port */ + dns_builder_add_invalid_domain (srv_rdata, answer->len + 2); + dns_builder_add_answer_data (answer, srv_rdata); + g_byte_array_unref (srv_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SRV, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_srv_invalid_too_short (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *srv_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SRV)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SRV rdata, https://datatracker.ietf.org/doc/html/rfc2782 + * + * Miss out the target field to trigger failure */ + srv_rdata = g_byte_array_new (); + dns_builder_add_uint16 (srv_rdata, 0); /* priority */ + dns_builder_add_uint16 (srv_rdata, 0); /* weight */ + dns_builder_add_uint16 (srv_rdata, 0); /* port */ + /* missing target field */ + dns_builder_add_answer_data (answer, srv_rdata); + g_byte_array_unref (srv_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SRV, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +static void +test_srv_invalid_too_short2 (void) +{ +#ifndef HAVE_DN_COMP + g_test_skip ("The dn_comp() function was not available."); + return; +#else + GByteArray *answer = NULL, *srv_rdata = NULL; + + answer = dns_header (); + + /* Resource record */ + dns_builder_add_domain (answer, "example.org"); + dns_builder_add_uint16 (answer, g_resolver_record_type_to_rrtype (G_RESOLVER_RECORD_SRV)); + dns_builder_add_uint16 (answer, 1); /* qclass=C_IN */ + dns_builder_add_uint32 (answer, 0); /* ttl (ignored) */ + + /* SRV rdata, https://datatracker.ietf.org/doc/html/rfc2782 + * + * Miss out the target and port fields to trigger failure */ + srv_rdata = g_byte_array_new (); + dns_builder_add_uint16 (srv_rdata, 0); /* priority */ + dns_builder_add_uint16 (srv_rdata, 0); /* weight */ + /* missing port and target fields */ + dns_builder_add_answer_data (answer, srv_rdata); + g_byte_array_unref (srv_rdata); + + assert_query_fails ("example.org", G_RESOLVER_RECORD_SRV, answer); + + g_byte_array_free (answer, TRUE); +#endif +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + g_test_add_func ("/gresolver/invalid-header", test_invalid_header); + g_test_add_func ("/gresolver/unknown-record-type", test_unknown_record_type); + g_test_add_func ("/gresolver/mx/valid", test_mx_valid); + g_test_add_func ("/gresolver/mx/invalid", test_mx_invalid); + g_test_add_func ("/gresolver/mx/invalid/too-short", test_mx_invalid_too_short); + g_test_add_func ("/gresolver/mx/invalid/too-short2", test_mx_invalid_too_short2); + g_test_add_func ("/gresolver/ns/valid", test_ns_valid); + g_test_add_func ("/gresolver/ns/invalid", test_ns_invalid); + g_test_add_func ("/gresolver/soa/valid", test_soa_valid); + g_test_add_func ("/gresolver/soa/invalid/mname", test_soa_invalid_mname); + g_test_add_func ("/gresolver/soa/invalid/rname", test_soa_invalid_rname); + g_test_add_func ("/gresolver/soa/invalid/too-short", test_soa_invalid_too_short); + g_test_add_func ("/gresolver/srv/valid", test_srv_valid); + g_test_add_func ("/gresolver/srv/invalid", test_srv_invalid); + g_test_add_func ("/gresolver/srv/invalid/too-short", test_srv_invalid_too_short); + g_test_add_func ("/gresolver/srv/invalid/too-short2", test_srv_invalid_too_short2); + g_test_add_func ("/gresolver/txt/valid", test_txt_valid); + g_test_add_func ("/gresolver/txt/valid/multiple-strings", test_txt_valid_multiple_strings); + g_test_add_func ("/gresolver/txt/invalid/empty", test_txt_invalid_empty); + g_test_add_func ("/gresolver/txt/invalid/overflow", test_txt_invalid_overflow); + + return g_test_run (); +} diff --git a/gio/tests/resolver.c b/gio/tests/resolver.c index 6e0c4d73b..d62a4fd18 100644 --- a/gio/tests/resolver.c +++ b/gio/tests/resolver.c @@ -44,12 +44,12 @@ static G_NORETURN void usage (void) { fprintf (stderr, "Usage: resolver [-s] [hostname | IP | service/protocol/domain ] ...\n"); - fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA] rrname ...\n"); + fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA|SRV] rrname ...\n"); fprintf (stderr, " resolver [-s] -c NUMBER [hostname | IP | service/protocol/domain ]\n"); fprintf (stderr, " Use -s to do synchronous lookups.\n"); fprintf (stderr, " Use -c NUMBER (and only a single resolvable argument) to test GSocketConnectable.\n"); fprintf (stderr, " The given NUMBER determines how many times the connectable will be enumerated.\n"); - fprintf (stderr, " Use -t with MX, TXT, NS or SOA to look up DNS records of those types.\n"); + fprintf (stderr, " Use -t with MX, TXT, NS, SOA or SRV to look up DNS records of those types.\n"); exit (1); } @@ -233,6 +233,46 @@ print_resolved_txt (const char *rrname, } static void +print_resolved_srv (const char *rrname, + GList *records, + GError *error) +{ + G_LOCK (response); + printf ("Domain: %s\n", rrname); + if (error) + { + printf ("Error: %s\n", error->message); + g_error_free (error); + } + else if (!records) + { + printf ("no SRV records\n"); + } + else + { + GList *t; + + for (t = records; t != NULL; t = t->next) + { + guint16 priority, weight, port; + const gchar *target; + + g_variant_get (t->data, "(qqq&s)", &priority, &weight, &port, &target); + + printf ("%s (priority %u, weight %u, port %u)\n", + target, (guint) priority, (guint) weight, (guint) port); + g_variant_unref (t->data); + } + + g_list_free (records); + } + printf ("\n"); + + done_lookup (); + G_UNLOCK (response); +} + +static void print_resolved_soa (const char *rrname, GList *records, GError *error) @@ -331,6 +371,9 @@ lookup_one_sync (const char *arg) case G_RESOLVER_RECORD_TXT: print_resolved_txt (arg, records, error); break; + case G_RESOLVER_RECORD_SRV: + print_resolved_srv (arg, records, error); + break; default: g_warn_if_reached (); break; @@ -449,6 +492,9 @@ lookup_records_callback (GObject *source, case G_RESOLVER_RECORD_TXT: print_resolved_txt (arg, records, error); break; + case G_RESOLVER_RECORD_SRV: + print_resolved_srv (arg, records, error); + break; default: g_warn_if_reached (); break; @@ -659,9 +705,11 @@ record_type_arg (const gchar *option_name, record_type = G_RESOLVER_RECORD_SOA; } else if (g_ascii_strcasecmp (value, "NS") == 0) { record_type = G_RESOLVER_RECORD_NS; + } else if (g_ascii_strcasecmp (value, "SRV") == 0) { + record_type = G_RESOLVER_RECORD_SRV; } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Specify MX, TXT, NS or SOA for the special record lookup types"); + "Specify MX, TXT, NS, SOA or SRV for the special record lookup types"); return FALSE; } diff --git a/gio/tests/resources.c b/gio/tests/resources.c index 325775fd6..7ba5896a0 100644 --- a/gio/tests/resources.c +++ b/gio/tests/resources.c @@ -63,7 +63,7 @@ test_resource (GResource *resource) "/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, G_RESOURCE_FLAGS_COMPRESSED); @@ -81,7 +81,7 @@ test_resource (GResource *resource) "/a_prefix/test2.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, 0); @@ -90,7 +90,7 @@ test_resource (GResource *resource) "/a_prefix/test2-alias.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, 0); @@ -137,13 +137,13 @@ test_resource (GResource *resource) "/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (in != NULL); + g_assert_nonnull (in); g_assert_no_error (error); success = g_input_stream_read_all (in, buffer, sizeof (buffer) - 1, &size, NULL, &error); - g_assert (success); + g_assert_true (success); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); buffer[size] = 0; @@ -175,7 +175,7 @@ test_resource (GResource *resource) "/a_prefix/test2.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -186,7 +186,7 @@ test_resource (GResource *resource) "/a_prefix/test2-alias.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -211,7 +211,7 @@ test_resource (GResource *resource) "/a_prefix", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (children != NULL); + g_assert_nonnull (children); g_assert_no_error (error); g_assert_cmpint (g_strv_length (children), ==, 2); g_strfreev (children); @@ -221,7 +221,7 @@ test_resource (GResource *resource) "/a_prefix/", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (children != NULL); + g_assert_nonnull (children); g_assert_no_error (error); g_assert_cmpint (g_strv_length (children), ==, 2); g_strfreev (children); @@ -238,7 +238,7 @@ test_resource (GResource *resource) "/with/no/trailing/slash", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (children == NULL); + g_assert_null (children); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); } @@ -250,12 +250,12 @@ test_resource_file (void) GError *error = NULL; resource = g_resource_load ("not-there", &error); - g_assert (resource == NULL); + g_assert_null (resource); g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT); g_clear_error (&error); resource = g_resource_load (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &error); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); test_resource (resource); @@ -293,10 +293,10 @@ test_resource_file_path (void) gchar *uri; file = g_file_new_for_uri (test_uris[i].input); - g_assert (file != NULL); + g_assert_nonnull (file); uri = g_file_get_uri (file); - g_assert (uri != NULL); + g_assert_nonnull (uri); g_assert_cmpstr (uri, ==, test_uris[i].expected); @@ -317,12 +317,12 @@ test_resource_data (void) loaded_file = g_file_get_contents (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &content, &content_size, NULL); - g_assert (loaded_file); + g_assert_true (loaded_file); data = g_bytes_new_take (content, content_size); resource = g_resource_new_from_data (data, &error); g_bytes_unref (data); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); test_resource (resource); @@ -342,7 +342,7 @@ test_resource_data_unaligned (void) loaded_file = g_file_get_contents (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &content, &content_size, NULL); - g_assert (loaded_file); + g_assert_true (loaded_file); content_copy = g_new (char, content_size + 1); memcpy (content_copy + 1, content, content_size); @@ -352,7 +352,7 @@ test_resource_data_unaligned (void) g_free (content); resource = g_resource_new_from_data (data, &error); g_bytes_unref (data); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); test_resource (resource); @@ -413,13 +413,13 @@ test_resource_registered (void) char buffer[128]; resource = g_resource_load (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &error); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); found = g_resources_get_info ("/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (!found); + g_assert_false (found); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); @@ -428,10 +428,10 @@ test_resource_registered (void) found = g_resources_get_info ("/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); - g_assert (flags == (G_RESOURCE_FLAGS_COMPRESSED)); + g_assert_cmpint (flags, ==, G_RESOURCE_FLAGS_COMPRESSED); found = g_resources_get_info ("/empty.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, @@ -439,12 +439,12 @@ test_resource_registered (void) g_assert_no_error (error); g_assert_true (found); g_assert_cmpint (size, ==, 0); - g_assert (flags == (G_RESOURCE_FLAGS_COMPRESSED)); + g_assert_cmpint (flags, ==, G_RESOURCE_FLAGS_COMPRESSED); found = g_resources_get_info ("/a_prefix/test2.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpint (flags, ==, 0); @@ -452,7 +452,7 @@ test_resource_registered (void) found = g_resources_get_info ("/a_prefix/test2-alias.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, 0); @@ -467,13 +467,13 @@ test_resource_registered (void) in = g_resources_open_stream ("/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (in != NULL); + g_assert_nonnull (in); g_assert_no_error (error); success = g_input_stream_read_all (in, buffer, sizeof (buffer) - 1, &size, NULL, &error); - g_assert (success); + g_assert_true (success); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); buffer[size] = 0; @@ -510,7 +510,7 @@ test_resource_registered (void) data = g_resources_lookup_data ("/a_prefix/test2.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -520,7 +520,7 @@ test_resource_registered (void) data = g_resources_lookup_data ("/a_prefix/test2-alias.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -530,14 +530,14 @@ test_resource_registered (void) children = g_resources_enumerate_children ("/not/here", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (children == NULL); + g_assert_null (children); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); children = g_resources_enumerate_children ("/a_prefix", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (children != NULL); + g_assert_nonnull (children); g_assert_no_error (error); g_assert_cmpint (g_strv_length (children), ==, 2); g_strfreev (children); @@ -548,7 +548,7 @@ test_resource_registered (void) found = g_resources_get_info ("/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (!found); + g_assert_false (found); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); } @@ -565,7 +565,7 @@ test_resource_automatic (void) found = g_resources_get_info ("/auto_loaded/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpint (flags, ==, 0); @@ -573,7 +573,7 @@ test_resource_automatic (void) data = g_resources_lookup_data ("/auto_loaded/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -593,7 +593,7 @@ test_resource_manual (void) found = g_resources_get_info ("/manual_loaded/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, 0); @@ -601,7 +601,7 @@ test_resource_manual (void) data = g_resources_lookup_data ("/manual_loaded/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -623,7 +623,7 @@ test_resource_manual2 (void) "/manual_loaded/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -730,7 +730,7 @@ test_resource_module (void) found = g_resources_get_info ("/resourceplugin/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (!found); + g_assert_false (found); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); @@ -739,7 +739,7 @@ test_resource_module (void) found = g_resources_get_info ("/resourceplugin/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (found); + g_assert_true (found); g_assert_no_error (error); g_assert_cmpint (size, ==, 6); g_assert_cmpuint (flags, ==, 0); @@ -747,7 +747,7 @@ test_resource_module (void) data = g_resources_lookup_data ("/resourceplugin/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &error); - g_assert (data != NULL); + g_assert_nonnull (data); g_assert_no_error (error); size = g_bytes_get_size (data); g_assert_cmpint (size, ==, 6); @@ -759,9 +759,11 @@ test_resource_module (void) found = g_resources_get_info ("/resourceplugin/test1.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, &size, &flags, &error); - g_assert (!found); + g_assert_false (found); g_assert_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND); g_clear_error (&error); + + g_clear_object (&module); } } @@ -783,12 +785,12 @@ test_uri_query_info (void) loaded_file = g_file_get_contents (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &content, &content_size, NULL); - g_assert (loaded_file); + g_assert_true (loaded_file); data = g_bytes_new_take (content, content_size); resource = g_resource_new_from_data (data, &error); g_bytes_unref (data); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); g_resources_register (resource); @@ -798,9 +800,9 @@ test_uri_query_info (void) g_assert_no_error (error); content_type = g_file_info_get_content_type (info); - g_assert (content_type); + g_assert_nonnull (content_type); mime_type = g_content_type_get_mime_type (content_type); - g_assert (mime_type); + g_assert_nonnull (mime_type); g_assert_cmpstr (mime_type, ==, "text/plain"); g_free (mime_type); @@ -847,19 +849,19 @@ test_uri_file (void) loaded_file = g_file_get_contents (g_test_get_filename (G_TEST_BUILT, "test.gresource", NULL), &content, &content_size, NULL); - g_assert (loaded_file); + g_assert_true (loaded_file); data = g_bytes_new_take (content, content_size); resource = g_resource_new_from_data (data, &error); g_bytes_unref (data); - g_assert (resource != NULL); + g_assert_nonnull (resource); g_assert_no_error (error); g_resources_register (resource); file = g_file_new_for_uri ("resource://" "/a_prefix/test2-alias.txt"); - g_assert (g_file_get_path (file) == NULL); + g_assert_null (g_file_get_path (file)); name = g_file_get_parse_name (file); g_assert_cmpstr (name, ==, "resource:///a_prefix/test2-alias.txt"); @@ -869,15 +871,15 @@ test_uri_file (void) g_assert_cmpstr (name, ==, "resource:///a_prefix/test2-alias.txt"); g_free (name); - g_assert (!g_file_is_native (file)); - g_assert (!g_file_has_uri_scheme (file, "http")); - g_assert (g_file_has_uri_scheme (file, "resource")); + g_assert_false (g_file_is_native (file)); + g_assert_false (g_file_has_uri_scheme (file, "http")); + g_assert_true (g_file_has_uri_scheme (file, "resource")); scheme = g_file_get_uri_scheme (file); g_assert_cmpstr (scheme, ==, "resource"); g_free (scheme); file2 = g_file_dup (file); - g_assert (g_file_equal (file, file2)); + g_assert_true (g_file_equal (file, file2)); g_object_unref (file2); parent = g_file_get_parent (file); @@ -886,31 +888,31 @@ test_uri_file (void) file2 = g_file_get_child_for_display_name (parent, "test2-alias.txt", &error); g_assert_no_error (error); - g_assert (g_file_equal (file, file2)); + g_assert_true (g_file_equal (file, file2)); g_object_unref (file2); info = g_file_enumerator_next_file (enumerator, NULL, &error); g_assert_no_error (error); - g_assert (info != NULL); + g_assert_nonnull (info); g_object_unref (info); info = g_file_enumerator_next_file (enumerator, NULL, &error); g_assert_no_error (error); - g_assert (info != NULL); + g_assert_nonnull (info); g_object_unref (info); info = g_file_enumerator_next_file (enumerator, NULL, &error); g_assert_no_error (error); - g_assert (info == NULL); + g_assert_null (info); g_file_enumerator_close (enumerator, NULL, &error); g_assert_no_error (error); g_object_unref (enumerator); file2 = g_file_new_for_uri ("resource://" "a_prefix/../a_prefix//test2-alias.txt"); - g_assert (g_file_equal (file, file2)); + g_assert_true (g_file_equal (file, file2)); - g_assert (g_file_has_prefix (file, parent)); + g_assert_true (g_file_has_prefix (file, parent)); name = g_file_get_relative_path (parent, file); g_assert_cmpstr (name, ==, "test2-alias.txt"); @@ -929,9 +931,9 @@ test_uri_file (void) stream = G_INPUT_STREAM (g_file_read (file, NULL, &error)); g_assert_no_error (error); g_assert_cmpint (g_seekable_tell (G_SEEKABLE (stream)), ==, 0); - g_assert (g_seekable_can_seek (G_SEEKABLE (G_SEEKABLE (stream)))); + g_assert_true (g_seekable_can_seek (G_SEEKABLE (G_SEEKABLE (stream)))); ret = g_seekable_seek (G_SEEKABLE (stream), 1, G_SEEK_SET, NULL, &error); - g_assert (ret); + g_assert_true (ret); g_assert_no_error (error); skipped = g_input_stream_skip (stream, 1, NULL, &error); g_assert_cmpint (skipped, ==, 1); @@ -939,7 +941,7 @@ test_uri_file (void) memset (buf, 0, 1024); ret = g_input_stream_read_all (stream, &buf, 1024, NULL, NULL, &error); - g_assert (ret); + g_assert_true (ret); g_assert_no_error (error); g_assert_cmpstr (buf, ==, "st2\n"); info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (stream), @@ -947,12 +949,12 @@ test_uri_file (void) NULL, &error); g_assert_no_error (error); - g_assert (info != NULL); + g_assert_nonnull (info); g_assert_cmpint (g_file_info_get_size (info), ==, 6); g_object_unref (info); ret = g_input_stream_close (stream, NULL, &error); - g_assert (ret); + g_assert_true (ret); g_assert_no_error (error); g_object_unref (stream); diff --git a/gio/tests/socket.c b/gio/tests/socket.c index ee38fabcd..4dae36b17 100644 --- a/gio/tests/socket.c +++ b/gio/tests/socket.c @@ -17,15 +17,21 @@ */ #include <gio/gio.h> +#include <glib/gstdio.h> #include <gio/gcredentialsprivate.h> +#include <gio/gunixconnection.h> + #ifdef G_OS_UNIX #include <errno.h> #include <sys/wait.h> #include <string.h> #include <stdlib.h> #include <gio/gnetworking.h> -#include <gio/gunixconnection.h> +#endif + +#ifdef G_OS_WIN32 +#include "giowin32-afunix.h" #endif #include "gnetworkingprivate.h" @@ -1327,7 +1333,23 @@ test_sockaddr (void) g_object_unref (saddr); } -#ifdef G_OS_UNIX +static void +bind_win32_unixfd (int fd) +{ +#ifdef G_OS_WIN32 + gint len, ret; + struct sockaddr_un addr; + + memset (&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + len = g_snprintf (addr.sun_path, sizeof addr.sun_path, "%s" G_DIR_SEPARATOR_S "%d.sock", g_get_tmp_dir (), fd); + g_assert_cmpint (len, <=, sizeof addr.sun_path); + ret = bind (fd, (struct sockaddr *)&addr, sizeof addr); + g_assert_cmpint (ret, ==, 0); + g_remove (addr.sun_path); +#endif +} + static void test_unix_from_fd (void) { @@ -1336,8 +1358,17 @@ test_unix_from_fd (void) GSocket *s; fd = socket (AF_UNIX, SOCK_STREAM, 0); +#ifdef G_OS_WIN32 + if (fd == -1) + { + g_test_skip ("AF_UNIX not supported on this Windows system."); + return; + } +#endif g_assert_cmpint (fd, !=, -1); + bind_win32_unixfd (fd); + error = NULL; s = g_socket_new_from_fd (fd, &error); g_assert_no_error (error); @@ -1356,8 +1387,17 @@ test_unix_connection (void) GSocketConnection *c; fd = socket (AF_UNIX, SOCK_STREAM, 0); +#ifdef G_OS_WIN32 + if (fd == -1) + { + g_test_skip ("AF_UNIX not supported on this Windows system."); + return; + } +#endif g_assert_cmpint (fd, !=, -1); + bind_win32_unixfd (fd); + error = NULL; s = g_socket_new_from_fd (fd, &error); g_assert_no_error (error); @@ -1367,6 +1407,7 @@ test_unix_connection (void) g_object_unref (s); } +#ifdef G_OS_UNIX static GSocketConnection * create_connection_for_fd (int fd) { @@ -1466,6 +1507,7 @@ test_unix_connection_ancillary_data (void) * g_unix_connection_receive_credentials(). */ } +#endif static gboolean postmortem_source_cb (GSocket *socket, @@ -1490,6 +1532,14 @@ test_source_postmortem (void) gboolean callback_visited = FALSE; socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); +#ifdef G_OS_WIN32 + if (error) + { + g_test_skip_printf ("AF_UNIX not supported on this Windows system: %s", error->message); + g_clear_error (&error); + return; + } +#endif g_assert_no_error (error); context = g_main_context_new (); @@ -1513,8 +1563,6 @@ test_source_postmortem (void) g_main_context_unref (context); } -#endif /* G_OS_UNIX */ - static void test_reuse_tcp (void) { @@ -2076,22 +2124,145 @@ client_setup_thread (gpointer user_data) return NULL; } -#ifdef G_OS_UNIX +#ifdef G_OS_WIN32 +/* + * _g_win32_socketpair: + * + * Create a pair of connected sockets, similar to POSIX/BSD socketpair(). + * + * Windows does not (yet) provide a socketpair() function. However, since the + * introduction of AF_UNIX sockets, it is possible to implement a fairly close + * function. + */ +static gint +_g_win32_socketpair (gint domain, + gint type, + gint protocol, + gint sv[2]) +{ + struct sockaddr_un addr = { 0, }; + socklen_t socklen; + SOCKET listener = INVALID_SOCKET; + SOCKET client = INVALID_SOCKET; + SOCKET server = INVALID_SOCKET; + gchar *path = NULL; + int tmpfd, rv = -1; + u_long arg, br; + + g_return_val_if_fail (sv != NULL, -1); + + addr.sun_family = AF_UNIX; + socklen = sizeof (addr); + + tmpfd = g_file_open_tmp (NULL, &path, NULL); + if (tmpfd == -1) + { + WSASetLastError (WSAEACCES); + goto out; + } + + g_close (tmpfd, NULL); + + if (strlen (path) >= sizeof (addr.sun_path)) + { + WSASetLastError (WSAEACCES); + goto out; + } + + strncpy (addr.sun_path, path, sizeof (addr.sun_path) - 1); + + listener = socket (domain, type, protocol); + if (listener == INVALID_SOCKET) + goto out; + + if (DeleteFile (path) == 0) + { + if (GetLastError () != ERROR_FILE_NOT_FOUND) + goto out; + } + + if (bind (listener, (struct sockaddr *) &addr, socklen) == SOCKET_ERROR) + goto out; + + if (listen (listener, 1) == SOCKET_ERROR) + goto out; + + client = socket (domain, type, protocol); + if (client == INVALID_SOCKET) + goto out; + + arg = 1; + if (ioctlsocket (client, FIONBIO, &arg) == SOCKET_ERROR) + goto out; + + if (connect (client, (struct sockaddr *) &addr, socklen) == SOCKET_ERROR && + WSAGetLastError () != WSAEWOULDBLOCK) + goto out; + + server = accept (listener, NULL, NULL); + if (server == INVALID_SOCKET) + goto out; + + arg = 0; + if (ioctlsocket (client, FIONBIO, &arg) == SOCKET_ERROR) + goto out; + + if (WSAIoctl (server, SIO_AF_UNIX_GETPEERPID, + NULL, 0U, + &arg, sizeof (arg), &br, + NULL, NULL) == SOCKET_ERROR || arg != GetCurrentProcessId ()) + { + WSASetLastError (WSAEACCES); + goto out; + } + + sv[0] = server; + server = INVALID_SOCKET; + sv[1] = client; + client = INVALID_SOCKET; + rv = 0; + + out: + if (listener != INVALID_SOCKET) + closesocket (listener); + if (client != INVALID_SOCKET) + closesocket (client); + if (server != INVALID_SOCKET) + closesocket (server); + + DeleteFile (path); + g_free (path); + return rv; +} +#endif /* G_OS_WIN32 */ + static void test_credentials_unix_socketpair (void) { gint fds[2]; gint status; - GSocket *sock; + GSocket *sock[2]; GError *error = NULL; GCredentials *creds; +#ifdef G_OS_WIN32 + status = _g_win32_socketpair (PF_UNIX, SOCK_STREAM, 0, fds); + if (status != 0) + { + g_test_skip ("AF_UNIX not supported on this Windows system."); + return; + } +#else status = socketpair (PF_UNIX, SOCK_STREAM, 0, fds); +#endif g_assert_cmpint (status, ==, 0); - sock = g_socket_new_from_fd (fds[0], &error); + sock[0] = g_socket_new_from_fd (fds[0], &error); + g_assert_no_error (error); + sock[1] = g_socket_new_from_fd (fds[1], &error); + g_assert_no_error (error); - creds = g_socket_get_credentials (sock, &error); + creds = g_socket_get_credentials (sock[0], &error); if (creds != NULL) { gchar *str = g_credentials_to_string (creds); @@ -2106,11 +2277,10 @@ test_credentials_unix_socketpair (void) g_clear_error (&error); } - g_object_unref (sock); - close (fds[1]); + g_object_unref (sock[0]); + g_object_unref (sock[1]); } #endif -#endif int main (int argc, @@ -2151,12 +2321,12 @@ main (int argc, g_test_add_func ("/socket/timed_wait", test_timed_wait); g_test_add_func ("/socket/fd_reuse", test_fd_reuse); g_test_add_func ("/socket/address", test_sockaddr); -#ifdef G_OS_UNIX g_test_add_func ("/socket/unix-from-fd", test_unix_from_fd); g_test_add_func ("/socket/unix-connection", test_unix_connection); +#ifdef G_OS_UNIX g_test_add_func ("/socket/unix-connection-ancillary-data", test_unix_connection_ancillary_data); - g_test_add_func ("/socket/source-postmortem", test_source_postmortem); #endif + g_test_add_func ("/socket/source-postmortem", test_source_postmortem); g_test_add_func ("/socket/reuse/tcp", test_reuse_tcp); g_test_add_func ("/socket/reuse/udp", test_reuse_udp); g_test_add_data_func ("/socket/get_available/datagram", GUINT_TO_POINTER (G_SOCKET_TYPE_DATAGRAM), @@ -2173,10 +2343,8 @@ main (int argc, #if G_CREDENTIALS_SUPPORTED g_test_add_func ("/socket/credentials/tcp_client", test_credentials_tcp_client); g_test_add_func ("/socket/credentials/tcp_server", test_credentials_tcp_server); -#ifdef G_OS_UNIX g_test_add_func ("/socket/credentials/unix_socketpair", test_credentials_unix_socketpair); #endif -#endif return g_test_run(); } diff --git a/gio/tests/testfilemonitor.c b/gio/tests/testfilemonitor.c index a52ade715..082f0db26 100644 --- a/gio/tests/testfilemonitor.c +++ b/gio/tests/testfilemonitor.c @@ -4,6 +4,17 @@ #include <stdlib.h> #include <gio/gio.h> +static gboolean +skip_win32 (void) +{ +#ifdef G_OS_WIN32 + g_test_skip ("FIXME, test is broken on win32"); + return TRUE; +#else + return FALSE; +#endif +} + /* These tests were written for the inotify implementation. * Other implementations may require slight adjustments in * the tests, e.g. the length of timeouts @@ -361,6 +372,9 @@ test_atomic_replace (Fixture *fixture, GError *error = NULL; TestData data; + if (skip_win32 ()) + return; + data.step = 0; data.events = NULL; @@ -466,6 +480,9 @@ test_file_changes (Fixture *fixture, GError *error = NULL; TestData data; + if (skip_win32 ()) + return; + data.step = 0; data.events = NULL; @@ -583,6 +600,9 @@ test_dir_monitor (Fixture *fixture, GError *error = NULL; TestData data; + if (skip_win32 ()) + return; + data.step = 0; data.events = NULL; @@ -680,6 +700,9 @@ test_dir_non_existent (Fixture *fixture, TestData data; GError *error = NULL; + if (skip_win32 ()) + return; + data.step = 0; data.events = NULL; @@ -789,6 +812,9 @@ test_cross_dir_moves (Fixture *fixture, GError *error = NULL; TestData data[2]; + if (skip_win32 ()) + return; + data[0].step = 0; data[0].events = NULL; @@ -960,6 +986,9 @@ test_file_hard_links (Fixture *fixture, g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=755721"); + if (skip_win32 ()) + return; + #ifdef HAVE_LINK g_test_message ("Running with hard link tests"); #else /* if !HAVE_LINK */ @@ -1007,6 +1036,57 @@ test_file_hard_links (Fixture *fixture, g_object_unref (data.output_stream); } +static void +test_finalize_in_callback (Fixture *fixture, + gconstpointer user_data) +{ + GFile *file = NULL; + guint i; + + g_test_summary ("Test that finalization of a GFileMonitor in one of its " + "callbacks doesn’t cause a deadlock."); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/1941"); + + file = g_file_get_child (fixture->tmp_dir, "race-file"); + + for (i = 0; i < 50; i++) + { + GFileMonitor *monitor = NULL; + GError *local_error = NULL; + + /* Monitor the file. */ + monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (monitor); + + /* Create the file. */ + g_file_replace_contents (file, "hello", 5, NULL, FALSE, + G_FILE_CREATE_NONE, NULL, NULL, &local_error); + g_assert_no_error (local_error); + + /* Immediately drop the last ref to the monitor in the hope that this + * happens in the middle of the critical section in + * g_file_monitor_source_handle_event(), so that any cleanup at the end + * of that function is done with a now-finalised file monitor. */ + g_object_unref (monitor); + + /* Re-create the monitor and do the same again for deleting the file, to + * give a second chance at hitting the race condition. */ + monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &local_error); + g_assert_no_error (local_error); + g_assert_nonnull (monitor); + + /* Delete the file. */ + g_file_delete (file, NULL, &local_error); + g_assert_no_error (local_error); + + /* Drop the ref again. */ + g_object_unref (monitor); + } + + g_object_unref (file); +} + int main (int argc, char *argv[]) { @@ -1018,6 +1098,7 @@ main (int argc, char *argv[]) g_test_add ("/monitor/dir-not-existent", Fixture, NULL, setup, test_dir_non_existent, teardown); g_test_add ("/monitor/cross-dir-moves", Fixture, NULL, setup, test_cross_dir_moves, teardown); g_test_add ("/monitor/file/hard-links", Fixture, NULL, setup, test_file_hard_links, teardown); + g_test_add ("/monitor/finalize-in-callback", Fixture, NULL, setup, test_finalize_in_callback, teardown); return g_test_run (); } diff --git a/gio/tests/tls-certificate.c b/gio/tests/tls-certificate.c index 2995b1028..bae582357 100644 --- a/gio/tests/tls-certificate.c +++ b/gio/tests/tls-certificate.c @@ -559,7 +559,7 @@ dns_names (void) g_assert_cmpuint (actual->len, ==, 1); g_assert_true (g_ptr_array_find_with_equal_func (actual, expected, (GEqualFunc)g_bytes_equal, NULL)); - g_ptr_array_free (actual, FALSE); + g_ptr_array_unref (actual); g_bytes_unref (expected); g_object_unref (cert); } @@ -586,6 +586,45 @@ ip_addresses (void) g_object_unref (cert); } +static void +from_pkcs12 (void) +{ + GTlsCertificate *cert; + GError *error = NULL; + const guint8 data[1] = { 0 }; + + /* This simply fails because our test backend doesn't support this + * property. This reflects using a backend that doesn't support it. + * The real test lives in glib-networking. */ + cert = g_tls_certificate_new_from_pkcs12 (data, 1, NULL, &error); + + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_error_free (error); +} + +static void +from_pkcs12_file (void) +{ + GTlsCertificate *cert; + GError *error = NULL; + char *path = g_test_build_filename (G_TEST_DIST, "cert-tests", "key-cert-password-123.p12", NULL); + + /* Fails on our test backend, see from_pkcs12() above. */ + cert = g_tls_certificate_new_from_file_with_password (path, "123", &error); + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + /* Just for coverage. */ + cert = g_tls_certificate_new_from_file (path, &error); + g_assert_null (cert); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_error_free (error); + + g_free (path); +} + int main (int argc, char *argv[]) @@ -656,6 +695,10 @@ main (int argc, from_pkcs11_uri); g_test_add_func ("/tls-certificate/pkcs11-uri-unsupported", from_unsupported_pkcs11_uri); + g_test_add_func ("/tls-certificate/from_pkcs12", + from_pkcs12); + g_test_add_func ("/tls-certificate/from_pkcs12_file", + from_pkcs12_file); g_test_add_func ("/tls-certificate/not-valid-before", not_valid_before); g_test_add_func ("/tls-certificate/not-valid-after", diff --git a/gio/win32/gwin32fsmonitorutils.c b/gio/win32/gwin32fsmonitorutils.c index b47124634..f2ab5472a 100644 --- a/gio/win32/gwin32fsmonitorutils.c +++ b/gio/win32/gwin32fsmonitorutils.c @@ -92,7 +92,7 @@ g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor, monitor->pfni_prev->Action == FILE_ACTION_RENAMED_OLD_NAME) { /* don't bother sending events, was already sent (rename) */ - fme = -1; + fme = (GFileMonitorEvent) -1; } else fme = G_FILE_MONITOR_EVENT_MOVED_IN; @@ -104,7 +104,7 @@ g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor, break; } - if (fme != -1) + if (fme != (GFileMonitorEvent) -1) return g_file_monitor_source_handle_event (monitor->fms, fme, filename, diff --git a/gio/win32/gwinhttpfile.c b/gio/win32/gwinhttpfile.c index 5b8dcfe0b..e73c87658 100644 --- a/gio/win32/gwinhttpfile.c +++ b/gio/win32/gwinhttpfile.c @@ -546,7 +546,7 @@ g_winhttp_file_query_info (GFile *file, NULL)) { gint64 cl; - int n; + size_t n; const char *gint64_format = "%"G_GINT64_FORMAT"%n"; wchar_t *gint64_format_w = g_utf8_to_utf16 (gint64_format, -1, NULL, NULL, NULL); diff --git a/gio/win32/gwinhttpvfs.c b/gio/win32/gwinhttpvfs.c index 03feaf983..4d5f51498 100644 --- a/gio/win32/gwinhttpvfs.c +++ b/gio/win32/gwinhttpvfs.c @@ -165,7 +165,7 @@ g_winhttp_vfs_get_file_for_uri (GVfs *vfs, const char *uri) { GWinHttpVfs *winhttp_vfs = G_WINHTTP_VFS (vfs); - int i; + gsize i; GFile *ret = NULL; /* If it matches one of "our" schemes, handle it */ @@ -192,7 +192,7 @@ g_winhttp_vfs_get_supported_uri_schemes (GVfs *vfs) { GWinHttpVfs *winhttp_vfs = G_WINHTTP_VFS (vfs); const gchar * const *wrapped_vfs_uri_schemes = g_vfs_get_supported_uri_schemes (winhttp_vfs->wrapped_vfs); - int i, n; + gsize i, n; const gchar **retval; n = 0; diff --git a/gio/xdgmime/meson.build b/gio/xdgmime/meson.build index d107f71bb..4c8a552a2 100644 --- a/gio/xdgmime/meson.build +++ b/gio/xdgmime/meson.build @@ -13,4 +13,5 @@ xdgmime_lib = static_library('xdgmime', sources : xdgmime_sources, include_directories : [configinc], pic : true, - c_args : [ '-DXDG_PREFIX=_gio_xdg' ] + glib_hidden_visibility_args) + c_args : [ '-DHAVE_CONFIG_H', + '-DXDG_PREFIX=_gio_xdg' ] + glib_hidden_visibility_args) diff --git a/gio/xdgmime/xdgmime.c b/gio/xdgmime/xdgmime.c index 9bb93f791..9ab676048 100644 --- a/gio/xdgmime/xdgmime.c +++ b/gio/xdgmime/xdgmime.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmime.h" #include "xdgmimeint.h" @@ -41,6 +45,10 @@ #include <unistd.h> #include <assert.h> +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + typedef struct XdgDirTimeList XdgDirTimeList; typedef struct XdgCallbackList XdgCallbackList; @@ -134,7 +142,8 @@ xdg_dir_time_list_free (XdgDirTimeList *list) } static int -xdg_mime_init_from_directory (const char *directory) +xdg_mime_init_from_directory (const char *directory, + void *user_data) { char *file_name; struct stat st; @@ -400,10 +409,11 @@ xdg_check_file (const char *file_path, static int xdg_check_dir (const char *directory, - int *invalid_dir_list) + void *user_data) { int invalid, exists; char *file_name; + int* invalid_dir_list = user_data; assert (directory != NULL); @@ -458,8 +468,7 @@ xdg_check_dirs (void) for (list = dir_time_list; list; list = list->next) list->checked = XDG_CHECKED_UNCHECKED; - xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir, - &invalid_dir_list); + xdg_run_command_on_dirs (xdg_check_dir, &invalid_dir_list); if (invalid_dir_list) return TRUE; @@ -515,8 +524,7 @@ xdg_mime_init (void) icon_list = _xdg_mime_icon_list_new (); generic_icon_list = _xdg_mime_icon_list_new (); - xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory, - NULL); + xdg_run_command_on_dirs (xdg_mime_init_from_directory, NULL); need_reread = FALSE; } @@ -549,8 +557,6 @@ xdg_mime_get_mime_type_for_data (const void *data, return _xdg_binary_or_text_fallback(data, len); } -#ifdef NOT_USED_IN_GIO - const char * xdg_mime_get_mime_type_for_file (const char *file_name, struct stat *statbuf) @@ -621,13 +627,13 @@ xdg_mime_get_mime_type_for_file (const char *file_name, mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL, mime_types, n); + if (!mime_type) + mime_type = _xdg_binary_or_text_fallback (data, bytes_read); + free (data); fclose (file); - if (mime_type) - return mime_type; - - return _xdg_binary_or_text_fallback(data, bytes_read); + return mime_type; } const char * @@ -646,8 +652,6 @@ xdg_mime_get_mime_type_from_file_name (const char *file_name) return XDG_MIME_TYPE_UNKNOWN; } -#endif - int xdg_mime_get_mime_types_from_file_name (const char *file_name, const char *mime_types[], @@ -661,8 +665,6 @@ xdg_mime_get_mime_types_from_file_name (const char *file_name, return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types); } -#ifdef NOT_USED_IN_GIO - int xdg_mime_is_valid_mime_type (const char *mime_type) { @@ -671,8 +673,6 @@ xdg_mime_is_valid_mime_type (const char *mime_type) return _xdg_utf8_validate (mime_type); } -#endif - void xdg_mime_shutdown (void) { @@ -888,19 +888,14 @@ xdg_mime_mime_type_subclass (const char *mime, char ** xdg_mime_list_mime_parents (const char *mime) { - const char *umime; const char **parents; char **result; int i, n; - xdg_mime_init (); - if (_caches) return _xdg_mime_cache_list_mime_parents (mime); - umime = _xdg_mime_unalias_mime_type (mime); - - parents = _xdg_mime_parent_list_lookup (parent_list, umime); + parents = xdg_mime_get_mime_parents (mime); if (!parents) return NULL; @@ -914,8 +909,6 @@ xdg_mime_list_mime_parents (const char *mime) return result; } -#ifdef NOT_USED_IN_GIO - const char ** xdg_mime_get_mime_parents (const char *mime) { @@ -945,7 +938,6 @@ xdg_mime_dump (void) _xdg_mime_cache_glob_dump (); } -#endif /* Registers a function to be called every time the mime database reloads its files */ @@ -973,8 +965,6 @@ xdg_mime_register_reload_callback (XdgMimeCallback callback, return callback_id - 1; } -#ifdef NOT_USED_IN_GIO - void xdg_mime_remove_callback (int callback_id) { @@ -1000,8 +990,6 @@ xdg_mime_remove_callback (int callback_id) } } -#endif - const char * xdg_mime_get_icon (const char *mime) { diff --git a/gio/xdgmime/xdgmime.h b/gio/xdgmime/xdgmime.h index b175de107..c5909967f 100644 --- a/gio/xdgmime/xdgmime.h +++ b/gio/xdgmime/xdgmime.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ @@ -71,9 +73,9 @@ typedef void (*XdgMimeDestroy) (void *user_data); #define xdg_mime_get_icon XDG_ENTRY(get_icon) #define xdg_mime_get_generic_icon XDG_ENTRY(get_generic_icon) -#define _xdg_mime_mime_type_subclass XDG_RESERVED_ENTRY(mime_type_subclass) #define _xdg_mime_mime_type_equal XDG_RESERVED_ENTRY(mime_type_equal) -#define _xdg_mime_unalias_mime_type XDG_RESERVED_ENTRY(unalias_mime_type) +#define _xdg_mime_mime_type_subclass XDG_RESERVED_ENTRY(mime_type_subclass) +#define _xdg_mime_unalias_mime_type XDG_RESERVED_ENTRY(unalias_mime_type) #endif extern const char xdg_mime_type_unknown[]; @@ -86,17 +88,13 @@ extern const char xdg_mime_type_textplain[]; const char *xdg_mime_get_mime_type_for_data (const void *data, size_t len, int *result_prio); -#ifdef NOT_USED_IN_GIO const char *xdg_mime_get_mime_type_for_file (const char *file_name, struct stat *statbuf); const char *xdg_mime_get_mime_type_from_file_name (const char *file_name); -#endif int xdg_mime_get_mime_types_from_file_name(const char *file_name, const char *mime_types[], int n_mime_types); -#ifdef NOT_USED_IN_GIO int xdg_mime_is_valid_mime_type (const char *mime_type); -#endif int xdg_mime_mime_type_equal (const char *mime_a, const char *mime_b); int xdg_mime_media_type_equal (const char *mime_a, @@ -108,24 +106,18 @@ int xdg_mime_mime_type_subclass (const char *mime_a, * instead, but notice that that function expects you to free * the array it returns. */ -#ifdef NOT_USED_IN_GIO const char **xdg_mime_get_mime_parents (const char *mime); -#endif char ** xdg_mime_list_mime_parents (const char *mime); const char *xdg_mime_unalias_mime_type (const char *mime); const char *xdg_mime_get_icon (const char *mime); const char *xdg_mime_get_generic_icon (const char *mime); int xdg_mime_get_max_buffer_extents (void); void xdg_mime_shutdown (void); -#ifdef NOT_USED_IN_GIO void xdg_mime_dump (void); -#endif int xdg_mime_register_reload_callback (XdgMimeCallback callback, void *data, XdgMimeDestroy destroy); -#ifdef NOT_USED_IN_GIO void xdg_mime_remove_callback (int callback_id); -#endif void xdg_mime_set_dirs (const char * const *dirs); diff --git a/gio/xdgmime/xdgmimealias.c b/gio/xdgmime/xdgmimealias.c index bf95bc0d3..0fc51f94c 100644 --- a/gio/xdgmime/xdgmimealias.c +++ b/gio/xdgmime/xdgmimealias.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmimealias.h" #include "xdgmimeint.h" @@ -161,8 +165,6 @@ _xdg_mime_alias_read_from_file (XdgAliasList *list, } -#ifdef NOT_USED_IN_GIO - void _xdg_mime_alias_list_dump (XdgAliasList *list) { @@ -179,4 +181,4 @@ _xdg_mime_alias_list_dump (XdgAliasList *list) } } -#endif + diff --git a/gio/xdgmime/xdgmimealias.h b/gio/xdgmime/xdgmimealias.h index 46cbc99c3..6e0cfff3b 100644 --- a/gio/xdgmime/xdgmimealias.h +++ b/gio/xdgmime/xdgmimealias.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_ALIAS_H__ @@ -44,7 +46,6 @@ XdgAliasList *_xdg_mime_alias_list_new (void); void _xdg_mime_alias_list_free (XdgAliasList *list); const char *_xdg_mime_alias_list_lookup (XdgAliasList *list, const char *alias); -#ifdef NOT_USED_IN_GIO void _xdg_mime_alias_list_dump (XdgAliasList *list); -#endif + #endif /* __XDG_MIME_ALIAS_H__ */ diff --git a/gio/xdgmime/xdgmimecache.c b/gio/xdgmime/xdgmimecache.c index 769b57836..234e4b467 100644 --- a/gio/xdgmime/xdgmimecache.c +++ b/gio/xdgmime/xdgmimecache.c @@ -19,10 +19,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <stdio.h> #include <stdlib.h> @@ -39,7 +43,7 @@ #ifdef HAVE_MMAP #include <sys/mman.h> #else -#warning Building xdgmime without MMAP support. Binary "mime.info" cache files will not be used. +#warning Building xdgmime without MMAP support. Binary "mime.cache" files will not be used. #endif #include <sys/stat.h> @@ -68,6 +72,10 @@ #define MAP_FAILED ((void *) -1) #endif +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + #define MAJOR_VERSION 1 #define MINOR_VERSION_MIN 1 #define MINOR_VERSION_MAX 2 @@ -117,9 +125,9 @@ _xdg_mime_cache_new_from_file (const char *file_name) int minor; /* Open the file and map it into memory */ - do + do { fd = open (file_name, O_RDONLY|_O_BINARY, 0); - while (fd == -1 && errno == EINTR); + } while (fd == -1 && errno == EINTR); if (fd < 0) return NULL; @@ -176,7 +184,7 @@ cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16); xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20); - int i, j; + xdg_uint32_t i, j; for (i = range_start; i < range_start + range_length; i++) { @@ -199,16 +207,9 @@ cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, } else { - for (j = 0; j < data_length; j++) - { - if (((unsigned char *)cache->buffer)[data_offset + j] != ((unsigned char *) data)[j + i]) - { - valid_matchlet = FALSE; - break; - } - } + valid_matchlet = memcmp(cache->buffer + data_offset, (unsigned char *)data + i, data_length) == 0; } - + if (valid_matchlet) return TRUE; } @@ -225,7 +226,7 @@ cache_magic_matchlet_compare (XdgMimeCache *cache, xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24); xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28); - int i; + xdg_uint32_t i; if (cache_magic_matchlet_compare_to_data (cache, offset, data, len)) { @@ -255,7 +256,7 @@ cache_magic_compare_to_data (XdgMimeCache *cache, xdg_uint32_t n_matchlets = GET_UINT32 (cache->buffer, offset + 8); xdg_uint32_t matchlet_offset = GET_UINT32 (cache->buffer, offset + 12); - int i; + xdg_uint32_t i; for (i = 0; i < n_matchlets; i++) { @@ -275,15 +276,13 @@ static const char * cache_magic_lookup_data (XdgMimeCache *cache, const void *data, size_t len, - int *prio, - const char *mime_types[], - int n_mime_types) + int *prio) { xdg_uint32_t list_offset; xdg_uint32_t n_entries; xdg_uint32_t offset; - int j, n; + xdg_uint32_t j; *prio = 0; @@ -299,21 +298,6 @@ cache_magic_lookup_data (XdgMimeCache *cache, data, len, prio); if (match) return match; - else - { - xdg_uint32_t mimetype_offset; - const char *non_match; - - mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * j + 4); - non_match = cache->buffer + mimetype_offset; - - for (n = 0; n < n_mime_types; n++) - { - if (mime_types[n] && - _xdg_mime_mime_type_equal (mime_types[n], non_match)) - mime_types[n] = NULL; - } - } } return NULL; @@ -428,12 +412,14 @@ cache_glob_lookup_literal (const char *file_name, static int cache_glob_lookup_fnmatch (const char *file_name, MimeWeight mime_types[], - int n_mime_types) + int n_mime_types, + int case_sensitive_check) { const char *mime_type; const char *ptr; - int i, j, n; + int i, n; + xdg_uint32_t j; n = 0; for (i = 0; _caches[i]; i++) @@ -454,16 +440,19 @@ cache_glob_lookup_fnmatch (const char *file_name, xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j); xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 4); int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 8); + int case_sensitive = weight & 0x100; weight = weight & 0xff; ptr = cache->buffer + offset; mime_type = cache->buffer + mimetype_offset; - - /* FIXME: Not UTF-8 safe */ - if (fnmatch (ptr, file_name, 0) == 0) + if (case_sensitive_check || !case_sensitive) { - mime_types[n].mime = mime_type; - mime_types[n].weight = weight; - n++; + /* FIXME: Not UTF-8 safe */ + if (fnmatch (ptr, file_name, 0) == 0) + { + mime_types[n].mime = mime_type; + mime_types[n].weight = weight; + n++; + } } } @@ -492,7 +481,8 @@ cache_glob_node_lookup_suffix (XdgMimeCache *cache, int weight; int case_sensitive; - int min, max, mid, n, i; + xdg_uint32_t i; + int min, max, mid, n; character = file_name[len - 1]; @@ -578,8 +568,8 @@ cache_glob_lookup_suffix (const char *file_name, n_entries = GET_UINT32 (cache->buffer, list_offset); offset = GET_UINT32 (cache->buffer, list_offset + 4); - n += cache_glob_node_lookup_suffix (cache, - n_entries, offset, + n += cache_glob_node_lookup_suffix (cache, + n_entries, offset, file_name, len, ignore_case, mime_types + n, @@ -680,14 +670,16 @@ cache_glob_lookup_file_name (const char *file_name, if (n < 2) n += cache_glob_lookup_suffix (file_name, len, TRUE, mimes + n, n_mimes - n); - free (lower_case); - /* Last, try fnmatch */ + if (n == 0) + n = cache_glob_lookup_fnmatch (lower_case, mimes, n_mimes, FALSE); if (n < 2) - n += cache_glob_lookup_fnmatch (file_name, mimes + n, n_mimes - n); + n += cache_glob_lookup_fnmatch (file_name, mimes + n, n_mimes - n, TRUE); n = filter_out_dupes (mimes, n); + free (lower_case); + qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight); if (n_mime_types < n) @@ -743,8 +735,7 @@ cache_get_mime_type_for_data (const void *data, if (cache->buffer == NULL) continue; - match = cache_magic_lookup_data (cache, data, len, &prio, - mime_types, n_mime_types); + match = cache_magic_lookup_data (cache, data, len, &prio); if (prio > priority) { priority = prio; @@ -754,14 +745,27 @@ cache_get_mime_type_for_data (const void *data, if (result_prio) *result_prio = priority; - + if (priority > 0) - return mime_type; + { + /* Pick glob-result R where mime_type inherits from R */ + for (n = 0; n < n_mime_types; n++) + { + if (mime_types[n] && _xdg_mime_cache_mime_type_subclass(mime_types[n], mime_type)) + return mime_types[n]; + } + if (n == 0) + { + /* No globs: return magic match */ + return mime_type; + } + } + /* Pick first glob result, as fallback */ for (n = 0; n < n_mime_types; n++) { if (mime_types[n]) - return mime_types[n]; + return mime_types[n]; } return NULL; @@ -775,8 +779,6 @@ _xdg_mime_cache_get_mime_type_for_data (const void *data, return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0); } -#ifdef NOT_USED_IN_GIO - const char * _xdg_mime_cache_get_mime_type_for_file (const char *file_name, struct stat *statbuf) @@ -844,7 +846,7 @@ _xdg_mime_cache_get_mime_type_for_file (const char *file_name, mime_types, n); if (!mime_type) - mime_type = _xdg_binary_or_text_fallback(data, bytes_read); + mime_type = _xdg_binary_or_text_fallback (data, bytes_read); free (data); fclose (file); @@ -863,8 +865,6 @@ _xdg_mime_cache_get_mime_type_from_file_name (const char *file_name) return XDG_MIME_TYPE_UNKNOWN; } -#endif - int _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, const char *mime_types[], @@ -905,7 +905,8 @@ _xdg_mime_cache_mime_type_subclass (const char *mime, { const char *umime, *ubase; - int i, j, min, max, med, cmp; + xdg_uint32_t j; + int i, min, max, med, cmp; umime = _xdg_mime_cache_unalias_mime_type (mime); ubase = _xdg_mime_cache_unalias_mime_type (base); @@ -995,7 +996,8 @@ _xdg_mime_cache_unalias_mime_type (const char *mime) char ** _xdg_mime_cache_list_mime_parents (const char *mime) { - int i, j, k, l, p; + int i, l, p; + xdg_uint32_t j, k; char *all_parents[128]; /* we'll stop at 128 */ char **result; @@ -1109,8 +1111,6 @@ _xdg_mime_cache_get_icon (const char *mime) return cache_lookup_icon (mime, 32); } -#ifdef NOT_USED_IN_GIO - static void dump_glob_node (XdgMimeCache *cache, xdg_uint32_t offset, @@ -1120,6 +1120,7 @@ dump_glob_node (XdgMimeCache *cache, xdg_uint32_t mime_offset; xdg_uint32_t n_children; xdg_uint32_t child_offset; + xdg_uint32_t k; int i; character = GET_UINT32 (cache->buffer, offset); @@ -1134,15 +1135,15 @@ dump_glob_node (XdgMimeCache *cache, printf ("\n"); if (child_offset) { - for (i = 0; i < n_children; i++) - dump_glob_node (cache, child_offset + 20 * i, depth + 1); + for (k = 0; k < n_children; k++) + dump_glob_node (cache, child_offset + 20 * k, depth + 1); } } void _xdg_mime_cache_glob_dump (void) { - int i, j; + xdg_uint32_t i, j; for (i = 0; _caches[i]; i++) { XdgMimeCache *cache = _caches[i]; @@ -1161,4 +1162,4 @@ _xdg_mime_cache_glob_dump (void) } } -#endif + diff --git a/gio/xdgmime/xdgmimecache.h b/gio/xdgmime/xdgmimecache.h index 2723c5396..df25b2a57 100644 --- a/gio/xdgmime/xdgmimecache.h +++ b/gio/xdgmime/xdgmimecache.h @@ -19,7 +19,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_CACHE_H__ @@ -56,16 +58,12 @@ void _xdg_mime_cache_unref (XdgMimeCache *cache); const char *_xdg_mime_cache_get_mime_type_for_data (const void *data, size_t len, int *result_prio); -#ifdef NOT_USED_IN_GIO const char *_xdg_mime_cache_get_mime_type_for_file (const char *file_name, struct stat *statbuf); -#endif int _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name, const char *mime_types[], int n_mime_types); -#ifdef NOT_USED_IN_GIO const char *_xdg_mime_cache_get_mime_type_from_file_name (const char *file_name); -#endif int _xdg_mime_cache_is_valid_mime_type (const char *mime_type); int _xdg_mime_cache_mime_type_equal (const char *mime_a, const char *mime_b); @@ -78,8 +76,6 @@ const char *_xdg_mime_cache_unalias_mime_type (const char *mime); int _xdg_mime_cache_get_max_buffer_extents (void); const char *_xdg_mime_cache_get_icon (const char *mime); const char *_xdg_mime_cache_get_generic_icon (const char *mime); -#ifdef NOT_USED_IN_GIO void _xdg_mime_cache_glob_dump (void); -#endif #endif /* __XDG_MIME_CACHE_H__ */ diff --git a/gio/xdgmime/xdgmimeglob.c b/gio/xdgmime/xdgmimeglob.c index c18762e9a..d68435c89 100644 --- a/gio/xdgmime/xdgmimeglob.c +++ b/gio/xdgmime/xdgmimeglob.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmimeglob.h" #include "xdgmimeint.h" @@ -86,7 +90,7 @@ _xdg_glob_list_new (void) return new_element; } -/* Frees glob_list and all of it's children */ +/* Frees glob_list and all of its children */ static void _xdg_glob_list_free (XdgGlobList *glob_list) { @@ -158,8 +162,6 @@ _xdg_glob_hash_node_new (void) return glob_hash_node; } -#ifdef NOT_USED_IN_GIO - static void _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, int depth) @@ -179,8 +181,6 @@ _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, _xdg_glob_hash_node_dump (glob_hash_node->next, depth); } -#endif - static XdgGlobHashNode * _xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node, xdg_unichar_t *text, @@ -603,8 +603,6 @@ _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, } } -#ifdef NOT_USED_IN_GIO - void _xdg_glob_hash_dump (XdgGlobHash *glob_hash) { @@ -641,7 +639,6 @@ _xdg_glob_hash_dump (XdgGlobHash *glob_hash) } } -#endif void _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, diff --git a/gio/xdgmime/xdgmimeglob.h b/gio/xdgmime/xdgmimeglob.h index 79ccdc292..8b1fa3ad4 100644 --- a/gio/xdgmime/xdgmimeglob.h +++ b/gio/xdgmime/xdgmimeglob.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_GLOB_H__ @@ -63,8 +65,6 @@ void _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, int weight, int case_sensitive); XdgGlobType _xdg_glob_determine_type (const char *glob); -#ifdef NOT_USED_IN_GIO void _xdg_glob_hash_dump (XdgGlobHash *glob_hash); -#endif #endif /* __XDG_MIME_GLOB_H__ */ diff --git a/gio/xdgmime/xdgmimeicon.c b/gio/xdgmime/xdgmimeicon.c index a2f4dd2a7..feb6c869c 100644 --- a/gio/xdgmime/xdgmimeicon.c +++ b/gio/xdgmime/xdgmimeicon.c @@ -19,10 +19,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmimeicon.h" #include "xdgmimeint.h" @@ -159,7 +163,6 @@ _xdg_mime_icon_read_from_file (XdgIconList *list, sizeof (XdgIcon), icon_entry_cmp); } -#ifdef NOT_USED_IN_GIO void _xdg_mime_icon_list_dump (XdgIconList *list) @@ -177,4 +180,4 @@ _xdg_mime_icon_list_dump (XdgIconList *list) } } -#endif + diff --git a/gio/xdgmime/xdgmimeicon.h b/gio/xdgmime/xdgmimeicon.h index 6141a8668..c416b3c5e 100644 --- a/gio/xdgmime/xdgmimeicon.h +++ b/gio/xdgmime/xdgmimeicon.h @@ -19,7 +19,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_ICON_H__ @@ -43,8 +45,6 @@ XdgIconList *_xdg_mime_icon_list_new (void); void _xdg_mime_icon_list_free (XdgIconList *list); const char *_xdg_mime_icon_list_lookup (XdgIconList *list, const char *mime); -#ifdef NOT_USED_IN_GIO void _xdg_mime_icon_list_dump (XdgIconList *list); -#endif #endif /* __XDG_MIME_ICON_H__ */ diff --git a/gio/xdgmime/xdgmimeint.c b/gio/xdgmime/xdgmimeint.c index 35c3635e2..5e4513c50 100644 --- a/gio/xdgmime/xdgmimeint.c +++ b/gio/xdgmime/xdgmimeint.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmimeint.h" #include <ctype.h> @@ -189,7 +193,7 @@ const char * _xdg_binary_or_text_fallback(const void *data, size_t len) { unsigned char *chardata; - int i; + size_t i; chardata = (unsigned char *) data; for (i = 0; i < 128 && i < len; ++i) diff --git a/gio/xdgmime/xdgmimeint.h b/gio/xdgmime/xdgmimeint.h index c9270139e..9a8256d17 100644 --- a/gio/xdgmime/xdgmimeint.h +++ b/gio/xdgmime/xdgmimeint.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_INT_H__ diff --git a/gio/xdgmime/xdgmimemagic.c b/gio/xdgmime/xdgmimemagic.c index ea986652d..dcee0fd59 100644 --- a/gio/xdgmime/xdgmimemagic.c +++ b/gio/xdgmime/xdgmimemagic.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <assert.h> #include "xdgmimemagic.h" @@ -318,7 +322,7 @@ _xdg_mime_magic_parse_magic_line (FILE *magic_file, int c; int end_of_file; int indent = 0; - int bytes_read; + size_t bytes_read; assert (magic_file != NULL); @@ -410,7 +414,7 @@ _xdg_mime_magic_parse_magic_line (FILE *magic_file, return XDG_MIME_MAGIC_ERROR; } bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file); - if (bytes_read != matchlet->value_length) + if (bytes_read != (size_t) matchlet->value_length) { _xdg_mime_magic_matchlet_free (matchlet); if (feof (magic_file)) @@ -430,7 +434,7 @@ _xdg_mime_magic_parse_magic_line (FILE *magic_file, return XDG_MIME_MAGIC_ERROR; } bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file); - if (bytes_read != matchlet->value_length) + if (bytes_read != (size_t) matchlet->value_length) { _xdg_mime_magic_matchlet_free (matchlet); if (feof (magic_file)) @@ -468,7 +472,7 @@ _xdg_mime_magic_parse_magic_line (FILE *magic_file, _xdg_mime_magic_matchlet_free (matchlet); return XDG_MIME_MAGIC_EOF; } - if (matchlet->range_length == -1) + if (matchlet->range_length == (unsigned int) -1) { _xdg_mime_magic_matchlet_free (matchlet); return XDG_MIME_MAGIC_ERROR; @@ -483,7 +487,7 @@ _xdg_mime_magic_parse_magic_line (FILE *magic_file, if (matchlet->word_size > 1) { #if LITTLE_ENDIAN - int i; + unsigned int i; #endif if (matchlet->value_length % matchlet->word_size != 0) { @@ -529,7 +533,7 @@ _xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet, const void *data, size_t len) { - int i, j; + unsigned int i, j; for (i = matchlet->offset; i < matchlet->offset + matchlet->range_length; i++) { int valid_matchlet = TRUE; diff --git a/gio/xdgmime/xdgmimemagic.h b/gio/xdgmime/xdgmimemagic.h index c990acee8..eb06a81f8 100644 --- a/gio/xdgmime/xdgmimemagic.h +++ b/gio/xdgmime/xdgmimemagic.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_MAGIC_H__ diff --git a/gio/xdgmime/xdgmimeparent.c b/gio/xdgmime/xdgmimeparent.c index b06b749d4..89b48fc16 100644 --- a/gio/xdgmime/xdgmimeparent.c +++ b/gio/xdgmime/xdgmimeparent.c @@ -20,10 +20,14 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ -#include "config.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include "xdgmimeparent.h" #include "xdgmimeint.h" @@ -196,7 +200,6 @@ _xdg_mime_parent_read_from_file (XdgParentList *list, sizeof (XdgMimeParents), &parent_entry_cmp); } -#ifdef NOT_USED_IN_GIO void _xdg_mime_parent_list_dump (XdgParentList *list) @@ -214,4 +217,4 @@ _xdg_mime_parent_list_dump (XdgParentList *list) } } -#endif + diff --git a/gio/xdgmime/xdgmimeparent.h b/gio/xdgmime/xdgmimeparent.h index e3cdad5bf..29f43bc1f 100644 --- a/gio/xdgmime/xdgmimeparent.h +++ b/gio/xdgmime/xdgmimeparent.h @@ -20,7 +20,9 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. */ #ifndef __XDG_MIME_PARENT_H__ @@ -44,8 +46,6 @@ XdgParentList *_xdg_mime_parent_list_new (void); void _xdg_mime_parent_list_free (XdgParentList *list); const char **_xdg_mime_parent_list_lookup (XdgParentList *list, const char *mime); -#ifdef NOT_USED_IN_GIO void _xdg_mime_parent_list_dump (XdgParentList *list); -#endif #endif /* __XDG_MIME_PARENT_H__ */ |