utils.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // SPDX-License-Identifier: MPL-2.0
  2. // Note: This file is derived from the asterinas osdk,
  3. // and we extend our heartfelt thanks to the developers of Asterinas!
  4. //! This module contains utilities for manipulating common Unix command-line arguments.
  5. use anyhow::{anyhow, Result};
  6. use indexmap::{IndexMap, IndexSet};
  7. /// Split a string of Unix arguments into an array of key-value strings or switches.
  8. /// Positional arguments are not supported.
  9. pub fn split_to_kv_array(args: &str) -> Result<Vec<String>> {
  10. let target = shlex::split(args).ok_or(anyhow!("Failed to split unix arguments"))?;
  11. // Join the key value arguments as a single element
  12. let mut joined = Vec::<String>::new();
  13. let mut last_has_value = false;
  14. for elem in target {
  15. if !elem.starts_with('-') && !last_has_value {
  16. if let Some(last) = joined.last_mut() {
  17. last.push(' ');
  18. last.push_str(&elem);
  19. last_has_value = true;
  20. continue;
  21. }
  22. }
  23. joined.push(elem);
  24. last_has_value = false;
  25. }
  26. Ok(joined)
  27. }
  28. /// Apply key-value pairs to an array of strings.
  29. ///
  30. /// The provided arguments will be appended to the array if the key is not already present or if the key is a multi-value key.
  31. /// Otherwise, the value will be updated.
  32. pub fn apply_kv_array(
  33. array: &mut Vec<String>,
  34. args: &Vec<String>,
  35. separator: &str,
  36. multi_value_keys: &[&str],
  37. single_value_keys: &[&str],
  38. ) -> Result<()> {
  39. let multi_value_keys = {
  40. let mut inferred_keys = infer_multi_value_keys(array, separator);
  41. for key in multi_value_keys {
  42. inferred_keys.insert(key.to_string());
  43. }
  44. inferred_keys
  45. };
  46. log::debug!("apply_kv_array: multi value keys: {:?}", multi_value_keys);
  47. // We use IndexMap to keep key orders
  48. let mut key_strings = IndexMap::new();
  49. let mut multi_value_key_strings: IndexMap<String, Vec<String>> = IndexMap::new();
  50. for item in array.drain(..) {
  51. // Each key-value string has two patterns:
  52. // 1. Separated by separator: key value / key=value
  53. if let Some(key) = get_key(&item, separator) {
  54. if multi_value_keys.contains(&key) {
  55. if let Some(v) = multi_value_key_strings.get_mut(&key) {
  56. v.push(item);
  57. } else {
  58. let v = vec![item];
  59. multi_value_key_strings.insert(key, v);
  60. }
  61. continue;
  62. }
  63. key_strings.insert(key, item);
  64. continue;
  65. }
  66. // 2. Only key, no value
  67. key_strings.insert(item.clone(), item);
  68. }
  69. for arg in args {
  70. if let Some(key) = get_key(arg, separator) {
  71. if multi_value_keys.contains(&key) {
  72. if let Some(v) = multi_value_key_strings.get_mut(&key) {
  73. v.push(arg.to_owned());
  74. } else {
  75. let v = vec![arg.to_owned()];
  76. multi_value_key_strings.insert(key, v);
  77. }
  78. continue;
  79. }
  80. key_strings.insert(key, arg.to_owned());
  81. continue;
  82. } else {
  83. if single_value_keys.contains(&arg.as_str()) || multi_value_keys.contains(arg) {
  84. return Err(anyhow!("Invalid argument: {}", arg));
  85. } else {
  86. key_strings.insert(arg.to_owned(), arg.to_owned());
  87. }
  88. }
  89. }
  90. *array = key_strings.into_iter().map(|(_, value)| value).collect();
  91. for (_, mut values) in multi_value_key_strings {
  92. array.append(&mut values);
  93. }
  94. Ok(())
  95. }
  96. fn infer_multi_value_keys(array: &Vec<String>, separator: &str) -> IndexSet<String> {
  97. let mut multi_val_keys = IndexSet::new();
  98. let mut occurred_keys = IndexSet::new();
  99. for item in array {
  100. let Some(key) = get_key(item, separator) else {
  101. continue;
  102. };
  103. if occurred_keys.contains(&key) {
  104. multi_val_keys.insert(key);
  105. } else {
  106. occurred_keys.insert(key);
  107. }
  108. }
  109. multi_val_keys
  110. }
  111. pub fn get_key(item: &str, separator: &str) -> Option<String> {
  112. let split = item.split(separator).collect::<Vec<_>>();
  113. let len = split.len();
  114. if len > 2 || len == 0 {
  115. log::error!("{} is an invalid argument.", item);
  116. return None;
  117. }
  118. if len == 1 {
  119. return None;
  120. }
  121. let key = split.first().unwrap();
  122. Some(key.to_string())
  123. }
  124. #[cfg(test)]
  125. pub mod test {
  126. use super::*;
  127. #[test]
  128. fn test_get_key() {
  129. let string1 = "init=/bin/init";
  130. let key = get_key(string1, "=").unwrap();
  131. assert_eq!(key.as_str(), "init");
  132. let string2 = "-m 8G";
  133. let key = get_key(string2, " ").unwrap();
  134. assert_eq!(key.as_str(), "-m");
  135. let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off";
  136. let key = get_key(string3, " ").unwrap();
  137. assert_eq!(key.as_str(), "-device");
  138. let string4 = "-device";
  139. assert!(get_key(string4, " ").is_none());
  140. let string5 = "-m 8G a";
  141. assert!(get_key(string5, " ").is_none());
  142. let string6 = "-m=8G";
  143. assert!(get_key(string6, " ").is_none());
  144. }
  145. #[test]
  146. fn test_apply_kv_array() {
  147. let qemu_args = &[
  148. "-enable-kvm",
  149. "-m 8G",
  150. "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
  151. "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
  152. ];
  153. let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"];
  154. let expected = &[
  155. "-enable-kvm",
  156. "-m 100G",
  157. "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off",
  158. "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
  159. "-device ioh3420,id=pcie.0,chassis=1",
  160. ];
  161. let mut array = qemu_args.iter().map(ToString::to_string).collect();
  162. let args = args.iter().map(ToString::to_string).collect();
  163. assert!(apply_kv_array(&mut array, &args, " ", &["-device"], &[]).is_ok());
  164. let expected: Vec<_> = expected.iter().map(ToString::to_string).collect();
  165. assert_eq!(expected, array);
  166. }
  167. #[test]
  168. fn test_apply_kv_array_insert_multi_value_key() {
  169. let mut array = vec!["key1=value1".to_string(), "key2=value2".to_string()];
  170. let args = vec!["key3=value3".to_string()];
  171. let separator = "=";
  172. let multi_value_keys = vec!["key3"];
  173. let single_value_keys: Vec<&str> = vec![];
  174. let result = apply_kv_array(
  175. &mut array,
  176. &args,
  177. separator,
  178. &multi_value_keys,
  179. &single_value_keys,
  180. );
  181. assert!(result.is_ok());
  182. assert_eq!(
  183. array,
  184. vec![
  185. "key1=value1".to_string(),
  186. "key2=value2".to_string(),
  187. "key3=value3".to_string(),
  188. ]
  189. );
  190. }
  191. }