wmem: allow wmem_destroy_list to ignore a NULL list.
[metze/wireshark/wip.git] / wsutil / filesystem.c
index 33c78655d56fc67e739733e6d4f3895364e66d4b..ffb7fed70b1c197f522623059f880cedfb7ff211 100644 (file)
@@ -5,30 +5,11 @@
  * By Gerald Combs <gerald@wireshark.org>
  * Copyright 1998 Gerald Combs
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include <config.h>
 
-/*
- * Required with GNU libc to get dladdr().
- * We define it here because <dlfcn.h> apparently gets included by
- * one of the headers we include below.
- */
-#define _GNU_SOURCE
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #endif
-#ifdef HAVE_DLADDR
+#ifdef HAVE_DLGET
 #include <dlfcn.h>
 #endif
 #include <pwd.h>
 #endif /* _WIN32 */
 
 #include "filesystem.h"
-#include <wsutil/report_err.h>
+#include <wsutil/report_message.h>
 #include <wsutil/privileges.h>
 #include <wsutil/file_util.h>
 #include <wsutil/utf8_entities.h>
 
 #define PROFILES_DIR    "profiles"
 #define PLUGINS_DIR_NAME    "plugins"
+#define PROFILES_INFO_NAME  "profile_files.txt"
+
+#define ENV_CONFIG_PATH_VAR  "WIRESHARK_CONFIG_DIR"
 
 char *persconffile_dir = NULL;
+char *datafile_dir = NULL;
 char *persdatafile_dir = NULL;
 char *persconfprofile = NULL;
 
@@ -284,15 +269,31 @@ static gboolean running_in_build_directory_flag = FALSE;
  * passed to the program, so it shouldn't be fooled by an argv[0]
  * that doesn't match the executable path.
  *
- * Sadly, not all UN*Xes necessarily have dladdr(), and those that
- * do don't necessarily have dladdr(main) return information about
- * the executable image, and those that do aren't necessarily running
- * on a platform wherein the executable image can get its own path
- * from the kernel (either by a call or by it being handed to it along
- * with argv[] and the environment), and those that can don't
- * necessarily use that to supply the path you get from dladdr(main),
- * so we try this first and, if that fails, use dladdr(main) if
- * available.
+ * We don't use dladdr() because:
+ *
+ *   not all UN*Xes necessarily have dladdr();
+ *
+ *   those that do have it don't necessarily have dladdr(main)
+ *   return information about the executable image;
+ *
+ *   those that do have a dladdr() where dladdr(main) returns
+ *   information about the executable image don't necessarily
+ *   have a mechanism by which the executable image can get
+ *   its own path from the kernel (either by a call or by it
+ *   being handed to it along with argv[] and the environment),
+ *   so they just fall back on getting it from argv[0], which we
+ *   already have code to do;
+ *
+ *   those that do have such a mechanism don't necessarily use
+ *   it in dladdr(), and, instead, just fall back on getting it
+ *   from argv[0];
+ *
+ * so the only places where it's worth bothering to use dladdr()
+ * are platforms where dladdr(main) return information about the
+ * executable image by getting it from the kernel rather than
+ * by looking at argv[0], and where we can't get at that information
+ * ourselves, and we haven't seen any indication that there are any
+ * such platforms.
  *
  * In particular, some dynamic linkers supply a dladdr() such that
  * dladdr(main) just returns something derived from argv[0], so
@@ -300,21 +301,11 @@ static gboolean running_in_build_directory_flag = FALSE;
  * another mechanism that can get you a more reliable version of
  * the executable path.
  *
