Branch data Line data Source code
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 : 18 : GncXmlBackend::~GncXmlBackend()
52 : : {
53 : 9 : session_end();
54 : 18 : };
55 : :
56 : : bool
57 : 30 : GncXmlBackend::check_path (const char* fullpath, bool create)
58 : : {
59 : : GStatBuf statbuf;
60 : 30 : char* dirname = g_path_get_dirname (fullpath);
61 : : /* Again check whether the directory can be accessed */
62 : 30 : auto rc = g_stat (dirname, &statbuf);
63 : 30 : if (rc != 0
64 : : #if COMPILER(MSVC)
65 : : || (statbuf.st_mode & _S_IFDIR) == 0
66 : : #else
67 : 30 : || !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 : 0 : set_error(ERR_FILEIO_FILE_NOT_FOUND);
74 : 0 : std::string msg {"Couldn't find directory for "};
75 : 0 : set_message(msg + fullpath);
76 : 0 : PWARN ("Couldn't find directory for %s", fullpath);
77 : 0 : g_free(dirname);
78 : 0 : return false;
79 : 0 : }
80 : :
81 : : /* Now check whether we can g_stat the file itself */
82 : 30 : rc = g_stat (fullpath, &statbuf);
83 : 30 : if ((rc != 0) && (!create))
84 : : {
85 : : /* Error on stat means the file doesn't exist */
86 : 2 : set_error(ERR_FILEIO_FILE_NOT_FOUND);
87 : 2 : std::string msg {"Couldn't find "};
88 : 2 : set_message(msg + fullpath);
89 : 2 : PWARN ("Couldn't find %s", fullpath);
90 : 2 : g_free(dirname);
91 : 2 : return false;
92 : 2 : }
93 : 28 : if (rc == 0
94 : : #if COMPILER(MSVC)
95 : : && (statbuf.st_mode & _S_IFDIR) != 0
96 : : #else
97 : 21 : && S_ISDIR (statbuf.st_mode)
98 : : #endif
99 : : )
100 : : {
101 : 0 : set_error(ERR_FILEIO_UNKNOWN_FILE_TYPE);
102 : 0 : std::string msg {"Path "};
103 : 0 : msg += fullpath;
104 : 0 : set_message(msg + " is a directory");
105 : 0 : PWARN ("Path %s is a directory", fullpath);
106 : 0 : g_free(dirname);
107 : 0 : return false;
108 : 0 : }
109 : 28 : g_free(dirname);
110 : 28 : return true;
111 : : }
112 : :
113 : : void
114 : 30 : GncXmlBackend::session_begin(QofSession* session, const char* new_uri,
115 : : SessionOpenMode mode)
116 : : {
117 : : /* Make sure the directory is there */
118 : 30 : auto path_str = gnc_uri_get_path (new_uri);
119 : 30 : m_fullpath = path_str;
120 : 30 : g_free (path_str);
121 : :
122 : 30 : if (m_fullpath.empty())
123 : : {
124 : 0 : set_error(ERR_FILEIO_FILE_NOT_FOUND);
125 : 0 : set_message("No path specified");
126 : 0 : return;
127 : : }
128 : 30 : if (mode == SESSION_NEW_STORE && save_may_clobber_data())
129 : : {
130 : 0 : set_error(ERR_BACKEND_STORE_EXISTS);
131 : 0 : PWARN ("Might clobber, no force");
132 : 0 : return;
133 : : }
134 : :
135 : 30 : if (!check_path(m_fullpath.c_str(),
136 : 30 : mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE))
137 : 2 : return;
138 : :
139 : 28 : auto dirname = g_path_get_dirname (m_fullpath.c_str());
140 : 28 : m_dirname = dirname;
141 : 28 : g_free (dirname);
142 : :
143 : :
144 : :
145 : : /* ---------------------------------------------------- */
146 : : /* We should now have a fully resolved path name.
147 : : * Let's start logging */
148 : 28 : xaccLogSetBaseName (m_fullpath.c_str());
149 : 28 : PINFO ("logpath=%s", m_fullpath.empty() ? "(null)" : m_fullpath.c_str());
150 : :
151 : 28 : if (mode == SESSION_READ_ONLY)
152 : 20 : return; // Read-only, don't care about locks.
153 : :
154 : : /* Set the lock file */
155 : 8 : m_lockfile = m_fullpath + ".LCK";
156 : 8 : get_file_lock(mode);
157 : : }
158 : :
159 : : void
160 : 33 : GncXmlBackend::session_end()
161 : : {
162 : 33 : if (m_book && qof_book_is_readonly (m_book))
163 : : {
164 : 0 : set_error(ERR_BACKEND_READONLY);
165 : 0 : return;
166 : : }
167 : :
168 : 33 : if (!m_linkfile.empty())
169 : 0 : g_unlink (m_linkfile.c_str());
170 : :
171 : 33 : if (m_lockfd != -1)
172 : : {
173 : 5 : close (m_lockfd);
174 : 5 : m_lockfd = -1;
175 : : }
176 : :
177 : 33 : 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 : 5 : rv = g_unlink (m_lockfile.c_str());
186 : 5 : if (rv)
187 : : {
188 : 0 : PWARN ("Error on g_unlink(%s): %d: %s", m_lockfile.c_str(),
189 : : errno, g_strerror (errno) ? g_strerror (errno) : "");
190 : : }
191 : : }
192 : :
193 : 33 : m_dirname.clear();
194 : 33 : m_fullpath.clear();
195 : 33 : m_lockfile.clear();
196 : 33 : m_linkfile.clear();
197 : : }
198 : :
199 : : static QofBookFileType
200 : 21 : determine_file_type (const std::string& path)
201 : : {
202 : : gboolean with_encoding;
203 : : QofBookFileType v2type;
204 : :
205 : 21 : v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding);
206 : 21 : if (v2type == GNC_BOOK_XML2_FILE)
207 : : {
208 : 21 : if (with_encoding)
209 : : {
210 : 21 : return GNC_BOOK_XML2_FILE;
211 : : }
212 : : else
213 : : {
214 : 0 : return GNC_BOOK_XML2_FILE_NO_ENCODING;
215 : : }
216 : : }
217 : 0 : else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
218 : : {
219 : 0 : return GNC_BOOK_POST_XML2_0_0_FILE;
220 : : }
221 : 0 : else if (v2type == GNC_BOOK_XML1_FILE)
222 : : {
223 : 0 : return GNC_BOOK_XML1_FILE;
224 : : }
225 : 0 : return GNC_BOOK_NOT_OURS;
226 : : }
227 : :
228 : : void
229 : 21 : GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
230 : : {
231 : :
232 : : QofBackendError error;
233 : :
234 : 21 : if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
235 : :
236 : 21 : error = ERR_BACKEND_NO_ERR;
237 : 21 : if (m_book)
238 : 0 : g_object_unref(m_book);
239 : 21 : m_book = QOF_BOOK(g_object_ref(book));
240 : :
241 : : int rc;
242 : 21 : switch (determine_file_type (m_fullpath))
243 : : {
244 : 21 : case GNC_BOOK_XML2_FILE:
245 : 21 : rc = qof_session_load_from_xml_file_v2 (this, book,
246 : : GNC_BOOK_XML2_FILE);
247 : 21 : if (rc == FALSE)
248 : : {
249 : 0 : PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
250 : 0 : error = ERR_FILEIO_PARSE_ERROR;
251 : : }
252 : 21 : break;
253 : :
254 : 0 : case GNC_BOOK_XML2_FILE_NO_ENCODING:
255 : 0 : error = ERR_FILEIO_NO_ENCODING;
256 : 0 : PWARN ("No character encoding in Xml File %s", m_fullpath.c_str());
257 : 0 : break;
258 : 0 : case GNC_BOOK_XML1_FILE:
259 : 0 : rc = qof_session_load_from_xml_file (book, m_fullpath.c_str());
260 : 0 : if (rc == FALSE)
261 : : {
262 : 0 : PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
263 : 0 : error = ERR_FILEIO_PARSE_ERROR;
264 : : }
265 : 0 : break;
266 : 0 : case GNC_BOOK_POST_XML2_0_0_FILE:
267 : 0 : error = ERR_BACKEND_TOO_NEW;
268 : 0 : PWARN ("Version of Xml file %s is newer than what we can read",
269 : : m_fullpath.c_str());
270 : 0 : break;
271 : 0 : 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 : 0 : switch (errno)
276 : : {
277 : 0 : case EACCES: /* No read permission */
278 : 0 : PWARN ("No read permission to file");
279 : 0 : error = ERR_FILEIO_FILE_EACCES;
280 : 0 : break;
281 : 0 : case EISDIR: /* File is a directory - but on this error we don't arrive here */
282 : 0 : PWARN ("Filename is a directory");
283 : 0 : error = ERR_FILEIO_FILE_NOT_FOUND;
284 : 0 : break;
285 : 0 : default:
286 : 0 : PWARN ("File not any known type");
287 : 0 : error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
288 : 0 : break;
289 : : }
290 : 0 : break;
291 : : }
292 : :
293 : 21 : if (error != ERR_BACKEND_NO_ERR)
294 : : {
295 : 0 : set_error(error);
296 : : }
297 : :
298 : : /* We just got done loading, it can't possibly be dirty !! */
299 : 21 : qof_book_mark_session_saved (book);
300 : : }
301 : :
302 : : void
303 : 4 : 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 : 4 : if (m_book == nullptr)
312 : 4 : m_book = QOF_BOOK(g_object_ref(book));
313 : 4 : if (book != m_book) return;
314 : :
315 : 4 : if (qof_book_is_readonly (m_book))
316 : : {
317 : : /* Are we read-only? Don't continue in this case. */
318 : 0 : set_error(ERR_BACKEND_READONLY);
319 : 0 : return;
320 : : }
321 : :
322 : 4 : write_to_file (true);
323 : 4 : remove_old_files();
324 : : }
325 : :
326 : : void
327 : 17560 : GncXmlBackend::commit(QofInstance* instance)
328 : : {
329 : 17560 : if (qof_instance_is_dirty(instance))
330 : 13865 : qof_instance_mark_clean(instance);
331 : 17560 : }
332 : :
333 : : bool
334 : 3 : GncXmlBackend::save_may_clobber_data()
335 : : {
336 : 3 : if (m_fullpath.empty())
337 : 0 : return false;
338 : : GStatBuf statbuf;
339 : 3 : auto rc = g_stat (m_fullpath.c_str(), &statbuf);
340 : 3 : return rc == 0;
341 : : }
342 : :
343 : : void
344 : 0 : GncXmlBackend::export_coa(QofBook* book)
345 : : {
346 : 0 : auto out = g_fopen(m_fullpath.c_str(), "w");
347 : 0 : if (out == NULL)
348 : : {
349 : 0 : set_error(ERR_FILEIO_WRITE_ERROR);
350 : 0 : set_message(strerror(errno));
351 : 0 : return;
352 : : }
353 : 0 : gnc_book_write_accounts_to_xml_filehandle_v2(this, book, out);
354 : 0 : fclose(out);
355 : : }
356 : :
357 : : bool
358 : 4 : GncXmlBackend::write_to_file (bool make_backup)
359 : : {
360 : : QofBackendError be_err;
361 : :
362 : 4 : ENTER (" book=%p file=%s", m_book, m_fullpath.c_str());
363 : :
364 : 4 : if (m_book && qof_book_is_readonly (m_book))
365 : : {
366 : : /* Are we read-only? Don't continue in this case. */
367 : 0 : set_error(ERR_BACKEND_READONLY);
368 : 0 : LEAVE ("");
369 : 0 : 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 : 4 : auto tmp_name = g_new (char, strlen (m_fullpath.c_str()) + 12);
378 : 4 : strcpy (tmp_name, m_fullpath.c_str());
379 : 4 : 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 : 4 : if (!mktemp (tmp_name))
391 : : #pragma GCC diagnostic pop
392 : : {
393 : 0 : g_free (tmp_name);
394 : 0 : set_error(ERR_BACKEND_MISC);
395 : 0 : set_message("Failed to make temp file");
396 : 0 : LEAVE ("");
397 : 0 : return FALSE;
398 : : }
399 : :
400 : 4 : if (make_backup)
401 : : {
402 : 4 : if (!backup_file ())
403 : : {
404 : 0 : g_free (tmp_name);
405 : 0 : LEAVE ("");
406 : 0 : return FALSE;
407 : : }
408 : : }
409 : :
410 : 4 : 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 : 4 : auto rc = g_stat (m_fullpath.c_str(), &statbuf);
416 : 4 : if (rc == 0)
417 : : {
418 : : /* We must never chmod the file /dev/null */
419 : 0 : g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0);
420 : :
421 : : /* Use the permissions from the original data file */
422 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 4 : if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
459 : : {
460 : 0 : set_error(ERR_BACKEND_READONLY);
461 : 0 : PWARN ("unable to unlink filename %s: %s",
462 : : m_fullpath.empty() ? "(null)" : m_fullpath.c_str(),
463 : : g_strerror (errno) ? g_strerror (errno) : "");
464 : 0 : g_free (tmp_name);
465 : 0 : LEAVE ("");
466 : 0 : return FALSE;
467 : : }
468 : 12 : if (!link_or_make_backup (tmp_name, m_fullpath))
469 : : {
470 : 0 : set_error(ERR_FILEIO_BACKUP_ERROR);
471 : 0 : std::string msg{"Failed to make backup file "};
472 : 0 : set_message(msg + (m_fullpath.empty() ? "NULL" : m_fullpath));
473 : 0 : g_free (tmp_name);
474 : 0 : LEAVE ("");
475 : 0 : return FALSE;
476 : 0 : }
477 : 4 : if (g_unlink (tmp_name) != 0)
478 : : {
479 : 0 : set_error(ERR_BACKEND_PERM);
480 : 0 : PWARN ("unable to unlink temp filename %s: %s",
481 : : tmp_name ? tmp_name : "(null)",
482 : : g_strerror (errno) ? g_strerror (errno) : "");
483 : 0 : g_free (tmp_name);
484 : 0 : LEAVE ("");
485 : 0 : return FALSE;
486 : : }
487 : 4 : g_free (tmp_name);
488 : :
489 : : /* Since we successfully saved the book,
490 : : * we should mark it clean. */
491 : 4 : qof_book_mark_session_saved (m_book);
492 : 4 : LEAVE (" successful save of book=%p to file=%s", m_book,
493 : : m_fullpath.c_str());
494 : 4 : return TRUE;
495 : : }
496 : : else
497 : : {
498 : 0 : if (g_unlink (tmp_name) != 0)
499 : : {
500 : 0 : switch (errno)
501 : : {
502 : 0 : case ENOENT: /* tmp_name doesn't exist? Assume "RO" error */
503 : : case EACCES:
504 : : case EPERM:
505 : : case ENOSYS:
506 : : case EROFS:
507 : 0 : be_err = ERR_BACKEND_READONLY;
508 : 0 : break;
509 : 0 : default:
510 : 0 : be_err = ERR_BACKEND_MISC;
511 : 0 : break;
512 : : }
513 : 0 : set_error(be_err);
514 : 0 : 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 */
522 : 0 : set_error(ERR_FILEIO_WRITE_ERROR);
523 : 0 : std::string msg{"Unable to write to temp file "};
524 : 0 : set_message(msg + (tmp_name ? tmp_name : "NULL"));
525 : 0 : }
526 : 0 : g_free (tmp_name);
527 : 0 : LEAVE ("");
528 : 0 : return FALSE;
529 : : }
530 : : g_free (tmp_name);
531 : : LEAVE ("");
532 : : return TRUE;
533 : : }
534 : :
535 : : static bool
536 : 0 : copy_file (const std::string& orig, const std::string& bkup)
537 : : {
538 : 0 : constexpr size_t buf_size = 1024;
539 : : char buf[buf_size];
540 : 0 : int flags = 0;
541 : 0 : ssize_t count_write = 0;
542 : 0 : ssize_t count_read = 0;
543 : :
544 : :
545 : : #ifdef G_OS_WIN32
546 : : flags = O_BINARY;
547 : : #endif
548 : :
549 : 0 : auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0);
550 : 0 : if (orig_fd == -1)
551 : : {
552 : 0 : return false;
553 : : }
554 : 0 : auto bkup_fd = g_open (bkup.c_str(),
555 : : O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
556 : 0 : if (bkup_fd == -1)
557 : : {
558 : 0 : close (orig_fd);
559 : 0 : return FALSE;
560 : : }
561 : :
562 : : do
563 : : {
564 : 0 : count_read = read (orig_fd, buf, buf_size);
565 : 0 : if (count_read == -1 && errno != EINTR)
566 : : {
567 : 0 : close (orig_fd);
568 : 0 : close (bkup_fd);
569 : 0 : return FALSE;
570 : : }
571 : :
572 : 0 : if (count_read > 0)
573 : : {
574 : 0 : count_write = write (bkup_fd, buf, count_read);
575 : 0 : if (count_write == -1)
576 : : {
577 : 0 : close (orig_fd);
578 : 0 : close (bkup_fd);
579 : 0 : return FALSE;
580 : : }
581 : : }
582 : : }
583 : 0 : while (count_read > 0);
584 : :
585 : 0 : close (orig_fd);
586 : 0 : close (bkup_fd);
587 : :
588 : 0 : return TRUE;
589 : : }
590 : :
591 : : bool
592 : 4 : GncXmlBackend::link_or_make_backup (const std::string& orig,
593 : : const std::string& bkup)
594 : : {
595 : 4 : gboolean copy_success = FALSE;
596 : : int err_ret =
597 : : #ifdef HAVE_LINK
598 : 4 : link (orig.c_str(), bkup.c_str())
599 : : #else
600 : : - 1
601 : : #endif
602 : : ;
603 : 4 : if (err_ret != 0)
604 : : {
605 : : #ifdef HAVE_LINK
606 : 0 : if (errno == EPERM || errno == ENOSYS
607 : : # ifdef EOPNOTSUPP
608 : 0 : || errno == EOPNOTSUPP
609 : : # endif
610 : : # ifdef ENOTSUP
611 : 0 : || errno == ENOTSUP
612 : : # endif
613 : : # ifdef ENOSYS
614 : 0 : || errno == ENOSYS
615 : : # endif
616 : : )
617 : : #endif
618 : : {
619 : 0 : copy_success = copy_file (orig.c_str(), bkup);
620 : : }
621 : :
622 : 0 : if (!copy_success)
623 : : {
624 : 0 : set_error(ERR_FILEIO_BACKUP_ERROR);
625 : 0 : 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 : 0 : return false;
628 : : }
629 : : }
630 : :
631 : 4 : return true;
632 : : }
633 : :
634 : : void
635 : 8 : GncXmlBackend::get_file_lock (SessionOpenMode mode)
636 : : {
637 : 8 : m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL ,
638 : : S_IRUSR | S_IWUSR);
639 : 8 : if (m_lockfd == -1)
640 : : {
641 : 0 : QofBackendError be_err{ERR_BACKEND_NO_ERR};
642 : : /* oops .. we can't create the lockfile .. */
643 : 0 : switch (errno)
644 : : {
645 : 0 : case EACCES:
646 : 0 : set_message("Unable to create lockfile, make sure that you have write access to the directory.");
647 : 0 : be_err = ERR_BACKEND_READONLY;
648 : 0 : break;
649 : :
650 : 0 : case EROFS:
651 : 0 : set_message("Unable to create lockfile, data file is on a read-only filesystem.");
652 : 0 : be_err = ERR_BACKEND_READONLY;
653 : 0 : break;
654 : 0 : case ENOSPC:
655 : 0 : set_message("Unable to create lockfile, no space on filesystem.");
656 : 0 : be_err = ERR_BACKEND_READONLY;
657 : 0 : break;
658 : 0 : case EEXIST:
659 : 0 : be_err = ERR_BACKEND_LOCKED;
660 : 0 : break;
661 : 0 : default:
662 : 0 : PWARN ("Unable to create the lockfile %s: %s",
663 : : m_lockfile.c_str(), strerror(errno));
664 : 0 : set_message("Lockfile creation failed. Please see the tracefile for details.");
665 : 0 : be_err = ERR_FILEIO_FILE_LOCKERR;
666 : : }
667 : 0 : if (!(mode == SESSION_BREAK_LOCK && be_err == ERR_BACKEND_LOCKED))
668 : : {
669 : 0 : set_error(be_err);
670 : 0 : m_lockfile.clear();
671 : : }
672 : : }
673 : 8 : }
674 : :
675 : : bool
676 : 4 : GncXmlBackend::backup_file()
677 : : {
678 : : GStatBuf statbuf;
679 : :
680 : 4 : auto datafile = m_fullpath.c_str();
681 : :
682 : 4 : auto rc = g_stat (datafile, &statbuf);
683 : 4 : if (rc)
684 : 4 : return (errno == ENOENT);
685 : :
686 : 0 : if (determine_file_type (m_fullpath) == GNC_BOOK_BIN_FILE)
687 : : {
688 : : /* make a more permanent safer backup */
689 : 0 : auto bin_bkup = m_fullpath + "-binfmt.bkup";
690 : 0 : auto bkup_ret = link_or_make_backup (m_fullpath, bin_bkup);
691 : 0 : if (!bkup_ret)
692 : : {
693 : 0 : return false;
694 : : }
695 : 0 : }
696 : :
697 : 0 : auto timestamp = gnc_date_timestamp ();
698 : 0 : auto backup = m_fullpath + "." + timestamp + GNC_DATAFILE_EXT;
699 : 0 : g_free (timestamp);
700 : :
701 : 0 : return link_or_make_backup (datafile, backup);
702 : 0 : }
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 : 4 : GncXmlBackend::remove_old_files ()
711 : : {
712 : : GStatBuf lockstatbuf, statbuf;
713 : :
714 : 4 : if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0)
715 : 0 : return;
716 : :
717 : 4 : auto dir = g_dir_open (m_dirname.c_str(), 0, NULL);
718 : 4 : if (!dir)
719 : 0 : return;
720 : :
721 : 4 : auto now = gnc_time (NULL);
722 : : const char* dent;
723 : 34 : while ((dent = g_dir_read_name (dir)) != NULL)
724 : : {
725 : : gchar* name;
726 : :
727 : : /* Ensure we only evaluate GnuCash related files. */
728 : 60 : if (! (g_str_has_suffix (dent, ".LNK") ||
729 : 30 : g_str_has_suffix (dent, ".xac") /* old data file extension */ ||
730 : 30 : g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
731 : 22 : g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
732 : 22 : continue;
733 : :
734 : 8 : name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL);
735 : :
736 : : /* Only evaluate files associated with the current data file. */
737 : 8 : if (!g_str_has_prefix (name, m_fullpath.c_str()))
738 : : {
739 : 8 : g_free (name);
740 : 8 : continue;
741 : : }
742 : :
743 : : /* Never remove the current data file itself */
744 : 0 : if (g_strcmp0 (name, m_fullpath.c_str()) == 0)
745 : : {
746 : 0 : g_free (name);
747 : 0 : continue;
748 : : }
749 : :
750 : : /* Test if the current file is a lock file */
751 : 0 : if (g_str_has_suffix (name, ".LNK"))
752 : : {
753 : : /* Is a lock file. Skip the active lock file */
754 : 0 : if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) &&
755 : : /* Only delete lock files older than the active one */
756 : 0 : (g_stat (name, &statbuf) == 0) &&
757 : 0 : (statbuf.st_mtime < lockstatbuf.st_mtime))
758 : : {
759 : 0 : PINFO ("remove stale lock file: %s", name);
760 : 0 : g_unlink (name);
761 : : }
762 : :
763 : 0 : g_free (name);
764 : 0 : 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 : 0 : gchar* stamp_start = name + m_fullpath.size();
782 : 0 : gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
783 : : GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
784 : 0 : gboolean got_date_stamp = FALSE;
785 : :
786 : 0 : if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
787 : 0 : PWARN ("Cannot compile regex for date stamp");
788 : 0 : else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
789 : 0 : got_date_stamp = TRUE;
790 : :
791 : 0 : regfree (&pattern);
792 : 0 : g_free (expression);
793 : :
794 : 0 : if (!got_date_stamp) /* Not a gnucash created file after all... */
795 : : {
796 : 0 : g_free (name);
797 : 0 : 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 : 0 : if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
805 : : {
806 : 0 : PINFO ("remove stale file: %s - reason: preference XML_RETAIN_NONE", name);
807 : 0 : g_unlink (name);
808 : : }
809 : 0 : else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
810 : 0 : (gnc_prefs_get_file_retention_days () > 0))
811 : : {
812 : : int days;
813 : :
814 : : /* Is the backup file old enough to delete */
815 : 0 : if (g_stat (name, &statbuf) != 0)
816 : : {
817 : 0 : g_free (name);
818 : 0 : continue;
819 : : }
820 : 0 : days = (int) (difftime (now, statbuf.st_mtime) / 86400);
821 : :
822 : 0 : PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ());
823 : 0 : if (days >= gnc_prefs_get_file_retention_days ())
824 : : {
825 : 0 : PINFO ("remove stale file: %s - reason: more than %d days old", name, days);
826 : 0 : g_unlink (name);
827 : : }
828 : : }
829 : 0 : g_free (name);
830 : : }
831 : 4 : g_dir_close (dir);
832 : : }
|