use crate::ported::utils::{metafy, unmeta, zwarn};
use std::fs::OpenOptions;
use std::io;
use std::io::Write;
use std::os::unix::fs::MetadataExt;
use std::io::Read;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(unix)]
pub fn setpmmapfile(name: &str, value: &str, readonly: bool) { let name_unmeta = unmeta(name); let value_unmeta = unmeta(value); let value_bytes = value_unmeta.as_bytes();
let len = value_bytes.len();
if readonly {
return; }
let file = match OpenOptions::new()
.read(true).write(true).create(true).truncate(false)
.open(&name_unmeta)
{
Ok(f) => f,
Err(_) => return, };
let fd = file.as_raw_fd();
if len == 0 {
return;
}
let mmptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
len,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
)
};
if mmptr == libc::MAP_FAILED {
if let Ok(mut fout) = OpenOptions::new().write(true).create(true).truncate(true)
.open(&name_unmeta)
{
let _ = fout.write_all(value_bytes); }
return;
}
unsafe {
if libc::ftruncate(fd, len as libc::off_t) < 0 { zwarn(&format!("ftruncate failed: {}", io::Error::last_os_error()));
}
std::ptr::copy_nonoverlapping(value_bytes.as_ptr(), mmptr as *mut u8, len);
libc::msync(mmptr, len, libc::MS_SYNC);
if libc::ftruncate(fd, len as libc::off_t) < 0 { zwarn(&format!("ftruncate failed: {}", io::Error::last_os_error()));
}
libc::munmap(mmptr, len);
}
}
#[cfg(not(unix))]
pub fn setpmmapfile(name: &str, value: &str, readonly: bool) { if readonly { return; } let name_unmeta = unmeta(name);
let value_unmeta = unmeta(value);
if let Ok(mut fout) = OpenOptions::new().write(true).create(true).truncate(true)
.open(&name_unmeta)
{
let _ = fout.write_all(value_unmeta.as_bytes()); }
}
pub fn unsetpmmapfile(pm: &str, exp: bool) { let fname = unmeta(pm); if !exp { let _ = std::fs::remove_file(&fname); }
}
pub fn setpmmapfiles(entries: &[(String, String)], readonly: bool) { if entries.is_empty() { return; }
if !readonly { for (name, value) in entries { setpmmapfile(name, value, readonly); }
}
}
#[cfg(unix)]
pub fn get_contents(fname: &str) -> Option<String> { let fname_unmeta = unmeta(fname);
let file = match OpenOptions::new().read(true).open(&fname_unmeta) {
Ok(f) => f, Err(_) => return None, };
let metadata = match file.metadata() {
Ok(m) => m,
Err(_) => return None, };
let size = metadata.size() as usize;
if size == 0 {
return Some(metafy(""));
}
let fd = file.as_raw_fd();
let mmptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ,
libc::MAP_PRIVATE,
fd,
0,
)
};
if mmptr == libc::MAP_FAILED {
let mut contents = Vec::new();
let mut file = file;
if file.read_to_end(&mut contents).is_err() {
return None;
}
let raw = String::from_utf8_lossy(&contents); return Some(metafy(&raw));
}
let slice = unsafe { std::slice::from_raw_parts(mmptr as *const u8, size) };
let raw = String::from_utf8_lossy(slice);
let val = metafy(&raw);
unsafe { libc::munmap(mmptr, size); }
Some(val)
}
#[cfg(not(unix))]
pub fn get_contents(fname: &str) -> Option<String> { let fname_unmeta = unmeta(fname);
let raw = std::fs::read_to_string(&fname_unmeta).ok()?;
Some(metafy(&raw))
}
pub fn getpmmapfile(name: &str) -> Option<String> { get_contents(name) }
pub fn scanpmmapfile() -> Vec<(String, String)> { let mut out = Vec::new();
let dir = match std::fs::read_dir(".") { Ok(d) => d,
Err(_) => return out, };
for entry in dir.flatten() { if let Some(n) = entry.file_name().to_str() {
if n == "." || n == ".." { continue; }
out.push((n.to_string(), String::new())); }
}
out
}
use crate::ported::zsh_h::module;
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { 0
}
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
#[test]
fn getpmmapfile_nonexistent_returns_none() {
assert!(getpmmapfile("/nonexistent/file/path/zshrs_mapfile").is_none());
}
#[test]
fn file_roundtrip() {
let test_file = "/tmp/zshrs_mapfile_test_roundtrip.txt";
let content = "Hello, mapfile!";
let _ = fs::remove_file(test_file);
setpmmapfile(test_file, content, false);
let read_content = get_contents(test_file).expect("file should exist");
assert_eq!(read_content, content);
let _ = fs::remove_file(test_file);
}
#[test]
fn empty_value_creates_file() {
let test_file = "/tmp/zshrs_mapfile_test_empty.txt";
let _ = fs::remove_file(test_file);
setpmmapfile(test_file, "", false);
assert!(Path::new(test_file).exists());
let read_content = get_contents(test_file).expect("file should exist");
assert!(read_content.is_empty());
let _ = fs::remove_file(test_file);
}
#[test]
fn scanpmmapfile_skips_dotdirs_and_returns_empty_values() {
let entries = scanpmmapfile();
for (name, val) in &entries {
assert!(name != "." && name != "..");
assert!(val.is_empty(),
"scanpmmapfile values are always empty per c:263");
}
}
#[test]
fn unsetpmmapfile_removes_file() {
let test_file = "/tmp/zshrs_mapfile_test_unset.txt";
let _ = fs::write(test_file, "content");
unsetpmmapfile(test_file, false);
assert!(!Path::new(test_file).exists());
}
#[test]
fn unsetpmmapfile_readonly_skips() {
let test_file = "/tmp/zshrs_mapfile_test_unset_ro.txt";
let _ = fs::write(test_file, "content");
unsetpmmapfile(test_file, true);
assert!(Path::new(test_file).exists());
let _ = fs::remove_file(test_file);
}
#[test]
fn setpmmapfile_readonly_skips_write() {
let test_file = "/tmp/zshrs_mapfile_test_set_ro.txt";
let _ = fs::remove_file(test_file);
setpmmapfile(test_file, "should not be written", true);
assert!(!Path::new(test_file).exists());
}
#[test]
fn setpmmapfiles_writes_entries() {
let f1 = "/tmp/zshrs_mapfile_bulk_1.txt";
let f2 = "/tmp/zshrs_mapfile_bulk_2.txt";
let _ = fs::remove_file(f1);
let _ = fs::remove_file(f2);
let entries = vec![
(f1.to_string(), "one".to_string()),
(f2.to_string(), "two".to_string()),
];
setpmmapfiles(&entries, false);
assert_eq!(get_contents(f1).as_deref(), Some("one"));
assert_eq!(get_contents(f2).as_deref(), Some("two"));
let _ = fs::remove_file(f1);
let _ = fs::remove_file(f2);
}
}
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 0,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 1,
n_abstract: 0,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["p:mapfile".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}