xapian-core  2.0.0
dbfactory.cc
Go to the documentation of this file.
1 
4 /* Copyright 2002-2024 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, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "xapian/dbfactory.h"
25 
26 #include "xapian/constants.h"
27 #include "xapian/database.h"
28 #include "xapian/error.h"
29 #include "xapian/version.h" // For XAPIAN_HAS_XXX_BACKEND.
30 
31 #include "backends.h"
32 #include "databasehelpers.h"
33 #include "debuglog.h"
34 #include "filetests.h"
35 #include "fileutils.h"
36 #include "posixy_wrapper.h"
37 #include "str.h"
38 
39 #include <cerrno>
40 
41 #ifdef XAPIAN_HAS_GLASS_BACKEND
42 # include "glass/glass_database.h"
43 #endif
44 #include "glass/glass_defs.h"
45 #ifdef XAPIAN_HAS_HONEY_BACKEND
46 # include "honey/honey_database.h"
47 #endif
48 #include "honey/honey_defs.h"
49 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
51 #endif
52 // Even if none of the above get included, we still need a definition of
53 // Database::Internal.
55 
56 #include <string>
57 
58 using namespace std;
59 
60 namespace Xapian {
61 
62 static void
63 open_stub(Database& db, string_view file)
64 {
65  read_stub_file(file,
66  [&db](string_view path) {
67  db.add_database(Database(path));
68  },
69  [&db](string_view path) {
70 #ifdef XAPIAN_HAS_GLASS_BACKEND
71  db.add_database(Database(new GlassDatabase(path)));
72 #else
73  (void)path;
74 #endif
75  },
76  [&db](string_view path) {
77 #ifdef XAPIAN_HAS_HONEY_BACKEND
78  db.add_database(Database(new HoneyDatabase(path)));
79 #else
80  (void)path;
81 #endif
82  },
83  [&db](string_view prog, string_view args) {
84 #ifdef XAPIAN_HAS_REMOTE_BACKEND
85  db.add_database(Remote::open(prog, args));
86 #else
87  (void)prog;
88  (void)args;
89 #endif
90  },
91  [&db](string_view host, unsigned port) {
92 #ifdef XAPIAN_HAS_REMOTE_BACKEND
93  db.add_database(Remote::open(host, port));
94 #else
95  (void)host;
96  (void)port;
97 #endif
98  },
99  [&db]() {
100 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
102 #endif
103  });
104 
105  // Allowing a stub database with no databases listed allows things like
106  // a "search all databases" feature to be implemented by generating a
107  // stub database file without having to special case there not being any
108  // databases yet.
109  //
110  // 1.0.x threw DatabaseOpeningError here, but with a "Bad line" message
111  // with the line number just past the end of the file, which was a bit odd.
112 }
113 
114 static void
115 open_stub(WritableDatabase& db, string_view file, int flags)
116 {
117  read_stub_file(file,
118  [&db, flags](string_view path) {
119  db.add_database(WritableDatabase(path, flags));
120  },
121  [&db, &flags](string_view path) {
122  flags |= DB_BACKEND_GLASS;
123  db.add_database(WritableDatabase(path, flags));
124  },
125  [](string_view) {
126  auto msg = "Honey databases don't support writing";
127  throw Xapian::DatabaseOpeningError(msg);
128  },
129  [&db, flags](string_view prog, string_view args) {
130 #ifdef XAPIAN_HAS_REMOTE_BACKEND
131  db.add_database(Remote::open_writable(prog, args,
132  0, flags));
133 #else
134  (void)prog;
135  (void)args;
136 #endif
137  },
138  [&db, flags](string_view host, unsigned port) {
139 #ifdef XAPIAN_HAS_REMOTE_BACKEND
140  db.add_database(Remote::open_writable(host, port,
141  0, 10000, flags));
142 #else
143  (void)host;
144  (void)port;
145 #endif
146  },
147  [&db]() {
150  });
151 
152  if (db.internal->size() == 0) {
153  throw DatabaseOpeningError(string{file} + ": No databases listed");
154  }
155 }
156 
157 Database::Database(string_view path, int flags)
158  : Database()
159 {
160  LOGCALL_CTOR(API, "Database", path|flags);
161 
162  int type = flags & DB_BACKEND_MASK_;
163  switch (type) {
164  case DB_BACKEND_GLASS:
165 #ifdef XAPIAN_HAS_GLASS_BACKEND
166  internal = new GlassDatabase(path);
167  return;
168 #else
169  throw FeatureUnavailableError("Glass backend disabled");
170 #endif
171  case DB_BACKEND_HONEY:
172 #ifdef XAPIAN_HAS_HONEY_BACKEND
173  internal = new HoneyDatabase(path);
174  return;
175 #else
176  throw FeatureUnavailableError("Honey backend disabled");
177 #endif
178  case DB_BACKEND_STUB:
179  open_stub(*this, path);
180  return;
181  case DB_BACKEND_INMEMORY:
182 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
183  internal = new InMemoryDatabase();
184  return;
185 #else
186  throw FeatureUnavailableError("Inmemory backend disabled");
187 #endif
188  }
189 
190  string filename{path};
191  struct stat statbuf;
192  if (stat(filename.c_str(), &statbuf) == -1) {
193  if (errno == ENOENT) {
194  throw DatabaseNotFoundError("Couldn't stat '" + filename + "'",
195  errno);
196  } else {
197  throw DatabaseOpeningError("Couldn't stat '" + filename + "'",
198  errno);
199  }
200  }
201 
202  if (S_ISREG(statbuf.st_mode)) {
203  // Could be a stub database file, or a single file glass database.
204 
205  // Initialise to avoid bogus warning from GCC 4.9.2 with -Os.
206  int fd = -1;
207  switch (test_if_single_file_db(statbuf, filename, &fd)) {
208  case BACKEND_GLASS:
209 #ifdef XAPIAN_HAS_GLASS_BACKEND
210  // Single file glass format.
211  internal = new GlassDatabase(fd);
212  return;
213 #else
214  throw FeatureUnavailableError("Glass backend disabled");
215 #endif
216  case BACKEND_HONEY:
217 #ifdef XAPIAN_HAS_HONEY_BACKEND
218  // Single file honey format.
219  internal = new HoneyDatabase(fd);
220  return;
221 #else
222  throw FeatureUnavailableError("Honey backend disabled");
223 #endif
224  }
225 
226  open_stub(*this, path);
227  return;
228  }
229 
230  if (rare(!S_ISDIR(statbuf.st_mode))) {
231  throw DatabaseOpeningError("Not a regular file or directory: "
232  "'" + filename + "'");
233  }
234 
235 #ifdef XAPIAN_HAS_GLASS_BACKEND
236  filename += "/iamglass";
237  if (file_exists(filename)) {
238  internal = new GlassDatabase(path);
239  return;
240  }
241 #endif
242 
243 #ifdef XAPIAN_HAS_HONEY_BACKEND
244  filename.resize(path.size());
245  filename += "/iamhoney";
246  if (file_exists(filename)) {
247  internal = new HoneyDatabase(path);
248  return;
249  }
250 #endif
251 
252  // Check for "stub directories".
253  filename.resize(path.size());
254  filename += "/XAPIANDB";
255  if (usual(file_exists(filename))) {
256  open_stub(*this, filename);
257  return;
258  }
259 
260 #ifndef XAPIAN_HAS_GLASS_BACKEND
261  filename.resize(path.size());
262  filename += "/iamglass";
263  if (file_exists(filename)) {
264  throw FeatureUnavailableError("Glass backend disabled");
265  }
266 #endif
267 #ifndef XAPIAN_HAS_HONEY_BACKEND
268  filename.resize(path.size());
269  filename += "/iamhoney";
270  if (file_exists(filename)) {
271  throw FeatureUnavailableError("Honey backend disabled");
272  }
273 #endif
274  filename.resize(path.size());
275  filename += "/iamchert";
276  if (file_exists(filename)) {
277  throw FeatureUnavailableError("Chert backend no longer supported");
278  }
279  filename.resize(path.size());
280  filename += "/iamflint";
281  if (file_exists(filename)) {
282  throw FeatureUnavailableError("Flint backend no longer supported");
283  }
284 
285  throw DatabaseNotFoundError("Couldn't detect type of database");
286 }
287 
295 static Database::Internal*
296 database_factory(int fd, int flags)
297 {
298  if (rare(fd < 0))
299  throw InvalidArgumentError("fd < 0", EBADF);
300 
301 #if defined XAPIAN_HAS_GLASS_BACKEND || defined XAPIAN_HAS_HONEY_BACKEND
302  int type = flags & DB_BACKEND_MASK_;
303  if (type == 0) {
304  switch (test_if_single_file_db(fd)) {
305  case BACKEND_GLASS:
306  type = DB_BACKEND_GLASS;
307  break;
308  case BACKEND_HONEY:
309  type = DB_BACKEND_HONEY;
310  break;
311  }
312  }
313  switch (type) {
314 #ifdef XAPIAN_HAS_GLASS_BACKEND
315  case DB_BACKEND_GLASS:
316  return new GlassDatabase(fd);
317 #endif
318 #ifdef XAPIAN_HAS_HONEY_BACKEND
319  case DB_BACKEND_HONEY:
320  return new HoneyDatabase(fd);
321 #endif
322  }
323 #endif
324 
325  (void)::close(fd);
326  throw DatabaseOpeningError("Couldn't detect type of database fd");
327 }
328 
329 Database::Database(int fd, int flags)
330  : internal(database_factory(fd, flags))
331 {
332  LOGCALL_CTOR(API, "Database", fd|flags);
333 }
334 
335 #if defined XAPIAN_HAS_GLASS_BACKEND
336 #define HAVE_DISK_BACKEND
337 #endif
338 
340  int flags,
341  int block_size)
342  : Database()
343 {
344  LOGCALL_CTOR(API, "WritableDatabase", path|flags|block_size);
345  // Avoid warning if all disk-based backends are disabled.
346  (void)block_size;
347  string filename{path};
348  int type = flags & DB_BACKEND_MASK_;
349  // Clear the backend bits, so we just pass on other flags to open_stub, etc.
350  flags &= ~DB_BACKEND_MASK_;
351  if (type == 0) {
352  struct stat statbuf;
353  if (stat(filename.c_str(), &statbuf) == -1) {
354  // ENOENT probably just means that we need to create the directory.
355  if (errno != ENOENT)
356  throw DatabaseOpeningError("Couldn't stat '" + filename + "'",
357  errno);
358  } else {
359  // File or directory already exists.
360 
361  if (S_ISREG(statbuf.st_mode)) {
362  // The path is a file, so assume it is a stub database file.
363  open_stub(*this, path, flags);
364  return;
365  }
366 
367  if (rare(!S_ISDIR(statbuf.st_mode))) {
368  throw DatabaseOpeningError("Not a regular file or directory: "
369  "'" + filename + "'");
370  }
371 
372  filename += "/iamglass";
373  if (file_exists(filename)) {
374  // Existing glass DB.
375 #ifdef XAPIAN_HAS_GLASS_BACKEND
376  type = DB_BACKEND_GLASS;
377 #else
378  throw FeatureUnavailableError("Glass backend disabled");
379 #endif
380  }
381 
382  filename.resize(path.size());
383  filename += "/iamhoney";
384  if (file_exists(filename)) {
385  // Existing honey DB.
386  throw InvalidOperationError("Honey backend doesn't support "
387  "updating existing databases");
388  }
389 
390  filename.resize(path.size());
391  filename += "/iamchert";
392  if (file_exists(filename)) {
393  // Existing chert DB.
394  throw FeatureUnavailableError("Chert backend no longer supported");
395  }
396 
397  filename.resize(path.size());
398  filename += "/iamflint";
399  if (file_exists(filename)) {
400  // Existing flint DB.
401  throw FeatureUnavailableError("Flint backend no longer supported");
402  }
403 
404  // Check for "stub directories".
405  filename.resize(path.size());
406  filename += "/XAPIANDB";
407  if (usual(file_exists(filename))) {
408  open_stub(*this, filename, flags);
409  return;
410  }
411  }
412  }
413 
414  switch (type) {
415  case DB_BACKEND_STUB:
416  open_stub(*this, path, flags);
417  return;
418  case 0:
419  // Fall through to first enabled case, so order the remaining cases
420  // by preference.
421 #ifdef XAPIAN_HAS_GLASS_BACKEND
422  case DB_BACKEND_GLASS:
423  internal = new GlassWritableDatabase(path, flags, block_size);
424  return;
425 #endif
426  case DB_BACKEND_HONEY:
427  throw InvalidArgumentError("Honey backend doesn't support "
428  "updating existing databases");
429  case DB_BACKEND_INMEMORY:
430 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
431  internal = new InMemoryDatabase();
432  return;
433 #else
434  throw FeatureUnavailableError("Inmemory backend disabled");
435 #endif
436  }
437 #ifndef HAVE_DISK_BACKEND
438  throw FeatureUnavailableError("No disk-based writable backend is enabled");
439 #endif
440 }
441 
442 }
BACKEND_* constants.
@ BACKEND_GLASS
Definition: backends.h:29
@ BACKEND_HONEY
Definition: backends.h:30
A backend designed for efficient indexing and retrieval, using compressed posting lists and a btree s...
A writable glass database.
Database using honey backend.
A database held entirely in memory.
Indicates an attempt to access a database not present.
Definition: error.h:1043
DatabaseOpeningError indicates failure to open a database.
Definition: error.h:569
Virtual base class for Database internals.
An indexed database of documents.
Definition: database.h:75
Database()
Construct a Database containing no shards.
Definition: database.cc:83
void add_database(const Database &other)
Add shards from another Database.
Definition: database.h:109
Xapian::Internal::intrusive_ptr_nonnull< Internal > internal
Definition: database.h:95
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:707
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:229
InvalidOperationError indicates the API was used in an invalid way.
Definition: error.h:271
This class provides read/write access to a database.
Definition: database.h:964
void add_database(const WritableDatabase &other)
Add shards from another WritableDatabase.
Definition: database.h:990
WritableDatabase()
Create a WritableDatabase with no subdatabases.
Definition: database.h:978
#define usual(COND)
Definition: config.h:608
#define rare(COND)
Definition: config.h:607
Constants in the Xapian namespace.
An indexed database of documents.
int test_if_single_file_db(int fd)
Probe if a file descriptor is a single-file database.
Helper functions for database handling.
void read_stub_file(std::string_view file, A1 action_auto, A2 action_glass, A3 action_honey, A4 action_remote_prog, A5 action_remote_tcp, A6 action_inmemory)
Open, read and process a stub database file.
Virtual base class for Database internals.
Factory functions for constructing Database and WritableDatabase objects.
Debug logging macros.
#define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS)
Definition: debuglog.h:480
Hierarchy of classes which Xapian can throw as exceptions.
int close(FD &fd)
Definition: fd.h:63
Utility functions for testing files.
bool file_exists(const char *path)
Test if a file exists.
Definition: filetests.h:40
File and path manipulation routines.
C++ class definition for glass database.
Definitions, types, etc for use inside glass.
Database using honey backend.
Definitions, types, etc for use inside honey.
C++ class definition for inmemory database access.
WritableDatabase open_writable(std::string_view host, unsigned int port, unsigned timeout=0, unsigned connect_timeout=10000, int flags=0)
Construct a WritableDatabase object for update access to a remote database accessed via a TCP connect...
Database open(std::string_view host, unsigned int port, unsigned timeout=10000, unsigned connect_timeout=10000)
Construct a Database object for read-only access to a remote database accessed via a TCP connection.
The Xapian namespace contains public interfaces for the Xapian library.
Definition: compactor.cc:82
const int DB_BACKEND_INMEMORY
Use the "in memory" backend.
Definition: constants.h:182
static Database::Internal * database_factory(int fd, int flags)
Helper factory function.
Definition: dbfactory.cc:296
static void open_stub(WritableDatabase &db, string_view file, int flags)
Definition: dbfactory.cc:115
const int DB_BACKEND_STUB
Open a stub database file.
Definition: constants.h:166
const int DB_BACKEND_HONEY
Use the honey backend.
Definition: constants.h:197
const int DB_BACKEND_GLASS
Use the glass backend.
Definition: constants.h:157
static void open_stub(Database &db, string_view file)
Definition: dbfactory.cc:63
Provides wrappers with POSIXy semantics.
#define S_ISREG(ST_MODE)
Definition: safesysstat.h:59
#define S_ISDIR(ST_MODE)
Definition: safesysstat.h:56
Convert types to std::string.
Define preprocessor symbols for the library version.