1 /** @file glass_changes.cc
2 * @brief Glass changesets
4 /* Copyright 2014,2016 Olly Betts
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.
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.
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
24 #include "glass_changes.h"
26 #include "glass_defs.h"
27 #include "glass_replicate_internal.h"
31 #include "posixy_wrapper.h"
33 #include "stringutils.h"
34 #include "wordaccess.h"
35 #include "xapian/constants.h"
36 #include "xapian/error.h"
44 GlassChanges::~GlassChanges()
46 if (changes_fd
>= 0) {
48 string changes_tmp
= changes_stem
;
50 io_unlink(changes_tmp
);
55 GlassChanges::start(glass_revision_number_t old_rev
,
56 glass_revision_number_t rev
,
60 // Don't generate a changeset for the first revision.
64 // Always check max_changesets for modification since last revision.
65 const char *p
= getenv("XAPIAN_MAX_CHANGESETS");
67 max_changesets
= atoi(p
);
72 if (max_changesets
== 0)
75 string changes_tmp
= changes_stem
;
77 changes_fd
= posixy_open(changes_tmp
.c_str(),
78 O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, 0666);
80 string message
= "Couldn't open changeset ";
81 message
+= changes_tmp
;
82 message
+= " to write";
83 throw Xapian::DatabaseError(message
, errno
);
86 // Write header for changeset file.
87 string header
= CHANGES_MAGIC_STRING
;
88 header
+= char(CHANGES_VERSION
);
89 pack_uint(header
, old_rev
);
90 pack_uint(header
, rev
);
92 if (flags
& Xapian::DB_DANGEROUS
) {
93 header
+= '\x01'; // Changes can't be applied to a live database.
95 header
+= '\x00'; // Changes can be applied to a live database.
98 io_write(changes_fd
, header
.data(), header
.size());
99 // FIXME: save the block stream as a single zlib stream...
101 // bool compressed = CHANGES_VERSION != 1; FIXME: always true for glass, but make optional?
106 GlassChanges::write_block(const char * p
, size_t len
)
108 io_write(changes_fd
, p
, len
);
112 GlassChanges::commit(glass_revision_number_t new_rev
, int flags
)
117 io_write(changes_fd
, "\xff", 1);
119 string changes_tmp
= changes_stem
;
120 changes_tmp
+= "tmp";
122 if (!(flags
& Xapian::DB_NO_SYNC
) && !io_sync(changes_fd
)) {
123 int saved_errno
= errno
;
124 (void)::close(changes_fd
);
126 (void)unlink(changes_tmp
.c_str());
127 string m
= changes_tmp
;
128 m
+= ": Failed to sync";
129 throw Xapian::DatabaseError(m
, saved_errno
);
132 (void)::close(changes_fd
);
135 string changes_file
= changes_stem
;
136 changes_file
+= str(new_rev
- 1); // FIXME: ?
138 if (!io_tmp_rename(changes_tmp
, changes_file
)) {
139 string m
= changes_tmp
;
140 m
+= ": Failed to rename to ";
142 throw Xapian::DatabaseError(m
, errno
);
145 if (new_rev
<= max_changesets
) {
146 // We can't yet have max_changesets old changesets.
150 // Only remove old changesets if we successfully wrote a new changeset.
151 // Start at the oldest changeset we know about, and stop at max_changesets
152 // before new_rev. If max_changesets is unchanged from the previous
153 // commit and nothing went wrong, exactly one changeset file should be
155 glass_revision_number_t stop_changeset
= new_rev
- max_changesets
;
156 while (oldest_changeset
< stop_changeset
) {
157 changes_file
.resize(changes_stem
.size());
158 changes_file
+= str(oldest_changeset
);
159 (void)io_unlink(changes_file
);
165 GlassChanges::check(const string
& changes_file
)
167 FD
fd(posixy_open(changes_file
.c_str(), O_RDONLY
| O_CLOEXEC
, 0666));
169 string message
= "Couldn't open changeset ";
170 message
+= changes_file
;
171 throw Xapian::DatabaseError(message
, errno
);
176 size_t n
= io_read(fd
, buf
, sizeof(buf
), CONST_STRLEN(CHANGES_MAGIC_STRING
) + 4);
177 if (memcmp(buf
, CHANGES_MAGIC_STRING
,
178 CONST_STRLEN(CHANGES_MAGIC_STRING
)) != 0) {
179 throw Xapian::DatabaseError("Changes file has wrong magic");
182 const char * p
= buf
+ CONST_STRLEN(CHANGES_MAGIC_STRING
);
183 if (*p
++ != CHANGES_VERSION
) {
184 throw Xapian::DatabaseError("Changes file has unknown version");
186 const char * end
= buf
+ n
;
188 glass_revision_number_t old_rev
, rev
;
189 if (!unpack_uint(&p
, end
, &old_rev
))
190 throw Xapian::DatabaseError("Changes file has bad old_rev");
191 if (!unpack_uint(&p
, end
, &rev
))
192 throw Xapian::DatabaseError("Changes file has bad rev");
194 throw Xapian::DatabaseError("Changes file has rev <= old_rev");
195 if (p
== end
|| (*p
!= 0 && *p
!= 1))
196 throw Xapian::DatabaseError("Changes file has bad dangerous flag");
202 n
+= io_read(fd
, buf
+ n
, sizeof(buf
) - n
);
205 throw Xapian::DatabaseError("Changes file truncated");
210 unsigned char v
= *p
++;
213 throw Xapian::DatabaseError("Changes file - junk at end");
218 glass_revision_number_t version_rev
;
219 if (!unpack_uint(&p
, end
, &version_rev
))
220 throw Xapian::DatabaseError("Changes file - bad version file revision");
221 if (rev
!= version_rev
)
222 throw Xapian::DatabaseError("Version file revision != changes file new revision");
224 if (!unpack_uint(&p
, end
, &len
))
225 throw Xapian::DatabaseError("Changes file - bad version file length");
226 if (len
<= size_t(end
- p
)) {
229 if (lseek(fd
, len
- (end
- p
), SEEK_CUR
) < 0)
230 throw Xapian::DatabaseError("Changes file - version file data truncated");
236 unsigned table
= (v
& 0x7);
239 throw Xapian::DatabaseError("Changes file - bad table code");
242 throw Xapian::DatabaseError("Changes file - bad block size");
243 unsigned block_size
= GLASS_MIN_BLOCKSIZE
<< v
;
245 if (!unpack_uint(&p
, end
, &block_number
))
246 throw Xapian::DatabaseError("Changes file - bad block number");
247 // Although the revision number is aligned within the block, the block
248 // data may not be aligned to a word boundary here.
249 uint4 block_rev
= unaligned_read4(reinterpret_cast<const uint8_t*>(p
));
250 (void)block_rev
; // FIXME: Sanity check value.
252 unsigned level
= static_cast<unsigned char>(*p
++);
253 (void)level
; // FIXME: Sanity check value.
254 if (block_size
<= unsigned(end
- p
)) {
257 if (lseek(fd
, block_size
- (end
- p
), SEEK_CUR
) < 0)
258 throw Xapian::DatabaseError("Changes file - block data truncated");