///|
pub fn init_worktree_probe() -> Unit {
let sync_meta : (&@bit.RepoFileSystem, String) -> @bitio.WorktreeEntryMeta? = fn(
_fs,
abs_path,
) {
native_lstat_meta(abs_path)
}
let provider = @bitio.WorktreeProbeProvider::new(
fn(
_fs : &@bit.RepoFileSystem,
abs_path : String,
) -> @bitio.WorktreeEntryMeta? noraise {
native_lstat_meta(abs_path)
},
fn(path : String) { native_read_symlink_target_path(path) },
entry_meta_sync=sync_meta,
lstat=native_lstat_meta,
readdir_typed=native_readdir_typed,
)
@bitio.set_worktree_probe(provider)
}
///|
fn native_lstat_meta(abs_path : String) -> @bitio.WorktreeEntryMeta? {
let path_buf = native_string_to_cstring(abs_path)
let buf = Bytes::new(44)
let ret = bit_lstat(path_buf, buf)
if ret < 0 {
return None
}
// Read st_mode (uint32 LE at offset 0)
let raw_mode = buf[0].to_int() |
(buf[1].to_int() << 8) |
(buf[2].to_int() << 16) |
(buf[3].to_int() << 24)
// File type from st_mode
let file_type = raw_mode & 0xF000
// S_IFLNK = 0xA000, S_IFREG = 0x8000, S_IFDIR = 0x4000
if file_type == 0xA000 {
// Symlink
let mtime_sec = native_read_i64_le(buf, 12)
let mtime_nsec = native_read_i64_le(buf, 20)
let dev = native_read_u32_le(buf, 28)
let ino = native_read_u32_le(buf, 32)
let uid = native_read_u32_le(buf, 36)
let gid = native_read_u32_le(buf, 40)
return Some(
@bitio.WorktreeEntryMeta::new(
@bitio.WorktreeKindMeta::symlink(),
0o120000,
None,
Some(mtime_sec),
Some(mtime_nsec),
dev=Some(dev),
ino=Some(ino),
uid=Some(uid),
gid=Some(gid),
),
)
}
if file_type != 0x8000 {
// Not a regular file (directory, etc.)
return None
}
// Regular file
let exec = (raw_mode & 0o111) != 0
let git_mode = if exec { 0o100755 } else { 0o100644 }
let size = native_read_i64_le(buf, 4)
let mtime_sec = native_read_i64_le(buf, 12)
let mtime_nsec = native_read_i64_le(buf, 20)
let dev = native_read_u32_le(buf, 28)
let ino = native_read_u32_le(buf, 32)
let uid = native_read_u32_le(buf, 36)
let gid = native_read_u32_le(buf, 40)
Some(
@bitio.WorktreeEntryMeta::new(
@bitio.WorktreeKindMeta::regular(),
git_mode,
Some(size),
Some(mtime_sec),
Some(mtime_nsec),
dev=Some(dev),
ino=Some(ino),
uid=Some(uid),
gid=Some(gid),
),
)
}
///|
fn native_read_u32_le(buf : Bytes, offset : Int) -> Int {
buf[offset].to_int() |
(buf[offset + 1].to_int() << 8) |
(buf[offset + 2].to_int() << 16) |
(buf[offset + 3].to_int() << 24)
}
///|
fn native_read_i64_le(buf : Bytes, offset : Int) -> Int {
buf[offset].to_int() |
(buf[offset + 1].to_int() << 8) |
(buf[offset + 2].to_int() << 16) |
(buf[offset + 3].to_int() << 24) |
(buf[offset + 4].to_int() << 32) |
(buf[offset + 5].to_int() << 40) |
(buf[offset + 6].to_int() << 48) |
(buf[offset + 7].to_int() << 56)
}
///|
fn native_read_symlink_target_path(path : String) -> String? {
native_try_readlink(path)
}
///|
fn native_try_readlink(path : String) -> String? {
let path_buf = native_string_to_cstring(path)
let mut buf_len = 256
while buf_len <= 131072 {
let buf = Bytes::new(buf_len)
let read_len = readlink(path_buf, buf, buf.length())
if read_len < 0 {
return None
}
if read_len < buf.length() {
return buf[:read_len] |> @utf8.decode_lossy |> Some
}
buf_len *= 2
}
let buf = Bytes::new(131072)
let read_len = readlink(path_buf, buf, buf.length())
if read_len < 0 {
None
} else {
buf[:read_len] |> @utf8.decode_lossy |> Some
}
}
///|
fn native_string_to_cstring(path : String) -> Bytes {
let encoded = @utf8.encode(path.to_string_view())
Bytes::add(encoded, Bytes::make(1, b'\x00'))
}
///|
#borrow(path_buf, buf)
extern "c" fn readlink(path_buf : Bytes, buf : Bytes, size : Int) -> Int = "readlink"
///|
#borrow(path_buf, buf)
extern "c" fn bit_lstat(path_buf : Bytes, buf : Bytes) -> Int = "bit_lstat"
///|
#borrow(path_buf, buf)
extern "c" fn bit_readdir(path_buf : Bytes, buf : Bytes, buf_size : Int) -> Int = "bit_readdir"
///|
/// Read directory entries with d_type info via native readdir.
/// Returns array of (name, d_type) pairs where d_type: 4=DT_DIR, 8=DT_REG, 10=DT_LNK, 0=DT_UNKNOWN.
pub fn native_readdir_typed(path : String) -> Array[(String, Int)]? {
let path_buf = native_string_to_cstring(path)
let buf_size = 65536
let buf = Bytes::new(buf_size)
let ret = bit_readdir(path_buf, buf, buf_size)
if ret < 0 {
return None
}
let total = ret
let result : Array[(String, Int)] = []
let mut pos = 0
while pos + 3 <= total {
let name_len = buf[pos].to_int() | (buf[pos + 1].to_int() << 8)
let d_type = buf[pos + 2].to_int()
if pos + 3 + name_len > total {
break
}
let name = @utf8.decode_lossy(buf[pos + 3:pos + 3 + name_len])
result.push((name, d_type))
pos = pos + 3 + name_len
}
Some(result)
}