Safe, opinionated Windows SMB client operations for existing disk shares.
Sam -> SMB -> Rust -> Samba is taken!? -> sambrs
sambrs keeps unsafe Windows FFI, UTF-16 conversion, buffer ownership,
validation, cleanup, and WNet quirks out of applications. It supports:
- deviceless, explicit-drive, and automatically assigned connections;
- temporary and persistent drive mappings;
- default, paired, and partial credentials;
- SMB signing and encryption requirements;
- guarded temporary mappings that disconnect on drop;
- querying mapped drives, connection users, and universal UNC paths;
- enumerating active connections, remembered mappings, and a server's disk shares.
It deliberately does not create or administer shares, handle printers, expose
raw WNet controls, or transfer files. See the boundary
ADR.
Passwords and their temporary UTF-16 buffers are wiped on drop. Operations,
targets, usernames, options, and raw Windows statuses are emitted through
tracing; passwords are never logged.
The crate is Windows-only, so cross-platform applications should make the dependency conditional:
[target.'cfg(windows)'.dependencies]
sambrs = "0.2"Configure an SmbTarget and establish a connection. Once connected,
std::fs works on the UNC path or mapped drive like any other filesystem
path:
use sambrs::{ConnectOptions, DisconnectOptions, DriveLetter, SmbTarget};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let target = SmbTarget::new(r"\\server.local\share")
.credentials(r"LOGONDOMAIN\user", "pass")
.mount_on(DriveLetter::D);
target.connect_with(
ConnectOptions::new()
.persist(true)
.require_privacy(true),
)?;
println!("{}", std::fs::metadata(r"D:\")?.is_dir());
target.disconnect_with(DisconnectOptions::default().forget(true))?;
Ok(())
}
Persistence requires an explicit or automatically assigned drive. A deviceless connection uses the UNC path directly and is never remembered:
# fn main() -> Result<(), sambrs::Error> {
let target = sambrs::SmbTarget::new(r"\\server.local\share");
target.connect()?;
# Ok(())
# }
Use a guard for an automatically cleaned-up temporary mapping:
# fn main() -> Result<(), sambrs::Error> {
let target = sambrs::SmbTarget::new(r"\\server.local\share");
let connection = target.connect_auto_guarded(sambrs::ConnectOptions::new())?;
println!("mapped on {}", connection.device());
# Ok(())
# }
Guarded connections cannot be persistent because persistence outlives the guard.
Inspect existing resources through the focused query and enumeration modules:
# fn main() -> Result<(), sambrs::Error> {
for resource in sambrs::enumerate::server_shares(r"\\fileserver")? {
println!("{:?}", resource?.remote_name);
}
let remote = sambrs::query::get_connection("D:")?;
let user = sambrs::query::get_user("D:")?;
let unc = sambrs::query::get_universal_name(r"D:\folder\file.txt")?;
# Ok(())
# }
The integration tests need a real share and are excluded by default. Point them at one and run them explicitly:
SAMBRS_TEST_SHARE=\\server\share
SAMBRS_TEST_USERNAME=DOMAIN\user
SAMBRS_TEST_PASSWORD=...
cargo test --test integration -- --test-threads=1
The tests mount real drive letters and must run single-threaded.
0.2 is a breaking rework of the API:
| 0.1 | 0.2 |
|---|---|
SmbShare::new(share, user, pass, Some('d')) |
SmbTarget::new(share).credentials(user, pass).mount_on(DriveLetter::D) |
share.connect(persist, interactive) |
target.connect_with(ConnectOptions::new().persist(persist)) |
share.disconnect(persist, force) |
target.disconnect_with(DisconnectOptions::default().forget(persist).force(force)) |
Error::CStringConversion |
Error::InteriorNul |
The old persist: true disconnect argument removed persistence, hence the
forget rename. Version 0.2 uses Unicode Windows APIs throughout and omitting
credentials now means "use the logged-on Windows identity."
This project is licensed under the MIT License. See the license.
Thanks to Christian Visintin for his article on accessing SMB shares with Rust on Windows. For a fully featured, cross-platform remote-file solution, see remotefs.