/src/backend/xml/gnc-backend-xml.c
C | 1333 lines | 970 code | 169 blank | 194 comment | 171 complexity | db8b7d3e8ff9cb8838db5acd9b5f7815 MD5 | raw file
Possible License(s): GPL-2.0
- /********************************************************************
- * gnc-backend-xml.c: load and save data to XML files *
- * *
- * 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-backend-xml.c
- * @brief load and save data to XML files
- * @author Copyright (c) 2000 Gnumatic Inc.
- * @author Copyright (c) 2002 Derek Atkins <warlord@MIT.EDU>
- * @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
- *
- * This file implements the top-level QofBackend API for saving/
- * restoring data to/from an ordinary Unix filesystem file.
- */
- #include "config.h"
- #include <glib.h>
- #include <glib/gi18n.h>
- #include <glib/gstdio.h>
- #include <libintl.h>
- #include <locale.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <regex.h>
- #ifdef HAVE_UNISTD_H
- # include <unistd.h>
- #else
- # ifdef _MSC_VER
- typedef int ssize_t;
- # endif
- #endif
- #include <errno.h>
- #include <string.h>
- #ifdef HAVE_DIRENT_H
- # include <dirent.h>
- #endif
- #include <time.h>
- #ifdef G_OS_WIN32
- # include <io.h>
- # define close _close
- # define mktemp _mktemp
- # define read _read
- # define write _write
- #endif
- #include "platform.h"
- #if COMPILER(MSVC)
- # define g_fopen fopen
- # define g_open _open
- #endif
- #include "qof.h"
- #include "TransLog.h"
- #include "gnc-engine.h"
- #include "gnc-uri-utils.h"
- #include "io-gncxml.h"
- #include "io-gncxml-v2.h"
- #include "gnc-backend-xml.h"
- #include "gnc-gconf-utils.h"
- #include "gnc-address-xml-v2.h"
- #include "gnc-bill-term-xml-v2.h"
- #include "gnc-customer-xml-v2.h"
- #include "gnc-employee-xml-v2.h"
- #include "gnc-entry-xml-v2.h"
- #include "gnc-invoice-xml-v2.h"
- #include "gnc-job-xml-v2.h"
- #include "gnc-order-xml-v2.h"
- #include "gnc-owner-xml-v2.h"
- #include "gnc-tax-table-xml-v2.h"
- #include "gnc-vendor-xml-v2.h"
- #ifndef HAVE_STRPTIME
- # include "strptime.h"
- #endif
- #define KEY_FILE_COMPRESSION "file_compression"
- #define KEY_RETAIN_TYPE "retain_type"
- #define KEY_RETAIN_DAYS "retain_days"
- static QofLogModule log_module = GNC_MOD_BACKEND;
- typedef enum
- {
- GNC_BOOK_NOT_OURS,
- GNC_BOOK_BIN_FILE,
- GNC_BOOK_XML1_FILE,
- GNC_BOOK_XML2_FILE,
- GNC_BOOK_XML2_FILE_NO_ENCODING,
- } QofBookFileType;
- static gboolean save_may_clobber_data (QofBackend *bend);
- /* ================================================================= */
- static gboolean
- gnc_xml_be_get_file_lock (FileBackend *be)
- {
- struct stat statbuf;
- #ifndef G_OS_WIN32
- char pathbuf[PATH_MAX];
- char *path = NULL;
- #endif
- int rc;
- QofBackendError be_err;
- rc = g_stat (be->lockfile, &statbuf);
- if (!rc)
- {
- /* oops .. file is locked by another user .. */
- qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
- return FALSE;
- }
- be->lockfd = g_open (be->lockfile, O_RDWR | O_CREAT | O_EXCL , S_IRUSR | S_IWUSR);
- if (be->lockfd < 0)
- {
- /* oops .. we can't create the lockfile .. */
- switch (errno)
- {
- case EACCES:
- case EROFS:
- case ENOSPC:
- PWARN( "Unable to create the lockfile %s; may not have write priv",
- be->lockfile );
- be_err = ERR_BACKEND_READONLY;
- break;
- default:
- be_err = ERR_BACKEND_LOCKED;
- break;
- }
- qof_backend_set_error ((QofBackend*)be, be_err);
- return FALSE;
- }
- /* OK, now work around some NFS atomic lock race condition
- * mumbo-jumbo. We do this by linking a unique file, and
- * then examining the link count. At least that's what the
- * NFS programmers guide suggests.
- * Note: the "unique filename" must be unique for the
- * triplet filename-host-process, otherwise accidental
- * aliases can occur.
- */
- /* apparently, even this code may not work for some NFS
- * implementations. In the long run, I am told that
- * ftp.debian.org
- * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
- * provides a better long-term solution.
- */
- #ifndef G_OS_WIN32
- strcpy (pathbuf, be->lockfile);
- path = strrchr (pathbuf, '.');
- sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
- rc = link (be->lockfile, pathbuf);
- if (rc)
- {
- /* If hard links aren't supported, just allow the lock. */
- if (errno == EPERM || errno == ENOSYS
- # ifdef EOPNOTSUPP
- || errno == EOPNOTSUPP
- # endif
- # ifdef ENOTSUP
- || errno == ENOTSUP
- # endif
- )
- {
- be->linkfile = NULL;
- return TRUE;
- }
- /* Otherwise, something else is wrong. */
- qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
- g_unlink (pathbuf);
- close (be->lockfd);
- g_unlink (be->lockfile);
- return FALSE;
- }
- rc = g_stat (be->lockfile, &statbuf);
- if (rc)
- {
- /* oops .. stat failed! This can't happen! */
- qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
- qof_backend_set_message ((QofBackend*)be, "Failed to stat lockfile %s",
- be->lockfile );
- g_unlink (pathbuf);
- close (be->lockfd);
- g_unlink (be->lockfile);
- return FALSE;
- }
- if (statbuf.st_nlink != 2)
- {
- qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
- g_unlink (pathbuf);
- close (be->lockfd);
- g_unlink (be->lockfile);
- return FALSE;
- }
- be->linkfile = g_strdup (pathbuf);
- return TRUE;
- #else /* ifndef G_OS_WIN32 */
- /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
- is sufficient for locking. */
- be->linkfile = NULL;
- return TRUE;
- #endif /* ifndef G_OS_WIN32 */
- }
- /* ================================================================= */
- #define XML_URI_PREFIX "xml://"
- #define FILE_URI_PREFIX "file://"
- static void
- xml_session_begin(QofBackend *be_start, QofSession *session,
- const char *book_id, gboolean ignore_lock,
- gboolean create, gboolean force)
- {
- FileBackend *be = (FileBackend*) be_start;
- ENTER (" ");
- /* Make sure the directory is there */
- be->fullpath = gnc_uri_get_path (book_id);
- if (NULL == be->fullpath)
- {
- qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
- qof_backend_set_message (be_start, "No path specified");
- LEAVE("");
- return;
- }
- if (create && !force && save_may_clobber_data( be_start ) )
- {
- qof_backend_set_error (be_start, ERR_BACKEND_STORE_EXISTS);
- LEAVE("Might clobber, no force");
- return;
- }
- be->be.fullpath = be->fullpath;
- be->dirname = g_path_get_dirname (be->fullpath);
- {
- struct stat statbuf;
- int rc;
- /* Again check whether the directory can be accessed */
- rc = g_stat (be->dirname, &statbuf);
- if (rc != 0
- #if COMPILER(MSVC)
- || (statbuf.st_mode & _S_IFDIR) == 0
- #else
- || !S_ISDIR(statbuf.st_mode)
- #endif
- )
- {
- /* Error on stat or if it isn't a directory means we
- cannot find this filename */
- qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
- qof_backend_set_message (be_start, "Couldn't find directory for %s", be->fullpath);
- g_free (be->fullpath);
- be->fullpath = NULL;
- g_free (be->dirname);
- be->dirname = NULL;
- LEAVE("");
- return;
- }
- /* Now check whether we can g_stat the file itself */
- rc = g_stat (be->fullpath, &statbuf);
- if ((rc != 0) && (!create))
- {
- /* Error on stat means the file doesn't exist */
- qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
- qof_backend_set_message (be_start, "Couldn't find %s", be->fullpath);
- g_free (be->fullpath);
- be->fullpath = NULL;
- g_free (be->dirname);
- be->dirname = NULL;
- LEAVE("");
- return;
- }
- if (rc == 0
- #if COMPILER(MSVC)
- && (statbuf.st_mode & _S_IFDIR) != 0
- #else
- && S_ISDIR(statbuf.st_mode)
- #endif
- )
- {
- qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
- qof_backend_set_message(be_start, "Path %s is a directory",
- be->fullpath);
- g_free (be->fullpath);
- be->fullpath = NULL;
- g_free (be->dirname);
- be->dirname = NULL;
- LEAVE("");
- return;
- }
- }
- /* ---------------------------------------------------- */
- /* We should now have a fully resolved path name.
- * Let's start logging */
- xaccLogSetBaseName (be->fullpath);
- PINFO ("logpath=%s", be->fullpath ? be->fullpath : "(null)");
- /* And let's see if we can get a lock on it. */
- be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
- if (!ignore_lock && !gnc_xml_be_get_file_lock (be))
- {
- g_free (be->lockfile);
- be->lockfile = NULL;
- LEAVE("");
- return;
- }
- LEAVE (" ");
- return;
- }
- /* ================================================================= */
- static void
- xml_session_end(QofBackend *be_start)
- {
- FileBackend *be = (FileBackend*)be_start;
- ENTER (" ");
- if (be->linkfile)
- g_unlink (be->linkfile);
- if (be->lockfd > 0)
- close (be->lockfd);
- if (be->lockfile)
- {
- int rv;
- #ifdef G_OS_WIN32
- /* On windows, we need to allow write-access before
- g_unlink() can succeed */
- rv = g_chmod (be->lockfile, S_IWRITE | S_IREAD);
- #endif
- rv = g_unlink (be->lockfile);
- if (rv)
- {
- PWARN("Error on g_unlink(%s): %d: %s", be->lockfile,
- errno, g_strerror(errno) ? g_strerror(errno) : "");
- }
- }
- g_free (be->dirname);
- be->dirname = NULL;
- g_free (be->fullpath);
- be->fullpath = NULL;
- g_free (be->lockfile);
- be->lockfile = NULL;
- g_free (be->linkfile);
- be->linkfile = NULL;
- LEAVE (" ");
- }
- static void
- xml_destroy_backend(QofBackend *be)
- {
- /* Stop transaction logging */
- xaccLogSetBaseName (NULL);
- qof_backend_destroy(be);
- g_free(be);
- }
- /* ================================================================= */
- /* Write the financial data in a book to a file, returning FALSE on
- error and setting the error_result to indicate what went wrong if
- it's not NULL. This function does not manage file locks in any
- way.
- If make_backup is true, write out a time-stamped copy of the file
- into the same directory as the indicated file, with a filename of
- "file.YYYYMMDDHHMMSS.gnucash" where YYYYMMDDHHMMSS is replaced with the
- current year/month/day/hour/minute/second. */
- /* The variable buf_size must be a compile-time constant */
- #define buf_size 1024
- static gboolean
- copy_file(const char *orig, const char *bkup)
- {
- char buf[buf_size];
- int orig_fd;
- int bkup_fd;
- int flags = 0;
- ssize_t count_write;
- ssize_t count_read;
- #ifdef G_OS_WIN32
- flags = O_BINARY;
- #endif
- orig_fd = g_open(orig, O_RDONLY | flags, 0);
- if (orig_fd == -1)
- {
- return FALSE;
- }
- bkup_fd = g_open(bkup, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
- if (bkup_fd == -1)
- {
- close(orig_fd);
- return FALSE;
- }
- do
- {
- count_read = read(orig_fd, buf, buf_size);
- if (count_read == -1 && errno != EINTR)
- {
- close(orig_fd);
- close(bkup_fd);
- return FALSE;
- }
- if (count_read > 0)
- {
- count_write = write(bkup_fd, buf, count_read);
- if (count_write == -1)
- {
- close(orig_fd);
- close(bkup_fd);
- return FALSE;
- }
- }
- }
- while (count_read > 0);
- close(orig_fd);
- close(bkup_fd);
- return TRUE;
- }
- /* ================================================================= */
- static gboolean
- gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
- {
- gboolean copy_success = FALSE;
- int err_ret =
- #ifdef HAVE_LINK
- link (orig, bkup)
- #else
- - 1
- #endif
- ;
- if (err_ret != 0)
- {
- #ifdef HAVE_LINK
- if (errno == EPERM || errno == ENOSYS
- # ifdef EOPNOTSUPP
- || errno == EOPNOTSUPP
- # endif
- # ifdef ENOTSUP
- || errno == ENOTSUP
- # endif
- # ifdef ENOSYS
- || errno == ENOSYS
- # endif
- )
- #endif
- {
- copy_success = copy_file(orig, bkup);
- }
- if (!copy_success)
- {
- qof_backend_set_error((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR);
- PWARN ("unable to make file backup from %s to %s: %s",
- orig, bkup, g_strerror(errno) ? g_strerror(errno) : "");
- return FALSE;
- }
- }
- return TRUE;
- }
- /* ================================================================= */
- static QofBookFileType
- gnc_xml_be_determine_file_type(const char *path)
- {
- gboolean with_encoding;
- if (gnc_is_xml_data_file_v2(path, &with_encoding))
- {
- if (with_encoding)
- {
- return GNC_BOOK_XML2_FILE;
- }
- else
- {
- return GNC_BOOK_XML2_FILE_NO_ENCODING;
- }
- }
- else if (gnc_is_xml_data_file(path))
- {
- return GNC_BOOK_XML1_FILE;
- }
- return GNC_BOOK_NOT_OURS;
- }
- static gboolean
- gnc_determine_file_type (const char *uri)
- {
- struct stat sbuf;
- int rc;
- FILE *t;
- gchar *filename;
- gboolean result;
- if (!uri)
- {
- return FALSE;
- }
- filename = gnc_uri_get_path ( uri );
- if (0 == safe_strcmp(filename, QOF_STDOUT))
- {
- result = FALSE;
- goto det_exit;
- }
- t = g_fopen( filename, "r" );
- if (!t)
- {
- PINFO (" new file");
- result = TRUE;
- goto det_exit;
- }
- fclose(t);
- rc = g_stat(filename, &sbuf);
- if (rc < 0)
- {
- result = FALSE;
- goto det_exit;
- }
- if (sbuf.st_size == 0)
- {
- PINFO (" empty file");
- result = TRUE;
- goto det_exit;
- }
- if (gnc_is_xml_data_file_v2(filename, NULL))
- {
- result = TRUE;
- goto det_exit;
- }
- else if (gnc_is_xml_data_file(filename))
- {
- result = TRUE;
- goto det_exit;
- }
- PINFO (" %s is not a gnc XML file", filename);
- result = FALSE;
- det_exit:
- g_free ( filename );
- return result;
- }
- static gboolean
- gnc_xml_be_backup_file(FileBackend *be)
- {
- gboolean bkup_ret;
- char *timestamp;
- char *backup;
- const char *datafile;
- struct stat statbuf;
- int rc;
- datafile = be->fullpath;
- rc = g_stat (datafile, &statbuf);
- if (rc)
- return (errno == ENOENT);
- if (gnc_xml_be_determine_file_type(datafile) == GNC_BOOK_BIN_FILE)
- {
- /* make a more permanent safer backup */
- const char *back = "-binfmt.bkup";
- char *bin_bkup = g_new(char, strlen(datafile) + strlen(back) + 1);
- strcpy(bin_bkup, datafile);
- strcat(bin_bkup, back);
- bkup_ret = gnc_int_link_or_make_backup(be, datafile, bin_bkup);
- g_free(bin_bkup);
- if (!bkup_ret)
- {
- return FALSE;
- }
- }
- timestamp = xaccDateUtilGetStampNow ();
- backup = g_strconcat( datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL );
- g_free (timestamp);
- bkup_ret = gnc_int_link_or_make_backup(be, datafile, backup);
- g_free(backup);
- return bkup_ret;
- }
- /* ================================================================= */
- static gboolean
- gnc_xml_be_write_to_file(FileBackend *fbe,
- QofBook *book,
- const gchar *datafile,
- gboolean make_backup)
- {
- QofBackend *be = &fbe->be;
- char *tmp_name;
- struct stat statbuf;
- int rc;
- QofBackendError be_err;
- ENTER (" book=%p file=%s", book, datafile);
- /* If the book is 'clean', recently saved, then don't save again. */
- /* XXX this is currently broken due to faulty 'Save As' logic. */
- /* if (FALSE == qof_book_not_saved (book)) return FALSE; */
- tmp_name = g_new(char, strlen(datafile) + 12);
- strcpy(tmp_name, datafile);
- strcat(tmp_name, ".tmp-XXXXXX");
- if (!mktemp(tmp_name))
- {
- qof_backend_set_error(be, ERR_BACKEND_MISC);
- qof_backend_set_message( be, "Failed to make temp file" );
- LEAVE("");
- return FALSE;
- }
- if (make_backup)
- {
- if (!gnc_xml_be_backup_file(fbe))
- {
- LEAVE("");
- return FALSE;
- }
- }
- if (gnc_book_write_to_xml_file_v2(book, tmp_name, fbe->file_compression))
- {
- /* Record the file's permissions before g_unlinking it */
- rc = g_stat(datafile, &statbuf);
- if (rc == 0)
- {
- /* We must never chmod the file /dev/null */
- g_assert(safe_strcmp(tmp_name, "/dev/null") != 0);
- /* Use the permissions from the original data file */
- if (g_chmod(tmp_name, statbuf.st_mode) != 0)
- {
- /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
- /* qof_backend_set_message( be, "Failed to chmod filename %s", tmp_name ); */
- /* Even if the chmod did fail, the save
- nevertheless completed successfully. It is
- therefore wrong to signal the ERR_BACKEND_PERM
- error here which implies that the saving itself
- failed. Instead, we simply ignore this. */
- PWARN("unable to chmod filename %s: %s",
- tmp_name ? tmp_name : "(null)",
- g_strerror(errno) ? g_strerror(errno) : "");
- #if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */
- /* g_free(tmp_name); */
- /* return FALSE; */
- #endif
- }
- #ifdef HAVE_CHOWN
- /* Don't try to change the owner. Only root can do
- that. */
- if (chown(tmp_name, -1, statbuf.st_gid) != 0)
- {
- /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
- /* qof_backend_set_message( be, "Failed to chown filename %s", tmp_name ); */
- /* A failed chown doesn't mean that the saving itself
- failed. So don't abort with an error here! */
- PWARN("unable to chown filename %s: %s",
- tmp_name ? tmp_name : "(null)",
- strerror(errno) ? strerror(errno) : "");
- #if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
- /* g_free(tmp_name);
- return FALSE; */
- #endif
- }
- #endif
- }
- if (g_unlink(datafile) != 0 && errno != ENOENT)
- {
- qof_backend_set_error(be, ERR_BACKEND_READONLY);
- PWARN("unable to unlink filename %s: %s",
- datafile ? datafile : "(null)",
- g_strerror(errno) ? g_strerror(errno) : "");
- g_free(tmp_name);
- LEAVE("");
- return FALSE;
- }
- if (!gnc_int_link_or_make_backup(fbe, tmp_name, datafile))
- {
- qof_backend_set_error(be, ERR_FILEIO_BACKUP_ERROR);
- qof_backend_set_message( be, "Failed to make backup file %s",
- datafile ? datafile : "NULL" );
- g_free(tmp_name);
- LEAVE("");
- return FALSE;
- }
- if (g_unlink(tmp_name) != 0)
- {
- qof_backend_set_error(be, ERR_BACKEND_PERM);
- PWARN("unable to unlink temp filename %s: %s",
- tmp_name ? tmp_name : "(null)",
- g_strerror(errno) ? g_strerror(errno) : "");
- g_free(tmp_name);
- LEAVE("");
- return FALSE;
- }
- g_free(tmp_name);
- /* Since we successfully saved the book,
- * we should mark it clean. */
- qof_book_mark_saved (book);
- LEAVE (" successful save of book=%p to file=%s", book, datafile);
- return TRUE;
- }
- else
- {
- if (g_unlink(tmp_name) != 0)
- {
- switch (errno)
- {
- case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
- case EACCES:
- case EPERM:
- case ENOSYS:
- case EROFS:
- be_err = ERR_BACKEND_READONLY;
- break;
- default:
- be_err = ERR_BACKEND_MISC;
- }
- qof_backend_set_error(be, be_err);
- PWARN("unable to unlink temp_filename %s: %s",
- tmp_name ? tmp_name : "(null)",
- g_strerror(errno) ? g_strerror(errno) : "");
- /* already in an error just flow on through */
- }
- else
- {
- /* Use a generic write error code */
- qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
- qof_backend_set_message( be, "Unable to write to temp file %s",
- tmp_name ? tmp_name : "NULL" );
- }
- g_free(tmp_name);
- LEAVE("");
- return FALSE;
- }
- LEAVE("");
- return TRUE;
- }
- /* ================================================================= */
- /*
- * Clean up any lock files from prior crashes, and clean up old
- * backup and log files.
- */
- static void
- gnc_xml_be_remove_old_files(FileBackend *be)
- {
- const gchar *dent;
- GDir *dir;
- struct stat lockstatbuf, statbuf;
- time_t now;
- if (g_stat (be->lockfile, &lockstatbuf) != 0)
- return;
- dir = g_dir_open (be->dirname, 0, NULL);
- if (!dir)
- return;
- now = time(NULL);
- while ((dent = g_dir_read_name(dir)) != NULL)
- {
- gchar *name;
- /* Ensure we only evaluate GnuCash related files. */
- if ( !(g_str_has_suffix(dent, ".LNK") ||
- g_str_has_suffix(dent, ".xac") /* old data file extension */ ||
- g_str_has_suffix(dent, GNC_DATAFILE_EXT) ||
- g_str_has_suffix(dent, GNC_LOGFILE_EXT)) )
- continue;
- name = g_build_filename(be->dirname, dent, (gchar*)NULL);
- /* Only evaluate files associated with the current data file. */
- if (!g_str_has_prefix(name, be->fullpath))
- continue;
- /* Never remove the current data file itself */
- if (g_strcmp0(name, be->fullpath) == 0)
- continue;
- /* Test if the current file is a lock file */
- if (g_str_has_suffix(name, ".LNK"))
- {
- /* Is a lock file. Skip the active lock file */
- if ((g_strcmp0(name, be->linkfile) != 0) &&
- /* Only delete lock files older than the active one */
- (g_stat(name, &statbuf) == 0) &&
- (statbuf.st_mtime < lockstatbuf.st_mtime))
- {
- PINFO ("remove stale lock file: %s", name);
- g_unlink(name);
- }
- continue;
- }
- /* At this point we're sure the file's name is in one of these forms:
- * <fullpath/to/datafile><anything>.gnucash
- * <fullpath/to/datafile><anything>.xac
- * <fullpath/to/datafile><anything>.log
- *
- * To be a file generated by GnuCash, the <anything> part should consist
- * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
- * regular expression.
- */
- {
- /* Find the start of the date stamp. This takes some pointer
- * juggling, but considering the above tests, this should always
- * be safe */
- regex_t pattern;
- gchar *stamp_start = name + strlen(be->fullpath);
- gchar *expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
- GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
- gboolean got_date_stamp = FALSE;
- if (regcomp(&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
- PWARN("Cannot compile regex for date stamp");
- else if (regexec(&pattern, stamp_start, 0, NULL, 0) == 0)
- got_date_stamp = TRUE;
- g_free(expression);
- if (!got_date_stamp) /* Not a gnucash created file after all... */
- continue;
- }
- /* The file is a backup or log file. Check the user's retention preference
- * to determine if we should keep it or not
- */
- if (be->file_retention_type == XML_RETAIN_NONE)
- {
- PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
- g_unlink(name);
- }
- else if ((be->file_retention_type == XML_RETAIN_DAYS) &&
- (be->file_retention_days > 0))
- {
- int days;
- /* Is the backup file old enough to delete */
- if (g_stat(name, &statbuf) != 0)
- continue;
- days = (int)(difftime(now, statbuf.st_mtime) / 86400);
- PINFO ("file retention = %d days", be->file_retention_days);
- if (days >= be->file_retention_days)
- {
- PINFO ("remove stale file: %s - reason: more than %d days old", name, days);
- g_unlink(name);
- }
- }
- g_free(name);
- }
- g_dir_close (dir);
- }
- static void
- xml_sync_all(QofBackend* be, QofBook *book)
- {
- FileBackend *fbe = (FileBackend *) be;
- ENTER ("book=%p, primary=%p", book, fbe->primary_book);
- /* We make an important assumption here, that we might want to change
- * in the future: when the user says 'save', we really save the one,
- * the only, the current open book, and nothing else. We do this
- * because we assume that any other books that we are dealing with
- * are 'read-only', non-editable, because they are closed books.
- * If we ever want to have more than one book open read-write,
- * this will have to change.
- */
- if (NULL == fbe->primary_book) fbe->primary_book = book;
- if (book != fbe->primary_book) return;
- gnc_xml_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
- gnc_xml_be_remove_old_files (fbe);
- LEAVE ("book=%p", book);
- }
- /* ================================================================= */
- /* Routines to deal with the creation of multiple books.
- * The core design assumption here is that the book
- * begin-edit/commit-edit routines are used solely to write out
- * closed accounting periods to files. They're not currently
- * designed to do anything other than this. (Although they could be).
- */
- static char *
- build_period_filepath (FileBackend *fbe, QofBook *book)
- {
- int len;
- char *str, *p, *q;
- len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
- str = g_new (char, len);
- strcpy (str, fbe->fullpath);
- /* XXX it would be nice for the user if we made the book
- * closing date and/or title part of the file-name. */
- p = strrchr (str, G_DIR_SEPARATOR);
- p++;
- p = stpcpy (p, "book-");
- p = guid_to_string_buff (qof_book_get_guid(book), p);
- p = stpcpy (p, "-");
- q = strrchr (fbe->fullpath, G_DIR_SEPARATOR);
- q++;
- p = stpcpy (p, q);
- p = stpcpy (p, ".gml");
- return str;
- }
- static void
- xml_begin_edit (QofBackend *be, QofInstance *inst)
- {
- if (0) build_period_filepath(0, 0);
- #if BORKEN_FOR_NOW
- FileBackend *fbe = (FileBackend *) be;
- QofBook *book = gp;
- const char * filepath;
- QofIdTypeConst typ = QOF_INSTANCE(inst)->e_type;
- if (strcmp (GNC_ID_PERIOD, typ)) return;
- filepath = build_period_filepath(fbe, book);
- PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
- if (NULL == fbe->primary_book)
- {
- PERR ("You should have saved the data "
- "at least once before closing the books!\n");
- }
- /* XXX To be anal about it, we should really be checking to see
- * if there already is a file with this book GncGUID, and disallowing
- * further progress. This is because we are not allowed to
- * modify books that are closed (They should be treated as
- * 'read-only').
- */
- #endif
- }
- static void
- xml_rollback_edit (QofBackend *be, QofInstance *inst)
- {
- #if BORKEN_FOR_NOW
- QofBook *book = gp;
- if (strcmp (GNC_ID_PERIOD, typ)) return;
- PINFO ("book=%p", book);
- #endif
- }
- static void
- xml_commit_edit (QofBackend *be, QofInstance *inst)
- {
- if (qof_instance_get_dirty(inst) && qof_get_alt_dirty_mode() &&
- !(qof_instance_get_infant(inst) && qof_instance_get_destroying(inst)))
- {
- qof_collection_mark_dirty(qof_instance_get_collection(inst));
- qof_book_mark_dirty(qof_instance_get_book(inst));
- }
- #if BORKEN_FOR_NOW
- FileBackend *fbe = (FileBackend *) be;
- QofBook *book = gp;
- const char * filepath;
- if (strcmp (GNC_ID_PERIOD, typ)) return;
- filepath = build_period_filepath(fbe, book);
- PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
- gnc_xml_be_write_to_file(fbe, book, filepath, FALSE);
- /* We want to force a save of the current book at this point,
- * because if we don't, and the user forgets to do so, then
- * there'll be the same transactions in the closed book,
- * and also in the current book. */
- gnc_xml_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
- #endif
- }
- /* ---------------------------------------------------------------------- */
- /* Load financial data from a file into the book, automatically
- detecting the format of the file, if possible. Return FALSE on
- error, and set the error parameter to indicate what went wrong if
- it's not NULL. This function does not manage file locks in any
- way. */
- static void
- gnc_xml_be_load_from_file (QofBackend *bend, QofBook *book, QofBackendLoadType loadType)
- {
- QofBackendError error;
- gboolean rc;
- FileBackend *be = (FileBackend *) bend;
- if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
- error = ERR_BACKEND_NO_ERR;
- be->primary_book = book;
- switch (gnc_xml_be_determine_file_type(be->fullpath))
- {
- case GNC_BOOK_XML2_FILE:
- rc = qof_session_load_from_xml_file_v2 (be, book);
- if (FALSE == rc)
- {
- PWARN( "Syntax error in Xml File %s", be->fullpath );
- error = ERR_FILEIO_PARSE_ERROR;
- }
- break;
- case GNC_BOOK_XML2_FILE_NO_ENCODING:
- error = ERR_FILEIO_NO_ENCODING;
- PWARN( "No character encoding in Xml File %s", be->fullpath );
- break;
- case GNC_BOOK_XML1_FILE:
- rc = qof_session_load_from_xml_file (book, be->fullpath);
- if (FALSE == rc)
- {
- PWARN( "Syntax error in Xml File %s", be->fullpath );
- error = ERR_FILEIO_PARSE_ERROR;
- }
- break;
- default:
- /* If file type wasn't known, check errno again to give the
- user some more useful feedback for some particular error
- conditions. */
- switch (errno)
- {
- case EACCES: /* No read permission */
- PWARN("No read permission to file");
- error = ERR_FILEIO_FILE_EACCES;
- break;
- case EISDIR: /* File is a directory - but on this error we don't arrive here */
- PWARN("Filename is a directory");
- error = ERR_FILEIO_FILE_NOT_FOUND;
- break;
- default:
- PWARN("File not any known type");
- error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
- break;
- }
- break;
- }
- if (error != ERR_BACKEND_NO_ERR)
- {
- qof_backend_set_error(bend, error);
- }
- /* We just got done loading, it can't possibly be dirty !! */
- qof_book_mark_saved (book);
- }
- /* ---------------------------------------------------------------------- */
- static gboolean
- save_may_clobber_data (QofBackend *bend)
- {
- struct stat statbuf;
- if (!bend->fullpath) return FALSE;
- /* FIXME: Make sure this doesn't need more sophisticated semantics
- * in the face of special file, devices, pipes, symlinks, etc. */
- if (g_stat(bend->fullpath, &statbuf) == 0) return TRUE;
- return FALSE;
- }
- static void
- gnc_xml_be_write_accounts_to_file(QofBackend *be, QofBook *book)
- {
- const gchar *datafile;
- datafile = ((FileBackend *)be)->fullpath;
- gnc_book_write_accounts_to_xml_file_v2(be, book, datafile);
- }
- /* ================================================================= */
- #if 0 //def GNUCASH_MAJOR_VERSION
- QofBackend *
- libgncmod_backend_file_LTX_gnc_backend_new(void)
- {
- fbe->dirname = NULL;
- fbe->fullpath = NULL;
- fbe->lockfile = NULL;
- fbe->linkfile = NULL;
- fbe->lockfd = -1;
- fbe->primary_book = NULL;
- return be;
- }
- #endif
- static void
- retain_changed_cb(GConfEntry *entry, gpointer user_data)
- {
- FileBackend *be = (FileBackend*)user_data;
- g_return_if_fail(be != NULL);
- be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
- }
- static void
- retain_type_changed_cb(GConfEntry *entry, gpointer user_data)
- {
- FileBackend *be = (FileBackend*)user_data;
- gchar *choice = NULL;
- g_return_if_fail(be != NULL);
- choice = gnc_gconf_get_string(GCONF_GENERAL, KEY_RETAIN_TYPE, NULL);
- if (!choice)
- choice = g_strdup("days");
- if (safe_strcmp (choice, "never") == 0)
- be->file_retention_type = XML_RETAIN_NONE;
- else if (safe_strcmp (choice, "forever") == 0)
- be->file_retention_type = XML_RETAIN_ALL;
- else
- {
- if (safe_strcmp (choice, "days") != 0)
- PERR("bad value '%s'", choice ? choice : "(null)");
- be->file_retention_type = XML_RETAIN_DAYS;
- }
- g_free (choice);
- }
- static void
- compression_changed_cb(GConfEntry *entry, gpointer user_data)
- {
- FileBackend *be = (FileBackend*)user_data;
- g_return_if_fail(be != NULL);
- be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
- }
- static QofBackend*
- gnc_backend_new(void)
- {
- FileBackend *gnc_be;
- QofBackend *be;
- gnc_be = g_new0(FileBackend, 1);
- be = (QofBackend*) gnc_be;
- qof_backend_init(be);
- be->session_begin = xml_session_begin;
- be->session_end = xml_session_end;
- be->destroy_backend = xml_destroy_backend;
- be->load = gnc_xml_be_load_from_file;
- /* The file backend treats accounting periods transactionally. */
- be->begin = xml_begin_edit;
- be->commit = xml_commit_edit;
- be->rollback = xml_rollback_edit;
- /* The file backend always loads all data ... */
- be->compile_query = NULL;
- be->free_query = NULL;
- be->run_query = NULL;
- /* The file backend will never be multi-user... */
- be->events_pending = NULL;
- be->process_events = NULL;
- be->sync = xml_sync_all;
- be->load_config = NULL;
- be->get_config = NULL;
- be->export_fn = gnc_xml_be_write_accounts_to_file;
- gnc_be->dirname = NULL;
- gnc_be->fullpath = NULL;
- gnc_be->lockfile = NULL;
- gnc_be->linkfile = NULL;
- gnc_be->lockfd = -1;
- gnc_be->primary_book = NULL;
- gnc_be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
- gnc_be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
- retain_type_changed_cb(NULL, (gpointer)be); /* Get retain_type from gconf */
- if ( (gnc_be->file_retention_type == XML_RETAIN_DAYS) &&
- (gnc_be->file_retention_days == 0 ) )
- {
- /* Backwards compatibility code. Pre 2.3.15, 0 retain_days meant
- * "keep forever". From 2.3.15 on this is controlled via a multiple
- * choice ("retain_type"). So if we find a 0 retain_days value with
- * a "days" retain_type, we should interpret it as if we got a
- * "forever" retain_type.
- */
- gnc_be->file_retention_type = XML_RETAIN_ALL;
- gnc_gconf_set_string (GCONF_GENERAL, KEY_RETAIN_TYPE, "forever", NULL);
- }
- gnc_gconf_general_register_cb(KEY_RETAIN_DAYS, retain_changed_cb, be);
- gnc_gconf_general_register_cb(KEY_RETAIN_TYPE, retain_type_changed_cb, be);
- gnc_gconf_general_register_cb(KEY_FILE_COMPRESSION, compression_changed_cb, be);
- return be;
- }
- static void
- business_core_xml_init(void)
- {
- /* Initialize our pointers into the backend subsystem */
- gnc_address_xml_initialize ();
- gnc_billterm_xml_initialize ();
- gnc_customer_xml_initialize ();
- gnc_employee_xml_initialize ();
- gnc_entry_xml_initialize ();
- gnc_invoice_xml_initialize ();
- gnc_job_xml_initialize ();
- gnc_order_xml_initialize ();
- gnc_owner_xml_initialize ();
- gnc_taxtable_xml_initialize ();
- gnc_vendor_xml_initialize ();
- }
- static void
- gnc_provider_free (QofBackendProvider *prov)
- {
- prov->provider_name = NULL;
- prov->access_method = NULL;
- g_free (prov);
- }
- #ifndef GNC_NO_LOADABLE_MODULES
- G_MODULE_EXPORT void
- qof_backend_module_init(void)
- {
- gnc_module_init_backend_xml();
- }
- #endif
- void
- gnc_module_init_backend_xml(void)
- {
- QofBackendProvider *prov;
- prov = g_new0 (QofBackendProvider, 1);
- prov->provider_name = "GnuCash File Backend Version 2";
- prov->access_method = "file";
- prov->partial_book_supported = FALSE;
- prov->backend_new = gnc_backend_new;
- prov->provider_free = gnc_provider_free;
- prov->check_data_type = gnc_determine_file_type;
- qof_backend_register_provider (prov);
- prov = g_new0 (QofBackendProvider, 1);
- prov->provider_name = "GnuCash File Backend Version 2";
- prov->access_method = "xml";
- prov->partial_book_supported = FALSE;
- prov->backend_new = gnc_backend_new;
- prov->provider_free = gnc_provider_free;
- prov->check_data_type = gnc_determine_file_type;
- qof_backend_register_provider (prov);
- /* And the business objects */
- business_core_xml_init();
- }
- /* ========================== END OF FILE ===================== */
- /* For emacs we set some variables concerning indentation:
- * Local Variables: *
- * indent-tabs-mode:nil *
- * c-basic-offset:4 *
- * tab-width:8 *
- * End: */