bpf_loader_lib/meta/
arg_parser.rs

1//!  SPDX-License-Identifier: MIT
2//!
3//! Copyright (c) 2023, eunomia-bpf
4//! All rights reserved.
5//!
6
7use anyhow::{anyhow, bail, Context, Result};
8use clap::ArgMatches;
9use serde_json::{json, Value};
10
11use super::EunomiaObjectMeta;
12
13/// What to do if we met a variable which neither has the default value or has been supplied from command argument
14pub enum UnpresentVariableAction {
15    /// Fill the un-provided variables with zero
16    FillWithZero,
17    /// If un-provided variables are encountered, just report error
18    ReportError,
19}
20
21impl EunomiaObjectMeta {
22    /// Parser the values from the command line parser
23    ///
24    /// The clap parser gives us all values as strings, so here will try to parse the strings into the variable's own types (e.g, string, int, unsigned int). If it fails, `Err` will be returned
25    ///
26    /// This function will fill the `value` field in the `DataSectionVariableMeta`.
27    ///
28    /// If the `on_unpresent` behavior is `FillWithZero`, in this way if we find a command line argument with no values provided (this situation may only happen if the command line argument was created with no default values, a.k.a the `value` field is `None` in `DataSectionVariableMeta`), the `value` field will still be leaved `None`. In this way, the section_loader will fill zeros in the corresponding memory areas.
29    ///
30    /// If the `on_unpresent` behavior is `ReportError`, in this way if we find a command line argument with no values, we'll report an error.
31    pub fn parse_arguments_and_fill_skeleton_variables(
32        &mut self,
33        args: &ArgMatches,
34        on_unpresent: UnpresentVariableAction,
35    ) -> Result<()> {
36        for section in self.bpf_skel.data_sections.iter_mut() {
37            for variable in section.variables.iter_mut() {
38                if variable.name.starts_with("__eunomia_dummy") {
39                    continue;
40                }
41                if variable.ty == "bool" {
42                    let flag = args.get_one::<bool>(&variable.name).unwrap();
43                    variable.value = Some(json!(flag));
44                } else {
45                    let user_value = args
46                        .get_one::<String>(&variable.name)
47                        .map(|v| v.to_string());
48                    let parsed_value = if let Some(user_value) = user_value {
49                        Some(parse_value(&variable.ty, user_value).with_context(|| {
50                            anyhow!("Failed to parse user input value of `{}`", variable.name)
51                        })?)
52                    } else {
53                        match on_unpresent {
54                            UnpresentVariableAction::FillWithZero => {
55                                // Leave it none to let section_loader fill zeros.
56                                None
57                            }
58                            UnpresentVariableAction::ReportError => bail!(
59                                "Variable `{}` has neither default values nor command-line sources",
60                                variable.name
61                            ),
62                        }
63                    };
64                    variable.value = parsed_value;
65                }
66            }
67        }
68        self.debug_verbose = args.get_flag("verbose");
69        Ok(())
70    }
71}
72
73macro_rules! parse_value_decl {
74    ($raw_value: expr, $input_ty_name: expr,  $(($type_name: expr, $to_type: ty)), * ) => {
75        {
76            use anyhow::{anyhow,  Context};
77            use serde_json::json;
78            match $input_ty_name {
79                $(
80                    $type_name => Some(json!(
81                        $raw_value.parse::<$to_type>().with_context(|| anyhow!("Failed to parse `{}` into {}", $raw_value, stringify!($to_type)))?
82                    )),
83                )*
84                _ => None
85            }
86        }
87    };
88}
89
90fn parse_value(ty: &str, v: impl AsRef<str>) -> Result<Value> {
91    let s = v.as_ref();
92    let result = parse_value_decl!(
93        s,
94        ty,
95        ("bool", bool),
96        ("pid_t", i32),
97        ("int", i32),
98        ("short", i16),
99        ("long", i64),
100        ("long long", i64),
101        ("unsigned int", u32),
102        ("unsigned short", u16),
103        ("unsigned long long", u64),
104        ("float", f32),
105        ("double", f64)
106    );
107    if let Some(v) = result {
108        Ok(v)
109    } else if ty.starts_with("char[") {
110        Ok(json!(s))
111    } else {
112        bail!("Not supporting parsing into type `{}`", ty);
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use serde_json::json;
119
120    use crate::{
121        meta::{arg_parser::UnpresentVariableAction, EunomiaObjectMeta},
122        tests::get_assets_dir,
123    };
124
125    #[test]
126    fn test_arg_parser() {
127        let mut skel = serde_json::from_str::<EunomiaObjectMeta>(
128            &std::fs::read_to_string(get_assets_dir().join("arg_builder_test").join("skel.json"))
129                .unwrap(),
130        )
131        .unwrap();
132        let cmd = skel.build_argument_parser().unwrap();
133        let matches = cmd
134            .try_get_matches_from([
135                "myprog",
136                "-1",
137                "1234",
138                "--const_val_2",
139                "2345",
140                "--const_val_3",
141                "abcdefg",
142                "--boolflag",
143                "--bss_val_1",
144                "7890",
145            ])
146            .unwrap();
147        skel.parse_arguments_and_fill_skeleton_variables(
148            &matches,
149            UnpresentVariableAction::FillWithZero,
150        )
151        .unwrap();
152        println!("{:#?}", skel.bpf_skel.data_sections);
153        let sections = &skel.bpf_skel.data_sections;
154        assert_eq!(sections[0].variables[0].value, Some(json!(1234)));
155        assert_eq!(sections[0].variables[1].value, Some(json!(2345)));
156        assert_eq!(sections[0].variables[2].value, Some(json!("abcdefg")));
157        assert_eq!(sections[0].variables[3].value, Some(json!(true)));
158        assert_eq!(sections[1].variables[0].value, Some(json!(7890)));
159    }
160    #[test]
161    #[should_panic = "Failed to parse `abcdefg` into i32"]
162    fn test_arg_parser_with_invalid_value_1() {
163        let mut skel = serde_json::from_str::<EunomiaObjectMeta>(
164            &std::fs::read_to_string(get_assets_dir().join("arg_builder_test").join("skel.json"))
165                .unwrap(),
166        )
167        .unwrap();
168        let cmd = skel.build_argument_parser().unwrap();
169        let matches = cmd
170            .try_get_matches_from(["myprog", "-1", "abcdefg"])
171            .unwrap();
172        skel.parse_arguments_and_fill_skeleton_variables(
173            &matches,
174            UnpresentVariableAction::FillWithZero,
175        )
176        .unwrap();
177    }
178    #[test]
179    #[should_panic = "Failed to parse `111111111111111111` into i32"]
180    fn test_arg_parser_with_invalid_value_2() {
181        let mut skel = serde_json::from_str::<EunomiaObjectMeta>(
182            &std::fs::read_to_string(get_assets_dir().join("arg_builder_test").join("skel.json"))
183                .unwrap(),
184        )
185        .unwrap();
186        let cmd = skel.build_argument_parser().unwrap();
187        let matches = cmd
188            .try_get_matches_from(["myprog", "-1", "111111111111111111"])
189            .unwrap();
190        skel.parse_arguments_and_fill_skeleton_variables(
191            &matches,
192            UnpresentVariableAction::FillWithZero,
193        )
194        .unwrap();
195    }
196    #[test]
197    fn test_arg_parser_with_true_boolflag() {
198        let mut skel = serde_json::from_str::<EunomiaObjectMeta>(
199            &std::fs::read_to_string(get_assets_dir().join("arg_builder_test").join("skel.json"))
200                .unwrap(),
201        )
202        .unwrap();
203        let cmd = skel.build_argument_parser().unwrap();
204        let matches = cmd.try_get_matches_from(["myprog"]).unwrap();
205        skel.parse_arguments_and_fill_skeleton_variables(
206            &matches,
207            UnpresentVariableAction::FillWithZero,
208        )
209        .unwrap();
210        assert_eq!(
211            skel.bpf_skel.data_sections[0].variables[4].value,
212            Some(json!(true))
213        );
214    }
215}