// SafeC Standard Library — Directories: POSIX backend (Linux, macOS, BSD)
// (see dir.h; the tree operations shared with Windows are in dir.sc)
//
// struct dirent and struct stat have no portable layout, so the fields
// read here are at per-platform offsets:
// - readdir: d_type / d_name at 18/19 (glibc), 20/21 (macOS arm64,
// 64-bit inodes), 6/8 (macOS x86_64's legacy 'readdir' symbol),
// 18/24 (FreeBSD 12+, also the fallback for other BSDs);
// - file metadata: Linux uses the statx syscall (one fixed layout on
// every architecture, and no dependency on glibc's stat symbols, which
// only exist as real symbols from glibc 2.33); macOS uses lstat
// (64-bit-inode layout on arm64, the legacy layout on x86_64); other
// systems get kind and size without stat (opendir/fopen probes) and a
// zero mtime_ns.
#pragma once
#include <std/stddef.h>
#include <std/dir.h>
#include <std/mem.h>
#include <std/io_file.h>
#include <std/collections/string.h>
#ifdef __linux__
#define SAFEC_DIRENT_TYPE_OFF 18UL
#define SAFEC_DIRENT_NAME_OFF 19UL
#else
#ifdef __APPLE__
#ifdef __aarch64__
#define SAFEC_DIRENT_TYPE_OFF 20UL
#define SAFEC_DIRENT_NAME_OFF 21UL
#else
#define SAFEC_DIRENT_TYPE_OFF 6UL
#define SAFEC_DIRENT_NAME_OFF 8UL
#endif
#else
#define SAFEC_DIRENT_TYPE_OFF 18UL
#define SAFEC_DIRENT_NAME_OFF 24UL
#endif
#endif
// d_type values (identical on Linux, macOS and the BSDs).
#define SAFEC_DT_UNKNOWN 0
#define SAFEC_DT_DIR 4
#define SAFEC_DT_REG 8
#define SAFEC_DT_LNK 10
// st_mode file type bits (identical everywhere).
#define SAFEC_S_IFMT 0xF000
#define SAFEC_S_IFDIR 0x4000
#define SAFEC_S_IFREG 0x8000
#define SAFEC_S_IFLNK 0xA000
namespace std {
// DIR is opaque; readdir's struct dirent is read as bytes at the
// SAFEC_DIRENT_* offsets.
struct CDir_ { char opaque_; };
extern CDir_* opendir(const char* path);
extern unsigned char* readdir(CDir_* dirp);
extern int closedir(CDir_* dirp);
extern int mkdir(const char* path, unsigned int mode);
extern int rmdir(const char* path);
extern int unlink(const char* path);
#ifdef __linux__
extern long syscall(long number, ...);
#ifdef __x86_64__
#define SAFEC_SYS_STATX 332L
#else
#define SAFEC_SYS_STATX 291L // aarch64, riscv64 and the other generic-syscall ABIs
#endif
#define SAFEC_AT_FDCWD (-100)
#define SAFEC_AT_SYMLINK_NOFOLLOW 0x100
#define SAFEC_STATX_BASIC_STATS 0x7ff
#else
#ifdef __APPLE__
extern int lstat(const char* path, unsigned char* buf);
#endif
#endif
// What a Dir handle points at.
struct DirPosix_ {
CDir_* dirp;
struct String path; // for entries whose d_type is DT_UNKNOWN
};
?&Dir Dir::open(const &char path) {
CDir_* d;
unsafe { d = opendir(path); }
if (d == (CDir_*)0) { return (?&Dir)null; }
struct DirPosix_* h;
unsafe {
h = (struct DirPosix_*)alloc_raw_((unsigned long)sizeof(struct DirPosix_));
h->dirp = d;
h->path = String::from(path);
return (?&Dir)h;
}
}
int Dir::next(&DirEntry out) {
unsafe {
struct DirPosix_* h = (struct DirPosix_*)self;
for (;;) {
unsigned char* ent = readdir(h->dirp);
if (ent == (unsigned char*)0) { return 0; }
const char* name = (const char*)(ent + SAFEC_DIRENT_NAME_OFF);
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { continue; }
int t = (int)ent[SAFEC_DIRENT_TYPE_OFF];
out.name.clear();
out.name.push(name);
if (t == SAFEC_DT_DIR) { out.kind = DIR_DIR; }
else if (t == SAFEC_DT_REG) { out.kind = DIR_FILE; }
else if (t == SAFEC_DT_LNK) { out.kind = DIR_LINK; }
else if (t == SAFEC_DT_UNKNOWN) {
// Some file systems don't fill d_type: ask stat.
struct String full = h->path.clone();
full.push_char('/');
full.push(name);
struct FileStat st;
file_stat(full.as_ptr(), &st);
out.kind = st.kind == DIR_NONE ? DIR_OTHER : st.kind;
full.free();
}
else { out.kind = DIR_OTHER; }
return 1;
}
}
return 0;
}
void Dir::close() { self.drop(); }
void Dir::drop() {
unsafe {
struct DirPosix_* h = (struct DirPosix_*)self;
closedir(h->dirp);
h->path.free();
dealloc(h);
}
}
static int dir_kind_from_mode_(unsigned int mode) {
unsigned int t = mode & (unsigned int)SAFEC_S_IFMT;
if (t == (unsigned int)SAFEC_S_IFDIR) { return DIR_DIR; }
if (t == (unsigned int)SAFEC_S_IFREG) { return DIR_FILE; }
if (t == (unsigned int)SAFEC_S_IFLNK) { return DIR_LINK; }
return DIR_OTHER;
}
// Little-endian field reads out of a stat buffer.
static unsigned long long dir_le_(const &unsigned char b, unsigned long off, unsigned long n) {
unsigned long long v = 0ULL;
unsafe {
for (unsigned long i = 0UL; i < n; i = i + 1UL) {
v = v | ((unsigned long long)b[off + i] << (8UL * i));
}
}
return v;
}
// Without a stat call: a directory if opendir succeeds, a file if fopen does.
static int dir_probe_stat_(const &char path, &FileStat out) {
?&Dir d = Dir::open(path);
match (d) {
case some(h): { h.close(); out.kind = DIR_DIR; return 1; }
case null: { }
}
?&File f = File::open(path, "rb");
match (f) {
case some(h): {
out.kind = DIR_FILE;
out.size = h.size();
h.close();
return 1;
}
case null: { }
}
return 0;
}
int file_stat(const &char path, &FileStat out) {
out.kind = DIR_NONE;
out.size = 0LL;
out.mtime_ns = 0LL;
unsigned char buf[256];
#ifdef __linux__
long rc;
unsafe { rc = syscall(SAFEC_SYS_STATX, SAFEC_AT_FDCWD, path, SAFEC_AT_SYMLINK_NOFOLLOW,
SAFEC_STATX_BASIC_STATS, &buf[0]); }
if (rc == 0L) {
out.kind = dir_kind_from_mode_((unsigned int)dir_le_(&buf[0], 28UL, 2UL));
out.size = (long long)dir_le_(&buf[0], 40UL, 8UL);
out.mtime_ns = (long long)dir_le_(&buf[0], 112UL, 8UL) * 1000000000LL +
(long long)dir_le_(&buf[0], 120UL, 4UL);
return 1;
}
return dir_probe_stat_(path, out); // kernels before 4.11 have no statx
#else
#ifdef __APPLE__
int rc;
unsafe { rc = lstat(path, &buf[0]); }
if (rc != 0) { return 0; }
#ifdef __aarch64__
out.kind = dir_kind_from_mode_((unsigned int)dir_le_(&buf[0], 4UL, 2UL));
out.mtime_ns = (long long)dir_le_(&buf[0], 48UL, 8UL) * 1000000000LL + (long long)dir_le_(&buf[0], 56UL, 8UL);
out.size = (long long)dir_le_(&buf[0], 96UL, 8UL);
#else
out.kind = dir_kind_from_mode_((unsigned int)dir_le_(&buf[0], 8UL, 2UL));
out.mtime_ns = (long long)dir_le_(&buf[0], 40UL, 8UL) * 1000000000LL + (long long)dir_le_(&buf[0], 48UL, 8UL);
out.size = (long long)dir_le_(&buf[0], 72UL, 8UL);
#endif
return 1;
#else
buf[0] = (unsigned char)0;
return dir_probe_stat_(path, out);
#endif
#endif
}
int dir_create(const &char path) {
int rc;
unsafe { rc = mkdir(path, 511U); } // 0777, narrowed by the umask
if (rc == 0) { return 1; }
return path_kind(path) == DIR_DIR ? 1 : 0;
}
int dir_remove(const &char path) {
int rc;
unsafe { rc = rmdir(path); }
return rc == 0 ? 1 : 0;
}
int file_remove(const &char path) {
int rc;
unsafe { rc = unlink(path); }
return rc == 0 ? 1 : 0;
}
} // namespace std