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 9012 linas * @author Copyright (c) 2003 Linas Vepstas 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 11981 hampton #include 11994 hampton #include 11986 hampton #include 6824 hampton #include 5075 peticolas #include 5075 peticolas #include 5075 peticolas #include 5075 peticolas #include 5075 peticolas #include 5075 peticolas #include 5075 peticolas #include 6824 hampton #include 6824 hampton #include 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 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 ===================== */