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