18#include <glib/gstdio.h>
42#include "gnc-xml-backend.hpp"
47#define XML_URI_PREFIX "xml://"
48#define FILE_URI_PREFIX "file://"
49static QofLogModule log_module = GNC_MOD_BACKEND;
51GncXmlBackend::~GncXmlBackend()
57GncXmlBackend::check_path (
const char* fullpath,
bool create)
60 char* dirname = g_path_get_dirname (fullpath);
62 auto rc = g_stat (dirname, &statbuf);
65 || (statbuf.st_mode & _S_IFDIR) == 0
67 || !S_ISDIR (statbuf.st_mode)
74 std::string msg {
"Couldn't find directory for "};
76 PWARN (
"Couldn't find directory for %s", fullpath);
82 rc = g_stat (fullpath, &statbuf);
83 if ((rc != 0) && (!create))
87 std::string msg {
"Couldn't find "};
89 PWARN (
"Couldn't find %s", fullpath);
95 && (statbuf.st_mode & _S_IFDIR) != 0
97 && S_ISDIR (statbuf.st_mode)
102 std::string msg {
"Path "};
105 PWARN (
"Path %s is a directory", fullpath);
131 PWARN (
"Might clobber, no force");
139 auto dirname = g_path_get_dirname (
m_fullpath.c_str());
160GncXmlBackend::session_end()
168 if (!m_linkfile.empty())
169 g_unlink (m_linkfile.c_str());
177 if (!m_lockfile.empty())
183 rv = g_chmod (m_lockfile.c_str(), S_IWRITE | S_IREAD);
185 rv = g_unlink (m_lockfile.c_str());
188 PWARN (
"Error on g_unlink(%s): %d: %s", m_lockfile.c_str(),
189 errno, g_strerror (errno) ? g_strerror (errno) :
"");
199static QofBookFileType
200determine_file_type (
const std::string& path)
202 gboolean with_encoding;
203 QofBookFileType v2type;
205 v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding);
206 if (v2type == GNC_BOOK_XML2_FILE)
210 return GNC_BOOK_XML2_FILE;
214 return GNC_BOOK_XML2_FILE_NO_ENCODING;
217 else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
219 return GNC_BOOK_POST_XML2_0_0_FILE;
221 else if (v2type == GNC_BOOK_XML1_FILE)
223 return GNC_BOOK_XML1_FILE;
225 return GNC_BOOK_NOT_OURS;
234 if (loadType != LOAD_TYPE_INITIAL_LOAD)
return;
236 error = ERR_BACKEND_NO_ERR;
238 g_object_unref(m_book);
239 m_book = QOF_BOOK(g_object_ref(book));
244 case GNC_BOOK_XML2_FILE:
245 rc = qof_session_load_from_xml_file_v2 (
this, book,
254 case GNC_BOOK_XML2_FILE_NO_ENCODING:
258 case GNC_BOOK_XML1_FILE:
259 rc = qof_session_load_from_xml_file (book,
m_fullpath.c_str());
266 case GNC_BOOK_POST_XML2_0_0_FILE:
268 PWARN (
"Version of Xml file %s is newer than what we can read",
278 PWARN (
"No read permission to file");
282 PWARN (
"Filename is a directory");
286 PWARN (
"File not any known type");
293 if (error != ERR_BACKEND_NO_ERR)
311 if (m_book ==
nullptr)
312 m_book = QOF_BOOK(g_object_ref(book));
313 if (book != m_book)
return;
322 write_to_file (
true);
330 qof_instance_mark_clean(instance);
334GncXmlBackend::save_may_clobber_data()
339 auto rc = g_stat (
m_fullpath.c_str(), &statbuf);
353 gnc_book_write_accounts_to_xml_filehandle_v2(
this, book, out);
358GncXmlBackend::write_to_file (
bool make_backup)
377 auto tmp_name = g_new (
char, strlen (
m_fullpath.c_str()) + 12);
379 strcat (tmp_name,
".tmp-XXXXXX");
388#pragma GCC diagnostic push
389#pragma GCC diagnostic warning "-Wdeprecated-declarations"
390 if (!mktemp (tmp_name))
391#pragma GCC diagnostic pop
410 if (gnc_book_write_to_xml_file_v2 (m_book, tmp_name,
411 gnc_prefs_get_file_save_compressed ()))
415 auto rc = g_stat (
m_fullpath.c_str(), &statbuf);
419 g_assert (g_strcmp0 (tmp_name,
"/dev/null") != 0);
422 if (g_chmod (tmp_name, statbuf.st_mode) != 0)
431 PWARN (
"unable to chmod filename %s: %s",
432 tmp_name ? tmp_name :
"(null)",
433 g_strerror (errno) ? g_strerror (errno) :
"");
442 if (chown (tmp_name, -1, statbuf.st_gid) != 0)
448 PWARN (
"unable to chown filename %s: %s",
449 tmp_name ? tmp_name :
"(null)",
450 strerror (errno) ? strerror (errno) :
"");
458 if (g_unlink (
m_fullpath.c_str()) != 0 && errno != ENOENT)
461 PWARN (
"unable to unlink filename %s: %s",
463 g_strerror (errno) ? g_strerror (errno) :
"");
468 if (!link_or_make_backup (tmp_name,
m_fullpath))
471 std::string msg{
"Failed to make backup file "};
477 if (g_unlink (tmp_name) != 0)
480 PWARN (
"unable to unlink temp filename %s: %s",
481 tmp_name ? tmp_name :
"(null)",
482 g_strerror (errno) ? g_strerror (errno) :
"");
492 LEAVE (
" successful save of book=%p to file=%s", m_book,
498 if (g_unlink (tmp_name) != 0)
514 PWARN (
"unable to unlink temp_filename %s: %s",
515 tmp_name ? tmp_name :
"(null)",
516 g_strerror (errno) ? g_strerror (errno) :
"");
523 std::string msg{
"Unable to write to temp file "};
536copy_file (
const std::string& orig,
const std::string& bkup)
538 constexpr size_t buf_size = 1024;
541 ssize_t count_write = 0;
542 ssize_t count_read = 0;
549 auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0);
554 auto bkup_fd = g_open (bkup.c_str(),
555 O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
564 count_read = read (orig_fd, buf, buf_size);
565 if (count_read == -1 && errno != EINTR)
574 count_write = write (bkup_fd, buf, count_read);
575 if (count_write == -1)
583 while (count_read > 0);
592GncXmlBackend::link_or_make_backup (
const std::string& orig,
593 const std::string& bkup)
595 gboolean copy_success = FALSE;
598 link (orig.c_str(), bkup.c_str())
606 if (errno == EPERM || errno == ENOSYS
608 || errno == EOPNOTSUPP
619 copy_success = copy_file (orig.c_str(), bkup);
625 PWARN (
"unable to make file backup from %s to %s: %s",
626 orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) :
"");
637 m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL ,
646 set_message(
"Unable to create lockfile, make sure that you have write access to the directory.");
651 set_message(
"Unable to create lockfile, data file is on a read-only filesystem.");
655 set_message(
"Unable to create lockfile, no space on filesystem.");
662 PWARN (
"Unable to create the lockfile %s: %s",
663 m_lockfile.c_str(), strerror(errno));
664 set_message(
"Lockfile creation failed. Please see the tracefile for details.");
676GncXmlBackend::backup_file()
682 auto rc = g_stat (datafile, &statbuf);
684 return (errno == ENOENT);
686 if (determine_file_type (
m_fullpath) == GNC_BOOK_BIN_FILE)
690 auto bkup_ret = link_or_make_backup (
m_fullpath, bin_bkup);
698 auto backup =
m_fullpath +
"." + timestamp + GNC_DATAFILE_EXT;
701 return link_or_make_backup (datafile, backup);
710GncXmlBackend::remove_old_files ()
712 GStatBuf lockstatbuf, statbuf;
714 if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0)
717 auto dir = g_dir_open (m_dirname.c_str(), 0, NULL);
723 while ((dent = g_dir_read_name (dir)) != NULL)
728 if (! (g_str_has_suffix (dent,
".LNK") ||
729 g_str_has_suffix (dent,
".xac") ||
730 g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
731 g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
734 name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL);
737 if (!g_str_has_prefix (name,
m_fullpath.c_str()))
744 if (g_strcmp0 (name,
m_fullpath.c_str()) == 0)
751 if (g_str_has_suffix (name,
".LNK"))
754 if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) &&
756 (g_stat (name, &statbuf) == 0) &&
757 (statbuf.st_mtime < lockstatbuf.st_mtime))
759 PINFO (
"remove stale lock file: %s", name);
781 gchar* stamp_start = name +
m_fullpath.size();
782 gchar* expression = g_strdup_printf (
"^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
783 GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
784 gboolean got_date_stamp = FALSE;
786 if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
787 PWARN (
"Cannot compile regex for date stamp");
788 else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
789 got_date_stamp = TRUE;
804 if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
806 PINFO (
"remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
809 else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
810 (gnc_prefs_get_file_retention_days () > 0))
815 if (g_stat (name, &statbuf) != 0)
820 days = (int) (difftime (now, statbuf.st_mtime) / 86400);
822 PINFO (
"file retention = %d days", gnc_prefs_get_file_retention_days ());
823 if (days >= gnc_prefs_get_file_retention_days ())
825 PINFO (
"remove stale file: %s - reason: more than %d days old", name, days);
API for the transaction logger.
void session_begin(QofSession *session, const char *new_uri, SessionOpenMode mode) override
Open the file or connect to the server.
void commit(QofInstance *instance) override
Commits the changes from the engine to the backend data storage.
void sync(QofBook *book) override
Synchronizes the engine contents to the backend.
void export_coa(QofBook *) override
Extract the chart of accounts from the current database and create a new database with it.
void load(QofBook *book, QofBackendLoadType loadType) override
Load the minimal set of application data needed for the application to be operable at initial startup...
load and save data to files
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
Utility functions for convert uri in separate components and back.
QofBackendError
The errors that can be reported to the GUI & other front-end users.
SessionOpenMode
Mode for opening sessions.
@ ERR_FILEIO_FILE_EACCES
No read access permission for the given file.
@ ERR_BACKEND_STORE_EXISTS
File exists, data would be destroyed.
@ ERR_BACKEND_LOCKED
in use by another user (ETXTBSY)
@ ERR_FILEIO_WRITE_ERROR
couldn't write to the file
@ ERR_FILEIO_FILE_LOCKERR
mangled locks (unspecified error)
@ ERR_FILEIO_NO_ENCODING
file does not specify encoding
@ ERR_FILEIO_UNKNOWN_FILE_TYPE
didn't recognize the file type
@ ERR_BACKEND_MISC
undetermined error
@ ERR_FILEIO_BACKUP_ERROR
couldn't make a backup of the file
@ ERR_BACKEND_TOO_NEW
file/db version newer than what we can read
@ ERR_FILEIO_PARSE_ERROR
couldn't parse the data in the file
@ ERR_BACKEND_PERM
user login successful, but no permissions to access the desired object
@ ERR_FILEIO_FILE_NOT_FOUND
not found / no such file
@ ERR_BACKEND_READONLY
cannot write to file/directory
@ SESSION_READ_ONLY
Create a new store at the URI even if a store already exists there.
@ SESSION_NEW_STORE
Open will fail if the URI doesn't exist or is locked.
@ SESSION_BREAK_LOCK
Open the session read-only, ignoring any existing lock and not creating one if the URI isn't locked.
@ SESSION_NEW_OVERWRITE
Create a new store at the URI.
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
void qof_book_mark_session_saved(QofBook *book)
The qof_book_mark_saved() routine marks the book as having been saved (to a file, to a database).
time64 gnc_time(time64 *tbuf)
get the current time
gchar * gnc_date_timestamp(void)
Make a timestamp in YYYYMMDDHHMMSS format.
#define qof_instance_is_dirty
Return value of is_dirty flag.
#define PINFO(format, args...)
Print an informational note.
#define LEAVE(format, args...)
Print a function exit debugging message.
#define PWARN(format, args...)
Log a warning.
#define ENTER(format, args...)
Print a function entry debugging message.
void set_message(std::string &&)
Set a descriptive message that can be displayed to the user when there's an error.
std::string m_fullpath
Each backend resolves a fully-qualified file path.
void set_error(QofBackendError err)
Set the error value only if there isn't already an error already.
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name.
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
api for GnuCash version 2 XML-based file format
api for Version 1 XML-based file format