atmos/os_lib/aura/builtins/
os_fs.rs1use super::*;
4
5pub(crate) fn head_str(s: &str, max: usize) -> &str {
7 let mut c = max.min(s.len());
8 while c > 0 && !s.is_char_boundary(c) {
9 c -= 1;
10 }
11 s.get(..c).unwrap_or("")
12}
13
14pub(crate) fn unquote(v: &Value) -> String {
15 let s = v.to_string();
16 if s.starts_with('"') && s.ends_with('"') {
17 s.get(1..s.len() - 1).map(String::from).unwrap_or(s)
18 } else {
19 s
20 }
21}
22
23pub(crate) fn resolve_path(env: &Env, path: &str) -> Result<String, String> {
24 let mut abs_path = if path.starts_with('/') {
25 String::from(path)
26 } else {
27 if let Some(Value::Str(cwd)) = env.get("cwd") {
28 if cwd.ends_with('/') {
29 alloc::format!("{}{}", cwd, path)
30 } else {
31 alloc::format!("{}/{}", cwd, path)
32 }
33 } else {
34 alloc::format!("/user/{}", path) }
36 };
37
38 if let Some(sandbox) = &env.sandbox {
40 if let Some(safe_path) = sandbox.resolve_path(&abs_path) {
41 abs_path = safe_path;
42 } else {
43 return Err(alloc::format!(
44 "Permission denied: Access to '{}' is restricted by the sandbox",
45 abs_path
46 ));
47 }
48 }
49
50 Ok(abs_path)
51}
52
53pub(crate) fn builtin_ls(env: &mut Env, args: &[AST]) -> Result<Value, String> {
54 let vals = eval_args(env, args)?;
55 let mut show_hidden = false;
56 let mut show_details = false;
57 let mut target_dir = if let Some(Value::Str(c)) = env.get("cwd") {
58 c.clone()
59 } else {
60 String::from("/user/")
61 };
62
63 for val in vals {
64 match val {
65 Value::Str(s) => {
66 let unquoted = unquote(&Value::Str(s));
67 if unquoted.starts_with('-') {
68 if unquoted.contains('a') {
69 show_hidden = true;
70 }
71 if unquoted.contains('l') {
72 show_details = true;
73 }
74 } else {
75 target_dir = unquoted;
76 }
77 }
78 _ => return Err(String::from("Invalid argument for ls")),
79 }
80 }
81
82 let safe_cwd = resolve_path(env, &target_dir)?;
83 let fs = crate::kernel::fs::get_fs();
84 let files = fs.list_dir(&safe_cwd);
85 let mut out = alloc::format!("Listing {}:\n", safe_cwd);
86 for file in files {
87 let name = file.get_filename();
88 let simple_name = if let Some(idx) = name.rfind('/') {
89 name.get(idx + 1..).unwrap_or(name)
90 } else {
91 name
92 };
93 if !show_hidden && simple_name.starts_with('.') {
94 continue;
95 }
96 let f_size = file.size;
97 if show_details {
98 out.push_str(&alloc::format!(
99 " - {} ({} bytes) labels:[{}]\n",
100 simple_name,
101 f_size,
102 file.get_labels()
103 ));
104 } else {
105 out.push_str(&alloc::format!(" - {} ({})\n", simple_name, f_size));
106 }
107 }
108 Ok(Value::Str(out))
109}
110
111pub(crate) fn builtin_cd(env: &mut Env, args: &[AST]) -> Result<Value, String> {
112 if args.is_empty() {
113 return Err(String::from("USAGE: cd(\"path\")"));
114 }
115 let path = unquote(&eval(env, &args[0])?);
116 let new_path = resolve_path(env, &path)?;
117 env.set(String::from("cwd"), Value::Str(new_path.clone()));
118 Ok(Value::Str(alloc::format!("Changed dir to {}", new_path)))
119}
120
121pub(crate) fn builtin_pwd(env: &mut Env, _args: &[AST]) -> Result<Value, String> {
122 let cwd = if let Some(Value::Str(c)) = env.get("cwd") {
123 c.clone()
124 } else {
125 String::from("/user/")
126 };
127 Ok(Value::Str(cwd))
128}
129
130pub(crate) fn builtin_mkdir(env: &mut Env, args: &[AST]) -> Result<Value, String> {
131 let vals = eval_args(env, args)?;
132 if vals.is_empty() {
133 return Err(String::from("USAGE: mkdir(\"path\")"));
134 }
135 let raw_path = unquote(&vals[0]);
136 let path = resolve_path(env, &raw_path)?;
137
138 let keep_file = if path.ends_with('/') {
139 alloc::format!("{}.keep", path)
140 } else {
141 alloc::format!("{}/.keep", path)
142 };
143
144 let fs = crate::kernel::fs::get_fs();
145 if fs.save_file(&keep_file, &[], "dir").is_ok() {
146 Ok(Value::Str(alloc::format!(
147 "SUCCESS: Created directory '{}'",
148 path
149 )))
150 } else {
151 Err(String::from("ERROR: Failed to create directory"))
152 }
153}
154
155pub(crate) fn builtin_cp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
156 let vals = eval_args(env, args)?;
157 if vals.len() < 2 {
158 return Err(String::from("USAGE: cp(\"src\" \"dst\")"));
159 }
160 let src_raw = unquote(&vals[0]);
161 let dst_raw = unquote(&vals[1]);
162 let src_path = resolve_path(env, &src_raw)?;
163 let dst_path = resolve_path(env, &dst_raw)?;
164
165 let fs = crate::kernel::fs::get_fs();
166 if let Some((meta, content)) = fs.read_file(&src_path) {
167 if fs.save_file(&dst_path, &content, meta.get_labels()).is_ok() {
168 Ok(Value::Str(alloc::format!(
169 "SUCCESS: Copied '{}' to '{}'",
170 src_path,
171 dst_path
172 )))
173 } else {
174 Err(String::from("ERROR: Failed to save destination file"))
175 }
176 } else {
177 Err(alloc::format!(
178 "ERROR: Source file '{}' not found",
179 src_path
180 ))
181 }
182}
183
184pub(crate) fn builtin_mv(env: &mut Env, args: &[AST]) -> Result<Value, String> {
185 let vals = eval_args(env, args)?;
186 if vals.len() < 2 {
187 return Err(String::from("USAGE: mv(\"src\" \"dst\")"));
188 }
189 let src_raw = unquote(&vals[0]);
190 let dst_raw = unquote(&vals[1]);
191 let src_path = resolve_path(env, &src_raw)?;
192 let dst_path = resolve_path(env, &dst_raw)?;
193
194 let fs = crate::kernel::fs::get_fs();
195 if let Some((meta, content)) = fs.read_file(&src_path) {
196 if fs.save_file(&dst_path, &content, meta.get_labels()).is_ok() {
197 if let Err(e) = fs.delete_file(&src_path) {
200 return Err(alloc::format!(
201 "ERROR: Copied to '{}' but could not remove '{}': {}",
202 dst_path,
203 src_path,
204 e
205 ));
206 }
207 Ok(Value::Str(alloc::format!(
208 "SUCCESS: Moved '{}' to '{}'",
209 src_path,
210 dst_path
211 )))
212 } else {
213 Err(String::from("ERROR: Failed to save destination file"))
214 }
215 } else {
216 Err(alloc::format!(
217 "ERROR: Source file '{}' not found",
218 src_path
219 ))
220 }
221}
222
223pub(crate) fn builtin_rm(env: &mut Env, args: &[AST]) -> Result<Value, String> {
224 let vals = eval_args(env, args)?;
225 if vals.is_empty() {
226 return Err(String::from("USAGE: rm(\"path\")"));
227 }
228 let raw_path = unquote(&vals[0]);
229 let path = resolve_path(env, &raw_path)?;
230
231 let fs = crate::kernel::fs::get_fs();
232 if fs.delete_file(&path).is_ok() {
233 Ok(Value::Str(alloc::format!(
234 "SUCCESS: Removed file '{}'",
235 path
236 )))
237 } else {
238 Err(alloc::format!(
239 "ERROR: Failed to remove file '{}' or not found",
240 path
241 ))
242 }
243}
244
245pub(crate) fn builtin_cat(env: &mut Env, args: &[AST]) -> Result<Value, String> {
246 let vals = eval_args(env, args)?;
247 if vals.is_empty() {
248 return Err(String::from("USAGE: cat(\"path\")"));
249 }
250 let raw_path = unquote(&vals[0]);
251 let path = resolve_path(env, &raw_path)?;
252
253 let fs = crate::kernel::fs::get_fs();
254 if let Some((_, content)) = fs.read_file(&path) {
255 let content_str = core::str::from_utf8(&content)
256 .map_err(|_| String::from("ERROR: Binary or invalid UTF-8 content"))?;
257 Ok(Value::Str(String::from(content_str)))
258 } else {
259 Err(alloc::format!("ERROR: File '{}' not found", path))
260 }
261}
262
263pub(crate) fn builtin_grep(env: &mut Env, args: &[AST]) -> Result<Value, String> {
264 let vals = eval_args(env, args)?;
265 if vals.len() < 2 {
266 return Err(String::from("USAGE: grep(\"pattern\" \"path\")"));
267 }
268 let pattern = unquote(&vals[0]);
269 let raw_path = unquote(&vals[1]);
270 let path = resolve_path(env, &raw_path)?;
271
272 let fs = crate::kernel::fs::get_fs();
273 if let Some((_, content)) = fs.read_file(&path) {
274 let content_str = core::str::from_utf8(&content)
275 .map_err(|_| String::from("ERROR: Binary or invalid UTF-8 content"))?;
276
277 let mut matching_lines = String::new();
278 for (i, line) in content_str.lines().enumerate() {
279 if line.contains(&pattern) {
280 matching_lines.push_str(&alloc::format!("{}: {}\n", i + 1, line));
281 }
282 }
283 Ok(Value::Str(matching_lines))
284 } else {
285 Err(alloc::format!("ERROR: File '{}' not found", path))
286 }
287}
288
289pub(crate) fn builtin_head(env: &mut Env, args: &[AST]) -> Result<Value, String> {
290 let vals = eval_args(env, args)?;
291 if vals.is_empty() {
292 return Err(String::from("USAGE: head(\"path\" [lines])"));
293 }
294 let raw_path = unquote(&vals[0]);
295 let path = resolve_path(env, &raw_path)?;
296
297 let mut num_lines = 10;
298 if vals.len() > 1 {
299 if let Value::Num(n) = &vals[1] {
300 num_lines = n.to_string().parse::<usize>().unwrap_or(10);
301 }
302 }
303
304 let fs = crate::kernel::fs::get_fs();
305 if let Some((_, content)) = fs.read_file(&path) {
306 let content_str = core::str::from_utf8(&content)
307 .map_err(|_| String::from("ERROR: Binary or invalid UTF-8 content"))?;
308
309 let mut out = String::new();
310 for line in content_str.lines().take(num_lines) {
311 out.push_str(line);
312 out.push('\n');
313 }
314 Ok(Value::Str(out))
315 } else {
316 Err(alloc::format!("ERROR: File '{}' not found", path))
317 }
318}
319
320pub(crate) fn builtin_tail(env: &mut Env, args: &[AST]) -> Result<Value, String> {
321 let vals = eval_args(env, args)?;
322 if vals.is_empty() {
323 return Err(String::from("USAGE: tail(\"path\" [lines])"));
324 }
325 let raw_path = unquote(&vals[0]);
326 let path = resolve_path(env, &raw_path)?;
327
328 let mut num_lines = 10;
329 if vals.len() > 1 {
330 if let Value::Num(n) = &vals[1] {
331 num_lines = n.to_string().parse::<usize>().unwrap_or(10);
332 }
333 }
334
335 let fs = crate::kernel::fs::get_fs();
336 if let Some((_, content)) = fs.read_file(&path) {
337 let content_str = core::str::from_utf8(&content)
338 .map_err(|_| String::from("ERROR: Binary or invalid UTF-8 content"))?;
339
340 let lines: Vec<&str> = content_str.lines().collect();
341 let start = if lines.len() > num_lines {
342 lines.len() - num_lines
343 } else {
344 0
345 };
346
347 let mut out = String::new();
348 for line in &lines[start..] {
349 out.push_str(line);
350 out.push('\n');
351 }
352 Ok(Value::Str(out))
353 } else {
354 Err(alloc::format!("ERROR: File '{}' not found", path))
355 }
356}
357
358pub(crate) fn builtin_save(env: &mut Env, args: &[AST]) -> Result<Value, String> {
359 let vals = eval_args(env, args)?;
360 if vals.len() < 2 {
361 return Err(String::from("USAGE: save(\"name\" \"data\" [\"labels\"])"));
362 }
363 let raw_filename = unquote(&vals[0]);
364 let filename = resolve_path(env, &raw_filename)?;
365 let content = unquote(&vals[1]);
366 let labels = if vals.len() > 2 {
367 unquote(&vals[2])
368 } else {
369 String::new()
370 };
371
372 let fs = crate::kernel::fs::get_fs();
373 if fs.save_file(&filename, content.as_bytes(), &labels).is_ok() {
374 Ok(Value::Str(alloc::format!(
375 "SUCCESS: Saved file '{}'",
376 filename
377 )))
378 } else {
379 Err(String::from("ERROR: Failed to save file"))
380 }
381}
382
383pub(crate) fn builtin_show(env: &mut Env, args: &[AST]) -> Result<Value, String> {
384 let vals = eval_args(env, args)?;
385 if vals.is_empty() {
386 return Err(String::from("USAGE: show(\"filename\")"));
387 }
388 let raw_filename = unquote(&vals[0]);
389 let filename = resolve_path(env, &raw_filename)?;
390
391 let fs = crate::kernel::fs::get_fs();
392 if let Some((meta, content)) = fs.read_file(&filename) {
393 let content_str = unsafe { core::str::from_utf8_unchecked(&content) };
394 Ok(Value::Str(alloc::format!(
395 "FILE: '{}' | LABELS: [{}]\nDATA: {}",
396 filename,
397 meta.get_labels(),
398 content_str
399 )))
400 } else {
401 Err(String::from("ERROR: File not found or corrupt!"))
402 }
403}
404
405pub(crate) fn builtin_find(env: &mut Env, args: &[AST]) -> Result<Value, String> {
406 let vals = eval_args(env, args)?;
407 if vals.is_empty() {
408 return Err(String::from("USAGE: find(\"label\")"));
409 }
410 let label = unquote(&vals[0]);
411
412 let fs = crate::kernel::fs::get_fs();
413 let files = fs.find_by_label(&label);
414 if !files.is_empty() {
415 let mut out = alloc::format!("FOUND {} file(s) for label '{}':\n", files.len(), label);
416 for file in files {
417 let f_size = file.size;
418 out.push_str(&alloc::format!(
419 " - {} ({} bytes) labels:[{}]\n",
420 file.get_filename(),
421 f_size,
422 file.get_labels()
423 ));
424 }
425 Ok(Value::Str(out))
426 } else {
427 Ok(Value::Str(alloc::format!(
428 "No files found with label '{}'",
429 label
430 )))
431 }
432}
433
434pub(crate) fn builtin_addlabel(env: &mut Env, args: &[AST]) -> Result<Value, String> {
435 let vals = eval_args(env, args)?;
436 if vals.len() < 2 {
437 return Err(String::from("USAGE: addlabel(\"filename\" \"label\")"));
438 }
439 let raw_filename = unquote(&vals[0]);
440 let filename = resolve_path(env, &raw_filename)?;
441 let new_label = unquote(&vals[1]);
442
443 let fs = crate::kernel::fs::get_fs();
444 if let Some((meta, content)) = fs.read_file(&filename) {
445 let old_labels = meta.get_labels();
446 let joined_labels = if old_labels.is_empty() {
447 new_label.clone()
448 } else {
449 alloc::format!("{},{}", old_labels, new_label)
450 };
451 if fs.save_file(&filename, &content, &joined_labels).is_ok() {
452 Ok(Value::Str(alloc::format!(
453 "SUCCESS: Added label '{}' to '{}'",
454 new_label,
455 filename
456 )))
457 } else {
458 Err(String::from("ERROR: Failed to save updated file"))
459 }
460 } else {
461 Err(String::from("ERROR: File not found"))
462 }
463}
464
465pub(crate) fn builtin_corrupt(env: &mut Env, args: &[AST]) -> Result<Value, String> {
466 let vals = eval_args(env, args)?;
467 if vals.is_empty() {
468 return Err(String::from("USAGE: corrupt(sector)"));
469 }
470
471 let sector = match &vals[0] {
472 Value::Num(n) => {
473 let s = n.to_string();
474 s.parse::<usize>().unwrap_or(0)
475 }
476 v => {
477 let s = unquote(v);
478 s.parse::<usize>().unwrap_or(0)
479 }
480 };
481
482 let fs = crate::kernel::fs::get_fs();
483 if fs.device.force_corrupt_raw_sector(sector).is_ok() {
484 Ok(Value::Str(alloc::format!(
485 "DEBUG: Sector {} on RAMDisk corrupted successfully!",
486 sector
487 )))
488 } else {
489 Err(String::from("ERROR: Failed to corrupt sector"))
490 }
491}
492
493pub(crate) fn builtin_keymap(env: &mut Env, args: &[AST]) -> Result<Value, String> {
494 let vals = eval_args(env, args)?;
495 if let Some(ctx) = &env.context {
496 if vals.is_empty() {
497 let app = crate::apps::keymap::KeymapApp::new();
498 let win = crate::kernel::window_mgr::Window::new(
499 200,
500 150,
501 400,
502 500,
503 "Keymap & Checker",
504 alloc::boxed::Box::new(app),
505 );
506 crate::kernel::window_mgr::get_instance().add_window(win);
507 return Ok(Value::Str(String::from("Launched Keymap App")));
508 }
509 let layout_name = unquote(&vals[0]);
510 if let Some(layout) = crate::kernel::keyboard::KeyboardLayout::parse(&layout_name) {
511 unsafe {
512 let kbd = &mut **ctx;
513 kbd.set_layout(layout);
514 }
515 Ok(Value::Str(alloc::format!(
516 "SUCCESS: Keyboard layout switched to {}",
517 layout_name
518 )))
519 } else {
520 Err(String::from("USAGE: keymap(\"us\"|\"js\")"))
521 }
522 } else {
523 Err(String::from("OS Context not available for keymap command"))
524 }
525}
526
527