Serenity Operating System
1/*
2 * Copyright (c) 2021, Jan de Visser <jan@de-visser.net>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/DeprecatedString.h>
8#include <AK/Format.h>
9#include <AK/QuickSort.h>
10#include <LibCore/IODevice.h>
11#include <LibCore/System.h>
12#include <LibSQL/Heap.h>
13#include <LibSQL/Serializer.h>
14#include <sys/stat.h>
15#include <sys/types.h>
16
17namespace SQL {
18
19Heap::Heap(DeprecatedString file_name)
20{
21 set_name(move(file_name));
22}
23
24Heap::~Heap()
25{
26 if (m_file && !m_write_ahead_log.is_empty()) {
27 if (auto maybe_error = flush(); maybe_error.is_error())
28 warnln("~Heap({}): {}", name(), maybe_error.error());
29 }
30}
31
32ErrorOr<void> Heap::open()
33{
34 size_t file_size = 0;
35 struct stat stat_buffer;
36 if (stat(name().characters(), &stat_buffer) != 0) {
37 if (errno != ENOENT) {
38 warnln("Heap::open({}): could not stat: {}"sv, name(), strerror(errno));
39 return Error::from_string_literal("Heap::open(): could not stat file");
40 }
41 } else if (!S_ISREG(stat_buffer.st_mode)) {
42 warnln("Heap::open({}): can only use regular files"sv, name());
43 return Error::from_string_literal("Heap::open(): can only use regular files");
44 } else {
45 file_size = stat_buffer.st_size;
46 }
47 if (file_size > 0)
48 m_next_block = m_end_of_file = file_size / BLOCKSIZE;
49
50 auto file = TRY(Core::File::open(name(), Core::File::OpenMode::ReadWrite));
51 m_file = TRY(Core::BufferedFile::create(move(file)));
52
53 if (file_size > 0) {
54 if (auto error_maybe = read_zero_block(); error_maybe.is_error()) {
55 m_file = nullptr;
56 return error_maybe.release_error();
57 }
58 } else {
59 initialize_zero_block();
60 }
61
62 // FIXME: We should more gracefully handle version incompatibilities. For now, we drop the database.
63 if (m_version != current_version) {
64 dbgln_if(SQL_DEBUG, "Heap file {} opened has incompatible version {}. Deleting for version {}.", name(), m_version, current_version);
65 m_file = nullptr;
66
67 TRY(Core::System::unlink(name()));
68 return open();
69 }
70
71 dbgln_if(SQL_DEBUG, "Heap file {} opened. Size = {}", name(), size());
72 return {};
73}
74
75ErrorOr<ByteBuffer> Heap::read_block(u32 block)
76{
77 if (!m_file) {
78 warnln("Heap({})::read_block({}): Heap file not opened"sv, name(), block);
79 return Error::from_string_literal("Heap()::read_block(): Heap file not opened");
80 }
81
82 if (auto buffer = m_write_ahead_log.get(block); buffer.has_value())
83 return TRY(ByteBuffer::copy(*buffer));
84
85 if (block >= m_next_block) {
86 warnln("Heap({})::read_block({}): block # out of range (>= {})"sv, name(), block, m_next_block);
87 return Error::from_string_literal("Heap()::read_block(): block # out of range");
88 }
89
90 dbgln_if(SQL_DEBUG, "Read heap block {}", block);
91 TRY(seek_block(block));
92
93 auto buffer = TRY(ByteBuffer::create_uninitialized(BLOCKSIZE));
94 TRY(m_file->read_until_filled(buffer));
95
96 dbgln_if(SQL_DEBUG, "{:hex-dump}", buffer.bytes().trim(8));
97
98 return buffer;
99}
100
101ErrorOr<void> Heap::write_block(u32 block, ByteBuffer& buffer)
102{
103 if (!m_file) {
104 warnln("Heap({})::write_block({}): Heap file not opened"sv, name(), block);
105 return Error::from_string_literal("Heap()::write_block(): Heap file not opened");
106 }
107 if (block > m_next_block) {
108 warnln("Heap({})::write_block({}): block # out of range (> {})"sv, name(), block, m_next_block);
109 return Error::from_string_literal("Heap()::write_block(): block # out of range");
110 }
111 if (buffer.size() > BLOCKSIZE) {
112 warnln("Heap({})::write_block({}): Oversized block ({} > {})"sv, name(), block, buffer.size(), BLOCKSIZE);
113 return Error::from_string_literal("Heap()::write_block(): Oversized block");
114 }
115
116 dbgln_if(SQL_DEBUG, "Write heap block {} size {}", block, buffer.size());
117 TRY(seek_block(block));
118
119 if (auto current_size = buffer.size(); current_size < BLOCKSIZE) {
120 TRY(buffer.try_resize(BLOCKSIZE));
121 memset(buffer.offset_pointer(current_size), 0, BLOCKSIZE - current_size);
122 }
123
124 dbgln_if(SQL_DEBUG, "{:hex-dump}", buffer.bytes().trim(8));
125 TRY(m_file->write_until_depleted(buffer));
126
127 if (block == m_end_of_file)
128 m_end_of_file++;
129 return {};
130}
131
132ErrorOr<void> Heap::seek_block(u32 block)
133{
134 if (!m_file) {
135 warnln("Heap({})::seek_block({}): Heap file not opened"sv, name(), block);
136 return Error::from_string_literal("Heap()::seek_block(): Heap file not opened");
137 }
138 if (block > m_end_of_file) {
139 warnln("Heap({})::seek_block({}): Cannot seek beyond end of file at block {}"sv, name(), block, m_end_of_file);
140 return Error::from_string_literal("Heap()::seek_block(): Cannot seek beyond end of file");
141 }
142
143 if (block == m_end_of_file)
144 TRY(m_file->seek(0, SeekMode::FromEndPosition));
145 else
146 TRY(m_file->seek(block * BLOCKSIZE, SeekMode::SetPosition));
147
148 return {};
149}
150
151u32 Heap::new_record_pointer()
152{
153 VERIFY(m_file);
154 if (m_free_list) {
155 auto block_or_error = read_block(m_free_list);
156 if (block_or_error.is_error()) {
157 warnln("FREE LIST CORRUPTION");
158 VERIFY_NOT_REACHED();
159 }
160 auto new_pointer = m_free_list;
161 memcpy(&m_free_list, block_or_error.value().offset_pointer(0), sizeof(u32));
162 update_zero_block();
163 return new_pointer;
164 }
165 return m_next_block++;
166}
167
168ErrorOr<void> Heap::flush()
169{
170 VERIFY(m_file);
171 Vector<u32> blocks;
172 for (auto& wal_entry : m_write_ahead_log) {
173 blocks.append(wal_entry.key);
174 }
175 quick_sort(blocks);
176 for (auto& block : blocks) {
177 auto buffer_it = m_write_ahead_log.find(block);
178 VERIFY(buffer_it != m_write_ahead_log.end());
179 dbgln_if(SQL_DEBUG, "Flushing block {} to {}", block, name());
180 TRY(write_block(block, buffer_it->value));
181 }
182 m_write_ahead_log.clear();
183 dbgln_if(SQL_DEBUG, "WAL flushed. Heap size = {}", size());
184 return {};
185}
186
187constexpr static auto FILE_ID = "SerenitySQL "sv;
188constexpr static auto VERSION_OFFSET = FILE_ID.length();
189constexpr static auto SCHEMAS_ROOT_OFFSET = VERSION_OFFSET + sizeof(u32);
190constexpr static auto TABLES_ROOT_OFFSET = SCHEMAS_ROOT_OFFSET + sizeof(u32);
191constexpr static auto TABLE_COLUMNS_ROOT_OFFSET = TABLES_ROOT_OFFSET + sizeof(u32);
192constexpr static auto FREE_LIST_OFFSET = TABLE_COLUMNS_ROOT_OFFSET + sizeof(u32);
193constexpr static auto USER_VALUES_OFFSET = FREE_LIST_OFFSET + sizeof(u32);
194
195ErrorOr<void> Heap::read_zero_block()
196{
197 auto buffer = TRY(read_block(0));
198 auto file_id_buffer = TRY(buffer.slice(0, FILE_ID.length()));
199 auto file_id = StringView(file_id_buffer);
200 if (file_id != FILE_ID) {
201 warnln("{}: Zero page corrupt. This is probably not a {} heap file"sv, name(), FILE_ID);
202 return Error::from_string_literal("Heap()::read_zero_block(): Zero page corrupt. This is probably not a SerenitySQL heap file");
203 }
204
205 dbgln_if(SQL_DEBUG, "Read zero block from {}", name());
206
207 memcpy(&m_version, buffer.offset_pointer(VERSION_OFFSET), sizeof(u32));
208 dbgln_if(SQL_DEBUG, "Version: {}.{}", (m_version & 0xFFFF0000) >> 16, (m_version & 0x0000FFFF));
209
210 memcpy(&m_schemas_root, buffer.offset_pointer(SCHEMAS_ROOT_OFFSET), sizeof(u32));
211 dbgln_if(SQL_DEBUG, "Schemas root node: {}", m_schemas_root);
212
213 memcpy(&m_tables_root, buffer.offset_pointer(TABLES_ROOT_OFFSET), sizeof(u32));
214 dbgln_if(SQL_DEBUG, "Tables root node: {}", m_tables_root);
215
216 memcpy(&m_table_columns_root, buffer.offset_pointer(TABLE_COLUMNS_ROOT_OFFSET), sizeof(u32));
217 dbgln_if(SQL_DEBUG, "Table columns root node: {}", m_table_columns_root);
218
219 memcpy(&m_free_list, buffer.offset_pointer(FREE_LIST_OFFSET), sizeof(u32));
220 dbgln_if(SQL_DEBUG, "Free list: {}", m_free_list);
221
222 memcpy(m_user_values.data(), buffer.offset_pointer(USER_VALUES_OFFSET), m_user_values.size() * sizeof(u32));
223 for (auto ix = 0u; ix < m_user_values.size(); ix++) {
224 if (m_user_values[ix]) {
225 dbgln_if(SQL_DEBUG, "User value {}: {}", ix, m_user_values[ix]);
226 }
227 }
228 return {};
229}
230
231void Heap::update_zero_block()
232{
233 dbgln_if(SQL_DEBUG, "Write zero block to {}", name());
234 dbgln_if(SQL_DEBUG, "Version: {}.{}", (m_version & 0xFFFF0000) >> 16, (m_version & 0x0000FFFF));
235 dbgln_if(SQL_DEBUG, "Schemas root node: {}", m_schemas_root);
236 dbgln_if(SQL_DEBUG, "Tables root node: {}", m_tables_root);
237 dbgln_if(SQL_DEBUG, "Table Columns root node: {}", m_table_columns_root);
238 dbgln_if(SQL_DEBUG, "Free list: {}", m_free_list);
239 for (auto ix = 0u; ix < m_user_values.size(); ix++) {
240 if (m_user_values[ix]) {
241 dbgln_if(SQL_DEBUG, "User value {}: {}", ix, m_user_values[ix]);
242 }
243 }
244
245 // FIXME: Handle an OOM failure here.
246 auto buffer = ByteBuffer::create_zeroed(BLOCKSIZE).release_value_but_fixme_should_propagate_errors();
247 buffer.overwrite(0, FILE_ID.characters_without_null_termination(), FILE_ID.length());
248 buffer.overwrite(VERSION_OFFSET, &m_version, sizeof(u32));
249 buffer.overwrite(SCHEMAS_ROOT_OFFSET, &m_schemas_root, sizeof(u32));
250 buffer.overwrite(TABLES_ROOT_OFFSET, &m_tables_root, sizeof(u32));
251 buffer.overwrite(TABLE_COLUMNS_ROOT_OFFSET, &m_table_columns_root, sizeof(u32));
252 buffer.overwrite(FREE_LIST_OFFSET, &m_free_list, sizeof(u32));
253 buffer.overwrite(USER_VALUES_OFFSET, m_user_values.data(), m_user_values.size() * sizeof(u32));
254
255 add_to_wal(0, buffer);
256}
257
258void Heap::initialize_zero_block()
259{
260 m_version = current_version;
261 m_schemas_root = 0;
262 m_tables_root = 0;
263 m_table_columns_root = 0;
264 m_next_block = 1;
265 m_free_list = 0;
266 for (auto& user : m_user_values) {
267 user = 0u;
268 }
269 update_zero_block();
270}
271
272}