All pastes #1203022 Raw Edit

gnc-backend-file.c blamed

public text v1 · immutable
#1203022 ·published 2008-09-15 02:44 UTC
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 ===================== */