- * However, at least in newer versions of DragonFly BSD, the dynamic
- * linker *does* get it from the aux vector passed to the program
- * by the kernel,  readlink /proc/curproc/file - which came first?
- *
- * On OpenBSD, dladdr(main) returns a value derived from argv[0],
- * and there doesn't appear to be any way to get the executable path
- * from the kernel, so we're out of luck there.
- *
- * So, on platforms where some versions have a version of dladdr()
- * that gives an argv[0]-based path and that also have a mechanism
- * to get a more reliable version of the path, we try that.  On
- * other platforms, we return NULL.  If our caller gets back a NULL
- * from us, it falls back on dladdr(main) if dladdr() is available,
- * and if that fails or is unavailable, it falls back on processing
- * argv[0] itself.
+ * So, on platforms where we know of a mechanism to get that path
+ * (where getting that path doesn't involve argv[0], which is not
+ * guaranteed to reflect the path to the binary), this routine
+ * attempsts to use that platform's mechanism.  On other platforms,
+ * it just returns NULL.
  *
  * This is not guaranteed to return an absolute path; if it doesn't,
  * our caller must prepend the current directory if it's a path.
@@ -427,8 +418,9 @@ get_executable_path(void)
         return NULL;
     executable_path[r] = '\0';
     return executable_path;
-#elif (defined(sun) || defined(__sun)) && defined(HAVE_GETEXECNAME)
+#elif defined(HAVE_GETEXECNAME)
     /*
+     * Solaris, with getexecname().
      * It appears that getexecname() dates back to at least Solaris 8,
      * but /proc/{pid}/path is first documented in the Solaris 10 documentation,
      * so we use getexecname() if available, rather than /proc/self/path/a.out
@@ -436,6 +428,19 @@ get_executable_path(void)
      * executable image file).
      */
     return getexecname();
+#elif defined(HAVE_DLGET)
+    /*
+     * HP-UX 11, with dlget(); use dlget() and dlgetname().
+     * See
+     *
+     *  https://web.archive.org/web/20081025174755/http://h21007.www2.hp.com/portal/site/dspp/menuitem.863c3e4cbcdc3f3515b49c108973a801?ciid=88086d6e1de021106d6e1de02110275d6e10RCRD#two
+     */
+    struct load_module_desc desc;
+
+    if (dlget(-2, &desc, sizeof(desc)) != NULL)
+        return dlgetname(&desc, sizeof(desc), NULL, NULL, NULL);
+    else
+        return NULL;
 #else
     /* Fill in your favorite UN*X's code here, if there is something */
     return NULL;
@@ -453,10 +458,6 @@ init_progfile_dir(const char *arg0
 #ifdef _WIN32
     _U_
 #endif
-, int (*function_addr)(int, char **)
-#if defined(_WIN32) || !defined(HAVE_DLADDR)
-    _U_
-#endif
 )
 {
 #ifdef _WIN32
@@ -519,9 +520,6 @@ init_progfile_dir(const char *arg0
             msg, error);
     }
 #else
-#ifdef HAVE_DLADDR
-    Dl_info info;
-#endif
     const char *execname;
     char *prog_pathname;
     char *curdir;
