LCOV - code coverage report
Current view: top level - libgnucash/core-utils - gnc-filepath-utils.cpp (source / functions) Hit Total Coverage
Test: gnucash.info Lines: 224 470 47.7 %
Date: 2024-10-31 11:06:40 Functions: 23 42 54.8 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /********************************************************************\
       2                 :            :  * gnc-filepath-utils.c -- file path resolution utility             *
       3                 :            :  *                                                                  *
       4                 :            :  * This program is free software; you can redistribute it and/or    *
       5                 :            :  * modify it under the terms of the GNU General Public License as   *
       6                 :            :  * published by the Free Software Foundation; either version 2 of   *
       7                 :            :  * the License, or (at your option) any later version.              *
       8                 :            :  *                                                                  *
       9                 :            :  * This program is distributed in the hope that it will be useful,  *
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      12                 :            :  * GNU General Public License for more details.                     *
      13                 :            :  *                                                                  *
      14                 :            :  * You should have received a copy of the GNU General Public License*
      15                 :            :  * along with this program; if not, contact:                        *
      16                 :            :  *                                                                  *
      17                 :            :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      18                 :            :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      19                 :            :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      20                 :            : \********************************************************************/
      21                 :            : 
      22                 :            : /*
      23                 :            :  * @file gnc-filepath-utils.c
      24                 :            :  * @brief file path resolution utilities
      25                 :            :  * @author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
      26                 :            :  * @author Copyright (c) 2000 Dave Peticolas
      27                 :            :  */
      28                 :            : 
      29                 :            : #include <glib.h>
      30                 :            : #include <glib/gi18n.h>
      31                 :            : #include <glib/gprintf.h>
      32                 :            : #include <glib/gstdio.h>
      33                 :            : 
      34                 :            : #include <config.h>
      35                 :            : 
      36                 :            : #include <platform.h>
      37                 :            : #if PLATFORM(WINDOWS)
      38                 :            : #include <windows.h>
      39                 :            : #include <Shlobj.h>
      40                 :            : #endif
      41                 :            : 
      42                 :            : 
      43                 :            : #include <stdlib.h>
      44                 :            : #include <stdio.h>
      45                 :            : #include <string.h>
      46                 :            : #include <sys/types.h>
      47                 :            : #include <sys/stat.h>
      48                 :            : #ifdef HAVE_UNISTD_H
      49                 :            : # include <unistd.h>
      50                 :            : #endif
      51                 :            : #include <errno.h>
      52                 :            : 
      53                 :            : #include "gnc-path.h"
      54                 :            : #include "gnc-filepath-utils.h"
      55                 :            : 
      56                 :            : #if defined (_MSC_VER) || defined (G_OS_WIN32)
      57                 :            : #include <glib/gwin32.h>
      58                 :            : #ifndef PATH_MAX
      59                 :            : #define PATH_MAX MAXPATHLEN
      60                 :            : #endif
      61                 :            : #endif
      62                 :            : #ifdef MAC_INTEGRATION
      63                 :            : #include <Foundation/Foundation.h>
      64                 :            : #endif
      65                 :            : 
      66                 :            : #include "gnc-locale-utils.hpp"
      67                 :            : #include <boost/filesystem.hpp>
      68                 :            : #include <boost/locale.hpp>
      69                 :            : #include <regex>
      70                 :            : #include <iostream>
      71                 :            : 
      72                 :            : /* Below cvt and bfs_locale should be used with boost::filesystem::path (bfs)
      73                 :            :  * objects created alter in this source file. The rationale is as follows:
      74                 :            :  * - a bfs object has an internal, locale and platform dependent
      75                 :            :  *   representation of a file system path
      76                 :            :  * - glib's internal representation is always utf8
      77                 :            :  * - when creating a bfs object, we should pass a cvt to convert from
      78                 :            :  *   utf8 to the object's internal representation
      79                 :            :  * - if we later want to use the bfs object's internal representation
      80                 :            :  *   in a glib context we should imbue the locale below so that
      81                 :            :  *   bfs will use it to convert back to utf8
      82                 :            :  * - if the bfs object's internal representation will never be used
      83                 :            :  *   in a glib context, imbuing is not needed (although probably more
      84                 :            :  *   future proof)
      85                 :            :  * - also note creating a bfs based on another one will inherit the
      86                 :            :  *   locale from the source path. So in that case there's not need
      87                 :            :  *   to imbue it again.
      88                 :            :  */
      89                 :            : #if PLATFORM(WINDOWS)
      90                 :            : #include <codecvt>
      91                 :            : using codecvt = std::codecvt_utf8<wchar_t, 0x10FFFF, std::little_endian>;
      92                 :            : using string = std::wstring;
      93                 :            : #else
      94                 :            : /* See https://stackoverflow.com/questions/41744559/is-this-a-bug-of-gcc */
      95                 :            : template<class I, class E, class S>
      96                 :            : struct codecvt_r : std::codecvt<I, E, S>
      97                 :            : {
      98                 :        354 :     ~codecvt_r() {}
      99                 :            : };
     100                 :            : using codecvt = codecvt_r<wchar_t, char, std::mbstate_t>;
     101                 :            : using string = std::string;
     102                 :            : #endif
     103                 :            : static codecvt cvt;
     104                 :            : static std::locale bfs_locale(std::locale(), new codecvt);
     105                 :            : 
     106                 :            : namespace bfs = boost::filesystem;
     107                 :            : namespace bst = boost::system;
     108                 :            : namespace bl = boost::locale;
     109                 :            : 
     110                 :            : /** Check if the path exists and is a regular file.
     111                 :            :  *
     112                 :            :  * \param path -- freed if the path doesn't exist or isn't a regular file
     113                 :            :  *
     114                 :            :  *  \return NULL or the path
     115                 :            :  */
     116                 :            : static gchar *
     117                 :        456 : check_path_return_if_valid(gchar *path)
     118                 :            : {
     119                 :        456 :     if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
     120                 :            :     {
     121                 :          0 :         return path;
     122                 :            :     }
     123                 :        456 :     g_free (path);
     124                 :        456 :     return NULL;
     125                 :            : }
     126                 :            : 
     127                 :            : /** @brief Create an absolute path when given a relative path;
     128                 :            :  *  otherwise return the argument.
     129                 :            :  *
     130                 :            :  *  @warning filefrag should be a simple path fragment. It shouldn't
     131                 :            :  *  contain xml:// or http:// or <whatever>:// other protocol specifiers.
     132                 :            :  *
     133                 :            :  *  If passed a string which g_path_is_absolute declares an absolute
     134                 :            :  *  path, return the argument.
     135                 :            :  *
     136                 :            :  *  Otherwise, assume that filefrag is a well-formed relative path and
     137                 :            :  *  try to find a file with its path relative to
     138                 :            :  *  \li the current working directory,
     139                 :            :  *  \li the installed system-wide data directory (e.g., /usr/local/share/gnucash),
     140                 :            :  *  \li the installed system configuration directory (e.g., /usr/local/etc/gnucash),
     141                 :            :  *  \li or in the user's configuration directory (e.g., $HOME/.gnucash/data)
     142                 :            :  *
     143                 :            :  *  The paths are searched for in that order. If a matching file is
     144                 :            :  *  found, return the absolute path to it.
     145                 :            : 
     146                 :            :  *  If one isn't found, return a absolute path relative to the
     147                 :            :  *  user's configuration directory and note in the trace file that it
     148                 :            :  *  needs to be created.
     149                 :            :  *
     150                 :            :  *  @param filefrag The file path to resolve
     151                 :            :  *
     152                 :            :  *  @return An absolute file path.
     153                 :            :  */
     154                 :            : gchar *
     155                 :        184 : gnc_resolve_file_path (const gchar * filefrag)
     156                 :            : {
     157                 :        184 :     gchar *fullpath = NULL, *tmp_path = NULL;
     158                 :            : 
     159                 :            :     /* seriously invalid */
     160                 :        184 :     if (!filefrag)
     161                 :            :     {
     162                 :          0 :         g_critical("filefrag is NULL");
     163                 :          0 :         return NULL;
     164                 :            :     }
     165                 :            : 
     166                 :            :     /* ---------------------------------------------------- */
     167                 :            :     /* OK, now we try to find or build an absolute file path */
     168                 :            : 
     169                 :            :     /* check for an absolute file path */
     170                 :        184 :     if (g_path_is_absolute(filefrag))
     171                 :         32 :         return g_strdup (filefrag);
     172                 :            : 
     173                 :            :     /* Look in the current working directory */
     174                 :        152 :     tmp_path = g_get_current_dir();
     175                 :        152 :     fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
     176                 :        152 :     g_free(tmp_path);
     177                 :        152 :     fullpath = check_path_return_if_valid(fullpath);
     178                 :        152 :     if (fullpath != NULL)
     179                 :          0 :         return fullpath;
     180                 :            : 
     181                 :            :     /* Look in the data dir (e.g. $PREFIX/share/gnucash) */
     182                 :        152 :     tmp_path = gnc_path_get_pkgdatadir();
     183                 :        152 :     fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
     184                 :        152 :     g_free(tmp_path);
     185                 :        152 :     fullpath = check_path_return_if_valid(fullpath);
     186                 :        152 :     if (fullpath != NULL)
     187                 :          0 :         return fullpath;
     188                 :            : 
     189                 :            :     /* Look in the config dir (e.g. $PREFIX/share/gnucash/accounts) */
     190                 :        152 :     tmp_path = gnc_path_get_accountsdir();
     191                 :        152 :     fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
     192                 :        152 :     g_free(tmp_path);
     193                 :        152 :     fullpath = check_path_return_if_valid(fullpath);
     194                 :        152 :     if (fullpath != NULL)
     195                 :          0 :         return fullpath;
     196                 :            : 
     197                 :            :     /* Look in the users config dir (e.g. $HOME/.gnucash/data) */
     198                 :        152 :     fullpath = g_strdup(gnc_build_data_path(filefrag));
     199                 :        152 :     if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR))
     200                 :          0 :         return fullpath;
     201                 :            : 
     202                 :            :     /* OK, it's not there. Note that it needs to be created and pass it
     203                 :            :      * back anyway */
     204                 :        152 :     g_warning("create new file %s", fullpath);
     205                 :        152 :     return fullpath;
     206                 :            : 
     207                 :            : }
     208                 :            : 
     209                 :        434 : gchar *gnc_file_path_relative_part (const gchar *prefix, const gchar *path)
     210                 :            : {
     211                 :        434 :     std::string p{path};
     212                 :        434 :     if (p.find(prefix) == 0)
     213                 :            :     {
     214                 :        434 :         auto str = p.substr(strlen(prefix));
     215                 :        434 :         return g_strdup(str.c_str());
     216                 :        434 :     }
     217                 :          0 :     return g_strdup(path);
     218                 :        434 : }
     219                 :            : 
     220                 :            : /* Searches for a file fragment paths set via GNC_DOC_PATH environment
     221                 :            :  * variable. If this variable is not set, fall back to search in
     222                 :            :  * - a html directory in the local user's gnucash settings directory
     223                 :            :  *   (typically $HOME/.gnucash/html)
     224                 :            :  * - the gnucash documentation directory
     225                 :            :  *   (typically /usr/share/doc/gnucash)
     226                 :            :  * - the gnucash data directory
     227                 :            :  *   (typically /usr/share/gnucash)
     228                 :            :  * It searches in this order.
     229                 :            :  *
     230                 :            :  * This is used by gnc_path_find_localized_file to search for
     231                 :            :  * localized versions of files if they exist.
     232                 :            :  */
     233                 :            : static gchar *
     234                 :          0 : gnc_path_find_localized_html_file_internal (const gchar * file_name)
     235                 :            : {
     236                 :          0 :     gchar *full_path = NULL;
     237                 :            :     int i;
     238                 :          0 :     const gchar *env_doc_path = g_getenv("GNC_DOC_PATH");
     239                 :          0 :     const gchar *default_dirs[] =
     240                 :            :         {
     241                 :          0 :             gnc_build_userdata_path ("html"),
     242                 :          0 :             gnc_path_get_pkgdocdir (),
     243                 :          0 :             gnc_path_get_pkgdatadir (),
     244                 :            :             NULL
     245                 :          0 :         };
     246                 :            :     gchar **dirs;
     247                 :            : 
     248                 :          0 :     if (!file_name || *file_name == '\0')
     249                 :          0 :         return NULL;
     250                 :            : 
     251                 :            :     /* Allow search path override via GNC_DOC_PATH environment variable */
     252                 :          0 :     if (env_doc_path)
     253                 :          0 :         dirs = g_strsplit (env_doc_path, G_SEARCHPATH_SEPARATOR_S, -1);
     254                 :            :     else
     255                 :          0 :         dirs = (gchar **)default_dirs;
     256                 :            : 
     257                 :          0 :     for (i = 0; dirs[i]; i++)
     258                 :            :     {
     259                 :          0 :         full_path = g_build_filename (dirs[i], file_name, (gchar *)NULL);
     260                 :          0 :         g_debug ("Checking for existence of %s", full_path);
     261                 :          0 :         full_path = check_path_return_if_valid (full_path);
     262                 :          0 :         if (full_path != NULL)
     263                 :          0 :             return full_path;
     264                 :            :     }
     265                 :            : 
     266                 :          0 :     return NULL;
     267                 :            : }
     268                 :            : 
     269                 :            : /** @brief Find an absolute path to a localized version of a given
     270                 :            :  *  relative path to a html or html related file.
     271                 :            :  *  If no localized version exists, an absolute path to the file
     272                 :            :  *  is searched for. If that file doesn't exist either, returns NULL.
     273                 :            :  *
     274                 :            :  *  @warning file_name should be a simple path fragment. It shouldn't
     275                 :            :  *  contain xml:// or http:// or <whatever>:// other protocol specifiers.
     276                 :            :  *
     277                 :            :  *  If passed a string which g_path_is_absolute declares an absolute
     278                 :            :  *  path, return the argument.
     279                 :            :  *
     280                 :            :  *  Otherwise, assume that file_name is a well-formed relative path and
     281                 :            :  *  try to find a file with its path relative to
     282                 :            :  *  \li a localized subdirectory in the html directory
     283                 :            :  *      of the user's configuration directory
     284                 :            :  *      (e.g. $HOME/.gnucash/html/de_DE, $HOME/.gnucash/html/en,...)
     285                 :            :  *  \li a localized subdirectory in the gnucash documentation directory
     286                 :            :  *      (e.g. /usr/share/doc/gnucash/C,...)
     287                 :            :  *  \li the html directory of the user's configuration directory
     288                 :            :  *      (e.g. $HOME/.gnucash/html)
     289                 :            :  *  \li the gnucash documentation directory
     290                 :            :  *      (e.g. /usr/share/doc/gnucash/)
     291                 :            :  *  \li last resort option: the gnucash data directory
     292                 :            :  *      (e.g. /usr/share/gnucash/)
     293                 :            :  *
     294                 :            :  *  The paths are searched for in that order. If a matching file is
     295                 :            :  *  found, return the absolute path to it.
     296                 :            : 
     297                 :            :  *  If one isn't found, return NULL.
     298                 :            :  *
     299                 :            :  *  @param file_name The file path to resolve
     300                 :            :  *
     301                 :            :  *  @return An absolute file path or NULL if no file is found.
     302                 :            :  */
     303                 :            : gchar *
     304                 :          0 : gnc_path_find_localized_html_file (const gchar *file_name)
     305                 :            : {
     306                 :          0 :     gchar *loc_file_name = NULL;
     307                 :          0 :     gchar *full_path = NULL;
     308                 :            :     const gchar * const *lang;
     309                 :            : 
     310                 :          0 :     if (!file_name || *file_name == '\0')
     311                 :          0 :         return NULL;
     312                 :            : 
     313                 :            :     /* An absolute path is returned unmodified. */
     314                 :          0 :     if (g_path_is_absolute (file_name))
     315                 :          0 :         return g_strdup (file_name);
     316                 :            : 
     317                 :            :     /* First try to find the file in any of the localized directories
     318                 :            :      * the user has set up on his system
     319                 :            :      */
     320                 :          0 :     for (lang = g_get_language_names (); *lang; lang++)
     321                 :            :     {
     322                 :          0 :         loc_file_name = g_build_filename (*lang, file_name, (gchar *)NULL);
     323                 :          0 :         full_path = gnc_path_find_localized_html_file_internal (loc_file_name);
     324                 :          0 :         g_free (loc_file_name);
     325                 :          0 :         if (full_path != NULL)
     326                 :          0 :             return full_path;
     327                 :            :     }
     328                 :            : 
     329                 :            :     /* If not found in a localized directory, try to find the file
     330                 :            :      * in any of the base directories
     331                 :            :      */
     332                 :          0 :     return gnc_path_find_localized_html_file_internal (file_name);
     333                 :            : 
     334                 :            : }
     335                 :            : 
     336                 :            : /* ====================================================================== */
     337                 :            : static auto gnc_userdata_home = bfs::path();
     338                 :            : static auto gnc_userconfig_home = bfs::path();
     339                 :            : static auto build_dir = bfs::path();
     340                 :            : /*Provide static-stored strings for gnc_userdata_home and
     341                 :            :  * gnc_userconfig_home to ensure that the cstrings don't go out of
     342                 :            :  * scope when gnc_userdata_dir and gnc_userconfig_dir return them.
     343                 :            :  */
     344                 :            : static std::string gnc_userdata_home_str;
     345                 :            : static std::string gnc_userconfig_home_str;
     346                 :            : 
     347                 :        189 : static bool dir_is_descendant (const bfs::path& path, const bfs::path& base)
     348                 :            : {
     349                 :        189 :     auto test_path = path;
     350                 :        189 :     if (bfs::exists (path))
     351                 :        171 :         test_path = bfs::canonical (path);
     352                 :        189 :     auto test_base = base;
     353                 :        189 :     if (bfs::exists (base))
     354                 :        182 :         test_base = bfs::canonical (base);
     355                 :            : 
     356                 :        189 :     auto is_descendant = (test_path.string() == test_base.string());
     357                 :        536 :     while (!test_path.empty() && !is_descendant)
     358                 :            :     {
     359                 :        347 :         test_path = test_path.parent_path();
     360                 :        347 :         is_descendant = (test_path.string() == test_base.string());
     361                 :            :     }
     362                 :        189 :     return is_descendant;
     363                 :        189 : }
     364                 :            : 
     365                 :            : /** @brief Check that the supplied directory path exists, is a directory, and
     366                 :            :  * that the user has adequate permissions to use it.
     367                 :            :  *
     368                 :            :  * @param dirname The path to check
     369                 :            :  */
     370                 :            : static bool
     371                 :        189 : gnc_validate_directory (const bfs::path &dirname)
     372                 :            : {
     373                 :        189 :     if (dirname.empty())
     374                 :          0 :         return false;
     375                 :            : 
     376                 :        189 :     auto create_dirs = true;
     377                 :        189 :     if (build_dir.empty() || !dir_is_descendant (dirname, build_dir))
     378                 :            :     {
     379                 :            :         /* Gnucash won't create a home directory
     380                 :            :          * if it doesn't exist yet. So if the directory to create
     381                 :            :          * is a descendant of the homedir, we can't create it either.
     382                 :            :          * This check is conditioned on do_homedir_check because
     383                 :            :          * we need to overrule it during build (when guile interferes)
     384                 :            :          * and testing.
     385                 :            :          */
     386                 :         24 :         bfs::path home_dir(g_get_home_dir(), cvt);
     387                 :         24 :         home_dir.imbue(bfs_locale);
     388                 :         24 :         auto homedir_exists = bfs::exists(home_dir);
     389                 :         24 :         auto is_descendant = dir_is_descendant (dirname, home_dir);
     390                 :         24 :         if (!homedir_exists && is_descendant)
     391                 :          2 :             create_dirs = false;
     392                 :         24 :     }
     393                 :            : 
     394                 :            :     /* Create directories if they don't exist yet and we can
     395                 :            :      *
     396                 :            :      * Note this will do nothing if the directory and its
     397                 :            :      * parents already exist, but will fail if the path
     398                 :            :      * points to a file or a softlink. So it serves as a test
     399                 :            :      * for that as well.
     400                 :            :      */
     401                 :        189 :     if (create_dirs)
     402                 :        187 :         bfs::create_directories(dirname);
     403                 :            :     else
     404                 :          2 :         throw (bfs::filesystem_error (
     405                 :          6 :             std::string (dirname.string() +
     406                 :          4 :                          " is a descendant of a non-existing home directory. As " +
     407                 :            :                          PACKAGE_NAME +
     408                 :            :                          " will never create a home directory this path can't be used"),
     409                 :          4 :             dirname, bst::error_code(bst::errc::permission_denied, bst::generic_category())));
     410                 :            : 
     411                 :        187 :     auto d = bfs::directory_entry (dirname);
     412                 :        187 :     auto perms = d.status().permissions();
     413                 :            : 
     414                 :            :     /* On Windows only write permission will be checked.
     415                 :            :      * So strictly speaking we'd need two error messages here depending
     416                 :            :      * on the platform. For simplicity this detail is glossed over though. */
     417                 :            : #if PLATFORM(WINDOWS)
     418                 :            :     auto check_perms = bfs::owner_read | bfs::owner_write;
     419                 :            : #else
     420                 :        187 :     auto check_perms = bfs::owner_all;
     421                 :            : #endif
     422                 :        187 :     if ((perms & check_perms) != check_perms)
     423                 :          0 :         throw (bfs::filesystem_error(
     424                 :          0 :             std::string("Insufficient permissions, at least write and access permissions required: ")
     425                 :          0 :             + dirname.string(), dirname,
     426                 :          0 :             bst::error_code(bst::errc::permission_denied, bst::generic_category())));
     427                 :            : 
     428                 :        187 :     return true;
     429                 :        187 : }
     430                 :            : 
     431                 :            : /* Will attempt to copy all files and directories from src to dest
     432                 :            :  * Returns true if successful or false if not */
     433                 :            : static bool
     434                 :          3 : copy_recursive(const bfs::path& src, const bfs::path& dest)
     435                 :            : {
     436                 :          3 :     if (!bfs::exists(src))
     437                 :          3 :         return false;
     438                 :            : 
     439                 :            :     // Don't copy on self
     440                 :          0 :     if (src.compare(dest) == 0)
     441                 :          0 :         return false;
     442                 :            : 
     443                 :          0 :     auto old_str = src.string();
     444                 :          0 :     auto old_len = old_str.size();
     445                 :            :     try
     446                 :            :     {
     447                 :            :         /* Note: the for(auto elem : iterator) construct fails
     448                 :            :          * on travis (g++ 4.8.x, boost 1.54) so I'm using
     449                 :            :          * a traditional for loop here */
     450                 :          0 :         for(auto direntry = bfs::recursive_directory_iterator(src);
     451                 :          0 :             direntry != bfs::recursive_directory_iterator(); ++direntry)
     452                 :            :         {
     453                 :            : #ifdef G_OS_WIN32
     454                 :            :             string cur_str = direntry->path().wstring();
     455                 :            : #else
     456                 :          0 :             string cur_str = direntry->path().string();
     457                 :            : #endif
     458                 :          0 :             auto cur_len = cur_str.size();
     459                 :          0 :             string rel_str(cur_str, old_len, cur_len - old_len);
     460                 :          0 :             bfs::path relpath(rel_str, cvt);
     461                 :          0 :             auto newpath = bfs::absolute (relpath.relative_path(), dest);
     462                 :          0 :             newpath.imbue(bfs_locale);
     463                 :          0 :             bfs::copy(direntry->path(), newpath);
     464                 :          0 :         }
     465                 :            :     }
     466                 :          0 :     catch(const bfs::filesystem_error& ex)
     467                 :            :     {
     468                 :          0 :         g_warning("An error occurred while trying to migrate the user configation from\n%s to\n%s"
     469                 :            :                   "(Error: %s)",
     470                 :            :                   src.string().c_str(), gnc_userdata_home_str.c_str(),
     471                 :            :                   ex.what());
     472                 :          0 :         return false;
     473                 :          0 :     }
     474                 :            : 
     475                 :          0 :     return true;
     476                 :          0 : }
     477                 :            : 
     478                 :            : #ifdef G_OS_WIN32
     479                 :            : /* g_get_user_data_dir defaults to CSIDL_LOCAL_APPDATA, but
     480                 :            :  * the roaming profile makes more sense.
     481                 :            :  * So this function is a copy of glib's internal get_special_folder
     482                 :            :  * and minimally adjusted to fetch CSIDL_APPDATA
     483                 :            :  */
     484                 :            : static bfs::path
     485                 :            : get_user_data_dir ()
     486                 :            : {
     487                 :            :     wchar_t path[MAX_PATH+1];
     488                 :            :     HRESULT hr;
     489                 :            :     LPITEMIDLIST pidl = NULL;
     490                 :            :     BOOL b;
     491                 :            : 
     492                 :            :     hr = SHGetSpecialFolderLocation (NULL, CSIDL_APPDATA, &pidl);
     493                 :            :     if (hr == S_OK)
     494                 :            :     {
     495                 :            :         b = SHGetPathFromIDListW (pidl, path);
     496                 :            :         CoTaskMemFree (pidl);
     497                 :            :     }
     498                 :            :     bfs::path retval(path, cvt);
     499                 :            :     retval.imbue(bfs_locale);
     500                 :            :     return retval;
     501                 :            : }
     502                 :            : #elif defined MAC_INTEGRATION
     503                 :            : static bfs::path
     504                 :            : get_user_data_dir()
     505                 :            : {
     506                 :            :     NSFileManager*fm = [NSFileManager defaultManager];
     507                 :            :     NSArray* appSupportDir = [fm URLsForDirectory:NSApplicationSupportDirectory
     508                 :            :     inDomains:NSUserDomainMask];
     509                 :            :     NSString *dirPath = nullptr;
     510                 :            :     if ([appSupportDir count] > 0)
     511                 :            :     {
     512                 :            :         NSURL* dirUrl = [appSupportDir objectAtIndex:0];
     513                 :            :         dirPath = [dirUrl path];
     514                 :            :     }
     515                 :            :     return [dirPath UTF8String];
     516                 :            : }
     517                 :            : #else
     518                 :            : static bfs::path
     519                 :          3 : get_user_data_dir()
     520                 :            : {
     521                 :          3 :     return g_get_user_data_dir();
     522                 :            : }
     523                 :            : #endif
     524                 :            : 
     525                 :            : /* Returns an absolute path to the user's data directory.
     526                 :            :  * Note the default path depends on the platform.
     527                 :            :  *  - Windows: CSIDL_APPDATA
     528                 :            :  *  - OS X: $HOME/Application Support
     529                 :            :  *  - Linux: $XDG_DATA_HOME (or the default $HOME/.local/share)
     530                 :            :  */
     531                 :            : static bfs::path
     532                 :          3 : get_userdata_home(void)
     533                 :            : {
     534                 :          3 :     auto try_tmp_dir = true;
     535                 :          3 :     auto userdata_home = get_user_data_dir();
     536                 :            : 
     537                 :            :     /* g_get_user_data_dir doesn't check whether the path exists nor attempts to
     538                 :            :      * create it. So while it may return an actual path we may not be able to use it.
     539                 :            :      * Let's check that now */
     540                 :          3 :     if (!userdata_home.empty())
     541                 :            :     {
     542                 :            :         try
     543                 :            :         {
     544                 :          3 :             gnc_validate_directory(userdata_home);  // May throw
     545                 :          2 :             try_tmp_dir = false;
     546                 :            :         }
     547                 :          1 :         catch (const bfs::filesystem_error& ex)
     548                 :            :         {
     549                 :          1 :             auto path_string = userdata_home.string();
     550                 :          1 :             g_warning("%s is not a suitable base directory for the user data. "
     551                 :            :             "Trying temporary directory instead.\n(Error: %s)",
     552                 :            :             path_string.c_str(), ex.what());
     553                 :          1 :         }
     554                 :            :     }
     555                 :            : 
     556                 :            :     /* The path we got is not usable, so fall back to a path in TMP_DIR.
     557                 :            :        Hopefully we can always write there. */
     558                 :          3 :     if (try_tmp_dir)
     559                 :            :     {
     560                 :          1 :         bfs::path newpath(g_get_tmp_dir (), cvt);
     561                 :          1 :         userdata_home = newpath / g_get_user_name ();
     562                 :          1 :         userdata_home.imbue(bfs_locale);
     563                 :          1 :     }
     564                 :          3 :     g_assert(!userdata_home.empty());
     565                 :            : 
     566                 :          3 :     return userdata_home;
     567                 :          0 : }
     568                 :            : 
     569                 :            : /* Returns an absolute path to the user's config directory.
     570                 :            :  * Note the default path depends on the platform.
     571                 :            :  *  - Windows: CSIDL_APPDATA
     572                 :            :  *  - OS X: $HOME/Application Support
     573                 :            :  *  - Linux: $XDG_CONFIG_HOME (or the default $HOME/.config)
     574                 :            :  */
     575                 :            : static bfs::path
     576                 :         41 : get_userconfig_home(void)
     577                 :            : {
     578                 :            :     /* On Windows and Macs the data directory is used, for Linux
     579                 :            :        $HOME/.config is used */
     580                 :            : #if defined (G_OS_WIN32) || defined (MAC_INTEGRATION)
     581                 :            :     return get_user_data_dir();
     582                 :            : #else
     583                 :         41 :     return g_get_user_config_dir();
     584                 :            : #endif
     585                 :            : }
     586                 :            : 
     587                 :          3 : static std::string migrate_gnc_datahome()
     588                 :            : {
     589                 :            :     // Specify location of dictionaries
     590                 :          3 :     bfs::path old_dir(g_get_home_dir(), cvt);
     591                 :          3 :     old_dir /= ".gnucash";
     592                 :            : 
     593                 :          3 :     std::stringstream migration_msg;
     594                 :          3 :     migration_msg.imbue(gnc_get_boost_locale());
     595                 :            : 
     596                 :            :     /* Step 1: copy directory $HOME/.gnucash to $GNC_DATA_HOME */
     597                 :          3 :     auto full_copy = copy_recursive (old_dir, gnc_userdata_home);
     598                 :            : 
     599                 :            :     /* Step 2: move user editable config files from GNC_DATA_HOME to GNC_CONFIG_HOME
     600                 :            :                 These files are:
     601                 :            :                 - log.conf
     602                 :            :                 - the most recent of "config-2.0.user", "config-1.8.user", "config-1.6.user",
     603                 :            :                     "config.user"
     604                 :            :                 Note: we'll also  rename config.user to config-user.scm to make it more clear
     605                 :            :                       this file is meant for custom scm code to load at run time */
     606                 :          3 :     auto failed = std::vector<std::string>{};
     607                 :          3 :     auto succeeded = std::vector<std::string>{};
     608                 :            : 
     609                 :            :     /* Move log.conf
     610                 :            :      * Note on OS X/Quarz and Windows GNC_DATA_HOME and GNC_CONFIG_HOME are the same, so this will do nothing */
     611                 :          3 :     auto oldlogpath = gnc_userdata_home / "log.conf";
     612                 :          3 :     auto newlogpath = gnc_userconfig_home / "log.conf";
     613                 :            :     try
     614                 :            :     {
     615                 :          3 :         if (bfs::exists (oldlogpath) && gnc_validate_directory (gnc_userconfig_home) &&
     616                 :          0 :             (oldlogpath != newlogpath))
     617                 :            :         {
     618                 :          0 :             bfs::rename (oldlogpath, newlogpath);
     619                 :          0 :             succeeded.emplace_back ("log.conf");
     620                 :            :         }
     621                 :            :     }
     622                 :          0 :     catch (const bfs::filesystem_error& ex)
     623                 :            :     {
     624                 :          0 :         failed.emplace_back ("log.conf");
     625                 :          0 :     }
     626                 :            : 
     627                 :            :     /* Move/rename the most relevant config*.user file. The relevance comes from
     628                 :            :      * the order in which these files were searched for at gnucash startup.
     629                 :            :      * Make note of other config*.user files found to inform the user they are now ignored */
     630                 :            :     auto user_config_files = std::vector<std::string>
     631                 :            :     {
     632                 :            :         "config-2.0.user", "config-1.8.user",
     633                 :            :         "config-1.6.user", "config.user"
     634                 :         21 :     };
     635                 :          3 :     auto conf_exist_vec = std::vector<std::string> {};
     636                 :          3 :     auto renamed_config = std::string();
     637                 :         15 :     for (auto conf_file : user_config_files)
     638                 :            :     {
     639                 :         12 :         auto oldconfpath = gnc_userdata_home / conf_file;
     640                 :            :         try
     641                 :            :         {
     642                 :         12 :             if (bfs::exists (oldconfpath) && gnc_validate_directory (gnc_userconfig_home))
     643                 :            :             {
     644                 :            :                 // Only migrate the most relevant of the config*.user files
     645                 :          0 :                 if (renamed_config.empty())
     646                 :            :                 {
     647                 :            :                     /* Translators: this string refers to a file name that gets renamed */
     648                 :          0 :                     renamed_config = conf_file + " (" + _("Renamed to:") + " config-user.scm)";
     649                 :          0 :                     auto newconfpath = gnc_userconfig_home / "config-user.scm";
     650                 :          0 :                     bfs::rename (oldconfpath, newconfpath);
     651                 :          0 :                 }
     652                 :            :                 else
     653                 :            :                 {
     654                 :            :                     /* We want to report the obsolete file to the user */
     655                 :          0 :                     conf_exist_vec.emplace_back (conf_file);
     656                 :          0 :                     if (full_copy)
     657                 :            :                         /* As we copied from .gnucash, just delete the obsolete file as well. It's still
     658                 :            :                          * present in .gnucash if the user wants to recover it */
     659                 :          0 :                         bfs::remove (oldconfpath);
     660                 :            :                 }
     661                 :            :             }
     662                 :            :         }
     663                 :          0 :         catch (const bfs::filesystem_error& ex)
     664                 :            :         {
     665                 :          0 :             failed.emplace_back (conf_file);
     666                 :          0 :         }
     667                 :         12 :     }
     668                 :          3 :     if (!renamed_config.empty())
     669                 :          0 :         succeeded.emplace_back (renamed_config);
     670                 :            : 
     671                 :            :     /* Step 3: inform the user of additional changes */
     672                 :          3 :     if (full_copy || !succeeded.empty() || !conf_exist_vec.empty() || !failed.empty())
     673                 :          0 :         migration_msg << _("Notice") << std::endl << std::endl;
     674                 :            : 
     675                 :          3 :     if (full_copy)
     676                 :            :     {
     677                 :            :         migration_msg
     678                 :          0 :         << _("Your gnucash metadata has been migrated.") << std::endl << std::endl
     679                 :            :         /* Translators: this refers to a directory name. */
     680                 :          0 :         << _("Old location:") << " " << old_dir.string() << std::endl
     681                 :            :         /* Translators: this refers to a directory name. */
     682                 :          0 :         << _("New location:") << " " << gnc_userdata_home.string() << std::endl << std::endl
     683                 :            :         // Translators {1} will be replaced with the package name (typically Gnucash) at runtime
     684                 :          0 :         << bl::format (std::string{_("If you no longer intend to run {1} 2.6.x or older on this system you can safely remove the old directory.")})
     685                 :          0 :         % PACKAGE_NAME;
     686                 :            :     }
     687                 :            : 
     688                 :          3 :     if (full_copy &&
     689                 :          0 :         (!succeeded.empty() || !conf_exist_vec.empty() || !failed.empty()))
     690                 :          0 :         migration_msg << std::endl << std::endl
     691                 :          0 :                         << _("In addition:");
     692                 :            : 
     693                 :          3 :     if (!succeeded.empty())
     694                 :            :     {
     695                 :          0 :         migration_msg << std::endl << std::endl;
     696                 :          0 :         if (full_copy)
     697                 :          0 :             migration_msg << bl::format (std::string{ngettext("The following file has been copied to {1} instead:",
     698                 :            :                                                         "The following files have been copied to {1} instead:",
     699                 :          0 :                                                        succeeded.size())}) % gnc_userconfig_home.string().c_str();
     700                 :            :         else
     701                 :          0 :             migration_msg << bl::format (std::string{_("The following file in {1} has been renamed:")})
     702                 :          0 :                                          % gnc_userconfig_home.string().c_str();
     703                 :            : 
     704                 :          0 :         migration_msg << std::endl;
     705                 :          0 :         for (const auto& success_file : succeeded)
     706                 :          0 :             migration_msg << "- " << success_file << std::endl;
     707                 :            :     }
     708                 :          3 :     if (!conf_exist_vec.empty())
     709                 :            :     {
     710                 :            :         migration_msg << "\n\n" 
     711                 :          0 :                       << ngettext("The following file has become obsolete and will be ignored:",
     712                 :            :                                         "The following files have become obsolete and will be ignored:",
     713                 :          0 :                                         conf_exist_vec.size())
     714                 :          0 :                       << std::endl;
     715                 :          0 :         for (const auto& obs_file : conf_exist_vec)
     716                 :          0 :             migration_msg << "- " << obs_file << std::endl;
     717                 :            :     }
     718                 :          3 :     if (!failed.empty())
     719                 :            :     {
     720                 :          0 :         migration_msg << std::endl << std::endl
     721                 :          0 :                       << bl::format (std::string{ngettext("The following file could not be moved to {1}:",
     722                 :            :                                                     "The following files could not be moved to {1}:",
     723                 :          0 :                                                           failed.size())}) % gnc_userconfig_home.string().c_str()
     724                 :          0 :                       << std::endl;
     725                 :          0 :         for (const auto& failed_file : failed)
     726                 :          0 :             migration_msg << "- " << failed_file << std::endl;
     727                 :            :     }
     728                 :            : 
     729                 :          6 :     return migration_msg.str ();
     730                 :          3 : }
     731                 :            : 
     732                 :            : 
     733                 :            : 
     734                 :            : #if defined G_OS_WIN32 ||defined MAC_INTEGRATION
     735                 :            : constexpr auto path_package = PACKAGE_NAME;
     736                 :            : #else
     737                 :            : constexpr auto path_package = PROJECT_NAME;
     738                 :            : #endif
     739                 :            : 
     740                 :            : // Initialize the user's config directory for gnucash
     741                 :            : // creating it if it doesn't exist yet.
     742                 :            : static void
     743                 :         37 : gnc_file_path_init_config_home (void)
     744                 :            : {
     745                 :         37 :     auto have_valid_userconfig_home = false;
     746                 :            : 
     747                 :            :     /* If this code is run while building/testing, use a fake GNC_CONFIG_HOME
     748                 :            :      * in the base of the build directory. This is to deal with all kinds of
     749                 :            :      * issues when the build environment is not a complete environment (like
     750                 :            :      * it could be missing a valid home directory). */
     751                 :         37 :     auto env_build_dir = g_getenv ("GNC_BUILDDIR");
     752                 :         37 :     bfs::path new_dir(env_build_dir ? env_build_dir : "", cvt);
     753                 :         37 :     new_dir.imbue(bfs_locale);
     754                 :         37 :     build_dir = std::move(new_dir);
     755                 :         37 :     auto running_uninstalled = (g_getenv ("GNC_UNINSTALLED") != NULL);
     756                 :         37 :     if (running_uninstalled && !build_dir.empty())
     757                 :            :     {
     758                 :         33 :         gnc_userconfig_home = build_dir / "gnc_config_home";
     759                 :            :         try
     760                 :            :         {
     761                 :         33 :             gnc_validate_directory (gnc_userconfig_home); // May throw
     762                 :         33 :             have_valid_userconfig_home = true;
     763                 :            :         }
     764                 :          0 :         catch (const bfs::filesystem_error& ex)
     765                 :            :         {
     766                 :          0 :             auto path_string = gnc_userconfig_home.string();
     767                 :          0 :             g_warning("%s (due to run during at build time) is not a suitable directory for user configuration files. "
     768                 :            :             "Trying another directory instead.\n(Error: %s)",
     769                 :            :                       path_string.c_str(), ex.what());
     770                 :          0 :         }
     771                 :            :     }
     772                 :            : 
     773                 :         37 :     if (!have_valid_userconfig_home)
     774                 :            :     {
     775                 :            :         /* If environment variable GNC_CONFIG_HOME is set, try whether
     776                 :            :          * it points at a valid directory. */
     777                 :          4 :         auto gnc_userconfig_home_env = g_getenv ("GNC_CONFIG_HOME");
     778                 :          4 :         if (gnc_userconfig_home_env)
     779                 :            :         {
     780                 :          0 :             bfs::path newdir(gnc_userconfig_home_env, cvt);
     781                 :          0 :             newdir.imbue(bfs_locale);
     782                 :          0 :             gnc_userconfig_home = std::move(newdir);
     783                 :            :             try
     784                 :            :             {
     785                 :          0 :                 gnc_validate_directory (gnc_userconfig_home); // May throw
     786                 :          0 :                 have_valid_userconfig_home = true;
     787                 :            :             }
     788                 :          0 :             catch (const bfs::filesystem_error& ex)
     789                 :            :             {
     790                 :          0 :                 auto path_string = gnc_userconfig_home.string();
     791                 :          0 :                 g_warning("%s (from environment variable 'GNC_CONFIG_HOME') is not a suitable directory for user configuration files. "
     792                 :            :                 "Trying the default instead.\n(Error: %s)",
     793                 :            :                           path_string.c_str(), ex.what());
     794                 :          0 :             }
     795                 :          0 :         }
     796                 :            :     }
     797                 :            : 
     798                 :         37 :     if (!have_valid_userconfig_home)
     799                 :            :     {
     800                 :            :         /* Determine platform dependent default userconfig_home_path
     801                 :            :          * and check whether it's valid */
     802                 :          4 :         auto userconfig_home = get_userconfig_home();
     803                 :          4 :         gnc_userconfig_home = userconfig_home / path_package;
     804                 :            :         try
     805                 :            :         {
     806                 :          4 :             gnc_validate_directory (gnc_userconfig_home);
     807                 :            :         }
     808                 :          0 :         catch (const bfs::filesystem_error& ex)
     809                 :            :         {
     810                 :          0 :             g_warning ("User configuration directory doesn't exist, yet could not be created. Proceed with caution.\n"
     811                 :            :             "(Error: %s)", ex.what());
     812                 :          0 :         }
     813                 :          4 :     }
     814                 :         37 :     gnc_userconfig_home_str = gnc_userconfig_home.string();
     815                 :         37 : }
     816                 :            : 
     817                 :            : // Initialize the user's config directory for gnucash
     818                 :            : // creating it if it didn't exist yet.
     819                 :            : // The function will return true if the directory already
     820                 :            : // existed or false if it had to be created
     821                 :            : static bool
     822                 :         37 : gnc_file_path_init_data_home (void)
     823                 :            : {
     824                 :            :     // Initialize the user's data directory for gnucash
     825                 :         37 :     auto gnc_userdata_home_exists = false;
     826                 :         37 :     auto have_valid_userdata_home = false;
     827                 :            : 
     828                 :            :     /* If this code is run while building/testing, use a fake GNC_DATA_HOME
     829                 :            :      * in the base of the build directory. This is to deal with all kinds of
     830                 :            :      * issues when the build environment is not a complete environment (like
     831                 :            :      * it could be missing a valid home directory). */
     832                 :         37 :     auto env_build_dir = g_getenv ("GNC_BUILDDIR");
     833                 :         37 :     bfs::path new_dir(env_build_dir ? env_build_dir : "", cvt);
     834                 :         37 :     new_dir.imbue(bfs_locale);
     835                 :         37 :     build_dir = std::move(new_dir);
     836                 :         37 :     auto running_uninstalled = (g_getenv ("GNC_UNINSTALLED") != NULL);
     837                 :         37 :     if (running_uninstalled && !build_dir.empty())
     838                 :            :     {
     839                 :         33 :         gnc_userdata_home = build_dir / "gnc_data_home";
     840                 :            :         try
     841                 :            :         {
     842                 :         33 :             gnc_validate_directory (gnc_userdata_home); // May throw
     843                 :         33 :             have_valid_userdata_home = true;
     844                 :         33 :             gnc_userdata_home_exists = true; // To prevent possible migration further down
     845                 :            :         }
     846                 :          0 :         catch (const bfs::filesystem_error& ex)
     847                 :            :         {
     848                 :          0 :             auto path_string = gnc_userdata_home.string();
     849                 :          0 :             g_warning("%s (due to run during at build time) is not a suitable directory for user data. "
     850                 :            :             "Trying another directory instead.\n(Error: %s)",
     851                 :            :                       path_string.c_str(), ex.what());
     852                 :          0 :         }
     853                 :            :     }
     854                 :            : 
     855                 :         37 :     if (!have_valid_userdata_home)
     856                 :            :     {
     857                 :            :         /* If environment variable GNC_DATA_HOME is set, try whether
     858                 :            :          * it points at a valid directory. */
     859                 :          4 :         auto gnc_userdata_home_env = g_getenv ("GNC_DATA_HOME");
     860                 :          4 :         if (gnc_userdata_home_env)
     861                 :            :         {
     862                 :          2 :             bfs::path newdir(gnc_userdata_home_env, cvt);
     863                 :          2 :             newdir.imbue(bfs_locale);
     864                 :          2 :             gnc_userdata_home = std::move(newdir);
     865                 :            :             try
     866                 :            :             {
     867                 :          2 :                 gnc_userdata_home_exists = bfs::exists (gnc_userdata_home);
     868                 :          2 :                 gnc_validate_directory (gnc_userdata_home); // May throw
     869                 :          1 :                 have_valid_userdata_home = true;
     870                 :            :             }
     871                 :          1 :             catch (const bfs::filesystem_error& ex)
     872                 :            :             {
     873                 :          1 :                 auto path_string = gnc_userdata_home.string();
     874                 :          1 :                 g_warning("%s (from environment variable 'GNC_DATA_HOME') is not a suitable directory for user data. "
     875                 :            :                 "Trying the default instead.\n(Error: %s)",
     876                 :            :                           path_string.c_str(), ex.what());
     877                 :          1 :             }
     878                 :          2 :         }
     879                 :            :     }
     880                 :            : 
     881                 :         37 :     if (!have_valid_userdata_home)
     882                 :            :     {
     883                 :            :         /* Determine platform dependent default userdata_home_path
     884                 :            :          * and check whether it's valid */
     885                 :          3 :         auto userdata_home = get_userdata_home();
     886                 :          3 :         gnc_userdata_home = userdata_home / path_package;
     887                 :            :         try
     888                 :            :         {
     889                 :          3 :             gnc_userdata_home_exists = bfs::exists (gnc_userdata_home);
     890                 :          3 :             gnc_validate_directory (gnc_userdata_home);
     891                 :            :         }
     892                 :          0 :         catch (const bfs::filesystem_error& ex)
     893                 :            :         {
     894                 :          0 :             g_warning ("User data directory doesn't exist, yet could not be created. Proceed with caution.\n"
     895                 :            :             "(Error: %s)", ex.what());
     896                 :          0 :         }
     897                 :          3 :     }
     898                 :         37 :     gnc_userdata_home_str = gnc_userdata_home.string();
     899                 :         37 :     return gnc_userdata_home_exists;
     900                 :         37 : }
     901                 :            : 
     902                 :            : // Initialize the user's config and data directory for gnucash
     903                 :            : // This function will also create these directories if they didn't
     904                 :            : // exist yet.
     905                 :            : // In addition it will trigger a migration if the user's data home
     906                 :            : // didn't exist but the now obsolete GNC_DOT_DIR ($HOME/.gnucash)
     907                 :            : // does.
     908                 :            : // Finally it well ensure a number of default required directories
     909                 :            : // will be created if they don't exist yet.
     910                 :            : char *
     911                 :         37 : gnc_filepath_init (void)
     912                 :            : {
     913                 :         37 :     gnc_userconfig_home = get_userconfig_home() / path_package;
     914                 :         37 :     gnc_userconfig_home_str = gnc_userconfig_home.string();
     915                 :            : 
     916                 :         37 :     gnc_file_path_init_config_home ();
     917                 :         37 :     auto gnc_userdata_home_exists = gnc_file_path_init_data_home ();
     918                 :            : 
     919                 :            :     /* Run migration code before creating the default directories
     920                 :            :        If migrating, these default directories are copied instead of created. */
     921                 :         37 :     auto migration_notice = std::string ();
     922                 :         37 :     if (!gnc_userdata_home_exists)
     923                 :          3 :         migration_notice = migrate_gnc_datahome();
     924                 :            : 
     925                 :            :     /* Try to create the standard subdirectories for gnucash' user data */
     926                 :            :     try
     927                 :            :     {
     928                 :         37 :         gnc_validate_directory (gnc_userdata_home / "books");
     929                 :         37 :         gnc_validate_directory (gnc_userdata_home / "checks");
     930                 :         37 :         gnc_validate_directory (gnc_userdata_home / "translog");
     931                 :            :     }
     932                 :          0 :     catch (const bfs::filesystem_error& ex)
     933                 :            :     {
     934                 :          0 :         g_warning ("Default user data subdirectories don't exist, yet could not be created. Proceed with caution.\n"
     935                 :            :         "(Error: %s)", ex.what());
     936                 :          0 :     }
     937                 :            : 
     938                 :         74 :     return migration_notice.empty() ? NULL : g_strdup (migration_notice.c_str());
     939                 :         37 : }
     940                 :            : 
     941                 :            : /** @fn const gchar * gnc_userdata_dir ()
     942                 :            :  *  @brief Ensure that the user's configuration directory exists and is minimally populated.
     943                 :            :  *
     944                 :            :  *  Note that the default path depends on the platform.
     945                 :            :  *  - Windows: CSIDL_APPDATA/Gnucash
     946                 :            :  *  - OS X: $HOME/Application Support/Gnucash
     947                 :            :  *  - Linux: $XDG_DATA_HOME/gnucash (or the default $HOME/.local/share/gnucash)
     948                 :            :  *
     949                 :            :  *  If any of these paths don't exist and can't be created the function will
     950                 :            :  *  fall back to
     951                 :            :  *  - $HOME/.gnucash
     952                 :            :  *  - <TMP_DIR>/gnucash
     953                 :            :  *  (in that order)
     954                 :            :  *
     955                 :            :  *  @return An absolute path to the configuration directory. This string is
     956                 :            :  *  owned by the gnc_filepath_utils code and should not be freed by the user.
     957                 :            :  */
     958                 :            : 
     959                 :            : 
     960                 :            : /* Note Don't create missing directories automatically
     961                 :            :  * here and in the next function except if the
     962                 :            :  * target directory is the temporary directory. This
     963                 :            :  * should be done properly at a higher level (in the gui
     964                 :            :  * code most likely) very early in application startup.
     965                 :            :  * This call is just a fallback to prevent the code from
     966                 :            :  * crashing because no directories were configured. This
     967                 :            :  * weird construct is set up because compiling our guile
     968                 :            :  * scripts also triggers this code and that's not the
     969                 :            :  * right moment to start creating the necessary directories.
     970                 :            :  * FIXME A better approach would be to have the gnc_userdata_home
     971                 :            :  * verification/creation be part of the application code instead
     972                 :            :  * of libgnucash. If libgnucash needs access to this directory
     973                 :            :  * libgnucash will need some kind of initialization routine
     974                 :            :  * that the application can call to set (among others) the proper
     975                 :            :  * gnc_uderdata_home for libgnucash. The only other aspect to
     976                 :            :  * consider here is how to handle this in the bindings (if they
     977                 :            :  * need it).
     978                 :            :  */
     979                 :            : const gchar *
     980                 :          0 : gnc_userdata_dir (void)
     981                 :            : {
     982                 :          0 :     if (gnc_userdata_home.empty())
     983                 :          0 :         gnc_filepath_init();
     984                 :          0 :     return g_strdup(gnc_userdata_home_str.c_str());
     985                 :            : }
     986                 :            : 
     987                 :            : /** @fn const gchar * gnc_userconfig_dir ()
     988                 :            :  *  @brief Return the user's config directory for gnucash
     989                 :            :  *
     990                 :            :  *  @note the default path depends on the platform.
     991                 :            :  *  - Windows: CSIDL_APPDATA/Gnucash
     992                 :            :  *  - OS X: $HOME/Application Support/Gnucash
     993                 :            :  *  - Linux: $XDG_CONFIG_HOME/gnucash (or the default $HOME/.config/gnucash)
     994                 :            :  *
     995                 :            :  *  @note gnucash will not create this directory if it doesn't exist
     996                 :            :  *
     997                 :            :  *  @return An absolute path to the configuration directory. This string is
     998                 :            :  *  owned by the gnc_filepath_utils code and should not be freed by the user.
     999                 :            :  */
    1000                 :            : const gchar *
    1001                 :          0 : gnc_userconfig_dir (void)
    1002                 :            : {
    1003                 :          0 :     if (gnc_userdata_home.empty())
    1004                 :          0 :         gnc_filepath_init();
    1005                 :            : 
    1006                 :          0 :     return gnc_userconfig_home_str.c_str();
    1007                 :            : }
    1008                 :            : 
    1009                 :            : static const bfs::path&
    1010                 :        205 : gnc_userdata_dir_as_path (void)
    1011                 :            : {
    1012                 :        205 :     if (gnc_userdata_home.empty())
    1013                 :            :         /* Don't create missing directories automatically except
    1014                 :            :          * if the target directory is the temporary directory. This
    1015                 :            :          * should be done properly at a higher level (in the gui
    1016                 :            :          * code most likely) very early in application startup.
    1017                 :            :          * This call is just a fallback to prevent the code from
    1018                 :            :          * crashing because no directories were configured. */
    1019                 :         34 :         gnc_filepath_init();
    1020                 :            : 
    1021                 :        205 :     return gnc_userdata_home;
    1022                 :            : }
    1023                 :            : 
    1024                 :            : static const bfs::path&
    1025                 :          0 : gnc_userconfig_dir_as_path (void)
    1026                 :            : {
    1027                 :          0 :     if (gnc_userdata_home.empty())
    1028                 :            :         /* Don't create missing directories automatically except
    1029                 :            :          * if the target directory is the temporary directory. This
    1030                 :            :          * should be done properly at a higher level (in the gui
    1031                 :            :          * code most likely) very early in application startup.
    1032                 :            :          * This call is just a fallback to prevent the code from
    1033                 :            :          * crashing because no directories were configured. */
    1034                 :          0 :         gnc_filepath_init();
    1035                 :            : 
    1036                 :          0 :     return gnc_userconfig_home;
    1037                 :            : }
    1038                 :            : 
    1039                 :          0 : gchar *gnc_file_path_absolute (const gchar *prefix, const gchar *relative)
    1040                 :            : {
    1041                 :          0 :     bfs::path path_relative (relative);
    1042                 :          0 :     path_relative.imbue (bfs_locale);
    1043                 :          0 :     bfs::path path_absolute;
    1044                 :          0 :     bfs::path path_head;
    1045                 :            : 
    1046                 :          0 :     if (prefix == nullptr)
    1047                 :            :     {
    1048                 :          0 :         const gchar *doc_dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
    1049                 :          0 :         if (doc_dir == nullptr)
    1050                 :          0 :             path_head = bfs::path (gnc_userdata_dir ()); // running as root maybe
    1051                 :            :         else
    1052                 :          0 :             path_head = bfs::path (doc_dir);
    1053                 :            : 
    1054                 :          0 :         path_head.imbue (bfs_locale);
    1055                 :          0 :         path_absolute = absolute (path_relative, path_head);
    1056                 :            :     }
    1057                 :            :     else
    1058                 :            :     {
    1059                 :          0 :         bfs::path path_head (prefix);
    1060                 :          0 :         path_head.imbue (bfs_locale);
    1061                 :          0 :         path_absolute = absolute (path_relative, path_head);
    1062                 :          0 :     }
    1063                 :          0 :     path_absolute.imbue (bfs_locale);
    1064                 :            : 
    1065                 :          0 :     return g_strdup (path_absolute.string().c_str());
    1066                 :          0 : }
    1067                 :            : 
    1068                 :            : /** @fn gchar * gnc_build_userdata_path (const gchar *filename)
    1069                 :            :  *  @brief Make a path to filename in the user's gnucash data directory.
    1070                 :            :  *
    1071                 :            :  * @param filename The name of the file
    1072                 :            :  *
    1073                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1074                 :            :  *  using g_free().
    1075                 :            :  */
    1076                 :            : 
    1077                 :            : gchar *
    1078                 :         38 : gnc_build_userdata_path (const gchar *filename)
    1079                 :            : {
    1080                 :         38 :     return g_strdup((gnc_userdata_dir_as_path() / filename).string().c_str());
    1081                 :            : }
    1082                 :            : 
    1083                 :            : /** @fn gchar * gnc_build_userconfig_path (const gchar *filename)
    1084                 :            :  *  @brief Make a path to filename in the user's configuration directory.
    1085                 :            :  *
    1086                 :            :  * @param filename The name of the file
    1087                 :            :  *
    1088                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1089                 :            :  *  using g_free().
    1090                 :            :  */
    1091                 :            : 
    1092                 :            : gchar *
    1093                 :          0 : gnc_build_userconfig_path (const gchar *filename)
    1094                 :            : {
    1095                 :          0 :     return g_strdup((gnc_userconfig_dir_as_path() / filename).string().c_str());
    1096                 :            : }
    1097                 :            : 
    1098                 :            : /* Test whether c is a valid character for a win32 file name.
    1099                 :            :  * If so return false, otherwise return true.
    1100                 :            :  */
    1101                 :            : static bool
    1102                 :       4149 : is_invalid_char (char c)
    1103                 :            : {
    1104                 :       4149 :     return (c == '/') || ( c == ':');
    1105                 :            : }
    1106                 :            : 
    1107                 :            : static bfs::path
    1108                 :        167 : gnc_build_userdata_subdir_path (const gchar *subdir, const gchar *filename)
    1109                 :            : {
    1110                 :        167 :     auto fn = std::string(filename);
    1111                 :            : 
    1112                 :        167 :     std::replace_if (fn.begin(), fn.end(), is_invalid_char, '_');
    1113                 :        334 :     auto result = (gnc_userdata_dir_as_path() / subdir) / fn;
    1114                 :        334 :     return result;
    1115                 :        167 : }
    1116                 :            : 
    1117                 :            : /** @fn gchar * gnc_build_book_path (const gchar *filename)
    1118                 :            :  *  @brief Make a path to filename in the book subdirectory of the user's configuration directory.
    1119                 :            :  *
    1120                 :            :  * @param filename The name of the file
    1121                 :            :  *
    1122                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1123                 :            :  *  using g_free().
    1124                 :            :  */
    1125                 :            : 
    1126                 :            : gchar *
    1127                 :          5 : gnc_build_book_path (const gchar *filename)
    1128                 :            : {
    1129                 :          5 :     auto path = gnc_build_userdata_subdir_path("books", filename).string();
    1130                 :         10 :     return g_strdup(path.c_str());
    1131                 :          5 : }
    1132                 :            : 
    1133                 :            : /** @fn gchar * gnc_build_translog_path (const gchar *filename)
    1134                 :            :  *  @brief Make a path to filename in the translog subdirectory of the user's configuration directory.
    1135                 :            :  *
    1136                 :            :  * @param filename The name of the file
    1137                 :            :  *
    1138                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1139                 :            :  *  using g_free().
    1140                 :            :  */
    1141                 :            : 
    1142                 :            : gchar *
    1143                 :          5 : gnc_build_translog_path (const gchar *filename)
    1144                 :            : {
    1145                 :          5 :     auto path = gnc_build_userdata_subdir_path("translog", filename).string();
    1146                 :         10 :     return g_strdup(path.c_str());
    1147                 :          5 : }
    1148                 :            : 
    1149                 :            : /** @fn gchar * gnc_build_data_path (const gchar *filename)
    1150                 :            :  *  @brief Make a path to filename in the data subdirectory of the user's configuration directory.
    1151                 :            :  *
    1152                 :            :  * @param filename The name of the file
    1153                 :            :  *
    1154                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1155                 :            :  *  using g_free().
    1156                 :            :  */
    1157                 :            : 
    1158                 :            : gchar *
    1159                 :        157 : gnc_build_data_path (const gchar *filename)
    1160                 :            : {
    1161                 :        157 :     auto path = gnc_build_userdata_subdir_path("data", filename).string();
    1162                 :        314 :     return g_strdup(path.c_str());
    1163                 :        157 : }
    1164                 :            : 
    1165                 :            : /** @fn gchar * gnc_build_scm_path (const gchar *filename)
    1166                 :            :  *  @brief Make a path to filename in the scm directory.
    1167                 :            :  *
    1168                 :            :  * @param filename The name of the file
    1169                 :            :  *
    1170                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1171                 :            :  *  using g_free().
    1172                 :            :  */
    1173                 :            : 
    1174                 :            : gchar *
    1175                 :         12 : gnc_build_scm_path (const gchar *filename)
    1176                 :            : {
    1177                 :         12 :     gchar *scmdir = gnc_path_get_scmdir ();
    1178                 :         12 :     gchar *result = g_build_filename (scmdir, filename, (gchar *)NULL);
    1179                 :         12 :     g_free (scmdir);
    1180                 :         12 :     return result;
    1181                 :            : }
    1182                 :            : 
    1183                 :            : /** @fn gchar * gnc_build_report_path (const gchar *filename)
    1184                 :            :  *  @brief Make a path to filename in the report directory.
    1185                 :            :  *
    1186                 :            :  * @param filename The name of the file
    1187                 :            :  *
    1188                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1189                 :            :  *  using g_free().
    1190                 :            :  */
    1191                 :            : 
    1192                 :            : gchar *
    1193                 :          0 : gnc_build_report_path (const gchar *filename)
    1194                 :            : {
    1195                 :          0 :     gchar *rptdir = gnc_path_get_reportdir ();
    1196                 :          0 :     gchar *result = g_build_filename (rptdir, filename, (gchar *)NULL);
    1197                 :          0 :     g_free (rptdir);
    1198                 :          0 :     return result;
    1199                 :            : }
    1200                 :            : 
    1201                 :            : /** @fn gchar * gnc_build_reports_path (const gchar *dirname)
    1202                 :            :  *  @brief Make a path to dirname in the reports directory.
    1203                 :            :  *
    1204                 :            :  * @param dirname The name of the subdirectory
    1205                 :            :  *
    1206                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1207                 :            :  *  using g_free().
    1208                 :            :  */
    1209                 :            : 
    1210                 :            : gchar *
    1211                 :          0 : gnc_build_reports_path (const gchar *dirname)
    1212                 :            : {
    1213                 :          0 :     gchar *rptsdir = gnc_path_get_reportsdir ();
    1214                 :          0 :     gchar *result = g_build_filename (rptsdir, dirname, (gchar *)NULL);
    1215                 :          0 :     g_free (rptsdir);
    1216                 :          0 :     return result;
    1217                 :            : }
    1218                 :            : 
    1219                 :            : /** @fn gchar * gnc_build_stdreports_path (const gchar *filename)
    1220                 :            :  *  @brief Make a path to filename in the standard reports directory.
    1221                 :            :  *
    1222                 :            :  * @param filename The name of the file
    1223                 :            :  *
    1224                 :            :  *  @return An absolute path. The returned string should be freed by the user
    1225                 :            :  *  using g_free().
    1226                 :            :  */
    1227                 :            : 
    1228                 :            : gchar *
    1229                 :          0 : gnc_build_stdreports_path (const gchar *filename)
    1230                 :            : {
    1231                 :          0 :     gchar *stdrptdir = gnc_path_get_stdreportsdir ();
    1232                 :          0 :     gchar *result = g_build_filename (stdrptdir, filename, (gchar *)NULL);
    1233                 :          0 :     g_free (stdrptdir);
    1234                 :          0 :     return result;
    1235                 :            : }
    1236                 :            : 
    1237                 :            : static gchar *
    1238                 :          0 : gnc_filepath_locate_file (const gchar *default_path, const gchar *name)
    1239                 :            : {
    1240                 :            :     gchar *fullname;
    1241                 :            : 
    1242                 :          0 :     g_return_val_if_fail (name != NULL, NULL);
    1243                 :            : 
    1244                 :          0 :     if (g_path_is_absolute (name))
    1245                 :          0 :         fullname = g_strdup (name);
    1246                 :          0 :     else if (default_path)
    1247                 :          0 :         fullname = g_build_filename (default_path, name, nullptr);
    1248                 :            :     else
    1249                 :          0 :         fullname = gnc_resolve_file_path (name);
    1250                 :            : 
    1251                 :          0 :     if (!g_file_test (fullname, G_FILE_TEST_IS_REGULAR))
    1252                 :            :     {
    1253                 :          0 :         g_warning ("Could not locate file %s", name);
    1254                 :          0 :         g_free (fullname);
    1255                 :          0 :         return NULL;
    1256                 :            :     }
    1257                 :            : 
    1258                 :          0 :     return fullname;
    1259                 :            : }
    1260                 :            : 
    1261                 :            : gchar *
    1262                 :          0 : gnc_filepath_locate_data_file (const gchar *name)
    1263                 :            : {
    1264                 :          0 :     gchar *pkgdatadir = gnc_path_get_pkgdatadir ();
    1265                 :          0 :     gchar *result = gnc_filepath_locate_file (pkgdatadir, name);
    1266                 :          0 :     g_free (pkgdatadir);
    1267                 :          0 :     return result;
    1268                 :            : }
    1269                 :            : 
    1270                 :            : gchar *
    1271                 :          0 : gnc_filepath_locate_pixmap (const gchar *name)
    1272                 :            : {
    1273                 :            :     gchar *default_path;
    1274                 :            :     gchar *fullname;
    1275                 :          0 :     gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
    1276                 :            : 
    1277                 :          0 :     default_path = g_build_filename (pkgdatadir, "pixmaps", nullptr);
    1278                 :          0 :     g_free(pkgdatadir);
    1279                 :          0 :     fullname = gnc_filepath_locate_file (default_path, name);
    1280                 :          0 :     g_free(default_path);
    1281                 :            : 
    1282                 :          0 :     return fullname;
    1283                 :            : }
    1284                 :            : 
    1285                 :            : gchar *
    1286                 :          0 : gnc_filepath_locate_ui_file (const gchar *name)
    1287                 :            : {
    1288                 :            :     gchar *default_path;
    1289                 :            :     gchar *fullname;
    1290                 :          0 :     gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
    1291                 :            : 
    1292                 :          0 :     default_path = g_build_filename (pkgdatadir, "ui", nullptr);
    1293                 :          0 :     g_free(pkgdatadir);
    1294                 :          0 :     fullname = gnc_filepath_locate_file (default_path, name);
    1295                 :          0 :     g_free(default_path);
    1296                 :            : 
    1297                 :          0 :     return fullname;
    1298                 :            : }
    1299                 :            : 
    1300                 :            : gchar *
    1301                 :          0 : gnc_filepath_locate_doc_file (const gchar *name)
    1302                 :            : {
    1303                 :          0 :     gchar *docdir = gnc_path_get_pkgdocdir ();
    1304                 :          0 :     gchar *result = gnc_filepath_locate_file (docdir, name);
    1305                 :          0 :     g_free (docdir);
    1306                 :          0 :     return result;
    1307                 :            : }
    1308                 :            : 
    1309                 :            : std::vector<EnvPaths>
    1310                 :          0 : gnc_list_all_paths ()
    1311                 :            : {
    1312                 :          0 :     if (gnc_userdata_home.empty())
    1313                 :          0 :         gnc_filepath_init ();
    1314                 :            : 
    1315                 :            :     return {
    1316                 :          0 :         { "GNC_USERDATA_DIR", gnc_userdata_home_str.c_str(), true},
    1317                 :          0 :         { "GNC_USERCONFIG_DIR", gnc_userconfig_home_str.c_str(), true },
    1318                 :          0 :         { "GNC_BIN", g_getenv ("GNC_BIN"), false },
    1319                 :          0 :         { "GNC_LIB", g_getenv ("GNC_LIB"), false },
    1320                 :          0 :         { "GNC_CONF", g_getenv ("GNC_CONF"), false },
    1321                 :          0 :         { "GNC_DATA", g_getenv ("GNC_DATA"), false },
    1322                 :          0 :     };
    1323                 :            : }
    1324                 :            : 
    1325                 :            : static const std::regex
    1326                 :            : backup_regex (".*[.](?:xac|gnucash)[.][0-9]{14}[.](?:xac|gnucash)$");
    1327                 :            : 
    1328                 :          0 : gboolean gnc_filename_is_backup (const char *filename)
    1329                 :            : {
    1330                 :          0 :     return std::regex_match (filename, backup_regex);
    1331                 :            : }
    1332                 :            : 
    1333                 :            : static const std::regex
    1334                 :            : datafile_regex (".*[.](?:xac|gnucash)$");
    1335                 :            : 
    1336                 :          0 : gboolean gnc_filename_is_datafile (const char *filename)
    1337                 :            : {
    1338                 :          0 :     return !gnc_filename_is_backup (filename) &&
    1339                 :          0 :         std::regex_match (filename, datafile_regex);
    1340                 :            : }
    1341                 :            : 
    1342                 :            : std::ofstream
    1343                 :          0 : gnc_open_filestream(const char* path)
    1344                 :            : {
    1345                 :          0 :     bfs::path bfs_path(path, cvt);
    1346                 :          0 :     bfs_path.imbue(bfs_locale);
    1347                 :          0 :     return std::ofstream(bfs_path.c_str());
    1348                 :          0 : }
    1349                 :            : /* =============================== END OF FILE ========================== */

Generated by: LCOV version 1.14