Vendor dependencies
Let's see how I like this workflow.
This commit is contained in:
parent
34d1830413
commit
9c435dc440
7500 changed files with 1665121 additions and 99 deletions
1
vendor/users/.cargo-checksum.json
vendored
Normal file
1
vendor/users/.cargo-checksum.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.lock":"c9ada6d2bfa84d46192e0e6fa32447593b3bff0d665fe16c767abbbdd2c65ca4","Cargo.toml":"da29cc763ec004970957799df1ebcdcc51e8f854522bdbe73980b95d80d2b32a","Justfile":"1293564ae4d6639392bd045b0cb850cea433f9d376f12723eebc17fa4be0ae26","LICENCE":"ac84d716b3ca37857b9465476a7d6adc3684a774bc775ada8318c550187ed2b5","README.md":"7376a66fd7955c3115eabe65b70acc3a3c0a9038d830331748623f96220ba72a","examples/example.rs":"1500d9c04605096ef9928883f7ae07f48a98da166007f3a91f3803818a0ac0e6","examples/groups.rs":"ba6f7307aa0f204e387451a1e457a0f93628d253d5f607ac9e370e8307726f50","examples/list.rs":"c64574b89f84ba144d7601a6c66762fbb0e23d0a81d21ea97e3e16a564e6ac4b","examples/os.rs":"46d3217736c6d2b63ca107cfa1a7425ef574191d0b374fb81b918918f0bb9809","examples/switching.rs":"6584c8f06a3c3820bdcfc9cd4e4e8842915c559822b237a0213a5b284be0e782","examples/threading.rs":"681b760bf3f976d6eb82e87f428eecc7a456014c4e5236d5b7d475d124c6259e","src/base.rs":"f65d28fb398b871316e34ce3bc4f737c67ebd7c140d2fd1dc3de47ced9a53679","src/cache.rs":"dd934d88e1059348760b6f32e933888d4fc9f85d5a15cd152b48e444c1c1adc4","src/lib.rs":"2d2805dc46e4f718f62395f8dd65349fc0169d70452d5f6d8996d951bb6fac94","src/mock.rs":"0973ab8f55b02668f0866994546c1c32ca619c24a74f28a82c5dd1422da10f9b","src/switch.rs":"7354e65c0acbdabfefa3261586201a942df8585542612eaf8978d58585351d99","src/traits.rs":"9af80b4cb6cea0ad4b6caceb6602fbf27a6ae49f7a02df768f285463664a716a"},"package":"24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"}
|
||||
42
vendor/users/Cargo.lock
generated
vendored
Normal file
42
vendor/users/Cargo.lock
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
39
vendor/users/Cargo.toml
vendored
Normal file
39
vendor/users/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
authors = ["Benjamin Sago <ogham@bsago.me>"]
|
||||
exclude = ["/.rustfmt.toml", "/.travis.yml"]
|
||||
description = "Library for accessing Unix users and groups"
|
||||
documentation = "https://docs.rs/users/"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/ogham/rust-users"
|
||||
[dependencies.libc]
|
||||
version = "0.2"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
default_features = false
|
||||
[dev-dependencies.env_logger]
|
||||
version = "0.7"
|
||||
features = []
|
||||
default_features = false
|
||||
|
||||
[features]
|
||||
cache = []
|
||||
default = ["cache", "mock", "logging"]
|
||||
logging = ["log"]
|
||||
mock = []
|
||||
36
vendor/users/Justfile
vendored
Normal file
36
vendor/users/Justfile
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
all: build test
|
||||
all-release: build-release test-release
|
||||
|
||||
MIN_RUST := "1.31.0"
|
||||
|
||||
|
||||
# compiles the code
|
||||
build:
|
||||
cargo +{{MIN_RUST}} build
|
||||
cargo +stable build
|
||||
|
||||
# compiles the code in release mode
|
||||
build-release:
|
||||
cargo +{{MIN_RUST}} build --release --verbose
|
||||
cargo +stable build --release --verbose
|
||||
|
||||
# compiles the code with every combination of feature flags
|
||||
build-features:
|
||||
cargo +{{MIN_RUST}} hack build --feature-powerset
|
||||
cargo +stable hack build --feature-powerset
|
||||
|
||||
|
||||
# runs unit tests
|
||||
test:
|
||||
cargo +{{MIN_RUST}} test --all -- --quiet
|
||||
cargo +stable test --all -- --quiet
|
||||
|
||||
# runs unit tests in release mode
|
||||
test-release:
|
||||
cargo +{{MIN_RUST}} test --all --release --verbose
|
||||
cargo +stable test --all --release --verbose
|
||||
|
||||
# runs unit tests with every combination of feature flags
|
||||
test-features:
|
||||
cargo +{{MIN_RUST}} hack test --feature-powerset --lib -- --quiet
|
||||
cargo +stable hack test --feature-powerset --lib -- --quiet
|
||||
21
vendor/users/LICENCE
vendored
Normal file
21
vendor/users/LICENCE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Benjamin Sago
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
167
vendor/users/README.md
vendored
Normal file
167
vendor/users/README.md
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# rust-users [![users on crates.io][crates-badge]][crates-url] [![Minimum Rust Version 1.31.0][rustc-badge]][rustc-url] [![Build status][travis-badge]][travis-url]
|
||||
|
||||
[crates-badge]: https://meritbadge.herokuapp.com/users
|
||||
[crates-url]: https://crates.io/crates/users
|
||||
[travis-badge]: https://travis-ci.org/ogham/rust-users.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/github/ogham/rust-users
|
||||
[rustc-badge]: https://img.shields.io/badge/rustc-1.31+-lightgray.svg
|
||||
[rustc-url]: https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html
|
||||
|
||||
This is a library for accessing Unix users and groups.
|
||||
It supports getting the system users and groups, storing them in a cache, and creating your own mock tables.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/users)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](https://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
users = "0.11"
|
||||
```
|
||||
|
||||
The earliest version of Rust that this crate is tested against is [Rust v1.31.0][rustc-url].
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
In Unix, each user has an individual *user ID*, and each process has an *effective user ID* that says which user’s permissions it is using.
|
||||
Furthermore, users can be the members of *groups*, which also have names and IDs.
|
||||
This functionality is exposed in libc, the C standard library, but as an unsafe Rust interface.
|
||||
This wrapper library provides a safe interface, using `User` and `Group` types and functions such as `get_user_by_id` instead of low-level pointers and strings.
|
||||
It also offers basic caching functionality.
|
||||
|
||||
It does not (yet) offer *editing* functionality; the values returned are read-only.
|
||||
|
||||
|
||||
## Users
|
||||
|
||||
The function `get_current_uid` returns a `uid_t` value representing the user currently running the program, and the `get_user_by_uid` function scans the users database and returns a `User` with the user’s information.
|
||||
This function returns `None` when there is no user for that ID.
|
||||
|
||||
A `User` has the following accessors:
|
||||
|
||||
- **uid:** The user’s ID
|
||||
- **name:** The user’s name
|
||||
- **primary_group:** The ID of this user’s primary group
|
||||
|
||||
Here is a complete example that prints out the current user’s name:
|
||||
|
||||
```rust
|
||||
use users::{get_user_by_uid, get_current_uid};
|
||||
|
||||
let user = get_user_by_uid(get_current_uid()).unwrap();
|
||||
println!("Hello, {}!", user.name());
|
||||
```
|
||||
|
||||
This code assumes (with `unwrap()`) that the user hasn’t been deleted after the program has started running.
|
||||
For arbitrary user IDs, this is **not** a safe assumption: it’s possible to delete a user while it’s running a program, or is the owner of files, or for that user to have never existed.
|
||||
So always check the return values!
|
||||
|
||||
There is also a `get_current_username` function, as it’s such a common operation that it deserves special treatment.
|
||||
|
||||
|
||||
## Caching
|
||||
|
||||
Despite the above warning, the users and groups database rarely changes.
|
||||
While a short program may only need to get user information once, a long-running one may need to re-query the database many times, and a medium-length one may get away with caching the values to save on redundant system calls.
|
||||
|
||||
For this reason, this crate offers a caching interface to the database, which offers the same functionality while holding on to every result, caching the information so it can be re-used.
|
||||
|
||||
To introduce a cache, create a new `UsersCache` and call the same methods on it.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
use users::{Users, Groups, UsersCache};
|
||||
|
||||
let mut cache = UsersCache::new();
|
||||
let uid = cache.get_current_uid();
|
||||
let user = cache.get_user_by_uid(uid).unwrap();
|
||||
println!("Hello again, {}!", user.name());
|
||||
```
|
||||
|
||||
This cache is **only additive**: it’s not possible to drop it, or erase selected entries, as when the database may have been modified, it’s best to start entirely afresh.
|
||||
So to accomplish this, just start using a new `UsersCache`.
|
||||
|
||||
|
||||
## Groups
|
||||
|
||||
Finally, it’s possible to get groups in a similar manner.
|
||||
A `Group` has the following accessors:
|
||||
|
||||
- **gid:** The group’s ID
|
||||
- **name:** The group’s name
|
||||
|
||||
And again, a complete example:
|
||||
|
||||
```rust
|
||||
use users::{Users, Groups, UsersCache};
|
||||
|
||||
let mut cache = UsersCache::new();
|
||||
let group = cache.get_group_by_name("admin").expect("No such group 'admin'!");
|
||||
println!("The '{}' group has the ID {}", group.name(), group.gid());
|
||||
```
|
||||
|
||||
|
||||
## Logging
|
||||
|
||||
The `logging` feature, which is on by default, uses the `log` crate to record all interactions with the operating system at Trace log level.
|
||||
|
||||
|
||||
## Caveats
|
||||
|
||||
You should be prepared for the users and groups tables to be completely broken: IDs shouldn’t be assumed to map to actual users and groups, and usernames and group names aren’t guaranteed to map either!
|
||||
|
||||
|
||||
# Mockable users and groups
|
||||
|
||||
When you’re testing your code, you don’t want to actually rely on the system actually having various users and groups present - it’s much better to have a custom set of users that are *guaranteed* to be there, so you can test against them.
|
||||
|
||||
The `mock` module allows you to create these custom users and groups definitions, then access them using the same `Users` trait as in the main library, with few changes to your code.
|
||||
|
||||
|
||||
## Creating mock users
|
||||
|
||||
The only thing a mock users table needs to know in advance is the UID of the current user.
|
||||
Aside from that, you can add users and groups with `add_user` and `add_group` to the table:
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use users::mock::{MockUsers, User, Group};
|
||||
use users::os::unix::{UserExt, GroupExt};
|
||||
|
||||
let mut users = MockUsers::with_current_uid(1000);
|
||||
let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins");
|
||||
users.add_user(bobbins);
|
||||
users.add_group(Group::new(100, "funkyppl"));
|
||||
```
|
||||
|
||||
The exports get re-exported into the mock module, for simpler `use` lines.
|
||||
|
||||
|
||||
## Using mock users
|
||||
|
||||
To set your program up to use either type of `Users` table, make your functions and structs accept a generic parameter that implements the `Users` trait.
|
||||
Then, you can pass in a value of either OS or Mock type.
|
||||
|
||||
Here’s a complete example:
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
use users::{Users, UsersCache, User};
|
||||
use users::os::unix::UserExt;
|
||||
use users::mock::MockUsers;
|
||||
|
||||
fn print_current_username<U: Users>(users: &mut U) {
|
||||
println!("Current user: {:?}", users.get_current_username());
|
||||
}
|
||||
|
||||
let mut users = MockUsers::with_current_uid(1001);
|
||||
users.add_user(User::new(1001, "fred", 101));
|
||||
print_current_username(&mut users);
|
||||
|
||||
let mut actual_users = UsersCache::new();
|
||||
print_current_username(&mut actual_users);
|
||||
```
|
||||
20
vendor/users/examples/example.rs
vendored
Normal file
20
vendor/users/examples/example.rs
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
extern crate users;
|
||||
use users::{Users, Groups, UsersCache};
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cache = UsersCache::new();
|
||||
|
||||
let current_uid = cache.get_current_uid();
|
||||
println!("Your UID is {}", current_uid);
|
||||
|
||||
let you = cache.get_user_by_uid(current_uid).expect("No entry for current user!");
|
||||
println!("Your username is {}", you.name().to_string_lossy());
|
||||
|
||||
let primary_group = cache.get_group_by_gid(you.primary_group_id()).expect("No entry for your primary group!");
|
||||
println!("Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name().to_string_lossy());
|
||||
}
|
||||
31
vendor/users/examples/groups.rs
vendored
Normal file
31
vendor/users/examples/groups.rs
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
extern crate users;
|
||||
use users::{Users, Group, UsersCache, get_user_groups, group_access_list};
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cache = UsersCache::new();
|
||||
|
||||
let user = cache.get_user_by_uid(cache.get_current_uid())
|
||||
.expect("No current user?");
|
||||
|
||||
let mut groups: Vec<Group> = get_user_groups(user.name(), user.primary_group_id())
|
||||
.expect("No user groups?");
|
||||
|
||||
groups.sort_by(|a, b| a.gid().cmp(&b.gid()));
|
||||
for group in groups {
|
||||
println!("Group {} has name {}", group.gid(), group.name().to_string_lossy());
|
||||
}
|
||||
|
||||
let mut groups = group_access_list()
|
||||
.expect("Group access list");
|
||||
|
||||
groups.sort_by(|a, b| a.gid().cmp(&b.gid()));
|
||||
println!("\nGroup access list:");
|
||||
for group in groups {
|
||||
println!("Group {} has name {}", group.gid(), group.name().to_string_lossy());
|
||||
}
|
||||
}
|
||||
16
vendor/users/examples/list.rs
vendored
Normal file
16
vendor/users/examples/list.rs
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
extern crate users;
|
||||
use users::{User, all_users};
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let mut users: Vec<User> = unsafe { all_users() }.collect();
|
||||
users.sort_by(|a, b| a.uid().cmp(&b.uid()));
|
||||
|
||||
for user in users {
|
||||
println!("User {} has name {}", user.uid(), user.name().to_string_lossy());
|
||||
}
|
||||
}
|
||||
38
vendor/users/examples/os.rs
vendored
Normal file
38
vendor/users/examples/os.rs
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
extern crate users;
|
||||
use users::{Users, Groups, UsersCache};
|
||||
use users::os::unix::{UserExt, GroupExt};
|
||||
//use users::os::bsd::UserExt as BSDUserExt;
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cache = UsersCache::new();
|
||||
|
||||
let current_uid = cache.get_current_uid();
|
||||
println!("Your UID is {}", current_uid);
|
||||
|
||||
let you = cache.get_user_by_uid(current_uid).expect("No entry for current user!");
|
||||
println!("Your username is {}", you.name().to_string_lossy());
|
||||
println!("Your shell is {}", you.shell().display());
|
||||
println!("Your home directory is {}", you.home_dir().display());
|
||||
|
||||
// The two fields below are only available on BSD systems.
|
||||
// Linux systems don’t have the fields in their `passwd` structs!
|
||||
//println!("Your password change timestamp is {}", you.password_change_time());
|
||||
//println!("Your password expiry timestamp is {}", you.password_expire_time());
|
||||
|
||||
let primary_group = cache.get_group_by_gid(you.primary_group_id()).expect("No entry for your primary group!");
|
||||
println!("Your primary group has ID {} and name {}", primary_group.gid(), primary_group.name().to_string_lossy());
|
||||
|
||||
if primary_group.members().is_empty() {
|
||||
println!("There are no other members of that group.");
|
||||
}
|
||||
else {
|
||||
for username in primary_group.members() {
|
||||
println!("User {} is also a member of that group.", username.to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
29
vendor/users/examples/switching.rs
vendored
Normal file
29
vendor/users/examples/switching.rs
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
extern crate users;
|
||||
use users::{get_current_uid, get_current_gid, get_effective_uid, get_effective_gid, uid_t};
|
||||
use users::switch::switch_user_group;
|
||||
use std::mem::drop;
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
|
||||
const SAMPLE_ID: uid_t = 502;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
println!("\nInitial values:");
|
||||
print_state();
|
||||
|
||||
println!("\nValues after switching:");
|
||||
let guard = switch_user_group(SAMPLE_ID, SAMPLE_ID);
|
||||
print_state();
|
||||
|
||||
println!("\nValues after switching back:");
|
||||
drop(guard);
|
||||
print_state();
|
||||
}
|
||||
|
||||
fn print_state() {
|
||||
println!("Current UID/GID: {}/{}", get_current_uid(), get_current_gid());
|
||||
println!("Effective UID/GID: {}/{}", get_effective_uid(), get_effective_gid());
|
||||
}
|
||||
65
vendor/users/examples/threading.rs
vendored
Normal file
65
vendor/users/examples/threading.rs
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//! This example demonstrates how to use a `UsersCache` cache in a
|
||||
//! multi-threaded situation. The cache uses `RefCell`s internally, so it
|
||||
//! is distinctly not thread-safe. Instead, you’ll need to place it within
|
||||
//! some kind of lock in order to have threads access it one-at-a-time.
|
||||
//!
|
||||
//! It queries all the users it can find in the range 500..510. This is the
|
||||
//! default uid range on my Apple laptop -- Linux starts counting from 1000,
|
||||
//! but I can’t include both in the range! It spawns one thread per user to
|
||||
//! query, with each thread accessing the same cache.
|
||||
//!
|
||||
//! Then, afterwards, it retrieves references to the users that had been
|
||||
//! cached earlier.
|
||||
|
||||
// For extra fun, try uncommenting some of the lines of code below, making
|
||||
// the code try to access the users cache *without* a Mutex, and see it
|
||||
// spew compile errors at you.
|
||||
|
||||
extern crate users;
|
||||
use users::{Users, UsersCache, uid_t};
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::thread;
|
||||
|
||||
const LO: uid_t = 500;
|
||||
const HI: uid_t = 510;
|
||||
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// For thread-safely, our users cache needs to be within a Mutex, so
|
||||
// only one thread can access it once. This Mutex needs to be within an
|
||||
// Arc, so multiple threads can access the Mutex.
|
||||
let cache = Arc::new(Mutex::new(UsersCache::new()));
|
||||
// let cache = UsersCache::empty_cache();
|
||||
|
||||
// Loop over the range and query all the users in the range. Although we
|
||||
// could use the `&User` values returned, we just ignore them.
|
||||
for uid in LO .. HI {
|
||||
let cache = Arc::clone(&cache);
|
||||
|
||||
thread::spawn(move || {
|
||||
let cache = cache.lock().unwrap(); // Unlock the mutex
|
||||
let _ = cache.get_user_by_uid(uid); // Query our users cache!
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all the threads to finish.
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
// Loop over the same range and print out all the users we find.
|
||||
// These users will be retrieved from the cache.
|
||||
for uid in LO .. HI {
|
||||
let cache = cache.lock().unwrap(); // Re-unlock the mutex
|
||||
if let Some(u) = cache.get_user_by_uid(uid) { // Re-query our cache!
|
||||
println!("User #{} is {}", u.uid(), u.name().to_string_lossy())
|
||||
}
|
||||
else {
|
||||
println!("User #{} does not exist", uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
1269
vendor/users/src/base.rs
vendored
Normal file
1269
vendor/users/src/base.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
360
vendor/users/src/cache.rs
vendored
Normal file
360
vendor/users/src/cache.rs
vendored
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
//! A cache for users and groups provided by the OS.
|
||||
//!
|
||||
//! Because the users table changes so infrequently, it's common for
|
||||
//! short-running programs to cache the results instead of getting the most
|
||||
//! up-to-date entries every time. The [`UsersCache`](struct.UsersCache.html)
|
||||
//! type helps with this, providing methods that have the same name as the
|
||||
//! others in this crate, only they store the results.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::sync::Arc;
|
||||
//! use users::{Users, UsersCache};
|
||||
//!
|
||||
//! let mut cache = UsersCache::new();
|
||||
//! let user = cache.get_user_by_uid(502).expect("User not found");
|
||||
//! let same_user = cache.get_user_by_uid(502).unwrap();
|
||||
//!
|
||||
//! // The two returned values point to the same User
|
||||
//! assert!(Arc::ptr_eq(&user, &same_user));
|
||||
//! ```
|
||||
//!
|
||||
//! ## Caching, multiple threads, and mutability
|
||||
//!
|
||||
//! The `UsersCache` type is caught between a rock and a hard place when it
|
||||
//! comes to providing references to users and groups.
|
||||
//!
|
||||
//! Instead of returning a fresh `User` struct each time, for example, it will
|
||||
//! return a reference to the version it currently has in its cache. So you can
|
||||
//! ask for User #501 twice, and you’ll get a reference to the same value both
|
||||
//! time. Its methods are *idempotent* -- calling one multiple times has the
|
||||
//! same effect as calling one once.
|
||||
//!
|
||||
//! This works fine in theory, but in practice, the cache has to update its own
|
||||
//! state somehow: it contains several `HashMap`s that hold the result of user
|
||||
//! and group lookups. Rust provides mutability in two ways:
|
||||
//!
|
||||
//! 1. Have its methods take `&mut self`, instead of `&self`, allowing the
|
||||
//! internal maps to be mutated (“inherited mutability”)
|
||||
//! 2. Wrap the internal maps in a `RefCell`, allowing them to be modified
|
||||
//! (“interior mutability”).
|
||||
//!
|
||||
//! Unfortunately, Rust is also very protective of references to a mutable
|
||||
//! value. In this case, switching to `&mut self` would only allow for one user
|
||||
//! to be read at a time!
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use users::{Users, Groups, UsersCache};
|
||||
//!
|
||||
//! let mut cache = UsersCache::new();
|
||||
//!
|
||||
//! let uid = cache.get_current_uid(); // OK...
|
||||
//! let user = cache.get_user_by_uid(uid).unwrap(); // OK...
|
||||
//! let group = cache.get_group_by_gid(user.primary_group_id()); // No!
|
||||
//! ```
|
||||
//!
|
||||
//! When we get the `user`, it returns an optional reference (which we unwrap)
|
||||
//! to the user’s entry in the cache. This is a reference to something contained
|
||||
//! in a mutable value. Then, when we want to get the user’s primary group, it
|
||||
//! will return *another* reference to the same mutable value. This is something
|
||||
//! that Rust explicitly disallows!
|
||||
//!
|
||||
//! The compiler wasn’t on our side with Option 1, so let’s try Option 2:
|
||||
//! changing the methods back to `&self` instead of `&mut self`, and using
|
||||
//! `RefCell`s internally. However, Rust is smarter than this, and knows that
|
||||
//! we’re just trying the same trick as earlier. A simplified implementation of
|
||||
//! a user cache lookup would look something like this:
|
||||
//!
|
||||
//! ```text
|
||||
//! fn get_user_by_uid(&self, uid: uid_t) -> Option<&User> {
|
||||
//! let users = self.users.borrow_mut();
|
||||
//! users.get(uid)
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Rust won’t allow us to return a reference like this because the `Ref` of the
|
||||
//! `RefCell` just gets dropped at the end of the method, meaning that our
|
||||
//! reference does not live long enough.
|
||||
//!
|
||||
//! So instead of doing any of that, we use `Arc` everywhere in order to get
|
||||
//! around all the lifetime restrictions. Returning reference-counted users and
|
||||
//! groups mean that we don’t have to worry about further uses of the cache, as
|
||||
//! the values themselves don’t count as being stored *in* the cache anymore. So
|
||||
//! it can be queried multiple times or go out of scope and the values it
|
||||
//! produces are not affected.
|
||||
|
||||
use libc::{uid_t, gid_t};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use base::{User, Group, all_users};
|
||||
use traits::{Users, Groups};
|
||||
|
||||
|
||||
/// A producer of user and group instances that caches every result.
|
||||
///
|
||||
/// For more information, see the [`users::cache` module documentation](index.html).
|
||||
pub struct UsersCache {
|
||||
users: BiMap<uid_t, User>,
|
||||
groups: BiMap<gid_t, Group>,
|
||||
|
||||
uid: Cell<Option<uid_t>>,
|
||||
gid: Cell<Option<gid_t>>,
|
||||
euid: Cell<Option<uid_t>>,
|
||||
egid: Cell<Option<gid_t>>,
|
||||
}
|
||||
|
||||
/// A kinda-bi-directional `HashMap` that associates keys to values, and
|
||||
/// then strings back to keys.
|
||||
///
|
||||
/// It doesn’t go the full route and offer *values*-to-keys lookup, because we
|
||||
/// only want to search based on usernames and group names. There wouldn’t be
|
||||
/// much point offering a “User to uid” map, as the uid is present in the
|
||||
/// `User` struct!
|
||||
struct BiMap<K, V> {
|
||||
forward: RefCell< HashMap<K, Option<Arc<V>>> >,
|
||||
backward: RefCell< HashMap<Arc<OsStr>, Option<K>> >,
|
||||
}
|
||||
|
||||
|
||||
// Default has to be impl’d manually here, because there’s no
|
||||
// Default impl on User or Group, even though those types aren’t
|
||||
// needed to produce a default instance of any HashMaps...
|
||||
impl Default for UsersCache {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
users: BiMap {
|
||||
forward: RefCell::new(HashMap::new()),
|
||||
backward: RefCell::new(HashMap::new()),
|
||||
},
|
||||
|
||||
groups: BiMap {
|
||||
forward: RefCell::new(HashMap::new()),
|
||||
backward: RefCell::new(HashMap::new()),
|
||||
},
|
||||
|
||||
uid: Cell::new(None),
|
||||
gid: Cell::new(None),
|
||||
euid: Cell::new(None),
|
||||
egid: Cell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UsersCache {
|
||||
|
||||
/// Creates a new empty cache.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use users::cache::UsersCache;
|
||||
///
|
||||
/// let cache = UsersCache::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a new cache that contains all the users present on the system.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is `unsafe` because we cannot prevent data races if two caches
|
||||
/// were attempted to be initialised on different threads at the same time.
|
||||
/// For more information, see the [`all_users` documentation](../fn.all_users.html).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use users::cache::UsersCache;
|
||||
///
|
||||
/// let cache = unsafe { UsersCache::with_all_users() };
|
||||
/// ```
|
||||
pub unsafe fn with_all_users() -> Self {
|
||||
let cache = Self::new();
|
||||
|
||||
for user in all_users() {
|
||||
let uid = user.uid();
|
||||
let user_arc = Arc::new(user);
|
||||
cache.users.forward.borrow_mut().insert(uid, Some(Arc::clone(&user_arc)));
|
||||
cache.users.backward.borrow_mut().insert(Arc::clone(&user_arc.name_arc), Some(uid));
|
||||
}
|
||||
|
||||
cache
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: stop using ‘Arc::from’ with entry API
|
||||
// The ‘get_*_by_name’ functions below create a new Arc before even testing if
|
||||
// the user exists in the cache, essentially creating an unnecessary Arc.
|
||||
// https://internals.rust-lang.org/t/pre-rfc-abandonning-morals-in-the-name-of-performance-the-raw-entry-api/7043/51
|
||||
// https://github.com/rust-lang/rfcs/pull/1769
|
||||
|
||||
|
||||
impl Users for UsersCache {
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>> {
|
||||
let mut users_forward = self.users.forward.borrow_mut();
|
||||
|
||||
let entry = match users_forward.entry(uid) {
|
||||
Vacant(e) => e,
|
||||
Occupied(e) => return e.get().as_ref().map(Arc::clone),
|
||||
};
|
||||
|
||||
if let Some(user) = super::get_user_by_uid(uid) {
|
||||
let newsername = Arc::clone(&user.name_arc);
|
||||
let mut users_backward = self.users.backward.borrow_mut();
|
||||
users_backward.insert(newsername, Some(uid));
|
||||
|
||||
let user_arc = Arc::new(user);
|
||||
entry.insert(Some(Arc::clone(&user_arc)));
|
||||
Some(user_arc)
|
||||
}
|
||||
else {
|
||||
entry.insert(None);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_by_name<S: AsRef<OsStr> + ?Sized>(&self, username: &S) -> Option<Arc<User>> {
|
||||
let mut users_backward = self.users.backward.borrow_mut();
|
||||
|
||||
let entry = match users_backward.entry(Arc::from(username.as_ref())) {
|
||||
Vacant(e) => e,
|
||||
Occupied(e) => {
|
||||
return (*e.get()).and_then(|uid| {
|
||||
let users_forward = self.users.forward.borrow_mut();
|
||||
users_forward[&uid].as_ref().map(Arc::clone)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(user) = super::get_user_by_name(username) {
|
||||
let uid = user.uid();
|
||||
let user_arc = Arc::new(user);
|
||||
|
||||
let mut users_forward = self.users.forward.borrow_mut();
|
||||
users_forward.insert(uid, Some(Arc::clone(&user_arc)));
|
||||
entry.insert(Some(uid));
|
||||
|
||||
Some(user_arc)
|
||||
}
|
||||
else {
|
||||
entry.insert(None);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_uid(&self) -> uid_t {
|
||||
self.uid.get().unwrap_or_else(|| {
|
||||
let uid = super::get_current_uid();
|
||||
self.uid.set(Some(uid));
|
||||
uid
|
||||
})
|
||||
}
|
||||
|
||||
fn get_current_username(&self) -> Option<Arc<OsStr>> {
|
||||
let uid = self.get_current_uid();
|
||||
self.get_user_by_uid(uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
|
||||
fn get_effective_uid(&self) -> uid_t {
|
||||
self.euid.get().unwrap_or_else(|| {
|
||||
let uid = super::get_effective_uid();
|
||||
self.euid.set(Some(uid));
|
||||
uid
|
||||
})
|
||||
}
|
||||
|
||||
fn get_effective_username(&self) -> Option<Arc<OsStr>> {
|
||||
let uid = self.get_effective_uid();
|
||||
self.get_user_by_uid(uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Groups for UsersCache {
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>> {
|
||||
let mut groups_forward = self.groups.forward.borrow_mut();
|
||||
|
||||
let entry = match groups_forward.entry(gid) {
|
||||
Vacant(e) => e,
|
||||
Occupied(e) => return e.get().as_ref().map(Arc::clone),
|
||||
};
|
||||
|
||||
if let Some(group) = super::get_group_by_gid(gid) {
|
||||
let new_group_name = Arc::clone(&group.name_arc);
|
||||
let mut groups_backward = self.groups.backward.borrow_mut();
|
||||
groups_backward.insert(new_group_name, Some(gid));
|
||||
|
||||
let group_arc = Arc::new(group);
|
||||
entry.insert(Some(Arc::clone(&group_arc)));
|
||||
Some(group_arc)
|
||||
}
|
||||
else {
|
||||
entry.insert(None);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_group_by_name<S: AsRef<OsStr> + ?Sized>(&self, group_name: &S) -> Option<Arc<Group>> {
|
||||
let mut groups_backward = self.groups.backward.borrow_mut();
|
||||
|
||||
let entry = match groups_backward.entry(Arc::from(group_name.as_ref())) {
|
||||
Vacant(e) => e,
|
||||
Occupied(e) => {
|
||||
return (*e.get()).and_then(|gid| {
|
||||
let groups_forward = self.groups.forward.borrow_mut();
|
||||
groups_forward[&gid].as_ref().cloned()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(group) = super::get_group_by_name(group_name) {
|
||||
let group_arc = Arc::new(group.clone());
|
||||
let gid = group.gid();
|
||||
|
||||
let mut groups_forward = self.groups.forward.borrow_mut();
|
||||
groups_forward.insert(gid, Some(Arc::clone(&group_arc)));
|
||||
entry.insert(Some(gid));
|
||||
|
||||
Some(group_arc)
|
||||
}
|
||||
else {
|
||||
entry.insert(None);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_gid(&self) -> gid_t {
|
||||
self.gid.get().unwrap_or_else(|| {
|
||||
let gid = super::get_current_gid();
|
||||
self.gid.set(Some(gid));
|
||||
gid
|
||||
})
|
||||
}
|
||||
|
||||
fn get_current_groupname(&self) -> Option<Arc<OsStr>> {
|
||||
let gid = self.get_current_gid();
|
||||
self.get_group_by_gid(gid).map(|g| Arc::clone(&g.name_arc))
|
||||
}
|
||||
|
||||
fn get_effective_gid(&self) -> gid_t {
|
||||
self.egid.get().unwrap_or_else(|| {
|
||||
let gid = super::get_effective_gid();
|
||||
self.egid.set(Some(gid));
|
||||
gid
|
||||
})
|
||||
}
|
||||
|
||||
fn get_effective_groupname(&self) -> Option<Arc<OsStr>> {
|
||||
let gid = self.get_effective_gid();
|
||||
self.get_group_by_gid(gid).map(|g| Arc::clone(&g.name_arc))
|
||||
}
|
||||
}
|
||||
153
vendor/users/src/lib.rs
vendored
Normal file
153
vendor/users/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(nonstandard_style)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unreachable_pub)]
|
||||
#![warn(unused)]
|
||||
|
||||
|
||||
//! This is a library for getting information on Unix users and groups. It
|
||||
//! supports getting the system users, and creating your own mock tables.
|
||||
//!
|
||||
//! In Unix, each user has an individual *user ID*, and each process has an
|
||||
//! *effective user ID* that says which user’s permissions it is using.
|
||||
//! Furthermore, users can be the members of *groups*, which also have names and
|
||||
//! IDs. This functionality is exposed in libc, the C standard library, but as
|
||||
//! an unsafe Rust interface. This wrapper library provides a safe interface,
|
||||
//! using [`User`](struct.user.html) and [`Group`](struct.group.html) types
|
||||
//! and functions such as [`get_user_by_uid`](fn.get_user_by_uid.html) instead
|
||||
//! of low-level pointers and strings. It also offers basic caching
|
||||
//! functionality.
|
||||
//!
|
||||
//! It does not (yet) offer *editing* functionality; the values returned are
|
||||
//! read-only.
|
||||
//!
|
||||
//!
|
||||
//! ## Users
|
||||
//!
|
||||
//! The function [`get_current_uid`](fn.get_current_uid.html) returns a
|
||||
//! `uid_t` value representing the user currently running the program, and the
|
||||
//! [`get_user_by_uid`](fn.get_user_by_uid.html) function scans the users
|
||||
//! database and returns a `User` with the user’s information. This function
|
||||
//! returns `None` when there is no user for that ID. The `uid_t` type is
|
||||
//! re-exported from the libc crate.
|
||||
//!
|
||||
//! A [`User`](struct.User.html) value has the following accessors:
|
||||
//!
|
||||
//! - **uid:** The user’s ID
|
||||
//! - **name:** The user’s name
|
||||
//! - **primary_group:** The ID of this user’s primary group
|
||||
//!
|
||||
//! Here is a complete example that prints out the current user’s name:
|
||||
//!
|
||||
//! ```
|
||||
//! use users::{get_user_by_uid, get_current_uid};
|
||||
//!
|
||||
//! let user = get_user_by_uid(get_current_uid()).unwrap();
|
||||
//! println!("Hello, {}!", user.name().to_string_lossy());
|
||||
//! ```
|
||||
//!
|
||||
//! This code assumes (with `unwrap()`) that the user hasn’t been deleted after
|
||||
//! the program has started running. For arbitrary user IDs, this is **not** a
|
||||
//! safe assumption: it’s possible to delete a user while it’s running a
|
||||
//! program, or is the owner of files, or for that user to have never existed.
|
||||
//! So always check the return values!
|
||||
//!
|
||||
//! There is also a [`get_current_username`](fn.get_current_username.html)
|
||||
//! function, as it’s such a common operation that it deserves special
|
||||
//! treatment.
|
||||
//!
|
||||
//!
|
||||
//! ## Caching
|
||||
//!
|
||||
//! Despite the above warning, the users and groups database rarely changes.
|
||||
//! While a short program may only need to get user information once, a
|
||||
//! long-running one may need to re-query the database many times, and a
|
||||
//! medium-length one may get away with caching the values to save on redundant
|
||||
//! system calls.
|
||||
//!
|
||||
//! For this reason, this crate offers a caching interface to the database,
|
||||
//! which offers the same functionality while holding on to every result,
|
||||
//! caching the information so it can be re-used.
|
||||
//!
|
||||
//! To introduce a cache, create a new
|
||||
//! [`UsersCache`](cache/struct.UsersCache.html). It has functions with the
|
||||
//! same names as the ones from earlier. For example:
|
||||
//!
|
||||
//! ```
|
||||
//! use users::{Users, Groups, UsersCache};
|
||||
//!
|
||||
//! let mut cache = UsersCache::new();
|
||||
//! let uid = cache.get_current_uid();
|
||||
//! let user = cache.get_user_by_uid(uid).unwrap();
|
||||
//! println!("Hello again, {}!", user.name().to_string_lossy());
|
||||
//! ```
|
||||
//!
|
||||
//! This cache is **only additive**: it’s not possible to drop it, or erase
|
||||
//! selected entries, as when the database may have been modified, it’s best to
|
||||
//! start entirely afresh. So to accomplish this, just start using a new
|
||||
//! `UsersCache`.
|
||||
//!
|
||||
//!
|
||||
//! ## Groups
|
||||
//!
|
||||
//! Finally, it’s possible to get groups in a similar manner.
|
||||
//! A [`Group`](struct.Group.html) has the following accessors:
|
||||
//!
|
||||
//! - **gid:** The group’s ID
|
||||
//! - **name:** The group’s name
|
||||
//!
|
||||
//! And again, a complete example:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use users::{Users, Groups, UsersCache};
|
||||
//!
|
||||
//! let mut cache = UsersCache::new();
|
||||
//! let group = cache.get_group_by_name("admin").expect("No such group 'admin'!");
|
||||
//! println!("The '{}' group has the ID {}", group.name().to_string_lossy(), group.gid());
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Logging
|
||||
//!
|
||||
//! The `logging` feature, which is on by default, uses the `log` crate to
|
||||
//! record all interactions with the operating system.
|
||||
//!
|
||||
//!
|
||||
//! ## Caveats
|
||||
//!
|
||||
//! You should be prepared for the users and groups tables to be completely
|
||||
//! broken: IDs shouldn’t be assumed to map to actual users and groups, and
|
||||
//! usernames and group names aren’t guaranteed to map either!
|
||||
//!
|
||||
//! Use the [`mock`](mock/index.html) module to create custom tables to test
|
||||
//! your code for these edge cases.
|
||||
|
||||
|
||||
extern crate libc;
|
||||
pub use libc::{uid_t, gid_t};
|
||||
|
||||
mod base;
|
||||
pub use base::{User, Group, os};
|
||||
pub use base::{get_user_by_uid, get_user_by_name};
|
||||
pub use base::{get_group_by_gid, get_group_by_name};
|
||||
pub use base::{get_current_uid, get_current_username};
|
||||
pub use base::{get_effective_uid, get_effective_username};
|
||||
pub use base::{get_current_gid, get_current_groupname};
|
||||
pub use base::{get_effective_gid, get_effective_groupname};
|
||||
pub use base::{get_user_groups, group_access_list};
|
||||
pub use base::{all_users};
|
||||
|
||||
#[cfg(feature = "cache")]
|
||||
pub mod cache;
|
||||
|
||||
#[cfg(feature = "cache")]
|
||||
pub use cache::UsersCache;
|
||||
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
|
||||
pub mod switch;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{Users, Groups};
|
||||
237
vendor/users/src/mock.rs
vendored
Normal file
237
vendor/users/src/mock.rs
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
//! Mockable users and groups.
|
||||
//!
|
||||
//! When you’re testing your code, you don’t want to actually rely on the
|
||||
//! system actually having various users and groups present - it’s much better
|
||||
//! to have a custom set of users that are *guaranteed* to be there, so you can
|
||||
//! test against them.
|
||||
//!
|
||||
//! This module allows you to create these custom users and groups
|
||||
//! definitions, then access them using the same `Users` trait as in the main
|
||||
//! library, with few changes to your code.
|
||||
//!
|
||||
//!
|
||||
//! ## Creating Mock Users
|
||||
//!
|
||||
//! The only thing a mock users table needs to know in advance is the UID of
|
||||
//! the current user. Aside from that, you can add users and groups with
|
||||
//! `add_user` and `add_group` to the table:
|
||||
//!
|
||||
//! ```
|
||||
//! use std::sync::Arc;
|
||||
//! use users::mock::{MockUsers, User, Group};
|
||||
//! use users::os::unix::{UserExt, GroupExt};
|
||||
//!
|
||||
//! let mut users = MockUsers::with_current_uid(1000);
|
||||
//! let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins");
|
||||
//! users.add_user(bobbins);
|
||||
//! users.add_group(Group::new(100, "funkyppl"));
|
||||
//! ```
|
||||
//!
|
||||
//! The exports get re-exported into the mock module, for simpler `use` lines.
|
||||
//!
|
||||
//!
|
||||
//! ## Using Mock Users
|
||||
//!
|
||||
//! To set your program up to use either type of `Users` table, make your
|
||||
//! functions and structs accept a generic parameter that implements the `Users`
|
||||
//! trait. Then, you can pass in a value of either Cache or Mock type.
|
||||
//!
|
||||
//! Here’s a complete example:
|
||||
//!
|
||||
//! ```
|
||||
//! use std::sync::Arc;
|
||||
//! use users::{Users, UsersCache, User};
|
||||
//! use users::os::unix::UserExt;
|
||||
//! use users::mock::MockUsers;
|
||||
//!
|
||||
//! fn print_current_username<U: Users>(users: &mut U) {
|
||||
//! println!("Current user: {:?}", users.get_current_username());
|
||||
//! }
|
||||
//!
|
||||
//! let mut users = MockUsers::with_current_uid(1001);
|
||||
//! users.add_user(User::new(1001, "fred", 101));
|
||||
//! print_current_username(&mut users);
|
||||
//!
|
||||
//! let mut actual_users = UsersCache::new();
|
||||
//! print_current_username(&mut actual_users);
|
||||
//! ```
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use libc::{uid_t, gid_t};
|
||||
pub use base::{User, Group};
|
||||
pub use traits::{Users, Groups};
|
||||
|
||||
|
||||
/// A mocking users table that you can add your own users and groups to.
|
||||
pub struct MockUsers {
|
||||
users: HashMap<uid_t, Arc<User>>,
|
||||
groups: HashMap<gid_t, Arc<Group>>,
|
||||
uid: uid_t,
|
||||
}
|
||||
|
||||
|
||||
impl MockUsers {
|
||||
|
||||
/// Create a new, empty mock users table.
|
||||
pub fn with_current_uid(current_uid: uid_t) -> Self {
|
||||
Self {
|
||||
users: HashMap::new(),
|
||||
groups: HashMap::new(),
|
||||
uid: current_uid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a user to the users table.
|
||||
pub fn add_user(&mut self, user: User) -> Option<Arc<User>> {
|
||||
self.users.insert(user.uid(), Arc::new(user))
|
||||
}
|
||||
|
||||
/// Add a group to the groups table.
|
||||
pub fn add_group(&mut self, group: Group) -> Option<Arc<Group>> {
|
||||
self.groups.insert(group.gid(), Arc::new(group))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Users for MockUsers {
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>> {
|
||||
self.users.get(&uid).cloned()
|
||||
}
|
||||
|
||||
fn get_user_by_name<S: AsRef<OsStr> + ?Sized>(&self, username: &S) -> Option<Arc<User>> {
|
||||
self.users.values().find(|u| u.name() == username.as_ref()).cloned()
|
||||
}
|
||||
|
||||
fn get_current_uid(&self) -> uid_t {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn get_current_username(&self) -> Option<Arc<OsStr>> {
|
||||
self.users.get(&self.uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
|
||||
fn get_effective_uid(&self) -> uid_t {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn get_effective_username(&self) -> Option<Arc<OsStr>> {
|
||||
self.users.get(&self.uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Groups for MockUsers {
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>> {
|
||||
self.groups.get(&gid).cloned()
|
||||
}
|
||||
|
||||
fn get_group_by_name<S: AsRef<OsStr> + ?Sized>(&self, group_name: &S) -> Option<Arc<Group>> {
|
||||
self.groups.values().find(|g| g.name() == group_name.as_ref()).cloned()
|
||||
}
|
||||
|
||||
fn get_current_gid(&self) -> uid_t {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn get_current_groupname(&self) -> Option<Arc<OsStr>> {
|
||||
self.groups.get(&self.uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
|
||||
fn get_effective_gid(&self) -> uid_t {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn get_effective_groupname(&self) -> Option<Arc<OsStr>> {
|
||||
self.groups.get(&self.uid).map(|u| Arc::clone(&u.name_arc))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::MockUsers;
|
||||
use base::{User, Group};
|
||||
use traits::{Users, Groups};
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn current_username() {
|
||||
let mut users = MockUsers::with_current_uid(1337);
|
||||
users.add_user(User::new(1337, "fred", 101));
|
||||
assert_eq!(Some(Arc::from(OsStr::new("fred"))),
|
||||
users.get_current_username())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_current_username() {
|
||||
let users = MockUsers::with_current_uid(1337);
|
||||
assert_eq!(None, users.get_current_username())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid() {
|
||||
let mut users = MockUsers::with_current_uid(0);
|
||||
users.add_user(User::new(1337, "fred", 101));
|
||||
assert_eq!(Some(Arc::from(OsStr::new("fred"))),
|
||||
users.get_user_by_uid(1337).map(|u| Arc::clone(&u.name_arc)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username() {
|
||||
let mut users = MockUsers::with_current_uid(1337);
|
||||
users.add_user(User::new(1440, "fred", 101));
|
||||
assert_eq!(Some(1440),
|
||||
users.get_user_by_name("fred").map(|u| u.uid()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_username() {
|
||||
let mut users = MockUsers::with_current_uid(1337);
|
||||
users.add_user(User::new(1337, "fred", 101));
|
||||
assert_eq!(None,
|
||||
users.get_user_by_name("criminy").map(|u| u.uid()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_uid() {
|
||||
let users = MockUsers::with_current_uid(0);
|
||||
assert_eq!(None,
|
||||
users.get_user_by_uid(1337).map(|u| Arc::clone(&u.name_arc)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gid() {
|
||||
let mut users = MockUsers::with_current_uid(0);
|
||||
users.add_group(Group::new(1337, "fred"));
|
||||
assert_eq!(Some(Arc::from(OsStr::new("fred"))),
|
||||
users.get_group_by_gid(1337).map(|g| Arc::clone(&g.name_arc)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_name() {
|
||||
let mut users = MockUsers::with_current_uid(0);
|
||||
users.add_group(Group::new(1337, "fred"));
|
||||
assert_eq!(Some(1337),
|
||||
users.get_group_by_name("fred").map(|g| g.gid()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_group_name() {
|
||||
let mut users = MockUsers::with_current_uid(0);
|
||||
users.add_group(Group::new(1337, "fred"));
|
||||
assert_eq!(None,
|
||||
users.get_group_by_name("santa").map(|g| g.gid()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_gid() {
|
||||
let users = MockUsers::with_current_uid(0);
|
||||
assert_eq!(None,
|
||||
users.get_group_by_gid(1337).map(|g| Arc::clone(&g.name_arc)))
|
||||
}
|
||||
}
|
||||
265
vendor/users/src/switch.rs
vendored
Normal file
265
vendor/users/src/switch.rs
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
//! Functions for switching the running process’s user or group.
|
||||
|
||||
use std::io;
|
||||
use libc::{uid_t, gid_t, c_int};
|
||||
|
||||
use base::{get_effective_uid, get_effective_gid};
|
||||
|
||||
|
||||
// NOTE: for whatever reason, it seems these are not available in libc on BSD platforms, so they
|
||||
// need to be included manually
|
||||
extern {
|
||||
fn setreuid(ruid: uid_t, euid: uid_t) -> c_int;
|
||||
fn setregid(rgid: gid_t, egid: gid_t) -> c_int;
|
||||
}
|
||||
|
||||
|
||||
/// Sets the **current user** for the running process to the one with the
|
||||
/// given user ID.
|
||||
///
|
||||
/// Typically, trying to switch to anyone other than the user already running
|
||||
/// the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`setuid`](https://docs.rs/libc/*/libc/fn.setuid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `setuid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_current_uid;
|
||||
///
|
||||
/// set_current_uid(1001);
|
||||
/// // current user ID is 1001
|
||||
/// ```
|
||||
pub fn set_current_uid(uid: uid_t) -> io::Result<()> {
|
||||
match unsafe { libc::setuid(uid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("setuid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the **current group** for the running process to the one with the
|
||||
/// given group ID.
|
||||
///
|
||||
/// Typically, trying to switch to any group other than the group already
|
||||
/// running the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`setgid`](https://docs.rs/libc/*/libc/fn.setgid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `setgid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_current_gid;
|
||||
///
|
||||
/// set_current_gid(1001);
|
||||
/// // current group ID is 1001
|
||||
/// ```
|
||||
pub fn set_current_gid(gid: gid_t) -> io::Result<()> {
|
||||
match unsafe { libc::setgid(gid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("setgid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the **effective user** for the running process to the one with the
|
||||
/// given user ID.
|
||||
///
|
||||
/// Typically, trying to switch to anyone other than the user already running
|
||||
/// the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`seteuid`](https://docs.rs/libc/*/libc/fn.seteuid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `seteuid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_effective_uid;
|
||||
///
|
||||
/// set_effective_uid(1001);
|
||||
/// // current effective user ID is 1001
|
||||
/// ```
|
||||
pub fn set_effective_uid(uid: uid_t) -> io::Result<()> {
|
||||
match unsafe { libc::seteuid(uid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("seteuid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the **effective group** for the running process to the one with the
|
||||
/// given group ID.
|
||||
///
|
||||
/// Typically, trying to switch to any group other than the group already
|
||||
/// running the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`setegid`](https://docs.rs/libc/*/libc/fn.setegid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `setegid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_effective_gid;
|
||||
///
|
||||
/// set_effective_gid(1001);
|
||||
/// // current effective group ID is 1001
|
||||
/// ```
|
||||
pub fn set_effective_gid(gid: gid_t) -> io::Result<()> {
|
||||
match unsafe { libc::setegid(gid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("setegid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets both the **current user** and the **effective user** for the running
|
||||
/// process to the ones with the given user IDs.
|
||||
///
|
||||
/// Typically, trying to switch to anyone other than the user already running
|
||||
/// the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`setreuid`](https://docs.rs/libc/*/libc/fn.setreuid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `setreuid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_both_uid;
|
||||
///
|
||||
/// set_both_uid(1001, 1001);
|
||||
/// // current user ID and effective user ID are 1001
|
||||
/// ```
|
||||
pub fn set_both_uid(ruid: uid_t, euid: uid_t) -> io::Result<()> {
|
||||
match unsafe { setreuid(ruid, euid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("setreuid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets both the **current group** and the **effective group** for the
|
||||
/// running process to the ones with the given group IDs.
|
||||
///
|
||||
/// Typically, trying to switch to any group other than the group already
|
||||
/// running the process requires root privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`setregid`](https://docs.rs/libc/*/libc/fn.setregid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during the
|
||||
/// `setregid` call.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::set_both_gid;
|
||||
///
|
||||
/// set_both_gid(1001, 1001);
|
||||
/// // current user ID and effective group ID are 1001
|
||||
/// ```
|
||||
pub fn set_both_gid(rgid: gid_t, egid: gid_t) -> io::Result<()> {
|
||||
match unsafe { setregid(rgid, egid) } {
|
||||
0 => Ok(()),
|
||||
-1 => Err(io::Error::last_os_error()),
|
||||
n => unreachable!("setregid returned {}", n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard returned from a `switch_user_group` call.
|
||||
pub struct SwitchUserGuard {
|
||||
uid: uid_t,
|
||||
gid: gid_t,
|
||||
}
|
||||
|
||||
impl Drop for SwitchUserGuard {
|
||||
fn drop(&mut self) {
|
||||
set_effective_gid(self.gid).expect("Failed to set effective gid");
|
||||
set_effective_uid(self.uid).expect("Failed to set effective uid");
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the **effective user** and the **effective group** for the current
|
||||
/// scope.
|
||||
///
|
||||
/// Typically, trying to switch to any user or group other than the ones already
|
||||
/// running the process requires root privileges.
|
||||
///
|
||||
/// # Security considerations
|
||||
///
|
||||
/// - Because Rust does not guarantee running the destructor, it’s a good idea
|
||||
/// to call [`std::mem::drop`](https://doc.rust-lang.org/std/mem/fn.drop.html)
|
||||
/// on the guard manually in security-sensitive situations.
|
||||
/// - This function switches the group before the user to prevent the user’s
|
||||
/// privileges being dropped before trying to change the group (look up
|
||||
/// `POS36-C`).
|
||||
/// - This function will panic upon failing to set either walue, so the
|
||||
/// program does not continue executing with too many privileges.
|
||||
///
|
||||
/// # libc functions used
|
||||
///
|
||||
/// - [`seteuid`](https://docs.rs/libc/*/libc/fn.seteuid.html)
|
||||
/// - [`setegid`](https://docs.rs/libc/*/libc/fn.setegid.html)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err` when an I/O error occurs during either
|
||||
/// the `seteuid` or `setegid` calls.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use users::switch::switch_user_group;
|
||||
/// use std::mem::drop;
|
||||
///
|
||||
/// {
|
||||
/// let guard = switch_user_group(1001, 1001);
|
||||
/// // current and effective user and group IDs are 1001
|
||||
/// drop(guard);
|
||||
/// }
|
||||
/// // back to the old values
|
||||
/// ```
|
||||
pub fn switch_user_group(uid: uid_t, gid: gid_t) -> io::Result<SwitchUserGuard> {
|
||||
let current_state = SwitchUserGuard {
|
||||
gid: get_effective_gid(),
|
||||
uid: get_effective_uid(),
|
||||
};
|
||||
|
||||
set_effective_gid(gid)?;
|
||||
set_effective_uid(uid)?;
|
||||
Ok(current_state)
|
||||
}
|
||||
51
vendor/users/src/traits.rs
vendored
Normal file
51
vendor/users/src/traits.rs
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use libc::{uid_t, gid_t};
|
||||
|
||||
use base::{User, Group};
|
||||
|
||||
|
||||
/// Trait for producers of users.
|
||||
pub trait Users {
|
||||
|
||||
/// Returns a `User` if one exists for the given user ID; otherwise, returns `None`.
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>>;
|
||||
|
||||
/// Returns a `User` if one exists for the given username; otherwise, returns `None`.
|
||||
fn get_user_by_name<S: AsRef<OsStr> + ?Sized>(&self, username: &S) -> Option<Arc<User>>;
|
||||
|
||||
/// Returns the user ID for the user running the process.
|
||||
fn get_current_uid(&self) -> uid_t;
|
||||
|
||||
/// Returns the username of the user running the process.
|
||||
fn get_current_username(&self) -> Option<Arc<OsStr>>;
|
||||
|
||||
/// Returns the effective user id.
|
||||
fn get_effective_uid(&self) -> uid_t;
|
||||
|
||||
/// Returns the effective username.
|
||||
fn get_effective_username(&self) -> Option<Arc<OsStr>>;
|
||||
}
|
||||
|
||||
/// Trait for producers of groups.
|
||||
pub trait Groups {
|
||||
|
||||
/// Returns a `Group` if one exists for the given group ID; otherwise, returns `None`.
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>>;
|
||||
|
||||
/// Returns a `Group` if one exists for the given groupname; otherwise, returns `None`.
|
||||
fn get_group_by_name<S: AsRef<OsStr> + ?Sized>(&self, group_name: &S) -> Option<Arc<Group>>;
|
||||
|
||||
/// Returns the group ID for the user running the process.
|
||||
fn get_current_gid(&self) -> gid_t;
|
||||
|
||||
/// Returns the group name of the user running the process.
|
||||
fn get_current_groupname(&self) -> Option<Arc<OsStr>>;
|
||||
|
||||
/// Returns the effective group id.
|
||||
fn get_effective_gid(&self) -> gid_t;
|
||||
|
||||
/// Returns the effective group name.
|
||||
fn get_effective_groupname(&self) -> Option<Arc<OsStr>>;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue