/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * pikaenv.c
 * Copyright (C) 1999 Tor Lillqvist <tml@iki.fi>
 *
 * 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 3 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
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <errno.h>
#include <string.h>
#include <sys/types.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef PLATFORM_OSX
#include <AppKit/AppKit.h>
#endif

#include <gio/gio.h>
#include <glib/gstdio.h>

#include "pikabasetypes.h"

#define __PIKA_ENV_C__
#include "pikaenv.h"
#include "pikaversion.h"
#include "pikareloc.h"

#ifdef G_OS_WIN32
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <io.h>
#ifndef S_IWUSR
# define S_IWUSR _S_IWRITE
#endif
#ifndef S_IWGRP
#define S_IWGRP (_S_IWRITE>>3)
#define S_IWOTH (_S_IWRITE>>6)
#endif
#ifndef S_ISDIR
# define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
# define S_ISDIR(mode)  __S_ISTYPE((mode), _S_IFDIR)
#endif
#define uid_t gint
#define gid_t gint
#define geteuid() 0
#define getegid() 0

#include <shlobj.h>

/* Constant available since Shell32.dll 4.72 */
#ifndef CSIDL_APPDATA
#define CSIDL_APPDATA 0x001a
#endif

#endif


/**
 * SECTION: pikaenv
 * @title: pikaenv
 * @short_description: Functions to access the PIKA environment.
 *
 * A set of functions to find the locations of PIKA's data directories
 * and configuration files.
 **/


static gchar * pika_env_get_dir   (const gchar *pika_env_name,
                                   const gchar *compile_time_dir,
                                   const gchar *relative_subdir);


const guint pika_major_version = PIKA_MAJOR_VERSION;
const guint pika_minor_version = PIKA_MINOR_VERSION;
const guint pika_micro_version = PIKA_MICRO_VERSION;


/**
 * pika_env_init:
 * @plug_in: must be %TRUE if this function is called from a plug-in
 *
 * You don't need to care about this function. It is being called for
 * you automatically (by means of the MAIN() macro that every plug-in
 * runs). Calling it again will cause a fatal error.
 *
 * Since: 2.4
 */
