[ci] Fix clang-santisers job for GHA change
[xapian.git] / xapian-core / backends / dbfactory.cc
blobd83544d76361d5673ed3f63d36dd03cfdf74c985
1 /** @file
2 * @brief Database factories for non-remote databases.
3 */
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
20 * USA
23 #include <config.h>
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.
32 #include "backends.h"
33 #include "databasehelpers.h"
34 #include "debuglog.h"
35 #include "filetests.h"
36 #include "fileutils.h"
37 #include "posixy_wrapper.h"
38 #include "str.h"
40 #include <cerrno>
42 #ifdef XAPIAN_HAS_GLASS_BACKEND
43 # include "glass/glass_database.h"
44 #endif
45 #include "glass/glass_defs.h"
46 #ifdef XAPIAN_HAS_HONEY_BACKEND
47 # include "honey/honey_database.h"
48 #endif
49 #include "honey/honey_defs.h"
50 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
51 # include "inmemory/inmemory_database.h"
52 #endif
53 // Even if none of the above get included, we still need a definition of
54 // Database::Internal.
55 #include "backends/databaseinternal.h"
57 #include <string>
59 using namespace std;
61 namespace Xapian {
63 static void
64 open_stub(Database& db, string_view file)
66 read_stub_file(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)));
73 #else
74 (void)path;
75 #endif
77 [&db](string_view path) {
78 #ifdef XAPIAN_HAS_HONEY_BACKEND
79 db.add_database(Database(new HoneyDatabase(path)));
80 #else
81 (void)path;
82 #endif
84 [&db](string_view prog, string_view args) {
85 #ifdef XAPIAN_HAS_REMOTE_BACKEND
86 db.add_database(Remote::open(prog, args));
87 #else
88 (void)prog;
89 (void)args;
90 #endif
92 [&db](string_view host, unsigned port) {
93 #ifdef XAPIAN_HAS_REMOTE_BACKEND
94 db.add_database(Remote::open(host, port));
95 #else
96 (void)host;
97 (void)port;
98 #endif
100 [&db]() {
101 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
102 db.add_database(Database(""sv, DB_BACKEND_INMEMORY));
103 #endif
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
109 // databases yet.
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.
115 static void
116 open_stub(WritableDatabase& db, string_view file, int flags)
118 read_stub_file(file,
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));
126 [](string_view) {
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,
133 0, flags));
134 #else
135 (void)prog;
136 (void)args;
137 #endif
139 [&db, flags](string_view host, unsigned port) {
140 #ifdef XAPIAN_HAS_REMOTE_BACKEND
141 db.add_database(Remote::open_writable(host, port,
142 0, 10000, flags));
143 #else
144 (void)host;
145 (void)port;
146 #endif
148 [&db]() {
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)
159 : Database()
161 LOGCALL_CTOR(API, "Database", path|flags);
163 int type = flags & DB_BACKEND_MASK_;
164 switch (type) {
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);
170 return;
171 #else
172 throw FeatureUnavailableError("Glass backend disabled");
173 #endif
174 case DB_BACKEND_HONEY:
175 #ifdef XAPIAN_HAS_HONEY_BACKEND
176 internal = new HoneyDatabase(path);
177 return;
178 #else
179 throw FeatureUnavailableError("Honey backend disabled");
180 #endif
181 case DB_BACKEND_STUB:
182 open_stub(*this, path);
183 return;
184 case DB_BACKEND_INMEMORY:
185 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
186 internal = new InMemoryDatabase();
187 return;
188 #else
189 throw FeatureUnavailableError("Inmemory backend disabled");
190 #endif
193 string filename{path};
194 struct stat statbuf;
195 if (stat(filename.c_str(), &statbuf) == -1) {
196 if (errno == ENOENT) {
197 throw DatabaseNotFoundError("Couldn't stat '" + filename + "'",
198 errno);
199 } else {
200 throw DatabaseOpeningError("Couldn't stat '" + filename + "'",
201 errno);
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.
209 int fd = -1;
210 switch (test_if_single_file_db(statbuf, filename, &fd)) {
211 case BACKEND_GLASS:
212 #ifdef XAPIAN_HAS_GLASS_BACKEND
213 // Single file glass format.
214 internal = new GlassDatabase(fd);
215 return;
216 #else
217 throw FeatureUnavailableError("Glass backend disabled");
218 #endif
219 case BACKEND_HONEY:
220 #ifdef XAPIAN_HAS_HONEY_BACKEND
221 // Single file honey format.
222 internal = new HoneyDatabase(fd);
223 return;
224 #else
225 throw FeatureUnavailableError("Honey backend disabled");
226 #endif
229 open_stub(*this, path);
230 return;
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);
242 return;
244 #endif
246 #ifdef XAPIAN_HAS_HONEY_BACKEND
247 filename.resize(path.size());
248 filename += "/iamhoney";
249 if (file_exists(filename)) {
250 internal = new HoneyDatabase(path);
251 return;
253 #endif
255 // Check for "stub directories".
256 filename.resize(path.size());
257 filename += "/XAPIANDB";
258 if (usual(file_exists(filename))) {
259 open_stub(*this, filename);
260 return;
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");
269 #endif
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");
276 #endif
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)
301 if (rare(fd < 0))
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_;
306 if (type == 0) {
307 switch (test_if_single_file_db(fd)) {
308 case BACKEND_GLASS:
309 type = DB_BACKEND_GLASS;
310 break;
311 case BACKEND_HONEY:
312 type = DB_BACKEND_HONEY;
313 break;
316 switch (type) {
317 #ifdef XAPIAN_HAS_GLASS_BACKEND
318 case DB_BACKEND_GLASS:
319 return new GlassDatabase(fd);
320 #endif
321 #ifdef XAPIAN_HAS_HONEY_BACKEND
322 case DB_BACKEND_HONEY:
323 return new HoneyDatabase(fd);
324 #endif
326 #endif
328 (void)::close(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
340 #endif
342 WritableDatabase::WritableDatabase(std::string_view path,
343 int flags,
344 int block_size)
345 : Database()
347 LOGCALL_CTOR(API, "WritableDatabase", path|flags|block_size);
348 // Avoid warning if all disk-based backends are disabled.
349 (void)block_size;
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_;
354 if (type == 0) {
355 struct stat statbuf;
356 if (stat(filename.c_str(), &statbuf) == -1) {
357 // ENOENT probably just means that we need to create the directory.
358 if (errno != ENOENT)
359 throw DatabaseOpeningError("Couldn't stat '" + filename + "'",
360 errno);
361 } else {
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);
367 return;
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;
380 #else
381 throw FeatureUnavailableError("Glass backend disabled");
382 #endif
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);
412 return;
417 switch (type) {
418 case DB_BACKEND_STUB:
419 open_stub(*this, path, flags);
420 return;
421 case 0:
422 // Fall through to first enabled case, so order the remaining cases
423 // by preference.
424 #ifdef XAPIAN_HAS_GLASS_BACKEND
425 case DB_BACKEND_GLASS:
426 internal = new GlassWritableDatabase(path, flags, block_size);
427 return;
428 #endif
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();
437 return;
438 #else
439 throw FeatureUnavailableError("Inmemory backend disabled");
440 #endif
442 #ifndef HAVE_DISK_BACKEND
443 throw FeatureUnavailableError("No disk-based writable backend is enabled");
444 #endif