Fail to open POSIX file

hi, any idea why this bit of code fails to open a file ?

    pub fn dump_prg(self: Self) !void {
        const bufferSize = 100;
        var path: [bufferSize]u8 = [_]u8{0} ** bufferSize;
        _ = std.fmt.bufPrint(&path, "{s}.prg", .{self.name}) catch {
            path[bufferSize - 4] = '.';
            path[bufferSize - 3] = 'p';
            path[bufferSize - 2] = 'r';
            path[bufferSize - 1] = 'g';
        };

        std.log.info("Writing prg data to {s}", .{path});

        const outfile = try std.fs.cwd().createFile(&path, .{});
        defer outfile.close();
        const writer = outfile.writer();

        for (self.prg.items) |byte| {
            try writer.writeByte(byte);
        }
    }

I get the following error but it’s really not helping me that much

info: Writing prg data to tutor.prg
thread 1087418 panic: reached unreachable code
/opt/homebrew/Cellar/zig/0.14.1/lib/zig/std/debug.zig:550:14: 0x102af7c93 in assert (Nes)
    if (!ok) unreachable; // assertion failure
             ^
/opt/homebrew/Cellar/zig/0.14.1/lib/zig/std/posix.zig:7513:41: 0x102b13fa7 in toPosixPath (Nes)
    if (std.debug.runtime_safety) assert(mem.indexOfScalar(u8, file_path, 0) == null);
                                        ^
/opt/homebrew/Cellar/zig/0.14.1/lib/zig/std/fs/Dir.zig:995:41: 0x102b9029b in createFile (Nes)
    const path_c = try posix.toPosixPath(sub_path);
                                        ^
/Users/poss/code/misc/nes/src/Cartridge.zig:119:52: 0x102b8fdff in dump_prg (Nes)
        const outfile = try std.fs.cwd().createFile(&path, .{});
                                                   ^
/Users/poss/code/misc/nes/src/main.zig:18:27: 0x102b93b6f in main (Nes)
    try cartridge.dump_prg();
                          ^
/opt/homebrew/Cellar/zig/0.14.1/lib/zig/std/start.zig:660:37: 0x102b952d7 in main (Nes)
            const result = root.main() catch |err| {

you can see from the top line (the last line printed by the program) that the path string is correctly formatted, but it won’t create the file

ps: self.name is a []const u8

thanks!

also if there is a better way to format my path string please do tell me about it

Path contains zero bytes. You should not discard bufPrint return but use it.

but how does the log print access the path variable if it contains 0 bytes ?

Because the path you have provided contains the null character, which has the numeric code of 0.
You can see the assertion second from the top of the stack trace.

This is because bufPrint inserts at the start of the buffer and doesn’t touch the rest of it.
And you are passing the entire buffer to createFile.

Simply use the success result of bufPrint it’s a slice that points to just the part of the buffer that contains what it printed.

Your catch on bufPrint is also highly questionable, I would recommend one of the following:

pub fn dump_prg(self: Self) !void {
        const bufferSize = 100;
        var path_buf: [bufferSize]u8 = [_]u8{0} ** bufferSize;
        const path = std.fmt.bufPrint(&path_buf, "{s}.prg", .{self.name}) catch "name_too_long.prg";
    //...
}

or

pub fn dump_prg(self: Self) !void {
    const bufferSize = 100;
    var path_buf: [bufferSize]u8 = [_]u8{0} ** bufferSize;
    // truncate the name so it's gaurunteed to fit in the buffer
    const name = self.name[0..@min(self.name.len, bufferSize - 4)];
    const path = std.fmt.bufPrint(&path_buf, "{s}.prg", .{self.name}) catch unreachable;
    //...
}
4 Likes

The reason text shouldn’t contain the null character (0) is because It’s used to indicate the end of text, when you don’t have a length, for example when giving a file name to the OS to open/create it.
This is a holdover from when memory was more limited, it’s still common practice in c.

ah thank you very much, I was looking at that assertion and I did NOT understand it because I thought having a 0 at the end was the whole point (I come from C, I was about to handcode some kind of memcpy solution)

what if I want to truncate the name however ? If I read things correctly, the only possible error is NoSpaceLeft, in which case as much is written as possible, so it should be correct although it looks disgusting, I am open to suggestions

log.info prints all the 100 characters in path, most of them are zero bytes and are invisible.

When createFile tries to create the file, it detects that path contains zero bytes that are invalid characters for filenames.


The problem is that in the code, the return value of bufPrint is ignored, but this is a slice (a pair of pointer and length) with the correct length of the path written in buffer.

zig uses slices which are pointer + length, the std library prioritises the length property over null termination even when in this case it would work as its being lowered to the OS assuming your OS uses null termination when passing text to it, but it could result in unexpected behaviour so zig warns you.

I argue that you generally shouldn’t rely on the state of data under a failure condition. As that could, and IMO this case should, change.

Instead of using self.name for bufPrint, you can use:

const maxPath = bufferSize - ".prg".len;
self.name[0..@min(name.len, maxPath)-1]

or

const maxPath = bufferSize - ".prg".len;
if (self.name.len > maxPath) self.name[0..maxPath-1] else self.name

EDIT: introduced maxPath in place of bufferSize

1 Like

I appreciate everyone’s help, thank you very much I now understand why a null terminated string was the wrong move here

I am still open to an implementation that does what I want: open a file whose name is "{name}.nes" with a possible truncation of name

@dimdin and i have provided examples of truncating with slices you can take from :slight_smile:

1 Like

@dimdin 's answer arrived while I was typing, and I misread yours, thank you both I appreciate it !

PS: @vulpesx do you have any connection to streamer sodapoppin by any chance

Oh, sorry I missed that:

const name = self.name[0..@min(self.name.len, bufferSize - 4)];

in Fail to open POSIX file - #5 by vulpesx

2 Likes

Nope, I barely know they exist, curious why you thought I am connected to them

he has a moderator called Vulpes, but turns out y’all just like foxes

So that’s why I needed to add x to my twitch name lmao
perhaps i should read first

1 Like