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