2 * @brief Glass changesets
4 /* Copyright 2014,2016,2020 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"
32 #include "posixy_wrapper.h"
34 #include "stringutils.h"
35 #include "wordaccess.h"
36 #include "xapian/constants.h"
37 #include "xapian/error.h"
45 GlassChanges::~GlassChanges()
47 if (changes_fd
>= 0) {
49 string changes_tmp
= changes_stem
;
51 io_unlink(changes_tmp
);
56 GlassChanges::start(glass_revision_number_t old_rev
,
57 glass_revision_number_t rev
,
61 // Don't generate a changeset for the first revision.
65 // Always check max_changesets for modification since last revision.
66 const char *p
= getenv("XAPIAN_MAX_CHANGESETS");
68 if (!parse_unsigned(p
, max_changesets
)) {
69 throw Xapian::InvalidArgumentError("XAPIAN_MAX_CHANGESETS must be "
70 "a non-negative integer");
76 if (max_changesets
== 0)
79 string changes_tmp
= changes_stem
;
81 changes_fd
= posixy_open(changes_tmp
.c_str(),
82 O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, 0666);
84 string message
= "Couldn't open changeset ";
85 message
+= changes_tmp
;
86 message
+= " to write";
87 throw Xapian::DatabaseError(message
, errno
);
90 // Write header for changeset file.
91 string header
= CHANGES_MAGIC_STRING
;
92 header
+= char(CHANGES_VERSION
);
93 pack_uint(header
, old_rev
);
94 pack_uint(header
, rev
);
96 if (flags
& Xapian::DB_DANGEROUS
) {
97 header
+= '\x01'; // Changes can't be applied to a live database.
99 header
+= '\x00'; // Changes can be applied to a live database.
102 io_write(changes_fd
, header
.data(), header
.size());
103 // FIXME: save the block stream as a single zlib stream...
105 // bool compressed = CHANGES_VERSION != 1; FIXME: always true for glass, but make optional?
110 GlassChanges::write_block(const char * p
, size_t len
)
112 io_write(changes_fd
, p
, len
);
116 GlassChanges::commit(glass_revision_number_t new_rev
, int flags
)
121 io_write(changes_fd
, "\xff", 1);
123 string changes_tmp
= changes_stem
;
124 changes_tmp
+= "tmp";
126 if (!(flags
& Xapian::DB_NO_SYNC
) && !io_sync(changes_fd
)) {
127 int saved_errno
= errno
;
128 (void)::close(changes_fd
);
130 (void)unlink(changes_tmp
.c_str());
131 string m
= changes_tmp
;
132 m
+= ": Failed to sync";
133 throw Xapian::DatabaseError(m
, saved_errno
);
136 (void)::close(changes_fd
);
139 string changes_file
= changes_stem
;
140 changes_file
+= str(new_rev
- 1); // FIXME: ?
142 if (!io_tmp_rename(changes_tmp
, changes_file
)) {
143 string m
= changes_tmp
;
144 m
+= ": Failed to rename to ";
146 throw Xapian::DatabaseError(m
, errno
);
149 if (new_rev
<= max_changesets
) {
150 // We can't yet have max_changesets old changesets.
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
159 glass_revision_number_t stop_changeset
= new_rev
- max_changesets
;
160 while (oldest_changeset
< stop_changeset
) {
161 changes_file
.resize(changes_stem
.size());
162 changes_file
+= str(oldest_changeset
);
163 (void)io_unlink(changes_file
);
169 GlassChanges::check(const string
& changes_file
)
171 FD
fd(posixy_open(changes_file
.c_str(), O_RDONLY
| O_CLOEXEC
));
173 string message
= "Couldn't open changeset ";
174 message
+= changes_file
;
175 throw Xapian::DatabaseError(message
, errno
);
180 size_t n
= io_read(fd
, buf
, sizeof(buf
), CONST_STRLEN(CHANGES_MAGIC_STRING
) + 4);
181 if (memcmp(buf
, CHANGES_MAGIC_STRING
,
182 CONST_STRLEN(CHANGES_MAGIC_STRING
)) != 0) {
183 throw Xapian::DatabaseError("Changes file has wrong magic");
186 const char * p
= buf
+ CONST_STRLEN(CHANGES_MAGIC_STRING
);
187 if (*p
++ != CHANGES_VERSION
) {
188 throw Xapian::DatabaseError("Changes file has unknown version");
190 const char * end
= buf
+ n
;
192 glass_revision_number_t old_rev
, rev
;
193 if (!unpack_uint(&p
, end
, &old_rev
))
194 throw Xapian::DatabaseError("Changes file has bad old_rev");
195 if (!unpack_uint(&p
, end
, &rev
))
196 throw Xapian::DatabaseError("Changes file has bad rev");
198 throw Xapian::DatabaseError("Changes file has rev <= old_rev");
199 if (p
== end
|| (*p
!= 0 && *p
!= 1))
200 throw Xapian::DatabaseError("Changes file has bad dangerous flag");
206 n
+= io_read(fd
, buf
+ n
, sizeof(buf
) - n
);
209 throw Xapian::DatabaseError("Changes file truncated");
214 unsigned char v
= *p
++;
217 throw Xapian::DatabaseError("Changes file - junk at end");
222 glass_revision_number_t version_rev
;
223 if (!unpack_uint(&p
, end
, &version_rev
))
224 throw Xapian::DatabaseError("Changes file - bad version file revision");
225 if (rev
!= version_rev
)
226 throw Xapian::DatabaseError("Version file revision != changes file new revision");
228 if (!unpack_uint(&p
, end
, &len
))
229 throw Xapian::DatabaseError("Changes file - bad version file length");
230 if (len
<= size_t(end
- p
)) {
233 if (lseek(fd
, len
- (end
- p
), SEEK_CUR
) < 0)
234 throw Xapian::DatabaseError("Changes file - version file data truncated");
240 unsigned table
= (v
& 0x7);
243 throw Xapian::DatabaseError("Changes file - bad table code");
246 throw Xapian::DatabaseError("Changes file - bad block size");
247 unsigned block_size
= GLASS_MIN_BLOCKSIZE
<< v
;
249 if (!unpack_uint(&p
, end
, &block_number
))
250 throw Xapian::DatabaseError("Changes file - bad block number");
252 // Parse information from the start of the block.
254 // Although the revision number is aligned within the block, the block
255 // data may not be aligned to a word boundary here.
256 uint4 block_rev
= unaligned_read4(reinterpret_cast<const uint8_t*>(p
));
257 (void)block_rev
; // FIXME: Sanity check value.
258 unsigned level
= static_cast<unsigned char>(p
[4]);
259 (void)level
; // FIXME: Sanity check value.
261 // Skip over the block content.
262 if (block_size
<= unsigned(end
- p
)) {
265 if (lseek(fd
, block_size
- (end
- p
), SEEK_CUR
) < 0)
266 throw Xapian::DatabaseError("Changes file - block data truncated");