mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
It is split into - /libgnucash (for the non-gui bits) - /gnucash (for the gui) - /common (misc source files used by both) - /bindings (currently only holds python bindings) This is the first step in restructuring the code. It will need much more fine tuning later on.
641 lines
19 KiB
C
641 lines
19 KiB
C
/********************************************************************\
|
|
* gnc-filepath-utils.c -- file path resolutin utilitie *
|
|
* *
|
|
* 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, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
\********************************************************************/
|
|
|
|
/*
|
|
* @file gnc-filepath-utils.c
|
|
* @brief file path resolution utilities
|
|
* @author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
|
|
* @author Copyright (c) 2000 Dave Peticolas
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <platform.h>
|
|
#if PLATFORM(WINDOWS)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <glib/gprintf.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <errno.h>
|
|
|
|
#include "gnc-path.h"
|
|
#include "gnc-filepath-utils.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#include <glib/gwin32.h>
|
|
#define PATH_MAX MAXPATHLEN
|
|
#endif
|
|
|
|
/**
|
|
* Scrubs a filename by changing "strange" chars (e.g. those that are not
|
|
* valid in a win32 file name) to "_".
|
|
*
|
|
* @param filename File name - updated in place
|
|
*/
|
|
static void
|
|
scrub_filename(char* filename)
|
|
{
|
|
char* p;
|
|
|
|
#define STRANGE_CHARS "/:"
|
|
p = strpbrk(filename, STRANGE_CHARS);
|
|
while (p)
|
|
{
|
|
*p = '_';
|
|
p = strpbrk(filename, STRANGE_CHARS);
|
|
}
|
|
}
|
|
|
|
/** Check if the path exists and is a regular file.
|
|
*
|
|
* \param path -- freed if the path doesn't exist or isn't a regular file
|
|
*
|
|
* \return NULL or the path
|
|
*/
|
|
static gchar *
|
|
check_path_return_if_valid(gchar *path)
|
|
{
|
|
if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
|
|
{
|
|
return path;
|
|
}
|
|
g_free (path);
|
|
return NULL;
|
|
}
|
|
|
|
/** @brief Create an absolute path when given a relative path;
|
|
* otherwise return the argument.
|
|
*
|
|
* @warning filefrag should be a simple path fragment. It shouldn't
|
|
* contain xml:// or http:// or <whatever>:// other protocol specifiers.
|
|
*
|
|
* If passed a string which g_path_is_absolute declares an absolute
|
|
* path, return the argument.
|
|
*
|
|
* 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 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)
|
|
*
|
|
* The paths are searched for in that order. If a matching file is
|
|
* found, return the absolute path to it.
|
|
|
|
* If one isn't found, return a absolute path relative to the
|
|
* user's configuration directory and note in the trace file that it
|
|
* needs to be created.
|
|
*
|
|
* @param filefrag The file path to resolve
|
|
*
|
|
* @return An absolute file path.
|
|
*/
|
|
gchar *
|
|
gnc_resolve_file_path (const gchar * filefrag)
|
|
{
|
|
gchar *fullpath = NULL, *tmp_path = NULL;
|
|
|
|
/* seriously invalid */
|
|
if (!filefrag)
|
|
{
|
|
g_critical("filefrag is NULL");
|
|
return NULL;
|
|
}
|
|
|
|
/* ---------------------------------------------------- */
|
|
/* OK, now we try to find or build an absolute file path */
|
|
|
|
/* check for an absolute file path */
|
|
if (g_path_is_absolute(filefrag))
|
|
return g_strdup (filefrag);
|
|
|
|
/* Look in the current working directory */
|
|
tmp_path = g_get_current_dir();
|
|
fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
|
|
g_free(tmp_path);
|
|
fullpath = check_path_return_if_valid(fullpath);
|
|
if (fullpath != NULL)
|
|
return fullpath;
|
|
|
|
/* Look in the data dir (e.g. $PREFIX/share/gnucash) */
|
|
tmp_path = gnc_path_get_pkgdatadir();
|
|
fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
|
|
g_free(tmp_path);
|
|
fullpath = check_path_return_if_valid(fullpath);
|
|
if (fullpath != NULL)
|
|
return fullpath;
|
|
|
|
/* Look in the config dir (e.g. $PREFIX/share/gnucash/accounts) */
|
|
tmp_path = gnc_path_get_accountsdir();
|
|
fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
|
|
g_free(tmp_path);
|
|
fullpath = check_path_return_if_valid(fullpath);
|
|
if (fullpath != NULL)
|
|
return fullpath;
|
|
|
|
/* Look in the users config dir (e.g. $HOME/.gnucash/data) */
|
|
fullpath = gnc_build_data_path(filefrag);
|
|
if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR))
|
|
return fullpath;
|
|
|
|
/* OK, it's not there. Note that it needs to be created and pass it
|
|
* back anyway */
|
|
g_warning("create new file %s", fullpath);
|
|
return fullpath;
|
|
|
|
}
|
|
|
|
/* Searches for a file fragment paths set via GNC_DOC_PATH environment
|
|
* variable. If this variable is not set, fall back to search in
|
|
* - a html directory in the local user's gnucash settings directory
|
|
* (typically $HOME/.gnucash/html)
|
|
* - the gnucash documentation directory
|
|
* (typically /usr/share/doc/gnucash)
|
|
* - the gnucash data directory
|
|
* (typically /usr/share/gnucash)
|
|
* It searches in this order.
|
|
*
|
|
* This is used by gnc_path_find_localized_file to search for
|
|
* localized versions of files if they exist.
|
|
*/
|
|
static gchar *
|
|
gnc_path_find_localized_html_file_internal (const gchar * file_name)
|
|
{
|
|
gchar *full_path = NULL;
|
|
int i;
|
|
const gchar *env_doc_path = g_getenv("GNC_DOC_PATH");
|
|
const gchar *default_dirs[] =
|
|
{
|
|
gnc_build_dotgnucash_path ("html"),
|
|
gnc_path_get_pkgdocdir (),
|
|
gnc_path_get_pkgdatadir (),
|
|
NULL
|
|
};
|
|
gchar **dirs;
|
|
|
|
if (!file_name || *file_name == '\0')
|
|
return NULL;
|
|
|
|
/* Allow search path override via GNC_DOC_PATH environment variable */
|
|
if (env_doc_path)
|
|
dirs = g_strsplit (env_doc_path, G_SEARCHPATH_SEPARATOR_S, -1);
|
|
else
|
|
dirs = (gchar **)default_dirs;
|
|
|
|
for (i = 0; dirs[i]; i++)
|
|
{
|
|
full_path = g_build_filename (dirs[i], file_name, (gchar *)NULL);
|
|
g_debug ("Checking for existence of %s", full_path);
|
|
full_path = check_path_return_if_valid (full_path);
|
|
if (full_path != NULL)
|
|
return full_path;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** @brief Find an absolute path to a localized version of a given
|
|
* relative path to a html or html related file.
|
|
* If no localized version exists, an absolute path to the file
|
|
* is searched for. If that file doesn't exist either, returns NULL.
|
|
*
|
|
* @warning file_name should be a simple path fragment. It shouldn't
|
|
* contain xml:// or http:// or <whatever>:// other protocol specifiers.
|
|
*
|
|
* If passed a string which g_path_is_absolute declares an absolute
|
|
* path, return the argument.
|
|
*
|
|
* Otherwise, assume that file_name is a well-formed relative path and
|
|
* try to find a file with its path relative to
|
|
* \li a localized subdirectory in the html directory
|
|
* of the user's configuration directory
|
|
* (e.g. $HOME/.gnucash/html/de_DE, $HOME/.gnucash/html/en,...)
|
|
* \li a localized subdirectory in the gnucash documentation directory
|
|
* (e.g. /usr/share/doc/gnucash/C,...)
|
|
* \li the html directory of the user's configuration directory
|
|
* (e.g. $HOME/.gnucash/html)
|
|
* \li the gnucash documentation directory
|
|
* (e.g. /usr/share/doc/gnucash/)
|
|
* \li last resort option: the gnucash data directory
|
|
* (e.g. /usr/share/gnucash/)
|
|
*
|
|
* The paths are searched for in that order. If a matching file is
|
|
* found, return the absolute path to it.
|
|
|
|
* If one isn't found, return NULL.
|
|
*
|
|
* @param file_name The file path to resolve
|
|
*
|
|
* @return An absolute file path or NULL if no file is found.
|
|
*/
|
|
gchar *
|
|
gnc_path_find_localized_html_file (const gchar *file_name)
|
|
{
|
|
gchar *loc_file_name = NULL;
|
|
gchar *full_path = NULL;
|
|
const gchar * const *lang;
|
|
|
|
if (!file_name || *file_name == '\0')
|
|
return NULL;
|
|
|
|
/* An absolute path is returned unmodified. */
|
|
if (g_path_is_absolute (file_name))
|
|
return g_strdup (file_name);
|
|
|
|
/* First try to find the file in any of the localized directories
|
|
* the user has set up on his system
|
|
*/
|
|
for (lang = g_get_language_names (); *lang; lang++)
|
|
{
|
|
loc_file_name = g_build_filename (*lang, file_name, (gchar *)NULL);
|
|
full_path = gnc_path_find_localized_html_file_internal (loc_file_name);
|
|
g_free (loc_file_name);
|
|
if (full_path != NULL)
|
|
return full_path;
|
|
}
|
|
|
|
/* If not found in a localized directory, try to find the file
|
|
* in any of the base directories
|
|
*/
|
|
return gnc_path_find_localized_html_file_internal (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)
|
|
{
|
|
GStatBuf statbuf;
|
|
gint rc;
|
|
|
|
*msg = NULL;
|
|
rc = g_stat (dirname, &statbuf);
|
|
if (rc)
|
|
{
|
|
switch (errno)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
if ((statbuf.st_mode & S_IFDIR) != S_IFDIR)
|
|
{
|
|
*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;
|
|
}
|
|
#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
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/** @fn const gchar * gnc_dotgnucash_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
|
|
*/
|
|
const gchar *
|
|
gnc_dotgnucash_dir (void)
|
|
{
|
|
static gchar *dotgnucash = NULL;
|
|
gchar *tmp_dir;
|
|
gchar *errmsg = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_dotgnucash_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.
|
|
*/
|
|
|
|
gchar *
|
|
gnc_build_dotgnucash_path (const gchar *filename)
|
|
{
|
|
return g_build_filename(gnc_dotgnucash_dir(), filename, (gchar *)NULL);
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_book_path (const gchar *filename)
|
|
* @brief Make a path to filename in the book subdirectory of the user's configuration directory.
|
|
*
|
|
* @param filename The name of the file
|
|
*
|
|
* @return An absolute path.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_translog_path (const gchar *filename)
|
|
* @brief Make a path to filename in the translog subdirectory of the user's configuration directory.
|
|
*
|
|
* @param filename The name of the file
|
|
*
|
|
* @return An absolute path.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_data_path (const gchar *filename)
|
|
* @brief Make a path to filename in the data subdirectory of the user's configuration directory.
|
|
*
|
|
* @param filename The name of the file
|
|
*
|
|
* @return An absolute path.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_report_path (const gchar *filename)
|
|
* @brief Make a path to filename in the report directory.
|
|
*
|
|
* @param filename The name of the file
|
|
*
|
|
* @return An absolute path.
|
|
*/
|
|
|
|
gchar *
|
|
gnc_build_report_path (const gchar *filename)
|
|
{
|
|
gchar *result = g_build_filename(gnc_path_get_reportdir(), filename, (gchar *)NULL);
|
|
return result;
|
|
}
|
|
|
|
/** @fn gchar * gnc_build_stdreports_path (const gchar *filename)
|
|
* @brief Make a path to filename in the standard reports directory.
|
|
*
|
|
* @param filename The name of the file
|
|
*
|
|
* @return An absolute path.
|
|
*/
|
|
|
|
gchar *
|
|
gnc_build_stdreports_path (const gchar *filename)
|
|
{
|
|
gchar *result = g_build_filename(gnc_path_get_stdreportsdir(), filename, (gchar *)NULL);
|
|
return result;
|
|
}
|
|
|
|
static gchar *
|
|
gnc_filepath_locate_file (const gchar *default_path, const gchar *name)
|
|
{
|
|
gchar *fullname;
|
|
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
|
|
if (g_path_is_absolute (name))
|
|
fullname = g_strdup (name);
|
|
else if (default_path)
|
|
fullname = g_build_filename (default_path, name, NULL);
|
|
else
|
|
fullname = gnc_resolve_file_path (name);
|
|
|
|
if (!g_file_test (fullname, G_FILE_TEST_IS_REGULAR))
|
|
{
|
|
g_warning ("Could not locate file %s", name);
|
|
g_free (fullname);
|
|
return NULL;
|
|
}
|
|
|
|
return fullname;
|
|
}
|
|
|
|
gchar *
|
|
gnc_filepath_locate_data_file (const gchar *name)
|
|
{
|
|
return gnc_filepath_locate_file (gnc_path_get_pkgdatadir(), name);
|
|
}
|
|
|
|
gchar *
|
|
gnc_filepath_locate_pixmap (const gchar *name)
|
|
{
|
|
gchar *default_path;
|
|
gchar *fullname;
|
|
gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
|
|
|
|
default_path = g_build_filename (pkgdatadir, "pixmaps", NULL);
|
|
g_free(pkgdatadir);
|
|
fullname = gnc_filepath_locate_file (default_path, name);
|
|
g_free(default_path);
|
|
|
|
return fullname;
|
|
}
|
|
|
|
gchar *
|
|
gnc_filepath_locate_ui_file (const gchar *name)
|
|
{
|
|
gchar *default_path;
|
|
gchar *fullname;
|
|
gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
|
|
|
|
default_path = g_build_filename (pkgdatadir, "ui", NULL);
|
|
g_free(pkgdatadir);
|
|
fullname = gnc_filepath_locate_file (default_path, name);
|
|
g_free(default_path);
|
|
|
|
return fullname;
|
|
}
|
|
|
|
gchar *
|
|
gnc_filepath_locate_doc_file (const gchar *name)
|
|
{
|
|
return gnc_filepath_locate_file (gnc_path_get_pkgdocdir(), name);
|
|
}
|
|
|
|
|
|
/* =============================== END OF FILE ========================== */
|