Branch data Line data Source code
1 : : /** @file dbfactory.cc
2 : : * @brief Database factories for non-remote databases.
3 : : */
4 : : /* Copyright 2002,2003,2004,2005,2006,2007,2008,2009 Olly Betts
5 : : * Copyright 2008 Lemur Consulting Ltd
6 : : *
7 : : * This program is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU General Public License as
9 : : * published by the Free Software Foundation; either version 2 of the
10 : : * License, or (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
20 : : * USA
21 : : */
22 : :
23 : : #include <config.h>
24 : :
25 : : #include "xapian/dbfactory.h"
26 : :
27 : : #include "xapian/database.h"
28 : : #include "xapian/error.h"
29 : : #include "xapian/version.h" // For XAPIAN_HAS_XXX_BACKEND.
30 : :
31 : : #include "debuglog.h"
32 : : #include "fileutils.h"
33 : : #include "str.h"
34 : : #include "utils.h"
35 : :
36 : : #include "safeerrno.h"
37 : :
38 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
39 : : # include "brass/brass_database.h"
40 : : #endif
41 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
42 : : # include "chert/chert_database.h"
43 : : #endif
44 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
45 : : # include "flint/flint_database.h"
46 : : #endif
47 : : #ifdef XAPIAN_HAS_INMEMORY_BACKEND
48 : : # include "inmemory/inmemory_database.h"
49 : : #endif
50 : :
51 : : #include <fstream>
52 : : #include <string>
53 : :
54 : : using namespace std;
55 : :
56 : : namespace Xapian {
57 : :
58 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
59 : : Database
60 : 341 : Brass::open(const string &dir) {
61 : : LOGCALL_STATIC(API, Database, "Brass::open", dir);
62 : 341 : RETURN(Database(new BrassDatabase(dir)));
63 : : }
64 : :
65 : : WritableDatabase
66 : 312 : Brass::open(const string &dir, int action, int block_size) {
67 : : LOGCALL_STATIC(API, WritableDatabase, "Brass::open", dir | action | block_size);
68 : 315 : RETURN(WritableDatabase(new BrassWritableDatabase(dir, action, block_size)));
69 : : }
70 : : #endif
71 : :
72 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
73 : : Database
74 : 341 : Chert::open(const string &dir) {
75 : : LOGCALL_STATIC(API, Database, "Chert::open", dir);
76 : 341 : return Database(new ChertDatabase(dir));
77 : : }
78 : :
79 : : WritableDatabase
80 : 314 : Chert::open(const string &dir, int action, int block_size) {
81 : : LOGCALL_STATIC(API, WritableDatabase, "Chert::open", dir | action | block_size);
82 : 317 : return WritableDatabase(new ChertWritableDatabase(dir, action, block_size));
83 : : }
84 : : #endif
85 : :
86 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
87 : : Database
88 : 344 : Flint::open(const string &dir) {
89 : : LOGCALL_STATIC(API, Database, "Flint::open", dir);
90 : 348 : return Database(new FlintDatabase(dir));
91 : : }
92 : :
93 : : WritableDatabase
94 : 320 : Flint::open(const string &dir, int action, int block_size) {
95 : : LOGCALL_STATIC(API, WritableDatabase, "Flint::open", dir | action | block_size);
96 : 332 : return WritableDatabase(new FlintWritableDatabase(dir, action, block_size));
97 : : }
98 : : #endif
99 : :
100 : : #ifdef XAPIAN_HAS_INMEMORY_BACKEND
101 : : WritableDatabase
102 : 266 : InMemory::open() {
103 : : LOGCALL_STATIC(API, Database, "InMemory::open", NO_ARGS);
104 : 266 : return WritableDatabase(new InMemoryDatabase);
105 : : }
106 : : #endif
107 : :
108 : : static void
109 : 644 : open_stub(Database &db, const string &file)
110 : : {
111 : : // A stub database is a text file with one or more lines of this format:
112 : : // <dbtype> <serialised db object>
113 : : //
114 : : // Lines which start with a "#" character are ignored.
115 : : //
116 : : // Any paths specified in stub database files which are relative will be
117 : : // considered to be relative to the directory containing the stub database.
118 : 644 : ifstream stub(file.c_str());
119 : 644 : string line;
120 : 644 : unsigned int line_no = 0;
121 [ + + ]: 1959 : while (getline(stub, line)) {
122 : 1315 : ++line_no;
123 [ + - ][ + + ]: 1315 : if (line.empty() || line[0] == '#')
[ + + ]
124 : 166 : continue;
125 : 1149 : string::size_type space = line.find(' ');
126 [ + + ]: 1149 : if (space == string::npos) space = line.size();
127 : :
128 : 1149 : string type(line, 0, space);
129 : 1149 : line.erase(0, space + 1);
130 : :
131 [ + + ]: 1149 : if (type == "auto") {
132 : 137 : resolve_relative_path(line, file);
133 : 137 : db.add_database(Database(line));
134 : 137 : continue;
135 : : }
136 : :
137 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
138 [ + + ]: 1012 : if (type == "chert") {
139 : 322 : resolve_relative_path(line, file);
140 : 322 : db.add_database(Chert::open(line));
141 : 322 : continue;
142 : : }
143 : : #endif
144 : :
145 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
146 [ + + ]: 690 : if (type == "flint") {
147 : 318 : resolve_relative_path(line, file);
148 : 318 : db.add_database(Flint::open(line));
149 : 318 : continue;
150 : : }
151 : : #endif
152 : :
153 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
154 [ + + ]: 372 : if (type == "brass") {
155 : 322 : resolve_relative_path(line, file);
156 : 322 : db.add_database(Brass::open(line));
157 : 322 : continue;
158 : : }
159 : : #endif
160 : :
161 : : #ifdef XAPIAN_HAS_REMOTE_BACKEND
162 [ + + ]: 50 : if (type == "remote") {
163 : 12 : string::size_type colon = line.find(':');
164 [ + - ]: 12 : if (colon == 0) {
165 : : // prog
166 : : // FIXME: timeouts
167 : : // Is it a security risk?
168 : 12 : space = line.find(' ');
169 : 12 : string args;
170 [ + - ]: 12 : if (space != string::npos) {
171 : 12 : args.assign(line, space + 1, string::npos);
172 : 12 : line.assign(line, 1, space - 1);
173 : : } else {
174 : 0 : line.erase(0, 1);
175 : : }
176 : 12 : db.add_database(Remote::open(line, args));
177 [ # # ]: 0 : } else if (colon != string::npos) {
178 : : // tcp
179 : : // FIXME: timeouts
180 : 0 : unsigned int port = atoi(line.c_str() + colon + 1);
181 : 0 : line.erase(colon);
182 : 0 : db.add_database(Remote::open(line, port));
183 : : }
184 : 12 : continue;
185 : : }
186 : : #endif
187 : :
188 : : #ifdef XAPIAN_HAS_INMEMORY_BACKEND
189 [ + + ][ + - ]: 38 : if (type == "inmemory" && line.empty()) {
[ + + ]
190 : 2 : db.add_database(InMemory::open());
191 : 2 : continue;
192 : : }
193 : : #endif
194 : :
195 : : // Don't include the line itself - that might help an attacker
196 : : // by revealing part of a sensitive file's contents if they can
197 : : // arrange for it to be read as a stub database via infelicities in
198 : : // an application which uses Xapian. The line number is enough
199 : : // information to identify the problem line.
200 : 36 : throw DatabaseOpeningError(file + ':' + str(line_no) + ": Bad line");
201 : 680 : }
202 : :
203 : : // Allowing a stub database with no databases listed allows things like
204 : : // a "search all databases" feature to be implemented by generating a
205 : : // stub database file without having to special case there not being any
206 : : // databases yet.
207 : : //
208 : : // 1.0.x throws DatabaseOpeningError here, but with a "Bad line" message
209 : : // with the line number just past the end of the file, which is a bit odd.
210 : 608 : }
211 : :
212 : : static void
213 : 5 : open_stub(WritableDatabase &db, const string &file, int action)
214 : : {
215 : : // A stub database is a text file with one or more lines of this format:
216 : : // <dbtype> <serialised db object>
217 : : //
218 : : // Lines which start with a "#" character, and lines which have no spaces
219 : : // in them, are ignored.
220 : : //
221 : : // Any paths specified in stub database files which are relative will be
222 : : // considered to be relative to the directory containing the stub database.
223 : 5 : ifstream stub(file.c_str());
224 : 5 : string line;
225 : 5 : unsigned int line_no = 0;
226 : 11 : while (true) {
227 [ - + ]: 16 : if (db.internal.size() > 1) {
228 : 0 : throw DatabaseOpeningError(file + ": Can't open a stub database listing multiple databases as a WritableDatabase");
229 : : }
230 : :
231 [ + + ]: 16 : if (!getline(stub, line)) break;
232 : :
233 : 11 : ++line_no;
234 [ + - ][ + + ]: 11 : if (line.empty() || line[0] == '#')
[ + + ]
235 : 6 : continue;
236 : 5 : string::size_type space = line.find(' ');
237 [ + + ]: 5 : if (space == string::npos) space = line.size();
238 : :
239 : 5 : string type(line, 0, space);
240 : 5 : line.erase(0, space + 1);
241 : :
242 [ + + ]: 5 : if (type == "auto") {
243 : 3 : resolve_relative_path(line, file);
244 : 3 : db.add_database(WritableDatabase(line, action));
245 : 3 : continue;
246 : : }
247 : :
248 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
249 [ - + ]: 2 : if (type == "chert") {
250 : 0 : resolve_relative_path(line, file);
251 : 0 : db.add_database(Chert::open(line, action));
252 : 0 : continue;
253 : : }
254 : : #endif
255 : :
256 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
257 [ - + ]: 2 : if (type == "flint") {
258 : 0 : resolve_relative_path(line, file);
259 : 0 : db.add_database(Flint::open(line, action));
260 : 0 : continue;
261 : : }
262 : : #endif
263 : :
264 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
265 [ - + ]: 2 : if (type == "brass") {
266 : 0 : resolve_relative_path(line, file);
267 : 0 : db.add_database(Brass::open(line, action));
268 : 0 : continue;
269 : : }
270 : : #endif
271 : :
272 : : #ifdef XAPIAN_HAS_REMOTE_BACKEND
273 [ - + ]: 2 : if (type == "remote") {
274 : 0 : string::size_type colon = line.find(':');
275 [ # # ]: 0 : if (colon == 0) {
276 : : // prog
277 : : // FIXME: timeouts
278 : : // Is it a security risk?
279 : 0 : space = line.find(' ');
280 : 0 : string args;
281 [ # # ]: 0 : if (space != string::npos) {
282 : 0 : args.assign(line, space + 1, string::npos);
283 : 0 : line.assign(line, 1, space - 1);
284 : : } else {
285 : 0 : line.erase(0, 1);
286 : : }
287 : 0 : db.add_database(Remote::open_writable(line, args));
288 [ # # ]: 0 : } else if (colon != string::npos) {
289 : : // tcp
290 : : // FIXME: timeouts
291 : 0 : unsigned int port = atoi(line.c_str() + colon + 1);
292 : 0 : line.erase(colon);
293 : 0 : db.add_database(Remote::open_writable(line, port));
294 : : }
295 : 0 : continue;
296 : : }
297 : : #endif
298 : :
299 : : #ifdef XAPIAN_HAS_INMEMORY_BACKEND
300 [ + - ][ + - ]: 2 : if (type == "inmemory" && line.empty()) {
[ + - ]
301 : 2 : db.add_database(InMemory::open());
302 : 2 : continue;
303 : : }
304 : : #endif
305 : :
306 : : // Don't include the line itself - that might help an attacker
307 : : // by revealing part of a sensitive file's contents if they can
308 : : // arrange for it to be read as a stub database via infelicities in
309 : : // an application which uses Xapian. The line number is enough
310 : : // information to identify the problem line.
311 : 0 : throw DatabaseOpeningError(file + ':' + str(line_no) + ": Bad line");
312 : : }
313 : :
314 [ - + ]: 5 : if (db.internal.empty()) {
315 : 0 : throw DatabaseOpeningError(file + ": No databases listed");
316 : 5 : }
317 : 5 : }
318 : :
319 : : Database
320 : 31 : Auto::open_stub(const string &file)
321 : : {
322 : : LOGCALL_STATIC(API, Database, "Auto::open_stub", file);
323 : 31 : Database db;
324 : 31 : open_stub(db, file);
325 : 18 : RETURN(db);
326 : : }
327 : :
328 : : WritableDatabase
329 : 4 : Auto::open_stub(const string &file, int action)
330 : : {
331 : : LOGCALL_STATIC(API, WritableDatabase, "Auto::open_stub", file | action);
332 : 4 : WritableDatabase db;
333 : 4 : open_stub(db, file, action);
334 : 0 : RETURN(db);
335 : : }
336 : :
337 : 4331 : Database::Database(const string &path)
338 : : {
339 : : LOGCALL_CTOR(API, "Database", path);
340 : :
341 : : struct stat statbuf;
342 [ + + # # ]: 4331 : if (stat(path, &statbuf) == -1) {
343 : 40 : throw DatabaseOpeningError("Couldn't stat '" + path + "'", errno);
344 : : }
345 : :
346 [ + + ][ # # ]: 4291 : if (S_ISREG(statbuf.st_mode)) {
347 : : // The path is a file, so assume it is a stub database file.
348 : 521 : open_stub(*this, path);
349 : 503 : return;
350 : : }
351 : :
352 [ - + ][ # # ]: 3770 : if (rare(!S_ISDIR(statbuf.st_mode))) {
353 : 0 : throw DatabaseOpeningError("Not a regular file or directory: '" + path + "'");
354 : : }
355 : :
356 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
357 [ + + ][ # # ]: 3770 : if (file_exists(path + "/iamchert")) {
358 : 1267 : internal.push_back(new ChertDatabase(path));
359 : 1267 : return;
360 : : }
361 : : #endif
362 : :
363 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
364 [ + + ][ # # ]: 2503 : if (file_exists(path + "/iamflint")) {
365 : 1194 : internal.push_back(new FlintDatabase(path));
366 : 1192 : return;
367 : : }
368 : : #endif
369 : :
370 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
371 [ + + ][ # # ]: 1310 : if (file_exists(path + "/iambrass")) {
372 : 1218 : internal.push_back(new BrassDatabase(path));
373 : 1218 : return;
374 : : }
375 : : #endif
376 : :
377 : : // Check for "stub directories".
378 : 92 : string stub_file = path;
379 : 92 : stub_file += "/XAPIANDB";
380 [ - + # # ]: 92 : if (rare(!file_exists(stub_file))) {
381 : 0 : throw DatabaseOpeningError("Couldn't detect type of database");
382 : : }
383 : :
384 : 92 : open_stub(*this, stub_file);
385 : 4331 : }
386 : :
387 : : #if defined XAPIAN_HAS_FLINT_BACKEND || \
388 : : defined XAPIAN_HAS_CHERT_BACKEND || \
389 : : defined XAPIAN_HAS_BRASS_BACKEND
390 : : #define HAVE_DISK_BACKEND
391 : : #endif
392 : :
393 : 1041 : WritableDatabase::WritableDatabase(const std::string &path, int action)
394 : 1041 : : Database()
395 : : {
396 : : LOGCALL_CTOR(API, "WritableDatabase", path | action);
397 : : #ifdef HAVE_DISK_BACKEND
398 : : enum {
399 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
400 : : CHERT,
401 : : #endif
402 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
403 : : FLINT,
404 : : #endif
405 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
406 : : BRASS,
407 : : #endif
408 : : UNSET
409 : 1041 : } type = UNSET;
410 : : #endif
411 : : struct stat statbuf;
412 [ + + # # ]: 1041 : if (stat(path, &statbuf) == -1) {
413 : : // ENOENT probably just means that we need to create the directory.
414 [ - + ][ # # ]: 24 : if (errno != ENOENT)
415 : 0 : throw DatabaseOpeningError("Couldn't stat '" + path + "'", errno);
416 : : } else {
417 : : // File or directory already exists.
418 : :
419 [ + + ][ # # ]: 1017 : if (S_ISREG(statbuf.st_mode)) {
420 : : // The path is a file, so assume it is a stub database file.
421 : 1 : open_stub(*this, path, action);
422 : 1 : return;
423 : : }
424 : :
425 [ - + ][ # # ]: 1016 : if (rare(!S_ISDIR(statbuf.st_mode))) {
426 : 0 : throw DatabaseOpeningError("Not a regular file or directory: '" + path + "'");
427 : : }
428 : :
429 [ + + ][ # # ]: 1016 : if (file_exists(path + "/iamchert")) {
430 : : // Existing chert DB.
431 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
432 : 343 : type = CHERT;
433 : : #else
434 : : throw FeatureUnavailableError("Chert backend disabled");
435 : : #endif
436 [ + + ][ # # ]: 673 : } else if (file_exists(path + "/iamflint")) {
437 : : // Existing flint DB.
438 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
439 : 336 : type = FLINT;
440 : : #else
441 : : throw FeatureUnavailableError("Flint backend disabled");
442 : : #endif
443 [ + - ][ # # ]: 337 : } else if (file_exists(path + "/iambrass")) {
444 : : // Existing brass DB.
445 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
446 : 337 : type = BRASS;
447 : : #else
448 : : throw FeatureUnavailableError("Brass backend disabled");
449 : : #endif
450 : : } else {
451 : : // Check for "stub directories".
452 : 0 : string stub_file = path;
453 : 0 : stub_file += "/XAPIANDB";
454 [ # # # # ]: 0 : if (usual(file_exists(stub_file))) {
455 : 0 : open_stub(*this, stub_file, action);
456 : : return;
457 [ # # ][ # # ]: 0 : }
458 : : }
459 : : }
460 : :
461 : : #ifdef HAVE_DISK_BACKEND
462 [ + + + + ]: 1040 : switch (type) {
[ # # # # ]
463 : : case UNSET: {
464 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
465 : : // If only brass is enabled, there's no point checking the
466 : : // environmental variable.
467 : : # if defined XAPIAN_HAS_CHERT_BACKEND || defined XAPIAN_HAS_FLINT_BACKEND
468 : : // If $XAPIAN_PREFER_BRASS is set to a non-empty value, prefer brass
469 : : // if there's no existing database.
470 : 24 : const char *p = getenv("XAPIAN_PREFER_BRASS");
471 [ - + # # # : 24 : if (p && *p)
# ][ # # ]
472 : 0 : goto brass;
473 : : #endif
474 : : #endif
475 : : }
476 : : // Fall through to first enabled case, so order the remaining cases
477 : : // by preference.
478 : : #ifdef XAPIAN_HAS_CHERT_BACKEND
479 : : case CHERT:
480 : 368 : internal.push_back(new ChertWritableDatabase(path, action, 8192));
481 : 366 : break;
482 : : #endif
483 : : #ifdef XAPIAN_HAS_FLINT_BACKEND
484 : : case FLINT:
485 : 338 : internal.push_back(new FlintWritableDatabase(path, action, 8192));
486 : 334 : break;
487 : : #endif
488 : : #ifdef XAPIAN_HAS_BRASS_BACKEND
489 : : case BRASS:
490 : : brass:
491 : 1038 : internal.push_back(new BrassWritableDatabase(path, action, 8192));
492 : : break;
493 : : #endif
494 : : }
495 : : #else
496 : : throw FeatureUnavailableError("No disk-based writable backend is enabled");
497 : : #endif
498 : 1041 : }
499 : :
500 : : }
|