xapian-core  2.0.0
glass_changes.cc
Go to the documentation of this file.
1 
4 /* Copyright 2014,2016,2020 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, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "glass_changes.h"
24 
25 #include "glass_defs.h"
27 #include "fd.h"
28 #include "io_utils.h"
29 #include "pack.h"
30 #include "parseint.h"
31 #include "posixy_wrapper.h"
32 #include "str.h"
33 #include "stringutils.h"
34 #include "wordaccess.h"
35 #include "xapian/constants.h"
36 #include "xapian/error.h"
37 
38 #include <cerrno>
39 #include <cstdlib>
40 #include <string>
41 
42 using namespace std;
43 
45 {
46  if (changes_fd >= 0) {
47  ::close(changes_fd);
48  string changes_tmp = changes_stem;
49  changes_tmp += "tmp";
50  io_unlink(changes_tmp);
51  }
52 }
53 
57  int flags)
58 {
59  if (rev == 0) {
60  // Don't generate a changeset for the first revision.
61  return NULL;
62  }
63 
64  // Always check max_changesets for modification since last revision.
65  const char *p = getenv("XAPIAN_MAX_CHANGESETS");
66  if (p && *p) {
67  if (!parse_unsigned(p, max_changesets)) {
68  throw Xapian::InvalidArgumentError("XAPIAN_MAX_CHANGESETS must be "
69  "a non-negative integer");
70  }
71  } else {
72  max_changesets = 0;
73  }
74 
75  if (max_changesets == 0)
76  return NULL;
77 
78  string changes_tmp = changes_stem;
79  changes_tmp += "tmp";
80  changes_fd = posixy_open(changes_tmp.c_str(),
81  O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
82  if (changes_fd < 0) {
83  string message = "Couldn't open changeset ";
84  message += changes_tmp;
85  message += " to write";
86  throw Xapian::DatabaseError(message, errno);
87  }
88 
89  // Write header for changeset file.
90  string header = CHANGES_MAGIC_STRING;
91  header += char(CHANGES_VERSION);
92  pack_uint(header, old_rev);
93  pack_uint(header, rev);
94 
95  if (flags & Xapian::DB_DANGEROUS) {
96  header += '\x01'; // Changes can't be applied to a live database.
97  } else {
98  header += '\x00'; // Changes can be applied to a live database.
99  }
100 
101  io_write(changes_fd, header.data(), header.size());
102  // FIXME: save the block stream as a single zlib stream...
103 
104  // bool compressed = CHANGES_VERSION != 1; FIXME: always true for glass, but make optional?
105  return this;
106 }
107 
108 void
109 GlassChanges::write_block(const char * p, size_t len)
110 {
111  io_write(changes_fd, p, len);
112 }
113 
114 void
116 {
117  if (changes_fd < 0)
118  return;
119 
120  io_write(changes_fd, "\xff", 1);
121 
122  string changes_tmp = changes_stem;
123  changes_tmp += "tmp";
124 
125  if (!(flags & Xapian::DB_NO_SYNC) && !io_sync(changes_fd)) {
126  int saved_errno = errno;
127  (void)::close(changes_fd);
128  changes_fd = -1;
129  (void)unlink(changes_tmp.c_str());
130  string m = changes_tmp;
131  m += ": Failed to sync";
132  throw Xapian::DatabaseError(m, saved_errno);
133  }
134 
135  (void)::close(changes_fd);
136  changes_fd = -1;
137 
138  string changes_file = changes_stem;
139  changes_file += str(new_rev - 1); // FIXME: ?
140 
141  if (!io_tmp_rename(changes_tmp, changes_file)) {
142  string m = changes_tmp;
143  m += ": Failed to rename to ";
144  m += changes_file;
145  throw Xapian::DatabaseError(m, errno);
146  }
147 
148  if (new_rev <= max_changesets) {
149  // We can't yet have max_changesets old changesets.
150  return;
151  }
152 
153  // Only remove old changesets if we successfully wrote a new changeset.
154  // Start at the oldest changeset we know about, and stop at max_changesets
155  // before new_rev. If max_changesets is unchanged from the previous
156  // commit and nothing went wrong, exactly one changeset file should be
157  // deleted.
158  glass_revision_number_t stop_changeset = new_rev - max_changesets;
159  while (oldest_changeset < stop_changeset) {
160  changes_file.resize(changes_stem.size());
161  changes_file += str(oldest_changeset);
162  (void)io_unlink(changes_file);
163  ++oldest_changeset;
164  }
165 }
166 
167 void
168 GlassChanges::check(const string & changes_file)
169 {
170  FD fd(posixy_open(changes_file.c_str(), O_RDONLY | O_CLOEXEC));
171  if (fd < 0) {
172  string message = "Couldn't open changeset ";
173  message += changes_file;
174  throw Xapian::DatabaseError(message, errno);
175  }
176 
177  char buf[10240];
178 
179  size_t n = io_read(fd, buf, sizeof(buf), CONST_STRLEN(CHANGES_MAGIC_STRING) + 4);
180  if (memcmp(buf, CHANGES_MAGIC_STRING,
182  throw Xapian::DatabaseError("Changes file has wrong magic");
183  }
184 
185  const char * p = buf + CONST_STRLEN(CHANGES_MAGIC_STRING);
186  if (*p++ != CHANGES_VERSION) {
187  throw Xapian::DatabaseError("Changes file has unknown version");
188  }
189  const char * end = buf + n;
190 
191  glass_revision_number_t old_rev, rev;
192  if (!unpack_uint(&p, end, &old_rev))
193  throw Xapian::DatabaseError("Changes file has bad old_rev");
194  if (!unpack_uint(&p, end, &rev))
195  throw Xapian::DatabaseError("Changes file has bad rev");
196  if (rev <= old_rev)
197  throw Xapian::DatabaseError("Changes file has rev <= old_rev");
198  if (p == end || (*p != 0 && *p != 1))
199  throw Xapian::DatabaseError("Changes file has bad dangerous flag");
200  ++p;
201 
202  while (true) {
203  n -= (p - buf);
204  memmove(buf, p, n);
205  n += io_read(fd, buf + n, sizeof(buf) - n);
206 
207  if (n == 0)
208  throw Xapian::DatabaseError("Changes file truncated");
209 
210  p = buf;
211  end = buf + n;
212 
213  unsigned char v = *p++;
214  if (v == 0xff) {
215  if (p != end)
216  throw Xapian::DatabaseError("Changes file - junk at end");
217  break;
218  }
219  if (v == 0xfe) {
220  // Version file.
221  glass_revision_number_t version_rev;
222  if (!unpack_uint(&p, end, &version_rev))
223  throw Xapian::DatabaseError("Changes file - bad version file revision");
224  if (rev != version_rev)
225  throw Xapian::DatabaseError("Version file revision != changes file new revision");
226  size_t len;
227  if (!unpack_uint(&p, end, &len))
228  throw Xapian::DatabaseError("Changes file - bad version file length");
229  if (len <= size_t(end - p)) {
230  p += len;
231  } else {
232  if (lseek(fd, len - (end - p), SEEK_CUR) < 0)
233  throw Xapian::DatabaseError("Changes file - version file data truncated");
234  p = end = buf;
235  n = 0;
236  }
237  continue;
238  }
239  unsigned table = (v & 0x7);
240  v >>= 3;
241  if (table > 5)
242  throw Xapian::DatabaseError("Changes file - bad table code");
243  // Changed block.
244  if (v > 5)
245  throw Xapian::DatabaseError("Changes file - bad block size");
246  unsigned block_size = GLASS_MIN_BLOCKSIZE << v;
247  uint4 block_number;
248  if (!unpack_uint(&p, end, &block_number))
249  throw Xapian::DatabaseError("Changes file - bad block number");
250 
251  // Parse information from the start of the block.
252  //
253  // Although the revision number is aligned within the block, the block
254  // data may not be aligned to a word boundary here.
255  uint4 block_rev = unaligned_read4(reinterpret_cast<const uint8_t*>(p));
256  (void)block_rev; // FIXME: Sanity check value.
257  unsigned level = static_cast<unsigned char>(p[4]);
258  (void)level; // FIXME: Sanity check value.
259 
260  // Skip over the block content.
261  if (block_size <= unsigned(end - p)) {
262  p += block_size;
263  } else {
264  if (lseek(fd, block_size - (end - p), SEEK_CUR) < 0)
265  throw Xapian::DatabaseError("Changes file - block data truncated");
266  p = end = buf;
267  n = 0;
268  }
269  }
270 }
Definition: fd.h:30
void write_block(const char *p, size_t len)
GlassChanges * start(glass_revision_number_t old_rev, glass_revision_number_t rev, int flags)
static void check(const std::string &changes_file)
void commit(glass_revision_number_t new_rev, int flags)
DatabaseError indicates some sort of database related error.
Definition: error.h:355
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:229
Constants in the Xapian namespace.
PositionList * p
Hierarchy of classes which Xapian can throw as exceptions.
Wrapper class around a file descriptor to avoid leaks.
int close(FD &fd)
Definition: fd.h:63
Glass changesets.
Definitions, types, etc for use inside glass.
#define GLASS_MIN_BLOCKSIZE
Minimum B-tree block size.
Definition: glass_defs.h:33
uint4 glass_revision_number_t
The revision number of a glass database.
Definition: glass_defs.h:68
Internal definitions for glass database replication.
#define CHANGES_MAGIC_STRING
#define CHANGES_VERSION
uint32_t uint4
Definition: internaltypes.h:31
bool io_unlink(const std::string &filename)
Delete a file.
Definition: io_utils.cc:56
void io_write(int fd, const char *p, size_t n)
Write n bytes from block pointed to by p to file descriptor fd.
Definition: io_utils.cc:263
size_t io_read(int fd, char *p, size_t n, size_t min)
Read n bytes (or until EOF) into block pointed to by p from file descriptor fd.
Definition: io_utils.cc:241
bool io_tmp_rename(const std::string &tmp_file, const std::string &real_file)
Rename a temporary file to its final position.
Definition: io_utils.cc:573
Wrappers for low-level POSIX I/O routines.
bool io_sync(int fd)
Ensure all data previously written to file descriptor fd has been written to disk.
Definition: io_utils.h:107
string str(int value)
Convert int to std::string.
Definition: str.cc:91
XAPIAN_REVISION_TYPE rev
Revision number of a database.
Definition: types.h:108
const int DB_NO_SYNC
Don't attempt to ensure changes have hit disk.
Definition: constants.h:65
const int DB_DANGEROUS
Update the database in-place.
Definition: constants.h:102
Pack types into strings and unpack them again.
bool unpack_uint(const char **p, const char *end, U *result)
Decode an unsigned integer from a string.
Definition: pack.h:346
void pack_uint(std::string &s, U value)
Append an encoded unsigned integer to a string.
Definition: pack.h:315
Parse signed and unsigned type from string and check for trailing characters.
bool parse_unsigned(const char *p, T &res)
Definition: parseint.h:29
Provides wrappers with POSIXy semantics.
#define posixy_open
#define O_CLOEXEC
Definition: safefcntl.h:89
Convert types to std::string.
Various handy string-related helpers.
#define CONST_STRLEN(S)
Returns the length of a string constant.
Definition: stringutils.h:48
functions for reading and writing different width words
uint32_t unaligned_read4(const unsigned char *ptr)
Definition: wordaccess.h:147