Skip to content

dirent is wrong on OSX #414

@philippkeller

Description

@philippkeller

While trying to get readdir working on OSX (which is to be preferred over readdir_r, as readdir_r is deprecated), I ran into the error that d_name always seemed off by 2 bytes, e.g. for /var/tmp/ it showed:

.
bash_profile

X�
�

etc.

I suspected that something is wrong with the dirent struct which is defined in libc:

    pub struct dirent {
        pub d_ino: u64,
        pub d_seekoff: u64,
        pub d_reclen: u16,
        pub d_namlen: u16,
        pub d_type: u8,
        pub d_name: [::c_char; 1024],
    }

But when running bindgen on /usr/include/dirent.h on OSX I get this:

#[repr(C)]
#[derive(Copy)]
pub struct dirent {
    pub d_ino: __uint64_t,
    pub d_seekoff: __uint64_t,
    pub d_reclen: __uint16_t,
    pub d_namlen: __uint16_t,
    pub d_type: __uint8_t,
    pub d_name: [::std::os::raw::c_char; 1024usize],
}

Which is basically the same but still somehow wrong. Now, when I take the binding from freebsd it works. The freebsd binding (from libc):

    pub struct dirent {
        pub d_fileno: u32,
        pub d_reclen: u16,
        pub d_type: u8,
        pub d_namlen: u8,
        pub d_name: [::c_char; 256],
    }

Here's an example with the right and the wrong binding:

extern crate libc;

use std::ffi::{CString, CStr};
use std::str;
use libc::{opendir, closedir, DIR, dirent, c_char};

extern "C" {
    pub fn readdir(arg1: *mut DIR) -> *mut dirent;
}

pub struct my_dirent {
    pub d_fileno: u32,
    pub d_reclen: u16,
    pub d_type: u8,
    pub d_namlen: u8,
    pub d_name: [c_char; 256],
}

fn main() {
    let dir = CString::new("/usr").unwrap();
    unsafe {
        let dp = opendir(dir.as_ptr());
        let mut dirp = readdir(dp);
        while !dirp.is_null() {
            // fails
            println!("wrong: {:?}", CStr::from_ptr(&(*dirp).d_name[0]));

            // succeeds
            let dirp2 = &mut *(dirp as *mut my_dirent);
            println!("fileno: {}, reclen: {}, type: {}, namelen: {}, name: {:?}", 
                (*dirp2).d_fileno,
                (*dirp2).d_reclen,
                (*dirp2).d_type,
                (*dirp2).d_namlen,
                CStr::from_ptr(&(*dirp2).d_name[0]));
            dirp = readdir(dp);
        }
        closedir(dp);
    }
}

Output:

wrong: "."
fileno: 36788336, reclen: 12, type: 4, namelen: 1, name: "."
wrong: "bash_profile"
fileno: 36384337, reclen: 12, type: 4, namelen: 2, name: ".."
wrong: ""
fileno: 67850380, reclen: 24, type: 8, namelen: 13, name: ".bash_profile"
wrong: "X\xad\x04\x14"
fileno: 80172149, reclen: 20, type: 8, namelen: 9, name: ".DS_Store"
wrong: "\x16\xad\x04\x0c"
fileno: 78469237, reclen: 20, type: 4, namelen: 8, name: "__MACOSX"

etc. And the output of ls -ali as proof that also the other fields are mapped correctly:

36788336 drwxrwxrwt  106 root     wheel        3604 Oct  3 15:41 .
36384337 drwxr-xr-x   26 root     wheel         884 Sep  3 04:43 ..
67850380 -rw-------    1 philipp  wheel        3768 Jun  3 01:13 .bash_profile
80172149 -rw-r--r--@   1 philipp  wheel        6148 Sep 28 11:57 .DS_Store
78469237 drwxrwxr-x@   3 philipp  wheel         102 Feb 24  2014 __MACOSX

Anyone knows what's wrong here?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions