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