1 /** @file flint_version.cc
2 * @brief FlintVersion class
3 */
4 /* Copyright (C) 2006,2007,2008,2009,2013 Olly Betts
5 * Copyright 2010 Richard Boulton
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include <config.h>
23
24 #include "safeerrno.h"
25
26 #include <xapian/error.h>
27
28 #include "flint_version.h"
29 #include "io_utils.h"
30 #include "str.h"
31 #include "stringutils.h" // For STRINGIZE() and CONST_STRLEN().
32 #include "utils.h"
33
34 #ifdef __WIN32__
35 # include "msvc_posix_wrapper.h"
36 #endif
37
38 #include <cstdio> // For rename().
39 #include <cstring> // For memcmp().
40 #include <string>
41
42 #include "common/safeuuid.h"
43
44 using namespace std;
45
46 // YYYYMMDDX where X allows multiple format revisions in a day
47 #define FLINT_VERSION 200709120
48 // 200709120 1.0.3 Database::get_metadata(), WritableDatabase::set_metadata().
49 // Kill the unused "has_termfreqs" flag in the termlist table.
50 // 200706140 1.0.2 Optional value and position tables.
51 // 200704230 1.0.0 Use zlib compression of tags for record and termlist tables.
52 // 200611200 N/A Fixed occasional, architecture-dependent surplus bits in
53 // interpolative coding; "flicklock" -> "flintlock".
54 // 200506110 0.9.2 Fixed interpolative coding to work(!)
55 // 200505310 0.9.1 Interpolative coding for position lists.
56 // 200505280 N/A Total doclen and last docid entry moved to postlist table.
57 // 200505270 N/A First dated version.
58
59 #define MAGIC_STRING "IAmFlint"
60
61 #define MAGIC_LEN CONST_STRLEN(MAGIC_STRING)
62 #define VERSIONFILE_SIZE (MAGIC_LEN + 4)
63
create()64 void FlintVersion::create()
65 {
66 char buf[VERSIONFILE_SIZE] = MAGIC_STRING;
67 unsigned char *v = reinterpret_cast<unsigned char *>(buf) + MAGIC_LEN;
68 v[0] = static_cast<unsigned char>(FLINT_VERSION & 0xff);
69 v[1] = static_cast<unsigned char>((FLINT_VERSION >> 8) & 0xff);
70 v[2] = static_cast<unsigned char>((FLINT_VERSION >> 16) & 0xff);
71 v[3] = static_cast<unsigned char>((FLINT_VERSION >> 24) & 0xff);
72
73 int fd = ::open(filename.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
74
75 if (fd < 0) {
76 string msg("Failed to create flint version file: ");
77 msg += filename;
78 throw Xapian::DatabaseOpeningError(msg, errno);
79 }
80
81 try {
82 io_write(fd, buf, VERSIONFILE_SIZE);
83 } catch (...) {
84 (void)close(fd);
85 throw;
86 }
87
88 io_sync(fd);
89 if (close(fd) != 0) {
90 string msg("Failed to create flint version file: ");
91 msg += filename;
92 throw Xapian::DatabaseOpeningError(msg, errno);
93 }
94
95 uuid_clear(uuid);
96 ensure_uuid();
97 }
98
read_and_check(bool readonly)99 void FlintVersion::read_and_check(bool readonly)
100 {
101 int fd = ::open(filename.c_str(), O_RDONLY|O_BINARY);
102
103 if (fd < 0) {
104 string msg("Failed to open flint version file for reading: ");
105 msg += filename;
106 throw Xapian::DatabaseOpeningError(msg, errno);
107 }
108
109 // Try to read an extra byte so we know if the file is too long.
110 char buf[VERSIONFILE_SIZE + 1];
111 size_t size;
112 try {
113 size = io_read(fd, buf, VERSIONFILE_SIZE + 1, 0);
114 } catch (...) {
115 (void)close(fd);
116 throw;
117 }
118 (void)close(fd);
119
120 if (size != VERSIONFILE_SIZE) {
121 string msg("Flint version file ");
122 msg += filename;
123 msg += " should be " STRINGIZE(VERSIONFILE_SIZE) " bytes, actually ";
124 msg += str(size);
125 throw Xapian::DatabaseCorruptError(msg);
126 }
127
128 if (memcmp(buf, MAGIC_STRING, MAGIC_LEN) != 0) {
129 string msg("Flint version file doesn't contain the right magic string: ");
130 msg += filename;
131 throw Xapian::DatabaseCorruptError(msg);
132 }
133
134 const unsigned char *v;
135 v = reinterpret_cast<const unsigned char *>(buf) + MAGIC_LEN;
136 unsigned int version = v[0] | (v[1] << 8) | (v[2] << 16) | (v[3] << 24);
137 if (version >= 200704230 && version < 200709120) {
138 if (readonly) return;
139 // Upgrade the database to the current version since any changes we
140 // make won't be compatible with older versions of Xapian.
141 string filename_save = filename;
142 filename += ".tmp";
143 create();
144 int result;
145 #ifdef __WIN32__
146 result = msvc_posix_rename(filename.c_str(), filename_save.c_str());
147 #else
148 result = rename(filename.c_str(), filename_save.c_str());
149 #endif
150 filename = filename_save;
151 if (result == -1) {
152 string msg("Failed to update flint version file: ");
153 msg += filename;
154 throw Xapian::DatabaseOpeningError(msg);
155 }
156 return;
157 }
158 if (version != FLINT_VERSION) {
159 string msg("Flint version file ");
160 msg += filename;
161 msg += " is version ";
162 msg += str(version);
163 msg += " but I only understand " STRINGIZE(FLINT_VERSION);
164 throw Xapian::DatabaseVersionError(msg);
165 }
166
167 string f = filename;
168 f.resize(f.size() - CONST_STRLEN("iamflint"));
169 f += "uuid";
170 fd = ::open(f.c_str(), O_RDONLY|O_BINARY);
171
172 if (fd < 0) {
173 uuid_clear(uuid);
174 return;
175 }
176
177 try {
178 (void)io_read(fd, reinterpret_cast<char*>(uuid), 16, 16);
179 } catch (...) {
180 uuid_clear(uuid);
181 (void)close(fd);
182 throw;
183 }
184 (void)close(fd);
185 }
186
187 void
ensure_uuid() const188 FlintVersion::ensure_uuid() const
189 {
190 if (uuid_is_null(uuid)) {
191 string f = filename;
192 f.resize(f.size() - CONST_STRLEN("iamflint"));
193 f += "uuid";
194 int fd = ::open(f.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
195
196 // Might be read-only, so don't error, but instead fall back to
197 // using the mtime of the version file.
198 if (fd < 0) {
199 struct stat statbuf;
200 if (stat(filename, &statbuf) != 0) {
201 throw Xapian::DatabaseError("Couldn't stat " + filename, errno);
202 }
203 unsigned long mtime = statbuf.st_mtime;
204 // This isn't a validly generated UUID, but it'll do for the
205 // purpose of identifying a database master for replication
206 // while we transition to "real" UUIDs.
207 unsigned char *v = reinterpret_cast<unsigned char *>(uuid);
208 v[0] = static_cast<unsigned char>(mtime & 0xff);
209 v[1] = static_cast<unsigned char>((mtime >> 8) & 0xff);
210 v[2] = static_cast<unsigned char>((mtime >> 16) & 0xff);
211 v[3] = static_cast<unsigned char>((mtime >> 24) & 0xff);
212 return;
213 }
214
215 uuid_generate(uuid);
216 try {
217 io_write(fd, reinterpret_cast<const char*>(uuid), 16);
218 } catch (...) {
219 (void)close(fd);
220 throw;
221 }
222
223 if (close(fd) != 0) {
224 string msg("Failed to create flint uuid file: ");
225 msg += f;
226 throw Xapian::DatabaseError(msg, errno);
227 }
228 }
229 }
230