@@ -547,24 +545,6 @@ init_progfile_dir(const char *arg0
         running_in_build_directory_flag = TRUE;
 
     execname = get_executable_path();
-#ifdef HAVE_DLADDR
-    if (function_addr != NULL && execname == NULL) {
-        /*
-         * Try to use dladdr() to find the pathname of the executable.
-         * dladdr() is not guaranteed to give you anything better than
-         * argv[0] (i.e., it might not contain a / at all, much less
-         * being an absolute path), and doesn't appear to do so on
-         * Linux, but on other platforms it could give you an absolute
-         * path and obviate the need for us to determine the absolute
-         * path.
-         */
-DIAG_OFF(pedantic)
-        if (dladdr((void *)function_addr, &info)) {
-DIAG_ON(pedantic)
-            execname = info.dli_fname;
-        }
-    }
-#endif
     if (execname == NULL) {
         /*
          * OK, guess based on argv[0].
@@ -575,7 +555,7 @@ DIAG_ON(pedantic)
     /*
      * Try to figure out the directory in which the currently running
      * program resides, given something purporting to be the executable
-     * name (from dladdr() or from the argv[0] it was started with.
+     * name (from an OS mechanism or from the argv[0] it was started with).
      * That might be the absolute path of the program, or a path relative
      * to the current directory of the process that started it, or
      * just a name for the program if it was started from the command
@@ -606,7 +586,7 @@ DIAG_ON(pedantic)
         if (getcwd(curdir, path_max) == NULL) {
             /*
              * It failed - give up, and just stick
-             * with DATAFILE_DIR.
+             * with DATA_DIR.
              */
             g_free(curdir);
             return g_strdup_printf("getcwd failed: %s\n",
@@ -649,12 +629,6 @@ DIAG_ON(pedantic)
                  * That's not it.  If there are more
                  * path components to test, try them.
                  */
-                if (*path_end == '\0') {
-                    /*
-                     * There's nothing more to try.
-                     */
-                    break;
-                }
                 if (*path_end == ':')
                     path_end++;
                 path_start = path_end;
@@ -692,33 +666,11 @@ DIAG_ON(pedantic)
         *dir_end = '\0';
 
         /*
-         * Is there a "/.libs" at the end?
+         * Is there a "/run" at the end?
          */
         dir_end = strrchr(prog_pathname, '/');
         if (dir_end != NULL) {
-            if (strcmp(dir_end, "/.libs") == 0) {
-                /*
-                 * Yup, it's ".libs".
-                 * Strip that off; it's an
-                 * artifact of libtool.
-                 */
-                *dir_end = '\0';
-
-                /*
-                 * This presumably means we're run from
-                 * the libtool wrapper, which probably
-                 * means we're being run from the build
-                 * directory.  If we weren't started
-                 * with special privileges, set
-                 * running_in_build_directory_flag.
-                 *
-                 * XXX - should we check whether what
-                 * follows ".libs/" begins with "lt-"?
-                 */
-                if (!started_with_special_privs())
-                    running_in_build_directory_flag = TRUE;
-            }
-            else if (!started_with_special_privs()) {
+            if (!started_with_special_privs()) {
                 /*
                  * Check for the CMake output directory. As people may name
                  * their directories "run" (really?), also check for the
@@ -734,7 +686,7 @@ DIAG_ON(pedantic)
                     g_free(cmake_file);
                 }
 #ifdef __APPLE__
-                if (!running_in_build_directory_flag) {
+                {
                     /*
                      * Scan up the path looking for a component
                      * named "Contents".  If we find it, we assume
@@ -814,10 +766,12 @@ get_progfile_dir(void)
  * On Windows, we use the directory in which the executable for this
  * process resides.
  *
- * On UN*X, we use the DATAFILE_DIR value supplied by the configure
- * script, unless we think we're being run from the build directory,
- * in which case we use the directory in which the executable for this
- * process resides.
+ * On macOS (when executed from an app bundle), use a directory within
+ * that app bundle.
+ *
+ * Otherwise, if the program was executed from the build directory, use the
+ * directory in which the executable for this process resides. In all other
+ * cases, use the DATA_DIR value that was set at compile time.
  *
  * XXX - if we ever make libwireshark a real library, used by multiple
  * applications (more than just TShark and versions of Wireshark with
@@ -843,8 +797,6 @@ get_progfile_dir(void)
 const char *
 get_datafile_dir(void)
 {
-    static const char *datafile_dir = NULL;
-
     if (datafile_dir != NULL)
         return datafile_dir;
 
@@ -864,17 +816,40 @@ get_datafile_dir(void)
         /*
          * Yes, we do; use that.
          */
-        datafile_dir = progfile_dir;
+        datafile_dir = g_strdup(progfile_dir);
     } else {
         /*
          * No, we don't.
          * Fall back on the default installation directory.
          */
-        datafile_dir = "C:\\Program Files\\Wireshark\\";
+        datafile_dir = g_strdup("C:\\Program Files\\Wireshark\\");
     }
 #else
 
-    if (running_in_build_directory_flag) {
+    if (g_getenv("WIRESHARK_DATA_DIR") && !started_with_special_privs()) {
+        /*
+         * The user specified a different directory for data files
+         * and we aren't running with special privileges.
+         * XXX - We might be able to dispense with the priv check
+         */
+        datafile_dir = g_strdup(g_getenv("WIRESHARK_DATA_DIR"));
+    }
+#ifdef __APPLE__
+    /*
+     * If we're running from an app bundle and weren't started
+     * with special privileges, use the Contents/Resources/share/wireshark
+     * subdirectory of the app bundle.
+     *
+     * (appbundle_dir is not set to a non-null value if we're
+     * started with special privileges, so we need only check
+     * it; we don't need to call started_with_special_privs().)
+     */
+    else if (appbundle_dir != NULL) {
+        datafile_dir = g_strdup_printf("%s/Contents/Resources/share/wireshark",
+                                       appbundle_dir);
+    }
+#endif
+    else if (running_in_build_directory_flag && progfile_dir != NULL) {
         /*
          * We're (probably) being run from the build directory and
          * weren't started with special privileges.
@@ -883,62 +858,23 @@ get_datafile_dir(void)
          * if we're started with special privileges, so we need
          * only check it; we don't need to call started_with_special_privs().)
          *
-         * Use the top-level source directory as the datafile directory
-         * because most of our data files (radius/, COPYING) are there.
-         */
-#ifdef TOP_SRCDIR
-        /*
-         * When TOP_SRCDIR is defined, assume autotools where files are not
-         * copied to the build directory. This fallback location is relied on by
-         * wslua_get_actual_filename().
+         * Data files (console.lua, radius/, etc.) are copied to the build
+         * directory during the build which also contains executables. A special
+         * exception is macOS (when built with an app bundle).
          */
-        datafile_dir = TOP_SRCDIR;
-#else
-        /*
-         * Otherwise assume CMake. Here, data files (console.lua, radius/, etc.)
-         * are copied to the build directory during the build.
-         */
-        datafile_dir = BUILD_TIME_DATAFILE_DIR;
-#endif
-        return datafile_dir;
+        datafile_dir = g_strdup(progfile_dir);
     } else {
-        if (g_getenv("WIRESHARK_DATA_DIR") && !started_with_special_privs()) {
-            /*
-             * The user specified a different directory for data files
-             * and we aren't running with special privileges.
-             * XXX - We might be able to dispense with the priv check
-             */
-            datafile_dir = g_strdup(g_getenv("WIRESHARK_DATA_DIR"));
-        }
-#ifdef __APPLE__
-        /*
-         * If we're running from an app bundle and weren't started
-         * with special privileges, use the Contents/Resources/share/wireshark
-         * subdirectory of the app bundle.
-         *
-         * (appbundle_dir is not set to a non-null value if we're
-         * started with special privileges, so we need only check
-         * it; we don't need to call started_with_special_privs().)
-         */
-        else if (appbundle_dir != NULL) {
-            datafile_dir = g_strdup_printf("%s/Contents/Resources/share/wireshark",
-                                           appbundle_dir);
-        }
-#endif
-        else {
-            datafile_dir = DATAFILE_DIR;
-        }
+        datafile_dir = g_strdup(DATA_DIR);
     }
 
 #endif
     return datafile_dir;
 }
 
-#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
 /*
  * Find the directory where the plugins are stored.
  *
- * On Windows, we use the plugin/{VERSION} subdirectory of the datafile
+ * On Windows, we use the plugin\{VERSION} subdirectory of the datafile
  * directory, where {VERSION} is the version number of this version of
  * Wireshark.
  *
@@ -954,14 +890,18 @@ get_datafile_dir(void)
  *    otherwise, if we're running from an app bundle in macOS, we
  *    use the Contents/PlugIns/wireshark subdirectory of the app bundle;
  *
- *    otherwise, we use the PLUGIN_INSTALL_DIR value supplied by the
+ *    otherwise, we use the PLUGIN_DIR value supplied by the
  *    configure script.
  */
 static char *plugin_dir = NULL;
+static char *plugin_dir_with_version = NULL;
+static char *plugin_pers_dir = NULL;
+static char *plugin_pers_dir_with_version = NULL;
 
 static void
 init_plugin_dir(void)
 {
+#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
 #ifdef _WIN32
     /*
      * On Windows, the data file directory is the installation
@@ -971,7 +911,7 @@ init_plugin_dir(void)
      * on Windows, the data file directory is the directory
      * in which the Wireshark binary resides.
      */
-    plugin_dir = g_build_filename(get_datafile_dir(), "plugins", VERSION, (gchar *)NULL);
+    plugin_dir = g_build_filename(get_datafile_dir(), "plugins", (gchar *)NULL);
 
     /*
      * Make sure that pathname refers to a directory.
@@ -1026,28 +966,65 @@ init_plugin_dir(void)
         }
 #endif
         else {
-            plugin_dir = g_strdup(PLUGIN_INSTALL_DIR);
+            plugin_dir = g_strdup(PLUGIN_DIR);
         }
     }
 #endif
+#endif /* defined(HAVE_PLUGINS) || defined(HAVE_LUA) */
+}
+
+static void
+init_plugin_pers_dir(void)
+{
+#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
+#ifdef _WIN32
+    plugin_pers_dir = get_persconffile_path(PLUGINS_DIR_NAME, FALSE);
+#else
+    plugin_pers_dir = g_build_filename(g_get_home_dir(), ".local/lib/wireshark/" PLUGINS_DIR_NAME, (gchar *)NULL);
+#endif
+#endif /* defined(HAVE_PLUGINS) || defined(HAVE_LUA) */
 }
-#endif /* HAVE_PLUGINS || HAVE_LUA */
 
 /*
  * Get the directory in which the plugins are stored.
  */
 const char *
-get_plugin_dir(void)
+get_plugins_dir(void)
 {
-#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
-    if (!plugin_dir) init_plugin_dir();
+    if (!plugin_dir)
+        init_plugin_dir();
     return plugin_dir;
-#else
-    return NULL;
-#endif
 }
 
-#if defined(HAVE_EXTCAP)
+const char *
+get_plugins_dir_with_version(void)
+{
+    if (!plugin_dir)
+        init_plugin_dir();
+    if (plugin_dir && !plugin_dir_with_version)
+        plugin_dir_with_version = g_build_filename(plugin_dir, VERSION_RELEASE, (gchar *)NULL);
+    return plugin_dir_with_version;
+}
+
+/* Get the personal plugin dir */
+const char *
+get_plugins_pers_dir(void)
+{
+    if (!plugin_pers_dir)
+        init_plugin_pers_dir();
+    return plugin_pers_dir;
+}
+
+const char *
+get_plugins_pers_dir_with_version(void)
+{
+    if (!plugin_pers_dir)
+        init_plugin_pers_dir();
+    if (plugin_pers_dir && !plugin_pers_dir_with_version)
+        plugin_pers_dir_with_version = g_build_filename(plugin_pers_dir, VERSION_RELEASE, (gchar *)NULL);
+    return plugin_pers_dir_with_version;
+}
+
 /*
  * Find the directory where the extcap hooks are stored.
  *
@@ -1126,22 +1103,17 @@ static void init_extcap_dir(void) {
     }
 #endif
 }
-#endif /* HAVE_EXTCAP */
 
 /*
  * Get the directory in which the extcap hooks are stored.
  *
- * XXX - A fix instead of HAVE_EXTCAP must be found
  */
 const char *
-get_extcap_dir(void) {
-#if defined(HAVE_EXTCAP)
+get_extcap_dir(void)
+{
     if (!extcap_dir)
         init_extcap_dir();
     return extcap_dir;
-#else
-    return NULL;
-#endif
 }
 
 /*
@@ -1259,28 +1231,28 @@ profile_store_persconffiles(gboolean store)
 static const char *
 get_persconffile_dir_no_profile(void)
 {
-#ifdef _WIN32
     const char *env;
-#else
-    char *xdg_path, *path;
-    struct passwd *pwd;
-    const char *homedir;
-#endif
 
     /* Return the cached value, if available */
     if (persconffile_dir != NULL)
         return persconffile_dir;
 
-#ifdef _WIN32
     /*
      * See if the user has selected an alternate environment.
      */
-    env = g_getenv("WIRESHARK_APPDATA");
+    env = g_getenv(ENV_CONFIG_PATH_VAR);
+#ifdef _WIN32
+    if (env == NULL) {
+        /* for backward compatibility */
+        env = g_getenv("WIRESHARK_APPDATA");
+    }
+#endif
     if (env != NULL) {
         persconffile_dir = g_strdup(env);
         return persconffile_dir;
     }
 
+#ifdef _WIN32
     /*
      * Use %APPDATA% or %USERPROFILE%, so that configuration
      * files are stored in the user profile, rather than in
@@ -1313,6 +1285,10 @@ get_persconffile_dir_no_profile(void)
     persconffile_dir = g_build_filename("C:", "Wireshark", NULL);
     return persconffile_dir;
 #else
+    char *xdg_path, *path;
+    struct passwd *pwd;
+    const char *homedir;
+
     /*
      * Check if XDG_CONFIG_HOME/wireshark exists and is a directory.
      */
@@ -1378,6 +1354,45 @@ get_profiles_dir(void)
                     G_DIR_SEPARATOR_S, PROFILES_DIR);
 }
 
+int
+create_profiles_dir(char **pf_dir_path_return)
+{
+    char *pf_dir_path;
+    ws_statb64 s_buf;
+
+    /*
+     * Create the "Default" personal configuration files directory, if necessary.
+     */
+    if (create_persconffile_profile (NULL, pf_dir_path_return) == -1) {
+        return -1;
+    }
+
+    /*
+     * Check if profiles directory exists.
+     * If not then create it.
+     */
+    pf_dir_path = get_profiles_dir ();
+    if (ws_stat64(pf_dir_path, &s_buf) != 0) {
+        if (errno != ENOENT) {
+            /* Some other problem; give up now. */
+            *pf_dir_path_return = pf_dir_path;
+            return -1;
+        }
+
+        /*
+         * It doesn't exist; try to create it.
+         */
+        int ret = ws_mkdir(pf_dir_path, 0755);
+        if (ret == -1) {
+            *pf_dir_path_return = pf_dir_path;
+            return ret;
+        }
+    }
+    g_free(pf_dir_path);
+
+    return 0;
+}
+
 char *
 get_global_profiles_dir(void)
 {
@@ -1403,29 +1418,50 @@ get_persconffile_dir(const gchar *profilename)
     return persconffile_profile_dir;
 }
 
-gboolean
-profile_exists(const gchar *profilename, gboolean global)
+char *
+get_profile_dir(const char *profilename, gboolean is_global)
 {
-    gchar *path = NULL, *global_path;
-    if (global) {
-        global_path = get_global_profiles_dir();
-        path = g_strdup_printf ("%s%s%s", global_path,
-                           G_DIR_SEPARATOR_S, profilename);
-        g_free(global_path);
-        if (test_for_directory (path) == EISDIR) {
-            g_free (path);
-            return TRUE;
+    gchar *profile_dir;
+
+    if (is_global) {
+        if (profilename && strlen(profilename) > 0 &&
+            strcmp(profilename, DEFAULT_PROFILE) != 0)
+        {
+            gchar *global_path = get_global_profiles_dir();
+            profile_dir = g_build_filename(global_path, profilename, NULL);
+            g_free(global_path);
+        } else {
+            profile_dir = g_strdup(get_datafile_dir());
         }
     } else {
-        path = get_persconffile_dir (profilename);
-        if (test_for_directory (path) == EISDIR) {
-            g_free (path);
-            return TRUE;
-        }
+        /*
+         * If we didn't supply a profile name, i.e. if profilename is
+         * null, get_persconffile_dir() returns the default profile.
+         */
+        profile_dir = get_persconffile_dir(profilename);
     }
 
-    g_free (path);
-    return FALSE;
+    return profile_dir;
+}
+
+gboolean
+profile_exists(const gchar *profilename, gboolean global)
+{
+    gchar *path = NULL;
+    gboolean exists;
+
+    /*
+     * If we're looking up a global profile, we must have a
+     * profile name.
+     */
+    if (global && !profilename)
+        return FALSE;
+
+    path = get_profile_dir(profilename, global);
+    exists = (test_for_directory(path) == EISDIR) ? TRUE : FALSE;
+
+    g_free(path);
+    return exists;
 }
 
 static int
@@ -1491,6 +1527,7 @@ reset_default_profile(char **pf_dir_path_return)
         g_free(del_file);
         file = g_list_next(file);
     }
+    g_list_free(files);
 
     g_free(profile_dir);
     return 0;
@@ -1556,34 +1593,11 @@ create_persconffile_profile(const char *profilename, char **pf_dir_path_return)
 
     if (profilename) {
         /*
-         * Create the "Default" personal configuration files directory, if necessary.
+         * Create the personal profiles directory, if necessary.
          */
-        if (create_persconffile_profile (NULL, pf_dir_path_return) == -1) {
+        if (create_profiles_dir(pf_dir_path_return) == -1) {
             return -1;
         }
-
-        /*
-         * Check if profiles directory exists.
-         * If not then create it.
-         */
-        pf_dir_path = get_profiles_dir ();
-        if (ws_stat64(pf_dir_path, &s_buf) != 0) {
-            if (errno != ENOENT) {
-                /* Some other problem; give up now. */
-                *pf_dir_path_return = pf_dir_path;
-                return -1;
-            }
-
-            /*
-             * It doesn't exist; try to create it.
-             */
-            ret = ws_mkdir(pf_dir_path, 0755);
-            if (ret == -1) {
-                *pf_dir_path_return = pf_dir_path;
-                return ret;
-            }
-        }
-        g_free(pf_dir_path);
     }
 
     pf_dir_path = get_persconffile_dir(profilename);
@@ -1650,6 +1664,9 @@ create_persconffile_profile(const char *profilename, char **pf_dir_path_return)
     }
     if (ret == -1)
         *pf_dir_path_return = pf_dir_path;
+    else
+        g_free(pf_dir_path);
+
     return ret;
 }
 
@@ -1665,20 +1682,10 @@ copy_persconffile_profile(const char *toname, const char *fromname, gboolean fro
 {
     gchar *from_dir;
     gchar *to_dir = get_persconffile_dir(toname);
-    gchar *filename, *from_file, *to_file, *global_path;
+    gchar *filename, *from_file, *to_file;
     GList *files, *file;
 
-    if (from_global) {
-        if (strcmp(fromname, DEFAULT_PROFILE) == 0) {
-            from_dir = get_global_profiles_dir();
-        } else {
-            global_path = get_global_profiles_dir();
-            from_dir = g_strdup_printf ("%s%s%s", global_path, G_DIR_SEPARATOR_S, fromname);
-            g_free(global_path);
-        }
-    } else {
-        from_dir = get_persconffile_dir(fromname);
-    }
+    from_dir = get_profile_dir(fromname, from_global);
 
     files = g_hash_table_get_keys(profile_files);
     file = g_list_first(files);
@@ -1828,14 +1835,11 @@ get_persconffile_path(const char *filename, gboolean from_profile)
     }
 
     if (from_profile) {
-      dir = get_persconffile_dir(persconfprofile);
-      path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
-                 dir, filename);
+        dir = get_persconffile_dir(persconfprofile);
     } else {
-      dir = get_persconffile_dir(NULL);
-      path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s",
-                 dir, filename);
+        dir = get_persconffile_dir(NULL);
     }
+    path = g_build_filename(dir, filename, NULL);
 
     g_free(dir);
     return path;
@@ -1859,20 +1863,12 @@ get_datafile_path(const char *filename)
          * directory (not in the source/data directory).
          * (Oh the things we do to keep the source directory pristine...)
          */
-        return g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", get_progfile_dir(), filename);
+        return g_build_filename(get_progfile_dir(), filename, (char *)NULL);
     } else {
-        return g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", get_datafile_dir(), filename);
+        return g_build_filename(get_datafile_dir(), filename, (char *)NULL);
     }
 }
 
-/* Get the personal plugin dir */
-/* Return value is malloced so the caller should g_free() it. */
-char *
-get_plugins_pers_dir(void)
-{
-    return get_persconffile_path(PLUGINS_DIR_NAME, FALSE);
-}
-
 /*
  * Return an error message for UNIX-style errno indications on open or
  * create operations.
@@ -1955,7 +1951,7 @@ file_open_error_message(int err, gboolean for_writing)
          * Either you have a fixed swap partition or a fixed swap file,
          * and it needs to be made bigger.
          *
-         * This is UN*X, but it's not OS X, so we assume the user is
+         * This is UN*X, but it's not macOS, so we assume the user is
          * *somewhat* nerdy.
          */
 #define ENOMEM_REASON "your system is out of swap space"
@@ -2020,28 +2016,41 @@ file_exists(const char *fname)
         return FALSE;
     }
 
