diff --git a/docs/reference/glib/Makefile.am b/docs/reference/glib/Makefile.am index 4d4c8d80c..a3e716867 100644 --- a/docs/reference/glib/Makefile.am +++ b/docs/reference/glib/Makefile.am @@ -64,6 +64,7 @@ IGNORE_HFILES = \ glib-init.h \ gconstructor.h \ valgrind.h \ + gutilsprivate.h \ gvalgrind.h \ $(NULL) diff --git a/docs/reference/glib/meson.build b/docs/reference/glib/meson.build index eca891a20..2fb82481f 100644 --- a/docs/reference/glib/meson.build +++ b/docs/reference/glib/meson.build @@ -36,6 +36,7 @@ if get_option('gtk_doc') 'glib-init.h', 'gconstructor.h', 'valgrind.h', + 'gutilsprivate.h', 'gvalgrind.h', ] diff --git a/glib.supp b/glib.supp index c8f3682ef..3ff1fdbc0 100644 --- a/glib.supp +++ b/glib.supp @@ -544,6 +544,26 @@ fun:g_object_new_valist } +# g_set_user_dirs() deliberately leaks the previous cached g_get_user_*() values. +{ + g_set_user_dirs_str + Memcheck:Leak + fun:malloc + ... + fun:set_str_if_different + fun:g_set_user_dirs +} + +# g_set_user_dirs() deliberately leaks the previous cached g_get_user_*() values. +{ + g_set_user_dirs_strv + Memcheck:Leak + fun:malloc + ... + fun:set_strv_if_different + fun:g_set_user_dirs +} + # g_get_system_data_dirs() caches a one-time allocation { g_get_system_data_dirs diff --git a/glib/Makefile.am b/glib/Makefile.am index e072b8829..e433d48f3 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -191,6 +191,7 @@ libglib_2_0_la_SOURCES = \ gunicodeprivate.h \ gurifuncs.c \ gutils.c \ + gutilsprivate.h \ guuid.c \ gvalgrind.h \ gvariant.h \ diff --git a/glib/gutils.c b/glib/gutils.c index 51ea3426b..1e267602a 100644 --- a/glib/gutils.c +++ b/glib/gutils.c @@ -29,6 +29,7 @@ #include "config.h" #include "gutils.h" +#include "gutilsprivate.h" #include #include @@ -1177,6 +1178,113 @@ g_set_application_name (const gchar *application_name) g_warning ("g_set_application_name() called multiple times"); } +/* Set @global_str to a copy of @new_value if it’s currently unset or has a + * different value. If its current value matches @new_value, do nothing. If + * replaced, we have to leak the old value as client code could still have + * pointers to it. */ +static void +set_str_if_different (gchar **global_str, + const gchar *type, + const gchar *new_value) +{ + if (*global_str == NULL || + !g_str_equal (new_value, *global_str)) + { + g_debug ("g_set_user_dirs: Setting %s to %s", type, new_value); + + /* We have to leak the old value, as user code could be retaining pointers + * to it. */ + *global_str = g_strdup (new_value); + } +} + +static void +set_strv_if_different (gchar ***global_strv, + const gchar *type, + const gchar * const *new_value) +{ + if (*global_strv == NULL || + !g_strv_equal (new_value, (const gchar * const *) *global_strv)) + { + gchar *new_value_str = g_strjoinv (":", (gchar **) new_value); + g_debug ("g_set_user_dirs: Setting %s to %s", type, new_value_str); + g_free (new_value_str); + + /* We have to leak the old value, as user code could be retaining pointers + * to it. */ + *global_strv = g_strdupv ((gchar **) new_value); + } +} + +/* + * g_set_user_dirs: + * @first_dir_type: Type of the first directory to set + * @...: Value to set the first directory to, followed by additional type/value + * pairs, followed by %NULL + * + * Set one or more ‘user’ directories to custom values. This is intended to be + * used by test code (particularly with the %G_TEST_OPTION_ISOLATE_DIRS option) + * to override the values returned by the following functions, so that test + * code can be run without touching an installed system and user data: + * + * - g_get_home_dir() — use type `HOME`, pass a string + * - g_get_user_cache_dir() — use type `XDG_CACHE_HOME`, pass a string + * - g_get_system_config_dirs() — use type `XDG_CONFIG_DIRS`, pass a + * %NULL-terminated string array + * - g_get_user_config_dir() — use type `XDG_CONFIG_HOME`, pass a string + * - g_get_system_data_dirs() — use type `XDG_DATA_DIRS`, pass a + * %NULL-terminated string array + * - g_get_user_data_dir() — use type `XDG_DATA_HOME`, pass a string + * - g_get_user_runtime_dir() — use type `XDG_RUNTIME_DIR`, pass a string + * + * The list must be terminated with a %NULL type. All of the values must be + * non-%NULL — passing %NULL as a value won’t reset a directory. If a reference + * to a directory from the calling environment needs to be kept, copy it before + * the first call to g_set_user_dirs(). g_set_user_dirs() can be called multiple + * times. + * + * Since: 2.60 + */ +/*< private > */ +void +g_set_user_dirs (const gchar *first_dir_type, + ...) +{ + va_list args; + const gchar *dir_type; + + G_LOCK (g_utils_global); + + va_start (args, first_dir_type); + + for (dir_type = first_dir_type; dir_type != NULL; dir_type = va_arg (args, const gchar *)) + { + gconstpointer dir_value = va_arg (args, gconstpointer); + g_assert (dir_value != NULL); + + if (g_str_equal (dir_type, "HOME")) + set_str_if_different (&g_home_dir, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_CACHE_HOME")) + set_str_if_different (&g_user_cache_dir, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_CONFIG_DIRS")) + set_strv_if_different (&g_system_config_dirs, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_CONFIG_HOME")) + set_str_if_different (&g_user_config_dir, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_DATA_DIRS")) + set_strv_if_different (&g_system_data_dirs, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_DATA_HOME")) + set_str_if_different (&g_user_data_dir, dir_type, dir_value); + else if (g_str_equal (dir_type, "XDG_RUNTIME_DIR")) + set_str_if_different (&g_user_runtime_dir, dir_type, dir_value); + else + g_assert_not_reached (); + } + + va_end (args); + + G_UNLOCK (g_utils_global); +} + static gchar * g_build_user_data_dir (void) { diff --git a/glib/gutilsprivate.h b/glib/gutilsprivate.h new file mode 100644 index 000000000..5a0686086 --- /dev/null +++ b/glib/gutilsprivate.h @@ -0,0 +1,33 @@ +/* + * Copyright © 2018 Endless Mobile, 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 . + * + * Author: Philip Withnall + */ + +#ifndef __G_UTILS_PRIVATE_H__ +#define __G_UTILS_PRIVATE_H__ + +#include "gtypes.h" + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_60 +void g_set_user_dirs (const gchar *first_dir_type, + ...) G_GNUC_NULL_TERMINATED; + +G_END_DECLS + +#endif /* __G_UTILS_PRIVATE_H__ */ diff --git a/glib/meson.build b/glib/meson.build index 3275ee6e1..1fe4a6e73 100644 --- a/glib/meson.build +++ b/glib/meson.build @@ -199,6 +199,7 @@ glib_sources = files( 'gunidecomp.c', 'gurifuncs.c', 'gutils.c', + 'gutilsprivate.h', 'guuid.c', 'gvariant.c', 'gvariant-core.c',