Skip to content

std.c.dirent has incorrect field types for ino and off with 64-bit file offsets on linux #23622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sno2 opened this issue Apr 20, 2025 · 0 comments · Fixed by #23637
Closed
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@sno2
Copy link
Contributor

sno2 commented Apr 20, 2025

Zig Version

0.14.0

Steps to Reproduce and Observed Behavior

The following Zig program tries to use std.c.readdir to iterate through a directory.

const std = @import("std");

pub fn main() !void {
    const dir = std.c.opendir(".") orelse return error.OpenFailed;
    defer _ = std.c.closedir(dir);
    while (std.c.readdir(dir)) |entry| {
        std.log.info("null terminated={s}", .{std.mem.sliceTo(entry.name[0..], 0)});
        std.log.info("full={s}", .{entry.name});
    }
}

I would expect the null terminated=X to print out the name of the file, just like this C program:

#include <dirent.h>
#include <stdio.h>
#include <string.h>

int main() {
    DIR *dir = opendir(".");
    if (dir == NULL) {
        perror("opendir failed");
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

Here is the result of running these programs:

$ ls
a.out  build.zig  build.zig.zon  main.c  src  zig-out
$ zig build run
info: null terminated=���v
                           
info: full=���v
�:P��Y+.���&�b� zig-outo��8�n��z�srcwF�
�.main.c�����$�r/a.out-��~��q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cac
info: null terminated=��z�
�:P��Y+.���&�b�srcwF�
�.main.c�����$�r/a.out-��~��q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=P��Y+
info: full=P��Y+.���&�b�
�.main.c�����$�r/a.out-��~��q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=�
�. 
info: full=�
�.main.c�����$�r/a.out-��~��q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=$�r/ 
info: full=$�r/a.out-��~��q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=q��6(
info: full=q��6build.zig.zon#��?&>�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=�\w\ 
info: full=�\w\build.zig�<�xE���|�..���������� .zig-cache
info: null terminated=��|�
info: full=��|�..���������� .zig-cache
info: null terminated=���� 
info: full=���� .zig-cache

Expected Behavior

I would expect both the null terminated=X to print the same value as for the C program:

$ gcc main.c && ./a.out
zig-out
src
.
main.c
a.out
build.zig.zon
build.zig
..
.zig-cache

Here is the dirent in source:

    extern struct {
        ino: c_uint,
        off: c_uint,
        reclen: c_ushort,
        type: u8,
        name: [256]u8,
    }

I think the ino and the off should sometimes be u64 and i64, respectively. For example, this Zig program works fine:

const std = @import("std");

const dirent = extern struct {
    ino: u64,
    off: i64,
    reclen: c_ushort,
    type: u8,
    name: [256]u8,
};

extern "c" fn readdir(dir: *std.c.DIR) ?*dirent;

pub fn main() !void {
    const dir = std.c.opendir(".") orelse return error.OpenFailed;
    defer _ = std.c.closedir(dir);
    while (readdir(dir)) |entry| {
        std.debug.print("{s}\n", .{std.mem.sliceTo(entry.name[0..], 0)});
    }
}
$ zig build run
zig-out
src
.
main.c
a.out
build.zig.zon
build.zig
..
.zig-cache

I am willing to contribute to the change, but I am unsure how to detect if we should be using 64-bit ino_t and off_t. For example, here is the code from glibc:

struct dirent
  {
#ifndef __USE_FILE_OFFSET64
    __ino_t d_ino;
    __off_t d_off;
#else
    __ino64_t d_ino;
    __off64_t d_off;
#endif
    unsigned short int d_reclen;
    unsigned char d_type;
    char d_name[256];		/* We must not include limits.h! */
  };

Thanks

@sno2 sno2 added the bug Observed behavior contradicts documented or intended behavior label Apr 20, 2025
@alexrp alexrp closed this as completed in 573d9aa Apr 26, 2025
tiawl pushed a commit to tiawl/zig that referenced this issue May 1, 2025
Fixes ziglang#23622. The integer types used for these fields before would not
work on some platforms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant