LCOV - code coverage report
Current view: top level - libgnucash/backend/xml - gnc-xml-backend.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 36.1 % 368 133
Test Date: 2025-02-07 16:25:45 Functions: 87.5 % 16 14
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             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                 :             : }
        

Generated by: LCOV version 2.0-1