Open straight-shoota opened 5 years ago
:+1: on generally mirroring the Golang APIs for users and groups.
One potential pain point: it looks like Go uses the nonstandard getgrouplist
call to retrieve a user's associated groups. All of our (nix-y) targets should have some version of it, but we'll need to confirm that they all behave semi-consistently (or special-case the ones that don't). The alternative is the POSIX getgrent
family, but those are non-reentrant/MT-unsafe. There's getgrent_r
, but I believe it's glibc only.
When it works for Go, why shouldn't it work for Crystal? They already support more platforms than we do. And the implementation seems to be pretty straightforward and even if it's not a standard call cross-platform compatible (with only a small adjustment for darwin).
Is it planned that File::Info#owner
will return System::User
and File::Info#group
return System::Group
?
That's another issue. Please let's focus on the main API first.
In the API Arrays should be Sets for Group user_names, users and members.
I don't know @straight-shoota , it's not useful to provide an API if there is no other use inside the stdlib. Else, it can be a shard.
In the API Arrays should be Sets for Group user_names, users and members.
Following my proposal these properties should not be exposed in the API at all.
it's not useful to provide an API if there is no other use inside the stdlib.
Being able to retrieve basic user information is already useful by itself and an essential feature that should be provided in stdlib. Whether the User API gets integrated with other APIs is irrelevant. Although it is already planned to integrate it with the Process API.
More in-depth features such as getting more detailed information on users and groups (including group members) and perhaps also modifying these are highly platform-specific and should be supplied by a shard.
More in-depth features such as getting more detailed information on users and groups (including group members)
Just FWIW, getting group members is something #7725 supports (it's part of the information returned by the standard getgrnam_r
call). But I agree that most things more complicated than that belong in a shard :wink:
I also like the idea @j8r brought up about integrating these classes into File::Info
, but agree that the current PR shouldn't attempt that.
As for User#group_ids
: sounds good to me! Is that someone we want in #7725, or via a follow-up PR?
Yes, getgrnam_r
directly provides group members. But there must be some reason why Golang doesn't expose this. Maybe it's just because their Go-native implementation which doesn't use libc can't figure this out. Maybe there are some portability concerns. I'm not sure. But before adding it, we should make sure we can support it. And it could easily be added later.
As for User#group_ids: sounds good to me! Is that someone we want in #7725, or via a follow-up PR?
I'd rather have it in a follow-up. Let's work on smaller, contained pieces. That's easier.
Yes,
getgrnam_r
directly provides group members. But there must be some reason why Golang doesn't expose this. Maybe it's just because their Go-native implementation which doesn't use libc can't figure this out. Maybe there are some portability concerns. I'm not sure. But before adding it, we should make sure we can support it. And it could easily be added later.
Yeah, this is a bit of a mystery to me. From the Go source:
/*
Package user allows user account lookups by name or id.
For most Unix systems, this package has two internal implementations of
resolving user and group ids to names. One is written in pure Go and
parses /etc/passwd and /etc/group. The other is cgo-based and relies on
the standard C library (libc) routines such as getpwuid_r and getgrnam_r.
When cgo is available, cgo-based (libc-backed) code is used by default.
This can be overridden by using osusergo build tag, which enforces
the pure Go implementation.
*/
So, either way they have access to group memberships. They don't necessarily have to extract them when parsing /etc/group
, but they get them for free when using getgrnam_r
-- the call only succeeds if a sufficiently large buffer is passed for string storage. I'll dig into the actual implementation and see if I can figure out the reasoning.
I'd rather have it in a follow-up. Let's work on smaller, contained pieces. That's easier.
:+1:
So, here's where Go user getgrnam_r
:
That in turn calls buildGroup
, which only pulls the gid
and name
fields out:
func buildGroup(grp *C.struct_group) *Group {
g := &Group{
Gid: strconv.Itoa(int(grp.gr_gid)),
Name: C.GoString(grp.gr_name),
}
return g
}
So, pretty similar in implementation to our current one. I can't find any documentation online suggesting that the gr_mem
member of group
is unreliable or unsuitable for cross-platform code, but it's possible I'm missing something.
I like the new lookup methods using named arguments suggested in https://github.com/crystal-lang/crystal/pull/7725#issuecomment-512157450.
But are we settled on .find_by
?
I'd prefer simply .find
, which is exactly as expressive. IMO the suffix _by
doesn't add any value to the name. We don't need to follow ActiveRecord conventions here, this is a completely different domain (and language).
User.find(name: "foo")
User.find(id: 1)
vs.
User.find_by(name: "foo")
User.find_by(id: 1)
It's not a big difference, but a little bit more unnecessary clutter in my view.
/cc @ysbaddaden @woodruffw @bcardiff
I like how it reads. In AR is more needed since User.find would be by id. And the using User.find x
would depend on the argument been a hash or int. Buy my main argument is how it reads (find by name is proper).
Find by id and find by name read as plain English, and are just a little more explicit than find id and find name.
I'm also 👍 on find_by
vs. find
, although for different reasons (I didn't use AR much in Ruby) -- find
makes me think of an enumerable, whereas find_by
makes me think of a query against a resource.
The core System::User
and System::Group
classes have been implemented in #7725.
Now, we're still missing a few components and integrations with other APIs:
File
API: File.chown
/owner
/group
Process
API (#7829)One thing I discovered while trying to get Crystal running on Termux: The pw_passwd
and pw_gecos
fields are not part of POSIX, and Bionic, Android's C library implementation, declares these fields but always puts the null pointer in them. This means the following will always fail there:
require "system/user"
module Crystal::System::User
private def from_struct(pwd)
user = String.new(pwd.pw_gecos).partition(',')[0] # raises ArgumentError here
new(String.new(pwd.pw_name), pwd.pw_uid.to_s, pwd.pw_gid.to_s, user, String.new(pwd.pw_dir), String.new(pwd.pw_shell))
end
end
user_name = `id -un`.chomp
System::User.find_by(name: user_name) # Unhandled exception: Cannot create a string with a null pointer (ArgumentError)
I think @name
should default to @username
if this field is null.
Another question is, there hasn't been an implementation of the user / group APIs on Windows yet. Would these further restrict these interfaces or do we expose as many fields as possible?
A while ago I tried to implement what we have right now, based on Golang:
There is a lot of code for these classes, and it is hard to tell what works and what doesn't on a Windows Home installation. In fact I don't even know if System
should stay in the standard library.
Another useful reference, Cygwin: https://github.com/cygwin/cygwin/blob/master/winsup/cygwin/uinfo.cc
There have been some PR's adding
System::User
andSystem::Group
features, originally #5615 then it's reboot #5627 and now there's #7725 with a subset of the features, which is much easier to review.We've had discussions about the design of these APIs directly in the PRs, but that's not a good procedure, especially when we're talking about quite substantial, completely new APIs.
Instantiation
There have been several proposal for getting instances of
System::{Group,User}
. The last state from #5627 was.get(name_or_id)
,.from_gid(id)
,.from_groupname(name)
and equivalent forUser
(plus nilable?
methods).(There were also some methods to retrieve the name from id, or id from name directly (
.name
,.gid
). I'm not sure we really need them and they can easily be added later, so we don't need to discuss them right away.)7725 now comes with
.from_name
and.from_id
methods for both types (plus nilable?
methods). This is IMO an improvement, because we don't needgid
/uid
andgroupname´/
username` explicitly in the method name, when the fact that it's about a group/user is already determined by the type this method is called on.A generic method for looking up either by name or id might be useful, but this is a feature that can be added as an enhancement later.
Properties
The current proposals are mainly based on exposing properties provided from
getgrnam
,getpwnam
etc. But we need to take a wider look at exposing a portable API that is not POSIX-specific.5627 exposed the following properties:
User:
Group:
In #7725 we have
User:
Group:
There are already a few differences between both of them, and I think the correct solution is somewhere in between.
The interface in Golang is also a nice reference to a very neat API:
User:
Group:
I propose to use the exact same properties as used in Golang in our API. They are a good collection of essential features working cross-platform.
That's very minimal, but more features are always more complex. And honestly, you mostly won't need much more anyway. Let's start with this limited but solid set and discuss possible additions separately.