PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/backend/xml/gnc-backend-xml.c

http://github.com/mchochlov/Gnucash
C | 1333 lines | 970 code | 169 blank | 194 comment | 171 complexity | db8b7d3e8ff9cb8838db5acd9b5f7815 MD5 | raw file
Possible License(s): GPL-2.0
  1. /********************************************************************
  2. * gnc-backend-xml.c: load and save data to XML files *
  3. * *
  4. * This program is free software; you can redistribute it and/or *
  5. * modify it under the terms of the GNU General Public License as *
  6. * published by the Free Software Foundation; either version 2 of *
  7. * the License, or (at your option) any later version. *
  8. * *
  9. * This program is distributed in the hope that it will be useful, *
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  12. * GNU General Public License for more details. *
  13. * *
  14. * You should have received a copy of the GNU General Public License*
  15. * along with this program; if not, contact: *
  16. * *
  17. * Free Software Foundation Voice: +1-617-542-5942 *
  18. * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
  19. * Boston, MA 02110-1301, USA gnu@gnu.org *
  20. \********************************************************************/
  21. /** @file gnc-backend-xml.c
  22. * @brief load and save data to XML files
  23. * @author Copyright (c) 2000 Gnumatic Inc.
  24. * @author Copyright (c) 2002 Derek Atkins <warlord@MIT.EDU>
  25. * @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
  26. *
  27. * This file implements the top-level QofBackend API for saving/
  28. * restoring data to/from an ordinary Unix filesystem file.
  29. */
  30. #include "config.h"
  31. #include <glib.h>
  32. #include <glib/gi18n.h>
  33. #include <glib/gstdio.h>
  34. #include <libintl.h>
  35. #include <locale.h>
  36. #include <fcntl.h>
  37. #include <limits.h>
  38. #include <sys/stat.h>
  39. #include <sys/types.h>
  40. #include <regex.h>
  41. #ifdef HAVE_UNISTD_H
  42. # include <unistd.h>
  43. #else
  44. # ifdef _MSC_VER
  45. typedef int ssize_t;
  46. # endif
  47. #endif
  48. #include <errno.h>
  49. #include <string.h>
  50. #ifdef HAVE_DIRENT_H
  51. # include <dirent.h>
  52. #endif
  53. #include <time.h>
  54. #ifdef G_OS_WIN32
  55. # include <io.h>
  56. # define close _close
  57. # define mktemp _mktemp
  58. # define read _read
  59. # define write _write
  60. #endif
  61. #include "platform.h"
  62. #if COMPILER(MSVC)
  63. # define g_fopen fopen
  64. # define g_open _open
  65. #endif
  66. #include "qof.h"
  67. #include "TransLog.h"
  68. #include "gnc-engine.h"
  69. #include "gnc-uri-utils.h"
  70. #include "io-gncxml.h"
  71. #include "io-gncxml-v2.h"
  72. #include "gnc-backend-xml.h"
  73. #include "gnc-gconf-utils.h"
  74. #include "gnc-address-xml-v2.h"
  75. #include "gnc-bill-term-xml-v2.h"
  76. #include "gnc-customer-xml-v2.h"
  77. #include "gnc-employee-xml-v2.h"
  78. #include "gnc-entry-xml-v2.h"
  79. #include "gnc-invoice-xml-v2.h"
  80. #include "gnc-job-xml-v2.h"
  81. #include "gnc-order-xml-v2.h"
  82. #include "gnc-owner-xml-v2.h"
  83. #include "gnc-tax-table-xml-v2.h"
  84. #include "gnc-vendor-xml-v2.h"
  85. #ifndef HAVE_STRPTIME
  86. # include "strptime.h"
  87. #endif
  88. #define KEY_FILE_COMPRESSION "file_compression"
  89. #define KEY_RETAIN_TYPE "retain_type"
  90. #define KEY_RETAIN_DAYS "retain_days"
  91. static QofLogModule log_module = GNC_MOD_BACKEND;
  92. typedef enum
  93. {
  94. GNC_BOOK_NOT_OURS,
  95. GNC_BOOK_BIN_FILE,
  96. GNC_BOOK_XML1_FILE,
  97. GNC_BOOK_XML2_FILE,
  98. GNC_BOOK_XML2_FILE_NO_ENCODING,
  99. } QofBookFileType;
  100. static gboolean save_may_clobber_data (QofBackend *bend);
  101. /* ================================================================= */
  102. static gboolean
  103. gnc_xml_be_get_file_lock (FileBackend *be)
  104. {
  105. struct stat statbuf;
  106. #ifndef G_OS_WIN32
  107. char pathbuf[PATH_MAX];
  108. char *path = NULL;
  109. #endif
  110. int rc;
  111. QofBackendError be_err;
  112. rc = g_stat (be->lockfile, &statbuf);
  113. if (!rc)
  114. {
  115. /* oops .. file is locked by another user .. */
  116. qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
  117. return FALSE;
  118. }
  119. be->lockfd = g_open (be->lockfile, O_RDWR | O_CREAT | O_EXCL , S_IRUSR | S_IWUSR);
  120. if (be->lockfd < 0)
  121. {
  122. /* oops .. we can't create the lockfile .. */
  123. switch (errno)
  124. {
  125. case EACCES:
  126. case EROFS:
  127. case ENOSPC:
  128. PWARN( "Unable to create the lockfile %s; may not have write priv",
  129. be->lockfile );
  130. be_err = ERR_BACKEND_READONLY;
  131. break;
  132. default:
  133. be_err = ERR_BACKEND_LOCKED;
  134. break;
  135. }
  136. qof_backend_set_error ((QofBackend*)be, be_err);
  137. return FALSE;
  138. }
  139. /* OK, now work around some NFS atomic lock race condition
  140. * mumbo-jumbo. We do this by linking a unique file, and
  141. * then examining the link count. At least that's what the
  142. * NFS programmers guide suggests.
  143. * Note: the "unique filename" must be unique for the
  144. * triplet filename-host-process, otherwise accidental
  145. * aliases can occur.
  146. */
  147. /* apparently, even this code may not work for some NFS
  148. * implementations. In the long run, I am told that
  149. * ftp.debian.org
  150. * /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
  151. * provides a better long-term solution.
  152. */
  153. #ifndef G_OS_WIN32
  154. strcpy (pathbuf, be->lockfile);
  155. path = strrchr (pathbuf, '.');
  156. sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
  157. rc = link (be->lockfile, pathbuf);
  158. if (rc)
  159. {
  160. /* If hard links aren't supported, just allow the lock. */
  161. if (errno == EPERM || errno == ENOSYS
  162. # ifdef EOPNOTSUPP
  163. || errno == EOPNOTSUPP
  164. # endif
  165. # ifdef ENOTSUP
  166. || errno == ENOTSUP
  167. # endif
  168. )
  169. {
  170. be->linkfile = NULL;
  171. return TRUE;
  172. }
  173. /* Otherwise, something else is wrong. */
  174. qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
  175. g_unlink (pathbuf);
  176. close (be->lockfd);
  177. g_unlink (be->lockfile);
  178. return FALSE;
  179. }
  180. rc = g_stat (be->lockfile, &statbuf);
  181. if (rc)
  182. {
  183. /* oops .. stat failed! This can't happen! */
  184. qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
  185. qof_backend_set_message ((QofBackend*)be, "Failed to stat lockfile %s",
  186. be->lockfile );
  187. g_unlink (pathbuf);
  188. close (be->lockfd);
  189. g_unlink (be->lockfile);
  190. return FALSE;
  191. }
  192. if (statbuf.st_nlink != 2)
  193. {
  194. qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
  195. g_unlink (pathbuf);
  196. close (be->lockfd);
  197. g_unlink (be->lockfile);
  198. return FALSE;
  199. }
  200. be->linkfile = g_strdup (pathbuf);
  201. return TRUE;
  202. #else /* ifndef G_OS_WIN32 */
  203. /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
  204. is sufficient for locking. */
  205. be->linkfile = NULL;
  206. return TRUE;
  207. #endif /* ifndef G_OS_WIN32 */
  208. }
  209. /* ================================================================= */
  210. #define XML_URI_PREFIX "xml://"
  211. #define FILE_URI_PREFIX "file://"
  212. static void
  213. xml_session_begin(QofBackend *be_start, QofSession *session,
  214. const char *book_id, gboolean ignore_lock,
  215. gboolean create, gboolean force)
  216. {
  217. FileBackend *be = (FileBackend*) be_start;
  218. ENTER (" ");
  219. /* Make sure the directory is there */
  220. be->fullpath = gnc_uri_get_path (book_id);
  221. if (NULL == be->fullpath)
  222. {
  223. qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
  224. qof_backend_set_message (be_start, "No path specified");
  225. LEAVE("");
  226. return;
  227. }
  228. if (create && !force && save_may_clobber_data( be_start ) )
  229. {
  230. qof_backend_set_error (be_start, ERR_BACKEND_STORE_EXISTS);
  231. LEAVE("Might clobber, no force");
  232. return;
  233. }
  234. be->be.fullpath = be->fullpath;
  235. be->dirname = g_path_get_dirname (be->fullpath);
  236. {
  237. struct stat statbuf;
  238. int rc;
  239. /* Again check whether the directory can be accessed */
  240. rc = g_stat (be->dirname, &statbuf);
  241. if (rc != 0
  242. #if COMPILER(MSVC)
  243. || (statbuf.st_mode & _S_IFDIR) == 0
  244. #else
  245. || !S_ISDIR(statbuf.st_mode)
  246. #endif
  247. )
  248. {
  249. /* Error on stat or if it isn't a directory means we
  250. cannot find this filename */
  251. qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
  252. qof_backend_set_message (be_start, "Couldn't find directory for %s", be->fullpath);
  253. g_free (be->fullpath);
  254. be->fullpath = NULL;
  255. g_free (be->dirname);
  256. be->dirname = NULL;
  257. LEAVE("");
  258. return;
  259. }
  260. /* Now check whether we can g_stat the file itself */
  261. rc = g_stat (be->fullpath, &statbuf);
  262. if ((rc != 0) && (!create))
  263. {
  264. /* Error on stat means the file doesn't exist */
  265. qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
  266. qof_backend_set_message (be_start, "Couldn't find %s", be->fullpath);
  267. g_free (be->fullpath);
  268. be->fullpath = NULL;
  269. g_free (be->dirname);
  270. be->dirname = NULL;
  271. LEAVE("");
  272. return;
  273. }
  274. if (rc == 0
  275. #if COMPILER(MSVC)
  276. && (statbuf.st_mode & _S_IFDIR) != 0
  277. #else
  278. && S_ISDIR(statbuf.st_mode)
  279. #endif
  280. )
  281. {
  282. qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
  283. qof_backend_set_message(be_start, "Path %s is a directory",
  284. be->fullpath);
  285. g_free (be->fullpath);
  286. be->fullpath = NULL;
  287. g_free (be->dirname);
  288. be->dirname = NULL;
  289. LEAVE("");
  290. return;
  291. }
  292. }
  293. /* ---------------------------------------------------- */
  294. /* We should now have a fully resolved path name.
  295. * Let's start logging */
  296. xaccLogSetBaseName (be->fullpath);
  297. PINFO ("logpath=%s", be->fullpath ? be->fullpath : "(null)");
  298. /* And let's see if we can get a lock on it. */
  299. be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
  300. if (!ignore_lock && !gnc_xml_be_get_file_lock (be))
  301. {
  302. g_free (be->lockfile);
  303. be->lockfile = NULL;
  304. LEAVE("");
  305. return;
  306. }
  307. LEAVE (" ");
  308. return;
  309. }
  310. /* ================================================================= */
  311. static void
  312. xml_session_end(QofBackend *be_start)
  313. {
  314. FileBackend *be = (FileBackend*)be_start;
  315. ENTER (" ");
  316. if (be->linkfile)
  317. g_unlink (be->linkfile);
  318. if (be->lockfd > 0)
  319. close (be->lockfd);
  320. if (be->lockfile)
  321. {
  322. int rv;
  323. #ifdef G_OS_WIN32
  324. /* On windows, we need to allow write-access before
  325. g_unlink() can succeed */
  326. rv = g_chmod (be->lockfile, S_IWRITE | S_IREAD);
  327. #endif
  328. rv = g_unlink (be->lockfile);
  329. if (rv)
  330. {
  331. PWARN("Error on g_unlink(%s): %d: %s", be->lockfile,
  332. errno, g_strerror(errno) ? g_strerror(errno) : "");
  333. }
  334. }
  335. g_free (be->dirname);
  336. be->dirname = NULL;
  337. g_free (be->fullpath);
  338. be->fullpath = NULL;
  339. g_free (be->lockfile);
  340. be->lockfile = NULL;
  341. g_free (be->linkfile);
  342. be->linkfile = NULL;
  343. LEAVE (" ");
  344. }
  345. static void
  346. xml_destroy_backend(QofBackend *be)
  347. {
  348. /* Stop transaction logging */
  349. xaccLogSetBaseName (NULL);
  350. qof_backend_destroy(be);
  351. g_free(be);
  352. }
  353. /* ================================================================= */
  354. /* Write the financial data in a book to a file, returning FALSE on
  355. error and setting the error_result to indicate what went wrong if
  356. it's not NULL. This function does not manage file locks in any
  357. way.
  358. If make_backup is true, write out a time-stamped copy of the file
  359. into the same directory as the indicated file, with a filename of
  360. "file.YYYYMMDDHHMMSS.gnucash" where YYYYMMDDHHMMSS is replaced with the
  361. current year/month/day/hour/minute/second. */
  362. /* The variable buf_size must be a compile-time constant */
  363. #define buf_size 1024
  364. static gboolean
  365. copy_file(const char *orig, const char *bkup)
  366. {
  367. char buf[buf_size];
  368. int orig_fd;
  369. int bkup_fd;
  370. int flags = 0;
  371. ssize_t count_write;
  372. ssize_t count_read;
  373. #ifdef G_OS_WIN32
  374. flags = O_BINARY;
  375. #endif
  376. orig_fd = g_open(orig, O_RDONLY | flags, 0);
  377. if (orig_fd == -1)
  378. {
  379. return FALSE;
  380. }
  381. bkup_fd = g_open(bkup, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
  382. if (bkup_fd == -1)
  383. {
  384. close(orig_fd);
  385. return FALSE;
  386. }
  387. do
  388. {
  389. count_read = read(orig_fd, buf, buf_size);
  390. if (count_read == -1 && errno != EINTR)
  391. {
  392. close(orig_fd);
  393. close(bkup_fd);
  394. return FALSE;
  395. }
  396. if (count_read > 0)
  397. {
  398. count_write = write(bkup_fd, buf, count_read);
  399. if (count_write == -1)
  400. {
  401. close(orig_fd);
  402. close(bkup_fd);
  403. return FALSE;
  404. }
  405. }
  406. }
  407. while (count_read > 0);
  408. close(orig_fd);
  409. close(bkup_fd);
  410. return TRUE;
  411. }
  412. /* ================================================================= */
  413. static gboolean
  414. gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
  415. {
  416. gboolean copy_success = FALSE;
  417. int err_ret =
  418. #ifdef HAVE_LINK
  419. link (orig, bkup)
  420. #else
  421. - 1
  422. #endif
  423. ;
  424. if (err_ret != 0)
  425. {
  426. #ifdef HAVE_LINK
  427. if (errno == EPERM || errno == ENOSYS
  428. # ifdef EOPNOTSUPP
  429. || errno == EOPNOTSUPP
  430. # endif
  431. # ifdef ENOTSUP
  432. || errno == ENOTSUP
  433. # endif
  434. # ifdef ENOSYS
  435. || errno == ENOSYS
  436. # endif
  437. )
  438. #endif
  439. {
  440. copy_success = copy_file(orig, bkup);
  441. }
  442. if (!copy_success)
  443. {
  444. qof_backend_set_error((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR);
  445. PWARN ("unable to make file backup from %s to %s: %s",
  446. orig, bkup, g_strerror(errno) ? g_strerror(errno) : "");
  447. return FALSE;
  448. }
  449. }
  450. return TRUE;
  451. }
  452. /* ================================================================= */
  453. static QofBookFileType
  454. gnc_xml_be_determine_file_type(const char *path)
  455. {
  456. gboolean with_encoding;
  457. if (gnc_is_xml_data_file_v2(path, &with_encoding))
  458. {
  459. if (with_encoding)
  460. {
  461. return GNC_BOOK_XML2_FILE;
  462. }
  463. else
  464. {
  465. return GNC_BOOK_XML2_FILE_NO_ENCODING;
  466. }
  467. }
  468. else if (gnc_is_xml_data_file(path))
  469. {
  470. return GNC_BOOK_XML1_FILE;
  471. }
  472. return GNC_BOOK_NOT_OURS;
  473. }
  474. static gboolean
  475. gnc_determine_file_type (const char *uri)
  476. {
  477. struct stat sbuf;
  478. int rc;
  479. FILE *t;
  480. gchar *filename;
  481. gboolean result;
  482. if (!uri)
  483. {
  484. return FALSE;
  485. }
  486. filename = gnc_uri_get_path ( uri );
  487. if (0 == safe_strcmp(filename, QOF_STDOUT))
  488. {
  489. result = FALSE;
  490. goto det_exit;
  491. }
  492. t = g_fopen( filename, "r" );
  493. if (!t)
  494. {
  495. PINFO (" new file");
  496. result = TRUE;
  497. goto det_exit;
  498. }
  499. fclose(t);
  500. rc = g_stat(filename, &sbuf);
  501. if (rc < 0)
  502. {
  503. result = FALSE;
  504. goto det_exit;
  505. }
  506. if (sbuf.st_size == 0)
  507. {
  508. PINFO (" empty file");
  509. result = TRUE;
  510. goto det_exit;
  511. }
  512. if (gnc_is_xml_data_file_v2(filename, NULL))
  513. {
  514. result = TRUE;
  515. goto det_exit;
  516. }
  517. else if (gnc_is_xml_data_file(filename))
  518. {
  519. result = TRUE;
  520. goto det_exit;
  521. }
  522. PINFO (" %s is not a gnc XML file", filename);
  523. result = FALSE;
  524. det_exit:
  525. g_free ( filename );
  526. return result;
  527. }
  528. static gboolean
  529. gnc_xml_be_backup_file(FileBackend *be)
  530. {
  531. gboolean bkup_ret;
  532. char *timestamp;
  533. char *backup;
  534. const char *datafile;
  535. struct stat statbuf;
  536. int rc;
  537. datafile = be->fullpath;
  538. rc = g_stat (datafile, &statbuf);
  539. if (rc)
  540. return (errno == ENOENT);
  541. if (gnc_xml_be_determine_file_type(datafile) == GNC_BOOK_BIN_FILE)
  542. {
  543. /* make a more permanent safer backup */
  544. const char *back = "-binfmt.bkup";
  545. char *bin_bkup = g_new(char, strlen(datafile) + strlen(back) + 1);
  546. strcpy(bin_bkup, datafile);
  547. strcat(bin_bkup, back);
  548. bkup_ret = gnc_int_link_or_make_backup(be, datafile, bin_bkup);
  549. g_free(bin_bkup);
  550. if (!bkup_ret)
  551. {
  552. return FALSE;
  553. }
  554. }
  555. timestamp = xaccDateUtilGetStampNow ();
  556. backup = g_strconcat( datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL );
  557. g_free (timestamp);
  558. bkup_ret = gnc_int_link_or_make_backup(be, datafile, backup);
  559. g_free(backup);
  560. return bkup_ret;
  561. }
  562. /* ================================================================= */
  563. static gboolean
  564. gnc_xml_be_write_to_file(FileBackend *fbe,
  565. QofBook *book,
  566. const gchar *datafile,
  567. gboolean make_backup)
  568. {
  569. QofBackend *be = &fbe->be;
  570. char *tmp_name;
  571. struct stat statbuf;
  572. int rc;
  573. QofBackendError be_err;
  574. ENTER (" book=%p file=%s", book, datafile);
  575. /* If the book is 'clean', recently saved, then don't save again. */
  576. /* XXX this is currently broken due to faulty 'Save As' logic. */
  577. /* if (FALSE == qof_book_not_saved (book)) return FALSE; */
  578. tmp_name = g_new(char, strlen(datafile) + 12);
  579. strcpy(tmp_name, datafile);
  580. strcat(tmp_name, ".tmp-XXXXXX");
  581. if (!mktemp(tmp_name))
  582. {
  583. qof_backend_set_error(be, ERR_BACKEND_MISC);
  584. qof_backend_set_message( be, "Failed to make temp file" );
  585. LEAVE("");
  586. return FALSE;
  587. }
  588. if (make_backup)
  589. {
  590. if (!gnc_xml_be_backup_file(fbe))
  591. {
  592. LEAVE("");
  593. return FALSE;
  594. }
  595. }
  596. if (gnc_book_write_to_xml_file_v2(book, tmp_name, fbe->file_compression))
  597. {
  598. /* Record the file's permissions before g_unlinking it */
  599. rc = g_stat(datafile, &statbuf);
  600. if (rc == 0)
  601. {
  602. /* We must never chmod the file /dev/null */
  603. g_assert(safe_strcmp(tmp_name, "/dev/null") != 0);
  604. /* Use the permissions from the original data file */
  605. if (g_chmod(tmp_name, statbuf.st_mode) != 0)
  606. {
  607. /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
  608. /* qof_backend_set_message( be, "Failed to chmod filename %s", tmp_name ); */
  609. /* Even if the chmod did fail, the save
  610. nevertheless completed successfully. It is
  611. therefore wrong to signal the ERR_BACKEND_PERM
  612. error here which implies that the saving itself
  613. failed. Instead, we simply ignore this. */
  614. PWARN("unable to chmod filename %s: %s",
  615. tmp_name ? tmp_name : "(null)",
  616. g_strerror(errno) ? g_strerror(errno) : "");
  617. #if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */
  618. /* g_free(tmp_name); */
  619. /* return FALSE; */
  620. #endif
  621. }
  622. #ifdef HAVE_CHOWN
  623. /* Don't try to change the owner. Only root can do
  624. that. */
  625. if (chown(tmp_name, -1, statbuf.st_gid) != 0)
  626. {
  627. /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
  628. /* qof_backend_set_message( be, "Failed to chown filename %s", tmp_name ); */
  629. /* A failed chown doesn't mean that the saving itself
  630. failed. So don't abort with an error here! */
  631. PWARN("unable to chown filename %s: %s",
  632. tmp_name ? tmp_name : "(null)",
  633. strerror(errno) ? strerror(errno) : "");
  634. #if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
  635. /* g_free(tmp_name);
  636. return FALSE; */
  637. #endif
  638. }
  639. #endif
  640. }
  641. if (g_unlink(datafile) != 0 && errno != ENOENT)
  642. {
  643. qof_backend_set_error(be, ERR_BACKEND_READONLY);
  644. PWARN("unable to unlink filename %s: %s",
  645. datafile ? datafile : "(null)",
  646. g_strerror(errno) ? g_strerror(errno) : "");
  647. g_free(tmp_name);
  648. LEAVE("");
  649. return FALSE;
  650. }
  651. if (!gnc_int_link_or_make_backup(fbe, tmp_name, datafile))
  652. {
  653. qof_backend_set_error(be, ERR_FILEIO_BACKUP_ERROR);
  654. qof_backend_set_message( be, "Failed to make backup file %s",
  655. datafile ? datafile : "NULL" );
  656. g_free(tmp_name);
  657. LEAVE("");
  658. return FALSE;
  659. }
  660. if (g_unlink(tmp_name) != 0)
  661. {
  662. qof_backend_set_error(be, ERR_BACKEND_PERM);
  663. PWARN("unable to unlink temp filename %s: %s",
  664. tmp_name ? tmp_name : "(null)",
  665. g_strerror(errno) ? g_strerror(errno) : "");
  666. g_free(tmp_name);
  667. LEAVE("");
  668. return FALSE;
  669. }
  670. g_free(tmp_name);
  671. /* Since we successfully saved the book,
  672. * we should mark it clean. */
  673. qof_book_mark_saved (book);
  674. LEAVE (" successful save of book=%p to file=%s", book, datafile);
  675. return TRUE;
  676. }
  677. else
  678. {
  679. if (g_unlink(tmp_name) != 0)
  680. {
  681. switch (errno)
  682. {
  683. case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
  684. case EACCES:
  685. case EPERM:
  686. case ENOSYS:
  687. case EROFS:
  688. be_err = ERR_BACKEND_READONLY;
  689. break;
  690. default:
  691. be_err = ERR_BACKEND_MISC;
  692. }
  693. qof_backend_set_error(be, be_err);
  694. PWARN("unable to unlink temp_filename %s: %s",
  695. tmp_name ? tmp_name : "(null)",
  696. g_strerror(errno) ? g_strerror(errno) : "");
  697. /* already in an error just flow on through */
  698. }
  699. else
  700. {
  701. /* Use a generic write error code */
  702. qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
  703. qof_backend_set_message( be, "Unable to write to temp file %s",
  704. tmp_name ? tmp_name : "NULL" );
  705. }
  706. g_free(tmp_name);
  707. LEAVE("");
  708. return FALSE;
  709. }
  710. LEAVE("");
  711. return TRUE;
  712. }
  713. /* ================================================================= */
  714. /*
  715. * Clean up any lock files from prior crashes, and clean up old
  716. * backup and log files.
  717. */
  718. static void
  719. gnc_xml_be_remove_old_files(FileBackend *be)
  720. {
  721. const gchar *dent;
  722. GDir *dir;
  723. struct stat lockstatbuf, statbuf;
  724. time_t now;
  725. if (g_stat (be->lockfile, &lockstatbuf) != 0)
  726. return;
  727. dir = g_dir_open (be->dirname, 0, NULL);
  728. if (!dir)
  729. return;
  730. now = time(NULL);
  731. while ((dent = g_dir_read_name(dir)) != NULL)
  732. {
  733. gchar *name;
  734. /* Ensure we only evaluate GnuCash related files. */
  735. if ( !(g_str_has_suffix(dent, ".LNK") ||
  736. g_str_has_suffix(dent, ".xac") /* old data file extension */ ||
  737. g_str_has_suffix(dent, GNC_DATAFILE_EXT) ||
  738. g_str_has_suffix(dent, GNC_LOGFILE_EXT)) )
  739. continue;
  740. name = g_build_filename(be->dirname, dent, (gchar*)NULL);
  741. /* Only evaluate files associated with the current data file. */
  742. if (!g_str_has_prefix(name, be->fullpath))
  743. continue;
  744. /* Never remove the current data file itself */
  745. if (g_strcmp0(name, be->fullpath) == 0)
  746. continue;
  747. /* Test if the current file is a lock file */
  748. if (g_str_has_suffix(name, ".LNK"))
  749. {
  750. /* Is a lock file. Skip the active lock file */
  751. if ((g_strcmp0(name, be->linkfile) != 0) &&
  752. /* Only delete lock files older than the active one */
  753. (g_stat(name, &statbuf) == 0) &&
  754. (statbuf.st_mtime < lockstatbuf.st_mtime))
  755. {
  756. PINFO ("remove stale lock file: %s", name);
  757. g_unlink(name);
  758. }
  759. continue;
  760. }
  761. /* At this point we're sure the file's name is in one of these forms:
  762. * <fullpath/to/datafile><anything>.gnucash
  763. * <fullpath/to/datafile><anything>.xac
  764. * <fullpath/to/datafile><anything>.log
  765. *
  766. * To be a file generated by GnuCash, the <anything> part should consist
  767. * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
  768. * regular expression.
  769. */
  770. {
  771. /* Find the start of the date stamp. This takes some pointer
  772. * juggling, but considering the above tests, this should always
  773. * be safe */
  774. regex_t pattern;
  775. gchar *stamp_start = name + strlen(be->fullpath);
  776. gchar *expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
  777. GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
  778. gboolean got_date_stamp = FALSE;
  779. if (regcomp(&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
  780. PWARN("Cannot compile regex for date stamp");
  781. else if (regexec(&pattern, stamp_start, 0, NULL, 0) == 0)
  782. got_date_stamp = TRUE;
  783. g_free(expression);
  784. if (!got_date_stamp) /* Not a gnucash created file after all... */
  785. continue;
  786. }
  787. /* The file is a backup or log file. Check the user's retention preference
  788. * to determine if we should keep it or not
  789. */
  790. if (be->file_retention_type == XML_RETAIN_NONE)
  791. {
  792. PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
  793. g_unlink(name);
  794. }
  795. else if ((be->file_retention_type == XML_RETAIN_DAYS) &&
  796. (be->file_retention_days > 0))
  797. {
  798. int days;
  799. /* Is the backup file old enough to delete */
  800. if (g_stat(name, &statbuf) != 0)
  801. continue;
  802. days = (int)(difftime(now, statbuf.st_mtime) / 86400);
  803. PINFO ("file retention = %d days", be->file_retention_days);
  804. if (days >= be->file_retention_days)
  805. {
  806. PINFO ("remove stale file: %s - reason: more than %d days old", name, days);
  807. g_unlink(name);
  808. }
  809. }
  810. g_free(name);
  811. }
  812. g_dir_close (dir);
  813. }
  814. static void
  815. xml_sync_all(QofBackend* be, QofBook *book)
  816. {
  817. FileBackend *fbe = (FileBackend *) be;
  818. ENTER ("book=%p, primary=%p", book, fbe->primary_book);
  819. /* We make an important assumption here, that we might want to change
  820. * in the future: when the user says 'save', we really save the one,
  821. * the only, the current open book, and nothing else. We do this
  822. * because we assume that any other books that we are dealing with
  823. * are 'read-only', non-editable, because they are closed books.
  824. * If we ever want to have more than one book open read-write,
  825. * this will have to change.
  826. */
  827. if (NULL == fbe->primary_book) fbe->primary_book = book;
  828. if (book != fbe->primary_book) return;
  829. gnc_xml_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
  830. gnc_xml_be_remove_old_files (fbe);
  831. LEAVE ("book=%p", book);
  832. }
  833. /* ================================================================= */
  834. /* Routines to deal with the creation of multiple books.
  835. * The core design assumption here is that the book
  836. * begin-edit/commit-edit routines are used solely to write out
  837. * closed accounting periods to files. They're not currently
  838. * designed to do anything other than this. (Although they could be).
  839. */
  840. static char *
  841. build_period_filepath (FileBackend *fbe, QofBook *book)
  842. {
  843. int len;
  844. char *str, *p, *q;
  845. len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
  846. str = g_new (char, len);
  847. strcpy (str, fbe->fullpath);
  848. /* XXX it would be nice for the user if we made the book
  849. * closing date and/or title part of the file-name. */
  850. p = strrchr (str, G_DIR_SEPARATOR);
  851. p++;
  852. p = stpcpy (p, "book-");
  853. p = guid_to_string_buff (qof_book_get_guid(book), p);
  854. p = stpcpy (p, "-");
  855. q = strrchr (fbe->fullpath, G_DIR_SEPARATOR);
  856. q++;
  857. p = stpcpy (p, q);
  858. p = stpcpy (p, ".gml");
  859. return str;
  860. }
  861. static void
  862. xml_begin_edit (QofBackend *be, QofInstance *inst)
  863. {
  864. if (0) build_period_filepath(0, 0);
  865. #if BORKEN_FOR_NOW
  866. FileBackend *fbe = (FileBackend *) be;
  867. QofBook *book = gp;
  868. const char * filepath;
  869. QofIdTypeConst typ = QOF_INSTANCE(inst)->e_type;
  870. if (strcmp (GNC_ID_PERIOD, typ)) return;
  871. filepath = build_period_filepath(fbe, book);
  872. PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
  873. if (NULL == fbe->primary_book)
  874. {
  875. PERR ("You should have saved the data "
  876. "at least once before closing the books!\n");
  877. }
  878. /* XXX To be anal about it, we should really be checking to see
  879. * if there already is a file with this book GncGUID, and disallowing
  880. * further progress. This is because we are not allowed to
  881. * modify books that are closed (They should be treated as
  882. * 'read-only').
  883. */
  884. #endif
  885. }
  886. static void
  887. xml_rollback_edit (QofBackend *be, QofInstance *inst)
  888. {
  889. #if BORKEN_FOR_NOW
  890. QofBook *book = gp;
  891. if (strcmp (GNC_ID_PERIOD, typ)) return;
  892. PINFO ("book=%p", book);
  893. #endif
  894. }
  895. static void
  896. xml_commit_edit (QofBackend *be, QofInstance *inst)
  897. {
  898. if (qof_instance_get_dirty(inst) && qof_get_alt_dirty_mode() &&
  899. !(qof_instance_get_infant(inst) && qof_instance_get_destroying(inst)))
  900. {
  901. qof_collection_mark_dirty(qof_instance_get_collection(inst));
  902. qof_book_mark_dirty(qof_instance_get_book(inst));
  903. }
  904. #if BORKEN_FOR_NOW
  905. FileBackend *fbe = (FileBackend *) be;
  906. QofBook *book = gp;
  907. const char * filepath;
  908. if (strcmp (GNC_ID_PERIOD, typ)) return;
  909. filepath = build_period_filepath(fbe, book);
  910. PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
  911. gnc_xml_be_write_to_file(fbe, book, filepath, FALSE);
  912. /* We want to force a save of the current book at this point,
  913. * because if we don't, and the user forgets to do so, then
  914. * there'll be the same transactions in the closed book,
  915. * and also in the current book. */
  916. gnc_xml_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
  917. #endif
  918. }
  919. /* ---------------------------------------------------------------------- */
  920. /* Load financial data from a file into the book, automatically
  921. detecting the format of the file, if possible. Return FALSE on
  922. error, and set the error parameter to indicate what went wrong if
  923. it's not NULL. This function does not manage file locks in any
  924. way. */
  925. static void
  926. gnc_xml_be_load_from_file (QofBackend *bend, QofBook *book, QofBackendLoadType loadType)
  927. {
  928. QofBackendError error;
  929. gboolean rc;
  930. FileBackend *be = (FileBackend *) bend;
  931. if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
  932. error = ERR_BACKEND_NO_ERR;
  933. be->primary_book = book;
  934. switch (gnc_xml_be_determine_file_type(be->fullpath))
  935. {
  936. case GNC_BOOK_XML2_FILE:
  937. rc = qof_session_load_from_xml_file_v2 (be, book);
  938. if (FALSE == rc)
  939. {
  940. PWARN( "Syntax error in Xml File %s", be->fullpath );
  941. error = ERR_FILEIO_PARSE_ERROR;
  942. }
  943. break;
  944. case GNC_BOOK_XML2_FILE_NO_ENCODING:
  945. error = ERR_FILEIO_NO_ENCODING;
  946. PWARN( "No character encoding in Xml File %s", be->fullpath );
  947. break;
  948. case GNC_BOOK_XML1_FILE:
  949. rc = qof_session_load_from_xml_file (book, be->fullpath);
  950. if (FALSE == rc)
  951. {
  952. PWARN( "Syntax error in Xml File %s", be->fullpath );
  953. error = ERR_FILEIO_PARSE_ERROR;
  954. }
  955. break;
  956. default:
  957. /* If file type wasn't known, check errno again to give the
  958. user some more useful feedback for some particular error
  959. conditions. */
  960. switch (errno)
  961. {
  962. case EACCES: /* No read permission */
  963. PWARN("No read permission to file");
  964. error = ERR_FILEIO_FILE_EACCES;
  965. break;
  966. case EISDIR: /* File is a directory - but on this error we don't arrive here */
  967. PWARN("Filename is a directory");
  968. error = ERR_FILEIO_FILE_NOT_FOUND;
  969. break;
  970. default:
  971. PWARN("File not any known type");
  972. error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
  973. break;
  974. }
  975. break;
  976. }
  977. if (error != ERR_BACKEND_NO_ERR)
  978. {
  979. qof_backend_set_error(bend, error);
  980. }
  981. /* We just got done loading, it can't possibly be dirty !! */
  982. qof_book_mark_saved (book);
  983. }
  984. /* ---------------------------------------------------------------------- */
  985. static gboolean
  986. save_may_clobber_data (QofBackend *bend)
  987. {
  988. struct stat statbuf;
  989. if (!bend->fullpath) return FALSE;
  990. /* FIXME: Make sure this doesn't need more sophisticated semantics
  991. * in the face of special file, devices, pipes, symlinks, etc. */
  992. if (g_stat(bend->fullpath, &statbuf) == 0) return TRUE;
  993. return FALSE;
  994. }
  995. static void
  996. gnc_xml_be_write_accounts_to_file(QofBackend *be, QofBook *book)
  997. {
  998. const gchar *datafile;
  999. datafile = ((FileBackend *)be)->fullpath;
  1000. gnc_book_write_accounts_to_xml_file_v2(be, book, datafile);
  1001. }
  1002. /* ================================================================= */
  1003. #if 0 //def GNUCASH_MAJOR_VERSION
  1004. QofBackend *
  1005. libgncmod_backend_file_LTX_gnc_backend_new(void)
  1006. {
  1007. fbe->dirname = NULL;
  1008. fbe->fullpath = NULL;
  1009. fbe->lockfile = NULL;
  1010. fbe->linkfile = NULL;
  1011. fbe->lockfd = -1;
  1012. fbe->primary_book = NULL;
  1013. return be;
  1014. }
  1015. #endif
  1016. static void
  1017. retain_changed_cb(GConfEntry *entry, gpointer user_data)
  1018. {
  1019. FileBackend *be = (FileBackend*)user_data;
  1020. g_return_if_fail(be != NULL);
  1021. be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
  1022. }
  1023. static void
  1024. retain_type_changed_cb(GConfEntry *entry, gpointer user_data)
  1025. {
  1026. FileBackend *be = (FileBackend*)user_data;
  1027. gchar *choice = NULL;
  1028. g_return_if_fail(be != NULL);
  1029. choice = gnc_gconf_get_string(GCONF_GENERAL, KEY_RETAIN_TYPE, NULL);
  1030. if (!choice)
  1031. choice = g_strdup("days");
  1032. if (safe_strcmp (choice, "never") == 0)
  1033. be->file_retention_type = XML_RETAIN_NONE;
  1034. else if (safe_strcmp (choice, "forever") == 0)
  1035. be->file_retention_type = XML_RETAIN_ALL;
  1036. else
  1037. {
  1038. if (safe_strcmp (choice, "days") != 0)
  1039. PERR("bad value '%s'", choice ? choice : "(null)");
  1040. be->file_retention_type = XML_RETAIN_DAYS;
  1041. }
  1042. g_free (choice);
  1043. }
  1044. static void
  1045. compression_changed_cb(GConfEntry *entry, gpointer user_data)
  1046. {
  1047. FileBackend *be = (FileBackend*)user_data;
  1048. g_return_if_fail(be != NULL);
  1049. be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
  1050. }
  1051. static QofBackend*
  1052. gnc_backend_new(void)
  1053. {
  1054. FileBackend *gnc_be;
  1055. QofBackend *be;
  1056. gnc_be = g_new0(FileBackend, 1);
  1057. be = (QofBackend*) gnc_be;
  1058. qof_backend_init(be);
  1059. be->session_begin = xml_session_begin;
  1060. be->session_end = xml_session_end;
  1061. be->destroy_backend = xml_destroy_backend;
  1062. be->load = gnc_xml_be_load_from_file;
  1063. /* The file backend treats accounting periods transactionally. */
  1064. be->begin = xml_begin_edit;
  1065. be->commit = xml_commit_edit;
  1066. be->rollback = xml_rollback_edit;
  1067. /* The file backend always loads all data ... */
  1068. be->compile_query = NULL;
  1069. be->free_query = NULL;
  1070. be->run_query = NULL;
  1071. /* The file backend will never be multi-user... */
  1072. be->events_pending = NULL;
  1073. be->process_events = NULL;
  1074. be->sync = xml_sync_all;
  1075. be->load_config = NULL;
  1076. be->get_config = NULL;
  1077. be->export_fn = gnc_xml_be_write_accounts_to_file;
  1078. gnc_be->dirname = NULL;
  1079. gnc_be->fullpath = NULL;
  1080. gnc_be->lockfile = NULL;
  1081. gnc_be->linkfile = NULL;
  1082. gnc_be->lockfd = -1;
  1083. gnc_be->primary_book = NULL;
  1084. gnc_be->file_retention_days = (int)gnc_gconf_get_float(GCONF_GENERAL, KEY_RETAIN_DAYS, NULL);
  1085. gnc_be->file_compression = gnc_gconf_get_bool(GCONF_GENERAL, KEY_FILE_COMPRESSION, NULL);
  1086. retain_type_changed_cb(NULL, (gpointer)be); /* Get retain_type from gconf */
  1087. if ( (gnc_be->file_retention_type == XML_RETAIN_DAYS) &&
  1088. (gnc_be->file_retention_days == 0 ) )
  1089. {
  1090. /* Backwards compatibility code. Pre 2.3.15, 0 retain_days meant
  1091. * "keep forever". From 2.3.15 on this is controlled via a multiple
  1092. * choice ("retain_type"). So if we find a 0 retain_days value with
  1093. * a "days" retain_type, we should interpret it as if we got a
  1094. * "forever" retain_type.
  1095. */
  1096. gnc_be->file_retention_type = XML_RETAIN_ALL;
  1097. gnc_gconf_set_string (GCONF_GENERAL, KEY_RETAIN_TYPE, "forever", NULL);
  1098. }
  1099. gnc_gconf_general_register_cb(KEY_RETAIN_DAYS, retain_changed_cb, be);
  1100. gnc_gconf_general_register_cb(KEY_RETAIN_TYPE, retain_type_changed_cb, be);
  1101. gnc_gconf_general_register_cb(KEY_FILE_COMPRESSION, compression_changed_cb, be);
  1102. return be;
  1103. }
  1104. static void
  1105. business_core_xml_init(void)
  1106. {
  1107. /* Initialize our pointers into the backend subsystem */
  1108. gnc_address_xml_initialize ();
  1109. gnc_billterm_xml_initialize ();
  1110. gnc_customer_xml_initialize ();
  1111. gnc_employee_xml_initialize ();
  1112. gnc_entry_xml_initialize ();
  1113. gnc_invoice_xml_initialize ();
  1114. gnc_job_xml_initialize ();
  1115. gnc_order_xml_initialize ();
  1116. gnc_owner_xml_initialize ();
  1117. gnc_taxtable_xml_initialize ();
  1118. gnc_vendor_xml_initialize ();
  1119. }
  1120. static void
  1121. gnc_provider_free (QofBackendProvider *prov)
  1122. {
  1123. prov->provider_name = NULL;
  1124. prov->access_method = NULL;
  1125. g_free (prov);
  1126. }
  1127. #ifndef GNC_NO_LOADABLE_MODULES
  1128. G_MODULE_EXPORT void
  1129. qof_backend_module_init(void)
  1130. {
  1131. gnc_module_init_backend_xml();
  1132. }
  1133. #endif
  1134. void
  1135. gnc_module_init_backend_xml(void)
  1136. {
  1137. QofBackendProvider *prov;
  1138. prov = g_new0 (QofBackendProvider, 1);
  1139. prov->provider_name = "GnuCash File Backend Version 2";
  1140. prov->access_method = "file";
  1141. prov->partial_book_supported = FALSE;
  1142. prov->backend_new = gnc_backend_new;
  1143. prov->provider_free = gnc_provider_free;
  1144. prov->check_data_type = gnc_determine_file_type;
  1145. qof_backend_register_provider (prov);
  1146. prov = g_new0 (QofBackendProvider, 1);
  1147. prov->provider_name = "GnuCash File Backend Version 2";
  1148. prov->access_method = "xml";
  1149. prov->partial_book_supported = FALSE;
  1150. prov->backend_new = gnc_backend_new;
  1151. prov->provider_free = gnc_provider_free;
  1152. prov->check_data_type = gnc_determine_file_type;
  1153. qof_backend_register_provider (prov);
  1154. /* And the business objects */
  1155. business_core_xml_init();
  1156. }
  1157. /* ========================== END OF FILE ===================== */
  1158. /* For emacs we set some variables concerning indentation:
  1159. * Local Variables: *
  1160. * indent-tabs-mode:nil *
  1161. * c-basic-offset:4 *
  1162. * tab-width:8 *
  1163. * End: */