Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
on:
pull_request:
workflow_dispatch:
push:
branches:
- master
tags:
- v?[0-9]+.[0-9]+.[0-9]+*

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
DeterminateCI:
uses: DeterminateSystems/ci/.github/workflows/workflow.yml@main
permissions:
id-token: write
contents: read
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
zig-cache
.zig-cache
zig-out
*.dtb
!test/*.dtb

.direnv
result
result-*
43 changes: 31 additions & 12 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,40 @@ pub fn build(b: *std.Build) void {
const no_docs = b.option(bool, "no-docs", "skip installing documentation") orelse false;

const dtree = b.addModule("dtree", .{
.root_source_file = .{ .path = b.pathFromRoot("dtree.zig") },
.root_source_file = b.path("dtree.zig"),
.target = target,
.optimize = optimize,
});

if (!no_tests) {
const step_test = b.step("test", "Run all unit tests");

const unit_tests = b.addTest(.{
.root_source_file = .{
.path = b.pathFromRoot("dtree.zig"),
},
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("dtree.zig"),
.target = target,
.optimize = optimize,
}),
});

const run_unit_tests = b.addRunArtifact(unit_tests);
step_test.dependOn(&run_unit_tests.step);

const integration_tests = b.addTest(.{
.name = "integration-test",
.root_module = b.createModule(.{
.root_source_file = b.path("test/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "dtree", .module = dtree },
},
}),
});

const run_integration_tests = b.addRunArtifact(integration_tests);
step_test.dependOn(&run_integration_tests.step);

if (!no_docs) {
const docs = b.addInstallDirectory(.{
.source_dir = unit_tests.getEmittedDocs(),
Expand All @@ -37,13 +54,15 @@ pub fn build(b: *std.Build) void {

const exe_example = b.addExecutable(.{
.name = "example",
.root_source_file = .{
.path = b.pathFromRoot("example.zig"),
},
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("example.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "dtree", .module = dtree },
},
}),
});

exe_example.root_module.addImport("dtree", dtree);
b.installArtifact(exe_example);
}
7 changes: 4 additions & 3 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.{
.name = "dtree",
.version = "0.1.0",
.paths = .{"."},
.name = .dtree,
.version = "0.1.0",
.fingerprint = 0x3ceaa750f068abef,
.paths = .{"."},
}
5 changes: 5 additions & 0 deletions dtree.zig
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
pub const Reader = @import("dtree/reader.zig");
pub const types = @import("dtree/types.zig");

test {
_ = Reader;
_ = types;
}
128 changes: 73 additions & 55 deletions dtree/reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ pub const Node = union(enum) {
depth: usize,
name: []const u8,

pub fn format(self: Begin, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = options;

pub fn format(self: Begin, writer: *std.Io.Writer) std.Io.Writer.Error!void {
try writer.writeAll(@typeName(Begin));
try writer.print("{{ .depth = {}, .name = \"{s}\" }}", .{
try writer.print("{{ .depth = {d}, .name = \"{s}\" }}", .{
self.depth,
self.name,
});
Expand All @@ -33,11 +31,9 @@ pub const Node = union(enum) {
name: []const u8,
value: []const u8,

pub fn format(self: Prop, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = options;

pub fn format(self: Prop, writer: *std.Io.Writer) std.Io.Writer.Error!void {
try writer.writeAll(@typeName(Prop));
try writer.print("{{ .depth = {}, .name = \"{s}\", .value = {any} }}", .{
try writer.print("{{ .depth = {d}, .name = \"{s}\", .value = {any} }}", .{
self.depth,
self.name,
self.value,
Expand Down Expand Up @@ -98,7 +94,7 @@ pub const NodeIterator = struct {
}

pub fn readInt(self: *NodeIterator, comptime T: type) T {
const len = @divExact(@typeInfo(T).Int.bits, 8);
const len = @divExact(@typeInfo(T).int.bits, 8);
const pos = self.offset();
const value = self.reader.buff[pos..][0..len];
self.pos += len;
Expand All @@ -114,8 +110,8 @@ pub const NodeIterator = struct {
return res[0];
}

pub fn token(self: *NodeIterator) std.meta.IntToEnumError!types.Token {
return std.meta.intToEnum(types.Token, self.readInt(u32));
pub fn token(self: *NodeIterator) error{InvalidToken}!types.Token {
return std.enums.fromInt(types.Token, self.readInt(u32)) orelse error.InvalidToken;
}

pub fn stringAt(self: *NodeIterator, off: usize) []const u8 {
Expand Down Expand Up @@ -170,21 +166,15 @@ allocator: ?Allocator,
hdr: types.Header,
buff: []const u8,

fn init(
allocator: ?Allocator,
reader: anytype,
args: anytype,
errors: anytype,
initBufferFunc: fn (?Allocator, types.Header, @TypeOf(args)) (Allocator.Error || @TypeOf(reader).NoEofError || errors)![]const u8,
) !Self {
const hdr = try reader.readStructBig(types.Header);
if (hdr.magic != types.magic) return error.InvalidMagic;

const buff = try initBufferFunc(allocator, hdr, args);
errdefer {
if (allocator) |alloc| alloc.free(buff);
fn readHeader(bytes: *const [@sizeOf(types.Header)]u8) types.Header {
var hdr: types.Header = @bitCast(bytes.*);
if (builtin.cpu.arch.endian() != std.builtin.Endian.big) {
std.mem.byteSwapAllFields(types.Header, &hdr);
}
return hdr;
}

fn finish(allocator: ?Allocator, hdr: types.Header, buff: []const u8) error{ Truncated, OverRead, InvalidToken }!Self {
const buffSize = hdr.totalsize - @sizeOf(types.Header);
if (buff.len < buffSize) return error.Truncated;
if (buff.len > buffSize) return error.OverRead;
Expand All @@ -198,41 +188,26 @@ fn init(
}

pub fn initBuffer(buff: []const u8) !Self {
var stream = std.io.fixedBufferStream(buff);
return try init(null, stream.reader(), stream, error{}, (struct {
fn func(
_: ?Allocator,
hdr: types.Header,
argStream: std.io.FixedBufferStream([]const u8),
) (Allocator.Error || std.io.FixedBufferStream([]const u8).Reader.NoEofError)![]const u8 {
return argStream.buffer[argStream.pos..hdr.totalsize];
}
}).func);
if (buff.len < @sizeOf(types.Header)) return error.Truncated;
const hdr = readHeader(buff[0..@sizeOf(types.Header)]);
if (hdr.magic != types.magic) return error.InvalidMagic;
if (buff.len < hdr.totalsize) return error.Truncated;
return finish(null, hdr, buff[@sizeOf(types.Header)..hdr.totalsize]);
}

pub fn initReader(alloc: Allocator, reader: anytype) !Self {
return try init(alloc, reader, reader, error{StreamTooLong}, (struct {
fn func(
argAlloc: ?Allocator,
hdr: types.Header,
argReader: @TypeOf(reader),
) (Allocator.Error || @TypeOf(reader).NoEofError || error{StreamTooLong})![]const u8 {
return try argReader.readAllAlloc(argAlloc.?, hdr.totalsize - @sizeOf(types.Header));
}
}).func);
pub fn initReader(alloc: Allocator, reader: *std.Io.Reader) !Self {
const hdr = readHeader(try reader.takeArray(@sizeOf(types.Header)));
if (hdr.magic != types.magic) return error.InvalidMagic;

const buff = try reader.readAlloc(alloc, hdr.totalsize - @sizeOf(types.Header));
errdefer alloc.free(buff);
return finish(alloc, hdr, buff);
}

pub fn initFile(alloc: Allocator, file: std.fs.File) !Self {
return try init(alloc, file.reader(), file, std.fs.File.ReadError || std.fs.File.MetadataError || error{FileTooBig}, (struct {
fn func(
argAlloc: ?Allocator,
_: types.Header,
argFile: std.fs.File,
) (Allocator.Error || std.fs.File.Reader.NoEofError || std.fs.File.ReadError || std.fs.File.MetadataError || error{FileTooBig})![]const u8 {
const metadata = try argFile.metadata();
return try argFile.readToEndAlloc(argAlloc.?, metadata.size() - @sizeOf(types.Header));
}
}).func);
pub fn initFile(alloc: Allocator, io: std.Io, file: std.Io.File) !Self {
var buf: [4096]u8 = undefined;
var file_reader = file.reader(io, &buf);
return initReader(alloc, &file_reader.interface);
}

pub fn deinit(self: *const Self) void {
Expand Down Expand Up @@ -337,3 +312,46 @@ pub fn findLoose(self: *const Self, path: []const []const u8) ![]const u8 {
}
return error.NotFound;
}

pub fn findAs(self: *const Self, comptime T: type, path: []const []const u8) !T {
if (T == bool) {
_ = self.find(path) catch |err| {
if (err == error.NotFound) return false;
return err;
};
return true;
}

return decode(T, try self.find(path));
}

fn decode(comptime T: type, value: []const u8) error{WrongSize}!T {
return switch (@typeInfo(T)) {
.int => |info| blk: {
if (info.signedness != .unsigned) @compileError("findAs only supports unsigned integers, got " ++ @typeName(T));
const len = @divExact(info.bits, 8);
if (value.len != len) return error.WrongSize;
break :blk std.mem.readInt(T, value[0..len], .big);
},
.pointer => |info| blk: {
if (info.size != .slice or info.child != u8) @compileError("findAs only supports []const u8 slices, got " ++ @typeName(T));
const end = std.mem.indexOfScalar(u8, value, 0) orelse value.len;
break :blk value[0..end];
},
else => @compileError("findAs does not support " ++ @typeName(T)),
};
}

test "decode reads an unsigned cell big-endian" {
try std.testing.expectEqual(@as(u32, 0x989680), try decode(u32, &.{ 0x00, 0x98, 0x96, 0x80 }));
try std.testing.expectEqual(@as(u64, 0x1), try decode(u64, &.{ 0, 0, 0, 0, 0, 0, 0, 1 }));
}

test "decode rejects a mismatched width" {
try std.testing.expectError(error.WrongSize, decode(u32, &.{ 0x00, 0x98 }));
}

test "decode strips a trailing NUL from a string" {
try std.testing.expectEqualStrings("ok", try decode([]const u8, "ok\x00"));
try std.testing.expectEqualStrings("nonul", try decode([]const u8, "nonul"));
}
24 changes: 14 additions & 10 deletions dtree/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const std = @import("std");

pub const magic: u32 = 0xd00dfeed;

pub const Header = packed struct {
pub const Header = extern struct {
magic: u32,
totalsize: u32,
off_dt_struct: u32,
Expand All @@ -14,31 +14,29 @@ pub const Header = packed struct {
size_dt_strings: u32,
size_dt_struct: u32,

pub fn format(self: Header, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = options;

pub fn format(self: Header, writer: *std.Io.Writer) std.Io.Writer.Error!void {
try writer.writeAll(@typeName(Header));
try writer.print("{{ .magic = 0x{x}, .totalsize = {}, .off_dt_struct = 0x{x}, .off_dt_strings = 0x{x}, .off_mem_rsvmap = 0x{x}, .version = {}, .last_comp_version = {}, .boot_cpuid_phys = {}, .size_dt_strings = {}, .size_dt_struct = {} }}", .{
try writer.print("{{ .magic = 0x{x}, .totalsize = {d}, .off_dt_struct = 0x{x}, .off_dt_strings = 0x{x}, .off_mem_rsvmap = 0x{x}, .version = {d}, .last_comp_version = {d}, .boot_cpuid_phys = {d}, .size_dt_strings = {d}, .size_dt_struct = {d} }}", .{
self.magic,
std.fmt.fmtIntSizeDec(self.totalsize),
self.totalsize,
self.off_dt_struct,
self.off_dt_strings,
self.off_mem_rsvmap,
self.version,
self.last_comp_version,
self.boot_cpuid_phys,
std.fmt.fmtIntSizeDec(self.size_dt_strings),
std.fmt.fmtIntSizeDec(self.size_dt_struct),
self.size_dt_strings,
self.size_dt_struct,
});
}
};

pub const ReserveEntry = packed struct {
pub const ReserveEntry = extern struct {
address: u64,
size: u64,
};

pub const Prop = packed struct {
pub const Prop = extern struct {
len: u32,
name: u32,
};
Expand All @@ -50,3 +48,9 @@ pub const Token = enum(u32) {
nop = 0x00000004,
end = 0x00000009,
};

test "on-disk layouts match the FDT spec" {
try std.testing.expectEqual(@as(usize, 40), @sizeOf(Header));
try std.testing.expectEqual(@as(usize, 16), @sizeOf(ReserveEntry));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(Prop));
}
Loading
Loading