db.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-present The Bitcoin Core 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 <util/fs.h>
   9  #include <util/log.h>
  10  #include <wallet/db.h>
  11  
  12  #include <algorithm>
  13  #include <exception>
  14  #include <fstream>
  15  #include <string>
  16  #include <system_error>
  17  #include <vector>
  18  
  19  namespace wallet {
  20  bool operator<(BytePrefix a, std::span<const std::byte> b) { return std::ranges::lexicographical_compare(a.prefix, b.subspan(0, std::min(a.prefix.size(), b.size()))); }
  21  bool operator<(std::span<const std::byte> a, BytePrefix b) { return std::ranges::lexicographical_compare(a.subspan(0, std::min(a.size(), b.prefix.size())), b.prefix); }
  22  
  23  std::vector<std::pair<fs::path, std::string>> ListDatabases(const fs::path& wallet_dir)
  24  {
  25      std::vector<std::pair<fs::path, std::string>> paths;
  26      std::error_code ec;
  27  
  28      for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
  29          assert(!ec); // Loop should exit on error.
  30          try {
  31              const fs::path path{it->path().lexically_relative(wallet_dir)};
  32  
  33              if (it->status().type() == fs::file_type::directory) {
  34                  if (IsBDBFile(BDBDataFile(it->path()))) {
  35                      // Found a directory which contains wallet.dat btree file, add it as a wallet with BERKELEY format.
  36                      paths.emplace_back(path, "bdb");
  37                  } else if (IsSQLiteFile(SQLiteDataFile(it->path()))) {
  38                      // Found a directory which contains wallet.dat sqlite file, add it as a wallet with SQLITE format.
  39                      paths.emplace_back(path, "sqlite");
  40                  }
  41              } else if (it.depth() == 0 && it->symlink_status().type() == fs::file_type::regular && it->path().extension() != ".bak") {
  42                  if (it->path().filename() == "wallet.dat") {
  43                      // Found top-level wallet.dat file, add top level directory ""
  44                      // as a wallet.
  45                      if (IsBDBFile(it->path())) {
  46                          paths.emplace_back(fs::path(), "bdb");
  47                      } else if (IsSQLiteFile(it->path())) {
  48                          paths.emplace_back(fs::path(), "sqlite");
  49                      }
  50                  } else if (IsBDBFile(it->path())) {
  51                      // Found top-level btree file not called wallet.dat. Current bitcoin
  52                      // software will never create these files but will allow them to be
  53                      // opened in a shared database environment for backwards compatibility.
  54                      // Add it to the list of available wallets.
  55                      paths.emplace_back(path, "bdb");
  56                  }
  57              }
  58          } catch (const std::exception& e) {
  59              LogWarning("Error while scanning wallet dir item: %s [%s].", e.what(), fs::PathToString(it->path()));
  60              it.disable_recursion_pending();
  61          }
  62      }
  63      if (ec) {
  64          // Loop could have exited with an error due to one of:
  65          // * wallet_dir itself not being scannable.
  66          // * increment() failure. (Observed on Windows native builds when
  67          //   removing the ACL read permissions of a wallet directory after the
  68          //   process started).
  69          LogWarning("Error scanning directory entries under %s: %s", fs::PathToString(wallet_dir), ec.message());
  70      }
  71  
  72      return paths;
  73  }
  74  
  75  fs::path BDBDataFile(const fs::path& wallet_path)
  76  {
  77      if (fs::is_regular_file(wallet_path)) {
  78          // Special case for backwards compatibility: if wallet path points to an
  79          // existing file, treat it as the path to a BDB data file in a parent
  80          // directory that also contains BDB log files.
  81          return wallet_path;
  82      } else {
  83          // Normal case: Interpret wallet path as a directory path containing
  84          // data and log files.
  85          return wallet_path / "wallet.dat";
  86      }
  87  }
  88  
  89  fs::path SQLiteDataFile(const fs::path& path)
  90  {
  91      return path / "wallet.dat";
  92  }
  93  
  94  bool IsBDBFile(const fs::path& path)
  95  {
  96      if (!fs::exists(path)) return false;
  97  
  98      // A Berkeley DB Btree file has at least 4K.
  99      // This check also prevents opening lock files.
 100      std::error_code ec;
 101      auto size = fs::file_size(path, ec);
 102      if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
 103      if (size < 4096) return false;
 104  
 105      std::ifstream file{path.std_path(), std::ios::binary};
 106      if (!file.is_open()) return false;
 107  
 108      file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
 109      uint32_t data = 0;
 110      file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
 111  
 112      // Berkeley DB Btree magic bytes, from:
 113      //  https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
 114      //  - big endian systems - 00 05 31 62
 115      //  - little endian systems - 62 31 05 00
 116      return data == 0x00053162 || data == 0x62310500;
 117  }
 118  
 119  bool IsSQLiteFile(const fs::path& path)
 120  {
 121      if (!fs::exists(path)) return false;
 122  
 123      // A SQLite Database file is at least 512 bytes.
 124      std::error_code ec;
 125      auto size = fs::file_size(path, ec);
 126      if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
 127      if (size < 512) return false;
 128  
 129      std::ifstream file{path.std_path(), std::ios::binary};
 130      if (!file.is_open()) return false;
 131  
 132      // Magic is at beginning and is 16 bytes long
 133      char magic[16];
 134      file.read(magic, 16);
 135  
 136      // Application id is at offset 68 and 4 bytes long
 137      file.seekg(68, std::ios::beg);
 138      char app_id[4];
 139      file.read(app_id, 4);
 140  
 141      file.close();
 142  
 143      // Check the magic, see https://sqlite.org/fileformat.html
 144      std::string magic_str(magic, 16);
 145      if (magic_str != std::string{"SQLite format 3\000", 16}) {
 146          return false;
 147      }
 148  
 149      // Check the application id matches our network magic
 150      return memcmp(Params().MessageStart().data(), app_id, 4) == 0;
 151  }
 152  
 153  void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options)
 154  {
 155      // Override current options with args values, if any were specified
 156      options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync);
 157  }
 158  
 159  } // namespace wallet
 160