ezlua/binding/
fs.rs

1use crate::prelude::*;
2use alloc::string::String;
3use std::{
4    fs::{self, FileTimes, Metadata},
5    path::Path,
6    time::{Duration, SystemTime},
7};
8
9pub fn open(lua: &LuaState) -> LuaResult<LuaTable> {
10    let module = lua.new_table()?;
11
12    module.set_closure("chdir", std::env::set_current_dir::<&str>)?;
13    module.set_closure("currentdir", std::env::current_dir)?;
14
15    module.set_closure("dir", |dir: &str| {
16        fs::read_dir(dir).map(|iter| StaticIter::new(iter.flatten().map(|e| e.file_name())))
17    })?;
18
19    module.set_closure("link", |old: &str, new: &str, symbol: Option<bool>| {
20        #[allow(deprecated)]
21        if symbol.unwrap_or(false) {
22            fs::soft_link(old, new)
23        } else {
24            fs::hard_link(old, new)
25        }
26        .lua_result()
27    })?;
28
29    module.set_closure("readlink", |path: &str| NilError(std::fs::read_link(path)))?;
30    module.set_closure("copy", std::fs::copy::<&str, &str>)?;
31    module.set_closure("rename", std::fs::rename::<&str, &str>)?;
32    module.set_closure("removedir", std::fs::remove_dir::<&str>)?;
33    module.set_closure("remove", std::fs::remove_file::<&str>)?;
34
35    module.set_closure("mkdir", std::fs::create_dir::<&str>)?;
36    module.set_closure("mkdirs", std::fs::create_dir_all::<&str>)?;
37    module.set_closure("rmdir", std::fs::remove_dir::<&str>)?;
38    module.set_closure("rmdir_all", std::fs::remove_dir_all::<&str>)?;
39
40    module.set(
41        "attributes",
42        lua.new_closure2(|lua, path: &Path, arg2: Option<LuaValue>| {
43            if !path.exists() {
44                return LuaResult::Ok(LuaValue::Nil);
45            }
46
47            let arg2 = arg2.unwrap_or(LuaValue::Nil);
48            let mut res = None;
49            let mut key = None;
50            match arg2 {
51                LuaValue::Nil => {}
52                LuaValue::Table(t) => {
53                    res.replace(t);
54                }
55                _ => {
56                    key.replace(arg2);
57                }
58            }
59            let meta = fs::metadata(path).lua_result()?;
60            let res = lua_attribute(lua, res, path, meta)?;
61            if let Some(key) = key {
62                res.get(key).map(ValRef::into_value)
63            } else {
64                Ok(LuaValue::Table(res))
65            }
66        })?,
67    )?;
68
69    module.set(
70        "symlinkattributes",
71        lua.new_closure(|lua, path: &Path, arg2: Option<LuaString>| {
72            if !path.exists() {
73                return LuaResult::Ok(LuaValue::Nil);
74            }
75
76            let meta = fs::symlink_metadata(path).lua_result()?;
77            let res = lua_attribute(lua, None, path, meta)?;
78            res.set(
79                "target",
80                fs::read_link(path)
81                    .map(|p| p.to_string_lossy().into_owned())
82                    .unwrap_or_default(),
83            )?;
84
85            if let Some(key) = arg2 {
86                res.get(key).map(ValRef::into_value)
87            } else {
88                LuaResult::Ok(LuaValue::Table(res))
89            }
90        })?,
91    )?;
92
93    module.set_closure("touch", |file: &str, t: Option<f64>, a: Option<f64>| {
94        let file = fs::File::open(file).lua_result()?;
95        file.set_times(
96            FileTimes::new()
97                .set_modified(
98                    t.map(|t| SystemTime::UNIX_EPOCH + Duration::from_secs_f64(t))
99                        .unwrap_or(SystemTime::now()),
100                )
101                .set_accessed(
102                    a.map(|t| SystemTime::UNIX_EPOCH + Duration::from_secs_f64(t))
103                        .unwrap_or(SystemTime::now()),
104                ),
105        )
106        .lua_result()
107    })?;
108
109    Ok(module)
110}
111
112fn lua_attribute<'a>(
113    lua: &'a LuaState,
114    res: Option<LuaTable<'a>>,
115    path: &Path,
116    meta: Metadata,
117) -> LuaResult<LuaTable<'a>> {
118    let res = if let Some(res) = res {
119        res
120    } else {
121        lua.new_table()?
122    };
123    res.set(
124        "access",
125        meta.accessed()
126            .lua_result()?
127            .duration_since(SystemTime::UNIX_EPOCH)
128            .map(|x| x.as_secs())
129            .unwrap_or(0),
130    )?;
131    res.set(
132        "modification",
133        meta.modified()
134            .lua_result()?
135            .duration_since(SystemTime::UNIX_EPOCH)
136            .map(|x| x.as_secs())
137            .unwrap_or(0),
138    )?;
139
140    #[cfg(unix)]
141    {
142        use std::os::unix::fs::MetadataExt;
143
144        res.set("uid", meta.uid())?;
145        res.set("gid", meta.gid())?;
146        res.set("dev", meta.dev())?;
147        res.set("ino", meta.ino())?;
148        res.set("rdev", meta.rdev())?;
149        res.set("blocks", meta.blocks())?;
150    }
151
152    #[cfg(windows)]
153    {
154        res.set(
155            "created",
156            meta.created()
157                .lua_result()?
158                .duration_since(SystemTime::UNIX_EPOCH)
159                .map(|x| x.as_secs())
160                .unwrap_or(0),
161        )?;
162    }
163
164    res.set("readonly", meta.permissions().readonly())?;
165
166    res.set("size", meta.len())?;
167
168    let mut perms = [b'-'; 9];
169    #[cfg(windows)]
170    res.set("st_mode", unsafe {
171        use alloc::vec::Vec;
172        use std::os::windows::ffi::OsStrExt;
173
174        let mut path = path.as_os_str().encode_wide().collect::<Vec<_>>();
175        path.push(0);
176        let mut st = std::mem::zeroed();
177        let mode = if libc::wstat(path.as_ptr(), &mut st) == 0 {
178            st.st_mode as i32
179        } else {
180            0
181        };
182
183        if mode & libc::S_IREAD != 0 {
184            perms[0] = b'r';
185            perms[3] = b'r';
186            perms[6] = b'r';
187        }
188        if mode & libc::S_IWRITE != 0 {
189            perms[1] = b'w';
190            perms[4] = b'w';
191            perms[7] = b'w';
192        }
193        if mode & libc::S_IEXEC != 0 {
194            perms[2] = b'x';
195            perms[5] = b'x';
196            perms[8] = b'x';
197        }
198        mode
199    })?;
200    #[cfg(unix)]
201    res.set("st_mode", {
202        use std::os::unix::fs::PermissionsExt;
203
204        let mode = meta.permissions().mode() as libc::mode_t;
205        if mode & libc::S_IRUSR != 0 {
206            perms[0] = b'r';
207        }
208        if mode & libc::S_IWUSR != 0 {
209            perms[1] = b'w';
210        }
211        if mode & libc::S_IXUSR != 0 {
212            perms[2] = b'x';
213        }
214        if mode & libc::S_IRGRP != 0 {
215            perms[3] = b'r';
216        }
217        if mode & libc::S_IWGRP != 0 {
218            perms[4] = b'w';
219        }
220        if mode & libc::S_IXGRP != 0 {
221            perms[5] = b'x';
222        }
223        if mode & libc::S_IROTH != 0 {
224            perms[6] = b'r';
225        }
226        if mode & libc::S_IWOTH != 0 {
227            perms[7] = b'w';
228        }
229        if mode & libc::S_IXOTH != 0 {
230            perms[8] = b'x';
231        }
232        mode
233    })?;
234    res.set("permissions", String::from_utf8_lossy(&perms[..]))?;
235
236    if meta.file_type().is_dir() {
237        res.set("mode", "directory")?;
238    }
239    if meta.file_type().is_file() {
240        res.set("mode", "file")?;
241    }
242    if meta.file_type().is_symlink() {
243        res.set("mode", "link")?;
244    }
245
246    Ok(res)
247}