std::env::set_var safely
-rw-r--r--Cargo.lock25
-rw-r--r--Cargo.toml15
-rw-r--r--README.md13
-rw-r--r--src/lib.rs79
4 files changed, 132 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..4bb66ed
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,25 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "env"
+version = "1.0.0"
+dependencies = [
+ "num_threads",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.169"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..708da8f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,15 @@
+cargo-features = ["edition2024"]
+[package]
+name = "env"
+version = "1.0.0"
+edition = "2024"
+license = "MIT"
+description = "std::env::{`set_var`, `remove_var`} safely"
+keywords = ["environment", "safe"]
+categories = ["config", "os"]
+readme = "README.md"
+authors = ["bendn <[email protected]>"]
+repository = "https://github.com/bend-n/env"
+
+[dependencies]
+num_threads = "0.1.7"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..964d1a3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# `env`
+
+![rust 2024](https://img.shields.io/badge/rust-2024-blue?style=for-the-badge&logo=rust&logoColor=white)
+
+Provides a safe interface for <code>[std::env](http://doc.rust-lang.org/std/env)::{[set_var](https://doc.rust-lang.org/std/env/fn.set_var.html), [remove_var](https://doc.rust-lang.org/std/env/fn.remove_var.html)}</code>.
+
+## Rationale
+
+Since [#124636](https://github.com/rust-lang/rust/pull/124636), `std::env::set_var` and `std::env::remove_var}` have become unsafe, due to their being unsafe when in a multi-threaded unix context[^1].
+
+This crate wraps these functions, checking if these conditions are met at runtime, such that these functions are safe to call.
+
+[^1]: https://github.com/rust-lang/rust/issues/27970
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ce09c3a
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,79 @@
+//! Inspection and manipulation of the process's environment.
+//!
+//! This module contains functions to inspect various aspects such as
+//! environment variables, process arguments, the current directory, and various
+//! other important directories.
+//!
+//! There are several functions and structs in this module that have a
+//! counterpart ending in `os`. Those ending in `os` will return an [`OsString`](std::ffi::OsString)
+//! and those without will return a [`String`].
+//!
+//! This crate provides <code>[mod@std::env]::{[set_var], [remove_var]}</code> safely.
+#![forbid(clippy::missing_safety_doc)]
+pub use std::env::*;
+use std::ffi::OsStr;
+
+/// Whether the operating system has a thread-safe environment. This allows bypassing the check for if the process is multi-threaded.
+const SAFE: bool = matches!(
+ std::env::consts::OS.as_bytes(),
+ b"illumos" | b"netbsd" | b"windows"
+);
+
+/// Sets the environment variable `key` to the value `value` for the currently running
+/// process. (safely!)
+///
+/// # Safety
+///
+/// This function is safe to call.
+/// This function returns [`None`] if it would be unsafe to call this function.
+///
+/// See also [`std::env::set_var`].
+///
+/// # Panics
+///
+/// This function may panic if `key` is empty, contains an ASCII equals sign `'='`
+/// or the NUL character `'\0'`, or when `value` contains the NUL character.
+///
+/// # Examples
+///
+/// ```
+/// let key = "KEY";
+/// env::set_var(key, "VALUE").expect("ok");
+/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
+/// ```
+#[must_use = "this function may not always run"]
+pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, value: V) -> Option<()> {
+ (SAFE || num_threads::is_single_threaded() == Some(true))
+ .then(|| unsafe { std::env::set_var(key, value) })
+}
+
+/// Removes an environment variable from the environment of the currently running process. (safely!)
+///
+/// # Safety
+///
+/// This function is safe to call.
+/// This function returns [`None`] if it would be unsafe to call this function.
+///
+/// See also [`std::env::remove_var`].
+///
+/// # Panics
+///
+/// This function may panic if `key` is empty, contains an ASCII equals sign
+/// `'='` or the NUL character `'\0'`, or when the value contains the NUL
+/// character.
+///
+/// # Examples
+///
+/// ```
+/// let key = "KEY";
+/// env::set_var(key, "VALUE").expect("ok");
+/// assert_eq!(env::var(key), Ok("VALUE".to_string()));
+/// env::remove_var(key).expect("ok");
+/// assert!(env::var(key).is_err());
+/// ```
+#[must_use = "this function may not always run"]
+pub fn remove_var<K: AsRef<OsStr>>(key: K) -> Option<()> {
+ // SAFETY: this function is safe to call in single threaded environments or on good OS's.
+ (SAFE || num_threads::is_single_threaded() == Some(true))
+ .then(|| unsafe { std::env::remove_var(key) })
+}