-#if defined(_MSC_VER) && _MSC_VER < 1900
-
-    /*
-     * This is a bit tricky on win32. The st_ino field is documented as:
-     * "The inode, and therefore st_ino, has no meaning in the FAT, ..."
-     * but it *is* set to zero if stat() returns without an error,
-     * so this is working, but maybe not quite the way expected. ULFL
-     */
-    file_stat.st_ino = 1;   /* this will make things work if an error occurred */
-    ws_stat64(fname, &file_stat);
-    if (file_stat.st_ino == 0) {
-        return TRUE;
-    } else {
-        return FALSE;
-    }
-#else
     if (ws_stat64(fname, &file_stat) != 0 && errno == ENOENT) {
         return FALSE;
     } else {
         return TRUE;
     }
-#endif
+}
+
+gboolean config_file_exists_with_entries(const char *fname, char comment_char)
+{
+    gboolean start_of_line = TRUE;
+    gboolean has_entries = FALSE;
+    FILE *file;
+    int c;
+
+    if (!fname) {
+        return FALSE;
+    }
+
+    if ((file = ws_fopen(fname, "r")) == NULL) {
+        return FALSE;
+    }
+
+    do {
+        c = ws_getc_unlocked(file);
+        if (start_of_line && c != comment_char && !g_ascii_isspace(c) && g_ascii_isprint(c)) {
+            has_entries = TRUE;
+            break;
+        }
+        if (c == '\n' || !g_ascii_isspace(c)) {
+            start_of_line = (c == '\n');
+        }
+    } while (c != EOF);
+
+    fclose(file);
+    return has_entries;
 }
 
 /*
@@ -2200,18 +2209,26 @@ free_progdirs(void)
 {
     g_free(persconffile_dir);
     persconffile_dir = NULL;
+    g_free(datafile_dir);
+    datafile_dir = NULL;
     g_free(persdatafile_dir);
     persdatafile_dir = NULL;
     g_free(persconfprofile);
     persconfprofile = NULL;
     g_free(progfile_dir);
     progfile_dir = NULL;
+#if defined(HAVE_PLUGINS) || defined(HAVE_LUA)
     g_free(plugin_dir);
     plugin_dir = NULL;
-#ifdef HAVE_EXTCAP
+    g_free(plugin_dir_with_version);
+    plugin_dir_with_version = NULL;
+    g_free(plugin_pers_dir);
+    plugin_pers_dir = NULL;
+    g_free(plugin_pers_dir_with_version);
+    plugin_pers_dir_with_version = NULL;
+#endif
     g_free(extcap_dir);
     extcap_dir = NULL;
-#endif
 }
 
 /*