GnuCash c935c2f+
Loading...
Searching...
No Matches
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://"
49static QofLogModule log_module = GNC_MOD_BACKEND;
50
51GncXmlBackend::~GncXmlBackend()
52{
53 session_end();
54};
55
56bool
57GncXmlBackend::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 */
73 set_error(ERR_FILEIO_FILE_NOT_FOUND);
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 */
86 set_error(ERR_FILEIO_FILE_NOT_FOUND);
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
113void
114GncXmlBackend::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 */
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
159void
160GncXmlBackend::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
199static QofBookFileType
200determine_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
228void
229GncXmlBackend::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());
251 }
252 break;
253
254 case GNC_BOOK_XML2_FILE_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());
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");
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
302void
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
326void
328{
329 if (qof_instance_is_dirty(instance))
330 qof_instance_mark_clean(instance);
331}
332
333bool
334GncXmlBackend::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
343void
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
357bool
358GncXmlBackend::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
535static bool
536copy_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
591bool
592GncXmlBackend::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
634void
635GncXmlBackend::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.");
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
675bool
676GncXmlBackend::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
709void
710GncXmlBackend::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}
API for the transaction logger.
void session_begin(QofSession *session, const char *new_uri, SessionOpenMode mode) override
Open the file or connect to the server.
void commit(QofInstance *instance) override
Commits the changes from the engine to the backend data storage.
void sync(QofBook *book) override
Synchronizes the engine contents to the backend.
void export_coa(QofBook *) override
Extract the chart of accounts from the current database and create a new database with it.
void load(QofBook *book, QofBackendLoadType loadType) override
Load the minimal set of application data needed for the application to be operable at initial startup...
load and save data to files
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
Utility functions for convert uri in separate components and back.
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition qofbackend.h:58
SessionOpenMode
Mode for opening sessions.
Definition qofsession.h:121
@ ERR_FILEIO_FILE_EACCES
No read access permission for the given file.
Definition qofbackend.h:100
@ ERR_BACKEND_STORE_EXISTS
File exists, data would be destroyed.
Definition qofbackend.h:67
@ ERR_BACKEND_LOCKED
in use by another user (ETXTBSY)
Definition qofbackend.h:66
@ ERR_FILEIO_WRITE_ERROR
couldn't write to the file
Definition qofbackend.h:97
@ ERR_FILEIO_FILE_LOCKERR
mangled locks (unspecified error)
Definition qofbackend.h:91
@ ERR_FILEIO_NO_ENCODING
file does not specify encoding
Definition qofbackend.h:99
@ ERR_FILEIO_UNKNOWN_FILE_TYPE
didn't recognize the file type
Definition qofbackend.h:94
@ ERR_BACKEND_MISC
undetermined error
Definition qofbackend.h:79
@ ERR_FILEIO_BACKUP_ERROR
couldn't make a backup of the file
Definition qofbackend.h:96
@ ERR_BACKEND_TOO_NEW
file/db version newer than what we can read
Definition qofbackend.h:69
@ ERR_FILEIO_PARSE_ERROR
couldn't parse the data in the file
Definition qofbackend.h:95
@ ERR_BACKEND_PERM
user login successful, but no permissions to access the desired object
Definition qofbackend.h:73
@ ERR_FILEIO_FILE_NOT_FOUND
not found / no such file
Definition qofbackend.h:92
@ ERR_BACKEND_READONLY
cannot write to file/directory
Definition qofbackend.h:68
@ SESSION_READ_ONLY
Create a new store at the URI even if a store already exists there.
Definition qofsession.h:128
@ SESSION_NEW_STORE
Open will fail if the URI doesn't exist or is locked.
Definition qofsession.h:124
@ SESSION_BREAK_LOCK
Open the session read-only, ignoring any existing lock and not creating one if the URI isn't locked.
Definition qofsession.h:130
@ SESSION_NEW_OVERWRITE
Create a new store at the URI.
Definition qofsession.h:126
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition qofbook.cpp:497
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
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
gchar * gnc_date_timestamp(void)
Make a timestamp in YYYYMMDDHHMMSS format.
#define qof_instance_is_dirty
Return value of is_dirty flag.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void set_message(std::string &&)
Set a descriptive message that can be displayed to the user when there's an error.
std::string m_fullpath
Each backend resolves a fully-qualified file path.
void set_error(QofBackendError err)
Set the error value only if there isn't already an error already.
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name.
Definition TransLog.cpp:120
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
api for GnuCash version 2 XML-based file format
api for Version 1 XML-based file format
QofBook reference.
Definition qofbook-p.hpp:47