-
Notifications
You must be signed in to change notification settings - Fork 673
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
unistd: Add getgroups, setgroups, getgrouplist, initgroups #733
Conversation
src/unistd.rs
Outdated
@@ -942,6 +942,42 @@ pub fn setgid(gid: Gid) -> Result<()> { | |||
} | |||
|
|||
#[inline] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove #[inline]
for both functions. Also need documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, was just copying without knowing what I was doing 😅. Docs added.
src/unistd.rs
Outdated
@@ -942,6 +942,42 @@ pub fn setgid(gid: Gid) -> Result<()> { | |||
} | |||
|
|||
#[inline] | |||
pub fn setgroups(groups: &[Gid]) -> Result<()> { | |||
cfg_if! { | |||
if #[cfg(any(target_os = "macos", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alphabetize the target_os listing. This should be done everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about tests? Perhaps you could add a test that round trips a gidset from setgroups
through getgroups
?
src/unistd.rs
Outdated
type setgroups_ngroups_t = size_t; | ||
} | ||
} | ||
let gids: Vec<gid_t> = groups.iter().cloned().map(Into::into).collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it does a data copy. Can you eliminate it? sys::aio::lio_listio
does something similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty new to Rust and had no idea you could do this. Not entirely comfortable doing so before there are tests, though.
src/unistd.rs
Outdated
Errno::result(res).map(drop) | ||
} | ||
|
||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FreeBSD also has initgroups
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but it's unfortunately not implemented in rust-lang/libc
for FreeBSD yet.
src/unistd.rs
Outdated
// from a group between the two calls and we don't want to incorrectly set the length of | ||
// the Vec and expose uninitialized memory. | ||
unsafe { groups.set_len(s as usize) }; | ||
groups.iter().cloned().map(|gid| Gid::from_raw(gid)).collect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid the data copy here? You may have to mem::transmute
gid_t
s into Gid
s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can sure give it a go. Like I said with the other case, I'd like to have tests first, if possible.
@asomers I don't know much about the test infrastructure, but I've added a |
Thank you both for the reviews 😄 |
Rust's unit test framework lacks a skip method. So you should do a UID check. If non-zero, simply return 0. |
src/unistd.rs
Outdated
/// of. The additional group `group` is also added to the list. | ||
/// | ||
/// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html) | ||
// TODO: Get initgroups binding added for FreeBSD in libc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have this done before we merge this PR. Can you file a PR against libc adding the necessary FFI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created rust-lang/libc#726. Also added getgrouplist()
as it'd help for testing initgroups()
, at least on some platforms.
src/unistd.rs
Outdated
/// platforms. It returns the current group access list for the user associated | ||
/// with the effective user id of the process; the group access list may change | ||
/// over the lifetime of the process, and it is not affected by calls to | ||
/// `setgroups()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These notes are borrowed from the Python documentation: https://docs.python.org/3.6/library/os.html#os.getgroups
I can also implement |
I'm find with adding |
I think we can also squash this into a single commit, or a commit per function rather than the 14 it currently stands at. |
Ok(None) | Err(_) => <c_int>::max_value(), | ||
}; | ||
use std::cmp::min; | ||
let mut ngroups = min(ngroups_max, 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8 is pretty arbitrarily chosen. NGROUPS_MAX
can vary quite a bit:
- macOS: 16
- Debian (Linux/GNU): 65536
- Alpine (Linux/musl): 32
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of capping the number of groups?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand? I'm choosing 8 as a good size to start the groups
buffer at. If groups
is bigger than NGROUPS_MAX
then the getgrouplist()
call will error on most platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, re-read the docs and it makes sense now.
I've been on leave before this week so I have less time now. My git rebasing skills are... a little rusty. Not sure when I'll get to cleaning up these commits but may only be on the weekend. The code is all here for review for now at least. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd also be nice if there were some examples for using these functions? There are some examples in the man pages that might be worth copying. That being said I'm wondering if there isn't a better abstraction we can offer over a bare wrapper around the base functions? For example, I'm unclear when init_groups
is supposed to be called, but if we can enforce that it's called at the right time through some higher level abstraction, I think that'd be good.
src/unistd.rs
Outdated
Errno::result(res).map(drop) | ||
} | ||
|
||
/// Calculate the supplementary group access list. Gets the group IDs of all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the 2nd and third sentence a new paragraph.
src/unistd.rs
Outdated
/// groups that `user` is a member of. The additional group `group` is also | ||
/// added to the list. | ||
/// | ||
/// *Note:* Although the `getgrouplist()` call does not return any specific |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this under an # Errors
section (see the book)
src/unistd.rs
Outdated
/// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation | ||
/// will only ever return the complete list or else an error. | ||
/// | ||
/// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this above the errors section.
Ok(None) | Err(_) => <c_int>::max_value(), | ||
}; | ||
use std::cmp::min; | ||
let mut ngroups = min(ngroups_max, 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of capping the number of groups?
src/unistd.rs
Outdated
// Returns -1 if ngroups is too small, but does not set errno. | ||
// BSD systems will still fill the groups buffer with as many groups | ||
// as possible, but Linux manpages do not mention this behavior. | ||
if ret == -1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an else if
to be more readable.
src/unistd.rs
Outdated
let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; | ||
let mut size = try!(Errno::result(ret)); | ||
|
||
// Now actually get the groups |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment should be expanded to explain why we need to loop here.
src/unistd.rs
Outdated
Err(e) => return Err(e) | ||
} | ||
} | ||
Ok(groups) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be replaced with an unreachable!
.
src/unistd.rs
Outdated
libc::setgroups(groups.len() as setgroups_ngroups_t, groups.as_ptr() as *const gid_t) | ||
}; | ||
|
||
Errno::result(res).map(drop) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map(|_| ())
as well.
src/unistd.rs
Outdated
} | ||
} | ||
// We can coerce a pointer to some `Gid`s as a pointer to some `gid_t`s as | ||
// they have the same representation in memory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, not actually true.
test/test_unistd.rs
Outdated
@@ -104,6 +104,57 @@ mod linux_android { | |||
} | |||
} | |||
|
|||
#[test] | |||
#[cfg(not(any(target_os = "ios", target_os = "macos")))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't these run on these targets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the getgroups()
and setgroups()
calls don't work as expected on Apple platforms, as discussed in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please leave that as a comment in the code here for both this and the following test.
@Susurrus I guess the usual use for Hypothetically speaking, something like: let user = "daemon";
let passwd = Passwd::for_name(&user);
initgroups(passwd.name, passwd.gid)?;
setgid(passwd.gid)?;
setuid(passwd.uid)?; Guess I can try figure out an example without the passwd stuff though. Like, I said not a lot of free time right now but should get there... |
That looks to be a reasonable example then, let's add it as one. |
src/unistd.rs
Outdated
/// Set the list of supplementary group IDs for the calling process. | ||
/// | ||
/// *Note:* On macOS, `getgroups()` may not return the same group list set by | ||
/// calling `setgroups()`. Apple discourages the use of `setgroups()`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does Apple suggest one use instead? It's frustrating when this is stated and then there's no suggest alternative.
assert_eq!(new_groups, groups); | ||
|
||
// Revert back to the old groups | ||
setgroups(&old_groups).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this have one last assertion following this to check that they were properly reverted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getgroups()
and setgroups()
calls at the start and end of these tests aren't really part of the tests, they're just a small attempt to restore the state of the current process to what it was before the test ran. 😕
test/test_unistd.rs
Outdated
#[cfg(not(any(target_os = "ios", target_os = "macos")))] | ||
fn test_initgroups() { | ||
if !Uid::current().is_root() { | ||
// initgroups(), setgroups() require root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this comment above the if
and rephrase to something like "Skip this test when not run as root
as initgroups()
and setgroups()
both require root."
assert_eq!(new_groups, group_list); | ||
|
||
// Revert back to the old groups | ||
setgroups(&old_groups).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, shouldn't there be one last assertion following this?
test/test_unistd.rs
Outdated
@@ -104,6 +104,57 @@ mod linux_android { | |||
} | |||
} | |||
|
|||
#[test] | |||
#[cfg(not(any(target_os = "ios", target_os = "macos")))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please leave that as a comment in the code here for both this and the following test.
Alright, can you squash everything into a single commit and add a CHANGELOG entry for this? @asomers Can you approve your review if everything's been addressed? Once that's all done we should be good to merge! |
@Susurrus I've rebased to fewer, clearer commits. If you want just one commit then GitHub can do that when we merge? Thanks for the reviewing 😄 |
The number of commits are fine now, you didn't need to segregate them all, but it's fine. You have failures in CI that need resolving before we can merge. Also, @asomers can you resolve your review here? I want to make sure everything's fine with you (please click the "Approve review" or whatever link down towards the bottom of the page). |
Looks like rust-lang/libc#742 broke |
test/test_unistd.rs
Outdated
fn test_setgroups() { | ||
// Skip this test when not run as root as `setgroups()` requires root. | ||
if !Uid::current().is_root() { | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Until Cargo adds a way to skip tests, you should print something here to let the user know that the test was skipped. Otherwise it will rot, because people won't realize that the test isn't run. See tests/test_mount.rs. Ditto for line 137.
#[test] | ||
// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms | ||
#[cfg(not(any(target_os = "ios", target_os = "macos")))] | ||
fn test_setgroups() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two tests race against each other. You must add a mutex to prevent that. grep for CWD_MTX
for an example.
I have a few comments I made a while ago that didn't get a response and should get one before I'm okay with this PR. I don't have any other comments after those are all resolved. |
@JayH5 Pinging you on this as we'd love to get it merged. |
5b4f9e3
to
d6f42a3
Compare
@Susurrus sorry for the slow reply, I was on holiday. I think I've responded to all your comments now, except:
I'm honestly not sure what to do about this. I tried adding a test for Apple platforms based on the following note in the
But I don't actually see that behaviour on my machine running High Sierra ( So, honestly, no, I don't know enough about the way these calls work on Apple platforms to write tests for them. I could experiment and test what the behaviour is but I'm not sure I have the time/patience to do that. Perhaps these functions could just be disabled on Apple platforms? |
src/unistd.rs
Outdated
// resize of the groups Vec and try again... | ||
let cap = groups.capacity(); | ||
unsafe { groups.set_len(cap) }; | ||
groups.reserve(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, a comment specifying exactly that would be awesome.
And I agree with your final assessment above, that this should be disabled on Apple platforms since it's unclear what the expected behavior should be. Let's just make it clear in the documentation why this is disabled on Apple targets. |
Fix groups mutex name
Don't need to call as_c_str()
@Susurrus I've tried to do this now. Not sure if these functions should go inside a submodule, like with |
@JayH5 There's no need to put them in a submodule (I'd actually suggest we take those functions out of a submodule). But this LGTM. Thanks for your PR! bors r+ |
No description provided.