1 /** @file brass_version.cc
2  * @brief BrassVersion class
3  */
4 /* Copyright (C) 2006,2007,2008,2009,2010,2013 Olly Betts
5  * Copyright (C) 2011 Dan Colish
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 "brass_version.h"
29 #include "io_utils.h"
30 #include "omassert.h"
31 #include "stringutils.h" // For STRINGIZE() and CONST_STRLEN().
32 #include "str.h"
33 
34 #ifdef __WIN32__
35 # include "msvc_posix_wrapper.h"
36 #endif
37 
38 #include <cstring> // For memcmp() and memcpy().
39 #include <string>
40 
41 #include "common/safeuuid.h"
42 
43 using namespace std;
44 
45 // YYYYMMDDX where X allows multiple format revisions in a day
46 #define BRASS_VERSION 201103110
47 // 201103110 1.2.5 Bump for new max changesets dbstats
48 // 200912150 1.1.4 Brass debuts.
49 
50 #define MAGIC_STRING "IAmBrass"
51 
52 #define MAGIC_LEN CONST_STRLEN(MAGIC_STRING)
53 // 4 for the version number; 16 for the UUID.
54 #define VERSIONFILE_SIZE (MAGIC_LEN + 4 + 16)
55 
56 // Literal version of VERSIONFILE_SIZE, used for error message.  This needs
57 // to be updated by hand should VERSIONFILE_SIZE change, but that rarely
58 // happens so this isn't an onerous requirement.
59 #define VERSIONFILE_SIZE_LITERAL 28
60 
61 void
create()62 BrassVersion::create()
63 {
64     char buf[VERSIONFILE_SIZE] = MAGIC_STRING;
65     unsigned char *v = reinterpret_cast<unsigned char *>(buf) + MAGIC_LEN;
66     v[0] = static_cast<unsigned char>(BRASS_VERSION & 0xff);
67     v[1] = static_cast<unsigned char>((BRASS_VERSION >> 8) & 0xff);
68     v[2] = static_cast<unsigned char>((BRASS_VERSION >> 16) & 0xff);
69     v[3] = static_cast<unsigned char>((BRASS_VERSION >> 24) & 0xff);
70 
71     uuid_generate(uuid);
72     memcpy(buf + MAGIC_LEN + 4, (void*)uuid, 16);
73 
74     int fd = ::open(filename.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
75 
76     if (fd < 0) {
77 	string msg("Failed to create brass version file: ");
78 	msg += filename;
79 	throw Xapian::DatabaseOpeningError(msg, errno);
80     }
81 
82     try {
83 	io_write(fd, buf, VERSIONFILE_SIZE);
84     } catch (...) {
85 	(void)close(fd);
86 	throw;
87     }
88 
89     io_sync(fd);
90     if (close(fd) != 0) {
91 	string msg("Failed to create brass version file: ");
92 	msg += filename;
93 	throw Xapian::DatabaseOpeningError(msg, errno);
94     }
95 }
96 
97 void
read_and_check()98 BrassVersion::read_and_check()
99 {
100     int fd = ::open(filename.c_str(), O_RDONLY|O_BINARY);
101 
102     if (fd < 0) {
103 	string msg = filename;
104 	msg += ": Failed to open brass version file for reading";
105 	throw Xapian::DatabaseOpeningError(msg, errno);
106     }
107 
108     // Try to read an extra byte so we know if the file is too long.
109     char buf[VERSIONFILE_SIZE + 1];
110     size_t size;
111     try {
112 	size = io_read(fd, buf, VERSIONFILE_SIZE + 1, 0);
113     } catch (...) {
114 	(void)close(fd);
115 	throw;
116     }
117     (void)close(fd);
118 
119     if (size != VERSIONFILE_SIZE) {
120 	CompileTimeAssert(VERSIONFILE_SIZE == VERSIONFILE_SIZE_LITERAL);
121 	string msg = filename;
122 	msg += ": Brass version file should be "
123 	       STRINGIZE(VERSIONFILE_SIZE_LITERAL)" 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 = filename;
130 	msg += ": Brass version file doesn't contain the right magic string";
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 != BRASS_VERSION) {
138 	string msg = filename;
139 	msg += ": Brass version file is version ";
140 	msg += str(version);
141 	msg += " but I only understand " STRINGIZE(BRASS_VERSION);
142 	throw Xapian::DatabaseVersionError(msg);
143     }
144 
145     memcpy((void*)uuid, buf + MAGIC_LEN + 4, 16);
146 }
147