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