LCOV - code coverage report
Current view: top level - backends/glass - glass_changes.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core 954b5873a738 Lines: 51 138 37.0 %
Date: 2019-06-30 05:20:33 Functions: 4 5 80.0 %
Branches: 48 268 17.9 %

           Branch data     Line data    Source code
       1                 :            : /** @file glass_changes.cc
       2                 :            :  * @brief Glass changesets
       3                 :            :  */
       4                 :            : /* Copyright 2014,2016 Olly Betts
       5                 :            :  *
       6                 :            :  * This program is free software; you can redistribute it and/or
       7                 :            :  * modify it under the terms of the GNU General Public License as
       8                 :            :  * published by the Free Software Foundation; either version 2 of the
       9                 :            :  * License, or (at your option) any later version.
      10                 :            :  *
      11                 :            :  * This program is distributed in the hope that it will be useful,
      12                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14                 :            :  * GNU General Public License for more details.
      15                 :            :  *
      16                 :            :  * You should have received a copy of the GNU General Public License
      17                 :            :  * along with this program; if not, write to the Free Software
      18                 :            :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
      19                 :            :  * USA
      20                 :            :  */
      21                 :            : 
      22                 :            : #include <config.h>
      23                 :            : 
      24                 :            : #include "glass_changes.h"
      25                 :            : 
      26                 :            : #include "glass_defs.h"
      27                 :            : #include "glass_replicate_internal.h"
      28                 :            : #include "fd.h"
      29                 :            : #include "io_utils.h"
      30                 :            : #include "pack.h"
      31                 :            : #include "parseint.h"
      32                 :            : #include "posixy_wrapper.h"
      33                 :            : #include "str.h"
      34                 :            : #include "stringutils.h"
      35                 :            : #include "wordaccess.h"
      36                 :            : #include "xapian/constants.h"
      37                 :            : #include "xapian/error.h"
      38                 :            : 
      39                 :            : #include <cerrno>
      40                 :            : #include <cstdlib>
      41                 :            : #include <string>
      42                 :            : 
      43                 :            : using namespace std;
      44                 :            : 
      45                 :       8256 : GlassChanges::~GlassChanges()
      46                 :            : {
      47         [ +  + ]:       4128 :     if (changes_fd >= 0) {
      48                 :         27 :         ::close(changes_fd);
      49                 :         27 :         string changes_tmp = changes_stem;
      50                 :         27 :         changes_tmp += "tmp";
      51                 :         27 :         io_unlink(changes_tmp);
      52                 :            :     }
      53                 :       4128 : }
      54                 :            : 
      55                 :            : GlassChanges *
      56                 :       9680 : GlassChanges::start(glass_revision_number_t old_rev,
      57                 :            :                     glass_revision_number_t rev,
      58                 :            :                     int flags)
      59                 :            : {
      60         [ -  + ]:       9680 :     if (rev == 0) {
      61                 :            :         // Don't generate a changeset for the first revision.
      62                 :          0 :         return NULL;
      63                 :            :     }
      64                 :            : 
      65                 :            :     // Always check max_changesets for modification since last revision.
      66                 :       9680 :     const char *p = getenv("XAPIAN_MAX_CHANGESETS");
      67 [ +  + ][ +  - ]:       9680 :     if (p && *p) {
      68 [ +  - ][ -  + ]:       9234 :         if (!parse_unsigned(p, max_changesets)) {
      69                 :            :             throw Xapian::InvalidArgumentError("XAPIAN_MAX_CHANGESETS must be "
      70 [ #  # ][ #  # ]:          0 :                                                "a non-negative integer");
                 [ #  # ]
      71                 :            :         }
      72                 :            :     } else {
      73                 :        446 :         max_changesets = 0;
      74                 :            :     }
      75                 :            : 
      76         [ +  + ]:       9680 :     if (max_changesets == 0)
      77                 :       9639 :         return NULL;
      78                 :            : 
      79         [ +  - ]:         41 :     string changes_tmp = changes_stem;
      80         [ +  - ]:         41 :     changes_tmp += "tmp";
      81                 :            :     changes_fd = posixy_open(changes_tmp.c_str(),
      82         [ +  - ]:         41 :                              O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
      83         [ -  + ]:         41 :     if (changes_fd < 0) {
      84         [ #  # ]:          0 :         string message = "Couldn't open changeset ";
      85         [ #  # ]:          0 :         message += changes_tmp;
      86         [ #  # ]:          0 :         message += " to write";
      87         [ #  # ]:          0 :         throw Xapian::DatabaseError(message, errno);
      88                 :            :     }
      89                 :            : 
      90                 :            :     // Write header for changeset file.
      91         [ +  - ]:         82 :     string header = CHANGES_MAGIC_STRING;
      92         [ +  - ]:         41 :     header += char(CHANGES_VERSION);
      93         [ +  - ]:         41 :     pack_uint(header, old_rev);
      94         [ +  - ]:         41 :     pack_uint(header, rev);
      95                 :            : 
      96         [ -  + ]:         41 :     if (flags & Xapian::DB_DANGEROUS) {
      97         [ #  # ]:          0 :         header += '\x01'; // Changes can't be applied to a live database.
      98                 :            :     } else {
      99         [ +  - ]:         41 :         header += '\x00'; // Changes can be applied to a live database.
     100                 :            :     }
     101                 :            : 
     102         [ +  - ]:         41 :     io_write(changes_fd, header.data(), header.size());
     103                 :            :     // FIXME: save the block stream as a single zlib stream...
     104                 :            : 
     105                 :            :     // bool compressed = CHANGES_VERSION != 1; FIXME: always true for glass, but make optional?
     106                 :       9680 :     return this;
     107                 :            : }
     108                 :            : 
     109                 :            : void
     110                 :        228 : GlassChanges::write_block(const char * p, size_t len)
     111                 :            : {
     112                 :        228 :     io_write(changes_fd, p, len);
     113                 :        228 : }
     114                 :            : 
     115                 :            : void
     116                 :       9125 : GlassChanges::commit(glass_revision_number_t new_rev, int flags)
     117                 :            : {
     118         [ +  + ]:       9125 :     if (changes_fd < 0)
     119                 :       9125 :         return;
     120                 :            : 
     121         [ +  - ]:         14 :     io_write(changes_fd, "\xff", 1);
     122                 :            : 
     123         [ +  - ]:         14 :     string changes_tmp = changes_stem;
     124         [ +  - ]:         14 :     changes_tmp += "tmp";
     125                 :            : 
     126 [ +  - ][ +  - ]:         14 :     if (!(flags & Xapian::DB_NO_SYNC) && !io_sync(changes_fd)) {
         [ -  + ][ -  + ]
     127                 :          0 :         int saved_errno = errno;
     128         [ #  # ]:          0 :         (void)::close(changes_fd);
     129                 :          0 :         changes_fd = -1;
     130                 :          0 :         (void)unlink(changes_tmp.c_str());
     131         [ #  # ]:          0 :         string m = changes_tmp;
     132         [ #  # ]:          0 :         m += ": Failed to sync";
     133         [ #  # ]:          0 :         throw Xapian::DatabaseError(m, saved_errno);
     134                 :            :     }
     135                 :            : 
     136         [ +  - ]:         14 :     (void)::close(changes_fd);
     137                 :         14 :     changes_fd = -1;
     138                 :            : 
     139 [ +  - ][ +  + ]:         28 :     string changes_file = changes_stem;
     140 [ +  - ][ +  - ]:         14 :     changes_file += str(new_rev - 1); // FIXME: ?
     141                 :            : 
     142 [ +  - ][ -  + ]:         14 :     if (!io_tmp_rename(changes_tmp, changes_file)) {
     143         [ #  # ]:          0 :         string m = changes_tmp;
     144         [ #  # ]:          0 :         m += ": Failed to rename to ";
     145         [ #  # ]:          0 :         m += changes_file;
     146         [ #  # ]:          0 :         throw Xapian::DatabaseError(m, errno);
     147                 :            :     }
     148                 :            : 
     149         [ +  + ]:         14 :     if (new_rev <= max_changesets) {
     150                 :            :         // We can't yet have max_changesets old changesets.
     151                 :          8 :         return;
     152                 :            :     }
     153                 :            : 
     154                 :            :     // Only remove old changesets if we successfully wrote a new changeset.
     155                 :            :     // Start at the oldest changeset we know about, and stop at max_changesets
     156                 :            :     // before new_rev.  If max_changesets is unchanged from the previous
     157                 :            :     // commit and nothing went wrong, exactly one changeset file should be
     158                 :            :     // deleted.
     159                 :          6 :     glass_revision_number_t stop_changeset = new_rev - max_changesets;
     160 [ +  + ][ +  + ]:         19 :     while (oldest_changeset < stop_changeset) {
     161         [ +  - ]:          5 :         changes_file.resize(changes_stem.size());
     162 [ +  - ][ +  - ]:          5 :         changes_file += str(oldest_changeset);
     163         [ +  - ]:          5 :         (void)io_unlink(changes_file);
     164                 :          5 :         ++oldest_changeset;
     165                 :          6 :     }
     166                 :            : }
     167                 :            : 
     168                 :            : void
     169                 :          0 : GlassChanges::check(const string & changes_file)
     170                 :            : {
     171         [ #  # ]:          0 :     FD fd(posixy_open(changes_file.c_str(), O_RDONLY | O_CLOEXEC, 0666));
     172         [ #  # ]:          0 :     if (fd < 0) {
     173         [ #  # ]:          0 :         string message = "Couldn't open changeset ";
     174         [ #  # ]:          0 :         message += changes_file;
     175         [ #  # ]:          0 :         throw Xapian::DatabaseError(message, errno);
     176                 :            :     }
     177                 :            : 
     178                 :            :     char buf[10240];
     179                 :            : 
     180         [ #  # ]:          0 :     size_t n = io_read(fd, buf, sizeof(buf), CONST_STRLEN(CHANGES_MAGIC_STRING) + 4);
     181         [ #  # ]:          0 :     if (memcmp(buf, CHANGES_MAGIC_STRING,
     182                 :          0 :                CONST_STRLEN(CHANGES_MAGIC_STRING)) != 0) {
     183 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has wrong magic");
                 [ #  # ]
     184                 :            :     }
     185                 :            : 
     186                 :          0 :     const char * p = buf + CONST_STRLEN(CHANGES_MAGIC_STRING);
     187         [ #  # ]:          0 :     if (*p++ != CHANGES_VERSION) {
     188 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has unknown version");
                 [ #  # ]
     189                 :            :     }
     190                 :          0 :     const char * end = buf + n;
     191                 :            : 
     192                 :            :     glass_revision_number_t old_rev, rev;
     193         [ #  # ]:          0 :     if (!unpack_uint(&p, end, &old_rev))
     194 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has bad old_rev");
                 [ #  # ]
     195         [ #  # ]:          0 :     if (!unpack_uint(&p, end, &rev))
     196 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has bad rev");
                 [ #  # ]
     197         [ #  # ]:          0 :     if (rev <= old_rev)
     198 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has rev <= old_rev");
                 [ #  # ]
     199 [ #  # ][ #  # ]:          0 :     if (p == end || (*p != 0 && *p != 1))
                 [ #  # ]
     200 [ #  # ][ #  # ]:          0 :         throw Xapian::DatabaseError("Changes file has bad dangerous flag");
                 [ #  # ]
     201                 :          0 :     ++p;
     202                 :            : 
     203                 :            :     while (true) {
     204                 :          0 :         n -= (p - buf);
     205                 :          0 :         memmove(buf, p, n);
     206         [ #  # ]:          0 :         n += io_read(fd, buf + n, sizeof(buf) - n);
     207                 :            : 
     208         [ #  # ]:          0 :         if (n == 0)
     209 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseError("Changes file truncated");
                 [ #  # ]
     210                 :            : 
     211                 :          0 :         p = buf;
     212                 :          0 :         end = buf + n;
     213                 :            : 
     214                 :          0 :         unsigned char v = *p++;
     215         [ #  # ]:          0 :         if (v == 0xff) {
     216         [ #  # ]:          0 :             if (p != end)
     217 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseError("Changes file - junk at end");
                 [ #  # ]
     218                 :          0 :             break;
     219                 :            :         }
     220         [ #  # ]:          0 :         if (v == 0xfe) {
     221                 :            :             // Version file.
     222                 :            :             glass_revision_number_t version_rev;
     223         [ #  # ]:          0 :             if (!unpack_uint(&p, end, &version_rev))
     224 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseError("Changes file - bad version file revision");
                 [ #  # ]
     225         [ #  # ]:          0 :             if (rev != version_rev)
     226 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseError("Version file revision != changes file new revision");
                 [ #  # ]
     227                 :            :             size_t len;
     228         [ #  # ]:          0 :             if (!unpack_uint(&p, end, &len))
     229 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseError("Changes file - bad version file length");
                 [ #  # ]
     230         [ #  # ]:          0 :             if (len <= size_t(end - p)) {
     231                 :          0 :                 p += len;
     232                 :            :             } else {
     233         [ #  # ]:          0 :                 if (lseek(fd, len - (end - p), SEEK_CUR) < 0)
     234 [ #  # ][ #  # ]:          0 :                     throw Xapian::DatabaseError("Changes file - version file data truncated");
                 [ #  # ]
     235                 :          0 :                 p = end = buf;
     236                 :          0 :                 n = 0;
     237                 :            :             }
     238                 :          0 :             continue;
     239                 :            :         }
     240                 :          0 :         unsigned table = (v & 0x7);
     241                 :          0 :         v >>= 3;
     242         [ #  # ]:          0 :         if (table > 5)
     243 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseError("Changes file - bad table code");
                 [ #  # ]
     244                 :            :         // Changed block.
     245         [ #  # ]:          0 :         if (v > 5)
     246 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseError("Changes file - bad block size");
                 [ #  # ]
     247                 :          0 :         unsigned block_size = GLASS_MIN_BLOCKSIZE << v;
     248                 :            :         uint4 block_number;
     249         [ #  # ]:          0 :         if (!unpack_uint(&p, end, &block_number))
     250 [ #  # ][ #  # ]:          0 :             throw Xapian::DatabaseError("Changes file - bad block number");
                 [ #  # ]
     251                 :            :         // Although the revision number is aligned within the block, the block
     252                 :            :         // data may not be aligned to a word boundary here.
     253         [ #  # ]:          0 :         uint4 block_rev = unaligned_read4(reinterpret_cast<const uint8_t*>(p));
     254                 :            :         (void)block_rev; // FIXME: Sanity check value.
     255                 :          0 :         p += 4;
     256                 :          0 :         unsigned level = static_cast<unsigned char>(*p++);
     257                 :            :         (void)level; // FIXME: Sanity check value.
     258         [ #  # ]:          0 :         if (block_size <= unsigned(end - p)) {
     259                 :          0 :             p += block_size;
     260                 :            :         } else {
     261         [ #  # ]:          0 :             if (lseek(fd, block_size - (end - p), SEEK_CUR) < 0)
     262 [ #  # ][ #  # ]:          0 :                 throw Xapian::DatabaseError("Changes file - block data truncated");
                 [ #  # ]
     263                 :          0 :             p = end = buf;
     264                 :          0 :             n = 0;
     265                 :            :         }
     266                 :          0 :     }
     267                 :          0 : }

Generated by: LCOV version 1.11