1 use help::Help; 2 use path_clean::PathClean; 3 use regex::{Captures, Regex}; 4 use std::intrinsics::unlikely; 5 use std::io::Read; 6 use std::os::unix::ffi::OsStrExt; 7 use std::{ 8 format, 9 fs::{self, File, OpenOptions}, 10 io::Write, 11 path::Path, 12 print, println, 13 string::String, 14 vec::Vec, 15 }; 16 17 use crate::env::{Env, ENV_FILE_PATH, ROOT_PATH}; 18 use crate::shell::Shell; 19 20 mod help; 21 22 #[derive(Debug, PartialEq, Eq, Clone)] 23 enum CommandType { 24 InternalCommand(BuildInCmd), 25 ExternalCommand(String), 26 } 27 28 #[derive(Debug, PartialEq, Eq, Clone)] 29 pub struct Command { 30 args: Vec<String>, 31 cmd_type: CommandType, 32 } 33 34 #[derive(Debug, PartialEq, Eq, Clone)] 35 pub enum CommandError { 36 CommandNotFound(String), 37 InvalidArgument(String), 38 WrongArgumentCount(usize), 39 EnvironmentVariableNotFound(String), 40 PathNotFound(String), 41 FileNotFound(String), 42 DirectoryNotFound(String), 43 NotDirectory(String), 44 NotFile(String), 45 } 46 47 impl CommandError { 48 pub fn handle(e: CommandError) { 49 match e { 50 CommandError::CommandNotFound(command) => { 51 println!("cannot find command: {}", command) 52 } 53 CommandError::InvalidArgument(argument) => { 54 println!("invalid argument: {}", argument) 55 } 56 CommandError::WrongArgumentCount(count) => { 57 println!("argument count incorrect: {}", count) 58 } 59 CommandError::EnvironmentVariableNotFound(env) => { 60 println!("environment variable not found: {}", env); 61 } 62 CommandError::PathNotFound(path) => { 63 println!("cannot found file or dirctory: {}", path) 64 } 65 CommandError::FileNotFound(path) => { 66 println!("cannot found file: {}", path) 67 } 68 CommandError::DirectoryNotFound(path) => { 69 println!("cannot found dirctory: {}", path) 70 } 71 CommandError::NotDirectory(path) => { 72 println!("path is not a dirctory: {}", path) 73 } 74 CommandError::NotFile(path) => { 75 println!("path is not a file: {}", path) 76 } 77 }; 78 } 79 } 80 81 impl Command { 82 fn new(name: String, args: Vec<String>) -> Result<Command, CommandError> { 83 for BuildInCmd(cmd) in BuildInCmd::BUILD_IN_CMD { 84 if name == *cmd { 85 return Ok(Command { 86 args, 87 cmd_type: CommandType::InternalCommand(BuildInCmd(cmd)), 88 }); 89 } 90 } 91 92 return Ok(Command { 93 args, 94 cmd_type: CommandType::ExternalCommand(name), 95 }); 96 } 97 98 fn from_string(str: String) -> Result<Command, CommandError> { 99 let regex: Regex = Regex::new(r#"([^\s'"]|("[^"]*"|'[^']*'))+"#).unwrap(); 100 let hay = str.clone(); 101 let mut iter = regex 102 .captures_iter(hay.as_str()) 103 .map(|c| String::from(c.get(0).unwrap().as_str())); 104 // let mut iter = str.split_ascii_whitespace(); 105 let name = iter.next().unwrap(); 106 let re: Regex = Regex::new(r"\$[\w_]+").unwrap(); 107 let replacement = |caps: &Captures| -> String { 108 match Env::get(&String::from(&caps[0][1..])) { 109 Some(value) => value, 110 None => String::from(&caps[0]), 111 } 112 }; 113 let mut args: Vec<String> = Vec::new(); 114 for arg in iter.collect::<Vec<String>>().iter() { 115 let arg = re.replace_all(arg.as_str(), &replacement).to_string(); 116 match re.captures(arg.as_str()) { 117 Some(caps) => { 118 return Err(CommandError::EnvironmentVariableNotFound(String::from( 119 caps.get(0).unwrap().as_str(), 120 ))) 121 } 122 None => args.push(arg), 123 } 124 } 125 let cmd = Command::new(name, args); 126 return cmd; 127 } 128 129 pub fn from_strings(str: String) -> Vec<Command> { 130 str.split(';') 131 .filter_map(|s| match Command::from_string(String::from(s)) { 132 Ok(s) => Some(s), 133 Err(e) => { 134 CommandError::handle(e); 135 None 136 } 137 }) 138 .collect::<Vec<Command>>() 139 } 140 } 141 142 #[derive(Debug, PartialEq, Eq, Clone)] 143 pub struct BuildInCmd(pub &'static str); 144 145 impl BuildInCmd { 146 pub const BUILD_IN_CMD: &[BuildInCmd] = &[ 147 BuildInCmd("cd"), 148 BuildInCmd("ls"), 149 BuildInCmd("cat"), 150 BuildInCmd("touch"), 151 BuildInCmd("mkdir"), 152 BuildInCmd("rm"), 153 BuildInCmd("rmdir"), 154 BuildInCmd("pwd"), 155 BuildInCmd("cp"), 156 BuildInCmd("exec"), 157 BuildInCmd("echo"), 158 BuildInCmd("reboot"), 159 BuildInCmd("free"), 160 BuildInCmd("kill"), 161 BuildInCmd("help"), 162 BuildInCmd("export"), 163 BuildInCmd("env"), 164 BuildInCmd("compgen"), 165 BuildInCmd("complete"), 166 ]; 167 } 168 169 impl Shell { 170 pub fn exec_internal_command( 171 &mut self, 172 cmd: &str, 173 args: &Vec<String>, 174 ) -> Result<(), CommandError> { 175 match cmd { 176 "cd" => self.shell_cmd_cd(args), 177 "ls" => self.shell_cmd_ls(args), 178 "cat" => self.shell_cmd_cat(args), 179 "touch" => self.shell_cmd_touch(args), 180 "mkdir" => self.shell_cmd_mkdir(args), 181 "rm" => self.shell_cmd_rm(args), 182 "rmdir" => self.shell_cmd_rmdir(args), 183 "pwd" => self.shell_cmd_pwd(args), 184 "cp" => self.shell_cmd_cp(args), 185 "exec" => self.shell_cmd_exec(args), 186 "echo" => self.shell_cmd_echo(args), 187 "reboot" => self.shell_cmd_reboot(args), 188 "free" => self.shell_cmd_free(args), 189 "kill" => self.shell_cmd_kill(args), 190 "help" => self.shell_cmd_help(args), 191 "export" => self.shell_cmd_export(args), 192 "env" => self.shell_cmd_env(args), 193 "compgen" => self.shell_cmd_compgen(args), 194 "complete" => self.shell_cmd_complete(args), 195 196 _ => Err(CommandError::CommandNotFound(String::from(cmd))), 197 } 198 } 199 200 pub fn exec_external_command(&mut self, path: String, args: &Vec<String>) { 201 let mut full_args = args.clone(); 202 full_args.insert(0, path.clone()); 203 self.shell_cmd_exec(&full_args).unwrap_or_else(|e| { 204 let err = match e { 205 CommandError::FileNotFound(rp) => CommandError::CommandNotFound(rp), 206 _ => e, 207 }; 208 CommandError::handle(err); 209 }) 210 } 211 212 pub fn exec_command(&mut self, command: &Command) { 213 match &command.cmd_type { 214 CommandType::ExternalCommand(path) => { 215 self.exec_external_command(path.to_string(), &command.args); 216 } 217 218 CommandType::InternalCommand(BuildInCmd(cmd)) => { 219 match self.exec_internal_command(cmd, &command.args) { 220 Ok(_) => {} 221 Err(e) => CommandError::handle(e), 222 } 223 if command.args.contains(&String::from("--help")) { 224 Help::shell_help(cmd); 225 } 226 } 227 } 228 } 229 230 fn shell_cmd_cd(&mut self, args: &Vec<String>) -> Result<(), CommandError> { 231 let path = match args.len() { 232 0 => String::from(ROOT_PATH), 233 1 => self.is_dir(args.get(0).unwrap())?, 234 _ => return Err(CommandError::WrongArgumentCount(args.len())), 235 }; 236 self.chdir(&path); 237 Ok(()) 238 } 239 240 fn shell_cmd_ls(&self, args: &Vec<String>) -> Result<(), CommandError> { 241 let path = match args.len() { 242 0 => Self::current_dir(), 243 1 => self.is_dir(args.get(0).unwrap())?, 244 _ => return Err(CommandError::WrongArgumentCount(args.len())), 245 }; 246 let dir = match fs::read_dir(Path::new(&path)) { 247 Ok(readdir) => readdir, 248 Err(_) => return Err(CommandError::InvalidArgument(path)), 249 }; 250 251 for entry in dir { 252 let entry = entry.unwrap(); 253 if entry.file_type().unwrap().is_dir() { 254 crate::shell::Printer::print_color( 255 entry.file_name().as_bytes(), 256 0x000088ff, 257 0x00000000, 258 ); 259 print!(" "); 260 } else { 261 print!("{} ", entry.file_name().into_string().unwrap()); 262 } 263 } 264 println!(); 265 Ok(()) 266 } 267 268 fn shell_cmd_cat(&self, args: &Vec<String>) -> Result<(), CommandError> { 269 if args.len() <= 0 { 270 return Err(CommandError::WrongArgumentCount(args.len())); 271 } 272 let path = self.is_file(args.get(0).unwrap())?; 273 let mut buf: Vec<u8> = Vec::new(); 274 275 File::open(path).unwrap().read_to_end(&mut buf).unwrap(); 276 if args.len() == 1 { 277 println!("{}", String::from_utf8(buf.clone()).unwrap()); 278 } 279 280 //TODO: 这部分应该放在`Shell`中,所有指令公用 281 if args.len() == 3 { 282 let mut target_path = args.get(2).unwrap().clone(); 283 match self.is_file(&target_path) { 284 Ok(str) => target_path = str, 285 Err(e) => return Err(e), 286 } 287 288 if args[1] == ">" { 289 match OpenOptions::new().write(true).open(target_path) { 290 Ok(mut file) => { 291 file.write_all(&buf).unwrap(); 292 } 293 Err(e) => print!("{e}"), 294 } 295 } else if args[1] == ">>" { 296 match OpenOptions::new().append(true).open(target_path) { 297 Ok(mut file) => { 298 file.write_all(&buf).unwrap(); 299 } 300 Err(e) => print!("{e}"), 301 } 302 } 303 } 304 Ok(()) 305 } 306 307 fn shell_cmd_touch(&self, args: &Vec<String>) -> Result<(), CommandError> { 308 if unlikely(args.len() != 1) { 309 return Err(CommandError::WrongArgumentCount(args.len())); 310 } 311 let path = args.get(0).unwrap(); 312 313 //路径中提取目录和文件名 314 let index = path.rfind('/').unwrap_or(0); 315 let dir = &path[..index]; 316 let file_name = &path[index..]; 317 318 //判断文件所在目录是否存在 319 let str = self.is_dir(&dir.to_string())?; 320 //判断文件是否存在,存在时不操作,不存在时创建文件 321 let abs_path = format!("{}/{}", str, file_name); 322 if !Path::new(&abs_path).exists() { 323 File::create(&abs_path).unwrap(); 324 } 325 Ok(()) 326 } 327 328 fn shell_cmd_mkdir(&self, args: &Vec<String>) -> Result<(), CommandError> { 329 if unlikely(args.len() != 1) { 330 return Err(CommandError::WrongArgumentCount(args.len())); 331 } 332 let path = args.get(0).unwrap(); 333 if let Err(e) = fs::create_dir_all(path) { 334 print!("{e}") 335 } 336 Ok(()) 337 } 338 339 fn shell_cmd_rm(&self, args: &Vec<String>) -> Result<(), CommandError> { 340 if unlikely(args.len() != 1) { 341 return Err(CommandError::WrongArgumentCount(args.len())); 342 } 343 let path = self.is_file(args.get(0).unwrap())?; 344 let path_cstr = std::ffi::CString::new(path).unwrap(); 345 unsafe { 346 libc::syscall(libc::SYS_unlinkat, 0, path_cstr.as_ptr(), 0, 0, 0, 0); 347 } 348 Ok(()) 349 } 350 351 fn shell_cmd_rmdir(&self, args: &Vec<String>) -> Result<(), CommandError> { 352 if unlikely(args.len() != 1) { 353 return Err(CommandError::WrongArgumentCount(args.len())); 354 } 355 let path = self.is_dir(args.get(0).unwrap())?; 356 let path_cstr = std::ffi::CString::new(path).unwrap(); 357 unsafe { libc::unlinkat(0, path_cstr.as_ptr(), libc::AT_REMOVEDIR) }; 358 Ok(()) 359 } 360 361 fn shell_cmd_pwd(&self, args: &Vec<String>) -> Result<(), CommandError> { 362 if unlikely(args.len() != 0) { 363 return Err(CommandError::WrongArgumentCount(args.len())); 364 } 365 println!("{}", Self::current_dir()); 366 Ok(()) 367 } 368 369 fn shell_cmd_cp(&self, args: &Vec<String>) -> Result<(), CommandError> { 370 if args.len() == 2 { 371 let mut src_path = args.get(0).unwrap().clone(); 372 let mut target_path = args.get(1).unwrap().clone(); 373 374 match self.is_file(&src_path) { 375 Ok(str) => src_path = str, 376 Err(e) => return Err(e), 377 } 378 379 match self.is_file_or_dir(&target_path) { 380 Ok(str) => target_path = str, 381 Err(e) => { 382 let prefix = &target_path[..target_path.rfind('/').unwrap_or(0)]; 383 if !Path::new(prefix).is_dir() { 384 return Err(e); 385 } 386 } 387 } 388 389 if Path::new(&src_path).is_dir() { 390 let name = &src_path[src_path.rfind('/').unwrap_or(0)..]; 391 target_path = format!("{}/{}", target_path, name); 392 } 393 394 let mut src_file = File::open(&src_path).unwrap(); 395 let mut target_file = File::create(target_path).unwrap(); 396 let mut buf: Vec<u8> = Vec::new(); 397 src_file.read_to_end(&mut buf).unwrap(); 398 target_file.write_all(&buf).unwrap(); 399 return Ok(()); 400 } 401 return Err(CommandError::WrongArgumentCount(args.len())); 402 } 403 404 pub fn shell_cmd_exec(&self, args: &Vec<String>) -> Result<(), CommandError> { 405 if unlikely(args.len() <= 0) { 406 return Err(CommandError::WrongArgumentCount(args.len())); 407 } 408 let path = args.get(0).unwrap(); 409 //在环境变量中搜索 410 //TODO: 放在一个函数里来实现 411 let mut real_path = String::new(); 412 if !path.contains('/') { 413 let mut dir_collection = Env::path(); 414 dir_collection.insert(0, Self::current_dir()); 415 for dir in dir_collection { 416 let possible_path = format!("{}/{}", dir, path); 417 if Path::new(&possible_path).is_file() { 418 real_path = possible_path; 419 break; 420 } 421 } 422 if real_path.is_empty() { 423 return Err(CommandError::FileNotFound(path.clone())); 424 } 425 } else { 426 match self.is_file(path) { 427 Ok(path) => real_path = path, 428 Err(e) => return Err(e), 429 } 430 } 431 432 let mut args = args.clone(); 433 // 如果文件不存在,返回错误 434 if !Path::new(&real_path).is_file() { 435 // println!("{}: command not found", real_path); 436 return Err(CommandError::FileNotFound(real_path.clone())); 437 } 438 439 let pid: libc::pid_t = unsafe { 440 libc::syscall(libc::SYS_fork, 0, 0, 0, 0, 0, 0) 441 .try_into() 442 .unwrap() 443 }; 444 445 let name = &real_path[real_path.rfind('/').map(|pos| pos + 1).unwrap_or(0)..]; 446 *args.get_mut(0).unwrap() = name.to_string(); 447 let mut retval = 0; 448 if pid == 0 { 449 let path_cstr = std::ffi::CString::new(real_path).unwrap(); 450 let args_cstr = args 451 .iter() 452 .map(|str| std::ffi::CString::new(str.as_str()).unwrap()) 453 .collect::<Vec<std::ffi::CString>>(); 454 let mut args_ptr = args_cstr 455 .iter() 456 .map(|c_str| c_str.as_ptr()) 457 .collect::<Vec<*const i8>>(); 458 args_ptr.push(std::ptr::null()); 459 let argv = args_ptr.as_ptr(); 460 461 unsafe { 462 libc::execv(path_cstr.as_ptr(), argv); 463 } 464 } else { 465 if args.last().unwrap() != &"&" { 466 unsafe { libc::waitpid(pid, &mut retval as *mut i32, 0) }; 467 } else { 468 println!("[1] {}", pid); 469 } 470 } 471 return Ok(()); 472 } 473 474 fn shell_cmd_echo(&self, args: &Vec<String>) -> Result<(), CommandError> { 475 if args.len() > 0 { 476 let str = args.get(0).unwrap(); 477 if args.len() == 1 { 478 println!("{str}"); 479 } 480 481 //TODO: 和`cat`中的一样,应放在`Shell`中 482 if args.len() == 3 { 483 let mut target_path = args.get(2).unwrap().clone(); 484 match self.is_file(&target_path) { 485 Ok(str) => target_path = str, 486 Err(e) => return Err(e), 487 } 488 if args[1] == ">" { 489 match OpenOptions::new().write(true).open(target_path) { 490 Ok(mut file) => { 491 file.write_all(str.as_bytes()).unwrap(); 492 } 493 Err(e) => print!("{e}"), 494 } 495 } else if args[1] == ">>" { 496 match OpenOptions::new().append(true).open(target_path) { 497 Ok(mut file) => { 498 file.write_all(str.as_bytes()).unwrap(); 499 } 500 Err(e) => print!("{e}"), 501 } 502 } 503 } 504 return Ok(()); 505 } 506 return Err(CommandError::WrongArgumentCount(args.len())); 507 } 508 509 fn shell_cmd_reboot(&self, args: &Vec<String>) -> Result<(), CommandError> { 510 if args.len() == 0 { 511 unsafe { libc::syscall(libc::SYS_reboot, 0, 0, 0, 0, 0, 0) }; 512 return Ok(()); 513 } else { 514 return Err(CommandError::WrongArgumentCount(args.len())); 515 } 516 } 517 518 fn shell_cmd_free(&self, args: &Vec<String>) -> Result<(), CommandError> { 519 if args.len() == 1 && args.get(0).unwrap() != "-m" { 520 return Err(CommandError::InvalidArgument( 521 args.get(0).unwrap().to_string(), 522 )); 523 } 524 525 struct Mstat { 526 total: u64, // 计算机的总内存数量大小 527 used: u64, // 已使用的内存大小 528 free: u64, // 空闲物理页所占的内存大小 529 shared: u64, // 共享的内存大小 530 cache_used: u64, // 位于slab缓冲区中的已使用的内存大小 531 cache_free: u64, // 位于slab缓冲区中的空闲的内存大小 532 available: u64, // 系统总空闲内存大小(包括kmalloc缓冲区) 533 } 534 535 let mut mst = Mstat { 536 total: 0, 537 used: 0, 538 free: 0, 539 shared: 0, 540 cache_used: 0, 541 cache_free: 0, 542 available: 0, 543 }; 544 545 let mut info_file = File::open("/proc/meminfo").unwrap(); 546 let mut buf: Vec<u8> = Vec::new(); 547 info_file.read_to_end(&mut buf).unwrap(); 548 let str = String::from_utf8(buf).unwrap(); 549 let info = str 550 .split(&['\n', '\t', ' ']) 551 .filter_map(|str| str.parse::<u64>().ok()) 552 .collect::<Vec<u64>>(); 553 mst.total = *info.get(0).unwrap(); 554 mst.free = *info.get(1).unwrap(); 555 mst.used = mst.total - mst.free; 556 557 print!("\ttotal\t\tused\t\tfree\t\tshared\t\tcache_used\tcache_free\tavailable\n"); 558 print!("Mem:\t"); 559 560 if args.len() == 0 { 561 print!( 562 "{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\n", 563 mst.total, 564 mst.used, 565 mst.free, 566 mst.shared, 567 mst.cache_used, 568 mst.cache_free, 569 mst.available 570 ); 571 } else { 572 print!( 573 "{}\t\t{}\t\t{}\t\t{}\t\t{}\t\t{}\n", 574 mst.total >> 10, 575 mst.used >> 10, 576 mst.free >> 10, 577 mst.shared >> 10, 578 mst.cache_used >> 10, 579 mst.available >> 10 580 ); 581 } 582 Ok(()) 583 } 584 585 fn shell_cmd_kill(&self, args: &Vec<String>) -> Result<(), CommandError> { 586 if unlikely(args.len() != 1) { 587 return Err(CommandError::WrongArgumentCount(args.len())); 588 } 589 590 let pid = match args.get(0).unwrap().parse::<i32>() { 591 Ok(x) => x, 592 Err(_) => { 593 return Err(CommandError::InvalidArgument( 594 args.get(0).unwrap().to_string(), 595 )) 596 } 597 }; 598 unsafe { 599 libc::kill(pid, libc::SIGTERM); 600 } 601 Ok(()) 602 } 603 604 fn shell_cmd_help(&self, args: &Vec<String>) -> Result<(), CommandError> { 605 if args.len() == 0 { 606 for BuildInCmd(cmd) in BuildInCmd::BUILD_IN_CMD { 607 Help::shell_help(cmd) 608 } 609 return Ok(()); 610 } 611 return Err(CommandError::WrongArgumentCount(args.len())); 612 } 613 614 fn shell_cmd_export(&self, args: &Vec<String>) -> Result<(), CommandError> { 615 if args.len() == 1 { 616 let pair = args.get(0).unwrap().split('=').collect::<Vec<&str>>(); 617 618 if pair.len() == 2 && !pair.contains(&"") { 619 let name = pair.get(0).unwrap().to_string(); 620 let value = pair.get(1).unwrap().to_string(); 621 Env::insert(name, value); 622 return Ok(()); 623 } else { 624 return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone())); 625 } 626 } 627 return Err(CommandError::WrongArgumentCount(args.len())); 628 } 629 630 fn shell_cmd_env(&self, args: &Vec<String>) -> Result<(), CommandError> { 631 if args.len() == 0 { 632 let mut file = File::open(ENV_FILE_PATH).unwrap(); 633 let mut buf: Vec<u8> = Vec::new(); 634 file.read_to_end(&mut buf).unwrap(); 635 println!("{}", String::from_utf8(buf).unwrap()); 636 return Ok(()); 637 } else { 638 return Err(CommandError::InvalidArgument(args.get(0).unwrap().clone())); 639 } 640 } 641 642 fn shell_cmd_compgen(&self, _args: &Vec<String>) -> Result<(), CommandError> { 643 //TODO 644 Ok(()) 645 } 646 647 fn shell_cmd_complete(&self, _args: &Vec<String>) -> Result<(), CommandError> { 648 //TODO 649 Ok(()) 650 } 651 652 fn path_format(&self, path: &String) -> Result<String, CommandError> { 653 let mut abs_path = path.clone(); 654 if !path.starts_with('/') { 655 abs_path = format!("{}/{}", Self::current_dir(), path); 656 } 657 let path = Path::new(&abs_path).clean(); 658 let mut fmt_path = path.to_str().unwrap().to_string(); 659 let replacement = |_caps: ®ex::Captures| -> String { String::from("/") }; 660 let re = regex::Regex::new(r"\/{2,}").unwrap(); 661 fmt_path = re.replace_all(fmt_path.as_str(), replacement).to_string(); 662 return Ok(fmt_path); 663 } 664 665 fn is_file(&self, path_str: &String) -> Result<String, CommandError> { 666 match self.path_format(path_str) { 667 Ok(path_str) => { 668 let path = Path::new(&path_str); 669 if !path.is_file() { 670 return Err(CommandError::NotFile(path_str.clone())); 671 }; 672 Ok(path_str) 673 } 674 Err(_) => Err(CommandError::FileNotFound(path_str.clone())), 675 } 676 } 677 678 fn is_dir(&self, path_str: &String) -> Result<String, CommandError> { 679 match self.path_format(path_str) { 680 Ok(path_str) => { 681 let path = Path::new(&path_str); 682 if !path.is_dir() { 683 return Err(CommandError::NotDirectory(path_str.clone())); 684 }; 685 Ok(path_str) 686 } 687 Err(_) => Err(CommandError::DirectoryNotFound(path_str.clone())), 688 } 689 } 690 691 fn is_file_or_dir(&self, path_str: &String) -> Result<String, CommandError> { 692 match self.path_format(path_str) { 693 Ok(path_str) => Ok(path_str), 694 Err(_) => Err(CommandError::PathNotFound(path_str.clone())), 695 } 696 } 697 } 698