void
pika_env_init (gboolean plug_in)
{
  static gboolean  pika_env_initialized = FALSE;
  const gchar     *data_home = g_get_user_data_dir ();

  if (pika_env_initialized)
    g_error ("pika_env_init() must only be called once!");

  pika_env_initialized = TRUE;

#ifndef G_OS_WIN32
  if (plug_in)
    {
      _pika_reloc_init_lib (NULL);
    }
  else if (_pika_reloc_init (NULL))
    {
      /* Set $LD_LIBRARY_PATH to ensure that plugins can be loaded. */

      const gchar *ldpath = g_getenv ("LD_LIBRARY_PATH");
      gchar       *libdir = g_build_filename (pika_installation_directory (),
                                              "lib",
                                              NULL);

      if (ldpath && *ldpath)
        {
          gchar *tmp = g_strconcat (libdir, ":", ldpath, NULL);

          g_setenv ("LD_LIBRARY_PATH", tmp, TRUE);

          g_free (tmp);
        }
      else
        {
          g_setenv ("LD_LIBRARY_PATH", libdir, TRUE);
        }

      g_free (libdir);
    }
#endif

  /* The user data directory (XDG_DATA_HOME on Unix) is used to store
   * various data, like crash logs (win32) or recently used file history
   * (by GTK+). Yet it may be absent, in particular on non-Linux
   * platforms. Make sure it exists.
   */
  if (! g_file_test (data_home, G_FILE_TEST_IS_DIR))
    {
      if (g_mkdir_with_parents (data_home, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
        {
          g_warning ("Failed to create the data directory '%s': %s",
                     data_home, g_strerror (errno));
        }
    }
}

#ifdef G_OS_WIN32

static char *
get_known_folder (REFKNOWNFOLDERID id)
{
  wchar_t *path_utf16 = NULL;
  char    *path       = NULL;

  if (SUCCEEDED (SHGetKnownFolderPath (id, KF_FLAG_DEFAULT, NULL, &path_utf16)))
    path = g_utf16_to_utf8 (path_utf16, -1, NULL, NULL, NULL);

  if (path_utf16)
    CoTaskMemFree (path_utf16);

  return path;
}

extern IMAGE_DOS_HEADER __ImageBase;

static HMODULE
this_module (void)
{
  return (HMODULE) &__ImageBase;
}

#endif

/**
 * pika_directory:
 *
 * Returns the user-specific PIKA settings directory. If the
 * environment variable PIKA3_DIRECTORY exists, it is used. If it is
 * an absolute path, it is used as is.  If it is a relative path, it
 * is taken to be a subdirectory of the home directory. If it is a
 * relative path, and no home directory can be determined, it is taken
 * to be a subdirectory of pika_data_directory().
 *
 * The usual case is that no PIKA3_DIRECTORY environment variable
 * exists, and then we use the PIKADIR subdirectory of the local
 * configuration directory:
 *
 * - UNIX: $XDG_CONFIG_HOME (defaults to $HOME/.config/)
 *
 * - Windows: CSIDL_APPDATA
 *
 * - OSX (UNIX exception): the Application Support Directory.
 *
 * If neither the configuration nor home directory exist,
 * g_get_user_config_dir() will return {tmp}/{user_name}/.config/ where
 * the temporary directory {tmp} and the {user_name} are determined
 * according to platform rules.
 *
 * In any case, we always return some non-empty string, whether it
 * corresponds to an existing directory or not.
 *
 * In config files such as pikarc, the string ${pika_dir} expands to
 * this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8 (on Windows it is always
 * UTF-8.)
 *
 * Returns: The user-specific PIKA settings directory.
 **/
const gchar *
pika_directory (void)
{
  static gchar *pika_dir          = NULL;
  static gchar *last_env_pika_dir = NULL;

  const gchar  *env_pika_dir;

  env_pika_dir = g_getenv ("PIKA3_DIRECTORY");

  if (pika_dir)
    {
      gboolean pika3_directory_changed = FALSE;

      /* We have constructed the pika_dir already. We can return
       * pika_dir unless some parameter pika_dir depends on has
       * changed. For now we just check for changes to PIKA3_DIRECTORY
       */
      pika3_directory_changed =
        (env_pika_dir == NULL &&
         last_env_pika_dir != NULL) ||
        (env_pika_dir != NULL &&
         last_env_pika_dir == NULL) ||
        (env_pika_dir != NULL &&
         last_env_pika_dir != NULL &&
         strcmp (env_pika_dir, last_env_pika_dir) != 0);

      if (! pika3_directory_changed)
        {
          return pika_dir;
        }
      else
        {
          /* Free the old pika_dir and go on to update it */
          g_free (pika_dir);
          pika_dir = NULL;
        }
    }

  /* Remember the PIKA3_DIRECTORY to next invocation so we can check
   * if it changes
   */
  g_free (last_env_pika_dir);
  last_env_pika_dir = g_strdup (env_pika_dir);

  if (env_pika_dir)
    {
      if (g_path_is_absolute (env_pika_dir))
        {
          pika_dir = g_strdup (env_pika_dir);
        }
      else
        {
          const gchar *home_dir = g_get_home_dir ();

          if (home_dir)
            pika_dir = g_build_filename (home_dir, env_pika_dir, NULL);
          else
            pika_dir = g_build_filename (pika_data_directory (), env_pika_dir, NULL);
        }
    }
  else if (g_path_is_absolute (PIKADIR))
    {
      pika_dir = g_strdup (PIKADIR);
    }
  else
    {
#ifdef PLATFORM_OSX

      NSAutoreleasePool *pool;
      NSArray           *path;
      NSString          *library_dir;

      pool = [[NSAutoreleasePool alloc] init];

      path = NSSearchPathForDirectoriesInDomains (NSApplicationSupportDirectory,
                                                  NSUserDomainMask, YES);
      library_dir = [path objectAtIndex:0];

      pika_dir = g_build_filename ([library_dir UTF8String],
                                   PIKADIR, PIKA_USER_VERSION, NULL);

      [pool drain];

#elif defined G_OS_WIN32

      char *conf_dir = get_known_folder (&FOLDERID_RoamingAppData);

      pika_dir = g_build_filename (conf_dir,
                                   PIKADIR, PIKA_USER_VERSION, NULL);
      g_free(conf_dir);

#else /* UNIX */

      /* g_get_user_config_dir () always returns a path as a non-null
       * and non-empty string
       */
      pika_dir = g_build_filename (g_get_user_config_dir (),
                                   PIKADIR, PIKA_USER_VERSION, NULL);

#endif /* PLATFORM_OSX */
    }

  return pika_dir;
}

/**
 * pika_installation_directory:
 *
 * Returns the top installation directory of PIKA. On Unix the
 * compile-time defined installation prefix is used. On Windows, the
 * installation directory as deduced from the executable's full
 * filename is used. On OSX we ask [NSBundle mainBundle] for the
 * resource path to check if PIKA is part of a relocatable bundle.
 *
 * In config files such as pikarc, the string ${pika_installation_dir}
 * expands to this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.)
 *
 * Since: 2.8
 *
 * Returns: The toplevel installation directory of PIKA.
 **/
const gchar *
pika_installation_directory (void)
{
  static gchar *toplevel = NULL;

  if (toplevel)
    return toplevel;

#ifdef G_OS_WIN32

  toplevel = g_win32_get_package_installation_directory_of_module (this_module ());
  if (! toplevel)
    g_error ("g_win32_get_package_installation_directory_of_module() failed");

#elif PLATFORM_OSX

  {
    NSAutoreleasePool *pool;
    NSString          *resource_path;
    gchar             *basename;
    gchar             *basepath;
    gchar             *dirname;

    pool = [[NSAutoreleasePool alloc] init];

    resource_path = [[NSBundle mainBundle] resourcePath];

    basename = g_path_get_basename ([resource_path UTF8String]);
    basepath = g_path_get_dirname ([resource_path UTF8String]);
    dirname  = g_path_get_basename (basepath);

    if (! strcmp (basename, ".libs"))
      {
        /*  we are running from the source dir, do normal unix things  */

        toplevel = _pika_reloc_find_prefix (PREFIX);
      }
    else if (! strcmp (basename, "bin"))
      {
        /*  we are running the main app, but not from a bundle, the resource
         *  path is the directory which contains the executable
         */

        toplevel = g_strdup (basepath);
      }
    else if (! strcmp (dirname, "plug-ins") ||
             ! strcmp (dirname, "extensions"))
      {
        /*  same for plug-ins and extensions in subdirectory, go three
         *  levels up from prefix/lib/pika/x.y
         */

        gchar *tmp  = g_path_get_dirname (basepath);
        gchar *tmp2 = g_path_get_dirname (tmp);
        gchar *tmp3 = g_path_get_dirname (tmp2);

        toplevel = g_path_get_dirname (tmp3);

        g_free (tmp);
        g_free (tmp2);
        g_free (tmp3);
      }
    else if (strstr (basepath, "/Cellar/"))
      {
        /*  we are running from a Python.framework bundle built in homebrew
         *  during the build phase
         */

        gchar *fulldir = g_strdup (basepath);
        gchar *lastdir = g_path_get_basename (fulldir);
        gchar *tmp_fulldir;

        while (strcmp (lastdir, "Cellar"))
          {
            tmp_fulldir = g_path_get_dirname (fulldir);

            g_free (lastdir);
            g_free (fulldir);

            fulldir = tmp_fulldir;
            lastdir = g_path_get_basename (fulldir);
          }
        toplevel = g_path_get_dirname (fulldir);

        g_free (fulldir);
        g_free (lastdir);
      }
    else
      {
        /*  if none of the above match, we assume that we are really in a bundle  */

        toplevel = g_strdup ([resource_path UTF8String]);
      }

    g_free (basename);
    g_free (basepath);
    g_free (dirname);

    [pool drain];
  }

#else

  toplevel = _pika_reloc_find_prefix (PREFIX);

#endif

  return toplevel;
}

/**
 * pika_data_directory:
 *
 * Returns the default top directory for PIKA data. If the environment
 * variable PIKA3_DATADIR exists, that is used.  It should be an
 * absolute pathname.  Otherwise, on Unix the compile-time defined
 * directory is used. On Windows, the installation directory as
 * deduced from the executable's full filename is used.
 *
 * Note that the actual directories used for PIKA data files can be
 * overridden by the user in the preferences dialog.
 *
 * In config files such as pikarc, the string ${pika_data_dir} expands
 * to this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.)
 *
 * Returns: The top directory for PIKA data.
 **/
const gchar *
pika_data_directory (void)
{
  static gchar *pika_data_dir = NULL;

  if (! pika_data_dir)
    {
      gchar *tmp = g_build_filename ("share",
                                     PIKA_PACKAGE,
                                     PIKA_DATA_VERSION,
                                     NULL);

      pika_data_dir = pika_env_get_dir ("PIKA3_DATADIR", PIKADATADIR, tmp);
      g_free (tmp);
    }

  return pika_data_dir;
}

/**
 * pika_locale_directory:
 *
 * Returns the top directory for PIKA locale files. If the environment
 * variable PIKA3_LOCALEDIR exists, that is used.  It should be an
 * absolute pathname.  Otherwise, on Unix the compile-time defined
 * directory is used. On Windows, the installation directory as deduced
 * from the executable's full filename is used.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string encoding depends on the system where PIKA
 * is running: on UNIX it's in the encoding used for filenames by
 * the C library (which isn't necessarily UTF-8); on Windows it's UTF-8.
 *
 * On UNIX the returned string can be passed directly to the bindtextdomain()
 * function from libintl; on Windows the returned string can be converted to
 * UTF-16 and passed to the wbindtextdomain() function from libintl.
 *
 * Returns: (type filename): The top directory for PIKA locale files.
 */
const gchar *
pika_locale_directory (void)
{
  static gchar *pika_locale_dir = NULL;

  if (! pika_locale_dir)
    {
      gchar *tmp = g_build_filename ("share",
                                     "locale",
                                     NULL);

      pika_locale_dir = pika_env_get_dir ("PIKA3_LOCALEDIR", LOCALEDIR, tmp);
      g_free (tmp);
    }

  return pika_locale_dir;
}

/**
 * pika_sysconf_directory:
 *
 * Returns the top directory for PIKA config files. If the environment
 * variable PIKA3_SYSCONFDIR exists, that is used.  It should be an
 * absolute pathname.  Otherwise, on Unix the compile-time defined
 * directory is used. On Windows, the installation directory as deduced
 * from the executable's full filename is used.
 *
 * In config files such as pikarc, the string ${pika_sysconf_dir}
 * expands to this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.).
 *
 * Returns: The top directory for PIKA config files.
 **/
const gchar *
pika_sysconf_directory (void)
{
  static gchar *pika_sysconf_dir = NULL;

  if (! pika_sysconf_dir)
    {
      gchar *tmp = g_build_filename ("etc",
                                     PIKA_PACKAGE,
                                     PIKA_SYSCONF_VERSION,
                                     NULL);

      pika_sysconf_dir = pika_env_get_dir ("PIKA3_SYSCONFDIR", PIKASYSCONFDIR, tmp);
      g_free (tmp);
    }

  return pika_sysconf_dir;
}

/**
 * pika_plug_in_directory:
 *
 * Returns the default top directory for PIKA plug-ins and modules. If
 * the environment variable PIKA3_PLUGINDIR exists, that is used.  It
 * should be an absolute pathname. Otherwise, on Unix the compile-time
 * defined directory is used. On Windows, the installation directory
 * as deduced from the executable's full filename is used.
 *
 * Note that the actual directories used for PIKA plug-ins and modules
 * can be overridden by the user in the preferences dialog.
 *
 * In config files such as pikarc, the string ${pika_plug_in_dir}
 * expands to this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.)
 *
 * Returns: The top directory for PIKA plug_ins and modules.
 **/
const gchar *
pika_plug_in_directory (void)
{
  static gchar *pika_plug_in_dir = NULL;

  if (! pika_plug_in_dir)
    {
      gchar *tmp = g_build_filename ("lib",
                                     PIKA_PACKAGE,
                                     PIKA_PLUGIN_VERSION,
                                     NULL);

      pika_plug_in_dir = pika_env_get_dir ("PIKA3_PLUGINDIR", PLUGINDIR, tmp);
      g_free (tmp);
    }

  return pika_plug_in_dir;
}

/**
 * pika_cache_directory:
 *
 * Returns the default top directory for PIKA cached files. If the
 * environment variable PIKA3_CACHEDIR exists, that is used.  It
 * should be an absolute pathname.  Otherwise, a subdirectory of the
 * directory returned by g_get_user_cache_dir() is used.
 *
 * Note that the actual directories used for PIKA caches files can
 * be overridden by the user in the preferences dialog.
 *
 * In config files such as pikarc, the string ${pika_cache_dir}
 * expands to this directory.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.).
 *
 * Since: 2.10.10
 *
 * Returns: The default top directory for PIKA cached files.
 **/
const gchar *
pika_cache_directory (void)
{
  static gchar *pika_cache_dir = NULL;

  if (! pika_cache_dir)
    {
      gchar *tmp = g_build_filename (g_get_user_cache_dir (),
                                     PIKA_PACKAGE,
                                     PIKA_USER_VERSION,
                                     NULL);

      pika_cache_dir = pika_env_get_dir ("PIKA3_CACHEDIR", NULL, tmp);
      g_free (tmp);
    }

  return pika_cache_dir;
}

/**
 * pika_temp_directory:
 *
 * Returns the default top directory for PIKA temporary files. If the
 * environment variable PIKA3_TEMPDIR exists, that is used.  It
 * should be an absolute pathname.  Otherwise, a subdirectory of the
 * directory returned by g_get_tmp_dir() is used.
 *
 * In config files such as pikarc, the string ${pika_temp_dir} expands
 * to this directory.
 *
 * Note that the actual directories used for PIKA temporary files can
 * be overridden by the user in the preferences dialog.
 *
 * The returned string is owned by PIKA and must not be modified or
 * freed. The returned string is in the encoding used for filenames by
 * GLib, which isn't necessarily UTF-8. (On Windows it always is
 * UTF-8.).
 *
 * Since: 2.10.10
 *
 * Returns: The default top directory for PIKA temporary files.
 **/
const gchar *
pika_temp_directory (void)
{
  static gchar *pika_temp_dir = NULL;

  if (! pika_temp_dir)
    {
      gchar *tmp = g_build_filename (g_get_tmp_dir (),
                                     PIKA_PACKAGE,
                                     PIKA_USER_VERSION,
                                     NULL);

      pika_temp_dir = pika_env_get_dir ("PIKA3_TEMPDIR", NULL, tmp);
      g_free (tmp);
    }

  return pika_temp_dir;
}

static GFile *
pika_child_file (const gchar *parent,
                 const gchar *element,
                 va_list      args)
{
  GFile *file = g_file_new_for_path (parent);

  while (element)
    {
      GFile *child = g_file_get_child (file, element);

      g_object_unref (file);
      file = child;

      element = va_arg (args, const gchar *);
    }

  return file;
}

/**
 * pika_directory_file: (skip)
 * @first_element: the first element of a path to a file in the
 *                 user's PIKA directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the user's PIKA directory, or the PIKA
 * directory itself if @first_element is %NULL.
 *
 * See also: pika_directory().
 *
 * Since: 2.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_directory_file (const gchar *first_element,
                     ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_installation_directory_file: (skip)
 * @first_element: the first element of a path to a file in the
 *                 top installation directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the installation directory, or the installation
 * directory itself if @first_element is %NULL.
 *
 * See also: pika_installation_directory().
 *
 * Since: 2.10.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_installation_directory_file (const gchar *first_element,
                                  ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_installation_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_data_directory_file: (skip)
 * @first_element: the first element of a path to a file in the
 *                 data directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the data directory, or the data directory
 * itself if @first_element is %NULL.
 *
 * See also: pika_data_directory().
 *
 * Since: 2.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_data_directory_file (const gchar *first_element,
                          ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_data_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_locale_directory_file: (skip)
 * @first_element: the first element of a path to a file in the
 *                 locale directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the locale directory, or the locale directory
 * itself if @first_element is %NULL.
 *
 * See also: pika_locale_directory().
 *
 * Since: 2.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_locale_directory_file (const gchar *first_element,
                            ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_locale_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_sysconf_directory_file:
 * @first_element: the first element of a path to a file in the
 *                 sysconf directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the sysconf directory, or the sysconf directory
 * itself if @first_element is %NULL.
 *
 * See also: pika_sysconf_directory().
 *
 * Since: 2.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_sysconf_directory_file (const gchar *first_element,
                             ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_sysconf_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_plug_in_directory_file:
 * @first_element: the first element of a path to a file in the
 *                 plug-in directory, or %NULL.
 * @...: a %NULL terminated list of the remaining elements of the path
 *       to the file.
 *
 * Returns a #GFile in the plug-in directory, or the plug-in directory
 * itself if @first_element is %NULL.
 *
 * See also: pika_plug_in_directory().
 *
 * Since: 2.10
 *
 * Returns: (transfer full):
 *          a new @GFile for the path, Free with g_object_unref().
 **/
GFile *
pika_plug_in_directory_file (const gchar *first_element,
                             ...)
{
  GFile   *file;
  va_list  args;

  va_start (args, first_element);
  file = pika_child_file (pika_plug_in_directory (), first_element, args);
  va_end (args);

  return file;
}

/**
 * pika_path_runtime_fix:
 * @path: A pointer to a string (allocated with g_malloc) that is
 *        (or could be) a pathname.
 *
 * On Windows, this function checks if the string pointed to by @path
 * starts with the compile-time prefix, and in that case, replaces the
 * prefix with the run-time one.  @path should be a pointer to a
 * dynamically allocated (with g_malloc, g_strconcat, etc) string. If
 * the replacement takes place, the original string is deallocated,
 * and *@path is replaced with a pointer to a new string with the
 * run-time prefix spliced in.
 *
 * On Linux, it does the same thing, but only if BinReloc support is enabled.
 * On other Unices, it does nothing because those platforms don't have a
 * way to find out where our binary is.
 */
static void
pika_path_runtime_fix (gchar **path)
{
#if defined (G_OS_WIN32) && defined (PREFIX)
  gchar *p;

  /* Yes, I do mean forward slashes below */
  if (strncmp (*path, PREFIX "/", strlen (PREFIX "/")) == 0)
    {
      /* This is a compile-time entry. Replace the path with the
       * real one on this machine.
       */
      p = *path;
      *path = g_strconcat (pika_installation_directory (),
                           "\\",
                           *path + strlen (PREFIX "/"),
                           NULL);
      g_free (p);
    }
  /* Replace forward slashes with backslashes, just for
   * completeness */
  p = *path;
  while ((p = strchr (p, '/')) != NULL)
    {
      *p = '\\';
      p++;
    }
#elif defined (G_OS_WIN32)
  /* without defineing PREFIX do something useful too */
  gchar *p = *path;
  if (!g_path_is_absolute (p))
    {
      *path = g_build_filename (pika_installation_directory (), *path, NULL);
      g_free (p);
    }
#else
  gchar *p;

  if (strncmp (*path, PREFIX G_DIR_SEPARATOR_S,
               strlen (PREFIX G_DIR_SEPARATOR_S)) == 0)
    {
      /* This is a compile-time entry. Replace the path with the
       * real one on this machine.
       */
      p = *path;
      *path = g_build_filename (pika_installation_directory (),
                                *path + strlen (PREFIX G_DIR_SEPARATOR_S),
                                NULL);
      g_free (p);
    }
#endif
}

/**
 * pika_path_parse:
 * @path:         A list of directories separated by #G_SEARCHPATH_SEPARATOR.
 * @max_paths:    The maximum number of directories to return.
 * @check:        %TRUE if you want the directories to be checked.
 * @check_failed: (element-type filename) (out callee-allocates):
                  Returns a #GList of path elements for which the check failed.
 *
 * Returns: (element-type filename) (transfer full):
            A #GList of all directories in @path.
 **/
GList *
pika_path_parse (const gchar  *path,
                 gint          max_paths,
                 gboolean      check,
                 GList       **check_failed)
{
  gchar    **patharray;
  GList     *list      = NULL;
  GList     *fail_list = NULL;
  gint       i;
  gboolean   exists    = TRUE;

  if (!path || !*path || max_paths < 1 || max_paths > 256)
    return NULL;

  patharray = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, max_paths);

  for (i = 0; i < max_paths; i++)
    {
      GString *dir;

      if (! patharray[i])
        break;

#ifndef G_OS_WIN32
      if (*patharray[i] == '~')
        {
          dir = g_string_new (g_get_home_dir ());
          g_string_append (dir, patharray[i] + 1);
        }
      else
#endif
        {
          pika_path_runtime_fix (&patharray[i]);
          dir = g_string_new (patharray[i]);
        }

      if (check)
        exists = g_file_test (dir->str, G_FILE_TEST_IS_DIR);

      if (exists)
        {
          GList *dup;

          /*  check for duplicate entries, see bug #784502  */
          for (dup = list; dup; dup = g_list_next (dup))
            {
              if (! strcmp (dir->str, dup->data))
                break;
            }

          /*  only add to the list if it's not a duplicate  */
          if (! dup)
            list = g_list_prepend (list, g_strdup (dir->str));
        }
      else if (check_failed)
        {
          fail_list = g_list_prepend (fail_list, g_strdup (dir->str));
        }

      g_string_free (dir, TRUE);
    }

  g_strfreev (patharray);

  list = g_list_reverse (list);

  if (check && check_failed)
    {
      fail_list = g_list_reverse (fail_list);
      *check_failed = fail_list;
    }

  return list;
}

/**
 * pika_path_to_str:
 * @path: (element-type filename):
 *        A list of directories as returned by pika_path_parse().
 *
 * Returns: (type filename) (transfer full):
 *          A searchpath string separated by #G_SEARCHPATH_SEPARATOR.
 **/
gchar *
pika_path_to_str (GList *path)
{
  GString *str    = NULL;
  GList   *list;
  gchar   *retval = NULL;

  for (list = path; list; list = g_list_next (list))
    {
      gchar *dir = list->data;

      if (str)
        {
          g_string_append_c (str, G_SEARCHPATH_SEPARATOR);
          g_string_append (str, dir);
        }
      else
        {
          str = g_string_new (dir);
        }
    }

  if (str)
    retval = g_string_free (str, FALSE);

  return retval;
}

/**
 * pika_path_free:
 * @path: (element-type filename):
 *        A list of directories as returned by pika_path_parse().
 *
 * This function frees the memory allocated for the list and the strings
 * it contains.
 **/
void
pika_path_free (GList *path)
{
  g_list_free_full (path, (GDestroyNotify) g_free);
}

/**
 * pika_path_get_user_writable_dir:
 * @path: (element-type filename):
 *        A list of directories as returned by pika_path_parse().
 *
 * Note that you have to g_free() the returned string.
 *
 * Returns: The first directory in @path where the user has write permission.
 **/
gchar *
pika_path_get_user_writable_dir (GList *path)
{
  GList    *list;
  uid_t     euid;
  gid_t     egid;
  GStatBuf  filestat;
  gint      err;

  g_return_val_if_fail (path != NULL, NULL);

  euid = geteuid ();
  egid = getegid ();

  for (list = path; list; list = g_list_next (list))
    {
      gchar *dir = list->data;

      /*  check if directory exists  */
      err = g_stat (dir, &filestat);

      /*  this is tricky:
       *  if a file is e.g. owned by the current user but not user-writable,
       *  the user has no permission to write to the file regardless
       *  of their group's or other's write permissions
       */
      if (!err && S_ISDIR (filestat.st_mode) &&

          ((filestat.st_mode & S_IWUSR) ||

           ((filestat.st_mode & S_IWGRP) &&
            (euid != filestat.st_uid)) ||

           ((filestat.st_mode & S_IWOTH) &&
            (euid != filestat.st_uid) &&
            (egid != filestat.st_gid))))
        {
          return g_strdup (dir);
        }
    }

  return NULL;
}

static gchar *
pika_env_get_dir (const gchar *pika_env_name,
                  const gchar *compile_time_dir,
                  const gchar *relative_subdir)
{
  const gchar *env = g_getenv (pika_env_name);

  if (env)
    {
      if (! g_path_is_absolute (env))
        g_error ("%s environment variable should be an absolute path.",
                 pika_env_name);

      return g_strdup (env);
    }
  else if (compile_time_dir)
    {
      gchar *retval = g_strdup (compile_time_dir);

      pika_path_runtime_fix (&retval);

      return retval;
    }
  else if (! g_path_is_absolute (relative_subdir))
    {
      return g_build_filename (pika_installation_directory (),
                               relative_subdir,
                               NULL);
    }

  return g_strdup (relative_subdir);
}
