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}