Add getpwnam and related functions#864
Conversation
| fn from(pw: libc::passwd) -> Passwd { | ||
| unsafe { | ||
| Passwd { | ||
| name: CString::new(CStr::from_ptr(pw.pw_name).to_bytes()).unwrap(), |
There was a problem hiding this comment.
I wonder if it is smart to use CStrings here (as opposed to Strings).
Does the library use CStrings in any other places?
There was a problem hiding this comment.
Thank you very much for your review. I did have what I believed to be a good reason to use CStrings. If this function is ran on a non UTF8 system, the fields, especially gecos, can be in a non UTF8 encoding such as SHIFTIJIS. from_utf8_lossy will cause data loss. Should String still be used in light of this?
There was a problem hiding this comment.
Does Nix support any operating systems that run in non-UTF8 mode?
There was a problem hiding this comment.
Yes, many. FreeBSD, Linux etc can all use /etc/passwd files that are in the user's non-UTF8 locale. I hate to keep mentioning SHIFT-JIS, but because in my day job I'm dealing with a lot of SJIS data and SJIS systems, I am accutely aware of this problem. Japanese sysadmins, even in 2018, in many cases still prefer SJIS because it is byte for byte around 2/3 to 1/2 the size of UTF-8. So that means an uncompressed database of Japanese text data is double the size if it's UTF-8. Therefore, many many Unix systems in Japan use SJIS everywhere, including the passwd and group files.
| unsafe fn members(mem: *mut *mut c_char) -> Vec<CString> { | ||
| let mut ret = vec![]; | ||
|
|
||
| for i in 0.. { |
There was a problem hiding this comment.
This looks like it has too many moving parts.
There was a problem hiding this comment.
I couldn't think of another way to decode a **char
| pub fn getpwent() -> Result<Option<Passwd>> { | ||
| let pw = unsafe { libc::getpwent().as_ref() }; | ||
|
|
||
| if errno::errno() != 0 { |
There was a problem hiding this comment.
Is it smart to check the errno in this way?
There was a problem hiding this comment.
It works, but I do have to admit that I did not really understand too well the errno.rs module.
There was a problem hiding this comment.
libc::getpwent also uses a static object. It also isn't very Rusty, because it's basically the next method for an implied iterator. I don't think it should be directly exposed by Nix. Instead, I think you should create an iterator class that will internally use libc::getpwent_r to iterate through the password file. Something like this:
struct PwEntries {
...
}
impl PwEntries {
fn new() -> Self {
libc::setpwent();
...
}
}
impl Iterator for PwEntries {
type item = Passwd;
fn next(&mut self) -> Option<Passwd> {
...
}
}
impl Drop for PwEntries {
fn drop(&mut self) {
libc::endpwent();
}
}Unfortunately, this still isn't 100% reentrant since those setpwent and endpwent functions still store their file descriptors in static variables. But I don't see a better option.
There was a problem hiding this comment.
libc::getpwent_r does not exist, so I suppose this is on hold until I can figure out how to get it added to libc...
| #[inline] | ||
| pub fn endpwent() -> Result<()> { | ||
| unsafe { libc::endpwent() }; | ||
| if errno::errno() != 0 { |
| #[derive(Debug, Clone, PartialEq)] | ||
| pub struct Passwd { | ||
| /// username | ||
| pub name: CString, |
There was a problem hiding this comment.
Is it important to mark these all as pub?
There was a problem hiding this comment.
I think so. . . the whole point of these functions is to get data from this struct.
There was a problem hiding this comment.
One thing that other Nix modules do is create a Newtype to wrap the libc struct, and define accessors for that. See for example nix::sys::Kevent or nix::sys::UtsName. I think that pattern would work well here. You'd do something like:
pub struct Passwd(libc::passwd);
impl Passwd {
pub fn name(&self) -> &str {
...
}
}There was a problem hiding this comment.
@asomers That seems very Java-like to me and not necessary for these structs. Furthermore, some day I may also add putpwent, which would require a user to build this struct. putpwent is basically how the shell utilitty adduser works. http://man7.org/linux/man-pages/man3/putpwent.3.html
There was a problem hiding this comment.
Accessor functions may seem unnecessary to you, but converting C's struct passwd to a different layout seems unnecessary to me. As long as we're dealing with C and Rust datatypes that have incompatible layouts, we're going to need some kind of conversion. I like the accessor method version better, because that way we won't waste time converting fields that the user doesn't care about. We'll probably save RAM too.
| /// Gets a user DB entry based on username (see | ||
| /// [getpwnam(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html)) | ||
| pub fn getpwnam<S: Into<Vec<u8>>>(name: S) -> Result<Option<Passwd>> { | ||
| let pw = unsafe { libc::getpwnam(CString::new(name).unwrap().as_ptr()).as_ref() }; |
There was a problem hiding this comment.
The man page says that applications should explicitly set errno to 0 before calling getpwnam.
| /// Gets a user DB entry based on username (see | ||
| /// [getpwnam(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html)) | ||
| pub fn getpwnam<S: Into<Vec<u8>>>(name: S) -> Result<Option<Passwd>> { | ||
| let pw = unsafe { libc::getpwnam(CString::new(name).unwrap().as_ptr()).as_ref() }; |
There was a problem hiding this comment.
libc::getpwnam is not thread safe because it uses a static buffer. In fact, it's worse than that, because the static buffer is malloced on demand, and libc will free it and reallocate it if it isn't large enough. That means that multithreaded programs can use-after-free if they call getpwnam without synchronization. You should use libc::getpwnam_r instead.
| /// [getpwuid(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid.html)) | ||
| #[inline] | ||
| pub fn getpwuid(uid: Uid) -> Result<Option<Passwd>> { | ||
| let pw = unsafe { libc::getpwuid(uid.0).as_ref() }; |
There was a problem hiding this comment.
Same concerns here about thread-safety and initializing errno.
| pub fn getpwent() -> Result<Option<Passwd>> { | ||
| let pw = unsafe { libc::getpwent().as_ref() }; | ||
|
|
||
| if errno::errno() != 0 { |
There was a problem hiding this comment.
libc::getpwent also uses a static object. It also isn't very Rusty, because it's basically the next method for an implied iterator. I don't think it should be directly exposed by Nix. Instead, I think you should create an iterator class that will internally use libc::getpwent_r to iterate through the password file. Something like this:
struct PwEntries {
...
}
impl PwEntries {
fn new() -> Self {
libc::setpwent();
...
}
}
impl Iterator for PwEntries {
type item = Passwd;
fn next(&mut self) -> Option<Passwd> {
...
}
}
impl Drop for PwEntries {
fn drop(&mut self) {
libc::endpwent();
}
}Unfortunately, this still isn't 100% reentrant since those setpwent and endpwent functions still store their file descriptors in static variables. But I don't see a better option.
| /// Gets a group DB entry based on group name (see | ||
| /// [getgrnam(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html)) | ||
| #[inline] | ||
| pub fn getgrnam<S: Into<Vec<u8>>>(name: S) -> Result<Option<Group>> { |
There was a problem hiding this comment.
Ditto all the comments for the getpwent family of functions.
| fn from(pw: libc::passwd) -> Passwd { | ||
| unsafe { | ||
| Passwd { | ||
| name: CString::new(CStr::from_ptr(pw.pw_name).to_bytes()).unwrap(), |
There was a problem hiding this comment.
Does Nix support any operating systems that run in non-UTF8 mode?
| #[derive(Debug, Clone, PartialEq)] | ||
| pub struct Passwd { | ||
| /// username | ||
| pub name: CString, |
There was a problem hiding this comment.
One thing that other Nix modules do is create a Newtype to wrap the libc struct, and define accessors for that. See for example nix::sys::Kevent or nix::sys::UtsName. I think that pattern would work well here. You'd do something like:
pub struct Passwd(libc::passwd);
impl Passwd {
pub fn name(&self) -> &str {
...
}
}Add passwd/group APIs needed for nix-rust/nix#864 Hope I did this right. I only added platforms I could personally test. . . cc: @gnzlbg
|
I was able to get a bunch of functions added to libc in rust-lang/libc#934 . I am now working on getting the APIs I added there into nix again. Hoping no more libc changes are needed. |
|
I don't know why the test system is doing this: Why it immediately compiles old version after new? I got a PR into libc for the express purpose of getting this PR into nix, so using |
|
😟 |
|
|
||
| [dependencies] | ||
| libc = { git = "https://github.com/rust-lang/libc" } | ||
| libc = { version = ">=0.2.38", git = "https://github.com/rust-lang/libc", branch = "master" } |
There was a problem hiding this comment.
This change is unnecessary and can be reverted. Cargo will always try to use the latest version when you use a "git" specification. If the included libc version in your workspace seems behind, then you should run cargo update.
| /// Users::new() | ||
| /// .map( | ||
| /// |pw| println!("{}\t{}", | ||
| /// pw.name.to_string_lossy().into_owned(), |
There was a problem hiding this comment.
Do you really need the into_owned?
| /// .map( | ||
| /// |pw| println!("{}\t{}", | ||
| /// pw.name.to_string_lossy().into_owned(), | ||
| /// format!("{}", pw.uid))) |
There was a problem hiding this comment.
format is basically what the println is doing. I think you can shorten this entire line to pw.uid)
| /// | ||
| /// ``` | ||
| /// use nix::unistd::Users; | ||
| /// eprintln!("Users on this system:\nname\tuid"); |
There was a problem hiding this comment.
When you run cargo test, does all this stuff get printed? If so, you must silence it.
There was a problem hiding this comment.
Even with nocapture, nothing is printed.
Doc-tests nix
running 1 test
test src/unistd.rs - unistd::Users (line 2300) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 47 filtered out
| #[cfg(not(target_os = "android"))] | ||
| pub struct Users(pub usize); | ||
|
|
||
| impl Users { |
There was a problem hiding this comment.
This line seems unlikely to compile on android
There was a problem hiding this comment.
Yup you're right, I thought the compiler was smarter than it is, since the struct doesn't exist when compiling for Android.
| type Item = Passwd; | ||
| fn next(&mut self) -> Option<Passwd> { | ||
|
|
||
| let mut cbuf = vec![0i8; self.0]; |
There was a problem hiding this comment.
0u8 would be more appropriate than 0i8, I think. A 0i8 array can't store any text encodings that use the high bit.
There was a problem hiding this comment.
I can't, type mismatch. libc's type c_char = i8.
| #[derive(Debug, Clone, PartialEq)] | ||
| pub struct Passwd { | ||
| /// username | ||
| pub name: CString, |
There was a problem hiding this comment.
Accessor functions may seem unnecessary to you, but converting C's struct passwd to a different layout seems unnecessary to me. As long as we're dealing with C and Rust datatypes that have incompatible layouts, we're going to need some kind of conversion. I like the accessor method version better, because that way we won't waste time converting fields that the user doesn't care about. We'll probably save RAM too.
| let i = unsafe { libc::getpwent_r(&mut pwd, cbuf.as_mut_ptr(), self.0, &mut res) }; | ||
|
|
||
| match i { | ||
| 0 if !res.is_null() => { |
There was a problem hiding this comment.
You need a way to distinguish between "entry not found" (errno==0) and "not enough buffer space" (errno==ERANGE)
| let mut pwd: libc::passwd = unsafe { mem::zeroed() }; | ||
| let mut res = ptr::null_mut(); | ||
|
|
||
| let i = unsafe { libc::getpwent_r(&mut pwd, cbuf.as_mut_ptr(), self.0, &mut res) }; |
There was a problem hiding this comment.
Don't forget to initialize errno to zero before calling this function.
There was a problem hiding this comment.
To be certain, you mean I have to make a call to Errno::clear, right?
| let mut grp: libc::group = unsafe { mem::zeroed() }; | ||
| let mut res = ptr::null_mut(); | ||
|
|
||
| let i = unsafe { libc::getgrent_r(&mut grp, cbuf.as_mut_ptr(), self.0, &mut res) }; |
There was a problem hiding this comment.
You should probably initialize errno=0 here too.
| 0 if !res.is_null() => { | ||
| unsafe { Some(Group::from(*res)) } | ||
| }, | ||
| _ => None |
|
We already have a way of serializing tests that use global variables. Look in tests/test.rs at the bottom. There are several mutexes there. You should add another one to protect the global variables used by the |
|
Oh okay thanks, I get it, I'll make the tests use that when I get home from church, as well as deal with your other concerns 😃 |
|
Alright, got a bunch of work done today, but as you can see from the failing builds there is still more to do :-) Thank you everyone for your patience with me while I sort this out. Just realized I'm going to have to open another libc PR as my original accidentally neglected FreeBSD. |
|
FreeBSD build will stop failing when rust-lang/libc#939 is merged. |
|
Hey @asomers , really curious what you think of this API for querying instead of just exposing it under the names of the POSIX functions. It's getting late here, if you like it I'll make the Group version of it tomorrow. |
|
P.S. Anybody know what's up with https://travis-ci.org/nix-rust/nix/jobs/349841588 ? I can't get |
|
The query API could certainly work. One problem is that it's not specific enough. Why have enum values that can't be used to look up a user? Why have values that can't be used to look up a group? Better to have one enum for User queries, and one for Group queries. That way you'll catch usage problems at compile time. Another problem is that you aren't handling "user not found" errors correctly. You shouldn't return an Errno in that case. |
|
|
||
| [dependencies] | ||
| libc = { git = "https://github.com/rust-lang/libc" } | ||
| libc = { git = "https://github.com/rust-lang/libc", branch = "master" } |
There was a problem hiding this comment.
The branch specification isn't necessary. Please revert changes to this file.
| #[cfg(any(target_os = "android", target_os = "freebsd", | ||
| target_os = "linux", target_os = "openbsd"))] | ||
| pub use self::setres::*; | ||
| #[cfg(not(any(target_os = "android", |
There was a problem hiding this comment.
Why did you have to exclude so many architectures? Shouldn't this be architecture independent?
There was a problem hiding this comment.
Not really sure -- my builds were failing due to missing functions on those architectures. Could be a libc issue, but it's also not unheard of for GNU libc to not have every function in every platform.
| #[derive(Debug, Clone, PartialEq, Default)] | ||
| pub struct User { | ||
| /// username | ||
| pub name: String, |
There was a problem hiding this comment.
Why make some fields String if others must be CString? The inconsistency is weird.
There was a problem hiding this comment.
Because it's guaranteed that the fields I made String will conform to NAME_REGEX. gecos ("real name") has no such guarantee.
There was a problem hiding this comment.
Ahh, makes sense. Could you add a comment to that effect?
| #[derive(Debug, Clone, PartialEq)] | ||
| pub struct Group { | ||
| /// group name | ||
| pub name: String, |
There was a problem hiding this comment.
Should the concerns about shift-jis force this structure to use CString too?
| } | ||
|
|
||
| #[derive(Debug)] | ||
| /// Query a group. The buffer size variants should not be needed 99% of the time; the default |
There was a problem hiding this comment.
Doc comments, here and elsewhere, should begin with a short description. Then there should be a blank line followed by the detailed description.
| /// | ||
| /// ``` | ||
| /// | ||
| /// This iterator should not be used in different threads without synchronization; while doing so |
There was a problem hiding this comment.
Please put this in a # Safety section.
|
|
||
| /// This iterator can be used to get all of the groups on the system. For example: | ||
| /// | ||
| /// ``` |
There was a problem hiding this comment.
Please put this in an # Examples section.
| /// | ||
| /// ``` | ||
| /// | ||
| /// This iterator should not be used in different threads without synchronization; while doing so |
| #[test] | ||
| fn test_getpwnam() { | ||
| let res = User::query( UserQuery::Name("root".to_string()) ).unwrap(); | ||
| assert!(res.unwrap().uid == Uid::from_raw(0)); |
There was a problem hiding this comment.
This isn't a very good test. That field would've been zero even if getpwnam did nothing, because you initialize the struct to zero. Can you find a more interesting field to check?
There was a problem hiding this comment.
I transformed this into a doctest. About to push it.
| target_arch = "s390x")))] | ||
| fn test_users_iterator() { | ||
| #[allow(unused_variables)] | ||
| let m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test"); |
There was a problem hiding this comment.
Change the name to _m and you won't need the #[allow(unused_variables)]
There was a problem hiding this comment.
I copied this pattern from elsewhere in the file. I think that the reason that other tests do it this way is because maybe the compiler will optimize it out, but I did not test.
There was a problem hiding this comment.
I'm pretty sure the reason that other tests do it this way is because they were added back when the minimum compiler version was 1.14.0 or something, and it didn't understand that leading underscores denoted unused variables.
There was a problem hiding this comment.
I think that would be best left to another PR, since I'd be editing many other unrelated tests, which I'd be happy to work on after this one.
There was a problem hiding this comment.
Let's add the underscore and get rid of the #[allow( as part of this PR. You don't need to clean up existing examples, but since this is new code it should do this the right way from the start.
|
@asomers Any further concerns? |
| /// should be more than adequate. Only use the buffer size variants if you receive an ERANGE error | ||
| /// without them, or you know that you will be likely to. | ||
| /// | ||
| /// # Example |
There was a problem hiding this comment.
s/Example/Examples for consistency with the rest of the project
There was a problem hiding this comment.
@asomers What consistency? mkfifo, getcwd etc use "Example"...this makes sense because it's a lone example, in their case as well as mine.
There was a problem hiding this comment.
I'm not saying we're perfect, but currently Nix has 30 "Examples" compared to 9 "Example". Best stick to the predominant style.
|
There are still two things that concern me:
|
|
Why did you use raw pointers instead of references? Dereferencing raw pointers is |
|
@asomers Because I only ever convert |
|
Yeah, but you moved the raw pointer dereference into part of the public API! And in a safe function, no less! That means that any user can pass us a |
|
@asomers OK, I understand. Thank you very much for the explanation. I'm sorry if I'm annoying you. This is my first major commit to a Rust library. I'll think about how to handle this. Maybe using the |
Susurrus
left a comment
There was a problem hiding this comment.
A lot of minor style changes.
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] |
There was a problem hiding this comment.
These should always come after the doccomments.
Also is there a reason you didn't derive the other commonly-derived traits as well?
There was a problem hiding this comment.
moved below the doc-comments, and added the full set of Debug, Copy, Clone, Eq, PartialEq, Hash, Default, for UserQuery, GroupQuery, Users, Groups (where applicable, not all can be Copy, enums can't be Default)
| let mut pwd: libc::passwd = unsafe { mem::zeroed() }; | ||
| let mut res = ptr::null_mut(); | ||
|
|
||
| let i = { |
There was a problem hiding this comment.
Can you use a more descriptive variable name here? i is almost always a counter variable as well, so this is confusing.
There was a problem hiding this comment.
changed i to error for all four occurences
| }; | ||
|
|
||
| match i { | ||
| 0 => { |
There was a problem hiding this comment.
If you're just doing an if/else like this I'd recommend doing that instead of a match, which is a little more heavyweight.
| target_arch = "s390x")))] | ||
| mod usergroupiter { | ||
| use libc; | ||
| use libc::{c_char}; |
There was a problem hiding this comment.
You can just do use libc::{self, c_char};
| /// `setpwent` and `endpwent`, it is very likely that iterators running in different threads will | ||
| /// yield different numbers of items. | ||
| #[derive(Debug)] | ||
| pub struct Users(pub usize); |
There was a problem hiding this comment.
Is there any reason the user of this API needs to set the internal value? If not, you should make it private and add a getter to get the value instead. This is more common with Rust types and used elsewhere in nix.
| /// Representation of a group, based on `libc::group` | ||
| #[derive(Debug, Clone, PartialEq)] | ||
| pub struct Group { | ||
| /// group name |
There was a problem hiding this comment.
The first letters in all these doccomments should be capitalized.
| gid: Gid::from_raw((*pw).pw_gid), | ||
| #[cfg(not(any(target_os = "linux", target_os = "android")))] | ||
| class: CString::new(CStr::from_ptr((*pw).pw_class).to_bytes()).unwrap(), | ||
| #[cfg(not(any(target_os = "linux", target_os = "android")))] |
There was a problem hiding this comment.
The OSes need to be alphabetized. Additionally, these sould not use a negation and instead explicitly list all OSes.
There was a problem hiding this comment.
alphabetical order done; avoiding negations seems quite verbose to me, and cfg(not( seems to occur more througout the existing part as well.
| } | ||
|
|
||
|
|
||
| #[derive(Debug, Clone, PartialEq, Default)] |
There was a problem hiding this comment.
Attributes should got under the doccomments.
| /// group ID | ||
| pub gid: Gid, | ||
| #[cfg(not(target_os = "android"))] | ||
| /// user information |
There was a problem hiding this comment.
Doccomments go above the attributes.
| } | ||
|
|
||
| pub trait Queryable<Q> where Self: ::std::marker::Sized { | ||
| fn query(q: Q) -> Option<Result<Self>>; |
There was a problem hiding this comment.
This probably should be public, yes?
Additionally, what's with the weird Option<Result<_>> type?
There was a problem hiding this comment.
The Option<Result<_>> type makes sense in this case due to the lower level API.
If you receive Some(Ok(_)), it means that there was a user found and its data fit in the buffer and is now wrapped in the result.
If you receive a Some(Err(_)), it means that there was a user found but its data did not fit in the buffer, and you should recall with one of the WithBufsize variants with a higher size.
If you receive a None, it means that no user was found.
There was a problem hiding this comment.
I'm wondering if we shouldn't use our own return type to specify these different states. I haven't thought hard about this, so maybe one doesn't exist, but glancing at this I'd think we could have a more ergonomic API here.
There was a problem hiding this comment.
With all due respect, I'm not sure what that would look like and don't think there's anything wrong with the current return type...maybe an alternate return type would look better, but using standard types like this is certainly more ergonomic IMO.
There was a problem hiding this comment.
I think a better API would be for the caller to supply an optional bufsize. If not provided, then Nix will choose a bufsize, retrying with a larger buffer if necessary. Also, why is there a need for the Queryable trait? I get that User and Group have a similar API. But if there's never going to be any generic function with an indiscriminate Queryable type in its signature, then there's no need to define a trait. I might do it like this:
pub enum GroupQuery<'a> {
Gid(gid),
Name(&'a str)
}
impl Group {
fn query(q: GroupQuery, bufsize: Option<usize>) -> Option<Result<Self>>;
}
let res = Group::query(GroupQuery::Name("root"), Some(2048)).unwrap();However, it would be even simpler to eliminate the GroupQuery type entirely, replacing it with two separate constructors:
impl Group {
fn from_gid(gid: Gid, bufsize: Option<usize>) -> Option<Result<Self>>;
fn from_name(name: &str, bufsize: Option<usize>) -> Option<Result<Self>>;
}
let res = Group::from_name("root", Some(2048)).unwrap();What @Susurrus was suggesting would be to create a bespoke return type to get rid of the double unwrap wart. It would look like this:
enum GroupResult {
/// No group found
None,
/// An error was encountered while searching
Err(Error),
Some(Group)
}
Such a type could still support most of the common operators that are defined for std::option::Option and std::result::Result, i.e. unwrap, is_some, is_err, is_none, etc.
|
OK, sorry everyone, the priorities of my company changed so I hadn't had much time to work on this, but the microservice I wrote which uses this API is back under scrutiny again so I'll be soon working on getting this commit into nix since we do not want to use private branches of modules if we can help it for important parts of our code. |
Done. |
I think this should be handled in another PR as seems not related. |
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
asomers
left a comment
There was a problem hiding this comment.
That looks like a step in the right direction. It certainly reduced duplication.
| // Trigger the internal buffer resizing logic of `Vec` by requiring | ||
| // more space than the current capacity. | ||
| unsafe { cbuf.set_len(cbuf.capacity()); } | ||
| cbuf.reserve(1); |
There was a problem hiding this comment.
Extending by only one byte at a time could take awhile. It would be better to double the storage instead. That's what the C library does. Also, you shouldn't extend the buffer ad infinitum. At some point you should give up and return ERANGE. The C library does that at 1MB.
There was a problem hiding this comment.
I think we can add a top limit, however, the same strategy is used inside std. For example: https://doc.rust-lang.org/src/alloc/vec.rs.html#949
Also, inside nix code it is also used. See: https://github.com/nix-rust/nix/blob/master/src/unistd.rs#L579-L583
There was a problem hiding this comment.
The link from inside of Vec is very different. In that case, it really is only 1 additional element's worth of space that is needed. Here, an unknown amount of additional space is needed. That's why I suggest doubling the storage. Nix's getcwd() method should probably do the same.
There was a problem hiding this comment.
The link from inside of Vec is very different. In that case, it really is only 1 additional element's worth of space that is needed. Here, an unknown amount of additional space is needed. That's why I suggest doubling the storage. Nix's getcwd() method should probably do the same.
From reading the std code, it seems different. See:
and
So it seems todo exactly this; it seems good to keep the code simple but if really desired, I can rework this.
There was a problem hiding this comment.
That's just the current implementation. Nix shouldn't rely upon it. We should actually try to reserve a reasonable amount of space for what our application needs.
| } | ||
|
|
||
| impl User { | ||
| fn from_anything(f: impl Fn(*mut libc::passwd, |
There was a problem hiding this comment.
This signature is so big that it would probably be written better with a where clause:
fn from_anything<F>(f: F) -> Result<Option<Self>>
where
F: Fn(
*mut libc::passwd,
*mut libc::c_char,
libc::size_t,
*mut *mut libc::passwd,
) -> libc::c_intSigned-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
| return Err(Error::Sys(Errno::ERANGE)) | ||
| } | ||
|
|
||
| unsafe { buf.set_len(buf.capacity()) }; |
There was a problem hiding this comment.
No, absolutely not. This is very unsafe and totally unnecessary. Did you mean to commit this line?
| fn reserve_buffer_size<T>(buf: &mut Vec<T>, limit: usize) -> Result<()> { | ||
| use std::cmp::min; | ||
|
|
||
| if buf.len() == limit { |
| unsafe { buf.set_len(buf.capacity()) }; | ||
|
|
||
| let capacity = min(buf.capacity() * 2, limit); | ||
| buf.reserve_exact(capacity); |
There was a problem hiding this comment.
No need to override the standard library. You can use reserve instead of reserve_exact. I just don't want to rely on reserve(1) actually reserving more space than 1.
| let bufsize = match sysconf(SysconfVar::GETPW_R_SIZE_MAX) { | ||
| let cbuf_max = match sysconf(SysconfVar::GETPW_R_SIZE_MAX) { | ||
| Ok(Some(n)) => n as usize, | ||
| Ok(None) | Err(_) => 1024 as usize, |
There was a problem hiding this comment.
Not that it's defined in any publicly visible place, but FreeBSD internally sets this limit to 1MB.
| }; | ||
|
|
||
| let mut cbuf = Vec::with_capacity(bufsize); | ||
| let mut cbuf = Vec::with_capacity(512); |
There was a problem hiding this comment.
You were right the first time. GETPW_R_SIZE_MAX is the suggested initial size of the buffer for password entries, not the maximum size (earlier versions of POSIX did specify the maximum size).
| }; | ||
|
|
||
| let mut cbuf = Vec::with_capacity(bufsize); | ||
| let mut cbuf = Vec::with_capacity(512); |
There was a problem hiding this comment.
As above, bufsize should be the intial sizes of the vec, not the maximum.
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
| } else if Errno::last() == Errno::ERANGE { | ||
| // Trigger the internal buffer resizing logic. | ||
| reserve_buffer_size(&mut cbuf, cbuf_max)?; | ||
| reserve_double_buffer_size(&mut cbuf, bufsize)?; |
There was a problem hiding this comment.
The sets the initial and maximum sizes to the same value. You should probably choose a larger maximum size. As I said, FreeBSD internally uses 1MB.
There was a problem hiding this comment.
And how we can inquiry the OS to get this information? Blindly put a number does not sounds right.
There was a problem hiding this comment.
You can't. There's no way to query it. And there's no need to, for consumers of the C API. It's irrelevant for getpwnam_r since the user provides his own buffer, and the user doesn't need to know it when using getpwnam. But if we want to provide a Rusty API that is both reentrant and allocates the buffer internally, then we need to choose a sane limit ourselves. Do you have a better idea?
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
asomers
left a comment
There was a problem hiding this comment.
I'm curious. Why 16KB for the pwent limit?
| // groups has changed since the first call to getgroups() and the buffer is | ||
| // now too small. | ||
| let mut groups = Vec::<Gid>::with_capacity(Errno::result(ret)? as usize); | ||
| let mut groups = Vec::<Gid>::with_capacity(Errno::result(ngroups)? as usize); |
There was a problem hiding this comment.
I think the code would be more readable if you moved Errno::result(...)? closer to the call site on line 1339.
There was a problem hiding this comment.
Reworked. Also, the 16Kb was taken from https://linux.die.net/man/3/getpwuid_r example code.
Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
| @@ -1337,15 +1337,18 @@ pub fn setgid(gid: Gid) -> Result<()> { | |||
| pub fn getgroups() -> Result<Vec<Gid>> { | |||
| // First get the number of groups so we can size our Vec | |||
| let ngroups = unsafe { libc::getgroups(0, ptr::null_mut()) }; | |||
There was a problem hiding this comment.
What I meant is that it would be more idiomatic to write it like this:
let ngroups = unsafe { libc::getgroups(0, ptr::null_mut()) }? as usize;1139: Add `Users` and `Group` related functions r=asomers a=otavio This was a collaborative work between Johannes Schilling <dario@deaktualisierung.org>, Fredrick Brennan <copypaste@kittens.ph> 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 <otavio@ossystems.com.br> Co-authored-by: Otavio Salvador <otavio@ossystems.com.br>
|
I'm closing this because I'm tired of seeing it in my PR list. I won't delete the underlying branch, please just open a new PR if you will try to revive whatever wasn't included in #1139. Should you care to know why, I've written a small blog about it: https://gist.github.com/ctrlcctrlv/978b3ee4f55d4b4ec415a985e01cb1c9 |
We have one UNIX group that contains most of our users whose size is about 20 kB, so `Group::from_name` is failing with ERANGE. The discussion on PR nix-rust#864 suggests that 1 MB is a reasonable maximum - it follows what FreeBSD's libc does. (glibc appears to have no maximum on the _r function and will just double the buffer until malloc fails, but that's not particularly Rusty.)
We have one UNIX group that contains most of our users whose size is about 20 kB, so `Group::from_name` is failing with ERANGE. The discussion on PR nix-rust#864 suggests that 1 MB is a reasonable maximum - it follows what FreeBSD's libc does. (glibc appears to have no maximum on the _r function and will just double the buffer until malloc fails, but that's not particularly Rusty.)
1409: unistd: Increase maximum passwd/group buffer to 1MB r=asomers a=geofft We have one UNIX group that contains most of our users whose size is about 20 kB, so `Group::from_name` is failing with ERANGE. The discussion on PR #864 suggests that 1 MB is a reasonable maximum - it follows what FreeBSD's libc does. (glibc appears to have no maximum on the _r function and will just double the buffer until malloc fails, but that's not particularly Rusty.) Co-authored-by: Geoffrey Thomas <geofft@twosigma.com>
This PR closes #862.
Implemented functions:
User::query:Group::query:Usersiterator:Groupsiterator:I wanted to implementgetgrent,setgrentandendgrent, but they are missing fromlibc. (See rust-lang/libc#923 )