starina_api/
environ.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Environ, a collection of key-value pairs passed to the application.
use alloc::vec::Vec;
use core::fmt;

use hashbrown::HashMap;
use starina_types::environ::Device;
use starina_types::environ::EnvType;
use starina_types::environ::EnvironDeserializer;
use starina_types::handle::HandleId;

use crate::channel::Channel;
use crate::handle::OwnedHandle;
use crate::vmspace::VmSpace;

#[derive(Debug)]
enum Value {
    Channel(Channel),
    VmSpace(VmSpace),
    Devices(Vec<Device>),
    String(&'static str),
}

/// Environ, short for *environment*, is a collection of key-value pairs that
/// are used to:
///
/// - Dependency injection. Especially channels connected to dependent services.
/// - Configuration settings.
/// - Command-line arguments (shell is not available as of this writing though!).
/// - The [`VmSpace`] of the current process. To manage its own address space.
///
/// # Environ is a key-value store
///
/// The keys are always strings, and the values can be of different types.
/// Currently, the supported types are:
///
/// - Channel.
/// - VmSpace.
/// - A list of found devices (for device drivers).
///
/// # How to request environ items
///
/// To request an environ item,
///
/// # Examples
///
/// ```
/// pub fn main(mut env: Environ) {
///     // Dump all environ items.
///     info!("env: {:#?}", env);
///
///     // Take the ownership of the channel.
///     let driver_ch: Channel = env.take_channel("dep:ethernet_device").unwrap();
/// }
/// ```
///
/// This snippet logs:
///
/// ```text
/// [tcpip       ] INFO   env: {
///     "dep:ethernet_device": Channel(
///         Channel(#1),
///     ),
///     "dep:startup": Channel(
///         Channel(#2),
///     ),
/// }
/// ```
///
/// # Difference from environment variables
///
/// Environ is similar to environment variables in POSIX, and actually, internal
/// implementation is mostly the same (both key and value are strings). However,
/// the key difference is that Starina enforces convention on key names so that we can
/// provide a consistent and type-safe API.
///
/// Otherwise, each application would have different command-line parsers.
pub struct Environ {
    entries: HashMap<&'static str, Value>,
}

impl Environ {
    pub(crate) fn parse(raw: &'static str) -> Environ {
        let mut entries = HashMap::new();
        let mut deserializer = EnvironDeserializer::new(raw);
        while let Some((key, env_type, value_str)) = deserializer.pop() {
            let value = match env_type {
                EnvType::Channel => {
                    let raw_handle_id = value_str.parse::<i32>().unwrap();
                    debug_assert!(raw_handle_id >= 0);

                    let handle_id = HandleId::from_raw(raw_handle_id);
                    let channel = Channel::from_handle(OwnedHandle::from_raw(handle_id));
                    Value::Channel(channel)
                }
                EnvType::VmSpace => {
                    let raw_handle_id = value_str.parse::<i32>().unwrap();
                    debug_assert!(raw_handle_id >= 0);

                    let handle_id = HandleId::from_raw(raw_handle_id);
                    let vmspace = VmSpace::from_handle(OwnedHandle::from_raw(handle_id));
                    Value::VmSpace(vmspace)
                }
                EnvType::Devices => {
                    let devices = serde_json::from_str(value_str).unwrap();
                    Value::Devices(devices)
                }
                EnvType::String => Value::String(value_str),
            };

            entries.insert(key, value);
        }

        Environ { entries }
    }

    /// Returns the channel associated with the key.
    ///
    /// If the key is not found, or is already taken, `None` is returned.
    ///
    /// # Panics
    ///
    /// Panics if the value associated with the key is not a channel.
    pub fn take_channel(&mut self, key: &str) -> Option<Channel> {
        match self.entries.remove(key) {
            Some(Value::Channel(channel)) => Some(channel),
            Some(_) => panic!("not a channel"),
            None => None,
        }
    }

    /// Returns the vmspace associated with the key.
    ///
    /// If the key is not found, or is already taken, `None` is returned.
    ///
    /// # Panics
    ///
    /// Panics if the value associated with the key is not a vmspace.
    pub fn take_vmspace(&mut self, key: &str) -> Option<VmSpace> {
        match self.entries.remove(key) {
            Some(Value::VmSpace(vmspace)) => Some(vmspace),
            Some(_) => panic!("not a channel"),
            None => None,
        }
    }

    /// Returns the devices associated with the key.
    pub fn devices(&self, key: &str) -> Option<&[Device]> {
        match self.entries.get(key) {
            Some(Value::Devices(devices)) => Some(devices),
            Some(_) => panic!("not devices"),
            None => None,
        }
    }

    pub fn string(&self, key: &str) -> Option<&str> {
        match self.entries.get(key) {
            Some(Value::String(s)) => Some(s),
            Some(_) => panic!("not a string"),
            None => None,
        }
    }
}

impl fmt::Debug for Environ {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_map().entries(self.entries.iter()).finish()
    }
}