GnuCash 2.4.99
gnc-backend-xml.c
Go to the documentation of this file.
00001 /********************************************************************
00002  * gnc-backend-xml.c: load and save data to XML files               *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00031 #include "config.h"
00032 
00033 #include <glib.h>
00034 #include <glib/gi18n.h>
00035 #include <glib/gstdio.h>
00036 #include <libintl.h>
00037 #include <locale.h>
00038 #include <fcntl.h>
00039 #include <limits.h>
00040 #include <sys/stat.h>
00041 #include <sys/types.h>
00042 #include <regex.h>
00043 #ifdef HAVE_UNISTD_H
00044 # include <unistd.h>
00045 #else
00046 # ifdef _MSC_VER
00047 typedef int ssize_t;
00048 # endif
00049 #endif
00050 #include <errno.h>
00051 #include <string.h>
00052 #ifdef HAVE_DIRENT_H
00053 # include <dirent.h>
00054 #endif
00055 #include <time.h>
00056 #ifdef G_OS_WIN32
00057 # include <io.h>
00058 # define close _close
00059 # define mktemp _mktemp
00060 # define read _read
00061 # define write _write
00062 #endif
00063 #include "platform.h"
00064 #if COMPILER(MSVC)
00065 # define g_fopen fopen
00066 # define g_open _open
00067 #endif
00068 
00069 #include "qof.h"
00070 #include "TransLog.h"
00071 #include "gnc-engine.h"
00072 
00073 #include "gnc-uri-utils.h"
00074 
00075 #include "io-gncxml.h"
00076 #include "io-gncxml-v2.h"
00077 #include "gnc-backend-xml.h"
00078 #include "gnc-gconf-utils.h"
00079 
00080 #include "gnc-address-xml-v2.h"
00081 #include "gnc-bill-term-xml-v2.h"
00082 #include "gnc-customer-xml-v2.h"
00083 #include "gnc-employee-xml-v2.h"
00084 #include "gnc-entry-xml-v2.h"
00085 #include "gnc-invoice-xml-v2.h"
00086 #include "gnc-job-xml-v2.h"
00087 #include "gnc-order-xml-v2.h"
00088 #include "gnc-owner-xml-v2.h"
00089 #include "gnc-tax-table-xml-v2.h"
00090 #include "gnc-vendor-xml-v2.h"
00091 
00092 #ifndef HAVE_STRPTIME
00093 # include "strptime.h"
00094 #endif
00095 
00096 #define KEY_FILE_COMPRESSION  "file_compression"
00097 #define KEY_RETAIN_TYPE "retain_type"
00098 #define KEY_RETAIN_DAYS "retain_days"
00099 
00100 static QofLogModule log_module = GNC_MOD_BACKEND;
00101 
00102 static gboolean save_may_clobber_data (QofBackend *bend);
00103 
00104 /* ================================================================= */
00105 
00106 static gboolean
00107 gnc_xml_be_get_file_lock (FileBackend *be)
00108 {
00109     struct stat statbuf;
00110 #ifndef G_OS_WIN32
00111     char *pathbuf = NULL, *path = NULL;
00112     size_t pathbuf_size = 0;
00113 #endif
00114     int rc;
00115     QofBackendError be_err;
00116 
00117     rc = g_stat (be->lockfile, &statbuf);
00118     if (!rc)
00119     {
00120         /* oops .. file is locked by another user  .. */
00121         qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
00122         return FALSE;
00123     }
00124 
00125     be->lockfd = g_open (be->lockfile, O_RDWR | O_CREAT | O_EXCL , S_IRUSR | S_IWUSR);
00126     if (be->lockfd < 0)
00127     {
00128         /* oops .. we can't create the lockfile .. */
00129         switch (errno)
00130         {
00131         case EACCES:
00132         case EROFS:
00133         case ENOSPC:
00134             PWARN( "Unable to create the lockfile %s; may not have write priv",
00135                    be->lockfile );
00136             be_err = ERR_BACKEND_READONLY;
00137             break;
00138         default:
00139             be_err = ERR_BACKEND_LOCKED;
00140             break;
00141         }
00142         qof_backend_set_error ((QofBackend*)be, be_err);
00143         return FALSE;
00144     }
00145 
00146     /* OK, now work around some NFS atomic lock race condition
00147      * mumbo-jumbo.  We do this by linking a unique file, and
00148      * then examining the link count.  At least that's what the
00149      * NFS programmers guide suggests.
00150      * Note: the "unique filename" must be unique for the
00151      * triplet filename-host-process, otherwise accidental
00152      * aliases can occur.
00153      */
00154 
00155     /* apparently, even this code may not work for some NFS
00156      * implementations. In the long run, I am told that
00157      * ftp.debian.org
00158      *  /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
00159      * provides a better long-term solution.
00160      */
00161 
00162 #ifndef G_OS_WIN32
00163     pathbuf_size = strlen (be->lockfile) + 100;
00164     pathbuf = (char *) malloc (pathbuf_size);
00165     strcpy (pathbuf, be->lockfile);
00166     path = strrchr (pathbuf, '.');
00167     while (snprintf (path, pathbuf_size - (path - pathbuf), ".%lx.%d.LNK", gethostid(), getpid())
00168             >= pathbuf_size - (path - pathbuf))
00169     {
00170         pathbuf_size += 100;
00171         pathbuf = (char *) realloc (pathbuf, pathbuf_size);
00172     }
00173 
00174     rc = link (be->lockfile, pathbuf);
00175     if (rc)
00176     {
00177         /* If hard links aren't supported, just allow the lock. */
00178         if (errno == EPERM || errno == ENOSYS
00179 # ifdef EOPNOTSUPP
00180                 || errno == EOPNOTSUPP
00181 # endif
00182 # ifdef ENOTSUP
00183                 || errno == ENOTSUP
00184 # endif
00185            )
00186         {
00187             be->linkfile = NULL;
00188             free (pathbuf);
00189             return TRUE;
00190         }
00191 
00192         /* Otherwise, something else is wrong. */
00193         qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
00194         g_unlink (pathbuf);
00195         free (pathbuf);
00196         close (be->lockfd);
00197         g_unlink (be->lockfile);
00198         return FALSE;
00199     }
00200 
00201     rc = g_stat (be->lockfile, &statbuf);
00202     if (rc)
00203     {
00204         /* oops .. stat failed!  This can't happen! */
00205         qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
00206         qof_backend_set_message ((QofBackend*)be, "Failed to stat lockfile %s",
00207                                  be->lockfile );
00208         g_unlink (pathbuf);
00209         free (pathbuf);
00210         close (be->lockfd);
00211         g_unlink (be->lockfile);
00212         return FALSE;
00213     }
00214 
00215     if (statbuf.st_nlink != 2)
00216     {
00217         qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
00218         g_unlink (pathbuf);
00219         free (pathbuf);
00220         close (be->lockfd);
00221         g_unlink (be->lockfile);
00222         return FALSE;
00223     }
00224 
00225     be->linkfile = g_strdup (pathbuf);
00226     free (pathbuf);
00227 
00228     return TRUE;
00229 
00230 #else /* ifndef G_OS_WIN32 */
00231     /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
00232        is sufficient for locking. */
00233     be->linkfile = NULL;
00234     return TRUE;
00235 #endif /* ifndef G_OS_WIN32 */
00236 }
00237 
00238 /* ================================================================= */
00239 #define XML_URI_PREFIX "xml://"
00240 #define FILE_URI_PREFIX "file://"
00241 
00242 static void
00243 xml_session_begin(QofBackend *be_start, QofSession *session,
00244                   const char *book_id, gboolean ignore_lock,
00245                   gboolean create, gboolean force)
00246 {
00247     FileBackend *be = (FileBackend*) be_start;
00248 
00249     ENTER (" ");
00250 
00251     /* Make sure the directory is there */
00252     be->fullpath = gnc_uri_get_path (book_id);
00253 
00254     if (NULL == be->fullpath)
00255     {
00256         qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
00257         qof_backend_set_message (be_start, "No path specified");
00258         LEAVE("");
00259         return;
00260     }
00261     if (create && !force && save_may_clobber_data( be_start ) )
00262     {
00263         qof_backend_set_error (be_start, ERR_BACKEND_STORE_EXISTS);
00264         LEAVE("Might clobber, no force");
00265         return;
00266     }
00267 
00268     be->be.fullpath = be->fullpath;
00269     be->dirname = g_path_get_dirname (be->fullpath);
00270 
00271     {
00272         struct stat statbuf;
00273         int rc;
00274 
00275         /* Again check whether the directory can be accessed */
00276         rc = g_stat (be->dirname, &statbuf);
00277         if (rc != 0
00278 #if COMPILER(MSVC)
00279                 || (statbuf.st_mode & _S_IFDIR) == 0
00280 #else
00281                 || !S_ISDIR(statbuf.st_mode)
00282 #endif
00283            )
00284         {
00285             /* Error on stat or if it isn't a directory means we
00286                cannot find this filename */
00287             qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
00288             qof_backend_set_message (be_start, "Couldn't find directory for %s", be->fullpath);
00289             PWARN ("Couldn't find directory for %s", be->fullpath);
00290             g_free (be->fullpath);
00291             be->fullpath = NULL;
00292             g_free (be->dirname);
00293             be->dirname = NULL;
00294             LEAVE("");
00295             return;
00296         }
00297 
00298         /* Now check whether we can g_stat the file itself */
00299         rc = g_stat (be->fullpath, &statbuf);
00300         if ((rc != 0) && (!create))
00301         {
00302             /* Error on stat means the file doesn't exist */
00303             qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
00304             qof_backend_set_message (be_start, "Couldn't find %s", be->fullpath);
00305             PWARN ("Couldn't find %s", be->fullpath);
00306             g_free (be->fullpath);
00307             be->fullpath = NULL;
00308             g_free (be->dirname);
00309             be->dirname = NULL;
00310             LEAVE("");
00311             return;
00312         }
00313         if (rc == 0
00314 #if COMPILER(MSVC)
00315                 && (statbuf.st_mode & _S_IFDIR) != 0
00316 #else
00317                 && S_ISDIR(statbuf.st_mode)
00318 #endif
00319            )
00320         {
00321             qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
00322             qof_backend_set_message(be_start, "Path %s is a directory",
00323                                     be->fullpath);
00324             PWARN("Path %s is a directory", be->fullpath);
00325             g_free (be->fullpath);
00326             be->fullpath = NULL;
00327             g_free (be->dirname);
00328             be->dirname = NULL;
00329             LEAVE("");
00330             return;
00331         }
00332     }
00333 
00334 
00335     /* ---------------------------------------------------- */
00336     /* We should now have a fully resolved path name.
00337      * Let's start logging */
00338     xaccLogSetBaseName (be->fullpath);
00339     PINFO ("logpath=%s", be->fullpath ? be->fullpath : "(null)");
00340 
00341     /* And let's see if we can get a lock on it. */
00342     be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
00343 
00344     if (!ignore_lock && !gnc_xml_be_get_file_lock (be))
00345     {
00346         // We should not ignore the lock, but couldn't get it. The
00347         // be_get_file_lock() already set the appropriate backend_error in this
00348         // case, so we just return here.
00349         g_free (be->lockfile);
00350         be->lockfile = NULL;
00351 
00352         if (force)
00353         {
00354             QofBackendError berror = qof_backend_get_error(be_start);
00355             if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
00356             {
00357                 // Even though we couldn't get the lock, we were told to force
00358                 // the opening. This is ok because the FORCE argument is
00359                 // changed only if the caller wants a read-only book.
00360             }
00361             else
00362             {
00363                 // Unknown error. Push it again on the error stack.
00364                 qof_backend_set_error(be_start, berror);
00365             }
00366         }
00367 
00368         LEAVE("");
00369         return;
00370     }
00371 
00372     LEAVE (" ");
00373     return;
00374 }
00375 
00376 /* ================================================================= */
00377 
00378 static void
00379 xml_session_end(QofBackend *be_start)
00380 {
00381     FileBackend *be = (FileBackend*)be_start;
00382     ENTER (" ");
00383 
00384     if ( be->book && qof_book_is_readonly( be->book ) )
00385     {
00386         qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_READONLY );
00387         return;
00388     }
00389 
00390     if (be->linkfile)
00391         g_unlink (be->linkfile);
00392 
00393     if (be->lockfd > 0)
00394         close (be->lockfd);
00395 
00396     if (be->lockfile)
00397     {
00398         int rv;
00399 #ifdef G_OS_WIN32
00400         /* On windows, we need to allow write-access before
00401            g_unlink() can succeed */
00402         rv = g_chmod (be->lockfile, S_IWRITE | S_IREAD);
00403 #endif
00404         rv = g_unlink (be->lockfile);
00405         if (rv)
00406         {
00407             PWARN("Error on g_unlink(%s): %d: %s", be->lockfile,
00408                   errno, g_strerror(errno) ? g_strerror(errno) : "");
00409         }
00410     }
00411 
00412     g_free (be->dirname);
00413     be->dirname = NULL;
00414 
00415     g_free (be->fullpath);
00416     be->fullpath = NULL;
00417 
00418     g_free (be->lockfile);
00419     be->lockfile = NULL;
00420 
00421     g_free (be->linkfile);
00422     be->linkfile = NULL;
00423     LEAVE (" ");
00424 }
00425 
00426 static void
00427 xml_destroy_backend(QofBackend *be)
00428 {
00429     /* Stop transaction logging */
00430     xaccLogSetBaseName (NULL);
00431 
00432     qof_backend_destroy(be);
00433     g_free(be);
00434 }
00435 
00436 /* ================================================================= */
00437 /* Write the financial data in a book to a file, returning FALSE on
00438    error and setting the error_result to indicate what went wrong if
00439    it's not NULL.  This function does not manage file locks in any
00440    way.
00441 
00442    If make_backup is true, write out a time-stamped copy of the file
00443    into the same directory as the indicated file, with a filename of
00444    "file.YYYYMMDDHHMMSS.gnucash" where YYYYMMDDHHMMSS is replaced with the
00445    current year/month/day/hour/minute/second. */
00446 
00447 /* The variable buf_size must be a compile-time constant */
00448 #define buf_size 1024
00449 
00450 static gboolean
00451 copy_file(const char *orig, const char *bkup)
00452 {
00453     char buf[buf_size];
00454     int orig_fd;
00455     int bkup_fd;
00456     int flags = 0;
00457     ssize_t count_write;
00458     ssize_t count_read;
00459 
00460 #ifdef G_OS_WIN32
00461     flags = O_BINARY;
00462 #endif
00463 
00464     orig_fd = g_open(orig, O_RDONLY | flags, 0);
00465     if (orig_fd == -1)
00466     {
00467         return FALSE;
00468     }
00469     bkup_fd = g_open(bkup, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
00470     if (bkup_fd == -1)
00471     {
00472         close(orig_fd);
00473         return FALSE;
00474     }
00475 
00476     do
00477     {
00478         count_read = read(orig_fd, buf, buf_size);
00479         if (count_read == -1 && errno != EINTR)
00480         {
00481             close(orig_fd);
00482             close(bkup_fd);
00483             return FALSE;
00484         }
00485 
00486         if (count_read > 0)
00487         {
00488             count_write = write(bkup_fd, buf, count_read);
00489             if (count_write == -1)
00490             {
00491                 close(orig_fd);
00492                 close(bkup_fd);
00493                 return FALSE;
00494             }
00495         }
00496     }
00497     while (count_read > 0);
00498 
00499     close(orig_fd);
00500     close(bkup_fd);
00501 
00502     return TRUE;
00503 }
00504 
00505 /* ================================================================= */
00506 
00507 static gboolean
00508 gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
00509 {
00510     gboolean copy_success = FALSE;
00511     int err_ret =
00512 #ifdef HAVE_LINK
00513         link (orig, bkup)
00514 #else
00515         - 1
00516 #endif
00517         ;
00518     if (err_ret != 0)
00519     {
00520 #ifdef HAVE_LINK
00521         if (errno == EPERM || errno == ENOSYS
00522 # ifdef EOPNOTSUPP
00523                 || errno == EOPNOTSUPP
00524 # endif
00525 # ifdef ENOTSUP
00526                 || errno == ENOTSUP
00527 # endif
00528 # ifdef ENOSYS
00529                 || errno == ENOSYS
00530 # endif
00531            )
00532 #endif
00533         {
00534             copy_success = copy_file(orig, bkup);
00535         }
00536 
00537         if (!copy_success)
00538         {
00539             qof_backend_set_error((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR);
00540             PWARN ("unable to make file backup from %s to %s: %s",
00541                    orig, bkup, g_strerror(errno) ? g_strerror(errno) : "");
00542             return FALSE;
00543         }
00544     }
00545 
00546     return TRUE;
00547 }
00548 
00549 /* ================================================================= */
00550 
00551 static QofBookFileType
00552 gnc_xml_be_determine_file_type(const char *path)
00553 {
00554     gboolean with_encoding;
00555     QofBookFileType v2type;
00556 
00557     v2type = gnc_is_xml_data_file_v2(path, &with_encoding);
00558     if (v2type == GNC_BOOK_XML2_FILE)
00559     {
00560         if (with_encoding)
00561         {
00562             return GNC_BOOK_XML2_FILE;
00563         }
00564         else
00565         {
00566             return GNC_BOOK_XML2_FILE_NO_ENCODING;
00567         }
00568     }
00569     else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
00570     {
00571         return GNC_BOOK_POST_XML2_0_0_FILE;
00572     }
00573     else if (v2type == GNC_BOOK_XML1_FILE)
00574     {
00575         return GNC_BOOK_XML1_FILE;
00576     }
00577     return GNC_BOOK_NOT_OURS;
00578 }
00579 
00580 static gboolean
00581 gnc_determine_file_type (const char *uri)
00582 {
00583     struct stat sbuf;
00584     int rc;
00585     FILE *t;
00586     gchar *filename;
00587     QofBookFileType xml_type;
00588     gboolean result;
00589 
00590     if (!uri)
00591     {
00592         return FALSE;
00593     }
00594 
00595     filename = gnc_uri_get_path ( uri );
00596     if (0 == safe_strcmp(filename, QOF_STDOUT))
00597     {
00598         result = FALSE;
00599         goto det_exit;
00600     }
00601     t = g_fopen( filename, "r" );
00602     if (!t)
00603     {
00604         PINFO (" new file");
00605         result = TRUE;
00606         goto det_exit;
00607     }
00608     fclose(t);
00609     rc = g_stat(filename, &sbuf);
00610     if (rc < 0)
00611     {
00612         result = FALSE;
00613         goto det_exit;
00614     }
00615     if (sbuf.st_size == 0)
00616     {
00617         PINFO (" empty file");
00618         result = TRUE;
00619         goto det_exit;
00620     }
00621     xml_type = gnc_is_xml_data_file_v2(filename, NULL);
00622     if ((xml_type == GNC_BOOK_XML2_FILE) ||
00623             (xml_type == GNC_BOOK_XML1_FILE) ||
00624             (xml_type == GNC_BOOK_POST_XML2_0_0_FILE))
00625     {
00626         result = TRUE;
00627         goto det_exit;
00628     }
00629     PINFO (" %s is not a gnc XML file", filename);
00630     result = FALSE;
00631 
00632 det_exit:
00633     g_free ( filename );
00634     return result;
00635 }
00636 
00637 static gboolean
00638 gnc_xml_be_backup_file(FileBackend *be)
00639 {
00640     gboolean bkup_ret;
00641     char *timestamp;
00642     char *backup;
00643     const char *datafile;
00644     struct stat statbuf;
00645     int rc;
00646 
00647     datafile = be->fullpath;
00648 
00649     rc = g_stat (datafile, &statbuf);
00650     if (rc)
00651         return (errno == ENOENT);
00652 
00653     if (gnc_xml_be_determine_file_type(datafile) == GNC_BOOK_BIN_FILE)
00654     {
00655         /* make a more permanent safer backup */
00656         const char *back = "-binfmt.bkup";
00657         char *bin_bkup = g_new(char, strlen(datafile) + strlen(back) + 1);
00658         strcpy(bin_bkup, datafile);
00659         strcat(bin_bkup, back);
00660         bkup_ret = gnc_int_link_or_make_backup(be, datafile, bin_bkup);
00661         g_free(bin_bkup);
00662         if (!bkup_ret)
00663         {
00664             return FALSE;
00665         }
00666     }
00667 
00668     timestamp = xaccDateUtilGetStampNow ();
00669     backup = g_strconcat( datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL );
00670     g_free (timestamp);
00671 
00672     bkup_ret = gnc_int_link_or_make_backup(be, datafile, backup);
00673     g_free(backup);
00674 
00675     return bkup_ret;
00676 }
00677 
00678 /* ================================================================= */
00679 
00680 static gboolean
00681 gnc_xml_be_write_to_file(FileBackend *fbe,
00682                          QofBook *book,
00683                          const gchar *datafile,
00684                          gboolean make_backup)
00685 {
00686     QofBackend *be = &fbe->be;
00687     char *tmp_name;
00688     struct stat statbuf;
00689     int rc;
00690     QofBackendError be_err;
00691 
00692     ENTER (" book=%p file=%s", book, datafile);
00693 
00694     if (book && qof_book_is_readonly(book))
00695     {
00696         /* Are we read-only? Don't continue in this case. */
00697         qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_READONLY );
00698         LEAVE("");
00699         return FALSE;
00700     }
00701 
00702     /* If the book is 'clean', recently saved, then don't save again. */
00703     /* XXX this is currently broken due to faulty 'Save As' logic. */
00704     /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */
00705 
00706     tmp_name = g_new(char, strlen(datafile) + 12);
00707     strcpy(tmp_name, datafile);
00708     strcat(tmp_name, ".tmp-XXXXXX");
00709 
00710     if (!mktemp(tmp_name))
00711     {
00712         qof_backend_set_error(be, ERR_BACKEND_MISC);
00713         qof_backend_set_message( be, "Failed to make temp file" );
00714         LEAVE("");
00715         return FALSE;
00716     }
00717 
00718     if (make_backup)
00719     {
00720         if (!gnc_xml_be_backup_file(fbe))
00721         {
00722             LEAVE("");
00723             return FALSE;
00724         }
00725     }
00726 
00727     if (gnc_book_write_to_xml_file_v2(book, tmp_name, fbe->file_compression))
00728     {
00729         /* Record the file's permissions before g_unlinking it */
00730         rc = g_stat(datafile, &statbuf);
00731         if (rc == 0)
00732         {
00733             /* We must never chmod the file /dev/null */
00734             g_assert(safe_strcmp(tmp_name, "/dev/null") != 0);
00735 
00736             /* Use the permissions from the original data file */
00737             if (g_chmod(tmp_name, statbuf.st_mode) != 0)
00738             {
00739                 /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
00740                 /* qof_backend_set_message( be, "Failed to chmod filename %s", tmp_name ); */
00741                 /* Even if the chmod did fail, the save
00742                    nevertheless completed successfully. It is
00743                    therefore wrong to signal the ERR_BACKEND_PERM
00744                    error here which implies that the saving itself
00745                    failed. Instead, we simply ignore this. */
00746                 PWARN("unable to chmod filename %s: %s",
00747                       tmp_name ? tmp_name : "(null)",
00748                       g_strerror(errno) ? g_strerror(errno) : "");
00749 #if VFAT_DOESNT_SUCK  /* chmod always fails on vfat/samba fs */
00750                 /* g_free(tmp_name); */
00751                 /* return FALSE; */
00752 #endif
00753             }
00754 #ifdef HAVE_CHOWN
00755             /* Don't try to change the owner. Only root can do
00756                that. */
00757             if (chown(tmp_name, -1, statbuf.st_gid) != 0)
00758             {
00759                 /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
00760                 /* qof_backend_set_message( be, "Failed to chown filename %s", tmp_name ); */
00761                 /* A failed chown doesn't mean that the saving itself
00762                 failed. So don't abort with an error here! */
00763                 PWARN("unable to chown filename %s: %s",
00764                       tmp_name ? tmp_name : "(null)",
00765                       strerror(errno) ? strerror(errno) : "");
00766 #if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
00767                 /* g_free(tmp_name);
00768                 return FALSE; */
00769 #endif
00770             }
00771 #endif
00772         }
00773         if (g_unlink(datafile) != 0 && errno != ENOENT)
00774         {
00775             qof_backend_set_error(be, ERR_BACKEND_READONLY);
00776             PWARN("unable to unlink filename %s: %s",
00777                   datafile ? datafile : "(null)",
00778                   g_strerror(errno) ? g_strerror(errno) : "");
00779             g_free(tmp_name);
00780             LEAVE("");
00781             return FALSE;
00782         }
00783         if (!gnc_int_link_or_make_backup(fbe, tmp_name, datafile))
00784         {
00785             qof_backend_set_error(be, ERR_FILEIO_BACKUP_ERROR);
00786             qof_backend_set_message( be, "Failed to make backup file %s",
00787                                      datafile ? datafile : "NULL" );
00788             g_free(tmp_name);
00789             LEAVE("");
00790             return FALSE;
00791         }
00792         if (g_unlink(tmp_name) != 0)
00793         {
00794             qof_backend_set_error(be, ERR_BACKEND_PERM);
00795             PWARN("unable to unlink temp filename %s: %s",
00796                   tmp_name ? tmp_name : "(null)",
00797                   g_strerror(errno) ? g_strerror(errno) : "");
00798             g_free(tmp_name);
00799             LEAVE("");
00800             return FALSE;
00801         }
00802         g_free(tmp_name);
00803 
00804         /* Since we successfully saved the book,
00805          * we should mark it clean. */
00806         qof_book_mark_session_saved (book);
00807         LEAVE (" successful save of book=%p to file=%s", book, datafile);
00808         return TRUE;
00809     }
00810     else
00811     {
00812         if (g_unlink(tmp_name) != 0)
00813         {
00814             switch (errno)
00815             {
00816             case ENOENT:     /* tmp_name doesn't exist?  Assume "RO" error */
00817             case EACCES:
00818             case EPERM:
00819             case ENOSYS:
00820             case EROFS:
00821                 be_err = ERR_BACKEND_READONLY;
00822                 break;
00823             default:
00824                 be_err = ERR_BACKEND_MISC;
00825             }
00826             qof_backend_set_error(be, be_err);
00827             PWARN("unable to unlink temp_filename %s: %s",
00828                   tmp_name ? tmp_name : "(null)",
00829                   g_strerror(errno) ? g_strerror(errno) : "");
00830             /* already in an error just flow on through */
00831         }
00832         else
00833         {
00834             /* Use a generic write error code */
00835             qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
00836             qof_backend_set_message( be, "Unable to write to temp file %s",
00837                                      tmp_name ? tmp_name : "NULL" );
00838         }
00839         g_free(tmp_name);
00840         LEAVE("");
00841         return FALSE;
00842     }
00843     LEAVE("");
00844     return TRUE;
00845 }
00846 
00847 /* ================================================================= */
00848 
00849 /*
00850  * Clean up any lock files from prior crashes, and clean up old
00851  * backup and log files.
00852  */
00853 
00854 static void
00855 gnc_xml_be_remove_old_files(FileBackend *be)
00856 {
00857     const gchar *dent;
00858     GDir *dir;
00859     struct stat lockstatbuf, statbuf;
00860     time_t now;
00861 
00862     if (g_stat (be->lockfile, &lockstatbuf) != 0)
00863         return;
00864 
00865     dir = g_dir_open (be->dirname, 0, NULL);
00866     if (!dir)
00867         return;
00868 
00869     now = time(NULL);
00870     while ((dent = g_dir_read_name(dir)) != NULL)
00871     {
00872         gchar *name;
00873 
00874         /* Ensure we only evaluate GnuCash related files. */
00875         if ( !(g_str_has_suffix(dent, ".LNK") ||
00876                 g_str_has_suffix(dent, ".xac") /* old data file extension */ ||
00877                 g_str_has_suffix(dent, GNC_DATAFILE_EXT) ||
00878                 g_str_has_suffix(dent, GNC_LOGFILE_EXT)) )
00879             continue;
00880 
00881         name = g_build_filename(be->dirname, dent, (gchar*)NULL);
00882 
00883         /* Only evaluate files associated with the current data file. */
00884         if (!g_str_has_prefix(name, be->fullpath))
00885             continue;
00886 
00887         /* Never remove the current data file itself */
00888         if (g_strcmp0(name, be->fullpath) == 0)
00889             continue;
00890 
00891         /* Test if the current file is a lock file */
00892         if (g_str_has_suffix(name, ".LNK"))
00893         {
00894             /* Is a lock file. Skip the active lock file */
00895             if ((g_strcmp0(name, be->linkfile) != 0) &&
00896                     /* Only delete lock files older than the active one */
00897                     (g_stat(name, &statbuf) == 0) &&
00898                     (statbuf.st_mtime < lockstatbuf.st_mtime))
00899             {
00900                 PINFO ("remove stale lock file: %s", name);
00901                 g_unlink(name);
00902             }
00903 
00904             continue;
00905         }
00906 
00907         /* At this point we're sure the file's name is in one of these forms:
00908          * <fullpath/to/datafile><anything>.gnucash
00909          * <fullpath/to/datafile><anything>.xac
00910          * <fullpath/to/datafile><anything>.log
00911          *
00912          * To be a file generated by GnuCash, the <anything> part should consist
00913          * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
00914          * regular expression.
00915          */
00916         {
00917             /* Find the start of the date stamp. This takes some pointer
00918              * juggling, but considering the above tests, this should always
00919              * be safe */
00920             regex_t pattern;
00921             gchar *stamp_start = name + strlen(be->fullpath);
00922             gchar *expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
00923                                                  GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
00924             gboolean got_date_stamp = FALSE;
00925 
00926             if (regcomp(&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
00927                 PWARN("Cannot compile regex for date stamp");
00928             else if (regexec(&pattern, stamp_start, 0, NULL, 0) == 0)
00929                 got_date_stamp = TRUE;
00930 
00931             regfree(&pattern);
00932             g_free(expression);
00933 
00934             if (!got_date_stamp) /* Not a gnucash created file after all... */
00935                 continue;
00936         }
00937 
00938         /* The file is a backup or log file. Check the user's retention preference
00939          * to determine if we should keep it or not
00940          */
00941         if (be->file_retention_type == XML_RETAIN_NONE)
00942         {
00943             PINFO ("remove stale file: %s  - reason: preference XML_RETAIN_NONE", name);
00944             g_unlink(name);
00945         }
00946         else if ((be->file_retention_type == XML_RETAIN_DAYS) &&
00947                  (be->file_retention_days > 0))
00948         {
00949             int days;
00950 
00951             /* Is the backup file old enough to delete */
00952             if (g_stat(name, &statbuf) != 0)
00953                 continue;
00954             days = (int)(difftime(now, statbuf.st_mtime) / 86400);
00955 
00956             PINFO ("file retention = %d days", be->file_retention_days);
00957             if (days >= be->file_retention_days)
00958             {
00959                 PINFO ("remove stale file: %s  - reason: more than %d days old", name, days);
00960                 g_unlink(name);
00961             }
00962         }
00963         g_free(name);
00964     }
00965     g_dir_close (dir);
00966 }
00967 
00968 static void
00969 xml_sync_all(QofBackend* be, QofBook *book)
00970 {
00971     FileBackend *fbe = (FileBackend *) be;
00972     ENTER ("book=%p, fbe->book=%p", book, fbe->book);
00973 
00974     /* We make an important assumption here, that we might want to change
00975      * in the future: when the user says 'save', we really save the one,
00976      * the only, the current open book, and nothing else. In any case the plans
00977      * for multiple books have been removed in the meantime and there is just one
00978      * book, no more.
00979      */
00980     if (NULL == fbe->book) fbe->book = book;
00981     if (book != fbe->book) return;
00982 
00983     if (qof_book_is_readonly( fbe->book ) )
00984     {
00985         /* Are we read-only? Don't continue in this case. */
00986         qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_READONLY );
00987         return;
00988     }
00989 
00990     gnc_xml_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
00991     gnc_xml_be_remove_old_files (fbe);
00992     LEAVE ("book=%p", book);
00993 }
00994 
00995 /* ================================================================= */
00996 /* Routines to deal with the creation of multiple books.
00997  * The core design assumption here is that the book
00998  * begin-edit/commit-edit routines are used solely to write out
00999  * closed accounting periods to files.  They're not currently
01000  * designed to do anything other than this. (Although they could be).
01001  */
01002 
01003 static char *
01004 build_period_filepath (FileBackend *fbe, QofBook *book)
01005 {
01006     int len;
01007     char *str, *p, *q;
01008 
01009     len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
01010     str = g_new (char, len);
01011     strcpy (str, fbe->fullpath);
01012 
01013     /* XXX it would be nice for the user if we made the book
01014      * closing date and/or title part of the file-name. */
01015     p = strrchr (str, G_DIR_SEPARATOR);
01016     p++;
01017     p = stpcpy (p, "book-");
01018     p = guid_to_string_buff (qof_book_get_guid(book), p);
01019     p = stpcpy (p, "-");
01020     q = strrchr (fbe->fullpath, G_DIR_SEPARATOR);
01021     q++;
01022     p = stpcpy (p, q);
01023     p = stpcpy (p, ".gml");
01024 
01025     return str;
01026 }
01027 
01028 static void
01029 xml_begin_edit (QofBackend *be, QofInstance *inst)
01030 {
01031     if (0) build_period_filepath(0, 0);
01032 #if BORKEN_FOR_NOW
01033     FileBackend *fbe = (FileBackend *) be;
01034     QofBook *book = gp;
01035     const char * filepath;
01036 
01037     QofIdTypeConst typ = QOF_INSTANCE(inst)->e_type;
01038     if (strcmp (GNC_ID_PERIOD, typ)) return;
01039     filepath = build_period_filepath(fbe, book);
01040     PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
01041 
01042     if (NULL == fbe->primary_book)
01043     {
01044         PERR ("You should have saved the data "
01045               "at least once before closing the books!\n");
01046     }
01047     /* XXX To be anal about it, we should really be checking to see
01048      * if there already is a file with this book GncGUID, and disallowing
01049      * further progress.  This is because we are not allowed to
01050      * modify books that are closed (They should be treated as
01051      * 'read-only').
01052      */
01053 #endif
01054 }
01055 
01056 static void
01057 xml_rollback_edit (QofBackend *be, QofInstance *inst)
01058 {
01059 #if BORKEN_FOR_NOW
01060     QofBook *book = gp;
01061 
01062     if (strcmp (GNC_ID_PERIOD, typ)) return;
01063     PINFO ("book=%p", book);
01064 #endif
01065 }
01066 
01067 static void
01068 xml_commit_edit (QofBackend *be, QofInstance *inst)
01069 {
01070     if (qof_instance_get_dirty(inst) && qof_get_alt_dirty_mode() &&
01071             !(qof_instance_get_infant(inst) && qof_instance_get_destroying(inst)))
01072     {
01073         qof_collection_mark_dirty(qof_instance_get_collection(inst));
01074         qof_book_mark_session_dirty(qof_instance_get_book(inst));
01075     }
01076 #if BORKEN_FOR_NOW
01077     FileBackend *fbe = (FileBackend *) be;
01078     QofBook *book = gp;
01079     const char * filepath;
01080 
01081     if (strcmp (GNC_ID_PERIOD, typ)) return;
01082     filepath = build_period_filepath(fbe, book);
01083     PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
01084     gnc_xml_be_write_to_file(fbe, book, filepath, FALSE);
01085 
01086     /* We want to force a save of the current book at this point,
01087      * because if we don't, and the user forgets to do so, then
01088      * there'll be the same transactions in the closed book,
01089      * and also in the current book. */
01090     gnc_xml_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
01091 #endif
01092 }
01093 
01094 /* ---------------------------------------------------------------------- */
01095 
01096 
01097 /* Load financial data from a file into the book, automatically
01098    detecting the format of the file, if possible.  Return FALSE on
01099    error, and set the error parameter to indicate what went wrong if
01100    it's not NULL.  This function does not manage file locks in any
01101    way. */
01102 
01103 static void
01104 gnc_xml_be_load_from_file (QofBackend *bend, QofBook *book, QofBackendLoadType loadType)
01105 {
01106     QofBackendError error;
01107     gboolean rc;
01108     FileBackend *be = (FileBackend *) bend;
01109 
01110     if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
01111 
01112     error = ERR_BACKEND_NO_ERR;
01113     be->book = book;
01114 
01115     switch (gnc_xml_be_determine_file_type(be->fullpath))
01116     {
01117     case GNC_BOOK_XML2_FILE:
01118         rc = qof_session_load_from_xml_file_v2 (be, book, GNC_BOOK_XML2_FILE);
01119         if (FALSE == rc)
01120         {
01121             PWARN( "Syntax error in Xml File %s", be->fullpath );
01122             error = ERR_FILEIO_PARSE_ERROR;
01123         }
01124         break;
01125 
01126     case GNC_BOOK_XML2_FILE_NO_ENCODING:
01127         error = ERR_FILEIO_NO_ENCODING;
01128         PWARN( "No character encoding in Xml File %s", be->fullpath );
01129         break;
01130     case GNC_BOOK_XML1_FILE:
01131         rc = qof_session_load_from_xml_file (book, be->fullpath);
01132         if (FALSE == rc)
01133         {
01134             PWARN( "Syntax error in Xml File %s", be->fullpath );
01135             error = ERR_FILEIO_PARSE_ERROR;
01136         }
01137         break;
01138     case GNC_BOOK_POST_XML2_0_0_FILE:
01139         error = ERR_BACKEND_TOO_NEW;
01140         PWARN( "Version of Xml file %s is newer than what we can read", be->fullpath );
01141         break;
01142     default:
01143         /* If file type wasn't known, check errno again to give the
01144         user some more useful feedback for some particular error
01145         conditions. */
01146         switch (errno)
01147         {
01148         case EACCES: /* No read permission */
01149             PWARN("No read permission to file");
01150             error = ERR_FILEIO_FILE_EACCES;
01151             break;
01152         case EISDIR: /* File is a directory - but on this error we don't arrive here */
01153             PWARN("Filename is a directory");
01154             error = ERR_FILEIO_FILE_NOT_FOUND;
01155             break;
01156         default:
01157             PWARN("File not any known type");
01158             error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
01159             break;
01160         }
01161         break;
01162     }
01163 
01164     if (error != ERR_BACKEND_NO_ERR)
01165     {
01166         qof_backend_set_error(bend, error);
01167     }
01168 
01169     /* We just got done loading, it can't possibly be dirty !! */
01170     qof_book_mark_session_saved (book);
01171 }
01172 
01173 /* ---------------------------------------------------------------------- */
01174 
01175 static gboolean
01176 save_may_clobber_data (QofBackend *bend)
01177 {
01178     struct stat statbuf;
01179     if (!bend->fullpath) return FALSE;
01180 
01181     /* FIXME: Make sure this doesn't need more sophisticated semantics
01182      * in the face of special file, devices, pipes, symlinks, etc. */
01183     if (g_stat(bend->fullpath, &statbuf) == 0) return TRUE;
01184     return FALSE;
01185 }
01186 
01187 
01188 static void
01189 gnc_xml_be_write_accounts_to_file(QofBackend *be, QofBook *book)
01190 {
01191     const gchar *datafile;
01192 
01193     datafile = ((FileBackend *)be)->fullpath;
01194     gnc_book_write_accounts_to_xml_file_v2(be, book, datafile);
01195 }
01196 
01197 /* ================================================================= */
01198 #if 0 //def GNUCASH_MAJOR_VERSION
01199 QofBackend *
01200 libgncmod_backend_file_LTX_gnc_backend_new(void)
01201 {
01202 
01203     fbe->dirname = NULL;
01204     fbe->fullpath = NULL;
01205     fbe->lockfile = NULL;
01206     fbe->linkfile = NULL;
01207     fbe->lockfd = -1;
01208 
01209     fbe->primary_book = NULL;
01210 
01211     return be;
01212 }
01213 #endif
01214 
01215 static void
01216 retain_changed_cb(GConfEntry *entry, gpointer user_data)
01217 {
01218     FileBackend *be = (FileBackend*)user_data;
01219     g_return_if_fail(be != NULL);
01220     be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
01221 }
01222 
01223 static void
01224 retain_type_changed_cb(GConfEntry *entry, gpointer user_data)
01225 {
01226     FileBackend *be = (FileBackend*)user_data;
01227     gchar *choice = NULL;
01228     g_return_if_fail(be != NULL);
01229     choice = gnc_gconf_get_string(GCONF_GENERAL, KEY_RETAIN_TYPE, NULL);
01230     if (!choice)
01231         choice = g_strdup("days");
01232 
01233     if (safe_strcmp (choice, "never") == 0)
01234         be->file_retention_type = XML_RETAIN_NONE;
01235     else if (safe_strcmp (choice, "forever") == 0)
01236         be->file_retention_type = XML_RETAIN_ALL;
01237     else
01238     {
01239         if (safe_strcmp (choice, "days") != 0)
01240             PERR("bad value '%s'", choice ? choice : "(null)");
01241         be->file_retention_type = XML_RETAIN_DAYS;
01242     }
01243 
01244     g_free (choice);
01245 }
01246 
01247 static void
01248 compression_changed_cb(GConfEntry *entry, gpointer user_data)
01249 {
01250     FileBackend *be = (FileBackend*)user_data;
01251     g_return_if_fail(be != NULL);
01252     be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
01253 }
01254 
01255 static QofBackend*
01256 gnc_backend_new(void)
01257 {
01258     FileBackend *gnc_be;
01259     QofBackend *be;
01260 
01261     gnc_be = g_new0(FileBackend, 1);
01262     be = (QofBackend*) gnc_be;
01263     qof_backend_init(be);
01264 
01265     be->session_begin = xml_session_begin;
01266     be->session_end = xml_session_end;
01267     be->destroy_backend = xml_destroy_backend;
01268 
01269     be->load = gnc_xml_be_load_from_file;
01270 
01271     /* The file backend treats accounting periods transactionally. */
01272     be->begin = xml_begin_edit;
01273     be->commit = xml_commit_edit;
01274     be->rollback = xml_rollback_edit;
01275 
01276     /* The file backend always loads all data ... */
01277     be->compile_query = NULL;
01278     be->free_query = NULL;
01279     be->run_query = NULL;
01280 
01281     /* The file backend will never be multi-user... */
01282     be->events_pending = NULL;
01283     be->process_events = NULL;
01284 
01285     be->sync = xml_sync_all;
01286     be->load_config = NULL;
01287     be->get_config = NULL;
01288 
01289     be->export_fn = gnc_xml_be_write_accounts_to_file;
01290 
01291     gnc_be->dirname = NULL;
01292     gnc_be->fullpath = NULL;
01293     gnc_be->lockfile = NULL;
01294     gnc_be->linkfile = NULL;
01295     gnc_be->lockfd = -1;
01296 
01297     gnc_be->book = NULL;
01298 
01299     gnc_be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
01300     gnc_be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
01301     retain_type_changed_cb(NULL, (gpointer)be); /* Get retain_type from gconf */
01302 
01303     if ( (gnc_be->file_retention_type == XML_RETAIN_DAYS) &&
01304             (gnc_be->file_retention_days == 0 ) )
01305     {
01306         /* Backwards compatibility code. Pre 2.3.15, 0 retain_days meant
01307          * "keep forever". From 2.3.15 on this is controlled via a multiple
01308          * choice ("retain_type"). So if we find a 0 retain_days value with
01309          * a "days" retain_type, we should interpret it as if we got a
01310          * "forever" retain_type.
01311          */
01312         gnc_be->file_retention_type = XML_RETAIN_ALL;
01313         gnc_gconf_set_string (GCONF_GENERAL, KEY_RETAIN_TYPE, "forever", NULL);
01314     }
01315 
01316     gnc_gconf_general_register_cb(KEY_RETAIN_DAYS, retain_changed_cb, be);
01317     gnc_gconf_general_register_cb(KEY_RETAIN_TYPE, retain_type_changed_cb, be);
01318     gnc_gconf_general_register_cb(KEY_FILE_COMPRESSION, compression_changed_cb, be);
01319 
01320     return be;
01321 }
01322 
01323 static void
01324 business_core_xml_init(void)
01325 {
01326     /* Initialize our pointers into the backend subsystem */
01327     gnc_address_xml_initialize ();
01328     gnc_billterm_xml_initialize ();
01329     gnc_customer_xml_initialize ();
01330     gnc_employee_xml_initialize ();
01331     gnc_entry_xml_initialize ();
01332     gnc_invoice_xml_initialize ();
01333     gnc_job_xml_initialize ();
01334     gnc_order_xml_initialize ();
01335     gnc_owner_xml_initialize ();
01336     gnc_taxtable_xml_initialize ();
01337     gnc_vendor_xml_initialize ();
01338 }
01339 
01340 static void
01341 gnc_provider_free (QofBackendProvider *prov)
01342 {
01343     prov->provider_name = NULL;
01344     prov->access_method = NULL;
01345     g_free (prov);
01346 }
01347 
01348 #ifndef GNC_NO_LOADABLE_MODULES
01349 G_MODULE_EXPORT void
01350 qof_backend_module_init(void)
01351 {
01352     gnc_module_init_backend_xml();
01353 }
01354 #endif
01355 
01356 void
01357 gnc_module_init_backend_xml(void)
01358 {
01359     QofBackendProvider *prov;
01360     prov = g_new0 (QofBackendProvider, 1);
01361     prov->provider_name = "GnuCash File Backend Version 2";
01362     prov->access_method = "file";
01363     prov->partial_book_supported = FALSE;
01364     prov->backend_new = gnc_backend_new;
01365     prov->provider_free = gnc_provider_free;
01366     prov->check_data_type = gnc_determine_file_type;
01367     qof_backend_register_provider (prov);
01368 
01369     prov = g_new0 (QofBackendProvider, 1);
01370     prov->provider_name = "GnuCash File Backend Version 2";
01371     prov->access_method = "xml";
01372     prov->partial_book_supported = FALSE;
01373     prov->backend_new = gnc_backend_new;
01374     prov->provider_free = gnc_provider_free;
01375     prov->check_data_type = gnc_determine_file_type;
01376     qof_backend_register_provider (prov);
01377 
01378     /* And the business objects */
01379     business_core_xml_init();
01380 }
01381 
01382 /* ========================== END OF FILE ===================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines