bash_builtins/variables/
assoc.rs

1//! Access to associative array variables.
2
3use super::VariableError;
4use crate::ffi::variables as ffi;
5use std::ffi::{CStr, CString};
6use std::os::raw::c_char;
7
8/// Returns a pointer to a C-string with the contents of `bytes`. `bytes` can't
9/// contain the nul byte.
10///
11/// This pointer is expected to be freed by Bash, so its memory is allocated
12/// with libc.
13fn cstrdup<T: AsRef<[u8]>>(bytes: T) -> Result<*const c_char, VariableError> {
14    let bytes = bytes.as_ref();
15
16    if bytes.contains(&b'\0') {
17        return Err(VariableError::InvalidValue);
18    }
19
20    unsafe { Ok(libc::strndup(bytes.as_ptr().cast(), bytes.len())) }
21}
22
23/// Change an element of the associative array contained in the shell variable
24/// referenced by `name`.
25///
26/// `value` is not required to be valid UTF-8, but it can't contain any nul
27/// byte.
28pub fn assoc_set<T0, T1>(name: &str, key: T0, value: T1) -> Result<(), VariableError>
29where
30    T0: AsRef<[u8]>,
31    T1: AsRef<[u8]>,
32{
33    let name = CString::new(name).map_err(|_| VariableError::InvalidName)?;
34    let key = cstrdup(key)?;
35    let value = cstrdup(value)?;
36
37    let res = unsafe {
38        if ffi::legal_identifier(name.as_ptr()) == 0 {
39            return Err(VariableError::InvalidName);
40        }
41
42        let mut shell_var = ffi::find_variable(name.as_ptr());
43
44        if shell_var.is_null() {
45            shell_var = ffi::make_new_assoc_variable(name.as_ptr());
46        } else if (*shell_var).attributes & ffi::ATT_ASSOC == 0 {
47            return Err(VariableError::NotAssocArray);
48        }
49
50        ffi::bind_assoc_variable(shell_var, name.as_ptr(), key, value, 0)
51    };
52
53    if res.is_null() {
54        Err(VariableError::InvalidValue)
55    } else {
56        Ok(())
57    }
58}
59
60/// Returns a copy of the value corresponding to a key in an associative array.
61pub fn assoc_get<T: AsRef<[u8]>>(name: &str, key: T) -> Option<CString> {
62    let key = key.as_ref();
63    let var = super::find_raw(name)?;
64
65    unsafe {
66        if !var.is_assoc() {
67            return None;
68        }
69
70        let value = var
71            .assoc_items()
72            .find(|&(k, _)| libc::strncmp(key.as_ptr().cast(), k, key.len()) == 0)
73            .map(|(_, s)| CStr::from_ptr(s).to_owned());
74
75        value
76    }
77}
78
79/// Iterator to get items in an associative array.
80pub(super) struct AssocItemsIterator<'a> {
81    table: &'a ffi::HashTable,
82    num_bucket: isize,
83    current_bucket_item: Option<*const ffi::BucketContents>,
84}
85
86impl AssocItemsIterator<'_> {
87    pub(super) unsafe fn new(table: &ffi::HashTable) -> AssocItemsIterator {
88        AssocItemsIterator {
89            table,
90            num_bucket: 0,
91            current_bucket_item: None,
92        }
93    }
94}
95
96impl Iterator for AssocItemsIterator<'_> {
97    type Item = (*const c_char, *const c_char);
98
99    fn next(&mut self) -> Option<Self::Item> {
100        while self.num_bucket < self.table.nbuckets as isize {
101            let bucket = self
102                .current_bucket_item
103                .take()
104                .unwrap_or_else(|| unsafe { *self.table.bucket_array.offset(self.num_bucket) });
105
106            if !bucket.is_null() {
107                unsafe {
108                    let bucket = &*bucket;
109                    let item = (bucket.key, bucket.data);
110                    self.current_bucket_item = Some(bucket.next);
111                    return Some(item);
112                }
113            }
114
115            self.num_bucket += 1;
116        }
117
118        None
119    }
120}