Skip to content

Commit 687b0e1

Browse files
authored
Replace users with nix crate (nushell#9610)
# Description The `users` crate hasn't been updated for a long time, this PR tries to replace `users` with `nix`. See [advisory page](https://rustsec.org/advisories/RUSTSEC-2023-0040.html) for additional details.
1 parent 881c349 commit 687b0e1

File tree

5 files changed

+101
-81
lines changed

5 files changed

+101
-81
lines changed

Cargo.lock

Lines changed: 2 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/nu-command/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ winreg = "0.50"
104104
[target.'cfg(unix)'.dependencies]
105105
libc = "0.2"
106106
umask = "2.1"
107-
users = "0.11"
107+
nix = { version = "0.26", default-features = false, features = ["user"] }
108108

109109
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
110110
optional = true

crates/nu-command/src/filesystem/cd.rs

Lines changed: 6 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
220220

221221
#[cfg(unix)]
222222
fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
223+
use crate::filesystem::util::users;
224+
223225
match dir.as_ref().metadata() {
224226
Ok(metadata) => {
225227
use std::os::unix::fs::MetadataExt;
@@ -272,75 +274,13 @@ fn have_permission(dir: impl AsRef<Path>) -> PermissionResult<'static> {
272274
}
273275
}
274276

275-
// NOTE: it's a re-export of https://github.com/ogham/rust-users crate
276-
// we need to use it because the upstream pr isn't merged: https://github.com/ogham/rust-users/pull/45
277-
// once it's merged, we can remove this module
278-
#[cfg(unix)]
279-
mod nu_users {
280-
use libc::c_int;
281-
use std::ffi::{CString, OsStr};
282-
use std::os::unix::ffi::OsStrExt;
283-
use users::{gid_t, Group};
284-
/// Returns groups for a provided user name and primary group id.
285-
///
286-
/// # libc functions used
287-
///
288-
/// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html)
289-
///
290-
/// # Examples
291-
///
292-
/// ```no_run
293-
/// use users::get_user_groups;
294-
///
295-
/// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") {
296-
/// println!("User is a member of group #{} ({:?})", group.gid(), group.name());
297-
/// }
298-
/// ```
299-
pub fn get_user_groups<S: AsRef<OsStr> + ?Sized>(
300-
username: &S,
301-
gid: gid_t,
302-
) -> Option<Vec<Group>> {
303-
// MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
304-
#[cfg(all(unix, target_os = "macos"))]
305-
let mut buff: Vec<i32> = vec![0; 1024];
306-
#[cfg(all(unix, not(target_os = "macos")))]
307-
let mut buff: Vec<gid_t> = vec![0; 1024];
308-
let name = CString::new(username.as_ref().as_bytes()).expect("OsStr is guaranteed to be zero-free, which is the condition for CString::new to succeed");
309-
let mut count = buff.len() as c_int;
310-
// MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
311-
// SAFETY:
312-
// int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
313-
//
314-
// `name` is valid CStr to be `const char*` for `user`
315-
// every valid value will be accepted for `group`
316-
// The capacity for `*groups` is passed in as `*ngroups` which is the buffer max length/capacity (as we initialize with 0)
317-
// Following reads from `*groups`/`buff` will only happen after `buff.truncate(*ngroups)`
318-
#[cfg(all(unix, target_os = "macos"))]
319-
let res =
320-
unsafe { libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count) };
321-
#[cfg(all(unix, not(target_os = "macos")))]
322-
let res = unsafe { libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count) };
323-
if res < 0 {
324-
None
325-
} else {
326-
buff.truncate(count as usize);
327-
buff.sort_unstable();
328-
buff.dedup();
329-
// allow trivial cast: on macos i is i32, on linux it's already gid_t
330-
#[allow(trivial_numeric_casts)]
331-
buff.into_iter()
332-
.filter_map(|i| users::get_group_by_gid(i as gid_t))
333-
.collect::<Vec<_>>()
334-
.into()
335-
}
336-
}
337-
}
338-
339277
#[cfg(unix)]
340278
fn any_group(current_user_gid: gid_t, owner_group: u32) -> bool {
279+
use crate::filesystem::util::users;
280+
341281
users::get_current_username()
342-
.map(|name| nu_users::get_user_groups(&name, current_user_gid).unwrap_or_default())
282+
.and_then(|name| users::get_user_groups(&name, current_user_gid))
343283
.unwrap_or_default()
344284
.into_iter()
345-
.any(|group| group.gid() == owner_group)
285+
.any(|gid| gid.as_raw() == owner_group)
346286
}

