db.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2021 The Limenka developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <chainparams.h>
   7  #include <common/args.h>
   8  #include <logging.h>
   9  #include <util/fs.h>
  10  #include <util/fs_helpers.h>
  11  #include <wallet/db.h>
  12  
  13  #include <algorithm>
  14  #include <exception>
  15  #include <fstream>
  16  #include <set>
  17  #include <string>
  18  #include <system_error>
  19  #include <vector>
  20  
  21  namespace wallet {
  22  bool operator<(BytePrefix a, Span<const std::byte> b) { return std::ranges::lexicographical_compare(a.prefix, b.subspan(0, std::min(a.prefix.size(), b.size()))); }
  23  bool operator<(Span<const std::byte> a, BytePrefix b) { return std::ranges::lexicographical_compare(a.subspan(0, std::min(a.size(), b.prefix.size())), b.prefix); }
  24  
  25  std::vector<std::pair<fs::path, std::string>> ListDatabases(const fs::path& wallet_dir)
  26  {
  27      const fs::path& data_dir = gArgs.GetDataDirNet();
  28      const fs::path& blocks_dir = gArgs.GetBlocksDirPath();
  29  
  30      // Here we place the top level dirs we want to skip in case walletdir is datadir or blocksdir
  31      // Those directories are referenced in doc/files.md
  32      const std::set<fs::path> ignore_paths = {
  33                                          blocks_dir,
  34                                          data_dir / "blktree",
  35                                          data_dir / "blocks",
  36                                          data_dir / "chainstate",
  37                                          data_dir / "coins",
  38                                          data_dir / "database",
  39                                          data_dir / "indexes",
  40                                          data_dir / "regtest",
  41                                          data_dir / "signet",
  42                                          data_dir / "testnet3"
  43                                          };
  44  
  45      std::vector<std::pair<fs::path, std::string>> paths;
  46      std::error_code ec;
  47  
  48      for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
  49          assert(!ec); // Loop should exit on error.
  50  
  51          // We don't want to iterate through those special node dirs
  52          if (ignore_paths.count(it->path())) {
  53              it.disable_recursion_pending();
  54              continue;
  55          }
  56  
  57          try {
  58              const fs::path path{it->path().lexically_relative(wallet_dir)};
  59  
  60              if (IsSymlink(it->path())) {
  61                  LogWarning("Not recursively searching symlink/reparse point at '%s'", fs::PathToString(it->path()));
  62                  it.disable_recursion_pending();
  63              }
  64  
  65              if (it->status().type() == fs::file_type::directory) {
  66                  if (IsBDBFile(BDBDataFile(it->path()))) {
  67                      // Found a directory which contains wallet.dat btree file, add it as a wallet with BERKELEY format.
  68                      paths.emplace_back(path, "bdb");
  69                  } else if (IsSQLiteFile(SQLiteDataFile(it->path()))) {
  70                      // Found a directory which contains wallet.dat sqlite file, add it as a wallet with SQLITE format.
  71                      paths.emplace_back(path, "sqlite");
  72                  }
  73              } else if (it.depth() == 0 && it->symlink_status().type() == fs::file_type::regular && it->path().extension() != ".bak") {
  74                  if (it->path().filename() == "wallet.dat") {
  75                      // Found top-level wallet.dat file, add top level directory ""
  76                      // as a wallet.
  77                      if (IsBDBFile(it->path())) {
  78                          paths.emplace_back(fs::path(), "bdb");
  79                      } else if (IsSQLiteFile(it->path())) {
  80                          paths.emplace_back(fs::path(), "sqlite");
  81                      }
  82                  } else if (IsBDBFile(it->path())) {
  83                      // Found top-level btree file not called wallet.dat. Current limenka
  84                      // software will never create these files but will allow them to be
  85                      // opened in a shared database environment for backwards compatibility.
  86                      // Add it to the list of available wallets.
  87                      paths.emplace_back(path, "bdb");
  88                  }
  89              }
  90          } catch (const std::exception& e) {
  91              LogWarning("Error while scanning wallet dir item: %s [%s].", e.what(), fs::PathToString(it->path()));
  92              it.disable_recursion_pending();
  93          }
  94      }
  95      if (ec) {
  96          // Loop could have exited with an error due to one of:
  97          // * wallet_dir itself not being scannable.
  98          // * increment() failure. (Observed on Windows native builds when
  99          //   removing the ACL read permissions of a wallet directory after the
 100          //   process started).
 101          LogWarning("Error scanning directory entries under %s: %s", fs::PathToString(wallet_dir), ec.message());
 102      }
 103  
 104      return paths;
 105  }
 106  
 107  fs::path BDBDataFile(const fs::path& wallet_path)
 108  {
 109      if (fs::is_regular_file(wallet_path)) {
 110          // Special case for backwards compatibility: if wallet path points to an
 111          // existing file, treat it as the path to a BDB data file in a parent
 112          // directory that also contains BDB log files.
 113          return wallet_path;
 114      } else {
 115          // Normal case: Interpret wallet path as a directory path containing
 116          // data and log files.
 117          return wallet_path / "wallet.dat";
 118      }
 119  }
 120  
 121  fs::path SQLiteDataFile(const fs::path& path)
 122  {
 123      return path / "wallet.dat";
 124  }
 125  
 126  bool IsBDBFile(const fs::path& path)
 127  {
 128      if (!fs::exists(path)) return false;
 129  
 130      // A Berkeley DB Btree file has at least 4K.
 131      // This check also prevents opening lock files.
 132      std::error_code ec;
 133      auto size = fs::file_size(path, ec);
 134      if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
 135      if (size < 4096) return false;
 136  
 137      std::ifstream file{path, std::ios::binary};
 138      if (!file.is_open()) return false;
 139  
 140      file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
 141      uint32_t data = 0;
 142      file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
 143  
 144      // Berkeley DB Btree magic bytes, from:
 145      //  https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
 146      //  - big endian systems - 00 05 31 62
 147      //  - little endian systems - 62 31 05 00
 148      return data == 0x00053162 || data == 0x62310500;
 149  }
 150  
 151  bool IsSQLiteFile(const fs::path& path)
 152  {
 153      if (!fs::exists(path)) return false;
 154  
 155      // A SQLite Database file is at least 512 bytes.
 156      std::error_code ec;
 157      auto size = fs::file_size(path, ec);
 158      if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
 159      if (size < 512) return false;
 160  
 161      std::ifstream file{path, std::ios::binary};
 162      if (!file.is_open()) return false;
 163  
 164      // Magic is at beginning and is 16 bytes long
 165      char magic[16];
 166      file.read(magic, 16);
 167  
 168      // Application id is at offset 68 and 4 bytes long
 169      file.seekg(68, std::ios::beg);
 170      char app_id[4];
 171      file.read(app_id, 4);
 172  
 173      file.close();
 174  
 175      // Check the magic, see https://sqlite.org/fileformat.html
 176      std::string magic_str(magic, 16);
 177      if (magic_str != std::string{"SQLite format 3\000", 16}) {
 178          return false;
 179      }
 180  
 181      // Check the application id matches our network magic
 182      return memcmp(Params().MessageStart().data(), app_id, 4) == 0;
 183  }
 184  
 185  void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options)
 186  {
 187      // Override current options with args values, if any were specified
 188      options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync);
 189      options.use_shared_memory = !args.GetBoolArg("-privdb", !options.use_shared_memory);
 190      options.max_log_mb = args.GetIntArg("-dblogsize", options.max_log_mb);
 191  }
 192  
 193  } // namespace wallet
 194