Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose getpwuid #1140

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/unistd.rs
Expand Up @@ -7,6 +7,7 @@ use fcntl::FcntlArg::F_SETFD;
use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
uid_t, gid_t, mode_t, PATH_MAX};
use std::{fmt, mem, ptr};
use std::convert::TryFrom;
use std::ffi::{CStr, OsString, OsStr};
use std::os::unix::ffi::{OsStringExt, OsStrExt};
use std::os::unix::io::RawFd;
Expand Down Expand Up @@ -1346,6 +1347,88 @@ pub fn setgid(gid: Gid) -> Result<()> {
Errno::result(res).map(drop)
}

#[derive(Debug, Clone)]
pub struct Passwd {
buf: Box<[u8]>,
inner: libc::passwd,
}

impl Passwd {
pub fn uid(&self) -> Uid {
Uid(self.inner.pw_uid)
}

pub fn gid(&self) -> Gid {
Gid(self.inner.pw_gid)
}

pub fn name(&self) -> Result<&CStr> {
range_check(&self.buf, self.inner.pw_name)
}

pub fn passwd(&self) -> Result<&CStr> {
range_check(&self.buf, self.inner.pw_passwd)
}

pub fn gecos(&self) -> Result<&CStr> {
range_check(&self.buf, self.inner.pw_gecos)
}

pub fn dir(&self) -> Result<&CStr> {
range_check(&self.buf, self.inner.pw_dir)
}

pub fn shell(&self) -> Result<&CStr> {
range_check(&self.buf, self.inner.pw_shell)
}
}

/// Confirm the provided `ptr` falls within `buf` before treating it as a `CStr`.
///
/// Note that this does not check that the end of the string is within `buf`,
/// i.e. `buf` must be null-terminated.
fn range_check(buf: &[u8], ptr: *const c_char) -> Result<&CStr> {
let comp_ptr = ptr as *const u8;
if comp_ptr < buf.as_ptr()
|| buf.len() > std::isize::MAX as usize
|| comp_ptr > unsafe { buf.as_ptr().offset(buf.len() as isize) } {
return Err(Error::Sys(Errno::ERANGE));
}

Ok(unsafe { CStr::from_ptr(ptr) })
}

/// Retrieve the specified user's `passwd` file entry.
///
/// See also [getpwuid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid.html)
pub fn getpwuid(uid: Uid) -> Result<Option<Passwd>> {
let mut inner: libc::passwd = unsafe { mem::zeroed() };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use mem::MaybeUninit instead of mem::zeroed.

let mut result = ptr::null_mut();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result, too, should use MaybeUninit.

let buf_size = sysconf(SysconfVar::GETPW_R_SIZE_MAX)?
.and_then(|v| usize::try_from(v).ok())
.unwrap_or(16_384);
let mut buf = vec![0u8; buf_size].into_boxed_slice();

let res = unsafe {
libc::getpwuid_r(
uid.as_raw(),
&mut inner,
buf.as_mut_ptr() as *mut c_char,
buf_size,
&mut result,
)
};

Errno::result(res)?;

if result.is_null() {
Ok(None)
} else {
debug_assert_eq!(result, (&mut inner) as *mut _);
Ok(Some(Passwd { buf, inner }))
}
}

/// Get the list of supplementary group IDs of the calling process.
///
/// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html)
Expand Down
18 changes: 18 additions & 0 deletions test/test_unistd.rs
Expand Up @@ -473,6 +473,24 @@ cfg_if!{
}
}

#[test]
fn test_passwd() -> nix::Result<()> {
let passwd = getpwuid(getuid())?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test functions should all return ().

.expect("current user has a passwd entry");

assert!(!passwd.name()?
.to_str()
.expect("current user's username is utf-8")
.is_empty());

assert!(passwd.shell()?
.to_str()
.expect("current user's shell is utf-8")
.starts_with('/'));

Ok(())
}

#[test]
fn test_acct() {
use tempfile::NamedTempFile;
Expand Down