1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <utility>
6
7 #include <fbl/algorithm.h>
8 #include <fbl/string.h>
9 #include <fbl/string_piece.h>
10 #include <lib/bootfs/parser.h>
11 #include <lib/zx/vmo.h>
12 #include <unittest/unittest.h>
13 #include <zircon/boot/bootdata.h>
14
15 namespace {
16
17 constexpr uint64_t kVmoSize = 1024 * 1024;
18
19 struct BootfsEntry {
20 fbl::String name;
21 fbl::StringPiece data;
22 };
23
24 // helper for creating a bootfs to use
CreateBootfs(BootfsEntry * entries,size_t num_entries,zx::vmo * vmo_out)25 zx_status_t CreateBootfs(BootfsEntry* entries, size_t num_entries, zx::vmo* vmo_out) {
26 zx::vmo vmo;
27 zx_status_t status;
28 status = zx::vmo::create(kVmoSize, 0, &vmo);
29 if (status != ZX_OK) {
30 return status;
31 }
32
33 uint32_t offset = static_cast<uint32_t>(sizeof(bootfs_header_t));
34 for (size_t i = 0; i < num_entries; ++i) {
35 auto& entry = entries[i];
36 // Must be page-aligned
37 const uint32_t data_offset = static_cast<uint32_t>(ZX_PAGE_SIZE * (i + 1));
38
39 uint32_t entry_header[3] = {
40 static_cast<uint32_t>(entry.name.size() + 1), // name_len
41 static_cast<uint32_t>(entry.data.size()), // data size
42 data_offset,
43 };
44
45 // Write header
46 status = vmo.write(entry_header, offset, sizeof(entry_header));
47 if (status != ZX_OK) {
48 return status;
49 }
50 offset += static_cast<uint32_t>(sizeof(entry_header));
51
52 // Write name
53 status = vmo.write(entry.name.c_str(), offset, entry_header[0]);
54 if (status != ZX_OK) {
55 return status;
56 }
57 offset += entry_header[0];
58
59 // Write data
60 status = vmo.write(entry.data.data(), data_offset, entry.data.size());
61 if (status != ZX_OK) {
62 return status;
63 }
64
65 // Entries must be 32-bit aligned
66 offset = fbl::round_up(offset, 4u);
67 }
68
69 bootfs_header_t header = {};
70 header.magic = BOOTFS_MAGIC;
71 header.dirsize = static_cast<uint32_t>(offset - sizeof(header));
72
73 status = vmo.write(&header, 0, sizeof(header));
74 if (status != ZX_OK) {
75 return status;
76 }
77
78 *vmo_out = std::move(vmo);
79 return ZX_OK;
80 }
81
TestParseWithoutInit()82 bool TestParseWithoutInit() {
83 BEGIN_TEST;
84
85 bootfs::Parser parser;
86
87 ASSERT_EQ(parser.Parse([](const bootfs_entry_t* entry) { return ZX_OK; }),
88 ZX_ERR_BAD_STATE);
89
90 END_TEST;
91 }
92
TestInitTwice()93 bool TestInitTwice() {
94 BEGIN_TEST;
95
96 zx::vmo vmo;
97 ASSERT_EQ(CreateBootfs(nullptr, 0, &vmo), ZX_OK);
98
99 bootfs::Parser parser;
100 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_OK);
101 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_BAD_STATE);
102
103 END_TEST;
104 }
105
TestInitBadMagic()106 bool TestInitBadMagic() {
107 BEGIN_TEST;
108
109 zx::vmo vmo;
110 zx_status_t status;
111 status = zx::vmo::create(kVmoSize, 0, &vmo);
112 if (status != ZX_OK) {
113 return status;
114 }
115
116 bootfs_header_t header = {};
117 header.magic = BOOTFS_MAGIC ^ 1;
118 header.dirsize = 0;
119
120 status = vmo.write(&header, 0, sizeof(header));
121 if (status != ZX_OK) {
122 return status;
123 }
124
125 bootfs::Parser parser;
126 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_IO);
127
128 END_TEST;
129 }
130
TestInitShortHeader()131 bool TestInitShortHeader() {
132 BEGIN_TEST;
133
134 zx::vmo vmo;
135 zx_status_t status;
136 status = zx::vmo::create(0, 0, &vmo);
137 if (status != ZX_OK) {
138 return status;
139 }
140
141 bootfs::Parser parser;
142 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_OUT_OF_RANGE);
143
144 END_TEST;
145 }
146
TestInitCantMap()147 bool TestInitCantMap() {
148 BEGIN_TEST;
149
150 zx::vmo vmo;
151 ASSERT_EQ(CreateBootfs(nullptr, 0, &vmo), ZX_OK);
152 ASSERT_EQ(vmo.replace(ZX_RIGHT_READ, &vmo), ZX_OK);
153
154 bootfs::Parser parser;
155 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_ACCESS_DENIED);
156
157 END_TEST;
158 }
159
TestParseSuccess()160 bool TestParseSuccess() {
161 BEGIN_TEST;
162
163 BootfsEntry entries[] = {
164 {
165 .name = "file 3",
166 .data = "lorem ipsum",
167 },
168 {
169 .name = "File 1",
170 .data = "",
171 },
172 {
173 .name = "file2",
174 .data = "0123456789",
175 },
176 };
177 zx::vmo vmo;
178 ASSERT_EQ(CreateBootfs(entries, fbl::count_of(entries), &vmo), ZX_OK);
179
180 bootfs::Parser parser;
181 ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_OK);
182
183 const bootfs_entry_t* parsed_entries[3];
184 size_t seen = 0;
185 EXPECT_EQ(parser.Parse([&entries, &parsed_entries, &seen](const bootfs_entry_t* entry) {
186 if (seen >= fbl::count_of(entries)) {
187 return ZX_ERR_BAD_STATE;
188 }
189 parsed_entries[seen] = entry;
190 ++seen;
191 return ZX_OK;
192 }), ZX_OK);
193 ASSERT_EQ(seen, fbl::count_of(entries));
194
195 for (size_t i = 0; i < seen; ++i) {
196 const auto& real_entry = entries[i];
197 const auto& parsed_entry = parsed_entries[i];
198 ASSERT_EQ(parsed_entry->name_len, real_entry.name.size() + 1);
199 ASSERT_EQ(parsed_entry->data_len, real_entry.data.size());
200 ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(parsed_entry->name),
201 reinterpret_cast<const uint8_t*>(real_entry.name.c_str()),
202 parsed_entry->name_len, "");
203
204 uint8_t buffer[parsed_entry->data_len];
205 ASSERT_EQ(vmo.read(buffer, parsed_entry->data_off, sizeof(buffer)), ZX_OK);
206 ASSERT_BYTES_EQ(buffer, reinterpret_cast<const uint8_t*>(real_entry.data.data()),
207 sizeof(buffer), "");
208 }
209
210 END_TEST;
211 }
212
213 } // namespace
214
215 BEGIN_TEST_CASE(bootfs_tests)
RUN_TEST(TestParseWithoutInit)216 RUN_TEST(TestParseWithoutInit)
217 RUN_TEST(TestInitTwice)
218 RUN_TEST(TestInitBadMagic)
219 RUN_TEST(TestInitShortHeader)
220 RUN_TEST(TestInitCantMap)
221 RUN_TEST(TestParseSuccess)
222 END_TEST_CASE(bootfs_tests)
223
224 int main(int argc, char** argv) {
225 return unittest_run_all_tests(argc, argv) ? 0 : -1;
226 }
227
228