From ae75bc963f18fd0b9a3a8333a9f2a93f503449df Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Tue, 22 Aug 2017 16:33:34 +0200 Subject: [PATCH] Rewrite several file path routines to use boost::filesystem This is a basis for moving .gnucash to a more modern location for application specific user data (following the xdg spec). --- CMakeLists.txt | 2 +- gnucash/gnome-utils/gnc-file.c | 4 +- gnucash/gnome-utils/gnc-gnome-utils.c | 14 +- gnucash/gnome/dialog-print-check.c | 6 +- gnucash/gnucash-bin.c | 4 +- .../import-export/qif-imp/qif-guess-map.scm | 4 +- .../report-gnome/gnc-plugin-page-report.c | 2 +- .../report/report-system/eguile-utilities.scm | 2 +- gnucash/report/report-system/gnc-report.c | 6 +- .../report/report-system/html-style-sheet.scm | 2 +- .../report-system/report-collectors.scm | 1 - libgnucash/app-utils/gfec.c | 2 +- libgnucash/app-utils/gfec.h | 2 +- libgnucash/app-utils/gnc-exp-parser.c | 2 +- libgnucash/core-utils/CMakeLists.txt | 4 +- libgnucash/core-utils/Makefile.am | 8 +- libgnucash/core-utils/core-utils.i | 4 +- libgnucash/core-utils/core-utils.scm | 2 +- ...ilepath-utils.c => gnc-filepath-utils.cpp} | 316 ++++++++---------- libgnucash/core-utils/gnc-filepath-utils.h | 4 +- libgnucash/core-utils/test/CMakeLists.txt | 5 +- libgnucash/core-utils/test/Makefile.am | 10 +- .../core-utils/test/test-resolve-file-path.c | 25 +- .../test/test-usr-conf-dir-invalid-home.c | 110 ++++++ .../core-utils/test/test-usr-conf-dir.c | 121 +++++++ po/POTFILES.in | 2 +- 26 files changed, 437 insertions(+), 227 deletions(-) rename libgnucash/core-utils/{gnc-filepath-utils.c => gnc-filepath-utils.cpp} (69%) create mode 100644 libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c create mode 100644 libgnucash/core-utils/test/test-usr-conf-dir.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ec44d6863..f017a85095 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,7 +438,7 @@ SET (Boost_FIND_QUIETLY ON) IF (NOT DEFINED ${BOOST_ROOT}) SET(BOOST_ROOT $ENV{BOOST_ROOT}) ENDIF() -FIND_PACKAGE (Boost 1.54.0 REQUIRED COMPONENTS date_time regex locale) +FIND_PACKAGE (Boost 1.54.0 REQUIRED COMPONENTS date_time regex locale filesystem) IF (Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) diff --git a/gnucash/gnome-utils/gnc-file.c b/gnucash/gnome-utils/gnc-file.c index 6682145439..680ebfc381 100644 --- a/gnucash/gnome-utils/gnc-file.c +++ b/gnucash/gnome-utils/gnc-file.c @@ -430,7 +430,7 @@ show_session_error (QofBackendError io_error, fmt = _("You attempted to save in\n%s\nor a subdirectory thereof. " "This is not allowed as %s reserves that directory for internal use.\n\n" "Please try again in a different directory."); - gnc_error_dialog (parent, fmt, gnc_dotgnucash_dir(), PACKAGE_NAME); + gnc_error_dialog (parent, fmt, gnc_userdata_dir(), PACKAGE_NAME); break; case ERR_SQL_DB_TOO_OLD: @@ -1139,7 +1139,7 @@ check_file_path (const char *path) { /* Remember the directory as the default. */ gchar *dir = g_path_get_dirname(path); - const gchar *dotgnucash = gnc_dotgnucash_dir(); + const gchar *dotgnucash = gnc_userdata_dir(); char *dirpath = dir; /* Prevent user from storing file in GnuCash' private configuration diff --git a/gnucash/gnome-utils/gnc-gnome-utils.c b/gnucash/gnome-utils/gnc-gnome-utils.c index 845f63e0d1..7687eadc89 100644 --- a/gnucash/gnome-utils/gnc-gnome-utils.c +++ b/gnucash/gnome-utils/gnc-gnome-utils.c @@ -719,16 +719,16 @@ gnc_gui_init(void) gnc_window_set_progressbar_window (GNC_WINDOW(main_window)); #ifdef MAC_INTEGRATION - map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME); + map = gnc_build_userdata_path(ACCEL_MAP_NAME); if (!g_file_test (map, G_FILE_TEST_EXISTS)) { - g_free (map); - data_dir = gnc_path_get_pkgdatadir(); - map = g_build_filename(data_dir, "ui", "osx_accel_map", NULL); - g_free(data_dir); + g_free (map); + data_dir = gnc_path_get_pkgdatadir(); + map = g_build_filename(data_dir, "ui", "osx_accel_map", NULL); + g_free(data_dir); } #else - map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME); + map = gnc_build_userdata_path(ACCEL_MAP_NAME); #endif /* MAC_INTEGRATION */ gtk_accel_map_load(map); g_free(map); @@ -765,7 +765,7 @@ gnc_gui_shutdown (void) if (gnome_is_running && !gnome_is_terminating) { gnome_is_terminating = TRUE; - map = gnc_build_dotgnucash_path(ACCEL_MAP_NAME); + map = gnc_build_userdata_path(ACCEL_MAP_NAME); gtk_accel_map_save(map); g_free(map); gtk_main_quit(); diff --git a/gnucash/gnome/dialog-print-check.c b/gnucash/gnome/dialog-print-check.c index 0c7f0f20e2..8cd00187cd 100644 --- a/gnucash/gnome/dialog-print-check.c +++ b/gnucash/gnome/dialog-print-check.c @@ -799,7 +799,7 @@ pcd_save_custom_data(PrintCheckDialog *pcd, const gchar *title) pcd->splits_account_x, pcd->splits_account_y); filename = g_strconcat(title, CHECK_NAME_EXTENSION, NULL); - pathname = g_build_filename(gnc_dotgnucash_dir(), CHECK_FMT_DIR, + pathname = g_build_filename(gnc_userdata_dir(), CHECK_FMT_DIR, filename, NULL); if (gnc_key_file_save_to_file(pathname, key_file, &error)) @@ -1558,7 +1558,7 @@ read_formats(PrintCheckDialog *pcd, GtkListStore *store) g_free(dirname); g_free(pkgdatadir); - dirname = gnc_build_dotgnucash_path(CHECK_FMT_DIR); + dirname = gnc_build_userdata_path(CHECK_FMT_DIR); /* Translators: This is a directory name. It may be presented to * the user to indicate that some data file was defined by a * user herself. */ @@ -1901,7 +1901,7 @@ read_image (const gchar *filename) if (!g_file_test(tmp_name, G_FILE_TEST_EXISTS)) { g_free(tmp_name); - dirname = gnc_build_dotgnucash_path(CHECK_FMT_DIR); + dirname = gnc_build_userdata_path(CHECK_FMT_DIR); tmp_name = g_build_filename(dirname, filename, (char *)NULL); g_free(dirname); } diff --git a/gnucash/gnucash-bin.c b/gnucash/gnucash-bin.c index dbacffd31d..5bc7063c1c 100644 --- a/gnucash/gnucash-bin.c +++ b/gnucash/gnucash-bin.c @@ -344,7 +344,7 @@ try_load_config_array(const gchar *fns[]) for (i = 0; fns[i]; i++) { - filename = gnc_build_dotgnucash_path(fns[i]); + filename = gnc_build_userdata_path(fns[i]); if (gfec_try_load(filename)) { g_free(filename); @@ -703,7 +703,7 @@ gnc_log_init() { gchar *log_config_filename; - log_config_filename = gnc_build_dotgnucash_path("log.conf"); + log_config_filename = gnc_build_userdata_path("log.conf"); if (g_file_test(log_config_filename, G_FILE_TEST_EXISTS)) qof_log_parse_log_config(log_config_filename); g_free(log_config_filename); diff --git a/gnucash/import-export/qif-imp/qif-guess-map.scm b/gnucash/import-export/qif-imp/qif-guess-map.scm index 554921c545..12802086e6 100644 --- a/gnucash/import-export/qif-imp/qif-guess-map.scm +++ b/gnucash/import-export/qif-imp/qif-guess-map.scm @@ -91,7 +91,7 @@ (false-if-exception (read))) - (let* ((pref-filename (gnc-build-dotgnucash-path "qif-accounts-map")) + (let* ((pref-filename (gnc-build-userdata-path "qif-accounts-map")) (results '())) ;; Get the user's saved mappings. @@ -293,7 +293,7 @@ ;; This procedure does all the work. We'll define it, then call it safely. (define (private-save) - (with-output-to-file (gnc-build-dotgnucash-path "qif-accounts-map") + (with-output-to-file (gnc-build-userdata-path "qif-accounts-map") (lambda () (display ";;; qif-accounts-map") (newline) diff --git a/gnucash/report/report-gnome/gnc-plugin-page-report.c b/gnucash/report/report-gnome/gnc-plugin-page-report.c index 97121935ef..3493600ce1 100644 --- a/gnucash/report/report-gnome/gnc-plugin-page-report.c +++ b/gnucash/report/report-gnome/gnc-plugin-page-report.c @@ -1116,7 +1116,7 @@ gnc_plugin_page_report_constr_init(GncPluginPageReport *plugin_page, gint report GncPluginPage *parent; gboolean use_new; gchar *name; - gchar *saved_reports_path = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE); + gchar *saved_reports_path = gnc_build_userdata_path (SAVED_REPORTS_FILE); gchar *report_save_str = g_strdup_printf ( _("Update the current report's saved configuration. " "The report will be saved in the file %s. "), saved_reports_path); diff --git a/gnucash/report/report-system/eguile-utilities.scm b/gnucash/report/report-system/eguile-utilities.scm index a582caee5e..5eb12e1e56 100644 --- a/gnucash/report/report-system/eguile-utilities.scm +++ b/gnucash/report/report-system/eguile-utilities.scm @@ -82,7 +82,7 @@ ;; Then look in Gnucash's standard report directory. ;; If no file is found, returns just 'fname' for use in error messages. ;; Note: this has been tested on Linux and Windows Vista so far... - (let* ((userpath (gnc-build-dotgnucash-path fname)) + (let* ((userpath (gnc-build-userdata-path fname)) (syspath (gnc-build-report-path fname))) ; make sure there's a trailing delimiter (if (access? userpath R_OK) diff --git a/gnucash/report/report-system/gnc-report.c b/gnucash/report/report-system/gnc-report.c index 763afad645..04b7164f2c 100644 --- a/gnucash/report/report-system/gnc-report.c +++ b/gnucash/report/report-system/gnc-report.c @@ -279,8 +279,8 @@ gnc_saved_reports_write_internal (const gchar *file, const gchar *contents, gboo gboolean gnc_saved_reports_backup (void) { gboolean success = FALSE; - gchar *saved_rpts_path = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE); - gchar *saved_rpts_bkp_path = g_strconcat (saved_rpts_path, "-backup", NULL); + gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE); + gchar *saved_rpts_bkp_path = gnc_build_userdata_path (SAVED_REPORTS_FILE "-backup"); gchar *contents = NULL; GError *save_error = NULL; @@ -310,7 +310,7 @@ gboolean gnc_saved_reports_write_to_file (const gchar* report_def, gboolean overwrite) { gboolean success = FALSE; - gchar *saved_rpts_path = gnc_build_dotgnucash_path (SAVED_REPORTS_FILE); + gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE); if (report_def) { diff --git a/gnucash/report/report-system/html-style-sheet.scm b/gnucash/report/report-system/html-style-sheet.scm index fc7f60b15c..fd7c3cc15d 100644 --- a/gnucash/report/report-system/html-style-sheet.scm +++ b/gnucash/report/report-system/html-style-sheet.scm @@ -127,7 +127,7 @@ (record-accessor 'style)) (define gnc:current-saved-stylesheets - (gnc-build-dotgnucash-path "stylesheets-2.0")) + (gnc-build-userdata-path "stylesheets-2.0")) (define (gnc:save-style-sheet-options) (let ((port (false-if-exception diff --git a/gnucash/report/report-system/report-collectors.scm b/gnucash/report/report-system/report-collectors.scm index ad3ae53692..78c9992cf2 100644 --- a/gnucash/report/report-system/report-collectors.scm +++ b/gnucash/report/report-system/report-collectors.scm @@ -30,7 +30,6 @@ (use-modules (gnucash report report-system)) (use-modules (gnucash app-utils)) (use-modules (gnucash engine)) -(use-modules (sw_engine)) (use-modules (gnucash report report-system collectors)) (use-modules (gnucash report report-system list-extras)) diff --git a/libgnucash/app-utils/gfec.c b/libgnucash/app-utils/gfec.c index b27c9119ad..631fcdd52c 100644 --- a/libgnucash/app-utils/gfec.c +++ b/libgnucash/app-utils/gfec.c @@ -228,7 +228,7 @@ error_handler(const char *msg) } gboolean -gfec_try_load(gchar *fn) +gfec_try_load(const gchar *fn) { g_debug("looking for %s", fn); if (g_file_test(fn, G_FILE_TEST_EXISTS)) diff --git a/libgnucash/app-utils/gfec.h b/libgnucash/app-utils/gfec.h index 16852cad2b..b1ecc30b68 100644 --- a/libgnucash/app-utils/gfec.h +++ b/libgnucash/app-utils/gfec.h @@ -18,6 +18,6 @@ typedef void (*gfec_error_handler)(const char *error_message); SCM gfec_eval_file(const char *file, gfec_error_handler error_handler); SCM gfec_eval_string(const char *str, gfec_error_handler error_handler); SCM gfec_apply(SCM proc, SCM arglist, gfec_error_handler error_handler); -gboolean gfec_try_load(gchar *fn); +gboolean gfec_try_load(const gchar *fn); #endif diff --git a/libgnucash/app-utils/gnc-exp-parser.c b/libgnucash/app-utils/gnc-exp-parser.c index c4cae40617..b3aa68b289 100644 --- a/libgnucash/app-utils/gnc-exp-parser.c +++ b/libgnucash/app-utils/gnc-exp-parser.c @@ -61,7 +61,7 @@ static gboolean parser_inited = FALSE; static gchar * gnc_exp_parser_filname (void) { - return gnc_build_dotgnucash_path("expressions-2.0"); + return gnc_build_userdata_path("expressions-2.0"); } void diff --git a/libgnucash/core-utils/CMakeLists.txt b/libgnucash/core-utils/CMakeLists.txt index 78474460e8..e4b5148d45 100644 --- a/libgnucash/core-utils/CMakeLists.txt +++ b/libgnucash/core-utils/CMakeLists.txt @@ -16,7 +16,7 @@ SET (core_utils_SOURCES binreloc.c gnc-prefs.c gnc-environment.c - gnc-filepath-utils.c + gnc-filepath-utils.cpp gnc-gkeyfile-utils.c gnc-glib-utils.c gnc-guile-utils.c @@ -117,7 +117,7 @@ SET(core_utils_noinst_HEADERS ) SET(core_utils_ALL_SOURCES ${core_utils_SOURCES} ${core_utils_noinst_HEADERS}) -SET(core_utils_ALL_LIBRARIES ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS} ${GTK_MAC_LDFLAGS}) +SET(core_utils_ALL_LIBRARIES ${Boost_LIBRARIES} -lboost_filesystem ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS} ${GTK_MAC_LDFLAGS}) SET(core_utils_ALL_INCLUDES ${CMAKE_SOURCE_DIR}/common ${CMAKE_BINARY_DIR}/common diff --git a/libgnucash/core-utils/Makefile.am b/libgnucash/core-utils/Makefile.am index 9733c60e7a..c9830f663f 100644 --- a/libgnucash/core-utils/Makefile.am +++ b/libgnucash/core-utils/Makefile.am @@ -8,7 +8,7 @@ libgnc_core_utils_la_SOURCES = \ binreloc.c \ gnc-prefs.c \ gnc-environment.c \ - gnc-filepath-utils.c \ + gnc-filepath-utils.cpp \ gnc-gkeyfile-utils.c \ gnc-glib-utils.c \ gnc-guile-utils.c \ @@ -21,7 +21,8 @@ libgnc_core_utils_la_LIBADD = \ ${GUILE_LIBS} \ ${GLIB_LIBS} \ ${BINRELOC_LIBS} \ - ${GTK_MAC_LIBS} + ${GTK_MAC_LIBS} \ + ${BOOST_LDFLAGS} -lboost_filesystem noinst_HEADERS = \ @@ -58,7 +59,8 @@ AM_CPPFLAGS = \ ${GLIB_CFLAGS} \ ${GTK_MAC_CFLAGS} \ -I${top_builddir}/common \ - -I${top_srcdir}/common + -I${top_srcdir}/common \ + $(BOOST_CPPFLAGS) gncscmmoddir = ${GNC_SCM_INSTALL_DIR}/gnucash gncscmmod_DATA = core-utils.scm diff --git a/libgnucash/core-utils/core-utils.i b/libgnucash/core-utils/core-utils.i index e92fae4723..d8fd67df4d 100644 --- a/libgnucash/core-utils/core-utils.i +++ b/libgnucash/core-utils/core-utils.i @@ -65,8 +65,8 @@ gchar * gnc_path_get_stdreportsdir(void); %newobject gnc_path_find_localized_html_file; gchar * gnc_path_find_localized_html_file(const gchar *); -%newobject gnc_build_dotgnucash_path; -gchar * gnc_build_dotgnucash_path(const gchar *); +%newobject gnc_build_userdata_path; +gchar * gnc_build_userdata_path(const gchar *); gchar * gnc_build_report_path(const gchar *); gchar * gnc_build_stdreports_path(const gchar *); diff --git a/libgnucash/core-utils/core-utils.scm b/libgnucash/core-utils/core-utils.scm index 8fd2869825..645228b033 100644 --- a/libgnucash/core-utils/core-utils.scm +++ b/libgnucash/core-utils/core-utils.scm @@ -41,7 +41,7 @@ (re-export gnc-path-get-bindir) (re-export gnc-path-get-stdreportsdir) (re-export gnc-path-find-localized-html-file) -(re-export gnc-build-dotgnucash-path) +(re-export gnc-build-userdata-path) (re-export gnc-build-report-path) (re-export gnc-build-stdreports-path) (re-export gnc-utf8?) diff --git a/libgnucash/core-utils/gnc-filepath-utils.c b/libgnucash/core-utils/gnc-filepath-utils.cpp similarity index 69% rename from libgnucash/core-utils/gnc-filepath-utils.c rename to libgnucash/core-utils/gnc-filepath-utils.cpp index 477cfb0d5e..045674c4f8 100644 --- a/libgnucash/core-utils/gnc-filepath-utils.c +++ b/libgnucash/core-utils/gnc-filepath-utils.cpp @@ -26,6 +26,7 @@ * @author Copyright (c) 2000 Dave Peticolas */ +extern "C" { #include "config.h" #include @@ -55,6 +56,12 @@ #include #define PATH_MAX MAXPATHLEN #endif +} + +#include + +namespace bfs = boost::filesystem; +namespace bst = boost::system; /** * Scrubs a filename by changing "strange" chars (e.g. those that are not @@ -104,7 +111,7 @@ check_path_return_if_valid(gchar *path) * * Otherwise, assume that filefrag is a well-formed relative path and * try to find a file with its path relative to - * \li the current working directory, + * \li the current working directory, * \li the installed system-wide data directory (e.g., /usr/local/share/gnucash), * \li the installed system configuration directory (e.g., /usr/local/etc/gnucash), * \li or in the user's configuration directory (e.g., $HOME/.gnucash/data) @@ -164,7 +171,7 @@ gnc_resolve_file_path (const gchar * filefrag) return fullpath; /* Look in the users config dir (e.g. $HOME/.gnucash/data) */ - fullpath = gnc_build_data_path(filefrag); + fullpath = g_strdup(gnc_build_data_path(filefrag)); if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR)) return fullpath; @@ -196,7 +203,7 @@ gnc_path_find_localized_html_file_internal (const gchar * file_name) const gchar *env_doc_path = g_getenv("GNC_DOC_PATH"); const gchar *default_dirs[] = { - gnc_build_dotgnucash_path ("html"), + gnc_build_userdata_path ("html"), gnc_path_get_pkgdocdir (), gnc_path_get_pkgdatadir (), NULL @@ -292,190 +299,156 @@ gnc_path_find_localized_html_file (const gchar *file_name) } /* ====================================================================== */ - /** @brief Check that the supplied directory path exists, is a directory, and * that the user has adequate permissions to use it. * * @param dirname The path to check */ -static gboolean -gnc_validate_directory (const gchar *dirname, gboolean create, gchar **msg) +static bool +gnc_validate_directory (const bfs::path &dirname, bool create) { - GStatBuf statbuf; - gint rc; + if (dirname.empty()) + return false; - *msg = NULL; - rc = g_stat (dirname, &statbuf); - if (rc) + if (!bfs::exists(dirname) && (!create)) + return false; + + /* Optionally create directories if they don't exist yet + * Note this will do nothing if the directory and its + * parents already exist, but will fail if the path + * points to a file or a softlink. So it serves as a test + * for that as well. + */ + bfs::create_directories(dirname); + + auto d = bfs::directory_entry (dirname); + auto perms = d.status().permissions(); + + /* On Windows only write permission will be checked. + * So strictly speaking we'd need two error messages here depending + * on the platform. For simplicity this detail is glossed over though. */ + if ((perms & bfs::owner_all) != bfs::owner_all) + throw (bfs::filesystem_error( + std::string(_("Insufficient permissions, at least write and access permissions required: ")) + + dirname.c_str(), dirname, + bst::error_code(bst::errc::permission_denied, bst::generic_category()))); + + return true; +} + +static auto usr_conf_dir = bfs::path(); + +static void +gnc_filepath_init() +{ + auto try_home_dir = true; + auto env_var = g_getenv("GNC_DOT_DIR"); + if (env_var) + usr_conf_dir = env_var; + + if (!usr_conf_dir.empty()) { - switch (errno) + try { - case ENOENT: - if (create) - { - rc = g_mkdir (dirname, -#ifdef G_OS_WIN32 - 0 /* The mode argument is ignored on windows */ -#else - S_IRWXU /* perms = S_IRWXU = 0700 */ -#endif - ); - if (rc) - { - *msg = g_strdup_printf( - _("An error occurred while creating the directory:\n" - " %s\n" - "Please correct the problem and restart GnuCash.\n" - "The reported error was '%s' (errno %d).\n"), - dirname, g_strerror(errno) ? g_strerror(errno) : "", errno); - return FALSE; - } - } - else - { - *msg = g_strdup_printf( - _("Note: the directory\n" - " %s\n" - "doesn't exist. This is however not fatal.\n"), - dirname); - return FALSE; - } - g_stat (dirname, &statbuf); - break; - - case EACCES: - *msg = g_strdup_printf( - _("The directory\n" - " %s\n" - "exists but cannot be accessed. This program \n" - "must have full access (read/write/execute) to \n" - "the directory in order to function properly.\n"), - dirname); - return FALSE; - - case ENOTDIR: - *msg = g_strdup_printf( - _("The path\n" - " %s\n" - "exists but it is not a directory. Please delete\n" - "the file and start GnuCash again.\n"), - dirname); - return FALSE; - - default: - *msg = g_strdup_printf( - _("An unknown error occurred when validating that the\n" - " %s\n" - "directory exists and is usable. Please correct the\n" - "problem and restart GnuCash. The reported error \n" - "was '%s' (errno %d)."), - dirname, g_strerror(errno) ? g_strerror(errno) : "", errno); - return FALSE; + gnc_validate_directory(usr_conf_dir, true); + try_home_dir = false; + } + catch (const bfs::filesystem_error& ex) + { + g_warning("%s is not a suitable base directory for the user configuration." + "Trying home directory instead.\nThe failure is\n%s", + usr_conf_dir.c_str(), ex.what()); } } - if ((statbuf.st_mode & S_IFDIR) != S_IFDIR) + if (try_home_dir) { - *msg = g_strdup_printf( - _("The path\n" - " %s\n" - "exists but it is not a directory. Please delete\n" - "the file and start GnuCash again.\n"), - dirname); - return FALSE; + usr_conf_dir = g_get_home_dir(); + try + { + if (!gnc_validate_directory(usr_conf_dir, false)) + usr_conf_dir = g_get_tmp_dir(); + } + catch (const bfs::filesystem_error& ex) + { + g_warning("Cannot find suitable home directory. Using tmp directory instead.\n" + "The failure is\n%s", ex.what()); + usr_conf_dir = g_get_tmp_dir(); + } } -#ifndef G_OS_WIN32 - /* The mode argument is ignored on windows anyway */ - if ((statbuf.st_mode & S_IRWXU) != S_IRWXU) - { - *msg = g_strdup_printf( - _("The permissions are wrong on the directory\n" - " %s\n" - "They must be at least 'rwx' for the user.\n"), - dirname); - return FALSE; - } -#endif + g_assert(!usr_conf_dir.empty()); - return TRUE; + usr_conf_dir /= ".gnucash"; + + if (!gnc_validate_directory(usr_conf_dir, true)) + { + g_warning("Cannot find suitable .gnucash directory in home directory. Using tmp directory instead."); + + usr_conf_dir = g_get_tmp_dir(); + g_assert(!usr_conf_dir.empty()); + usr_conf_dir /= ".gnucash"; + /* This may throw and end the program! */ + gnc_validate_directory(usr_conf_dir, true); + } + + /* Since we're in code that is only executed once.... + * Note these calls may throw and end the program! */ + gnc_validate_directory(usr_conf_dir / "books", true); + gnc_validate_directory(usr_conf_dir / "checks", true); + gnc_validate_directory(usr_conf_dir / "translog", true); } -/** @fn const gchar * gnc_dotgnucash_dir () +/** @fn const gchar * gnc_userdata_dir () * @brief Ensure that the user's configuration directory exists and is minimally populated. * * Note that the default path is $HOME/.gnucash; This can be changed * by the environment variable $GNC_DOT_DIR. * - * @return An absolute path to the configuration directory + * @return An absolute path to the configuration directory. This string is + * by the gnc_filepath_utils code and should not be freed by the user. */ const gchar * -gnc_dotgnucash_dir (void) +gnc_userdata_dir (void) { - static gchar *dotgnucash = NULL; - gchar *tmp_dir; - gchar *errmsg = NULL; + if (usr_conf_dir.empty()) + gnc_filepath_init(); - if (dotgnucash) - return dotgnucash; - - dotgnucash = g_strdup(g_getenv("GNC_DOT_DIR")); - - if (!dotgnucash) - { - const gchar *home = g_get_home_dir(); - if (!home || !gnc_validate_directory(home, FALSE, &errmsg)) - { - g_free(errmsg); - g_warning("Cannot find suitable home directory. Using tmp directory instead."); - home = g_get_tmp_dir(); - } - g_assert(home); - - dotgnucash = g_build_filename(home, ".gnucash", (gchar *)NULL); - } - if (!gnc_validate_directory(dotgnucash, TRUE, &errmsg)) - { - const gchar *tmp = g_get_tmp_dir(); - g_free(errmsg); - g_free(dotgnucash); - g_warning("Cannot find suitable .gnucash directory in home directory. Using tmp directory instead."); - g_assert(tmp); - - dotgnucash = g_build_filename(tmp, ".gnucash", (gchar *)NULL); - - if (!gnc_validate_directory(dotgnucash, TRUE, &errmsg)) - exit(1); - } - - /* Since we're in code that is only executed once.... */ - tmp_dir = g_build_filename(dotgnucash, "books", (gchar *)NULL); - if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg)) - exit(1); - g_free(tmp_dir); - tmp_dir = g_build_filename(dotgnucash, "checks", (gchar *)NULL); - if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg)) - exit(1); - g_free(tmp_dir); - tmp_dir = g_build_filename(dotgnucash, "translog", (gchar *)NULL); - if (!gnc_validate_directory(tmp_dir, TRUE, &errmsg)) - exit(1); - g_free(tmp_dir); - - return dotgnucash; + return usr_conf_dir.c_str(); } -/** @fn gchar * gnc_build_dotgnucash_path (const gchar *filename) +static const bfs::path& +gnc_userdata_dir_as_path (void) +{ + if (usr_conf_dir.empty()) + gnc_filepath_init(); + + return usr_conf_dir; +} + +/** @fn gchar * gnc_build_userdata_path (const gchar *filename) * @brief Make a path to filename in the user's configuration directory. * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * -gnc_build_dotgnucash_path (const gchar *filename) +gnc_build_userdata_path (const gchar *filename) { - return g_build_filename(gnc_dotgnucash_dir(), filename, (gchar *)NULL); + return g_strdup((gnc_userdata_dir_as_path() / filename).c_str()); +} + +static bfs::path +gnc_build_userdata_subdir_path (const gchar *subdir, const gchar *filename) +{ + gchar* filename_dup = g_strdup(filename); + + scrub_filename(filename_dup); + auto result = (gnc_userdata_dir_as_path() / subdir) / filename_dup; + g_free(filename_dup); + return result; } /** @fn gchar * gnc_build_book_path (const gchar *filename) @@ -483,20 +456,14 @@ gnc_build_dotgnucash_path (const gchar *filename) * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * gnc_build_book_path (const gchar *filename) { - gchar* filename_dup = g_strdup(filename); - gchar* result = NULL; - - scrub_filename(filename_dup); - result = g_build_filename(gnc_dotgnucash_dir(), "books", - filename_dup, (gchar *)NULL); - g_free(filename_dup); - return result; + return g_strdup(gnc_build_userdata_subdir_path("books", filename).c_str()); } /** @fn gchar * gnc_build_translog_path (const gchar *filename) @@ -504,20 +471,14 @@ gnc_build_book_path (const gchar *filename) * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * gnc_build_translog_path (const gchar *filename) { - gchar* filename_dup = g_strdup(filename); - gchar* result = NULL; - - scrub_filename(filename_dup); - result = g_build_filename(gnc_dotgnucash_dir(), "translog", - filename_dup, (gchar *)NULL); - g_free(filename_dup); - return result; + return g_strdup(gnc_build_userdata_subdir_path("translog", filename).c_str()); } /** @fn gchar * gnc_build_data_path (const gchar *filename) @@ -525,19 +486,14 @@ gnc_build_translog_path (const gchar *filename) * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * gnc_build_data_path (const gchar *filename) { - gchar* filename_dup = g_strdup(filename); - gchar* result; - - scrub_filename(filename_dup); - result = g_build_filename(gnc_dotgnucash_dir(), "data", filename_dup, (gchar *)NULL); - g_free(filename_dup); - return result; + return g_strdup(gnc_build_userdata_subdir_path("data", filename).c_str()); } /** @fn gchar * gnc_build_report_path (const gchar *filename) @@ -545,7 +501,8 @@ gnc_build_data_path (const gchar *filename) * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * @@ -560,7 +517,8 @@ gnc_build_report_path (const gchar *filename) * * @param filename The name of the file * - * @return An absolute path. + * @return An absolute path. The returned string should be freed by the user + * using g_free(). */ gchar * diff --git a/libgnucash/core-utils/gnc-filepath-utils.h b/libgnucash/core-utils/gnc-filepath-utils.h index 8f1c2dc122..0e8adfa25f 100644 --- a/libgnucash/core-utils/gnc-filepath-utils.h +++ b/libgnucash/core-utils/gnc-filepath-utils.h @@ -75,8 +75,8 @@ gchar *gnc_resolve_file_path (const gchar *filefrag); */ gchar *gnc_path_find_localized_html_file (const gchar *file_name); -const gchar *gnc_dotgnucash_dir (void); -gchar *gnc_build_dotgnucash_path (const gchar *filename); +const gchar *gnc_userdata_dir (void); +gchar *gnc_build_userdata_path (const gchar *filename); gchar *gnc_build_book_path (const gchar *filename); gchar *gnc_build_translog_path (const gchar *filename); gchar *gnc_build_data_path (const gchar *filename); diff --git a/libgnucash/core-utils/test/CMakeLists.txt b/libgnucash/core-utils/test/CMakeLists.txt index 15bf1a4ffa..1c7d8057e6 100644 --- a/libgnucash/core-utils/test/CMakeLists.txt +++ b/libgnucash/core-utils/test/CMakeLists.txt @@ -14,5 +14,8 @@ ENDMACRO() ADD_CORE_UTILS_TEST(test-gnc-glib-utils test-gnc-glib-utils.c) ADD_CORE_UTILS_TEST(test-resolve-file-path test-resolve-file-path.c) +ADD_CORE_UTILS_TEST(test-usr-conf-dir test-usr-conf-dir.c) +ADD_CORE_UTILS_TEST(test-usr-conf-dir-invalid-home test-usr-conf-dir-invalid-home.c) -SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c test-resolve-file-path.c) +SET_DIST_LIST(test_core_utils_DIST CMakeLists.txt Makefile.am test-gnc-glib-utils.c + test-resolve-file-path.c test-usr-conf-dir.c test-usr-conf-dir-invalid-home.c) diff --git a/libgnucash/core-utils/test/Makefile.am b/libgnucash/core-utils/test/Makefile.am index c88ace0bc5..2bcbdb3d07 100644 --- a/libgnucash/core-utils/test/Makefile.am +++ b/libgnucash/core-utils/test/Makefile.am @@ -10,20 +10,24 @@ AM_CPPFLAGS = \ -I${top_srcdir}/libgnucash/core-utils \ -I${top_srcdir}/libgnucash/engine \ ${GUILE_CFLAGS} \ - ${GLIB_CFLAGS} + ${GLIB_CFLAGS} \ + $(BOOST_CPPFLAGS) LDADD = \ ../libgnc-core-utils.la \ ${top_builddir}/common/test-core/libtest-core.la \ - ${GLIB_LIBS} + ${GLIB_LIBS} \ + ${BOOST_LDFLAGS} # these tests are ordered kind more or less in the order # that they should be executed, with more basic tests coming first. # TESTS = \ + test-gnc-glib-utils \ test-resolve-file-path \ - test-gnc-glib-utils + test-usr-conf-dir \ + test-usr-conf-dir-invalid-home GNC_TEST_DEPS = \ --library-dir ${top_builddir}/libgnucash/core-utils diff --git a/libgnucash/core-utils/test/test-resolve-file-path.c b/libgnucash/core-utils/test/test-resolve-file-path.c index b79d1360d3..ada10a7a19 100644 --- a/libgnucash/core-utils/test/test-resolve-file-path.c +++ b/libgnucash/core-utils/test/test-resolve-file-path.c @@ -29,16 +29,16 @@ #include "test-stuff.h" #include "gnc-filepath-utils.h" -struct test_strings_struct +struct relpath_strings_struct { char *input; char *output; int prefix_home; }; -typedef struct test_strings_struct test_strings; +typedef struct relpath_strings_struct relpath_strings; -test_strings strs[] = +relpath_strings strs[] = { { G_DIR_SEPARATOR_S ".gnucash" G_DIR_SEPARATOR_S "test-account-name", @@ -57,6 +57,18 @@ int main(int argc, char **argv) { int i; + char *home_dir = NULL; + + if (argc > 1) + /* One can pass a homedir on the command line. This + * will most likely cause the test to fail, but it can be + * used to pass invalid home directories manually. The + * test error messages should then show the system's temporary + * directory to be used instead */ + home_dir = argv[1]; + else + /* Set up a fake home directory to play with */ + home_dir = g_dir_make_tmp("gnucashXXXXXX", NULL); for (i = 0; strs[i].input != NULL; i++) { @@ -66,15 +78,15 @@ main(int argc, char **argv) if (strs[i].prefix_home == 1) { - dain = g_build_filename(g_get_home_dir(), strs[i].input, + dain = g_build_filename(home_dir, strs[i].input, (gchar *)NULL); - wantout = g_build_filename(g_get_home_dir(), strs[i].output, + wantout = g_build_filename(home_dir, strs[i].output, (gchar *)NULL); } else if (strs[i].prefix_home == 2) { dain = g_strdup(strs[i].input); - wantout = g_build_filename(g_get_home_dir(), strs[i].output, + wantout = g_build_filename(home_dir, strs[i].output, (gchar *)NULL); } else @@ -92,6 +104,7 @@ main(int argc, char **argv) g_free(wantout); g_free(daout); } + print_test_results(); return get_rv(); } diff --git a/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c b/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c new file mode 100644 index 0000000000..790f818670 --- /dev/null +++ b/libgnucash/core-utils/test/test-usr-conf-dir-invalid-home.c @@ -0,0 +1,110 @@ +/*************************************************************************** + * test-resolve-file-path.c + * + * Thu Sep 29 22:48:57 2005 + * Copyright 2005 GnuCash team + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" +#include +#include + +#include +#include "test-stuff.h" +#include "gnc-filepath-utils.h" + +struct usr_confpath_strings_struct +{ + int func_num; + char *funcname; + char *output; +}; + +typedef struct usr_confpath_strings_struct usr_confpath_strings; + +usr_confpath_strings strs2[] = +{ + { + 0, "gnc_build_userdata_path", + ".gnucash" + }, + { + 1, "gnc_build_book_path", + ".gnucash" G_DIR_SEPARATOR_S "books" + }, + { + 2, "gnc_build_translog_path", + ".gnucash" G_DIR_SEPARATOR_S "translog" + }, + { + 3, "gnc_build_data_path", + ".gnucash" G_DIR_SEPARATOR_S "data" + }, + { 0, NULL, NULL }, +}; + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + int i; + const char *tmp_dir = g_get_tmp_dir(); + + /* Run usr conf dir tests with a valid and writable homedir */ + g_setenv("HOME", "/notexist", TRUE); + for (i = 0; strs2[i].funcname != NULL; i++) + { + char *daout; + char *wantout; + + if (strs2[i].func_num == 0) + { + wantout = g_build_filename(tmp_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_userdata_path("foo"); + } + else if (strs2[i].func_num == 1) + { + wantout = g_build_filename(tmp_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_book_path("foo"); + } + else if (strs2[i].func_num == 2) + { + wantout = g_build_filename(tmp_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_translog_path("foo"); + } + else // if (strs2[i].prefix_home == 3) + { + wantout = g_build_filename(tmp_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_data_path("foo"); + } + + do_test_args(g_strcmp0(daout, wantout) == 0, + "gnc_build_x_path", + __FILE__, __LINE__, + "%s (%s) vs %s", daout, strs2[i].funcname, wantout); + g_free(wantout); + g_free(daout); + } + + print_test_results(); + return get_rv(); +} diff --git a/libgnucash/core-utils/test/test-usr-conf-dir.c b/libgnucash/core-utils/test/test-usr-conf-dir.c new file mode 100644 index 0000000000..91a6c675d2 --- /dev/null +++ b/libgnucash/core-utils/test/test-usr-conf-dir.c @@ -0,0 +1,121 @@ +/*************************************************************************** + * test-resolve-file-path.c + * + * Thu Sep 29 22:48:57 2005 + * Copyright 2005 GnuCash team + ****************************************************************************/ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "config.h" +#include +#include + +#include +#include "test-stuff.h" +#include "gnc-filepath-utils.h" + +struct usr_confpath_strings_struct +{ + int func_num; + char *funcname; + char *output; +}; + +typedef struct usr_confpath_strings_struct usr_confpath_strings; + +usr_confpath_strings strs2[] = +{ + { + 0, "gnc_build_userdata_path", + ".gnucash" + }, + { + 1, "gnc_build_book_path", + ".gnucash" G_DIR_SEPARATOR_S "books" + }, + { + 2, "gnc_build_translog_path", + ".gnucash" G_DIR_SEPARATOR_S "translog" + }, + { + 3, "gnc_build_data_path", + ".gnucash" G_DIR_SEPARATOR_S "data" + }, + { 0, NULL, NULL }, +}; + +int +main(int argc, char **argv) +{ + int i; + char *home_dir = NULL; + + if (argc > 1) + /* One can pass a homedir on the command line. This + * will most likely cause the test to fail, but it can be + * used to pass invalid home directories manually. The + * test error messages should then show the system's temporary + * directory to be used instead */ + home_dir = argv[1]; + else + /* Set up a fake home directory to play with */ + home_dir = g_dir_make_tmp("gnucashXXXXXX", NULL); + + /* Run usr conf dir tests with a valid and writable homedir */ + g_setenv("HOME", home_dir, TRUE); + for (i = 0; strs2[i].funcname != NULL; i++) + { + char *daout; + char *wantout; + + if (strs2[i].func_num == 0) + { + wantout = g_build_filename(home_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_userdata_path("foo"); + } + else if (strs2[i].func_num == 1) + { + wantout = g_build_filename(home_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_book_path("foo"); + } + else if (strs2[i].func_num == 2) + { + wantout = g_build_filename(home_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_translog_path("foo"); + } + else // if (strs2[i].prefix_home == 3) + { + wantout = g_build_filename(home_dir, strs2[i].output, "foo", + (gchar *)NULL); + daout = gnc_build_data_path("foo"); + } + + do_test_args(g_strcmp0(daout, wantout) == 0, + "gnc_build_x_path", + __FILE__, __LINE__, + "%s (%s) vs %s", daout, strs2[i].funcname, wantout); + g_free(wantout); + g_free(daout); + } + + print_test_results(); + return get_rv(); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index cb4a7773f1..3cf6ed4c74 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -598,7 +598,7 @@ libgnucash/backend/xml/sixtp-utils.cpp libgnucash/core-utils/binreloc.c libgnucash/core-utils/core-utils.scm libgnucash/core-utils/gnc-environment.c -libgnucash/core-utils/gnc-filepath-utils.c +libgnucash/core-utils/gnc-filepath-utils.cpp libgnucash/core-utils/gnc-gkeyfile-utils.c libgnucash/core-utils/gnc-glib-utils.c libgnucash/core-utils/gnc-guile-utils.c