|
GnuCash 2.4.99
|
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 ===================== */
1.7.4