rendered paste body 9012 linas /********************************************************************
9012 linas * gnc-backend-file.c: load and save data to files *
9012 linas * *
9012 linas * This program is free software; you can redistribute it and/or *
9012 linas * modify it under the terms of the GNU General Public License as *
9012 linas * published by the Free Software Foundation; either version 2 of *
9012 linas * the License, or (at your option) any later version. *
9012 linas * *
9012 linas * This program is distributed in the hope that it will be useful, *
9012 linas * but WITHOUT ANY WARRANTY; without even the implied warranty of *
9012 linas * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
9012 linas * GNU General Public License for more details. *
9012 linas * *
9012 linas * You should have received a copy of the GNU General Public License*
9012 linas * along with this program; if not, contact: *
9012 linas * *
9012 linas * Free Software Foundation Voice: +1-617-542-5942 *
11964 hampton * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
11964 hampton * Boston, MA 02110-1301, USA gnu@gnu.org *
9012 linas \********************************************************************/
9012 linas /** @file gnc-backend-file.c
11565 hampton * @brief load and save data to files
9012 linas * @author Copyright (c) 2000 Gnumatic Inc.
9012 linas * @author Copyright (c) 2002 Derek Atkins <warlord@MIT.EDU>
9012 linas * @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
6459 linas *
9012 linas * This file implements the top-level QofBackend API for saving/
9012 linas * restoring data to/from an ordinary Unix filesystem file.
9012 linas */
5075 peticolas
6824 hampton #define _GNU_SOURCE
6824 hampton
11994 hampton #include "config.h"
11994 hampton
11981 hampton #include <glib.h>
11981 hampton #include <glib/gi18n.h>
11994 hampton #include <libintl.h>
11986 hampton #include <locale.h>
6824 hampton #include <stdio.h>
5075 peticolas #include <fcntl.h>
5075 peticolas #include <limits.h>
5075 peticolas #include <sys/stat.h>
5075 peticolas #include <sys/types.h>
5075 peticolas #include <unistd.h>
5075 peticolas #include <errno.h>
5075 peticolas #include <string.h>
6824 hampton #include <dirent.h>
6824 hampton #include <time.h>
5075 peticolas
5075 peticolas #include "TransLog.h"
9012 linas #include "gnc-engine.h"
11565 hampton
10063 linas #include "gnc-filepath-utils.h"
9012 linas
5075 peticolas #include "io-gncxml.h"
5075 peticolas #include "io-gncbin.h"
5075 peticolas #include "io-gncxml-v2.h"
5088 peticolas #include "gnc-backend-api.h"
9012 linas #include "gnc-backend-file.h"
13084 jsled #include "gnc-gconf-utils.h"
5075 peticolas
11565 hampton #define GNC_BE_DAYS "file_retention_days"
11565 hampton #define GNC_BE_ZIP "file_compression"
8765 linas
11565 hampton static QofLogModule log_module = GNC_MOD_BACKEND;
5075 peticolas
9010 linas /* ================================================================= */
9010 linas
5075 peticolas static gboolean
5075 peticolas gnc_file_be_get_file_lock (FileBackend *be)
5075 peticolas {
5075 peticolas struct stat statbuf;
5075 peticolas char pathbuf[PATH_MAX];
5075 peticolas char *path = NULL;
5075 peticolas int rc;
8941 warlord QofBackendError be_err;
5075 peticolas
5075 peticolas rc = stat (be->lockfile, &statbuf);
5075 peticolas if (!rc)
5075 peticolas {
8941 warlord /* oops .. file is locked by another user .. */
8765 linas qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas be->lockfd = open (be->lockfile, O_RDWR | O_CREAT | O_EXCL , 0);
5075 peticolas if (be->lockfd < 0)
5075 peticolas {
8941 warlord /* oops .. we can't create the lockfile .. */
8941 warlord switch (errno) {
9010 linas case EACCES:
9010 linas case EROFS:
9010 linas case ENOSPC:
9010 linas be_err = ERR_BACKEND_READONLY;
9010 linas break;
9010 linas default:
9010 linas be_err = ERR_BACKEND_LOCKED;
9010 linas break;
9010 linas }
8941 warlord qof_backend_set_error ((QofBackend*)be, be_err);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas /* OK, now work around some NFS atomic lock race condition
5075 peticolas * mumbo-jumbo. We do this by linking a unique file, and
5075 peticolas * then examing the link count. At least that's what the
5075 peticolas * NFS programmers guide suggests.
5075 peticolas * Note: the "unique filename" must be unique for the
5075 peticolas * triplet filename-host-process, otherwise accidental
5075 peticolas * aliases can occur.
5075 peticolas */
5075 peticolas
5075 peticolas /* apparently, even this code may not work for some NFS
5075 peticolas * implementations. In the long run, I am told that
5075 peticolas * ftp.debian.org
5075 peticolas * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
5075 peticolas * provides a better long-term solution.
5075 peticolas */
5075 peticolas
5075 peticolas strcpy (pathbuf, be->lockfile);
5075 peticolas path = strrchr (pathbuf, '.');
5075 peticolas sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
5075 peticolas
5075 peticolas rc = link (be->lockfile, pathbuf);
5075 peticolas if (rc)
5075 peticolas {
5075 peticolas /* If hard links aren't supported, just allow the lock. */
5075 peticolas if (errno == EOPNOTSUPP || errno == EPERM)
5075 peticolas {
5075 peticolas be->linkfile = NULL;
5075 peticolas return TRUE;
5075 peticolas }
5075 peticolas
5075 peticolas /* Otherwise, something else is wrong. */
8765 linas qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
5075 peticolas unlink (pathbuf);
5075 peticolas close (be->lockfd);
5075 peticolas unlink (be->lockfile);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas rc = stat (be->lockfile, &statbuf);
5075 peticolas if (rc)
5075 peticolas {
5075 peticolas /* oops .. stat failed! This can't happen! */
8765 linas qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
5075 peticolas unlink (pathbuf);
5075 peticolas close (be->lockfd);
5075 peticolas unlink (be->lockfile);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas if (statbuf.st_nlink != 2)
5075 peticolas {
8765 linas qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
5075 peticolas unlink (pathbuf);
5075 peticolas close (be->lockfd);
5075 peticolas unlink (be->lockfile);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas be->linkfile = g_strdup (pathbuf);
5075 peticolas
5075 peticolas return TRUE;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
5075 peticolas
10063 linas static void
10063 linas file_session_begin(QofBackend *be_start, QofSession *session,
10063 linas const char *book_id,
10063 linas gboolean ignore_lock, gboolean create_if_nonexistent)
5075 peticolas {
10063 linas FileBackend *be = (FileBackend*) be_start;
10063 linas char *p;
5075 peticolas
10063 linas ENTER (" ");
10063 linas
10063 linas /* Make sure the directory is there */
10063 linas be->dirname = xaccResolveFilePath(book_id);
10063 linas if (NULL == be->dirname)
5075 peticolas {
10063 linas qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
10063 linas return;
5075 peticolas }
10063 linas be->fullpath = g_strdup (be->dirname);
10063 linas be->be.fullpath = be->fullpath;
10063 linas p = strrchr (be->dirname, '/');
10063 linas if (p && p != be->dirname)
10063 linas {
10063 linas struct stat statbuf;
10063 linas int rc;
5075 peticolas
10063 linas *p = '\0';
10063 linas rc = stat (be->dirname, &statbuf);
10063 linas if (rc != 0 || !S_ISDIR(statbuf.st_mode))
10063 linas {
10063 linas qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
10063 linas g_free (be->fullpath); be->fullpath = NULL;
10063 linas g_free (be->dirname); be->dirname = NULL;
10063 linas return;
10063 linas }
10063 linas rc = stat (be->fullpath, &statbuf);
10063 linas if (rc == 0 && S_ISDIR(statbuf.st_mode))
10063 linas {
10063 linas qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
10063 linas g_free (be->fullpath); be->fullpath = NULL;
10063 linas g_free (be->dirname); be->dirname = NULL;
10063 linas return;
10063 linas }
5075 peticolas }
5075 peticolas
10063 linas /* ---------------------------------------------------- */
10063 linas /* We should now have a fully resolved path name.
10063 linas * Lets see if we can get a lock on it. */
10063 linas
10063 linas be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
10063 linas
10063 linas if (!ignore_lock && !gnc_file_be_get_file_lock (be))
5075 peticolas {
10063 linas g_free (be->lockfile); be->lockfile = NULL;
10063 linas return;
5075 peticolas }
10063 linas
10063 linas LEAVE (" ");
10063 linas return;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
5075 peticolas
6459 linas static void
10063 linas file_session_end(QofBackend *be_start)
5075 peticolas {
10063 linas FileBackend *be = (FileBackend*)be_start;
10063 linas ENTER (" ");
5075 peticolas
10063 linas if (be->linkfile)
10063 linas unlink (be->linkfile);
9012 linas
10063 linas if (be->lockfd > 0)
10063 linas close (be->lockfd);
6459 linas
10063 linas if (be->lockfile)
10063 linas unlink (be->lockfile);
6459 linas
10063 linas g_free (be->dirname);
10063 linas be->dirname = NULL;
5075 peticolas
10063 linas g_free (be->fullpath);
10063 linas be->fullpath = NULL;
6459 linas
10063 linas g_free (be->lockfile);
10063 linas be->lockfile = NULL;
9012 linas
10063 linas g_free (be->linkfile);
10063 linas be->linkfile = NULL;
10063 linas LEAVE (" ");
5075 peticolas }
5075 peticolas
10063 linas static void
10063 linas file_destroy_backend(QofBackend *be)
10063 linas {
10063 linas g_free(be);
10063 linas }
10063 linas
10063 linas /* ================================================================= */
5075 peticolas /* Write the financial data in a book to a file, returning FALSE on
5075 peticolas error and setting the error_result to indicate what went wrong if
5075 peticolas it's not NULL. This function does not manage file locks in any
5075 peticolas way.
5075 peticolas
5075 peticolas If make_backup is true, write out a time-stamped copy of the file
5075 peticolas into the same directory as the indicated file, with a filename of
5075 peticolas "file.YYYYMMDDHHMMSS.xac" where YYYYMMDDHHMMSS is replaced with the
5075 peticolas current year/month/day/hour/minute/second. */
5075 peticolas
5075 peticolas static gboolean
5075 peticolas copy_file(const char *orig, const char *bkup)
5075 peticolas {
5075 peticolas static int buf_size = 1024;
9010 linas char buf[buf_size];
5075 peticolas int orig_fd;
5075 peticolas int bkup_fd;
5075 peticolas ssize_t count_write;
5075 peticolas ssize_t count_read;
5075 peticolas
5075 peticolas orig_fd = open(orig, O_RDONLY);
5075 peticolas if(orig_fd == -1)
5075 peticolas {
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas bkup_fd = creat(bkup, 0600);
5075 peticolas if(bkup_fd == -1)
5075 peticolas {
5075 peticolas close(orig_fd);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas do
5075 peticolas {
5075 peticolas count_read = read(orig_fd, buf, buf_size);
5075 peticolas if(count_read == -1 && errno != EINTR)
5075 peticolas {
5075 peticolas close(orig_fd);
5075 peticolas close(bkup_fd);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas if(count_read > 0)
5075 peticolas {
5075 peticolas count_write = write(bkup_fd, buf, count_read);
5075 peticolas if(count_write == -1)
5075 peticolas {
5075 peticolas close(orig_fd);
5075 peticolas close(bkup_fd);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas }
5075 peticolas } while(count_read > 0);
5075 peticolas
5075 peticolas close(orig_fd);
5075 peticolas close(bkup_fd);
5075 peticolas
5075 peticolas return TRUE;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
10063 linas
5075 peticolas static gboolean
5075 peticolas gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
5075 peticolas {
5075 peticolas int err_ret = link(orig, bkup);
5075 peticolas if(err_ret != 0)
5075 peticolas {
5075 peticolas if(errno == EPERM || errno == EOPNOTSUPP)
5075 peticolas {
5075 peticolas err_ret = copy_file(orig, bkup);
5075 peticolas }
5075 peticolas
5075 peticolas if(!err_ret)
5075 peticolas {
8765 linas qof_backend_set_error((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR);
6424 linas PWARN ("unable to make file backup from %s to %s: %s",
6424 linas orig, bkup, strerror(errno) ? strerror(errno) : "");
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas }
5075 peticolas
5075 peticolas return TRUE;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
10063 linas
5075 peticolas static gboolean
10063 linas is_gzipped_file(const gchar *name)
10063 linas {
10063 linas unsigned char buf[2];
10063 linas int fd = open(name, O_RDONLY);
10063 linas
10063 linas if(fd == 0)
10063 linas {
10063 linas return FALSE;
10063 linas }
10063 linas
10063 linas if(read(fd, buf, 2) != 2)
10063 linas {
10063 linas return FALSE;
10063 linas }
10063 linas
10063 linas if(buf[0] == 037 && buf[1] == 0213)
10063 linas {
10063 linas return TRUE;
10063 linas }
10063 linas
10063 linas return FALSE;
10063 linas }
10063 linas
10063 linas static QofBookFileType
10063 linas gnc_file_be_determine_file_type(const char *path)
10063 linas {
13084 jsled if (gnc_is_xml_data_file_v2(path)) {
13084 jsled return GNC_BOOK_XML2_FILE;
13084 jsled } else if (gnc_is_xml_data_file(path)) {
13084 jsled return GNC_BOOK_XML1_FILE;
13084 jsled } else if (is_gzipped_file(path)) {
13084 jsled return GNC_BOOK_XML2_FILE;
13084 jsled } else if (gnc_is_bin_file(path)) {
13084 jsled return GNC_BOOK_BIN_FILE;
13084 jsled }
13084 jsled return GNC_BOOK_NOT_OURS;
10063 linas }
10063 linas
11565 hampton static gboolean
11565 hampton gnc_determine_file_type (const char *path)
11565 hampton {
11565 hampton struct stat sbuf;
11565 hampton int rc;
11565 hampton FILE *t;
10063 linas
11565 hampton if (!path) { return FALSE; }
11565 hampton if (0 == safe_strcmp(path, QOF_STDOUT)) { return FALSE; }
11565 hampton t = fopen(path, "r");
11565 hampton if(!t) { PINFO (" new file"); return TRUE; }
11565 hampton fclose(t);
11565 hampton rc = stat(path, &sbuf);
11565 hampton if(rc < 0) { return FALSE; }
11565 hampton if (sbuf.st_size == 0) { PINFO (" empty file"); return TRUE; }
11565 hampton if(gnc_is_xml_data_file_v2(path)) { return TRUE; }
11565 hampton else if(gnc_is_xml_data_file(path)) { return TRUE; }
11565 hampton else if(is_gzipped_file(path)) { return TRUE; }
11565 hampton else if(gnc_is_bin_file(path)) { return TRUE; }
11565 hampton PINFO (" %s is not a gnc file", path);
11565 hampton return FALSE;
11565 hampton }
11565 hampton
10063 linas static gboolean
5075 peticolas gnc_file_be_backup_file(FileBackend *be)
5075 peticolas {
5075 peticolas gboolean bkup_ret;
5075 peticolas char *timestamp;
5075 peticolas char *backup;
5075 peticolas const char *datafile;
5075 peticolas struct stat statbuf;
5075 peticolas int rc;
5075 peticolas
5075 peticolas datafile = be->fullpath;
5075 peticolas
5075 peticolas rc = stat (datafile, &statbuf);
5075 peticolas if (rc)
5075 peticolas return (errno == ENOENT);
5075 peticolas
5075 peticolas if(gnc_file_be_determine_file_type(datafile) == GNC_BOOK_BIN_FILE)
5075 peticolas {
5075 peticolas /* make a more permament safer backup */
5075 peticolas const char *back = "-binfmt.bkup";
5075 peticolas char *bin_bkup = g_new(char, strlen(datafile) + strlen(back) + 1);
5075 peticolas strcpy(bin_bkup, datafile);
5075 peticolas strcat(bin_bkup, back);
5075 peticolas bkup_ret = gnc_int_link_or_make_backup(be, datafile, bin_bkup);
5075 peticolas g_free(bin_bkup);
5075 peticolas if(!bkup_ret)
5075 peticolas {
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas }
5075 peticolas
5075 peticolas timestamp = xaccDateUtilGetStampNow ();
5075 peticolas backup = g_new (char, strlen (datafile) + strlen (timestamp) + 6);
5075 peticolas strcpy (backup, datafile);
5075 peticolas strcat (backup, ".");
5075 peticolas strcat (backup, timestamp);
5075 peticolas strcat (backup, ".xac");
5075 peticolas g_free (timestamp);
5075 peticolas
5075 peticolas bkup_ret = gnc_int_link_or_make_backup(be, datafile, backup);
5075 peticolas g_free(backup);
5075 peticolas
5075 peticolas return bkup_ret;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
10063 linas
5075 peticolas static gboolean
9010 linas gnc_file_be_write_to_file(FileBackend *fbe,
9010 linas QofBook *book,
9010 linas const gchar *datafile,
9010 linas gboolean make_backup)
5075 peticolas {
9010 linas QofBackend *be = &fbe->be;
5075 peticolas char *tmp_name;
7121 cstim struct stat statbuf;
7121 cstim int rc;
8949 warlord QofBackendError be_err;
5075 peticolas
9010 linas ENTER (" book=%p file=%s", book, datafile);
9012 linas
9012 linas /* If the book is 'clean', recently saved, then don't save again. */
9014 linas /* XXX this is currently broken due to faulty 'Save As' logic. */
9014 linas /* if (FALSE == qof_book_not_saved (book)) return FALSE; */
9012 linas
5075 peticolas tmp_name = g_new(char, strlen(datafile) + 12);
5075 peticolas strcpy(tmp_name, datafile);
5075 peticolas strcat(tmp_name, ".tmp-XXXXXX");
5075 peticolas
5075 peticolas if(!mktemp(tmp_name))
5075 peticolas {
9010 linas qof_backend_set_error(be, ERR_BACKEND_MISC);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas
5075 peticolas if(make_backup)
5075 peticolas {
9010 linas if(!gnc_file_be_backup_file(fbe))
5075 peticolas {
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas }
5075 peticolas
13097 jsled if (gnc_book_write_to_xml_file_v2(book, tmp_name, fbe->file_compression))
5075 peticolas {
7121 cstim /* Record the file's permissions before unlinking it */
7121 cstim rc = stat(datafile, &statbuf);
7121 cstim if(rc == 0)
7121 cstim {
7121 cstim /* Use the permissions from the original data file */
7121 cstim if(chmod(tmp_name, statbuf.st_mode) != 0)
7121 cstim {
9012 linas qof_backend_set_error(be, ERR_BACKEND_PERM);
12132 cstim /* FIXME: Even if the chmod did fail, the save
12132 cstim nevertheless completed successfully. It is
12132 cstim therefore wrong to signal the ERR_BACKEND_PERM
12132 cstim error here which implies that the saving itself
12132 cstim failed! What should we do? */
7121 cstim PWARN("unable to chmod filename %s: %s",
12132 cstim tmp_name ? tmp_name : "(null)",
7121 cstim strerror(errno) ? strerror(errno) : "");
9012 linas #if VFAT_DOESNT_SUCK /* chmod always fails on vfat fs */
7121 cstim g_free(tmp_name);
7121 cstim return FALSE;
8947 warlord #endif
7121 cstim }
12132 cstim /* Don't try to change the owner. Only root can do
12132 cstim that. */
12132 cstim if(chown(tmp_name, -1, statbuf.st_gid) != 0)
8814 hampton {
12132 cstim /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
12132 cstim /* A failed chown doesn't mean that the saving itself
12132 cstim failed. So don't abort with an error here! */
8814 hampton PWARN("unable to chown filename %s: %s",
12132 cstim tmp_name ? tmp_name : "(null)",
8814 hampton strerror(errno) ? strerror(errno) : "");
9012 linas #if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
12132 cstim /* g_free(tmp_name);
12132 cstim return FALSE; */
8947 warlord #endif
8814 hampton }
7121 cstim }
5075 peticolas if(unlink(datafile) != 0 && errno != ENOENT)
5075 peticolas {
9012 linas qof_backend_set_error(be, ERR_FILEIO_BACKUP_ERROR);
6424 linas PWARN("unable to unlink filename %s: %s",
6424 linas datafile ? datafile : "(null)",
6424 linas strerror(errno) ? strerror(errno) : "");
5075 peticolas g_free(tmp_name);
5075 peticolas return FALSE;
5075 peticolas }
9010 linas if(!gnc_int_link_or_make_backup(fbe, tmp_name, datafile))
5075 peticolas {
9012 linas qof_backend_set_error(be, ERR_FILEIO_BACKUP_ERROR);
5075 peticolas g_free(tmp_name);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas if(unlink(tmp_name) != 0)
5075 peticolas {
9012 linas qof_backend_set_error(be, ERR_BACKEND_PERM);
6424 linas PWARN("unable to unlink temp filename %s: %s",
6424 linas tmp_name ? tmp_name : "(null)",
6424 linas strerror(errno) ? strerror(errno) : "");
5075 peticolas g_free(tmp_name);
5075 peticolas return FALSE;
5075 peticolas }
5075 peticolas g_free(tmp_name);
9012 linas
9012 linas /* Since we successfully saved the book,
9012 linas * we should mark it clean. */
9012 linas qof_book_mark_saved (book);
9012 linas LEAVE (" sucessful save of book=%p to file=%s", book, datafile);
5075 peticolas return TRUE;
5075 peticolas }
5075 peticolas else
5075 peticolas {
5075 peticolas if(unlink(tmp_name) != 0)
5075 peticolas {
9010 linas switch (errno) {
9010 linas case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
9010 linas case EACCES:
9010 linas case EPERM:
9010 linas case EROFS:
9010 linas be_err = ERR_BACKEND_READONLY;
9010 linas break;
9010 linas default:
9010 linas be_err = ERR_BACKEND_MISC;
9010 linas }
9010 linas qof_backend_set_error(be, be_err);
6424 linas PWARN("unable to unlink temp_filename %s: %s",
6424 linas tmp_name ? tmp_name : "(null)",
6424 linas strerror(errno) ? strerror(errno) : "");
5075 peticolas /* already in an error just flow on through */
5075 peticolas }
5075 peticolas g_free(tmp_name);
5075 peticolas return FALSE;
5075 peticolas }
9010 linas return TRUE;
5075 peticolas }
5075 peticolas
10063 linas /* ================================================================= */
10063 linas
10063 linas static int
10063 linas gnc_file_be_select_files (const struct dirent *d)
10063 linas {
10063 linas int len = strlen(d->d_name) - 4;
10063 linas
10063 linas if (len <= 0)
10063 linas return(0);
10063 linas
10063 linas return((strcmp(d->d_name + len, ".LNK") == 0) ||
10063 linas (strcmp(d->d_name + len, ".xac") == 0) ||
10063 linas (strcmp(d->d_name + len, ".log") == 0));
10063 linas }
10063 linas
7225 hampton static void
10063 linas gnc_file_be_remove_old_files(FileBackend *be)
10063 linas {
10063 linas struct dirent *dent;
10063 linas DIR *dir;
10063 linas struct stat lockstatbuf, statbuf;
10063 linas int pathlen;
10063 linas time_t now;
10063 linas
10063 linas if (stat (be->lockfile, &lockstatbuf) != 0)
10063 linas return;
10063 linas pathlen = strlen(be->fullpath);
10063 linas
10063 linas /*
10063 linas * Clean up any lockfiles from prior crashes, and clean up old
10063 linas * data and log files. Scandir will do a fist pass on the
10063 linas * filenames and cull the directory down to just files with the
10063 linas * appropriate extensions. Pity you can't pass user data into
10063 linas * scandir...
10063 linas */
10063 linas
10063 linas /*
10063 linas * Unfortunately scandir() is not portable, so re-write this
10063 linas * function without it. Note that this version will be even a bit
10063 linas * faster because it does not have to sort, malloc, or anything
10063 linas * else that scandir did, and it only performs a single pass
10063 linas * through the directory rather than one pass through the
10063 linas * directory and then one pass over the 'matching' files. --
10063 linas * warlord@MIT.EDU 2002-05-06
10063 linas */
10063 linas
10063 linas dir = opendir (be->dirname);
10063 linas if (!dir)
10063 linas return;
10063 linas
10063 linas now = time(NULL);
10063 linas while((dent = readdir(dir)) != NULL) {
10063 linas char *name;
10063 linas int len;
10063 linas
10063 linas if (gnc_file_be_select_files (dent) == 0)
10063 linas continue;
10063 linas
10063 linas name = g_strconcat(be->dirname, "/", dent->d_name, NULL);
10063 linas len = strlen(name) - 4;
10063 linas
10063 linas /* Is this file associated with the current data file */
10063 linas if (strncmp(name, be->fullpath, pathlen) == 0)
10063 linas {
10063 linas if ((safe_strcmp(name + len, ".LNK") == 0) &&
10063 linas /* Is a lock file. Skip the active lock file */
10063 linas (safe_strcmp(name, be->linkfile) != 0) &&
10063 linas /* Only delete lock files older than the active one */
10063 linas (stat(name, &statbuf) == 0) &&
10063 linas (statbuf.st_mtime <lockstatbuf.st_mtime))
10063 linas {
10063 linas PINFO ("unlink lock file: %s", name);
10063 linas unlink(name);
10063 linas }
13097 jsled else if (be->file_retention_days > 0)
10063 linas {
10063 linas time_t file_time;
10063 linas struct tm file_tm;
10063 linas int days;
10063 linas const char* res;
10063 linas
13097 jsled PINFO ("file retention = %d days", be->file_retention_days);
10063 linas
10063 linas /* Is the backup file old enough to delete */
10063 linas memset(&file_tm, 0, sizeof(file_tm));
10063 linas res = strptime(name+pathlen+1, "%Y%m%d%H%M%S", &file_tm);
10063 linas file_time = mktime(&file_tm);
10063 linas days = (int)(difftime(now, file_time) / 86400);
10063 linas
13084 jsled
13084 jsled if (res
13084 jsled && res != name+pathlen+1
13084 jsled && (strcmp(res, ".xac") == 0
13084 jsled || strcmp(res, ".log") == 0)
13084 jsled && file_time > 0
13097 jsled && days > be->file_retention_days)
10063 linas {
10063 linas PINFO ("unlink stale (%d days old) file: %s", days, name);
10063 linas unlink(name);
10063 linas }
10063 linas }
10063 linas }
10063 linas g_free(name);
10063 linas }
10063 linas closedir (dir);
10063 linas }
10063 linas
10063 linas static void
10063 linas file_sync_all(QofBackend* be, QofBook *book)
10063 linas {
10063 linas FileBackend *fbe = (FileBackend *) be;
10063 linas ENTER ("book=%p, primary=%p", book, fbe->primary_book);
10063 linas
10063 linas /* We make an important assumption here, that we might want to change
10063 linas * in the future: when the user says 'save', we really save the one,
10063 linas * the only, the current open book, and nothing else. We do this
10063 linas * because we assume that any other books that we are dealing with
10063 linas * are 'read-only', non-editable, because they are closed books.
10063 linas * If we ever want to have more than one book open read-write,
10063 linas * this will have to change.
10063 linas */
10063 linas if (NULL == fbe->primary_book) fbe->primary_book = book;
10063 linas if (book != fbe->primary_book) return;
10063 linas
10063 linas gnc_file_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
10063 linas gnc_file_be_remove_old_files (fbe);
10063 linas LEAVE ("book=%p", book);
10063 linas }
10063 linas
10063 linas /* ================================================================= */
10063 linas /* Routines to deal with the creation of multiple books.
10063 linas * The core design assumption here is that the book
10063 linas * begin-edit/commit-edit routines are used solely to write out
10063 linas * closed accounting periods to files. They're not currently
10063 linas * designed to do anything other than this. (Although they could be).
10063 linas */
10063 linas
10063 linas static char *
10063 linas build_period_filepath (FileBackend *fbe, QofBook *book)
10063 linas {
10063 linas int len;
10063 linas char *str, *p, *q;
10063 linas
10063 linas len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
10063 linas str = g_new (char, len);
10063 linas strcpy (str, fbe->fullpath);
10063 linas
10063 linas /* XXX it would be nice for the user if we made the book
10063 linas * closing date and/or title part of the file-name. */
10063 linas p = strrchr (str, '/');
10063 linas p++;
10063 linas p = stpcpy (p, "book-");
10063 linas p = guid_to_string_buff (qof_book_get_guid(book), p);
10063 linas p = stpcpy (p, "-");
10063 linas q = strrchr (fbe->fullpath, '/');
10063 linas q++;
10063 linas p = stpcpy (p, q);
10063 linas p = stpcpy (p, ".gml");
10063 linas
10063 linas return str;
10063 linas }
10063 linas
10063 linas static void
10063 linas file_begin_edit (QofBackend *be, QofInstance *inst)
10063 linas {
10063 linas if (0) build_period_filepath(0, 0);
10063 linas #if BORKEN_FOR_NOW
10063 linas FileBackend *fbe = (FileBackend *) be;
10063 linas QofBook *book = gp;
10063 linas const char * filepath;
10063 linas
10063 linas QofIdTypeConst typ = QOF_ENTITY(inst)->e_type;
10063 linas if (strcmp (GNC_ID_PERIOD, typ)) return;
10063 linas filepath = build_period_filepath(fbe, book);
10063 linas PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
10063 linas
10063 linas if (NULL == fbe->primary_book)
10063 linas {
10063 linas PERR ("You should have saved the data "
10063 linas "at least once before closing the books!\n");
10063 linas }
10063 linas /* XXX To be anal about it, we should really be checking to see
10063 linas * if there already is a file with this book GUID, and disallowing
10063 linas * further progress. This is because we are not allowed to
10063 linas * modify books that are closed (They should be treated as
10063 linas * 'read-only').
10063 linas */
10063 linas #endif
10063 linas }
10063 linas
10063 linas static void
10063 linas file_rollback_edit (QofBackend *be, QofInstance *inst)
10063 linas {
10063 linas #if BORKEN_FOR_NOW
10063 linas QofBook *book = gp;
10063 linas
10063 linas if (strcmp (GNC_ID_PERIOD, typ)) return;
10063 linas PINFO ("book=%p", book);
10063 linas #endif
10063 linas }
10063 linas
10063 linas static void
10063 linas file_commit_edit (QofBackend *be, QofInstance *inst)
10063 linas {
10063 linas #if BORKEN_FOR_NOW
10063 linas FileBackend *fbe = (FileBackend *) be;
10063 linas QofBook *book = gp;
10063 linas const char * filepath;
10063 linas
10063 linas if (strcmp (GNC_ID_PERIOD, typ)) return;
10063 linas filepath = build_period_filepath(fbe, book);
10063 linas PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
10063 linas gnc_file_be_write_to_file(fbe, book, filepath, FALSE);
10063 linas
10063 linas /* We want to force a save of the current book at this point,
10063 linas * because if we don't, and the user forgets to do so, then
10063 linas * there'll be the same transactions in the closed book,
10063 linas * and also in the current book. */
10063 linas gnc_file_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
10063 linas #endif
10063 linas }
10063 linas
10063 linas /* ---------------------------------------------------------------------- */
10063 linas
10063 linas
11565 hampton /* Load financial data from a file into the book, automatically
10063 linas detecting the format of the file, if possible. Return FALSE on
10063 linas error, and set the error parameter to indicate what went wrong if
10063 linas it's not NULL. This function does not manage file locks in any
10063 linas way. */
10063 linas
10063 linas static void
10063 linas gnc_file_be_load_from_file (QofBackend *bend, QofBook *book)
10063 linas {
11565 hampton QofBackendError error;
10063 linas gboolean rc;
10063 linas FileBackend *be = (FileBackend *) bend;
10063 linas
13084 jsled error = ERR_BACKEND_NO_ERR;
10063 linas be->primary_book = book;
10063 linas
10063 linas switch (gnc_file_be_determine_file_type(be->fullpath))
10063 linas {
10063 linas case GNC_BOOK_XML2_FILE:
10063 linas rc = qof_session_load_from_xml_file_v2 (be, book);
10063 linas if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
10063 linas break;
10063 linas
10063 linas case GNC_BOOK_XML1_FILE:
10063 linas rc = qof_session_load_from_xml_file (book, be->fullpath);
10063 linas if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
10063 linas break;
10063 linas case GNC_BOOK_BIN_FILE:
10063 linas qof_session_load_from_binfile(book, be->fullpath);
10063 linas error = gnc_get_binfile_io_error();
10063 linas break;
10063 linas default:
10063 linas PWARN("File not any known type");
10063 linas error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
10063 linas break;
10063 linas }
10063 linas
10063 linas if(error != ERR_BACKEND_NO_ERR)
10063 linas {
10063 linas qof_backend_set_error(bend, error);
10063 linas }
10063 linas
10063 linas /* We just got done loading, it can't possibly be dirty !! */
10063 linas qof_book_mark_saved (book);
10063 linas }
10063 linas
10063 linas /* ---------------------------------------------------------------------- */
10063 linas
10063 linas static gboolean
10063 linas gnc_file_be_save_may_clobber_data (QofBackend *bend)
10063 linas {
10063 linas struct stat statbuf;
10063 linas if (!bend->fullpath) return FALSE;
10063 linas
10063 linas /* FIXME: Make sure this doesn't need more sophisticated semantics
10063 linas * in the face of special file, devices, pipes, symlinks, etc. */
10063 linas if (stat(bend->fullpath, &statbuf) == 0) return TRUE;
10063 linas return FALSE;
10063 linas }
10063 linas
10063 linas
10063 linas static void
8765 linas gnc_file_be_write_accounts_to_file(QofBackend *be, QofBook *book)
7225 hampton {
7225 hampton const gchar *datafile;
7225 hampton
7225 hampton datafile = ((FileBackend *)be)->fullpath;
7225 hampton gnc_book_write_accounts_to_xml_file_v2(be, book, datafile);
7225 hampton }
7225 hampton
10063 linas /* ================================================================= */
11565 hampton #if 0 //def GNUCASH_MAJOR_VERSION
10063 linas QofBackend *
10063 linas libgncmod_backend_file_LTX_gnc_backend_new(void)
10063 linas {
10063 linas
10063 linas fbe->dirname = NULL;
10063 linas fbe->fullpath = NULL;
10063 linas fbe->lockfile = NULL;
10063 linas fbe->linkfile = NULL;
10063 linas fbe->lockfd = -1;
10063 linas
10063 linas fbe->primary_book = NULL;
10063 linas
10063 linas return be;
10063 linas }
11565 hampton #endif
13097 jsled
13097 jsled static void
13097 jsled retain_changed_cb(GConfEntry *entry, gpointer user_data)
13097 jsled {
13097 jsled FileBackend *be = (FileBackend*)user_data;
13097 jsled g_return_if_fail(be != NULL);
13097 jsled be->file_retention_days = (int)gnc_gconf_get_float("general", "retain_days", NULL);
13097 jsled }
13097 jsled
13097 jsled static void
13097 jsled compression_changed_cb(GConfEntry *entry, gpointer user_data)
13097 jsled {
13097 jsled FileBackend *be = (FileBackend*)user_data;
13097 jsled g_return_if_fail(be != NULL);
13097 jsled be->file_compression = gnc_gconf_get_bool("general", "file_compression", NULL);
13097 jsled }
13097 jsled
11565 hampton QofBackend*
11565 hampton gnc_backend_new(void)
11565 hampton {
11565 hampton FileBackend *gnc_be;
11565 hampton QofBackend *be;
10063 linas
11565 hampton gnc_be = g_new0(FileBackend, 1);
11565 hampton be = (QofBackend*) gnc_be;
11565 hampton qof_backend_init(be);
11565 hampton
11565 hampton be->session_begin = file_session_begin;
11565 hampton be->session_end = file_session_end;
11565 hampton be->destroy_backend = file_destroy_backend;
11565 hampton
11565 hampton be->load = gnc_file_be_load_from_file;
11565 hampton be->save_may_clobber_data = gnc_file_be_save_may_clobber_data;
11565 hampton
11565 hampton /* The file backend treats accounting periods transactionally. */
11565 hampton be->begin = file_begin_edit;
11565 hampton be->commit = file_commit_edit;
11565 hampton be->rollback = file_rollback_edit;
11565 hampton
11565 hampton /* The file backend always loads all data ... */
11565 hampton be->compile_query = NULL;
11565 hampton be->free_query = NULL;
11565 hampton be->run_query = NULL;
11565 hampton
11565 hampton be->counter = NULL;
11565 hampton
11565 hampton /* The file backend will never be multi-user... */
11565 hampton be->events_pending = NULL;
11565 hampton be->process_events = NULL;
11565 hampton
11565 hampton be->sync = file_sync_all;
13097 jsled be->load_config = NULL;
13097 jsled be->get_config = NULL;
11565 hampton
13417 jsled be->export = gnc_file_be_write_accounts_to_file;
13417 jsled
11565 hampton gnc_be->dirname = NULL;
11565 hampton gnc_be->fullpath = NULL;
11565 hampton gnc_be->lockfile = NULL;
11565 hampton gnc_be->linkfile = NULL;
11565 hampton gnc_be->lockfd = -1;
11565 hampton
11565 hampton gnc_be->primary_book = NULL;
13097 jsled
13097 jsled gnc_be->file_retention_days = (int)gnc_gconf_get_float("general", "retain_days", NULL);
13097 jsled gnc_be->file_compression = gnc_gconf_get_bool("general", "file_compression", NULL);
13273 jsled
13097 jsled gnc_gconf_general_register_cb("retain_days", retain_changed_cb, be);
13097 jsled gnc_gconf_general_register_cb("file_compression", compression_changed_cb, be);
13097 jsled
11565 hampton return be;
11565 hampton }
11565 hampton
11565 hampton static void
11565 hampton gnc_provider_free (QofBackendProvider *prov)
11565 hampton {
11565 hampton prov->provider_name = NULL;
11565 hampton prov->access_method = NULL;
11565 hampton g_free (prov);
11565 hampton }
11565 hampton
11565 hampton void
11565 hampton gnc_provider_init(void)
11565 hampton {
11565 hampton QofBackendProvider *prov;
11565 hampton #ifdef ENABLE_NLS
11565 hampton setlocale (LC_ALL, "");
11565 hampton bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
11565 hampton bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
11565 hampton textdomain (GETTEXT_PACKAGE);
11565 hampton #endif
11565 hampton prov = g_new0 (QofBackendProvider, 1);
11565 hampton prov->provider_name = "GnuCash File Backend Version 2";
11565 hampton prov->access_method = "file";
11565 hampton prov->partial_book_supported = FALSE;
11565 hampton prov->backend_new = gnc_backend_new;
11565 hampton prov->provider_free = gnc_provider_free;
11565 hampton prov->check_data_type = gnc_determine_file_type;
11565 hampton qof_backend_register_provider (prov);
11565 hampton }
11565 hampton
9010 linas /* ========================== END OF FILE ===================== */