crates/nu-command/src/filesystem/ls.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ pub(crate) fn dir_entry_dict(
484484

485485
#[cfg(unix)]
486486
{
487+
use crate::filesystem::util::users;
487488
use std::os::unix::fs::MetadataExt;
488489
let mode = md.permissions().mode();
489490
cols.push("mode".into());
@@ -509,7 +510,7 @@ pub(crate) fn dir_entry_dict(
509510
cols.push("user".into());
510511
if let Some(user) = users::get_user_by_uid(md.uid()) {
511512
vals.push(Value::String {
512-
val: user.name().to_string_lossy().into(),
513+
val: user.name,
513514
span,
514515
});
515516
} else {
@@ -522,7 +523,7 @@ pub(crate) fn dir_entry_dict(
522523
cols.push("group".into());
523524
if let Some(group) = users::get_group_by_gid(md.gid()) {
524525
vals.push(Value::String {
525-
val: group.name().to_string_lossy().into(),
526+
val: group.name,
526527
span,
527528
});
528529
} else {

crates/nu-command/src/filesystem/util.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,92 @@ pub fn is_older(src: &Path, dst: &Path) -> bool {
160160
src_ctime <= dst_ctime
161161
}
162162
}
163+
164+
#[cfg(unix)]
165+
pub mod users {
166+
use libc::{c_int, gid_t, uid_t};
167+
use nix::unistd::{Gid, Group, Uid, User};
168+
use std::ffi::CString;
169+
170+
pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
171+
User::from_uid(Uid::from_raw(uid)).ok().flatten()
172+
}
173+
174+
pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
175+
Group::from_gid(Gid::from_raw(gid)).ok().flatten()
176+
}
177+
178+
pub fn get_current_uid() -> uid_t {
179+
Uid::current().as_raw()
180+
}
181+
182+
pub fn get_current_gid() -> gid_t {
183+
Gid::current().as_raw()
184+
}
185+
186+
pub fn get_current_username() -> Option<String> {
187+
User::from_uid(Uid::current())
188+
.ok()
189+
.flatten()
190+
.map(|user| user.name)
191+
}
192+
193+
/// Returns groups for a provided user name and primary group id.
194+
///
195+
/// # libc functions used
196+
///
197+
/// - [`getgrouplist`](https://docs.rs/libc/*/libc/fn.getgrouplist.html)
198+
///
199+
/// # Examples
200+
///
201+
/// ```ignore
202+
/// use users::get_user_groups;
203+
///
204+
/// for group in get_user_groups("stevedore", 1001).expect("Error looking up groups") {
205+
/// println!("User is a member of group #{group}");
206+
/// }
207+
/// ```
208+
pub fn get_user_groups(username: &str, gid: gid_t) -> Option<Vec<Gid>> {
209+
// MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
210+
#[cfg(target_os = "macos")]
211+
let mut buff: Vec<i32> = vec![0; 1024];
212+
#[cfg(not(target_os = "macos"))]
213+
let mut buff: Vec<gid_t> = vec![0; 1024];
214+
215+
let Ok(name) = CString::new(username.as_bytes()) else {
216+
return None;
217+
};
218+
219+
let mut count = buff.len() as c_int;
220+
221+
// MacOS uses i32 instead of gid_t in getgrouplist for unknown reasons
222+
// SAFETY:
223+
// int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
224+
//
225+
// `name` is valid CStr to be `const char*` for `user`
226+
// every valid value will be accepted for `group`
227+
// The capacity for `*groups` is passed in as `*ngroups` which is the buffer max length/capacity (as we initialize with 0)
228+
// Following reads from `*groups`/`buff` will only happen after `buff.truncate(*ngroups)`
229+
#[cfg(target_os = "macos")]
230+
let res =
231+
unsafe { libc::getgrouplist(name.as_ptr(), gid as i32, buff.as_mut_ptr(), &mut count) };
232+
233+
#[cfg(not(target_os = "macos"))]
234+
let res = unsafe { libc::getgrouplist(name.as_ptr(), gid, buff.as_mut_ptr(), &mut count) };
235+
236+
if res < 0 {
237+
None
238+
} else {
239+
buff.truncate(count as usize);
240+
buff.sort_unstable();
241+
buff.dedup();
242+
// allow trivial cast: on macos i is i32, on linux it's already gid_t
243+
#[allow(trivial_numeric_casts)]
244+
buff.into_iter()
245+
.filter_map(|i| get_group_by_gid(i as gid_t))
246+
.map(|group| group.gid)
247+
.collect::<Vec<_>>()
248+
.into()
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)