readdir-sys 0.0.0

A wrapper over POSIX `readdir`.
Documentation
/*
 * Description: Codify OS resources into traits and configure based on platform.
 * This code is largely copied from the rust compiler's sys/fs/unix.rs: https://github.com/rust-lang/rust/blob/cbfdf0b014cb04982a9cbeec1578001001167f6e/library/std/src/sys/fs/unix.rs.
 * The rust compiler is distributed under both the MIT and Apache v2 licenses.
 *
 * Copyright (C) 2025 d@nny mc² <dmc2@hypnicjerk.ai>
 * SPDX-License-Identifier: LGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#![allow(non_camel_case_types)]
#![cfg(unix)]

//! Description: Codify OS resources into traits and configure based on platform.
//!
//! This code is largely copied from the rust compiler's [`sys/fs/unix.rs`](https://github.com/rust-lang/rust/blob/cbfdf0b014cb04982a9cbeec1578001001167f6e/library/std/src/sys/fs/unix.rs).

use measure_null_str::{MeasuredNullTermStr, NullTermStr};


pub trait HasInode {
  fn ino(&self) -> libc::ino_t;
}
pub trait HasEagerType {
  fn eager_file_type(&self) -> libc::c_uchar;
}
pub trait HasNoEagerType {}
pub trait HasNameLen {
  fn name_len(&self) -> usize;
}
pub trait MeasureNameLen {
  fn measure_name_len<'s>(&self, s: NullTermStr<'s>) -> &'s MeasuredNullTermStr { s.measure() }
}
impl<M> MeasureNameLen for M
where M: HasNameLen
{
  fn measure_name_len<'s>(&self, s: NullTermStr<'s>) -> &'s MeasuredNullTermStr {
    let n = self.name_len();
    unsafe { MeasuredNullTermStr::given_measurement(s, n) }
  }
}

macro_rules! rename_imports {
  (use64) => {
    pub use libc::{dirent64, fstatat64, ino64_t, openat64, readdir64, stat64};
  };
  (rename64) => {
    pub use libc::{
      dirent as dirent64, fstatat as fstatat64, ino_t as ino64_t, openat as openat64,
      readdir as readdir64, stat as stat64,
    };
  };
}
macro_rules! generate_dirent {
    () => {
      #[derive(Copy, Clone, Debug)]
      pub struct dirent64_min {}
      impl dirent64_min {
        pub unsafe const fn new(_entry_ptr: *const dirent64) -> Self { Self {} }
      }
    };
    (d_ino) => {
      #[derive(Copy, Clone, Debug)]
      pub struct dirent64_min {
        pub d_ino: libc::ino64_t,
      }
      impl dirent64_min {
        pub unsafe fn new(entry_ptr: *const dirent64) -> Self {
          unsafe {
            Self {
              // When loading from a field, we can skip the `&raw const`; `(*entry_ptr).d_ino` as
              // a value expression will do the right thing: `byte_offset` to the field and then
              // only access those bytes.
              d_ino: (*entry_ptr).ino(),
            }
          }
        }
      }
      impl HasInode for dirent64_min {
        fn ino(&self) -> libc::ino64_t { self.d_ino }
      }
    };
    (d_ino, d_type) => {
      #[derive(Copy, Clone, Debug)]
      pub struct dirent64_min {
        pub d_ino: libc::ino64_t,
        pub d_type: libc::c_uchar,
      }
      impl dirent64_min {
        pub unsafe fn new(entry_ptr: *const dirent64) -> Self {
          unsafe {
            Self {
              d_ino: (*entry_ptr).ino(),
              d_type: (*entry_ptr).eager_file_type(),
            }
          }
        }
      }
      impl HasInode for dirent64_min {
        fn ino(&self) -> libc::ino64_t { self.d_ino }
      }
      impl HasEagerType for dirent64_min {
        fn eager_file_type(&self) -> libc::c_uchar { self.d_type }
      }
    };
    (d_ino, d_type, d_namlen) => {
      #[derive(Copy, Clone, Debug)]
      pub struct dirent64_min {
        pub d_ino: libc::ino64_t,
        pub d_type: libc::c_uchar,
        pub d_namlen: libc::c_uchar,
      }
      impl dirent64_min {
        pub unsafe fn new(entry_ptr: *const dirent64) -> Self {
          unsafe {
            Self {
              d_ino: (*entry_ptr).ino(),
              d_type: (*entry_ptr).eager_file_type(),
              d_namlen: (*entry_ptr).name_len(),
            }
          }
        }
      }
      impl HasInode for dirent64_min {
        fn ino(&self) -> libc::ino64_t { self.d_ino }
      }
      impl HasEagerType for dirent64_min {
        fn eager_file_type(&self) -> libc::c_uchar { self.d_type }
      }
      impl HasNameLen for dirent64_min {
        fn name_len(&self) -> usize { self.d_namlen }
      }
    };
  }
macro_rules! generate_imports {
  (no_r, $use64:tt) => {
    generate_dirent!();
    rename_imports!($use64);
  };
  (no_r,d_ino, $use64:tt) => {
    generate_dirent!(d_ino);
    rename_imports!($use64);
  };
  (no_r,d_ino,d_type, $use64:tt) => {
    generate_dirent!(d_ino, d_type);
    rename_imports!($use64);
  };
  (no_r,d_ino,d_type,d_namlen, $use64:tt) => {
    generate_dirent!(d_ino, d_type, d_namlen);
    rename_imports!($use64);
  };
}

cfg_if::cfg_if! {
  if #[cfg(target_os = "android")] {
    generate_imports!(no_r, d_ino, d_type, rename64);
  } else if #[cfg(target_os = "vita")] {
    generate_imports!(no_r, rename64);
  } else if #[cfg(any(
    target_os = "solaris",
    target_os = "illumos",
    target_os = "aix",
    target_os = "nto",
  ))] {
    generate_imports!(no_r, d_ino, rename64);
  } else if #[cfg(any(target_os = "fuchsia", target_os = "redox"))] {
    generate_imports!(no_r, d_ino, d_type, rename64);
  } else if #[cfg(target_os = "hurd")] {
    generate_imports!(no_r, d_ino, d_type, d_namlen, use64);
  } else if #[cfg(target_os = "linux")] {
    cfg_if::cfg_if! {
      if #[cfg(target_env = "musl")] {
        generate_imports!(no_r, d_ino, d_type, rename64);
      } else {
        generate_imports!(no_r, d_ino, d_type, use64);
      }
    }
  } else if #[cfg(target_os = "l4re")] {
    generate_imports!(no_r, d_ino, d_type, use64);
  } else if #[cfg(target_os = "nuttx")] {
    generate_imports!(no_r, rename64);
  } else if #[cfg(any(target_os = "haiku", target_os = "vxworks"))] {
    generate_imports!(no_r, d_ino, rename64);
  } else if #[cfg(any(
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_vendor = "apple",
  ))] {
    generate_imports!(no_r, d_ino, d_type, d_namlen, rename64);
  } else {
    generate_imports!(no_r, d_ino, d_type, rename64);
  }
}

cfg_if::cfg_if! {
  if #[cfg(any(
    target_os = "freebsd",
    target_os = "openbsd",
    target_os = "netbsd",
    target_os = "dragonfly"
  ))] {
    impl HasInode for dirent64 {
      fn ino(&self) -> libc::ino64_t { self.d_fileno }
    }
  } else if #[cfg(any(target_os = "vita", target_os = "nuttx"))] {
  } else {
    impl HasInode for dirent64 {
      fn ino(&self) -> libc::ino64_t { self.d_ino }
    }
  }
}

cfg_if::cfg_if! {
  if #[cfg(any(
      target_os = "solaris",
      target_os = "illumos",
      target_os = "haiku",
      target_os = "vxworks",
      target_os = "aix",
      target_os = "nto",
      target_os = "vita",
  ))] {
    impl HasNoEagerType for dirent64 {}
  } else {
    impl HasEagerType for dirent64 {
      fn eager_file_type(&self) -> libc::c_uchar { self.d_type }
    }
  }
}

cfg_if::cfg_if! {
  if #[cfg(any(
    target_os = "hurd",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_vendor = "apple",
  ))] {
    impl HasNameLen for dirent64 {
      fn name_len(&self) -> usize { self.d_namlen }
    }
  } else {
    impl MeasureNameLen for dirent64 {}
    impl MeasureNameLen for dirent64_min {}
  }
}