Skip to content

Commit cfc550f

Browse files
bors[bot]otavio
andauthored
Merge #1139
1139: Add `Users` and `Group` related functions r=asomers a=otavio This was a collaborative work between Johannes Schilling <[email protected]>, Fredrick Brennan <[email protected]> and myself. This PR is a split-off from #864 so we merge ready parts first. Next, we work on the iterators. Signed-off-by: Otavio Salvador <[email protected]> Co-authored-by: Otavio Salvador <[email protected]>
2 parents 6b57a5a + b14415a commit cfc550f

File tree

2 files changed

+257
-1
lines changed

2 files changed

+257
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2020
- Added `mkfifoat`
2121
([#1133](https://github.com/nix-rust/nix/pull/1133))
2222

23+
- Added `User::from_uid`, `User::from_name`, `User::from_gid` and
24+
`Group::from_name`,
25+
([#1139](https://github.com/nix-rust/nix/pull/1139))
26+
2327
### Changed
2428
- `sys::socket::recvfrom` now returns
2529
`Result<(usize, Option<SockAddr>)>` instead of `Result<(usize, SockAddr)>`.

src/unistd.rs

Lines changed: 253 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use fcntl::FcntlArg::F_SETFD;
77
use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
88
uid_t, gid_t, mode_t, PATH_MAX};
99
use std::{fmt, mem, ptr};
10-
use std::ffi::{CStr, OsString, OsStr};
10+
use std::ffi::{CString, CStr, OsString, OsStr};
1111
use std::os::unix::ffi::{OsStringExt, OsStrExt};
1212
use std::os::unix::io::RawFd;
1313
use std::path::PathBuf;
@@ -2424,3 +2424,255 @@ pub fn access<P: ?Sized + NixPath>(path: &P, amode: AccessFlags) -> Result<()> {
24242424
})?;
24252425
Errno::result(res).map(drop)
24262426
}
2427+
2428+
/// Representation of a User, based on `libc::passwd`
2429+
///
2430+
/// The reason some fields in this struct are `String` and others are `CString` is because some
2431+
/// fields are based on the user's locale, which could be non-UTF8, while other fields are
2432+
/// guaranteed to conform to [`NAME_REGEX`](https://serverfault.com/a/73101/407341), which only
2433+
/// contains ASCII.
2434+
#[derive(Debug, Clone, PartialEq)]
2435+
pub struct User {
2436+
/// Username
2437+
pub name: String,
2438+
/// User password (probably encrypted)
2439+
pub passwd: CString,
2440+
/// User ID
2441+
pub uid: Uid,
2442+
/// Group ID
2443+
pub gid: Gid,
2444+
/// User information
2445+
#[cfg(not(target_os = "android"))]
2446+
pub gecos: CString,
2447+
/// Home directory
2448+
pub dir: PathBuf,
2449+
/// Path to shell
2450+
pub shell: PathBuf,
2451+
/// Login class
2452+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2453+
pub class: CString,
2454+
/// Last password change
2455+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2456+
pub change: libc::time_t,
2457+
/// Expiration time of account
2458+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2459+
pub expire: libc::time_t
2460+
}
2461+
2462+
impl From<&libc::passwd> for User {
2463+
fn from(pw: &libc::passwd) -> User {
2464+
unsafe {
2465+
User {
2466+
name: CStr::from_ptr((*pw).pw_name).to_string_lossy().into_owned(),
2467+
passwd: CString::new(CStr::from_ptr((*pw).pw_passwd).to_bytes()).unwrap(),
2468+
#[cfg(not(target_os = "android"))]
2469+
gecos: CString::new(CStr::from_ptr((*pw).pw_gecos).to_bytes()).unwrap(),
2470+
dir: PathBuf::from(OsStr::from_bytes(CStr::from_ptr((*pw).pw_dir).to_bytes())),
2471+
shell: PathBuf::from(OsStr::from_bytes(CStr::from_ptr((*pw).pw_shell).to_bytes())),
2472+
uid: Uid::from_raw((*pw).pw_uid),
2473+
gid: Gid::from_raw((*pw).pw_gid),
2474+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2475+
class: CString::new(CStr::from_ptr((*pw).pw_class).to_bytes()).unwrap(),
2476+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2477+
change: (*pw).pw_change,
2478+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
2479+
expire: (*pw).pw_expire
2480+
}
2481+
}
2482+
}
2483+
}
2484+
2485+
impl User {
2486+
fn from_anything<F>(f: F) -> Result<Option<Self>>
2487+
where
2488+
F: Fn(*mut libc::passwd,
2489+
*mut libc::c_char,
2490+
libc::size_t,
2491+
*mut *mut libc::passwd) -> libc::c_int
2492+
{
2493+
let buflimit = 16384;
2494+
let bufsize = match sysconf(SysconfVar::GETPW_R_SIZE_MAX) {
2495+
Ok(Some(n)) => n as usize,
2496+
Ok(None) | Err(_) => buflimit as usize,
2497+
};
2498+
2499+
let mut cbuf = Vec::with_capacity(bufsize);
2500+
let mut pwd = mem::MaybeUninit::<libc::passwd>::uninit();
2501+
let mut res = ptr::null_mut();
2502+
2503+
loop {
2504+
let error = f(pwd.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res);
2505+
if error == 0 {
2506+
if res.is_null() {
2507+
return Ok(None);
2508+
} else {
2509+
let pwd = unsafe { pwd.assume_init() };
2510+
return Ok(Some(User::from(&pwd)));
2511+
}
2512+
} else if Errno::last() == Errno::ERANGE {
2513+
// Trigger the internal buffer resizing logic.
2514+
reserve_double_buffer_size(&mut cbuf, buflimit)?;
2515+
} else {
2516+
return Err(Error::Sys(Errno::last()));
2517+
}
2518+
}
2519+
}
2520+
2521+
/// Get a user by UID.
2522+
///
2523+
/// Internally, this function calls
2524+
/// [getpwuid_r(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html)
2525+
///
2526+
/// # Examples
2527+
///
2528+
/// ```
2529+
/// use nix::unistd::{Uid, User};
2530+
/// // Returns an Result<Option<User>>, thus the double unwrap.
2531+
/// let res = User::from_uid(Uid::from_raw(0)).unwrap().unwrap();
2532+
/// assert!(res.name == "root");
2533+
/// ```
2534+
pub fn from_uid(uid: Uid) -> Result<Option<Self>> {
2535+
User::from_anything(|pwd, cbuf, cap, res| {
2536+
unsafe { libc::getpwuid_r(uid.0, pwd, cbuf, cap, res) }
2537+
})
2538+
}
2539+
2540+
/// Get a user by name.
2541+
///
2542+
/// Internally, this function calls
2543+
/// [getpwnam_r(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html)
2544+
///
2545+
/// # Examples
2546+
///
2547+
/// ```
2548+
/// use nix::unistd::User;
2549+
/// // Returns an Result<Option<User>>, thus the double unwrap.
2550+
/// let res = User::from_name("root").unwrap().unwrap();
2551+
/// assert!(res.name == "root");
2552+
/// ```
2553+
pub fn from_name(name: &str) -> Result<Option<Self>> {
2554+
let name = CString::new(name).unwrap();
2555+
User::from_anything(|pwd, cbuf, cap, res| {
2556+
unsafe { libc::getpwnam_r(name.as_ptr(), pwd, cbuf, cap, res) }
2557+
})
2558+
}
2559+
}
2560+
2561+
/// Representation of a Group, based on `libc::group`
2562+
#[derive(Debug, Clone, PartialEq)]
2563+
pub struct Group {
2564+
/// Group name
2565+
pub name: String,
2566+
/// Group ID
2567+
pub gid: Gid,
2568+
/// List of Group members
2569+
pub mem: Vec<String>
2570+
}
2571+
2572+
impl From<&libc::group> for Group {
2573+
fn from(gr: &libc::group) -> Group {
2574+
unsafe {
2575+
Group {
2576+
name: CStr::from_ptr((*gr).gr_name).to_string_lossy().into_owned(),
2577+
gid: Gid::from_raw((*gr).gr_gid),
2578+
mem: Group::members((*gr).gr_mem)
2579+
}
2580+
}
2581+
}
2582+
}
2583+
2584+
impl Group {
2585+
unsafe fn members(mem: *mut *mut c_char) -> Vec<String> {
2586+
let mut ret = Vec::new();
2587+
2588+
for i in 0.. {
2589+
let u = mem.offset(i);
2590+
if (*u).is_null() {
2591+
break;
2592+
} else {
2593+
let s = CStr::from_ptr(*u).to_string_lossy().into_owned();
2594+
ret.push(s);
2595+
}
2596+
}
2597+
2598+
ret
2599+
}
2600+
2601+
fn from_anything<F>(f: F) -> Result<Option<Self>>
2602+
where
2603+
F: Fn(*mut libc::group,
2604+
*mut libc::c_char,
2605+
libc::size_t,
2606+
*mut *mut libc::group) -> libc::c_int
2607+
{
2608+
let buflimit = 16384;
2609+
let bufsize = match sysconf(SysconfVar::GETGR_R_SIZE_MAX) {
2610+
Ok(Some(n)) => n as usize,
2611+
Ok(None) | Err(_) => buflimit as usize,
2612+
};
2613+
2614+
let mut cbuf = Vec::with_capacity(bufsize);
2615+
let mut grp = mem::MaybeUninit::<libc::group>::uninit();
2616+
let mut res = ptr::null_mut();
2617+
2618+
loop {
2619+
let error = f(grp.as_mut_ptr(), cbuf.as_mut_ptr(), cbuf.capacity(), &mut res);
2620+
if error == 0 {
2621+
if res.is_null() {
2622+
return Ok(None);
2623+
} else {
2624+
let grp = unsafe { grp.assume_init() };
2625+
return Ok(Some(Group::from(&grp)));
2626+
}
2627+
} else if Errno::last() == Errno::ERANGE {
2628+
// Trigger the internal buffer resizing logic.
2629+
reserve_double_buffer_size(&mut cbuf, buflimit)?;
2630+
} else {
2631+
return Err(Error::Sys(Errno::last()));
2632+
}
2633+
}
2634+
}
2635+
2636+
/// Get a group by GID.
2637+
///
2638+
/// Internally, this function calls
2639+
/// [getgrgid_r(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html)
2640+
///
2641+
/// # Examples
2642+
///
2643+
// Disable this test on all OS except Linux as root group may not exist.
2644+
#[cfg_attr(not(target_os = "linux"), doc = " ```no_run")]
2645+
#[cfg_attr(target_os = "linux", doc = " ```")]
2646+
/// use nix::unistd::{Gid, Group};
2647+
/// // Returns an Result<Option<Group>>, thus the double unwrap.
2648+
/// let res = Group::from_gid(Gid::from_raw(0)).unwrap().unwrap();
2649+
/// assert!(res.name == "root");
2650+
/// ```
2651+
pub fn from_gid(gid: Gid) -> Result<Option<Self>> {
2652+
Group::from_anything(|grp, cbuf, cap, res| {
2653+
unsafe { libc::getgrgid_r(gid.0, grp, cbuf, cap, res) }
2654+
})
2655+
}
2656+
2657+
/// Get a group by name.
2658+
///
2659+
/// Internally, this function calls
2660+
/// [getgrnam_r(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html)
2661+
///
2662+
/// # Examples
2663+
///
2664+
// Disable this test on all OS except Linux as root group may not exist.
2665+
#[cfg_attr(not(target_os = "linux"), doc = " ```no_run")]
2666+
#[cfg_attr(target_os = "linux", doc = " ```")]
2667+
/// use nix::unistd::Group;
2668+
/// // Returns an Result<Option<Group>>, thus the double unwrap.
2669+
/// let res = Group::from_name("root").unwrap().unwrap();
2670+
/// assert!(res.name == "root");
2671+
/// ```
2672+
pub fn from_name(name: &str) -> Result<Option<Self>> {
2673+
let name = CString::new(name).unwrap();
2674+
Group::from_anything(|grp, cbuf, cap, res| {
2675+
unsafe { libc::getgrnam_r(name.as_ptr(), grp, cbuf, cap, res) }
2676+
})
2677+
}
2678+
}

0 commit comments

Comments
 (0)