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