2 * @brief Database factories for non-remote databases.
4 /* Copyright 2002,2003,2004,2005,2006,2007,2008,2009,2011,2012,2013,2014,2015,2016,2017,2019 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 "databasehelpers.h"
35 #include "filetests.h"
36 #include "fileutils.h"
37 #include "posixy_wrapper.h"
42 #ifdef XAPIAN_HAS_GLASS_BACKEND
43 # include "glass/glass_database.h"
45 #ifdef XAPIAN_HAS_CHERT_BACKEND
46 # include "chert/chert_database.h"
48 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
49 # include "inmemory/inmemory_database.h"
51 // Even if none of the above get included, we still need a definition of
52 // Database::Internal.
53 #include "backends/database.h"
62 open_stub(Database
& db
, const string
& file
)
65 [&db
](const string
& path
) {
66 db
.add_database(Database(path
));
68 [&db
](const string
& path
) {
69 #ifdef XAPIAN_HAS_CHERT_BACKEND
70 db
.add_database(Database(new ChertDatabase(path
)));
75 [&db
](const string
& path
) {
76 #ifdef XAPIAN_HAS_GLASS_BACKEND
77 db
.add_database(Database(new GlassDatabase(path
)));
82 [&db
](const string
& prog
, const string
& args
) {
83 #ifdef XAPIAN_HAS_REMOTE_BACKEND
84 db
.add_database(Remote::open(prog
, args
));
90 [&db
](const string
& host
, unsigned port
) {
91 #ifdef XAPIAN_HAS_REMOTE_BACKEND
92 db
.add_database(Remote::open(host
, port
));
99 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
100 db
.add_database(Database(string(), DB_BACKEND_INMEMORY
));
104 // Allowing a stub database with no databases listed allows things like
105 // a "search all databases" feature to be implemented by generating a
106 // stub database file without having to special case there not being any
109 // 1.0.x threw DatabaseOpeningError here, but with a "Bad line" message
110 // with the line number just past the end of the file, which was a bit odd.
114 open_stub(WritableDatabase
& db
, const string
& file
, int flags
)
117 [&db
, flags
](const string
& path
) {
118 db
.add_database(WritableDatabase(path
, flags
));
120 [&db
, &flags
](const string
& path
) {
121 flags
|= DB_BACKEND_CHERT
;
122 db
.add_database(WritableDatabase(path
, flags
));
124 [&db
, &flags
](const string
& path
) {
125 flags
|= DB_BACKEND_GLASS
;
126 db
.add_database(WritableDatabase(path
, flags
));
128 [&db
, flags
](const string
& prog
, const string
& args
) {
129 #ifdef XAPIAN_HAS_REMOTE_BACKEND
130 db
.add_database(Remote::open_writable(prog
, args
,
137 [&db
, flags
](const string
& host
, unsigned port
) {
138 #ifdef XAPIAN_HAS_REMOTE_BACKEND
139 db
.add_database(Remote::open_writable(host
, port
,
147 db
.add_database(WritableDatabase(string(),
148 DB_BACKEND_INMEMORY
));
151 if (db
.internal
.empty()) {
152 throw DatabaseOpeningError(file
+ ": No databases listed");
156 Database::Database(const string
&path
, int flags
)
158 LOGCALL_CTOR(API
, "Database", path
|flags
);
160 int type
= flags
& DB_BACKEND_MASK_
;
162 case DB_BACKEND_CHERT
:
163 #ifdef XAPIAN_HAS_CHERT_BACKEND
164 internal
.push_back(new ChertDatabase(path
));
167 throw FeatureUnavailableError("Chert backend disabled");
169 case DB_BACKEND_GLASS
:
170 #ifdef XAPIAN_HAS_GLASS_BACKEND
171 internal
.push_back(new GlassDatabase(path
));
174 throw FeatureUnavailableError("Glass backend disabled");
176 case DB_BACKEND_STUB
:
177 open_stub(*this, path
);
179 case DB_BACKEND_INMEMORY
:
180 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
181 internal
.push_back(new InMemoryDatabase());
184 throw FeatureUnavailableError("Inmemory backend disabled");
189 if (stat(path
.c_str(), &statbuf
) == -1) {
190 if (errno
== ENOENT
) {
191 throw DatabaseNotFoundError("Couldn't stat '" + path
+ "'", errno
);
193 throw DatabaseOpeningError("Couldn't stat '" + path
+ "'", errno
);
197 if (S_ISREG(statbuf
.st_mode
)) {
198 // Could be a stub database file, or a single file glass database.
200 // Initialise to avoid bogus warning from GCC 4.9.2 with -Os.
202 switch (test_if_single_file_db(statbuf
, path
, &fd
)) {
204 #ifdef XAPIAN_HAS_GLASS_BACKEND
205 // Single file glass format.
206 internal
.push_back(new GlassDatabase(fd
));
209 throw FeatureUnavailableError("Glass backend disabled");
213 open_stub(*this, path
);
217 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
218 throw DatabaseOpeningError("Not a regular file or directory: '" + path
+ "'");
221 #ifdef XAPIAN_HAS_CHERT_BACKEND
222 if (file_exists(path
+ "/iamchert")) {
223 internal
.push_back(new ChertDatabase(path
));
228 #ifdef XAPIAN_HAS_GLASS_BACKEND
229 if (file_exists(path
+ "/iamglass")) {
230 internal
.push_back(new GlassDatabase(path
));
235 // Check for "stub directories".
236 string stub_file
= path
;
237 stub_file
+= "/XAPIANDB";
238 if (usual(file_exists(stub_file
))) {
239 open_stub(*this, stub_file
);
243 #ifndef XAPIAN_HAS_CHERT_BACKEND
244 if (file_exists(path
+ "/iamchert")) {
245 throw FeatureUnavailableError("Chert backend disabled");
248 #ifndef XAPIAN_HAS_GLASS_BACKEND
249 if (file_exists(path
+ "/iamglass")) {
250 throw FeatureUnavailableError("Glass backend disabled");
253 if (file_exists(path
+ "/iamflint")) {
254 throw FeatureUnavailableError("Flint backend no longer supported");
257 throw DatabaseNotFoundError("Couldn't detect type of database");
260 Database::Database(int fd
, int flags
)
262 LOGCALL_CTOR(API
, "Database", fd
|flags
);
264 throw InvalidArgumentError("fd < 0");
266 #ifdef XAPIAN_HAS_GLASS_BACKEND
267 int type
= flags
& DB_BACKEND_MASK_
;
269 case 0: case DB_BACKEND_GLASS
:
270 internal
.push_back(new GlassDatabase(fd
));
278 throw DatabaseOpeningError("Couldn't detect type of database");
281 #if defined XAPIAN_HAS_CHERT_BACKEND || \
282 defined XAPIAN_HAS_GLASS_BACKEND
283 #define HAVE_DISK_BACKEND
286 WritableDatabase::WritableDatabase(const std::string
&path
, int flags
, int block_size
)
289 LOGCALL_CTOR(API
, "WritableDatabase", path
|flags
|block_size
);
290 // Avoid warning if both chert and glass are disabled.
292 int type
= flags
& DB_BACKEND_MASK_
;
293 // Clear the backend bits, so we just pass on other flags to open_stub, etc.
294 flags
&= ~DB_BACKEND_MASK_
;
297 if (stat(path
.c_str(), &statbuf
) == -1) {
298 // ENOENT probably just means that we need to create the directory.
300 throw DatabaseOpeningError("Couldn't stat '" + path
+ "'", errno
);
302 // File or directory already exists.
304 if (S_ISREG(statbuf
.st_mode
)) {
305 // The path is a file, so assume it is a stub database file.
306 open_stub(*this, path
, flags
);
310 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
311 throw DatabaseOpeningError("Not a regular file or directory: '" + path
+ "'");
314 if (file_exists(path
+ "/iamchert")) {
315 // Existing chert DB.
316 #ifdef XAPIAN_HAS_CHERT_BACKEND
317 type
= DB_BACKEND_CHERT
;
319 throw FeatureUnavailableError("Chert backend disabled");
321 } else if (file_exists(path
+ "/iamglass")) {
322 // Existing glass DB.
323 #ifdef XAPIAN_HAS_GLASS_BACKEND
324 type
= DB_BACKEND_GLASS
;
326 throw FeatureUnavailableError("Glass backend disabled");
328 } else if (file_exists(path
+ "/iamflint")) {
329 // Existing flint DB.
330 throw FeatureUnavailableError("Flint backend no longer supported");
332 // Check for "stub directories".
333 string stub_file
= path
;
334 stub_file
+= "/XAPIANDB";
335 if (usual(file_exists(stub_file
))) {
336 open_stub(*this, stub_file
, flags
);
344 case DB_BACKEND_STUB
:
345 open_stub(*this, path
, flags
);
348 // Fall through to first enabled case, so order the remaining cases
350 #ifdef XAPIAN_HAS_GLASS_BACKEND
351 case DB_BACKEND_GLASS
:
352 internal
.push_back(new GlassWritableDatabase(path
, flags
, block_size
));
355 #ifdef XAPIAN_HAS_CHERT_BACKEND
356 case DB_BACKEND_CHERT
:
357 internal
.push_back(new ChertWritableDatabase(path
, flags
, block_size
));
360 case DB_BACKEND_INMEMORY
:
361 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
362 internal
.push_back(new InMemoryDatabase());
365 throw FeatureUnavailableError("Inmemory backend disabled");
368 #ifndef HAVE_DISK_BACKEND
369 throw FeatureUnavailableError("No disk-based writable backend is enabled");