GnuCash  5.6-150-g038405b370+
gnc-xml-backend.cpp
1 /********************************************************************
2  * gnc-xml-backend.cpp: Implement XML file backend. *
3  * Copyright 2016 John Ralls <jralls@ceridwen.us> *
4  * *
5  * This program is distributed in the hope that it will be useful, *
6  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
8  * GNU General Public License for more details. *
9  * *
10  * You should have received a copy of the GNU General Public License*
11  * along with this program; if not, contact: *
12  * *
13  * Free Software Foundation Voice: +1-617-542-5942 *
14  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
15  * Boston, MA 02110-1301, USA gnu@gnu.org *
16 \********************************************************************/
17 #include <glib.h>
18 #include <glib/gstdio.h>
19 
20 #include <config.h>
21 #include <platform.h>
22 #if PLATFORM(WINDOWS)
23 #include <windows.h>
24 #endif
25 #include <errno.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <sys/stat.h>
30 #include <regex.h>
31 #if COMPILER(MSVC)
32 # define g_fopen fopen
33 #endif
34 
35 #include <gnc-engine.h> //for GNC_MOD_BACKEND
36 #include <gnc-uri-utils.h>
37 #include <TransLog.h>
38 #include <gnc-prefs.h>
39 
40 #include <sstream>
41 
42 #include "gnc-xml-backend.hpp"
43 #include "gnc-backend-xml.h"
44 #include "io-gncxml-v2.h"
45 #include "io-gncxml.h"
46 
47 #define XML_URI_PREFIX "xml://"
48 #define FILE_URI_PREFIX "file://"
49 static QofLogModule log_module = GNC_MOD_BACKEND;
50 
51 GncXmlBackend::~GncXmlBackend()
52 {
53  session_end();
54 };
55 
56 bool
57 GncXmlBackend::check_path (const char* fullpath, bool create)
58 {
59  GStatBuf statbuf;
60  char* dirname = g_path_get_dirname (fullpath);
61  /* Again check whether the directory can be accessed */
62  auto rc = g_stat (dirname, &statbuf);
63  if (rc != 0
64 #if COMPILER(MSVC)
65  || (statbuf.st_mode & _S_IFDIR) == 0
66 #else
67  || !S_ISDIR (statbuf.st_mode)
68 #endif
69  )
70  {
71  /* Error on stat or if it isn't a directory means we
72  cannot find this filename */
74  std::string msg {"Couldn't find directory for "};
75  set_message(msg + fullpath);
76  PWARN ("Couldn't find directory for %s", fullpath);
77  g_free(dirname);
78  return false;
79  }
80 
81  /* Now check whether we can g_stat the file itself */
82  rc = g_stat (fullpath, &statbuf);
83  if ((rc != 0) && (!create))
84  {
85  /* Error on stat means the file doesn't exist */
87  std::string msg {"Couldn't find "};
88  set_message(msg + fullpath);
89  PWARN ("Couldn't find %s", fullpath);
90  g_free(dirname);
91  return false;
92  }
93  if (rc == 0
94 #if COMPILER(MSVC)
95  && (statbuf.st_mode & _S_IFDIR) != 0
96 #else
97  && S_ISDIR (statbuf.st_mode)
98 #endif
99  )
100  {
102  std::string msg {"Path "};
103  msg += fullpath;
104  set_message(msg + " is a directory");
105  PWARN ("Path %s is a directory", fullpath);
106  g_free(dirname);
107  return false;
108  }
109  g_free(dirname);
110  return true;
111 }
112 
113 void
114 GncXmlBackend::session_begin(QofSession* session, const char* new_uri,
115  SessionOpenMode mode)
116 {
117  /* Make sure the directory is there */
118  auto path_str = gnc_uri_get_path (new_uri);
119  m_fullpath = path_str;
120  g_free (path_str);
121 
122  if (m_fullpath.empty())
123  {
125  set_message("No path specified");
126  return;
127  }
128  if (mode == SESSION_NEW_STORE && save_may_clobber_data())
129  {
131  PWARN ("Might clobber, no force");
132  return;
133  }
134 
135  if (!check_path(m_fullpath.c_str(),
136  mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE))
137  return;
138 
139  auto dirname = g_path_get_dirname (m_fullpath.c_str());
140  m_dirname = dirname;
141  g_free (dirname);
142 
143 
144 
145  /* ---------------------------------------------------- */
146  /* We should now have a fully resolved path name.
147  * Let's start logging */
148  xaccLogSetBaseName (m_fullpath.c_str());
149  PINFO ("logpath=%s", m_fullpath.empty() ? "(null)" : m_fullpath.c_str());
150 
151  if (mode == SESSION_READ_ONLY)
152  return; // Read-only, don't care about locks.
153 
154  /* Set the lock file */
155  m_lockfile = m_fullpath + ".LCK";
156  get_file_lock(mode);
157 }
158 
159 void
160 GncXmlBackend::session_end()
161 {
162  if (m_book && qof_book_is_readonly (m_book))
163  {
165  return;
166  }
167 
168  if (!m_linkfile.empty())
169  g_unlink (m_linkfile.c_str());
170 
171  if (m_lockfd != -1)
172  {
173  close (m_lockfd);
174  m_lockfd = -1;
175  }
176 
177  if (!m_lockfile.empty())
178  {
179  int rv;
180 #ifdef G_OS_WIN32
181  /* On windows, we need to allow write-access before
182  g_unlink() can succeed */
183  rv = g_chmod (m_lockfile.c_str(), S_IWRITE | S_IREAD);
184 #endif
185  rv = g_unlink (m_lockfile.c_str());
186  if (rv)
187  {
188  PWARN ("Error on g_unlink(%s): %d: %s", m_lockfile.c_str(),
189  errno, g_strerror (errno) ? g_strerror (errno) : "");
190  }
191  }
192 
193  m_dirname.clear();
194  m_fullpath.clear();
195  m_lockfile.clear();
196  m_linkfile.clear();
197 }
198 
199 static QofBookFileType
200 determine_file_type (const std::string& path)
201 {
202  gboolean with_encoding;
203  QofBookFileType v2type;
204 
205  v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding);
206  if (v2type == GNC_BOOK_XML2_FILE)
207  {
208  if (with_encoding)
209  {
210  return GNC_BOOK_XML2_FILE;
211  }
212  else
213  {
214  return GNC_BOOK_XML2_FILE_NO_ENCODING;
215  }
216  }
217  else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
218  {
219  return GNC_BOOK_POST_XML2_0_0_FILE;
220  }
221  else if (v2type == GNC_BOOK_XML1_FILE)
222  {
223  return GNC_BOOK_XML1_FILE;
224  }
225  return GNC_BOOK_NOT_OURS;
226 }
227 
228 void
229 GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
230 {
231 
232  QofBackendError error;
233 
234  if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
235 
236  error = ERR_BACKEND_NO_ERR;
237  if (m_book)
238  g_object_unref(m_book);
239  m_book = QOF_BOOK(g_object_ref(book));
240 
241  int rc;
242  switch (determine_file_type (m_fullpath))
243  {
244  case GNC_BOOK_XML2_FILE:
245  rc = qof_session_load_from_xml_file_v2 (this, book,
246  GNC_BOOK_XML2_FILE);
247  if (rc == FALSE)
248  {
249  PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
250  error = ERR_FILEIO_PARSE_ERROR;
251  }
252  break;
253 
254  case GNC_BOOK_XML2_FILE_NO_ENCODING:
255  error = ERR_FILEIO_NO_ENCODING;
256  PWARN ("No character encoding in Xml File %s", m_fullpath.c_str());
257  break;
258  case GNC_BOOK_XML1_FILE:
259  rc = qof_session_load_from_xml_file (book, m_fullpath.c_str());
260  if (rc == FALSE)
261  {
262  PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
263  error = ERR_FILEIO_PARSE_ERROR;
264  }
265  break;
266  case GNC_BOOK_POST_XML2_0_0_FILE:
267  error = ERR_BACKEND_TOO_NEW;
268  PWARN ("Version of Xml file %s is newer than what we can read",
269  m_fullpath.c_str());
270  break;
271  default:
272  /* If file type wasn't known, check errno again to give the
273  user some more useful feedback for some particular error
274  conditions. */
275  switch (errno)
276  {
277  case EACCES: /* No read permission */
278  PWARN ("No read permission to file");
279  error = ERR_FILEIO_FILE_EACCES;
280  break;
281  case EISDIR: /* File is a directory - but on this error we don't arrive here */
282  PWARN ("Filename is a directory");
284  break;
285  default:
286  PWARN ("File not any known type");
288  break;
289  }
290  break;
291  }
292 
293  if (error != ERR_BACKEND_NO_ERR)
294  {
295  set_error(error);
296  }
297 
298  /* We just got done loading, it can't possibly be dirty !! */
300 }
301 
302 void
303 GncXmlBackend::sync(QofBook* book)
304 {
305  /* We make an important assumption here, that we might want to change
306  * in the future: when the user says 'save', we really save the one,
307  * the only, the current open book, and nothing else. In any case the plans
308  * for multiple books have been removed in the meantime and there is just one
309  * book, no more.
310  */
311  if (m_book == nullptr)
312  m_book = QOF_BOOK(g_object_ref(book));
313  if (book != m_book) return;
314 
315  if (qof_book_is_readonly (m_book))
316  {
317  /* Are we read-only? Don't continue in this case. */
319  return;
320  }
321 
322  write_to_file (true);
323  remove_old_files();
324 }
325 
326 void
328 {
329  if (qof_instance_is_dirty(instance))
330  qof_instance_mark_clean(instance);
331 }
332 
333 bool
334 GncXmlBackend::save_may_clobber_data()
335 {
336  if (m_fullpath.empty())
337  return false;
338  GStatBuf statbuf;
339  auto rc = g_stat (m_fullpath.c_str(), &statbuf);
340  return rc == 0;
341 }
342 
343 void
345 {
346  auto out = g_fopen(m_fullpath.c_str(), "w");
347  if (out == NULL)
348  {
350  set_message(strerror(errno));
351  return;
352  }
353  gnc_book_write_accounts_to_xml_filehandle_v2(this, book, out);
354  fclose(out);
355 }
356 
357 bool
358 GncXmlBackend::write_to_file (bool make_backup)
359 {
360  QofBackendError be_err;
361 
362  ENTER (" book=%p file=%s", m_book, m_fullpath.c_str());
363 
364  if (m_book && qof_book_is_readonly (m_book))
365  {
366  /* Are we read-only? Don't continue in this case. */
368  LEAVE ("");
369  return FALSE;
370  }
371 
372  /* If the book is 'clean', recently saved, then don't save again. */
373  /* XXX this is currently broken due to faulty 'Save As' logic. */
374  /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */
375 
376 
377  auto tmp_name = g_new (char, strlen (m_fullpath.c_str()) + 12);
378  strcpy (tmp_name, m_fullpath.c_str());
379  strcat (tmp_name, ".tmp-XXXXXX");
380 
381  /* Clang static analyzer and GNU ld flag mktemp as a security risk, which is
382  * theoretically true, but we can't use mkstemp because we need to
383  * open the file ourselves because of compression. None of the alternatives
384  * is any more secure.
385  *
386  * Xcode marks mktemp as deprecated
387  */
388 #pragma GCC diagnostic push
389 #pragma GCC diagnostic warning "-Wdeprecated-declarations"
390  if (!mktemp (tmp_name))
391 #pragma GCC diagnostic pop
392  {
393  g_free (tmp_name);
395  set_message("Failed to make temp file");
396  LEAVE ("");
397  return FALSE;
398  }
399 
400  if (make_backup)
401  {
402  if (!backup_file ())
403  {
404  g_free (tmp_name);
405  LEAVE ("");
406  return FALSE;
407  }
408  }
409 
410  if (gnc_book_write_to_xml_file_v2 (m_book, tmp_name,
411  gnc_prefs_get_file_save_compressed ()))
412  {
413  /* Record the file's permissions before g_unlinking it */
414  GStatBuf statbuf;
415  auto rc = g_stat (m_fullpath.c_str(), &statbuf);
416  if (rc == 0)
417  {
418  /* We must never chmod the file /dev/null */
419  g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0);
420 
421  /* Use the permissions from the original data file */
422  if (g_chmod (tmp_name, statbuf.st_mode) != 0)
423  {
424  /* set_error(ERR_BACKEND_PERM); */
425  /* set_message("Failed to chmod filename %s", tmp_name ); */
426  /* Even if the chmod did fail, the save
427  nevertheless completed successfully. It is
428  therefore wrong to signal the ERR_BACKEND_PERM
429  error here which implies that the saving itself
430  failed. Instead, we simply ignore this. */
431  PWARN ("unable to chmod filename %s: %s",
432  tmp_name ? tmp_name : "(null)",
433  g_strerror (errno) ? g_strerror (errno) : "");
434 #if VFAT_DOESNT_SUCK /* chmod always fails on vfat/samba fs */
435  /* g_free(tmp_name); */
436  /* return FALSE; */
437 #endif
438  }
439 #ifdef HAVE_CHOWN
440  /* Don't try to change the owner. Only root can do
441  that. */
442  if (chown (tmp_name, -1, statbuf.st_gid) != 0)
443  {
444  /* set_error(ERR_BACKEND_PERM); */
445  /* set_message("Failed to chown filename %s", tmp_name ); */
446  /* A failed chown doesn't mean that the saving itself
447  failed. So don't abort with an error here! */
448  PWARN ("unable to chown filename %s: %s",
449  tmp_name ? tmp_name : "(null)",
450  strerror (errno) ? strerror (errno) : "");
451 #if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
452  /* g_free(tmp_name);
453  return FALSE; */
454 #endif
455  }
456 #endif
457  }
458  if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
459  {
461  PWARN ("unable to unlink filename %s: %s",
462  m_fullpath.empty() ? "(null)" : m_fullpath.c_str(),
463  g_strerror (errno) ? g_strerror (errno) : "");
464  g_free (tmp_name);
465  LEAVE ("");
466  return FALSE;
467  }
468  if (!link_or_make_backup (tmp_name, m_fullpath))
469  {
471  std::string msg{"Failed to make backup file "};
472  set_message(msg + (m_fullpath.empty() ? "NULL" : m_fullpath));
473  g_free (tmp_name);
474  LEAVE ("");
475  return FALSE;
476  }
477  if (g_unlink (tmp_name) != 0)
478  {
480  PWARN ("unable to unlink temp filename %s: %s",
481  tmp_name ? tmp_name : "(null)",
482  g_strerror (errno) ? g_strerror (errno) : "");
483  g_free (tmp_name);
484  LEAVE ("");
485  return FALSE;
486  }
487  g_free (tmp_name);
488 
489  /* Since we successfully saved the book,
490  * we should mark it clean. */
492  LEAVE (" successful save of book=%p to file=%s", m_book,
493  m_fullpath.c_str());
494  return TRUE;
495  }
496  else
497  {
498  if (g_unlink (tmp_name) != 0)
499  {
500  switch (errno)
501  {
502  case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
503  case EACCES:
504  case EPERM:
505  case ENOSYS:
506  case EROFS:
507  be_err = ERR_BACKEND_READONLY;
508  break;
509  default:
510  be_err = ERR_BACKEND_MISC;
511  break;
512  }
513  set_error(be_err);
514  PWARN ("unable to unlink temp_filename %s: %s",
515  tmp_name ? tmp_name : "(null)",
516  g_strerror (errno) ? g_strerror (errno) : "");
517  /* already in an error just flow on through */
518  }
519  else
520  {
521  /* Use a generic write error code */
523  std::string msg{"Unable to write to temp file "};
524  set_message(msg + (tmp_name ? tmp_name : "NULL"));
525  }
526  g_free (tmp_name);
527  LEAVE ("");
528  return FALSE;
529  }
530  g_free (tmp_name);
531  LEAVE ("");
532  return TRUE;
533 }
534 
535 static bool
536 copy_file (const std::string& orig, const std::string& bkup)
537 {
538  constexpr size_t buf_size = 1024;
539  char buf[buf_size];
540  int flags = 0;
541  ssize_t count_write = 0;
542  ssize_t count_read = 0;
543 
544 
545 #ifdef G_OS_WIN32
546  flags = O_BINARY;
547 #endif
548 
549  auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0);
550  if (orig_fd == -1)
551  {
552  return false;
553  }
554  auto bkup_fd = g_open (bkup.c_str(),
555  O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
556  if (bkup_fd == -1)
557  {
558  close (orig_fd);
559  return FALSE;
560  }
561 
562  do
563  {
564  count_read = read (orig_fd, buf, buf_size);
565  if (count_read == -1 && errno != EINTR)
566  {
567  close (orig_fd);
568  close (bkup_fd);
569  return FALSE;
570  }
571 
572  if (count_read > 0)
573  {
574  count_write = write (bkup_fd, buf, count_read);
575  if (count_write == -1)
576  {
577  close (orig_fd);
578  close (bkup_fd);
579  return FALSE;
580  }
581  }
582  }
583  while (count_read > 0);
584 
585  close (orig_fd);
586  close (bkup_fd);
587 
588  return TRUE;
589 }
590 
591 bool
592 GncXmlBackend::link_or_make_backup (const std::string& orig,
593  const std::string& bkup)
594 {
595  gboolean copy_success = FALSE;
596  int err_ret =
597 #ifdef HAVE_LINK
598  link (orig.c_str(), bkup.c_str())
599 #else
600  - 1
601 #endif
602  ;
603  if (err_ret != 0)
604  {
605 #ifdef HAVE_LINK
606  if (errno == EPERM || errno == ENOSYS
607 # ifdef EOPNOTSUPP
608  || errno == EOPNOTSUPP
609 # endif
610 # ifdef ENOTSUP
611  || errno == ENOTSUP
612 # endif
613 # ifdef ENOSYS
614  || errno == ENOSYS
615 # endif
616  )
617 #endif
618  {
619  copy_success = copy_file (orig.c_str(), bkup);
620  }
621 
622  if (!copy_success)
623  {
625  PWARN ("unable to make file backup from %s to %s: %s",
626  orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) : "");
627  return false;
628  }
629  }
630 
631  return true;
632 }
633 
634 void
635 GncXmlBackend::get_file_lock (SessionOpenMode mode)
636 {
637  m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL ,
638  S_IRUSR | S_IWUSR);
639  if (m_lockfd == -1)
640  {
641  QofBackendError be_err{ERR_BACKEND_NO_ERR};
642  /* oops .. we can't create the lockfile .. */
643  switch (errno)
644  {
645  case EACCES:
646  set_message("Unable to create lockfile, make sure that you have write access to the directory.");
647  be_err = ERR_BACKEND_READONLY;
648  break;
649 
650  case EROFS:
651  set_message("Unable to create lockfile, data file is on a read-only filesystem.");
652  be_err = ERR_BACKEND_READONLY;
653  break;
654  case ENOSPC:
655  set_message("Unable to create lockfile, no space on filesystem.");
656  be_err = ERR_BACKEND_READONLY;
657  break;
658  case EEXIST:
659  be_err = ERR_BACKEND_LOCKED;
660  break;
661  default:
662  PWARN ("Unable to create the lockfile %s: %s",
663  m_lockfile.c_str(), strerror(errno));
664  set_message("Lockfile creation failed. Please see the tracefile for details.");
665  be_err = ERR_FILEIO_FILE_LOCKERR;
666  }
667  if (!(mode == SESSION_BREAK_LOCK && be_err == ERR_BACKEND_LOCKED))
668  {
669  set_error(be_err);
670  m_lockfile.clear();
671  }
672  }
673 }
674 
675 bool
676 GncXmlBackend::backup_file()
677 {
678  GStatBuf statbuf;
679 
680  auto datafile = m_fullpath.c_str();
681 
682  auto rc = g_stat (datafile, &statbuf);
683  if (rc)
684  return (errno == ENOENT);
685 
686  if (determine_file_type (m_fullpath) == GNC_BOOK_BIN_FILE)
687  {
688  /* make a more permanent safer backup */
689  auto bin_bkup = m_fullpath + "-binfmt.bkup";
690  auto bkup_ret = link_or_make_backup (m_fullpath, bin_bkup);
691  if (!bkup_ret)
692  {
693  return false;
694  }
695  }
696 
697  auto timestamp = gnc_date_timestamp ();
698  auto backup = m_fullpath + "." + timestamp + GNC_DATAFILE_EXT;
699  g_free (timestamp);
700 
701  return link_or_make_backup (datafile, backup);
702 }
703 
704 /*
705  * Clean up any lock files from prior crashes, and clean up old
706  * backup and log files.
707  */
708 
709 void
710 GncXmlBackend::remove_old_files ()
711 {
712  GStatBuf lockstatbuf, statbuf;
713 
714  if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0)
715  return;
716 
717  auto dir = g_dir_open (m_dirname.c_str(), 0, NULL);
718  if (!dir)
719  return;
720 
721  auto now = gnc_time (NULL);
722  const char* dent;
723  while ((dent = g_dir_read_name (dir)) != NULL)
724  {
725  gchar* name;
726 
727  /* Ensure we only evaluate GnuCash related files. */
728  if (! (g_str_has_suffix (dent, ".LNK") ||
729  g_str_has_suffix (dent, ".xac") /* old data file extension */ ||
730  g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
731  g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
732  continue;
733 
734  name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL);
735 
736  /* Only evaluate files associated with the current data file. */
737  if (!g_str_has_prefix (name, m_fullpath.c_str()))
738  {
739  g_free (name);
740  continue;
741  }
742 
743  /* Never remove the current data file itself */
744  if (g_strcmp0 (name, m_fullpath.c_str()) == 0)
745  {
746  g_free (name);
747  continue;
748  }
749 
750  /* Test if the current file is a lock file */
751  if (g_str_has_suffix (name, ".LNK"))
752  {
753  /* Is a lock file. Skip the active lock file */
754  if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) &&
755  /* Only delete lock files older than the active one */
756  (g_stat (name, &statbuf) == 0) &&
757  (statbuf.st_mtime < lockstatbuf.st_mtime))
758  {
759  PINFO ("remove stale lock file: %s", name);
760  g_unlink (name);
761  }
762 
763  g_free (name);
764  continue;
765  }
766 
767  /* At this point we're sure the file's name is in one of these forms:
768  * <fullpath/to/datafile><anything>.gnucash
769  * <fullpath/to/datafile><anything>.xac
770  * <fullpath/to/datafile><anything>.log
771  *
772  * To be a file generated by GnuCash, the <anything> part should consist
773  * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
774  * regular expression.
775  */
776  {
777  /* Find the start of the date stamp. This takes some pointer
778  * juggling, but considering the above tests, this should always
779  * be safe */
780  regex_t pattern;
781  gchar* stamp_start = name + m_fullpath.size();
782  gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
783  GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
784  gboolean got_date_stamp = FALSE;
785 
786  if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
787  PWARN ("Cannot compile regex for date stamp");
788  else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
789  got_date_stamp = TRUE;
790 
791  regfree (&pattern);
792  g_free (expression);
793 
794  if (!got_date_stamp) /* Not a gnucash created file after all... */
795  {
796  g_free (name);
797  continue;
798  }
799  }
800 
801  /* The file is a backup or log file. Check the user's retention preference
802  * to determine if we should keep it or not
803  */
804  if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
805  {
806  PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
807  g_unlink (name);
808  }
809  else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
810  (gnc_prefs_get_file_retention_days () > 0))
811  {
812  int days;
813 
814  /* Is the backup file old enough to delete */
815  if (g_stat (name, &statbuf) != 0)
816  {
817  g_free (name);
818  continue;
819  }
820  days = (int) (difftime (now, statbuf.st_mtime) / 86400);
821 
822  PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ());
823  if (days >= gnc_prefs_get_file_retention_days ())
824  {
825  PINFO ("remove stale file: %s - reason: more than %d days old", name, days);
826  g_unlink (name);
827  }
828  }
829  g_free (name);
830  }
831  g_dir_close (dir);
832 }
No read access permission for the given file.
Definition: qofbackend.h:100
couldn&#39;t parse the data in the file
Definition: qofbackend.h:95
#define qof_instance_is_dirty
Return value of is_dirty flag.
Definition: qofinstance.h:166
char * gnc_date_timestamp(void)
Make a timestamp in YYYYMMDDHHMMSS format.
Definition: gnc-date.cpp:1100
not found / no such file
Definition: qofbackend.h:92
void set_message(std::string &&)
Set a descriptive message that can be displayed to the user when there&#39;s an error.
Definition: qof-backend.cpp:79
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:57
void sync(QofBook *book) override
Synchronizes the engine contents to the backend.
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
in use by another user (ETXTBSY)
Definition: qofbackend.h:66
Create a new store at the URI.
Definition: qofsession.h:126
couldn&#39;t write to the file
Definition: qofbackend.h:97
Open the session read-only, ignoring any existing lock and not creating one if the URI isn&#39;t locked...
Definition: qofsession.h:130
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void export_coa(QofBook *) override
Extract the chart of accounts from the current database and create a new database with it...
Create a new store at the URI even if a store already exists there.
Definition: qofsession.h:128
void load(QofBook *book, QofBackendLoadType loadType) override
Load the minimal set of application data needed for the application to be operable at initial startup...
api for Version 1 XML-based file format
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
didn&#39;t recognize the file type
Definition: qofbackend.h:94
user login successful, but no permissions to access the desired object
Definition: qofbackend.h:73
file does not specify encoding
Definition: qofbackend.h:99
void qof_book_mark_session_saved(QofBook *book)
The qof_book_mark_saved() routine marks the book as having been saved (to a file, to a database)...
Definition: qofbook.cpp:383
api for GnuCash version 2 XML-based file format
undetermined error
Definition: qofbackend.h:79
file/db version newer than what we can read
Definition: qofbackend.h:69
void commit(QofInstance *instance) override
Commits the changes from the engine to the backend data storage.
All type declarations for the whole Gnucash engine.
std::string m_fullpath
Each backend resolves a fully-qualified file path.
void session_begin(QofSession *session, const char *new_uri, SessionOpenMode mode) override
Open the file or connect to the server.
Generic api to store and retrieve preferences.
API for the transaction logger.
couldn&#39;t make a backup of the file
Definition: qofbackend.h:96
SessionOpenMode
Mode for opening sessions.
Definition: qofsession.h:120
File exists, data would be destroyed.
Definition: qofbackend.h:67
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name...
Definition: TransLog.cpp:119
mangled locks (unspecified error)
Definition: qofbackend.h:91
cannot write to file/directory
Definition: qofbackend.h:68
gboolean qof_session_load_from_xml_file(QofBook *, const char *filename)
Read in an account group from a file.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
Utility functions for convert uri in separate components and back.
load and save data to files
void set_error(QofBackendError err)
Set the error value only if there isn&#39;t already an error already.
Definition: qof-backend.cpp:56
Open will fail if the URI doesn&#39;t exist or is locked.
Definition: qofsession.h:124