From e8aad5b15dbcbcb892ecb5979d8c4e00b4046610 Mon Sep 17 00:00:00 2001 From: by Date: Thu, 23 Jul 2026 23:49:36 +0800 Subject: [PATCH 1/7] uf2: adapt to 0.17 API, add file tracking support --- tools/uf2/src/uf2.zig | 289 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 254 insertions(+), 35 deletions(-) diff --git a/tools/uf2/src/uf2.zig b/tools/uf2/src/uf2.zig index 9f2924f93..b70d05759 100644 --- a/tools/uf2/src/uf2.zig +++ b/tools/uf2/src/uf2.zig @@ -6,23 +6,34 @@ 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), + files: std.ArrayList(FileEntry), + // TODO: keep track of contained files - 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); + //free file name at line 236 + 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 { @@ -133,7 +144,7 @@ pub const Archive = struct { .family_id = if (opts.family_id) |family_id| family_id else - @as(FamilyId, @enumFromInt(0)), + @as(FamilyId, @enumFromInt(@as(u32, 0))), }, .data = @splat(0), }); @@ -156,17 +167,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 = .{ @@ -187,38 +202,80 @@ 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]; 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; - break; + 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) { + 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; + // 文件已读完,只复制剩余的路径 + 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 }); + } } }; @@ -327,7 +384,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, .{}); @@ -350,3 +407,165 @@ 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" { + const allocator = std.testing.allocator; + const io = std.testing.io; + var archive = Archive.init(allocator, io); + defer archive.deinit(); + + // 创建临时测试文件 + { + var file = try std.Io.Dir.cwd().createFile(io, "test1.uf2", .{}); + defer file.close(io); + // 写入一些数据 先至少一个 block,然后再写入更多数据。 + 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 {}; + + // 添加测试文件 + try archive.add_file("test1.uf2"); + try archive.add_file("test2.uf2"); + + // 验证文件记录 + 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); + + // 验证 block 范围 + const file1_blocks = archive.get_file_blocks(0).?; + try std.testing.expect(file1_blocks.start < file1_blocks.end); + + // 测试重复检测 + try std.testing.expectError(error.FileAlreadyAdded, archive.add_file("test1.uf2")); + + // 测试 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(); + + // 创建测试文件 + { + var file = try std.Io.Dir.cwd().createFile(io, "test1.uf2", .{}); + defer file.close(io); + // 写入超过 476 字节(触发多 block) + var buf: [1024]u8 = @splat(0x41); // 全部填 '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); // 全部填 '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 {}; + + // 测试 1: 添加文件 + try archive.add_file("test1.uf2"); + try archive.add_file("test2.uf2"); + try std.testing.expectEqual(@as(usize, 2), archive.files.items.len); + + // 测试 2: 验证 block 范围不重叠 + const file1 = archive.get_file_blocks(0).?; + const file2 = archive.get_file_blocks(1).?; + try std.testing.expect(file1.end <= file2.start); + + // 测试 3: 验证 block 数据 + const block0 = archive.blocks.items[file1.start]; + try std.testing.expectEqual(@as(u8, 0x41), block0.data[0]); // 文件1的数据 + if (block0.payload_size < block0.data.len) { + try std.testing.expectEqual(@as(u8, 0), block0.data[block0.payload_size]); // null terminator + } + + // 测试 4: 重复检测 + try std.testing.expectError(error.FileAlreadyAdded, archive.add_file("test1.uf2")); + + // 测试 5: has_file + try std.testing.expect(archive.has_file("test1.uf2")); + try std.testing.expect(!archive.has_file("nonexistent.uf2")); + + // 测试 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); + + // 添加文件 + { + 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 应该释放所有内存 + archive.deinit(); + + // 重新初始化验证没有内存泄漏 + 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(); + + // 创建一个长路径文件名(超过 block 容量) + const long_name = "very_long_filename_that_should_exceed_block_capacity.uf2"; + { + var file = try std.Io.Dir.cwd().createFile(io, long_name, .{}); + defer file.close(io); + var buf: [256]u8 = @splat(0); + var writer_buf: [256]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); +} From bea4b827495b2377fe79d1498c2b3ceb8984a5f8 Mon Sep 17 00:00:00 2001 From: salalaika Date: Fri, 24 Jul 2026 09:43:12 +0800 Subject: [PATCH 2/7] fix: elf2uf2,example,update_family_id. --- tools/uf2/src/elf2uf2.zig | 2 +- tools/uf2/src/example.zig | 4 ++-- tools/uf2/src/update_family_id.zig | 24 +++++++++++++++--------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tools/uf2/src/elf2uf2.zig b/tools/uf2/src/elf2uf2.zig index 8eafe8ee9..633492d7e 100644 --- a/tools/uf2/src/elf2uf2.zig +++ b/tools/uf2/src/elf2uf2.zig @@ -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, .{}); diff --git a/tools/uf2/src/example.zig b/tools/uf2/src/example.zig index fe8319a8a..b0a647a6c 100644 --- a/tools/uf2/src/example.zig +++ b/tools/uf2/src/example.zig @@ -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], .{}); @@ -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, .{}); diff --git a/tools/uf2/src/update_family_id.zig b/tools/uf2/src/update_family_id.zig index 54462a9bc..50c5361eb 100644 --- a/tools/uf2/src/update_family_id.zig +++ b/tools/uf2/src/update_family_id.zig @@ -6,19 +6,25 @@ const FamilyEntry = struct { description: []const u8, }; -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const allocator = arena.allocator(); +pub fn main(init: std.process.Init) !void { + // var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer arena.deinit(); + // const allocator = arena.allocator(); + + //const args = try std.process.argsAlloc(allocator); + const io = init.io; + var area = init.arena; + defer area.deinit(); + const allocator = area.allocator(); + const args = try init.minimal.args.toSlice(init.arena.allocator()); - const args = try std.process.argsAlloc(allocator); if (args.len != 3) return error.UsageError; - const json_data = try std.fs.cwd().readFileAlloc(allocator, args[1], 100_000); + const json_data = try std.Io.Dir.cwd().readFileAlloc(io, args[1], allocator, .limited(100_000)); - const output_file = try std.fs.cwd().createFile(args[2], .{}); - defer output_file.close(); - var output_file_writer = output_file.writer(&.{}); + const output_file = try std.Io.Dir.cwd().createFile(io, args[2], .{}); + defer output_file.close(io); + var output_file_writer = output_file.writer(io,&.{}); const entries = try std.json.parseFromSliceLeaky([]FamilyEntry, allocator, json_data, .{}); From 65ce759eaf177c3a384bd53fb31d34e8ee7e5a67 Mon Sep 17 00:00:00 2001 From: by Date: Fri, 24 Jul 2026 22:11:06 +0800 Subject: [PATCH 3/7] remove unused code. --- tools/uf2/src/update_family_id.zig | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/uf2/src/update_family_id.zig b/tools/uf2/src/update_family_id.zig index 50c5361eb..6b38d8f4d 100644 --- a/tools/uf2/src/update_family_id.zig +++ b/tools/uf2/src/update_family_id.zig @@ -7,11 +7,6 @@ const FamilyEntry = struct { }; pub fn main(init: std.process.Init) !void { - // var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - // defer arena.deinit(); - // const allocator = arena.allocator(); - - //const args = try std.process.argsAlloc(allocator); const io = init.io; var area = init.arena; defer area.deinit(); @@ -24,7 +19,7 @@ pub fn main(init: std.process.Init) !void { const output_file = try std.Io.Dir.cwd().createFile(io, args[2], .{}); defer output_file.close(io); - var output_file_writer = output_file.writer(io,&.{}); + var output_file_writer = output_file.writer(io, &.{}); const entries = try std.json.parseFromSliceLeaky([]FamilyEntry, allocator, json_data, .{}); From 8d2b0269d819ca5506a121fc0cd915ad1b1261d1 Mon Sep 17 00:00:00 2001 From: by Date: Sun, 26 Jul 2026 12:04:42 +0800 Subject: [PATCH 4/7] Address review feedback: remove stale TODO, fix line number reference, simplify redundant if, use English comments --- tools/uf2/src/uf2.zig | 65 ++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/tools/uf2/src/uf2.zig b/tools/uf2/src/uf2.zig index b70d05759..6d88892ca 100644 --- a/tools/uf2/src/uf2.zig +++ b/tools/uf2/src/uf2.zig @@ -20,8 +20,6 @@ pub const Archive = struct { families: std.AutoArrayHashMapUnmanaged(FamilyId, void), files: std.ArrayList(FileEntry), - // TODO: keep track of contained files - pub fn init(allocator: std.mem.Allocator, io: std.Io) Archive { return .{ .allocator = allocator, .blocks = .empty, .families = .empty, .files = .empty, .io = io }; } @@ -29,7 +27,6 @@ pub const Archive = struct { pub fn deinit(self: *Archive) void { self.blocks.deinit(self.allocator); self.families.deinit(self.allocator); - //free file name at line 236 for (self.files.items) |*file| { self.allocator.free(file.name); } @@ -208,19 +205,17 @@ pub const Archive = struct { 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) { - 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; - } + 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 { - // 文件已读完,只复制剩余的路径 + // 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); @@ -413,11 +408,11 @@ test "File tracking" { 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); - // 写入一些数据 先至少一个 block,然后再写入更多数据。 + // 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); @@ -437,23 +432,23 @@ test "File tracking" { } 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); - // 验证 block 范围 + // 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")); - // 测试 has_file + // Test has_file try std.testing.expect(archive.has_file("test1.uf2")); try std.testing.expect(!archive.has_file("test3.uf2")); } @@ -463,12 +458,12 @@ test "File tracking comprehensive" { 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); - // 写入超过 476 字节(触发多 block) - var buf: [1024]u8 = @splat(0x41); // 全部填 'A' + // 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); @@ -479,7 +474,7 @@ test "File tracking comprehensive" { { var file = try std.Io.Dir.cwd().createFile(io, "test2.uf2", .{}); defer file.close(io); - var buf: [512]u8 = @splat(0x42); // 全部填 'B' + 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); @@ -487,31 +482,31 @@ test "File tracking comprehensive" { } defer std.Io.Dir.cwd().deleteFile(io, "test2.uf2") catch {}; - // 测试 1: 添加文件 + // 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); - // 测试 2: 验证 block 范围不重叠 + // 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); - // 测试 3: 验证 block 数据 + // Test 3: Verify block data const block0 = archive.blocks.items[file1.start]; - try std.testing.expectEqual(@as(u8, 0x41), block0.data[0]); // 文件1的数据 + 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 } - // 测试 4: 重复检测 + // Test 4: Duplicate detection try std.testing.expectError(error.FileAlreadyAdded, archive.add_file("test1.uf2")); - // 测试 5: has_file + // Test 5: has_file try std.testing.expect(archive.has_file("test1.uf2")); try std.testing.expect(!archive.has_file("nonexistent.uf2")); - // 测试 6: get_files + // 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); @@ -522,7 +517,7 @@ test "File tracking - deinit cleanup" { 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); @@ -537,10 +532,10 @@ test "File tracking - deinit cleanup" { try archive.add_file("cleanup_test.uf2"); try std.testing.expectEqual(@as(usize, 1), archive.files.items.len); - // deinit 应该释放所有内存 + // 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); @@ -552,7 +547,7 @@ test "File tracking - long path" { var archive = Archive.init(allocator, io); defer archive.deinit(); - // 创建一个长路径文件名(超过 block 容量) + // Create a long filename that exceeds block capacity const long_name = "very_long_filename_that_should_exceed_block_capacity.uf2"; { var file = try std.Io.Dir.cwd().createFile(io, long_name, .{}); From e560786e61a08127b16aca41e4d100682595212e Mon Sep 17 00:00:00 2001 From: salalaika Date: Wed, 29 Jul 2026 12:29:50 +0800 Subject: [PATCH 5/7] update test case --- tools/uf2/src/uf2.zig | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tools/uf2/src/uf2.zig b/tools/uf2/src/uf2.zig index 6d88892ca..9327e6b63 100644 --- a/tools/uf2/src/uf2.zig +++ b/tools/uf2/src/uf2.zig @@ -141,7 +141,7 @@ pub const Archive = struct { .family_id = if (opts.family_id) |family_id| family_id else - @as(FamilyId, @enumFromInt(@as(u32, 0))), + @as(FamilyId, @fromBackingInt(@intCast(@as(u32, 0)))), }, .data = @splat(0), }); @@ -402,7 +402,7 @@ 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" { +test "File tracking - basic" { const allocator = std.testing.allocator; const io = std.testing.io; var archive = Archive.init(allocator, io); @@ -452,7 +452,7 @@ test "File tracking" { try std.testing.expect(archive.has_file("test1.uf2")); try std.testing.expect(!archive.has_file("test3.uf2")); } -test "File tracking comprehensive" { +test "File tracking - comprehensive" { const allocator = std.testing.allocator; const io = std.testing.io; var archive = Archive.init(allocator, io); @@ -547,13 +547,21 @@ test "File tracking - long path" { var archive = Archive.init(allocator, io); defer archive.deinit(); - // Create a long filename that exceeds block capacity - const long_name = "very_long_filename_that_should_exceed_block_capacity.uf2"; + // 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: [256]u8 = @splat(0); - var writer_buf: [256]u8 = undefined; + 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(); @@ -563,4 +571,8 @@ test "File tracking - long path" { 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); } From 0d2e26aa930a03a7b49349111b926f13e219fb6e Mon Sep 17 00:00:00 2001 From: by Date: Wed, 29 Jul 2026 23:04:21 +0800 Subject: [PATCH 6/7] =?UTF-8?q?When=20saving=20with=20Ctrl+S,=20ZLS=20(Zig?= =?UTF-8?q?=20Language=20Server)=20automatically=20changes=20this:=20@as(F?= =?UTF-8?q?amilyId,=20@enumFromInt(0))=20into=20this:=20@as(FamilyId,=20@f?= =?UTF-8?q?romBackingInt(@intCast(0))).=20I=20don=E2=80=99t=20=20know=20wh?= =?UTF-8?q?y.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/uf2/src/uf2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/uf2/src/uf2.zig b/tools/uf2/src/uf2.zig index 9327e6b63..fd23fdf83 100644 --- a/tools/uf2/src/uf2.zig +++ b/tools/uf2/src/uf2.zig @@ -141,7 +141,7 @@ pub const Archive = struct { .family_id = if (opts.family_id) |family_id| family_id else - @as(FamilyId, @fromBackingInt(@intCast(@as(u32, 0)))), + @as(FamilyId, @enumFromInt(0)), }, .data = @splat(0), }); From c2785013e20fe23bfea1d48cb65f1b0db8b4e245 Mon Sep 17 00:00:00 2001 From: salalaika Date: Thu, 30 Jul 2026 09:40:00 +0800 Subject: [PATCH 7/7] format code. --- tools/uf2/src/elf2uf2.zig | 2 +- tools/uf2/src/example.zig | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/uf2/src/elf2uf2.zig b/tools/uf2/src/elf2uf2.zig index 633492d7e..28fc9f52b 100644 --- a/tools/uf2/src/elf2uf2.zig +++ b/tools/uf2/src/elf2uf2.zig @@ -72,7 +72,7 @@ pub fn main(init: std.process.Init) !void { else null; - var archive = uf2.Archive.init(gpa,io); + var archive = uf2.Archive.init(gpa, io); defer archive.deinit(); const elf_file = try std.Io.Dir.cwd().openFile(io, elf_path, .{}); diff --git a/tools/uf2/src/example.zig b/tools/uf2/src/example.zig index b0a647a6c..ff94d730e 100644 --- a/tools/uf2/src/example.zig +++ b/tools/uf2/src/example.zig @@ -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,io); + var archive = uf2.Archive.init(gpa, io); defer archive.deinit(); const file = try std.Io.Dir.cwd().openFile(io, args[1], .{}); @@ -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,io); + var archive: uf2.Archive = .init(gpa, io); defer archive.deinit(); try archive.read_from(&reader.interface, .{});