gutils: Add internal API to override XDG directories

Add a new internal function, g_set_user_dirs(), which will safely
override the values returned by g_get_user_data_dir() and friends, and
the value returned by g_get_home_dir().

This is intended to be used by unit tests, and will be hooked up to them
in a following commit.

This can be called as many times as needed by the current process. It’s
thread-safe. It does not modify the environment, so none of the changes
are propagated to any subsequently spawned subprocesses.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

https://gitlab.gnome.org/GNOME/glib/issues/538
This commit is contained in:
Philip Withnall 2018-11-30 17:47:18 +00:00
parent b87dfb4960
commit 91defdb34e
7 changed files with 165 additions and 0 deletions

View File

@ -64,6 +64,7 @@ IGNORE_HFILES = \
glib-init.h \ glib-init.h \
gconstructor.h \ gconstructor.h \
valgrind.h \ valgrind.h \
gutilsprivate.h \
gvalgrind.h \ gvalgrind.h \
$(NULL) $(NULL)

View File

@ -36,6 +36,7 @@ if get_option('gtk_doc')
'glib-init.h', 'glib-init.h',
'gconstructor.h', 'gconstructor.h',
'valgrind.h', 'valgrind.h',
'gutilsprivate.h',
'gvalgrind.h', 'gvalgrind.h',
] ]

View File

@ -544,6 +544,26 @@
fun:g_object_new_valist 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() caches a one-time allocation
{ {
g_get_system_data_dirs g_get_system_data_dirs

View File

@ -191,6 +191,7 @@ libglib_2_0_la_SOURCES = \
gunicodeprivate.h \ gunicodeprivate.h \
gurifuncs.c \ gurifuncs.c \
gutils.c \ gutils.c \
gutilsprivate.h \
guuid.c \ guuid.c \
gvalgrind.h \ gvalgrind.h \
gvariant.h \ gvariant.h \

View File

@ -29,6 +29,7 @@
#include "config.h" #include "config.h"
#include "gutils.h" #include "gutils.h"
#include "gutilsprivate.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
@ -1177,6 +1178,113 @@ g_set_application_name (const gchar *application_name)
g_warning ("g_set_application_name() called multiple times"); g_warning ("g_set_application_name() called multiple times");
} }
/* Set @global_str to a copy of @new_value if its 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 wont 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 * static gchar *
g_build_user_data_dir (void) g_build_user_data_dir (void)
{ {

33
glib/gutilsprivate.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Author: Philip Withnall <withnall@endlessm.com>
*/
#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__ */

View File

@ -199,6 +199,7 @@ glib_sources = files(
'gunidecomp.c', 'gunidecomp.c',
'gurifuncs.c', 'gurifuncs.c',
'gutils.c', 'gutils.c',
'gutilsprivate.h',
'guuid.c', 'guuid.c',
'gvariant.c', 'gvariant.c',
'gvariant-core.c', 'gvariant-core.c',