diff --git a/liberty.c b/liberty.c index 1ae9df4..a9f30aa 100644 --- a/liberty.c +++ b/liberty.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -2193,6 +2194,37 @@ get_xdg_config_dirs (struct str_vector *out) split_str_ignore_empty (xdg_config_dirs, ':', out); } +static char * +try_expand_tilde (const char *filename) +{ + size_t until_slash = strcspn (filename, "/"); + if (!until_slash) + { + struct str expanded; + str_init (&expanded); + str_append_env_path (&expanded, "HOME", false); + str_append (&expanded, filename); + return str_steal (&expanded); + } + + int buf_len = sysconf (_SC_GETPW_R_SIZE_MAX); + if (buf_len < 0) + buf_len = 1024; + struct passwd pwd, *success = NULL; + + char *user = xstrndup (filename, until_slash); + char *buf = xmalloc (buf_len); + while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE) + buf = xrealloc (buf, buf_len <<= 1); + free (user); + + char *result = NULL; + if (success) + result = xstrdup_printf ("%s%s", pwd.pw_dir, filename + until_slash); + free (buf); + return result; +} + static char * resolve_config_filename (const char *filename) { @@ -2200,6 +2232,16 @@ resolve_config_filename (const char *filename) if (*filename == '/') return xstrdup (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; + print_debug ("failed to expand the home directory in `%s'", filename); + } + struct str_vector paths; str_vector_init (&paths); get_xdg_config_dirs (&paths);