2 * @brief Database factories for non-remote databases.
4 /* Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2011,2012,2013,2014,2015,2016 Olly Betts
5 * Copyright 2008 Lemur Consulting Ltd
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.
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.
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
25 #include "xapian/dbfactory.h"
27 #include "xapian/constants.h"
28 #include "xapian/database.h"
29 #include "xapian/error.h"
30 #include "xapian/version.h" // For XAPIAN_HAS_XXX_BACKEND.
33 #include "filetests.h"
34 #include "fileutils.h"
35 #include "posixy_wrapper.h"
38 #include "safeerrno.h"
39 #include <cstdlib> // For atoi().
41 #ifdef XAPIAN_HAS_GLASS_BACKEND
42 # include "glass/glass_database.h"
44 #ifdef XAPIAN_HAS_CHERT_BACKEND
45 # include "chert/chert_database.h"
47 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
48 # include "inmemory/inmemory_database.h"
50 // Even if none of the above get included, we still need a definition of
51 // Database::Internal.
52 #include "backends/database.h"
60 check_if_single_file_db(const struct stat
& sb
, const string
& path
,
63 #ifdef XAPIAN_HAS_GLASS_BACKEND
64 if (!S_ISREG(sb
.st_mode
)) return false;
65 // Look at the size as a clue - if it's 0 or not a multiple of 2048,
66 // then it's not a single-file glass database. If it is, peek at the start
67 // of the file to determine which it is.
68 if (sb
.st_size
== 0 || sb
.st_size
% 2048 != 0) return false;
69 int fd
= posixy_open(path
.c_str(), O_RDONLY
|O_BINARY
);
72 // FIXME: Don't duplicate magic check here...
73 if (io_read(fd
, magic_buf
, 14) == 14 &&
74 lseek(fd
, 0, SEEK_SET
) == 0 &&
75 memcmp(magic_buf
, "\x0f\x0dXapian Glass", 14) == 0) {
96 open_stub(Database
&db
, const string
&file
)
98 // A stub database is a text file with one or more lines of this format:
99 // <dbtype> <serialised db object>
101 // Lines which start with a "#" character are ignored.
103 // Any paths specified in stub database files which are relative will be
104 // considered to be relative to the directory containing the stub database.
105 ifstream
stub(file
.c_str());
107 string msg
= "Couldn't open stub database file: ";
109 throw Xapian::DatabaseOpeningError(msg
, errno
);
112 unsigned int line_no
= 0;
113 while (getline(stub
, line
)) {
115 if (line
.empty() || line
[0] == '#')
117 string::size_type space
= line
.find(' ');
118 if (space
== string::npos
) space
= line
.size();
120 string
type(line
, 0, space
);
121 line
.erase(0, space
+ 1);
123 if (type
== "auto") {
124 resolve_relative_path(line
, file
);
125 db
.add_database(Database(line
));
129 #ifdef XAPIAN_HAS_CHERT_BACKEND
130 if (type
== "chert") {
131 resolve_relative_path(line
, file
);
132 db
.add_database(Database(new ChertDatabase(line
)));
137 #ifdef XAPIAN_HAS_GLASS_BACKEND
138 if (type
== "glass") {
139 resolve_relative_path(line
, file
);
140 db
.add_database(Database(new GlassDatabase(line
)));
145 #ifdef XAPIAN_HAS_REMOTE_BACKEND
146 if (type
== "remote") {
147 string::size_type colon
= line
.find(':');
151 // Is it a security risk?
152 space
= line
.find(' ');
154 if (space
!= string::npos
) {
155 args
.assign(line
, space
+ 1, string::npos
);
156 line
.assign(line
, 1, space
- 1);
160 db
.add_database(Remote::open(line
, args
));
161 } else if (colon
!= string::npos
) {
164 unsigned int port
= atoi(line
.c_str() + colon
+ 1);
166 db
.add_database(Remote::open(line
, port
));
172 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
173 if (type
== "inmemory" && line
.empty()) {
174 db
.add_database(Database(string(), DB_BACKEND_INMEMORY
));
179 if (type
== "flint") {
180 throw FeatureUnavailableError("Flint backend no longer supported");
183 // Don't include the line itself - that might help an attacker
184 // by revealing part of a sensitive file's contents if they can
185 // arrange for it to be read as a stub database via infelicities in
186 // an application which uses Xapian. The line number is enough
187 // information to identify the problem line.
188 throw DatabaseOpeningError(file
+ ':' + str(line_no
) + ": Bad line");
191 // Allowing a stub database with no databases listed allows things like
192 // a "search all databases" feature to be implemented by generating a
193 // stub database file without having to special case there not being any
196 // 1.0.x throws DatabaseOpeningError here, but with a "Bad line" message
197 // with the line number just past the end of the file, which is a bit odd.
201 open_stub(WritableDatabase
&db
, const string
&file
, int flags
)
203 // A stub database is a text file with one or more lines of this format:
204 // <dbtype> <serialised db object>
206 // Lines which start with a "#" character, and lines which have no spaces
207 // in them, are ignored.
209 // Any paths specified in stub database files which are relative will be
210 // considered to be relative to the directory containing the stub database.
211 ifstream
stub(file
.c_str());
213 string msg
= "Couldn't open stub database file: ";
215 throw Xapian::DatabaseOpeningError(msg
, errno
);
218 unsigned int line_no
= 0;
220 if (!getline(stub
, line
)) break;
223 if (line
.empty() || line
[0] == '#')
225 string::size_type space
= line
.find(' ');
226 if (space
== string::npos
) space
= line
.size();
228 string
type(line
, 0, space
);
229 line
.erase(0, space
+ 1);
231 if (type
== "auto") {
232 resolve_relative_path(line
, file
);
233 db
.add_database(WritableDatabase(line
, flags
));
237 #ifdef XAPIAN_HAS_CHERT_BACKEND
238 if (type
== "chert") {
239 resolve_relative_path(line
, file
);
240 db
.add_database(WritableDatabase(line
, flags
|DB_BACKEND_CHERT
));
245 #ifdef XAPIAN_HAS_GLASS_BACKEND
246 if (type
== "glass") {
247 resolve_relative_path(line
, file
);
248 db
.add_database(WritableDatabase(line
, flags
|DB_BACKEND_GLASS
));
253 #ifdef XAPIAN_HAS_REMOTE_BACKEND
254 if (type
== "remote") {
255 string::size_type colon
= line
.find(':');
259 // Is it a security risk?
260 space
= line
.find(' ');
262 if (space
!= string::npos
) {
263 args
.assign(line
, space
+ 1, string::npos
);
264 line
.assign(line
, 1, space
- 1);
268 db
.add_database(Remote::open_writable(line
, args
, 0, flags
));
269 } else if (colon
!= string::npos
) {
272 unsigned int port
= atoi(line
.c_str() + colon
+ 1);
274 db
.add_database(Remote::open_writable(line
, port
, 0, 10000, flags
));
280 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
281 if (type
== "inmemory" && line
.empty()) {
282 db
.add_database(WritableDatabase(string(), DB_BACKEND_INMEMORY
));
287 if (type
== "flint") {
288 throw FeatureUnavailableError("Flint backend no longer supported");
291 // Don't include the line itself - that might help an attacker
292 // by revealing part of a sensitive file's contents if they can
293 // arrange for it to be read as a stub database via infelicities in
294 // an application which uses Xapian. The line number is enough
295 // information to identify the problem line.
296 throw DatabaseOpeningError(file
+ ':' + str(line_no
) + ": Bad line");
299 if (db
.internal
.empty()) {
300 throw DatabaseOpeningError(file
+ ": No databases listed");
304 Database::Database(const string
&path
, int flags
)
306 LOGCALL_CTOR(API
, "Database", path
|flags
);
308 int type
= flags
& DB_BACKEND_MASK_
;
310 case DB_BACKEND_CHERT
:
311 #ifdef XAPIAN_HAS_CHERT_BACKEND
312 internal
.push_back(new ChertDatabase(path
));
315 throw FeatureUnavailableError("Chert backend disabled");
317 case DB_BACKEND_GLASS
:
318 #ifdef XAPIAN_HAS_GLASS_BACKEND
319 internal
.push_back(new GlassDatabase(path
));
322 throw FeatureUnavailableError("Glass backend disabled");
324 case DB_BACKEND_STUB
:
325 open_stub(*this, path
);
327 case DB_BACKEND_INMEMORY
:
328 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
329 internal
.push_back(new InMemoryDatabase());
332 throw FeatureUnavailableError("Inmemory backend disabled");
337 if (stat(path
.c_str(), &statbuf
) == -1) {
338 throw DatabaseOpeningError("Couldn't stat '" + path
+ "'", errno
);
341 if (S_ISREG(statbuf
.st_mode
)) {
342 // Could be a stub database file, or a single file glass database.
344 if (check_if_single_file_db(statbuf
, path
, &fd
)) {
345 // Single file glass format.
346 internal
.push_back(new GlassDatabase(fd
));
350 open_stub(*this, path
);
354 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
355 throw DatabaseOpeningError("Not a regular file or directory: '" + path
+ "'");
358 #ifdef XAPIAN_HAS_CHERT_BACKEND
359 if (file_exists(path
+ "/iamchert")) {
360 internal
.push_back(new ChertDatabase(path
));
365 #ifdef XAPIAN_HAS_GLASS_BACKEND
366 if (file_exists(path
+ "/iamglass")) {
367 internal
.push_back(new GlassDatabase(path
));
372 // Check for "stub directories".
373 string stub_file
= path
;
374 stub_file
+= "/XAPIANDB";
375 if (usual(file_exists(stub_file
))) {
376 open_stub(*this, stub_file
);
380 #ifndef XAPIAN_HAS_CHERT_BACKEND
381 if (file_exists(path
+ "/iamchert")) {
382 throw FeatureUnavailableError("Chert backend disabled");
385 #ifndef XAPIAN_HAS_GLASS_BACKEND
386 if (file_exists(path
+ "/iamglass")) {
387 throw FeatureUnavailableError("Glass backend disabled");
390 if (file_exists(path
+ "/iamflint")) {
391 throw FeatureUnavailableError("Flint backend no longer supported");
394 throw DatabaseOpeningError("Couldn't detect type of database");
397 Database::Database(int fd
, int flags
)
399 LOGCALL_CTOR(API
, "Database", fd
|flags
);
401 throw InvalidArgumentError("fd < 0");
403 #ifdef XAPIAN_HAS_GLASS_BACKEND
404 int type
= flags
& DB_BACKEND_MASK_
;
406 case 0: case DB_BACKEND_GLASS
:
407 internal
.push_back(new GlassDatabase(fd
));
415 throw DatabaseOpeningError("Couldn't detect type of database");
418 #if defined XAPIAN_HAS_CHERT_BACKEND || \
419 defined XAPIAN_HAS_GLASS_BACKEND
420 #define HAVE_DISK_BACKEND
423 WritableDatabase::WritableDatabase(const std::string
&path
, int flags
, int block_size
)
426 LOGCALL_CTOR(API
, "WritableDatabase", path
|flags
|block_size
);
427 // Avoid warning if both chert and glass are disabled.
429 int type
= flags
& DB_BACKEND_MASK_
;
430 // Clear the backend bits, so we just pass on other flags to open_stub, etc.
431 flags
&= ~DB_BACKEND_MASK_
;
434 if (stat(path
.c_str(), &statbuf
) == -1) {
435 // ENOENT probably just means that we need to create the directory.
437 throw DatabaseOpeningError("Couldn't stat '" + path
+ "'", errno
);
439 // File or directory already exists.
441 if (S_ISREG(statbuf
.st_mode
)) {
442 // The path is a file, so assume it is a stub database file.
443 open_stub(*this, path
, flags
);
447 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
448 throw DatabaseOpeningError("Not a regular file or directory: '" + path
+ "'");
451 if (file_exists(path
+ "/iamchert")) {
452 // Existing chert DB.
453 #ifdef XAPIAN_HAS_CHERT_BACKEND
454 type
= DB_BACKEND_CHERT
;
456 throw FeatureUnavailableError("Chert backend disabled");
458 } else if (file_exists(path
+ "/iamglass")) {
459 // Existing glass DB.
460 #ifdef XAPIAN_HAS_GLASS_BACKEND
461 type
= DB_BACKEND_GLASS
;
463 throw FeatureUnavailableError("Glass backend disabled");
465 } else if (file_exists(path
+ "/iamflint")) {
466 // Existing flint DB.
467 throw FeatureUnavailableError("Flint backend no longer supported");
469 // Check for "stub directories".
470 string stub_file
= path
;
471 stub_file
+= "/XAPIANDB";
472 if (usual(file_exists(stub_file
))) {
473 open_stub(*this, stub_file
, flags
);
481 case DB_BACKEND_STUB
:
482 open_stub(*this, path
, flags
);
485 // Fall through to first enabled case, so order the remaining cases
487 #ifdef XAPIAN_HAS_GLASS_BACKEND
488 case DB_BACKEND_GLASS
:
489 internal
.push_back(new GlassWritableDatabase(path
, flags
, block_size
));
492 #ifdef XAPIAN_HAS_CHERT_BACKEND
493 case DB_BACKEND_CHERT
:
494 internal
.push_back(new ChertWritableDatabase(path
, flags
, block_size
));
497 case DB_BACKEND_INMEMORY
:
498 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
499 internal
.push_back(new InMemoryDatabase());
502 throw FeatureUnavailableError("Inmemory backend disabled");
505 #ifndef HAVE_DISK_BACKEND
506 throw FeatureUnavailableError("No disk-based writable backend is enabled");