aboutsummaryrefslogtreecommitdiff
path: root/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils.c')
-rw-r--r--src/utils.c118
1 files changed, 118 insertions, 0 deletions
diff --git a/src/utils.c b/src/utils.c
index 3bba022..612eaaa 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -19,10 +19,14 @@
#include <glib.h>
#include <glib/gprintf.h>
#include <gio/gio.h>
+#include <glib/gstdio.h>
+
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
+#include <pwd.h>
+
#include "config.h"
#include "utils.h"
@@ -105,3 +109,117 @@ fatal (const gchar *format, ...)
exit (EXIT_FAILURE);
va_end (ap);
}
+
+// At times, GLib even with its sheer size is surprisingly useless,
+// and I need to port some code over from "liberty".
+
+static gchar **
+get_xdg_config_dirs (void)
+{
+ GPtrArray *paths = g_ptr_array_new ();
+ g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
+ for (const gchar *const *system = g_get_system_config_dirs ();
+ *system; system++)
+ g_ptr_array_add (paths, (gpointer) *system);
+ g_ptr_array_add (paths, NULL);
+ return (gchar **) g_ptr_array_free (paths, FALSE);
+}
+
+gchar *
+resolve_relative_filename_generic
+ (gchar **paths, const gchar *tail, const gchar *filename)
+{
+ for (; *paths; paths++)
+ {
+ // As per XDG spec, relative paths are ignored
+ if (**paths != '/')
+ continue;
+
+ gchar *file = g_build_filename (*paths, tail, filename, NULL);
+ GStatBuf st;
+ if (!g_stat (file, &st))
+ return file;
+ g_free (file);
+ }
+ return NULL;
+}
+
+gchar *
+resolve_relative_config_filename (const gchar *filename)
+{
+ gchar **paths = get_xdg_config_dirs ();
+ gchar *result = resolve_relative_filename_generic
+ (paths, PROJECT_NAME, filename);
+ g_strfreev (paths);
+ return result;
+}
+
+static gchar *
+try_expand_tilde (const gchar *filename)
+{
+ size_t until_slash = strcspn (filename, "/");
+ if (!until_slash)
+ return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
+
+ long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
+ if (buf_len < 0)
+ buf_len = 1024;
+ struct passwd pwd, *success = NULL;
+
+ gchar *user = g_strndup (filename, until_slash);
+ gchar *buf = g_malloc (buf_len);
+ while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
+ buf = g_realloc (buf, buf_len <<= 1);
+ g_free (user);
+
+ gchar *result = NULL;
+ if (success)
+ result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
+ g_free (buf);
+ return result;
+}
+
+gchar *
+resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
+{
+ // Absolute path is absolute
+ if (*filename == '/')
+ return g_strdup (filename);
+
+ // We don't want to use wordexp() for this as it may execute /bin/sh
+ if (*filename == '~')
+ {
+ // Paths to home directories ought to be absolute
+ char *expanded = try_expand_tilde (filename + 1);
+ if (expanded)
+ return expanded;
+ g_debug ("failed to expand the home directory in `%s'", filename);
+ }
+ return relative_cb (filename);
+}
+
+GKeyFile *
+load_project_config_file (GError **error)
+{
+ GKeyFile *key_file = g_key_file_new ();
+ gchar **paths = get_xdg_config_dirs ();
+ GError *e = NULL;
+
+ // XXX: if there are dashes in the final path component,
+ // the function tries to replace them with directory separators,
+ // which is completely undocumented
+ g_key_file_load_from_dirs (key_file,
+ PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
+ (const gchar **) paths, NULL, 0, &e);
+ g_strfreev (paths);
+ if (!e)
+ return key_file;
+
+ if (e->code == G_KEY_FILE_ERROR_NOT_FOUND)
+ g_error_free (e);
+ else
+ g_propagate_error (error, e);
+
+ g_key_file_free (key_file);
+ return NULL;
+}