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}