/ src / wallet / db.cpp
db.cpp
  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 <logging.h>
  9  #include <util/fs.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