diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 17abfb8..bf632c4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v1 with: - version: 0.14.0 + version: 0.15.2 - name: Build Demo run: | diff --git a/build.zig b/build.zig index 796c21d..8cecee0 100644 --- a/build.zig +++ b/build.zig @@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const link_libc = !(b.option(bool, "no-libc", "Prevents linking of libc by default") orelse false); + const link_libc = b.option(bool, "libc", "Forces the linking of libc"); const config = blk: { var config = Config{}; @@ -91,11 +91,11 @@ pub fn build(b: *std.Build) void { } if (maybe_volume_names) |volume_names| { - var volumes = std.ArrayList([]const u8).init(b.allocator); + var volumes: std.ArrayList([]const u8) = .empty; var iter = std.mem.splitScalar(u8, volume_names, ','); while (iter.next()) |name| { - volumes.append(name) catch @panic("out of memory"); + volumes.append(b.allocator, name) catch @panic("out of memory"); } config.volumes = .{ .named = volumes.items }; } @@ -109,20 +109,20 @@ pub fn build(b: *std.Build) void { config.sector_size = .{ .dynamic = .{ .minimum = std.meta.stringToEnum(SectorOption, min) orelse bad_config( - "Invalid value for -Dsector-size: '{}'", - .{std.zig.fmtEscapes(sector_config)}, + "Invalid value for -Dsector-size: '{f}'", + .{std.zig.fmtString(sector_config)}, ), .maximum = std.meta.stringToEnum(SectorOption, max) orelse bad_config( - "Invalid value for -Dsector-size: '{}'", - .{std.zig.fmtEscapes(sector_config)}, + "Invalid value for -Dsector-size: '{f}'", + .{std.zig.fmtString(sector_config)}, ), }, }; } else { config.sector_size = .{ .static = std.meta.stringToEnum(SectorOption, sector_config) orelse bad_config( - "Invalid value for -Dsector-size: '{}'", - .{std.zig.fmtEscapes(sector_config)}, + "Invalid value for -Dsector-size: '{f}'", + .{std.zig.fmtString(sector_config)}, ), }; } @@ -136,7 +136,7 @@ pub fn build(b: *std.Build) void { .style = .blank, .include_path = "ffconf.h", }, .{ - .FFCONF_DEF = 5380, + .FFCONF_DEF = 5385, }); switch (config.volumes) { @@ -147,13 +147,13 @@ pub fn build(b: *std.Build) void { }); }, .named => |strings| { - var list = std.ArrayList(u8).init(b.allocator); + var list: std.ArrayList(u8) = .empty; for (strings) |name| { if (list.items.len > 0) { - list.appendSlice(", ") catch @panic("out of memory"); + list.appendSlice(b.allocator, ", ") catch @panic("out of memory"); } - list.writer().print("\"{}\"", .{ - std.fmt.fmtSliceHexUpper(name), + list.writer(b.allocator).print("\"{X}\"", .{ + name, }) catch @panic("out of memory"); } config_header.addValues(.{ @@ -198,28 +198,27 @@ pub fn build(b: *std.Build) void { } // module: - const upstream = b.dependency("fatfs", .{}); const mod_options = b.addOptions(); mod_options.addOption(bool, "has_rtc", (config.rtc != .static)); // create copy of the upstream without ffconf.h const upstream_copy = b.addWriteFiles(); - _ = upstream_copy.addCopyFile(upstream.path("source/ff.c"), "ff.c"); - _ = upstream_copy.addCopyFile(upstream.path("source/ff.h"), "ff.h"); - _ = upstream_copy.addCopyFile(upstream.path("source/diskio.h"), "diskio.h"); - _ = upstream_copy.addCopyFile(upstream.path("source/ffunicode.c"), "ffunicode.c"); - _ = upstream_copy.addCopyFile(upstream.path("source/ffsystem.c"), "ffsystem.c"); + _ = upstream_copy.addCopyFile(b.path("vendor/fatfs/source/ff.c"), "ff.c"); + _ = upstream_copy.addCopyFile(b.path("vendor/fatfs/source/ff.h"), "ff.h"); + _ = upstream_copy.addCopyFile(b.path("vendor/fatfs/source/diskio.h"), "diskio.h"); + _ = upstream_copy.addCopyFile(b.path("vendor/fatfs/source/ffunicode.c"), "ffunicode.c"); + _ = upstream_copy.addCopyFile(b.path("vendor/fatfs/source/ffsystem.c"), "ffsystem.c"); const upstream_copy_dir = upstream_copy.getDirectory(); - const zfat_lib_mod = b.createModule(.{ + const zfat_mod = b.addModule("zfat", .{ + .root_source_file = b.path("src/fatfs.zig"), .target = target, .optimize = optimize, .link_libc = link_libc, }); - zfat_lib_mod.addCSourceFiles(.{ + zfat_mod.addCSourceFiles(.{ .root = upstream_copy_dir, - .files = &.{ "ff.c", "ffunicode.c", @@ -227,33 +226,23 @@ pub fn build(b: *std.Build) void { }, .flags = &.{"-std=c99"}, }); - zfat_lib_mod.addConfigHeader(config_header); - - const zfat = b.addLibrary(.{ - .linkage = .static, - .name = "zfat", - .root_module = zfat_lib_mod, - }); - zfat.installHeader(upstream_copy_dir.path(b, "ff.h"), "ff.h"); - zfat.installHeader(upstream_copy_dir.path(b, "diskio.h"), "diskio.h"); - zfat.installConfigHeader(config_header); - - const zfat_mod = b.addModule("zfat", .{ - .root_source_file = b.path("src/fatfs.zig"), - .target = target, - .optimize = optimize, - }); - zfat_mod.linkLibrary(zfat); + zfat_mod.addIncludePath(upstream_copy_dir.path(b, ".")); + zfat_mod.addConfigHeader(config_header); zfat_mod.addOptions("config", mod_options); // usage demo: const exe = b.addExecutable(.{ .name = "zfat-demo", - .root_source_file = b.path("demo/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("demo/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + .imports = &.{ + .{ .name = "zfat", .module = zfat_mod }, + }, + }), }); - exe.root_module.addImport("zfat", zfat_mod); const demo_exe = b.addInstallArtifact(exe, .{}); demo_step.dependOn(&demo_exe.step); @@ -285,7 +274,7 @@ fn add_config_field(config_header: *std.Build.Step.ConfigHeader, config: Config, } fn add_config_option(b: *std.Build, config: *Config, comptime field: @TypeOf(.tag), desc: []const u8) void { - const T = std.meta.FieldType(Config, field); + const T = @FieldType(Config, @tagName(field)); if (b.option(T, @tagName(field), desc)) |value| @field(config, @tagName(field)) = value; } diff --git a/build.zig.zon b/build.zig.zon index 0668383..7ea505d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,12 +2,7 @@ .name = .zfat, .fingerprint = 0x4212c7b5f54ad348, .version = "0.15.0", - .dependencies = .{ - .fatfs = .{ - .url = "http://elm-chan.org/fsw/ff/arc/ff15a.zip", - .hash = "N-V-__8AAFQITQCnpmdR7PARImvk-cgb-9lZmjKolexSWkUL", - }, - }, + .dependencies = .{}, .paths = .{ "build.zig", "build.zig.zon", diff --git a/demo/main.zig b/demo/main.zig index 1088d5b..9069c90 100644 --- a/demo/main.zig +++ b/demo/main.zig @@ -30,7 +30,33 @@ pub fn main() !u8 { var file = try fatfs.File.create("0:/firmware.uf2"); defer file.close(); - try file.writer().writeAll("Hello, World!\r\n"); + var buffer: [64]u8 = undefined; + + const test_string = "Hello, World!\r\n"; + + var writer = file.writer(&buffer); + try writer.writer.writeAll(test_string); + try writer.writer.flush(); + + try std.testing.expectEqual(true, file.endOfFile()); + try std.testing.expectEqual(test_string.len, file.size()); + + try file.seekTo(test_string.len / 2); + try std.testing.expectEqual(false, file.endOfFile()); + try std.testing.expectEqual(test_string.len / 2, file.tell()); + try std.testing.expectEqual(test_string.len, file.size()); + + try std.testing.expectEqual(false, file.hasError()); + + try file.rewind(); + try std.testing.expectEqual(0, file.tell()); + + var reader = file.reader(&buffer); + const written_string = try reader.reader.take(test_string.len); + try std.testing.expectEqualStrings(test_string, written_string); + + _ = try file.sync(); + _ = try file.truncate(); } return 0; diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 1ba4434..0000000 --- a/flake.lock +++ /dev/null @@ -1,147 +0,0 @@ -{ - "nodes": { - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1718229064, - "narHash": "sha256-ZFav8A9zPNfjZg/wrxh1uZeMJHELRfRgFP+meq01XYk=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "5c2ec3a5c2ee9909904f860dadc19bc12cd9cc44", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1708161998, - "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "84d981bae8b5e783b3b548de505b22880559515f", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "zig": "zig" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "zig": { - "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1718324667, - "narHash": "sha256-AZGskEGjvUmeb+fgBv4lxtCUtXmYBI+ABOlV+og9X14=", - "owner": "mitchellh", - "repo": "zig-overlay", - "rev": "b2c14e5f842af6b2bf03e634f73fd84f6956d4ba", - "type": "github" - }, - "original": { - "owner": "mitchellh", - "repo": "zig-overlay", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 49ae555..0000000 --- a/flake.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - description = "TurtleFont, a small vector graphics font file."; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11"; - flake-utils.url = "github:numtide/flake-utils"; - zig.url = "github:mitchellh/zig-overlay"; - }; - - outputs = { - self, - nixpkgs, - flake-utils, - ... - } @ inputs: let - overlays = [ - # Other overlays - (final: prev: { - zigpkgs = inputs.zig.packages.${prev.system}; - }) - ]; - - # Our supported systems are the same supported systems as the Zig binaries - systems = builtins.attrNames inputs.zig.packages; - in - flake-utils.lib.eachSystem systems ( - system: let - pkgs = import nixpkgs {inherit overlays system;}; - in let - zig = pkgs.zigpkgs."0.13.0"; - in rec { - packages.default = pkgs.stdenv.mkDerivation { - name = "turtlefont"; - src = ./.; - nativeBuildInputs = [ - zig - pkgs.gdb - ]; - - configurePhase = ""; - - buildPhase = '' - zig build - ''; - - installPhase = '' - mv zig-out $out - ''; - }; - } - ); -} diff --git a/src/fatfs.zig b/src/fatfs.zig index 2211cfc..f86d8aa 100644 --- a/src/fatfs.zig +++ b/src/fatfs.zig @@ -186,7 +186,7 @@ pub const Dir = struct { pub const ReadDirError = ErrorSet(&.{ FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT, FR_NOT_ENOUGH_CORE }); pub fn next(dir: *Self) ReadDirError.Error!?FileInfo { - var res: c.FILINFO = undefined; + var res: c.FILINFO = .{}; try ReadDirError.throw(api.readdir(&dir.raw, &res)); if (res.fname[0] == 0) return null; @@ -222,10 +222,7 @@ pub const Attributes = packed struct(u8) { std.debug.assert(@bitOffsetOf(Attributes, "archive") == 5); } - pub fn format(attrs: Attributes, fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - + pub fn format(attrs: Attributes, writer: *std.Io.Writer) !void { var keys = std.BoundedArray([]const u8, 8){}; if (attrs.read_only) keys.appendAssumeCapacity("read_only"); if (attrs.hidden) keys.appendAssumeCapacity("hidden"); @@ -285,10 +282,7 @@ pub const FileInfo = struct { pub const max_name_len = if (@hasDecl(c, "FF_LFN_BUF")) c.FF_LFN_BUF else 12; pub const max_altname_len = if (@hasDecl(c, "FF_SFN_BUF")) c.FF_SFN_BUF else 0; - pub fn format(info: FileInfo, fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - + pub fn format(info: FileInfo, writer: *std.Io.Writer) !void { try writer.print( \\{s}{{ .size={}, .date = {}, .time = {}, .kind = .{s}, .attributes = {}, .name = '{}', .altname = '{}' }} , .{ @@ -338,9 +332,7 @@ pub const Date = struct { }); } - pub fn format(date: Date, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = opt; + pub fn format(date: Date, writer: *std.Io.Writer) !void { try writer.print("{d:0>4}-{d:0>2}-{d:0>2}", .{ date.year, @intFromEnum(date.month), @@ -393,9 +385,7 @@ pub const Time = struct { }); } - pub fn format(time: Time, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = opt; + pub fn format(time: Time, writer: *std.Io.Writer) !void { try writer.print("{d:0>2}:{d:0>2}:{d:0>2}", .{ time.hour, time.minute, @@ -537,14 +527,71 @@ pub const File = struct { return written; } - pub const Reader = std.io.Reader(*Self, ReadError.Error, read); - pub fn reader(file: *Self) Reader { - return Reader{ .context = file }; + pub const Reader = struct { + file: *Self, + err: ?ReadError.Error = null, + reader: std.Io.Reader, + }; + + pub const Writer = struct { + file: *Self, + err: ?WriteError.Error = null, + writer: std.Io.Writer, + }; + + pub fn reader(file: *Self, buffer: []u8) Reader { + return .{ + .file = file, + .reader = .{ + .buffer = buffer, + .seek = 0, + .end = 0, + .vtable = comptime &.{ + .stream = reader_stream, + }, + }, + }; } - pub const Writer = std.io.Writer(*Self, WriteError.Error, write); - pub fn writer(file: *Self) Writer { - return Writer{ .context = file }; + pub fn writer(file: *Self, buffer: []u8) Writer { + return .{ + .file = file, + .writer = .{ + .buffer = buffer, + .vtable = &.{ + .drain = writer_drain, + }, + }, + }; + } + + fn reader_stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize { + const wrap: *Reader = @fieldParentPtr("reader", r); + + const buffer = limit.slice(try w.writableSliceGreedy(1)); + + const len = wrap.file.read(buffer) catch |err| { + wrap.err = err; + return error.ReadFailed; + }; + if (len == 0) + return error.EndOfStream; + w.advance(len); + return len; + } + + fn writer_drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + _ = splat; + const wrap: *Writer = @fieldParentPtr("writer", w); + const buffered = w.buffered(); + if (buffered.len != 0) return w.consume(wrap.file.write(buffered) catch |err| { + wrap.err = err; + return error.WriteFailed; + }); + return wrap.file.write(data[0]) catch |err| { + wrap.err = err; + return error.WriteFailed; + }; } }; diff --git a/vendor/fatfs/README.md b/vendor/fatfs/README.md deleted file mode 100644 index c5ddf05..0000000 --- a/vendor/fatfs/README.md +++ /dev/null @@ -1 +0,0 @@ -Offline copy of the download http://www.elm-chan.org/fsw/ff/00index_e.html with the original source files. diff --git a/vendor/fatfs/documents/00index_e.html b/vendor/fatfs/documents/00index_e.html index 81185ac..a984b5f 100644 --- a/vendor/fatfs/documents/00index_e.html +++ b/vendor/fatfs/documents/00index_e.html @@ -17,7 +17,7 @@
-FatFs is a generic FAT/exFAT filesystem module for small embedded systems. The FatFs module is written in compliance with ANSI C (C89) and completely separated from the disk I/O layer. Therefore it is independent of the platform. It can be incorporated into small microcontrollers with limited resource, such as 8051, PIC, AVR, ARM, Z80, RX and etc. Also Petit FatFs module for tiny microcontrollers is available here.
+FatFs is a generic FAT/exFAT filesystem module for small embedded systems. The FatFs module is written in compliance with ANSI C (C89) and completely separated from the disk control layer. Therefore it is independent of the platforms and storage devices. It can be incorporated into small microcontrollers with limited resource, such as 8051, PIC, AVR, ARM, Z80, RX and etc. Also Petit FatFs module for tiny microcontrollers is available here🔗.
-Since FatFs module is the Filesystem Layer independent of platforms and storage media, it is completely separated from the physical devices, such as memory card, harddisk and any type of storage device. The storage device control module is not any part of FatFs module and it needs to be provided by implementer. FatFs controls the storage devices via a simple media access interface shown below. Also sample implementations for some platforms are available in the downloads. A function checker for storage device control module is available here.
+Since FatFs module is the Filesystem Layer independent of platforms and storage media, it is completely separated from the physical devices, such as memory card, harddisk and any type of storage device. The storage device control module is not a part of FatFs module and it needs to be provided by implementer. FatFs controls the storage devices via a simple media access interface shown below. Also sample implementations for some platforms are available in the downloads. A function checker for storage device control module is available here.
The FatFs module is a free software opened for education, research and development. You can use, modify and/or redistribute it for any purpose without any restriction under your responsibility. For further information, refer to the application note.