Skip to content
Open
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
2 changes: 1 addition & 1 deletion tools/uf2/src/elf2uf2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn main(init: std.process.Init) !void {
else
null;

var archive = uf2.Archive.init(gpa);
var archive = uf2.Archive.init(gpa, io);
defer archive.deinit();

const elf_file = try std.Io.Dir.cwd().openFile(io, elf_path, .{});
Expand Down
4 changes: 2 additions & 2 deletions tools/uf2/src/example.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn main(init: std.process.Init) !void {
const args = try init.minimal.args.toSlice(init.arena.allocator());

if (args.len == 3) {
var archive = uf2.Archive.init(gpa);
var archive = uf2.Archive.init(gpa, io);
defer archive.deinit();

const file = try std.Io.Dir.cwd().openFile(io, args[1], .{});
Expand All @@ -33,7 +33,7 @@ pub fn main(init: std.process.Init) !void {
var buf: [4096]u8 = undefined;
var reader = file.reader(io, &buf);

var archive: uf2.Archive = .init(gpa);
var archive: uf2.Archive = .init(gpa, io);
defer archive.deinit();
try archive.read_from(&reader.interface, .{});

Expand Down
294 changes: 260 additions & 34 deletions tools/uf2/src/uf2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,31 @@ pub const FamilyId = @import("family_id.zig").FamilyId;

const prog_page_size = 256;

pub const FileEntry = struct {
name: []const u8,
start_block: u64,
end_block: u64,
family_id: ?FamilyId,
};

pub const Archive = struct {
allocator: Allocator,
io: std.Io,
blocks: std.ArrayList(Block),
families: std.AutoArrayHashMapUnmanaged(FamilyId, void),
// TODO: keep track of contained files
files: std.ArrayList(FileEntry),

pub fn init(allocator: std.mem.Allocator) Archive {
return .{
.allocator = allocator,
.blocks = .empty,
.families = .empty,
};
pub fn init(allocator: std.mem.Allocator, io: std.Io) Archive {
return .{ .allocator = allocator, .blocks = .empty, .families = .empty, .files = .empty, .io = io };
}

pub fn deinit(self: *Archive) void {
self.blocks.deinit(self.allocator);
self.families.deinit(self.allocator);
for (self.files.items) |*file| {
self.allocator.free(file.name);
}
self.files.deinit(self.allocator);
}

pub fn write_to(self: *Archive, writer: *std.Io.Writer) !void {
Expand Down Expand Up @@ -156,17 +164,21 @@ pub const Archive = struct {
}

pub fn add_file(self: *Archive, path: []const u8) !void {
if (self.has_file(path)) return error.FileAlreadyAdded;
const file = if (std.fs.path.isAbsolute(path))
try std.fs.openFileAbsolute(path, .{})
try std.Io.Dir.openFileAbsolute(self.io, path, .{})
else
try std.fs.cwd().openFile(path, .{});
defer file.close();
try std.Io.Dir.cwd().openFile(self.io, path, .{});
defer file.close(self.io);

const file_size = (try file.metadata()).size();
const file_size = @as(u32, @intCast((try file.stat(self.io)).size));
const start_block = @as(u32, @intCast(self.blocks.items.len));

var path_pos: u32 = 0;

var target_addr: u32 = 0;
var read_buf: [512]u8 = undefined;
var reader = file.reader(self.io, &read_buf);
while (true) {
try self.blocks.append(self.allocator, .{
.flags = .{
Expand All @@ -187,38 +199,78 @@ pub const Archive = struct {
});
errdefer _ = self.blocks.pop();

const block = &self.blocks.items[self.blocks.len - 1];
const block = &self.blocks.items[self.blocks.items.len - 1];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did this ever work?

if (target_addr < file_size) {
// copying file content into block
const n_read = file.reader().readAll(&block.data) catch |err| switch (err) {
error.EndOfStream => 0,
else => return err,
};

target_addr += n_read;
block.payload_size = n_read;
if (n_read != @sizeOf(block.data)) {
std.mem.copy(u8, block.data[n_read..], path);
path_pos = @min(block.len - n_read, path.len);
if (n_read + path_pos < block.data.len) {
// write null terminator too and we're done
block.data[n_read + path_pos] = 0;
const n_read = try reader.interface.readSliceShort(block.data[0..]);
target_addr += @as(u32, @intCast(n_read));
block.payload_size = @as(u32, @intCast(n_read));
if (n_read < block.data.len) {
const copy_len = @min(block.data.len - n_read, path.len);
path_pos = @intCast(copy_len);
@memcpy(block.data[n_read..][0..copy_len], path[0..copy_len]);
if (n_read + copy_len < block.data.len) {
block.data[n_read + copy_len] = 0;
break;
}
}
} else {
// copying null terminated path into block, likely crossing a
// block boundary
std.mem.copy(u8, &block.data, path[path_pos..]);
const n_copied = @min(block.data.len, path[path_pos..].len);
path_pos += n_copied;
if (n_copied < block.data.len) {
// write null terminator and peace out
block.data[n_copied] = 0;
// File fully read, copy remaining path
const copy_len = @min(block.data.len, path.len - path_pos);
@memcpy(block.data[0..copy_len], path[path_pos..][0..copy_len]);
path_pos += @intCast(copy_len);
if (copy_len < block.data.len) {
block.data[copy_len] = 0;
break;
}
}
}
// add file record
try self.files.append(self.allocator, .{
.name = try self.allocator.dupe(u8, path),
.start_block = start_block,
.end_block = @as(u32, @intCast(self.blocks.items.len)),
.family_id = null,
});
}

pub fn get_files(self: *const Archive) []const FileEntry {
return self.files.items;
}

pub fn has_file(self: *const Archive, name: []const u8) bool {
for (self.files.items) |file| {
if (std.mem.eql(u8, file.name, name)) {
return true;
}
}
return false;
}

pub fn get_file(self: *const Archive, name: []const u8) ?FileEntry {
for (self.files.items) |file| {
if (std.mem.eql(u8, file.name, name)) {
return file;
}
}
return null;
}

pub fn get_file_blocks(self: *const Archive, file_index: usize) ?struct { start: u64, end: u64 } {
if (file_index >= self.files.items.len) return null;
const file = self.files.items[file_index];
return .{
.start = file.start_block,
.end = file.end_block,
};
}

pub fn print_files(self: *const Archive) void {
std.log.info("Archive contains {d} files:", .{self.files.items.len});
for (self.files.items, 0..) |file, i| {
const blocks = file.end_block - file.start_block;
std.log.info(" [{d}] {s}: {d} blocks", .{ i, file.name, blocks });
}
}
};

Expand Down Expand Up @@ -327,7 +379,7 @@ test "Archive read and write" {

var data_1_reader: std.Io.Reader = .fixed(data_1.written());

var archive = Archive.init(std.testing.allocator);
var archive = Archive.init(std.testing.allocator, std.testing.io);
defer archive.deinit();

try archive.read_from(&data_1_reader, .{});
Expand All @@ -350,3 +402,177 @@ test "Archive read and write" {
var data_2_reader: std.Io.Reader = .fixed(data_2.written());
try std.testing.expectError(error.FamilyIdCollision, archive.read_from(&data_2_reader, .{}));
}
test "File tracking - basic" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var archive = Archive.init(allocator, io);
defer archive.deinit();

// Create temporary test files
{
var file = try std.Io.Dir.cwd().createFile(io, "test1.uf2", .{});
defer file.close(io);
// Write at least one block, then more data
var buf: [512]u8 = @splat(0);
var writer_buf: [512]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, "test1.uf2") catch {};

{
var file = try std.Io.Dir.cwd().createFile(io, "test2.uf2", .{});
defer file.close(io);
var buf: [512]u8 = @splat(0);
var writer_buf: [512]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, "test2.uf2") catch {};

// Add test files
try archive.add_file("test1.uf2");
try archive.add_file("test2.uf2");

// Verify file records
try std.testing.expectEqual(@as(usize, 2), archive.files.items.len);
try std.testing.expectEqualStrings("test1.uf2", archive.files.items[0].name);
try std.testing.expectEqualStrings("test2.uf2", archive.files.items[1].name);

// Verify block range
const file1_blocks = archive.get_file_blocks(0).?;
try std.testing.expect(file1_blocks.start < file1_blocks.end);

// Test duplicate detection
try std.testing.expectError(error.FileAlreadyAdded, archive.add_file("test1.uf2"));

// Test has_file
try std.testing.expect(archive.has_file("test1.uf2"));
try std.testing.expect(!archive.has_file("test3.uf2"));
}
test "File tracking - comprehensive" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var archive = Archive.init(allocator, io);
defer archive.deinit();

// Create test files
{
var file = try std.Io.Dir.cwd().createFile(io, "test1.uf2", .{});
defer file.close(io);
// Write more than 476 bytes (triggers multiple blocks)
var buf: [1024]u8 = @splat(0x41); // Fill with 'A'
var writer_buf: [1024]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, "test1.uf2") catch {};

{
var file = try std.Io.Dir.cwd().createFile(io, "test2.uf2", .{});
defer file.close(io);
var buf: [512]u8 = @splat(0x42); // Fill with 'B'
var writer_buf: [512]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, "test2.uf2") catch {};

// Test 1: Add files
try archive.add_file("test1.uf2");
try archive.add_file("test2.uf2");
try std.testing.expectEqual(@as(usize, 2), archive.files.items.len);

// Test 2: Verify block ranges don't overlap
const file1 = archive.get_file_blocks(0).?;
const file2 = archive.get_file_blocks(1).?;
try std.testing.expect(file1.end <= file2.start);

// Test 3: Verify block data
const block0 = archive.blocks.items[file1.start];
try std.testing.expectEqual(@as(u8, 0x41), block0.data[0]); // File 1 data
if (block0.payload_size < block0.data.len) {
try std.testing.expectEqual(@as(u8, 0), block0.data[block0.payload_size]); // null terminator
}

// Test 4: Duplicate detection
try std.testing.expectError(error.FileAlreadyAdded, archive.add_file("test1.uf2"));

// Test 5: has_file
try std.testing.expect(archive.has_file("test1.uf2"));
try std.testing.expect(!archive.has_file("nonexistent.uf2"));

// Test 6: get_files
const files = archive.get_files();
try std.testing.expectEqual(@as(usize, 2), files.len);
try std.testing.expectEqualStrings("test1.uf2", files[0].name);
}

test "File tracking - deinit cleanup" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var archive = Archive.init(allocator, io);

// Add file
{
var file = try std.Io.Dir.cwd().createFile(io, "cleanup_test.uf2", .{});
defer file.close(io);
var buf: [512]u8 = @splat(0);
var writer_buf: [512]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, "cleanup_test.uf2") catch {};

try archive.add_file("cleanup_test.uf2");
try std.testing.expectEqual(@as(usize, 1), archive.files.items.len);

// deinit should free all memory
archive.deinit();

// Reinitialize to verify no memory leak
var archive2 = Archive.init(allocator, io);
defer archive2.deinit();
try std.testing.expectEqual(@as(usize, 0), archive2.files.items.len);
}

test "File tracking - long path" {
const allocator = std.testing.allocator;
const io = std.testing.io;
var archive = Archive.init(allocator, io);
defer archive.deinit();

// Create a file that's almost a full block (476 bytes), so that
// even a short path requires crossing into a new block.
// remaining space = 476 - file_size, path must exceed this.
const file_size = 400;
const path_len = 77; // 77 > 476 - 400 = 76, crosses block boundary

var long_name_buf: [path_len]u8 = undefined;
@memset(long_name_buf[0 .. path_len - 4], 'x');
@memcpy(long_name_buf[path_len - 4 ..], ".uf2");
const long_name = long_name_buf[0..path_len];
{
var file = try std.Io.Dir.cwd().createFile(io, long_name, .{});
defer file.close(io);
var buf: [file_size]u8 = @splat(0);
var writer_buf: [512]u8 = undefined;
var writer = file.writer(io, &writer_buf);
try writer.interface.writeAll(&buf);
try writer.flush();
}
defer std.Io.Dir.cwd().deleteFile(io, long_name) catch {};

try archive.add_file(long_name);
try std.testing.expectEqual(@as(usize, 1), archive.files.items.len);
try std.testing.expectEqualStrings(long_name, archive.files.items[0].name);

// Verify path spans multiple blocks
const file_blocks = archive.get_file_blocks(0).?;
try std.testing.expect(file_blocks.end > file_blocks.start);
}
Loading
Loading