1 use std::{fs, io::BufRead, os::unix::fs::PermissionsExt, path::Path}; 2 3 use crate::{ 4 contants::{AF_INET, AF_INET6, IPV4_MIN_MTU, IPV6_MIN_MTU, PRIO_MAX, PRIO_MIN}, 5 error::parse_error::{ParseError, ParseErrorType}, 6 task::cmdtask::CmdTask, 7 unit::{service::ServiceUnit, target::TargetUnit, timer::TimerUnit, Unit, UnitType, Url}, 8 FileDescriptor, 9 }; 10 11 use super::{UnitParser, BASE_IEC, BASE_SI, SEC_UNIT_TABLE}; 12 13 #[allow(dead_code)] 14 #[derive(PartialEq)] 15 pub enum SizeBase { 16 IEC, 17 Si, 18 } 19 20 pub struct UnitParseUtil; 21 22 #[allow(dead_code)] 23 impl UnitParseUtil { 24 /// @brief 解析布尔值 25 /// 26 /// 将传入的字符串解析为布尔值 27 /// "yes","y","1","true","t","on"均可表示true 28 /// "no","n","0","false","f","off"均可表示false 29 /// 30 /// @param s 需解析的字符串 31 /// 32 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 33 pub fn parse_boolean(s: &str) -> Result<bool, ParseError> { 34 let t_table: Vec<&str> = vec!["yes", "y", "1", "true", "t", "on"]; 35 let f_table: Vec<&str> = vec!["no", "n", "0", "false", "f", "off"]; 36 37 if t_table.contains(&s) { 38 return Ok(true); 39 } else if f_table.contains(&s) { 40 return Ok(false); 41 } 42 43 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 44 } 45 46 /// @brief 解析pid 47 /// 48 /// 将传入的字符串解析为pid 49 /// 50 /// @param s 需解析的字符串 51 /// 52 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 53 pub fn parse_pid(s: &str) -> Result<i32, ParseError> { 54 let s = s.trim(); 55 //先使用u64变换 56 let pid_ul = match s.parse::<u64>() { 57 Ok(val) => val, 58 Err(_) => { 59 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 60 } 61 }; 62 let pid: i32 = pid_ul as i32; 63 64 if (pid as u64) != pid_ul { 65 //如果在从pid_t转换为u64之后与之前不等,则说明发生了截断,返回错误 66 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 67 } 68 69 if pid < 0 { 70 //pid小于0不合法 71 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 72 } 73 74 return Ok(pid); 75 } 76 77 /// @brief 解析pid 78 /// 79 /// 将传入的字符串解析为mode_t 80 /// 81 /// @param s 需解析的字符串 82 /// 83 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 84 pub fn parse_mode(s: &str) -> Result<u32, ParseError> { 85 let s = s.trim(); 86 let m = match u32::from_str_radix(s, 8) { 87 Ok(val) => val, 88 Err(_) => { 89 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 90 } 91 }; 92 93 //如果模式大于权限的最大值则为非法权限,返回错误 94 if m > 0o7777 { 95 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 96 } 97 98 return Ok(m); 99 } 100 101 /// @brief 解析网络接口索引 102 /// 103 /// 将传入的字符串解析为网络接口索引具体值 104 /// 105 /// @param s 需解析的字符串 106 /// 107 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 108 pub fn parse_ifindex(s: &str) -> Result<i32, ParseError> { 109 let s = s.trim(); 110 let ret: i32 = match s.parse::<i32>() { 111 Ok(val) => val, 112 Err(_) => { 113 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 114 } 115 }; 116 117 if ret <= 0 { 118 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 119 } 120 121 return Ok(ret); 122 } 123 124 /// @brief 解析最大传输单元(MTU) 125 /// 126 /// 将传入的字符串解析为具体值 127 /// 128 /// @param s 需解析的字符串 129 /// 130 /// @param family 网络地址族 131 /// 132 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 133 pub fn parse_mtu(s: &str, family: i32) -> Result<u32, ParseError> { 134 let s = s.trim(); 135 let mtu = match s.parse::<u64>() { 136 Ok(val) => val, 137 Err(_) => { 138 //针对非法字符出错时 139 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 140 } 141 }; 142 143 //针对数据溢出时的报错 144 if mtu > u32::MAX as u64 { 145 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 146 } 147 148 let mtu: u32 = mtu as u32; 149 //判断mtu是否合法 150 if family == AF_INET6 && mtu < IPV6_MIN_MTU { 151 return Err(ParseError::new(ParseErrorType::ERANGE, String::new(), 0)); 152 } else if family == AF_INET && mtu < IPV4_MIN_MTU { 153 return Err(ParseError::new(ParseErrorType::ERANGE, String::new(), 0)); 154 } else if family != AF_INET6 || family != AF_INET { 155 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 156 } 157 158 return Ok(mtu); 159 } 160 161 /// @brief 解析Size 162 /// 163 /// 将传入的字符串解析为具体的字节数 164 /// 可支持IEC二进制后缀,也可支持SI十进制后缀 165 /// 166 /// @param s 需解析的字符串 167 /// 168 /// @param base 设置为IEC二进制后缀或者SI十进制后缀 169 /// 170 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 171 pub fn parse_size(s: &str, base: SizeBase) -> Result<u64, ParseError> { 172 let s = s.trim(); 173 //将s分解为数字和后缀部分 174 let (number_str, suffix) = match s.find(|c: char| !c.is_digit(10) && c != '.') { 175 Some(mid) => s.split_at(mid), 176 None => (s, ""), 177 }; 178 179 //获得数字部分的整数和小数部分 180 let (integer, fraction) = match number_str.find(".") { 181 Some(mid) => { 182 let (integer, fraction) = number_str.split_at(mid); 183 let integer = integer.parse::<u64>().unwrap(); 184 let fraction = match fraction[1..].parse::<u64>() { 185 Ok(val) => val, 186 Err(_) => { 187 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 188 } 189 }; 190 (integer, fraction) 191 } 192 None => (number_str.parse::<u64>().unwrap(), 0), 193 }; 194 195 //从表中查找到后缀所对应的字节倍数 196 let mut factor: u64 = 0; 197 if base == SizeBase::IEC { 198 factor = match BASE_IEC.get(suffix) { 199 Some(val) => *val, 200 None => { 201 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 202 } 203 } 204 } else if base == SizeBase::Si { 205 factor = match BASE_SI.get(suffix) { 206 Some(val) => *val, 207 None => { 208 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 209 } 210 } 211 } 212 213 Ok(integer * factor + (fraction * factor) / (10u64.pow(fraction.to_string().len() as u32))) 214 } 215 216 /// @brief 解析扇区大小 217 /// 218 /// 将传入的字符串解析为具体的扇区大小 219 /// 若扇区大小小于512或者大于4096,将会返回错误,若扇区大小不为2的幂,返回错误。 220 /// 221 /// @param s 需解析的字符串 222 /// 223 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 224 pub fn parse_sector_size(s: &str) -> Result<u64, ParseError> { 225 let s = s.trim(); 226 let size: u64 = match s.parse::<u64>() { 227 Ok(val) => val, 228 Err(_) => { 229 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 230 } 231 }; 232 233 if size < 512 || size > 4096 { 234 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 235 } 236 237 //判断是否为2的幂,如果不是则报错 238 if (size & (size - 1)) != 0 { 239 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 240 } 241 242 return Ok(size); 243 } 244 245 /// @brief 解析范围 246 /// 247 /// 将传入的字符串解析为具体的范围 248 /// 249 /// @param s 需解析的字符串 250 /// 251 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 252 pub fn parse_range(s: &str) -> Result<(u32, u32), ParseError> { 253 let mid = match s.find('-') { 254 Some(val) => val, 255 None => { 256 //如果字符串中没有'-'符号,则表示一个值,所以范围两端都为该值 257 let s = s.trim(); 258 let ret = match s.parse::<u32>() { 259 Ok(val) => val, 260 Err(_) => { 261 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 262 } 263 }; 264 return Ok((ret, ret)); 265 } 266 }; 267 268 //若字符串中存在'-',则分别解析为u32,解析失败则报错 269 let (l, r) = s.split_at(mid); 270 271 let l = l.trim(); 272 let l = match l.parse::<u32>() { 273 Ok(val) => val, 274 Err(_) => { 275 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 276 } 277 }; 278 let r = r.trim(); 279 let r = match r.parse::<u32>() { 280 Ok(val) => val, 281 Err(_) => { 282 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 283 } 284 }; 285 286 return Ok((l, r)); 287 } 288 289 /// @brief 解析文件描述符 290 /// 291 /// 将传入的字符串解析为文件描述符fd 292 /// 293 /// @param s 需解析的字符串 294 /// 295 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 296 pub fn parse_fd(s: &str) -> Result<FileDescriptor, ParseError> { 297 let s = s.trim(); 298 let fd = match s.parse::<i32>() { 299 Ok(val) => val, 300 Err(_) => { 301 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 302 } 303 }; 304 305 if fd < 0 { 306 return Err(ParseError::new(ParseErrorType::EBADF, String::new(), 0)); 307 } 308 309 return Ok(FileDescriptor(fd as usize)); 310 } 311 312 /// @brief 解析nice 313 /// 314 /// 将传入的字符串解析为nice 315 /// 316 /// @param s 需解析的字符串 317 /// 318 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 319 pub fn parse_nice(s: &str) -> Result<i8, ParseError> { 320 let s = s.trim(); 321 let nice = match s.parse::<i8>() { 322 Ok(val) => val, 323 Err(_) => { 324 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 325 } 326 }; 327 328 if nice > PRIO_MAX || nice < PRIO_MIN { 329 return Err(ParseError::new(ParseErrorType::ERANGE, String::new(), 0)); 330 } 331 332 return Ok(nice); 333 } 334 335 /// @brief 解析端口号 336 /// 337 /// 将传入的字符串解析为端口号 338 /// 339 /// @param s 需解析的字符串 340 /// 341 /// @return 解析成功则返回Ok(解析后的值),否则返回Err 342 pub fn parse_ip_port(s: &str) -> Result<u16, ParseError> { 343 let s = s.trim(); 344 let port = match s.parse::<u16>() { 345 Ok(val) => val, 346 Err(_) => { 347 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 348 } 349 }; 350 351 if port == 0 { 352 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 353 } 354 355 return Ok(port); 356 } 357 358 /// @brief 解析端口范围 359 /// 360 /// 将传入的字符串解析为端口范围 361 /// 362 /// @param s 需解析的字符串 363 /// 364 /// @return 解析成功则返回Ok((u16,u16)),否则返回Err 365 pub fn parse_ip_port_range(s: &str) -> Result<(u16, u16), ParseError> { 366 let (l, h) = Self::parse_range(s)?; 367 368 let l = l as u16; 369 let h = h as u16; 370 if l <= 0 || l >= 65535 || h <= 0 || h >= 65535 { 371 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 372 } 373 374 return Ok((l, h)); 375 } 376 377 /// @brief 解析OOM(Out-of-Memory)分数调整值 378 /// 379 /// 将传入的字符串解析为OOM(Out-of-Memory)分数调整值 380 /// 381 /// @param s 需解析的字符串 382 /// 383 /// @return 解析成功则返回Ok(u32),否则返回Err 384 pub fn parse_ip_prefix_length(s: &str) -> Result<u32, ParseError> { 385 let len = match s.parse::<u32>() { 386 Ok(val) => val, 387 Err(_) => { 388 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 389 } 390 }; 391 392 if len > 128 { 393 return Err(ParseError::new(ParseErrorType::ERANGE, String::new(), 0)); 394 } 395 396 return Ok(len); 397 } 398 399 /// @brief 目前为简单的分割字符串,并未提供严谨的Url解析 400 /// 401 /// 将传入的字符串解析为Url结构体的Vec,若Url非法则返回错误 402 /// 403 /// @param s 需解析的字符串 404 /// 405 /// @return 解析成功则返回Ok(Url),否则返回Err 406 pub fn parse_url(s: &str) -> Result<Vec<Url>, ParseError> { 407 let _url = Url::default(); 408 let url_strs = s.split_whitespace().collect::<Vec<&str>>(); 409 let mut urls = Vec::new(); 410 for s in url_strs { 411 urls.push(Url { 412 url_string: String::from(s), 413 }) 414 } 415 return Ok(urls); 416 } 417 418 /// @brief 将对应的str解析为对应Unit 419 /// 420 /// 将传入的字符串解析为Unit,解析失败返回错误 421 /// 422 /// @param path 需解析的文件 423 /// 424 /// @return 解析成功则返回Ok(Arc<dyn Unit>),否则返回Err 425 pub fn parse_unit<T: Unit>(path: &str) -> Result<usize, ParseError> { 426 return T::from_path(path); 427 } 428 429 /// @brief 将对应的str解析为Arc<dyn Unit> 430 /// 431 /// 将传入的字符串解析为Arc<dyn Unit>,解析失败返回错误 432 /// 433 /// @param path 需解析的文件 434 /// 435 /// @return 解析成功则返回Ok(Arc<dyn Unit>),否则返回Err 436 pub fn parse_unit_no_type(path: &str) -> Result<usize, ParseError> { 437 let idx = match path.rfind('.') { 438 Some(val) => val, 439 None => { 440 return Err(ParseError::new(ParseErrorType::EFILE, path.to_string(), 0)); 441 } 442 }; 443 444 if idx == path.len() - 1 { 445 //处理非法文件xxxx. 类型 446 return Err(ParseError::new(ParseErrorType::EFILE, path.to_string(), 0)); 447 } 448 449 let suffix = &path[idx + 1..]; 450 451 //通过文件后缀分发给不同类型的Unit解析器解析 452 let unit = match suffix { 453 //TODO: 目前为递归,后续应考虑从DragonReach管理的Unit表中寻找是否有该Unit,并且通过记录消除递归 454 "service" => UnitParser::parse::<ServiceUnit>(path, UnitType::Service)?, 455 "target" => UnitParser::parse::<TargetUnit>(path, UnitType::Target)?, 456 "timer" => UnitParser::parse::<TimerUnit>(path, UnitType::Timer)?, 457 _ => { 458 return Err(ParseError::new(ParseErrorType::EFILE, path.to_string(), 0)); 459 } 460 }; 461 462 return Ok(unit); 463 } 464 465 pub fn parse_env(s: &str) -> Result<(String, String), ParseError> { 466 let s = s.trim().split('=').collect::<Vec<&str>>(); 467 if s.len() != 2 { 468 return Err(ParseError::new(ParseErrorType::EINVAL, "".to_string(), 0)); 469 } 470 471 return Ok((s[0].to_string(), s[1].to_string())); 472 } 473 474 /// @brief 将对应的str解析为对应CmdTask 475 /// 476 /// 将传入的字符串解析为CmdTask组,解析失败返回错误 477 /// 478 /// @param path 需解析的文件 479 /// 480 /// @return 解析成功则返回Ok(Vec<CmdTask>>),否则返回Err 481 pub fn parse_cmd_task(s: &str) -> Result<Vec<CmdTask>, ParseError> { 482 //分拆成单词Vec 483 let cmds = s.split_whitespace().collect::<Vec<&str>>(); 484 let mut tasks = Vec::new(); 485 let mut i = 0; 486 while i < cmds.len() { 487 let mut cmd_task = CmdTask::default(); 488 //匹配到这里时,这个单词肯定是路径,若路径以-开头则设置ignore 489 cmd_task.ignore = cmds[i].starts_with('-'); 490 491 //获取到一个CmdTask的路径部分 492 let path: String; 493 if cmd_task.ignore { 494 path = String::from(&cmds[i][1..]); 495 } else { 496 path = String::from(cmds[i]); 497 } 498 499 //得到的非绝对路径则不符合语法要求,报错 500 if !UnitParseUtil::is_valid_exec_path(path.as_str()) { 501 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 502 } 503 504 cmd_task.path = path; 505 506 //i += 1,继续匹配下一个单词 507 i += 1; 508 let mut cmd_vec = Vec::new(); 509 while i < cmds.len() && !UnitParseUtil::is_valid_exec_path(cmds[i]) { 510 //命令可能会有多个单词,将多个命令整理成一个 511 let cmd = cmds[i]; 512 cmd_vec.push(String::from(cmd)); 513 i += 1; 514 } 515 cmd_task.cmd = cmd_vec; 516 tasks.push(cmd_task); 517 //经过while到这里之后,cmds[i]对应的单词一点是路径,i不需要加一 518 } 519 return Ok(tasks); 520 } 521 522 /// @brief 判断是否为绝对路径,以及指向是否为可执行文件或者sh脚本 523 /// 524 /// 目前该方法仅判断是否为绝对路径 525 /// 526 /// @param path 路径 527 /// 528 /// @return 解析成功则返回true,否则返回false 529 pub fn is_valid_exec_path(path: &str) -> bool { 530 let path = Path::new(path); 531 return path.is_absolute(); 532 533 //TODO: 后续应判断该文件是否为合法文件 534 //let path = Path::new(path); 535 //return Self::is_executable_file(path) || Self::is_shell_script(path); 536 } 537 538 pub fn is_valid_file(path: &str) -> bool { 539 if !path.starts_with("/") { 540 return false; 541 } 542 543 let path = Path::new(path); 544 if let Ok(matadata) = fs::metadata(path) { 545 return matadata.is_file(); 546 } 547 548 return false; 549 } 550 551 fn is_executable_file(path: &Path) -> bool { 552 if let Ok(metadata) = fs::metadata(path) { 553 // 检查文件类型是否是普通文件并且具有可执行权限 554 if metadata.is_file() { 555 let permissions = metadata.permissions().mode(); 556 return permissions & 0o111 != 0; 557 } 558 } 559 false 560 } 561 562 fn is_shell_script(path: &Path) -> bool { 563 if let Some(extension) = path.extension() { 564 if extension == "sh" { 565 return true; 566 } 567 } 568 false 569 } 570 571 /// @brief 将对应的str解析为us(微秒) 572 /// 573 /// 将传入的字符串解析为秒数,解析失败返回错误 574 /// 575 /// @param path 需解析的文件 576 /// 577 /// @return 解析成功则返回Ok(u64),否则返回Err 578 pub fn parse_sec(s: &str) -> Result<u64, ParseError> { 579 let ss = s.split_whitespace().collect::<Vec<&str>>(); 580 let mut ret = 0; 581 for s in ss { 582 //下列参数分别记录整数部分,小数部分以及单位 583 let integer: u64; 584 let mut frac: u64 = 0; 585 let unit: &str; 586 587 match s.find('.') { 588 Some(idx) => { 589 //解析整数部分 590 integer = match s[..idx].parse::<u64>() { 591 Ok(val) => val, 592 Err(_) => { 593 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)) 594 } 595 }; 596 //获得小数+单位的字符串 597 let frac_and_unit = &s[(idx + 1)..]; 598 match frac_and_unit.find(|c: char| !c.is_digit(10)) { 599 Some(val) => { 600 //匹配小数部分 601 frac = match frac_and_unit[..val].parse::<u64>() { 602 Ok(val) => val, 603 Err(_) => { 604 return Err(ParseError::new( 605 ParseErrorType::EINVAL, 606 String::new(), 607 0, 608 )) 609 } 610 }; 611 //单位部分 612 unit = &frac_and_unit[val..]; 613 } 614 None => { 615 //没有单位的情况,直接匹配小数 616 frac = match frac_and_unit.parse::<u64>() { 617 Ok(val) => val, 618 Err(_) => { 619 return Err(ParseError::new( 620 ParseErrorType::EINVAL, 621 String::new(), 622 0, 623 )) 624 } 625 }; 626 unit = ""; 627 } 628 }; 629 } 630 None => { 631 //没有小数点则直接匹配整数部分和单位部分 632 match s.find(|c: char| !c.is_digit(10)) { 633 Some(idx) => { 634 integer = match s[..idx].parse::<u64>() { 635 Ok(val) => val, 636 Err(_) => { 637 return Err(ParseError::new( 638 ParseErrorType::EINVAL, 639 String::new(), 640 0, 641 )) 642 } 643 }; 644 unit = &s[idx..]; 645 } 646 None => { 647 integer = match s.parse::<u64>() { 648 Ok(val) => val, 649 Err(_) => { 650 return Err(ParseError::new( 651 ParseErrorType::EINVAL, 652 String::new(), 653 0, 654 )) 655 } 656 }; 657 unit = ""; 658 } 659 }; 660 } 661 }; 662 663 //从时间单位转换表中获取到单位转换为ns的倍数 664 let factor = match SEC_UNIT_TABLE.get(unit) { 665 Some(val) => val, 666 None => { 667 return Err(ParseError::new(ParseErrorType::EINVAL, String::new(), 0)); 668 } 669 }; 670 671 ret += integer * factor + (frac * factor) / (10u64.pow(frac.to_string().len() as u32)); 672 } 673 674 //计算ns 675 return Ok(ret); 676 } 677 /// @brief 判断对应路径是否为目录 678 /// 679 /// @param path 路径 680 /// 681 /// @return true/false 682 pub fn is_dir(path: &str) -> bool { 683 if let Ok(metadata) = fs::metadata(path) { 684 if metadata.is_dir() { 685 return true; 686 } 687 return false; 688 } 689 return false; 690 } 691 692 /// ## 通过文件名解析该Unit的类型 693 pub fn parse_type(path: &str) -> UnitType { 694 let ret: &str; 695 if let Some(index) = path.rfind('.') { 696 ret = &path[index + 1..]; 697 } else { 698 ret = path; 699 } 700 match ret { 701 "service" => return UnitType::Service, 702 "target" => return UnitType::Target, 703 "timer" => return UnitType::Timer, 704 //TODO: 添加文件类型 705 _ => return UnitType::Unknown, 706 } 707 } 708 709 /// ## 将读取环境变量文件解析为环境变量集合 710 pub fn parse_environment_file(env_file: &str) -> Result<Vec<(String, String)>, ParseError> { 711 let mut envs = Vec::new(); 712 if env_file.len() > 0 { 713 let env_reader = UnitParser::get_reader(env_file, UnitType::Unknown)?; 714 for line in env_reader.lines() { 715 if let Ok(line) = line { 716 let x = UnitParseUtil::parse_env(line.as_str())?; 717 envs.push(x); 718 } 719 } 720 } 721 return Ok(envs); 722 } 723 } 724