Skip to content

Commit c12f426

Browse files
committed
Add CpioReader for reading CPIO archives
Bug: b/503906646
1 parent f4dcec6 commit c12f426

4 files changed

Lines changed: 624 additions & 0 deletions

File tree

base/cvd/cuttlefish/io/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,33 @@ cf_cc_test(
8181
],
8282
)
8383

84+
cf_cc_library(
85+
name = "cpio",
86+
srcs = ["cpio.cc"],
87+
hdrs = ["cpio.h"],
88+
deps = [
89+
"//cuttlefish/common/libs/utils:size_utils",
90+
"//cuttlefish/io",
91+
"//cuttlefish/io:filesystem",
92+
"//cuttlefish/io:read_exact",
93+
"//cuttlefish/io:read_window_view",
94+
"//cuttlefish/result:expect",
95+
"//cuttlefish/result:result_type",
96+
],
97+
)
98+
99+
cf_cc_test(
100+
name = "cpio_test",
101+
srcs = ["cpio_test.cc"],
102+
deps = [
103+
"//cuttlefish/io:cpio",
104+
"//cuttlefish/io:in_memory",
105+
"//cuttlefish/io:read_exact",
106+
"//cuttlefish/io:string",
107+
"//cuttlefish/result:result_matchers",
108+
],
109+
)
110+
84111
cf_cc_library(
85112
name = "default_visitor",
86113
srcs = ["default_visitor.cc"],

base/cvd/cuttlefish/io/cpio.cc

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//
2+
// Copyright (C) 2026 The Android Open Source Project
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#include "cuttlefish/io/cpio.h"
17+
18+
#include <endian.h>
19+
#include <string.h>
20+
21+
#include <charconv>
22+
#include <string_view>
23+
24+
#include "cuttlefish/common/libs/utils/size_utils.h"
25+
#include "cuttlefish/io/read_exact.h"
26+
#include "cuttlefish/io/read_window_view.h"
27+
#include "cuttlefish/result/expect.h"
28+
29+
namespace cuttlefish {
30+
31+
// For the CPIO file format specification, see:
32+
// https://github.com/libyal/dtformats/blob/c55446a4369149bb109bf783f29a2838c7328718/documentation/Copy%20in%20and%20out%20(CPIO)%20archive%20format.asciidoc
33+
namespace {
34+
35+
constexpr std::string_view kMagicNewc1 = "070701";
36+
constexpr std::string_view kMagicNewc2 = "070702";
37+
constexpr std::string_view kMagicOdc = "070707";
38+
constexpr std::string_view kTrailerName = "TRAILER!!!";
39+
40+
Result<uint32_t> ParseHex(const char* buf, size_t size) {
41+
uint32_t val = 0;
42+
std::from_chars_result result = std::from_chars(buf, buf + size, val, 16);
43+
CF_EXPECT(result.ec == std::errc(), "Failed to parse hex value");
44+
return val;
45+
}
46+
47+
Result<uint32_t> ParseOctal(const char* buf, size_t size) {
48+
uint32_t val = 0;
49+
std::from_chars_result result = std::from_chars(buf, buf + size, val, 8);
50+
CF_EXPECT(result.ec == std::errc(), "Failed to parse octal value");
51+
return val;
52+
}
53+
54+
uint16_t Read16(const char* ptr, bool file_is_big_endian) {
55+
uint16_t val;
56+
memcpy(&val, ptr, sizeof(val)); // Guarantees alignment of `val`
57+
return file_is_big_endian ? be16toh(val) : le16toh(val);
58+
}
59+
60+
uint32_t Read32(const char* ptr, bool file_is_big_endian) {
61+
uint32_t val;
62+
memcpy(&val, ptr, sizeof(val)); // Guarantees alignment of `val`
63+
return file_is_big_endian ? be32toh(val) : le32toh(val);
64+
}
65+
66+
} // namespace
67+
68+
Result<std::unique_ptr<CpioReader>> CpioReader::Open(
69+
std::unique_ptr<ReaderSeeker> reader) {
70+
CF_EXPECT(reader != nullptr, "Reader is null");
71+
std::map<std::string, FileEntry, std::less<>> entries =
72+
CF_EXPECT(Parse(*reader));
73+
return std::unique_ptr<CpioReader>(
74+
new CpioReader(std::move(reader), std::move(entries)));
75+
}
76+
77+
CpioReader::CpioReader(std::unique_ptr<ReaderSeeker> reader,
78+
std::map<std::string, FileEntry, std::less<>> entries)
79+
: reader_(std::move(reader)), entries_(std::move(entries)) {}
80+
81+
Result<std::map<std::string, CpioReader::FileEntry, std::less<>>>
82+
CpioReader::Parse(ReaderSeeker& reader) {
83+
char magic[6];
84+
CF_EXPECT(PReadExact(reader, magic, 6, 0));
85+
86+
// Check for binary magic (2 bytes)
87+
uint8_t m0 = static_cast<uint8_t>(magic[0]);
88+
uint8_t m1 = static_cast<uint8_t>(magic[1]);
89+
if (m0 == 0xC7 && m1 == 0x71) {
90+
return ParseBin(reader, false);
91+
} else if (m0 == 0x71 && m1 == 0xC7) {
92+
return ParseBin(reader, true);
93+
}
94+
95+
std::string_view magic_view(magic, 6);
96+
if (magic_view == kMagicNewc1 || magic_view == kMagicNewc2) {
97+
return ParseNewc(reader);
98+
} else if (magic_view == kMagicOdc) {
99+
return ParseOdc(reader);
100+
} else {
101+
return CF_ERRF("Unknown cpio magic: {}", magic_view);
102+
}
103+
}
104+
105+
Result<std::map<std::string, CpioReader::FileEntry, std::less<>>>
106+
CpioReader::ParseNewc(ReaderSeeker& reader) {
107+
std::map<std::string, FileEntry, std::less<>> entries;
108+
uint64_t offset = 0;
109+
while (true) {
110+
// Align to 4 bytes before reading header
111+
offset = AlignToPowerOf2(offset, 2);
112+
113+
char header[110];
114+
CF_EXPECT(PReadExact(reader, header, 110, offset));
115+
116+
std::string_view magic(header, 6);
117+
CF_EXPECT(magic == kMagicNewc1 || magic == kMagicNewc2,
118+
"Invalid magic in header");
119+
120+
uint32_t filesize = CF_EXPECT(ParseHex(header + 54, 8));
121+
uint32_t namesize = CF_EXPECT(ParseHex(header + 94, 8));
122+
uint32_t mode = CF_EXPECT(ParseHex(header + 14, 8));
123+
124+
std::string name(namesize - 1, '\0');
125+
CF_EXPECT(PReadExact(reader, name.data(), namesize - 1, offset + 110));
126+
127+
if (name == kTrailerName) {
128+
break;
129+
}
130+
131+
uint64_t data_offset = AlignToPowerOf2(offset + 110 + namesize, 2);
132+
133+
entries.emplace(std::move(name), FileEntry{
134+
.offset = data_offset,
135+
.size = filesize,
136+
.mode = mode,
137+
});
138+
139+
offset = data_offset + filesize;
140+
}
141+
return entries;
142+
}
143+
144+
Result<std::map<std::string, CpioReader::FileEntry, std::less<>>>
145+
CpioReader::ParseOdc(ReaderSeeker& reader) {
146+
std::map<std::string, FileEntry, std::less<>> entries;
147+
uint64_t offset = 0;
148+
while (true) {
149+
char header[76];
150+
CF_EXPECT(PReadExact(reader, header, 76, offset));
151+
152+
std::string_view magic(header, 6);
153+
CF_EXPECT_EQ(magic, kMagicOdc, "Invalid magic in header");
154+
155+
uint32_t mode = CF_EXPECT(ParseOctal(header + 18, 6));
156+
uint32_t namesize = CF_EXPECT(ParseOctal(header + 59, 6));
157+
uint32_t filesize = CF_EXPECT(ParseOctal(header + 65, 11));
158+
159+
std::string name(namesize - 1, '\0');
160+
CF_EXPECT(PReadExact(reader, name.data(), namesize - 1, offset + 76));
161+
162+
if (name == kTrailerName) {
163+
break;
164+
}
165+
166+
uint64_t data_offset = offset + 76 + namesize;
167+
168+
entries.emplace(std::move(name), FileEntry{
169+
.offset = data_offset,
170+
.size = filesize,
171+
.mode = mode,
172+
});
173+
174+
offset = data_offset + filesize;
175+
}
176+
return entries;
177+
}
178+
179+
Result<std::map<std::string, CpioReader::FileEntry, std::less<>>>
180+
CpioReader::ParseBin(ReaderSeeker& reader, bool file_is_big_endian) {
181+
std::map<std::string, FileEntry, std::less<>> entries;
182+
uint64_t offset = 0;
183+
while (true) {
184+
char header[26];
185+
CF_EXPECT(PReadExact(reader, header, 26, offset));
186+
187+
uint8_t m0 = static_cast<uint8_t>(header[0]);
188+
uint8_t m1 = static_cast<uint8_t>(header[1]);
189+
if (file_is_big_endian) {
190+
CF_EXPECT(m0 == 0x71 && m1 == 0xC7, "Invalid magic in binary header");
191+
} else {
192+
CF_EXPECT(m0 == 0xC7 && m1 == 0x71, "Invalid magic in binary header");
193+
}
194+
195+
// It is possible to define a packed struct for this type, but the
196+
// `filesize` member is not aligned and would not be safe to read directly
197+
// from the struct.
198+
uint16_t mode = Read16(header + 6, file_is_big_endian);
199+
uint16_t namesize = Read16(header + 20, file_is_big_endian);
200+
uint32_t filesize = Read32(header + 22, file_is_big_endian);
201+
202+
std::string name(namesize - 1, '\0');
203+
CF_EXPECT(PReadExact(reader, name.data(), namesize - 1, offset + 26));
204+
205+
if (name == kTrailerName) {
206+
break;
207+
}
208+
209+
uint64_t data_offset = AlignToPowerOf2(offset + 26 + namesize, 1);
210+
211+
entries.emplace(std::move(name), FileEntry{
212+
.offset = data_offset,
213+
.size = filesize,
214+
.mode = mode,
215+
});
216+
217+
offset = AlignToPowerOf2(data_offset + filesize, 1);
218+
}
219+
return entries;
220+
}
221+
222+
Result<std::unique_ptr<ReaderSeeker>> CpioReader::OpenReadOnly(
223+
std::string_view path) {
224+
auto it = entries_.find(path);
225+
CF_EXPECTF(it != entries_.end(), "File not found in cpio: '{}'", path);
226+
return std::make_unique<ReadWindowView>(*reader_, it->second.offset,
227+
it->second.size);
228+
}
229+
230+
Result<uint32_t> CpioReader::FileAttributes(std::string_view path) const {
231+
auto it = entries_.find(path);
232+
CF_EXPECTF(it != entries_.end(), "File not found in cpio: '{}'", path);
233+
return it->second.mode;
234+
}
235+
236+
} // namespace cuttlefish

base/cvd/cuttlefish/io/cpio.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Copyright (C) 2026 The Android Open Source Project
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#pragma once
17+
18+
#include <stdint.h>
19+
20+
#include <map>
21+
#include <memory>
22+
#include <string>
23+
#include <string_view>
24+
25+
#include "cuttlefish/io/filesystem.h"
26+
#include "cuttlefish/io/io.h"
27+
#include "cuttlefish/result/result_type.h"
28+
29+
namespace cuttlefish {
30+
31+
class CpioReader : public ReadFilesystem {
32+
public:
33+
static Result<std::unique_ptr<CpioReader>> Open(
34+
std::unique_ptr<ReaderSeeker> reader);
35+
36+
Result<std::unique_ptr<ReaderSeeker>> OpenReadOnly(
37+
std::string_view path) override;
38+
39+
Result<uint32_t> FileAttributes(std::string_view path) const override;
40+
41+
private:
42+
struct FileEntry {
43+
uint64_t offset;
44+
uint64_t size;
45+
uint32_t mode;
46+
};
47+
48+
CpioReader(std::unique_ptr<ReaderSeeker> reader,
49+
std::map<std::string, FileEntry, std::less<>> entries);
50+
51+
static Result<std::map<std::string, FileEntry, std::less<>>> Parse(
52+
ReaderSeeker& reader);
53+
static Result<std::map<std::string, FileEntry, std::less<>>> ParseOdc(
54+
ReaderSeeker& reader);
55+
static Result<std::map<std::string, FileEntry, std::less<>>> ParseNewc(
56+
ReaderSeeker& reader);
57+
static Result<std::map<std::string, FileEntry, std::less<>>> ParseBin(
58+
ReaderSeeker& reader, bool file_is_big_endian);
59+
60+
std::unique_ptr<ReaderSeeker> reader_;
61+
std::map<std::string, FileEntry, std::less<>> entries_;
62+
};
63+
64+
} // namespace cuttlefish

0 commit comments

Comments
 (0)