bp3d_env/
lib.rs

1// Copyright (c) 2022, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use once_cell::sync::Lazy;
30use std::collections::HashMap;
31use std::ffi::{OsStr, OsString};
32use std::fs::File;
33use std::io::{BufRead, BufReader};
34use std::path::{Path, PathBuf};
35use std::sync::Mutex;
36
37static PATHS: Lazy<Mutex<Vec<PathBuf>>> = Lazy::new(|| Mutex::new(Vec::new()));
38static ENV_CACHE: Lazy<Mutex<HashMap<OsString, Option<OsString>>>> =
39    Lazy::new(|| Mutex::new(HashMap::new()));
40
41/// Adds a new override path.
42///
43/// If the path is already added, nothing happens. If the path is not already present, the
44/// requested path is cloned and inserted in the global path list.
45///
46/// Additionally, when a new path is added, the function invalidates the cache to let a chance
47/// to the getters to read the new override.
48///
49/// **Note: This is a slow function with allocations, locks and linear search.**
50///
51/// This is best called when initializing the application.
52///
53/// # Panics
54///
55/// The function panics if the path does not point to a file.
56pub fn add_override_path(path: &Path) {
57    if path.is_dir() || path.is_symlink() {
58        panic!("Cannot add non-file environment override path!");
59    }
60    let mut lock = PATHS.lock().unwrap();
61    if lock.iter().any(|p| p == path) {
62        return;
63    }
64    lock.push(path.into());
65    let mut lock1 = ENV_CACHE.lock().unwrap();
66    lock1.clear();
67}
68
69fn check_insert_key_value(
70    cache: &mut HashMap<OsString, Option<OsString>>,
71    searched: impl AsRef<OsStr>,
72    key: impl AsRef<OsStr>,
73    value: impl AsRef<OsStr>
74) -> Option<Option<OsString>> {
75    if key.as_ref() == searched.as_ref() {
76        if value.as_ref().is_empty() {
77            cache.insert(key.as_ref().into(), None);
78        } else {
79            cache.insert(key.as_ref().into(), Some(value.as_ref().into()));
80        }
81        Some(cache[key.as_ref()].clone())
82    } else {
83        None
84    }
85}
86
87#[cfg(windows)]
88fn windows_path(cache: &mut HashMap<OsString, Option<OsString>>, searched: impl AsRef<OsStr>, data: &[u8], pos: usize) -> Option<Option<OsString>> {
89    let key = match std::str::from_utf8(&data[..pos]) {
90        Ok(v) => v,
91        Err(_) => return None,
92    };
93    let value = match std::str::from_utf8(&data[pos + 1..]) {
94        Ok(v) => v,
95        Err(_) => return None,
96    };
97    check_insert_key_value(cache, searched, key, value)
98}
99
100/// Gets the content of an environment variable.
101///
102/// Returns None if the variable does not exist.
103///
104/// **Note: for optimization reasons, the functions caches values.**
105///
106/// The cost of this function is amortized O(1) (thanks to the cache). Once a value is loaded it's
107/// cached to avoid re-loading it. When a value is not loaded the cost of this function is O(nm)
108/// with n the number of items in the override path list and m the number of lines in each override
109/// file.
110pub fn get_os<T: AsRef<OsStr>>(name: T) -> Option<OsString> {
111    let mut cache = ENV_CACHE.lock().unwrap();
112    {
113        // Attempt to pull from the cache.
114        if let Some(val) = cache.get(name.as_ref()) {
115            return val.clone();
116        }
117    }
118    {
119        // Value is not in cache, try pulling from environment variables.
120        if let Some(val) = std::env::var_os(name.as_ref()) {
121            cache.insert(name.as_ref().into(), Some(val.clone()));
122            return Some(val.clone());
123        }
124    }
125    {
126        // Value is still not in cache, try pulling from the override file list.
127        let lock = PATHS.lock().unwrap();
128        for v in &*lock {
129            let file = match File::open(v) {
130                Ok(v) => BufReader::new(v),
131                Err(_) => continue,
132            };
133            for v in file.split(b'\n') {
134                let data = match v {
135                    Ok(v) => v,
136                    Err(_) => break, // If an IO error has occurred, skip loading the file
137                                     // completely.
138                };
139                let pos = match data.iter().position(|v| *v == b'=') {
140                    Some(v) => v,
141                    None => continue,
142                };
143                #[cfg(windows)]
144                if let Some(res) = windows_path(&mut cache, name.as_ref(), &data, pos) {
145                    return res;
146                }
147                #[cfg(unix)]
148                {
149                    use std::os::unix::ffi::OsStrExt;
150                    //Unix is better because it accepts constructing OsStr from a byte buffer.
151                    let key = OsStr::from_bytes(&data[..pos]);
152                    let value = OsStr::from_bytes(&data[pos + 1..]);
153                    if let Some(res) = check_insert_key_value(&mut cache, name.as_ref(), key, value) {
154                        return res;
155                    }
156                }
157            }
158        }
159    }
160    // Everything failed; just place a None in the cache and assume the variable does not exist.
161    cache.insert(name.as_ref().into(), None);
162    None
163}
164
165/// Gets the content of an environment variable.
166///
167/// Returns None if the variable does not exist or is not valid UTF-8.
168///
169/// **Note: for optimization reasons, the functions caches values.**
170///
171/// The cost of this function is amortized O(1) (thanks to the cache). Once a value is loaded it's
172/// cached to avoid re-loading it. When a value is not loaded the cost of this function is O(nm)
173/// with n the number of items in the override path list and m the number of lines in each override
174/// file.
175pub fn get<T: AsRef<OsStr>>(name: T) -> Option<String> {
176    get_os(name).and_then(|v| v.into_string().ok())
177}
178
179/// Gets a boolean environment variable.
180///
181/// Returns None if the variable does not exist or the format is unrecognized.
182///
183/// **Note: for optimization reasons, the functions caches values.**
184///
185/// The cost of this function is amortized O(1) (thanks to the cache). Once a value is loaded it's
186/// cached to avoid re-loading it. When a value is not loaded the cost of this function is O(nm)
187/// with n the number of items in the override path list and m the number of lines in each override
188/// file.
189pub fn get_bool<T: AsRef<OsStr>>(name: T) -> Option<bool> {
190    match &*get(name)? {
191        "off" | "OFF" | "FALSE" | "false" | "0" => Some(false),
192        "on" | "ON" | "TRUE" | "true" | "1" => Some(true),
193        _ => None,
194    }
195}