2 * @brief Database factories for non-remote databases.
4 /* Copyright 2002-2024 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 #include "glass/glass_defs.h"
46 #ifdef XAPIAN_HAS_HONEY_BACKEND
47 # include "honey/honey_database.h"
49 #include "honey/honey_defs.h"
50 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
51 # include "inmemory/inmemory_database.h"
53 // Even if none of the above get included, we still need a definition of
54 // Database::Internal.
55 #include "backends/databaseinternal.h"
64 open_stub(Database
& db
, string_view file
)
67 [&db
](string_view path
) {
68 db
.add_database(Database(path
));
70 [&db
](string_view path
) {
71 #ifdef XAPIAN_HAS_GLASS_BACKEND
72 db
.add_database(Database(new GlassDatabase(path
)));
77 [&db
](string_view path
) {
78 #ifdef XAPIAN_HAS_HONEY_BACKEND
79 db
.add_database(Database(new HoneyDatabase(path
)));
84 [&db
](string_view prog
, string_view args
) {
85 #ifdef XAPIAN_HAS_REMOTE_BACKEND
86 db
.add_database(Remote::open(prog
, args
));
92 [&db
](string_view host
, unsigned port
) {
93 #ifdef XAPIAN_HAS_REMOTE_BACKEND
94 db
.add_database(Remote::open(host
, port
));
101 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
102 db
.add_database(Database(""sv
, DB_BACKEND_INMEMORY
));
106 // Allowing a stub database with no databases listed allows things like
107 // a "search all databases" feature to be implemented by generating a
108 // stub database file without having to special case there not being any
111 // 1.0.x threw DatabaseOpeningError here, but with a "Bad line" message
112 // with the line number just past the end of the file, which was a bit odd.
116 open_stub(WritableDatabase
& db
, string_view file
, int flags
)
119 [&db
, flags
](string_view path
) {
120 db
.add_database(WritableDatabase(path
, flags
));
122 [&db
, &flags
](string_view path
) {
123 flags
|= DB_BACKEND_GLASS
;
124 db
.add_database(WritableDatabase(path
, flags
));
127 auto msg
= "Honey databases don't support writing";
128 throw Xapian::DatabaseOpeningError(msg
);
130 [&db
, flags
](string_view prog
, string_view args
) {
131 #ifdef XAPIAN_HAS_REMOTE_BACKEND
132 db
.add_database(Remote::open_writable(prog
, args
,
139 [&db
, flags
](string_view host
, unsigned port
) {
140 #ifdef XAPIAN_HAS_REMOTE_BACKEND
141 db
.add_database(Remote::open_writable(host
, port
,
149 db
.add_database(WritableDatabase(""sv
,
150 DB_BACKEND_INMEMORY
));
153 if (db
.internal
->size() == 0) {
154 throw DatabaseOpeningError(string
{file
} + ": No databases listed");
158 Database::Database(string_view path
, int flags
)
161 LOGCALL_CTOR(API
, "Database", path
|flags
);
163 int type
= flags
& DB_BACKEND_MASK_
;
165 case DB_BACKEND_CHERT
:
166 throw FeatureUnavailableError("Chert backend no longer supported");
167 case DB_BACKEND_GLASS
:
168 #ifdef XAPIAN_HAS_GLASS_BACKEND
169 internal
= new GlassDatabase(path
);
172 throw FeatureUnavailableError("Glass backend disabled");
174 case DB_BACKEND_HONEY
:
175 #ifdef XAPIAN_HAS_HONEY_BACKEND
176 internal
= new HoneyDatabase(path
);
179 throw FeatureUnavailableError("Honey backend disabled");
181 case DB_BACKEND_STUB
:
182 open_stub(*this, path
);
184 case DB_BACKEND_INMEMORY
:
185 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
186 internal
= new InMemoryDatabase();
189 throw FeatureUnavailableError("Inmemory backend disabled");
193 string filename
{path
};
195 if (stat(filename
.c_str(), &statbuf
) == -1) {
196 if (errno
== ENOENT
) {
197 throw DatabaseNotFoundError("Couldn't stat '" + filename
+ "'",
200 throw DatabaseOpeningError("Couldn't stat '" + filename
+ "'",
205 if (S_ISREG(statbuf
.st_mode
)) {
206 // Could be a stub database file, or a single file glass database.
208 // Initialise to avoid bogus warning from GCC 4.9.2 with -Os.
210 switch (test_if_single_file_db(statbuf
, filename
, &fd
)) {
212 #ifdef XAPIAN_HAS_GLASS_BACKEND
213 // Single file glass format.
214 internal
= new GlassDatabase(fd
);
217 throw FeatureUnavailableError("Glass backend disabled");
220 #ifdef XAPIAN_HAS_HONEY_BACKEND
221 // Single file honey format.
222 internal
= new HoneyDatabase(fd
);
225 throw FeatureUnavailableError("Honey backend disabled");
229 open_stub(*this, path
);
233 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
234 throw DatabaseOpeningError("Not a regular file or directory: "
235 "'" + filename
+ "'");
238 #ifdef XAPIAN_HAS_GLASS_BACKEND
239 filename
+= "/iamglass";
240 if (file_exists(filename
)) {
241 internal
= new GlassDatabase(path
);
246 #ifdef XAPIAN_HAS_HONEY_BACKEND
247 filename
.resize(path
.size());
248 filename
+= "/iamhoney";
249 if (file_exists(filename
)) {
250 internal
= new HoneyDatabase(path
);
255 // Check for "stub directories".
256 filename
.resize(path
.size());
257 filename
+= "/XAPIANDB";
258 if (usual(file_exists(filename
))) {
259 open_stub(*this, filename
);
263 #ifndef XAPIAN_HAS_GLASS_BACKEND
264 filename
.resize(path
.size());
265 filename
+= "/iamglass";
266 if (file_exists(filename
)) {
267 throw FeatureUnavailableError("Glass backend disabled");
270 #ifndef XAPIAN_HAS_HONEY_BACKEND
271 filename
.resize(path
.size());
272 filename
+= "/iamhoney";
273 if (file_exists(filename
)) {
274 throw FeatureUnavailableError("Honey backend disabled");
277 filename
.resize(path
.size());
278 filename
+= "/iamchert";
279 if (file_exists(filename
)) {
280 throw FeatureUnavailableError("Chert backend no longer supported");
282 filename
.resize(path
.size());
283 filename
+= "/iamflint";
284 if (file_exists(filename
)) {
285 throw FeatureUnavailableError("Flint backend no longer supported");
288 throw DatabaseNotFoundError("Couldn't detect type of database");
291 /** Helper factory function.
293 * This allows us to initialise Database::internal via the constructor's
294 * initialiser list, which we want to be able to do as Database::internal
295 * is an intrusive_ptr_nonnull, so we can't set it to NULL in the initialiser
296 * list and then fill it in later in the constructor body.
298 static Database::Internal
*
299 database_factory(int fd
, int flags
)
302 throw InvalidArgumentError("fd < 0", EBADF
);
304 #if defined XAPIAN_HAS_GLASS_BACKEND || defined XAPIAN_HAS_HONEY_BACKEND
305 int type
= flags
& DB_BACKEND_MASK_
;
307 switch (test_if_single_file_db(fd
)) {
309 type
= DB_BACKEND_GLASS
;
312 type
= DB_BACKEND_HONEY
;
317 #ifdef XAPIAN_HAS_GLASS_BACKEND
318 case DB_BACKEND_GLASS
:
319 return new GlassDatabase(fd
);
321 #ifdef XAPIAN_HAS_HONEY_BACKEND
322 case DB_BACKEND_HONEY
:
323 return new HoneyDatabase(fd
);
329 throw DatabaseOpeningError("Couldn't detect type of database fd");
332 Database::Database(int fd
, int flags
)
333 : internal(database_factory(fd
, flags
))
335 LOGCALL_CTOR(API
, "Database", fd
|flags
);
338 #if defined XAPIAN_HAS_GLASS_BACKEND
339 #define HAVE_DISK_BACKEND
342 WritableDatabase::WritableDatabase(std::string_view path
,
347 LOGCALL_CTOR(API
, "WritableDatabase", path
|flags
|block_size
);
348 // Avoid warning if all disk-based backends are disabled.
350 string filename
{path
};
351 int type
= flags
& DB_BACKEND_MASK_
;
352 // Clear the backend bits, so we just pass on other flags to open_stub, etc.
353 flags
&= ~DB_BACKEND_MASK_
;
356 if (stat(filename
.c_str(), &statbuf
) == -1) {
357 // ENOENT probably just means that we need to create the directory.
359 throw DatabaseOpeningError("Couldn't stat '" + filename
+ "'",
362 // File or directory already exists.
364 if (S_ISREG(statbuf
.st_mode
)) {
365 // The path is a file, so assume it is a stub database file.
366 open_stub(*this, path
, flags
);
370 if (rare(!S_ISDIR(statbuf
.st_mode
))) {
371 throw DatabaseOpeningError("Not a regular file or directory: "
372 "'" + filename
+ "'");
375 filename
+= "/iamglass";
376 if (file_exists(filename
)) {
377 // Existing glass DB.
378 #ifdef XAPIAN_HAS_GLASS_BACKEND
379 type
= DB_BACKEND_GLASS
;
381 throw FeatureUnavailableError("Glass backend disabled");
385 filename
.resize(path
.size());
386 filename
+= "/iamhoney";
387 if (file_exists(filename
)) {
388 // Existing honey DB.
389 throw InvalidOperationError("Honey backend doesn't support "
390 "updating existing databases");
393 filename
.resize(path
.size());
394 filename
+= "/iamchert";
395 if (file_exists(filename
)) {
396 // Existing chert DB.
397 throw FeatureUnavailableError("Chert backend no longer supported");
400 filename
.resize(path
.size());
401 filename
+= "/iamflint";
402 if (file_exists(filename
)) {
403 // Existing flint DB.
404 throw FeatureUnavailableError("Flint backend no longer supported");
407 // Check for "stub directories".
408 filename
.resize(path
.size());
409 filename
+= "/XAPIANDB";
410 if (usual(file_exists(filename
))) {
411 open_stub(*this, filename
, flags
);
418 case DB_BACKEND_STUB
:
419 open_stub(*this, path
, flags
);
422 // Fall through to first enabled case, so order the remaining cases
424 #ifdef XAPIAN_HAS_GLASS_BACKEND
425 case DB_BACKEND_GLASS
:
426 internal
= new GlassWritableDatabase(path
, flags
, block_size
);
429 case DB_BACKEND_HONEY
:
430 throw InvalidArgumentError("Honey backend doesn't support "
431 "updating existing databases");
432 case DB_BACKEND_CHERT
:
433 throw FeatureUnavailableError("Chert backend no longer supported");
434 case DB_BACKEND_INMEMORY
:
435 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
436 internal
= new InMemoryDatabase();
439 throw FeatureUnavailableError("Inmemory backend disabled");
442 #ifndef HAVE_DISK_BACKEND
443 throw FeatureUnavailableError("No disk-based writable backend is enabled");