Skip to content

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

Closed
@sno2

Description

@sno2

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions