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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce System::User and System::Group #7725
Changes from all commits
ce912de
e05e20e
1774301
66989d2
f23218f
274907e
1eeda59
1b63e44
edda4e6
6ba7c3b
dacbf7c
b6c11f1
674d39b
dfd55ae
541a3a9
29fb389
968c278
ccaefd8
afd3e7e
9f6922b
e88ad10
3f74d2d
0b186dd
7e3dc5b
82f9af9
adbaefc
fa39723
16a4639
1a1d8ea
3dc6e18
3ab2c93
653fecf
51cf041
b01143a
9a1ec5c
bee43b9
77d5a1f
dc96d4e
c199140
aa2cecb
2345d55
e2d8187
3f5081a
712166b
4c96a6f
856b657
1f4179d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
require "spec" | ||
require "system/group" | ||
|
||
GROUP_NAME = {{ `id -gn`.stringify.chomp }} | ||
GROUP_ID = {{ `id -g`.stringify.chomp }} | ||
|
||
describe System::Group do | ||
describe ".find_by(*, name)" do | ||
it "returns a group by name" do | ||
group = System::Group.find_by(name: GROUP_NAME) | ||
|
||
group.should be_a(System::Group) | ||
group.name.should eq(GROUP_NAME) | ||
group.id.should eq(GROUP_ID) | ||
end | ||
|
||
it "raises on nonexistent group" do | ||
expect_raises System::Group::NotFoundError, "No such group" do | ||
System::Group.find_by(name: "this_group_does_not_exist") | ||
end | ||
end | ||
end | ||
|
||
describe ".find_by(*, id)" do | ||
it "returns a group by id" do | ||
group = System::Group.find_by(id: GROUP_ID) | ||
|
||
group.should be_a(System::Group) | ||
group.id.should eq(GROUP_ID) | ||
group.name.should eq(GROUP_NAME) | ||
end | ||
|
||
it "raises on nonexistent group name" do | ||
expect_raises System::Group::NotFoundError, "No such group" do | ||
System::Group.find_by(id: "1234567") | ||
end | ||
end | ||
end | ||
|
||
describe ".find_by?(*, name)" do | ||
it "returns a group by name" do | ||
group = System::Group.find_by?(name: GROUP_NAME).not_nil! | ||
|
||
group.should be_a(System::Group) | ||
group.name.should eq(GROUP_NAME) | ||
group.id.should eq(GROUP_ID) | ||
end | ||
|
||
it "returns nil on nonexistent group" do | ||
group = System::Group.find_by?(name: "this_group_does_not_exist") | ||
group.should eq(nil) | ||
end | ||
end | ||
|
||
describe ".find_by?(*, id)" do | ||
it "returns a group by id" do | ||
group = System::Group.find_by?(id: GROUP_ID).not_nil! | ||
|
||
group.should be_a(System::Group) | ||
group.id.should eq(GROUP_ID) | ||
group.name.should eq(GROUP_NAME) | ||
end | ||
|
||
it "returns nil on nonexistent group id" do | ||
group = System::Group.find_by?(id: "1234567") | ||
group.should eq(nil) | ||
end | ||
end | ||
|
||
describe "#name" do | ||
it "is the same as the source name" do | ||
System::Group.find_by(name: GROUP_NAME).name.should eq(GROUP_NAME) | ||
end | ||
end | ||
|
||
describe "#id" do | ||
it "is the same as the source ID" do | ||
System::Group.find_by(id: GROUP_ID).id.should eq(GROUP_ID) | ||
end | ||
end | ||
|
||
describe "#to_s" do | ||
it "returns a string representation" do | ||
System::Group.find_by(name: GROUP_NAME).to_s.should eq("#{GROUP_NAME} (#{GROUP_ID})") | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
require "spec" | ||
require "system/user" | ||
|
||
USER_NAME = {{ `id -un`.stringify.chomp }} | ||
USER_ID = {{ `id -u`.stringify.chomp }} | ||
|
||
describe System::User do | ||
describe ".find_by(*, name)" do | ||
it "returns a user by name" do | ||
user = System::User.find_by(name: USER_NAME) | ||
|
||
user.should be_a(System::User) | ||
user.username.should eq(USER_NAME) | ||
user.id.should eq(USER_ID) | ||
end | ||
|
||
it "raises on a nonexistent user" do | ||
expect_raises System::User::NotFoundError, "No such user" do | ||
System::User.find_by(name: "this_user_does_not_exist") | ||
end | ||
end | ||
end | ||
|
||
describe ".find_by(*, id)" do | ||
it "returns a user by id" do | ||
user = System::User.find_by(id: USER_ID) | ||
|
||
user.should be_a(System::User) | ||
user.id.should eq(USER_ID) | ||
user.username.should eq(USER_NAME) | ||
end | ||
|
||
it "raises on nonexistent user id" do | ||
expect_raises System::User::NotFoundError, "No such user" do | ||
System::User.find_by(id: "1234567") | ||
end | ||
end | ||
end | ||
|
||
describe ".find_by?(*, name)" do | ||
it "returns a user by name" do | ||
user = System::User.find_by?(name: USER_NAME).not_nil! | ||
|
||
user.should be_a(System::User) | ||
user.username.should eq(USER_NAME) | ||
user.id.should eq(USER_ID) | ||
end | ||
|
||
it "returns nil on nonexistent user" do | ||
user = System::User.find_by?(name: "this_user_does_not_exist") | ||
user.should eq(nil) | ||
end | ||
end | ||
|
||
describe ".find_by?(*, id)" do | ||
it "returns a user by id" do | ||
user = System::User.find_by?(id: USER_ID).not_nil! | ||
|
||
user.should be_a(System::User) | ||
user.id.should eq(USER_ID) | ||
user.username.should eq(USER_NAME) | ||
end | ||
|
||
it "returns nil on nonexistent user id" do | ||
user = System::User.find_by?(id: "1234567") | ||
user.should eq(nil) | ||
end | ||
end | ||
|
||
describe "#username" do | ||
it "is the same as the source name" do | ||
System::User.find_by(name: USER_NAME).username.should eq(USER_NAME) | ||
end | ||
end | ||
|
||
describe "#id" do | ||
it "is the same as the source ID" do | ||
System::User.find_by(id: USER_ID).id.should eq(USER_ID) | ||
end | ||
end | ||
|
||
describe "#group_id" do | ||
it "calls without raising" do | ||
System::User.find_by(name: USER_NAME).group_id | ||
end | ||
end | ||
|
||
describe "#name" do | ||
it "calls without raising" do | ||
System::User.find_by(name: USER_NAME).name | ||
end | ||
end | ||
|
||
describe "#home_directory" do | ||
it "calls without raising" do | ||
System::User.find_by(name: USER_NAME).home_directory | ||
end | ||
end | ||
|
||
describe "#shell" do | ||
it "calls without raising" do | ||
System::User.find_by(name: USER_NAME).shell | ||
end | ||
end | ||
|
||
describe "#to_s" do | ||
it "returns a string representation" do | ||
System::User.find_by(name: USER_NAME).to_s.should eq("#{USER_NAME} (#{USER_ID})") | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{% if flag?(:unix) %} | ||
require "./unix/group" | ||
{% else %} | ||
{% raise "No Crystal::System::Group implementation available" %} | ||
{% end %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require "c/grp" | ||
|
||
module Crystal::System::Group | ||
private GETGR_R_SIZE_MAX = 1024 * 16 | ||
|
||
private def from_struct(grp) | ||
new(String.new(grp.gr_name), grp.gr_gid.to_s) | ||
end | ||
|
||
private def from_name?(groupname : String) | ||
groupname.check_no_null_byte | ||
|
||
grp = uninitialized LibC::Group | ||
grp_pointer = pointerof(grp) | ||
initial_buf = uninitialized UInt8[1024] | ||
buf = initial_buf.to_slice | ||
|
||
ret = LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bcardiff Yes, it's what I suggested. I forgot to come back to this because it's not possible due to a compiler bug. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, this is what I ran into. |
||
while ret == LibC::ERANGE && buf.size < GETGR_R_SIZE_MAX | ||
buf = Bytes.new(buf.size * 2) | ||
ret = LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)) | ||
end | ||
|
||
raise Errno.new("getgrnam_r") if ret != 0 | ||
|
||
from_struct(grp) if grp_pointer | ||
end | ||
|
||
private def from_id?(groupid : String) | ||
groupid = groupid.to_u32? | ||
return unless groupid | ||
|
||
grp = uninitialized LibC::Group | ||
grp_pointer = pointerof(grp) | ||
initial_buf = uninitialized UInt8[1024] | ||
buf = initial_buf.to_slice | ||
|
||
ret = LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)) | ||
while ret == LibC::ERANGE && buf.size < GETGR_R_SIZE_MAX | ||
buf = Bytes.new(buf.size * 2) | ||
ret = LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)) | ||
end | ||
|
||
raise Errno.new("getgrgid_r") if ret != 0 | ||
|
||
from_struct(grp) if grp_pointer | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
require "c/pwd" | ||
|
||
module Crystal::System::User | ||
private GETPW_R_SIZE_MAX = 1024 * 16 | ||
|
||
private def from_struct(pwd) | ||
user = String.new(pwd.pw_gecos).split(",").first | ||
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 | ||
|
||
private def from_username?(username : String) | ||
username.check_no_null_byte | ||
|
||
pwd = uninitialized LibC::Passwd | ||
pwd_pointer = pointerof(pwd) | ||
initial_buf = uninitialized UInt8[1024] | ||
buf = initial_buf.to_slice | ||
|
||
ret = LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) | ||
while ret == LibC::ERANGE && buf.size < GETPW_R_SIZE_MAX | ||
buf = Bytes.new(buf.size * 2) | ||
ret = LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) | ||
end | ||
|
||
raise Errno.new("getpwnam_r") if ret != 0 | ||
|
||
from_struct(pwd) if pwd_pointer | ||
end | ||
|
||
private def from_id?(id : String) | ||
id = id.to_u32? | ||
return unless id | ||
|
||
pwd = uninitialized LibC::Passwd | ||
pwd_pointer = pointerof(pwd) | ||
initial_buf = uninitialized UInt8[1024] | ||
buf = initial_buf.to_slice | ||
|
||
ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) | ||
while ret == LibC::ERANGE && buf.size < GETPW_R_SIZE_MAX | ||
buf = Bytes.new(buf.size * 2) | ||
ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) | ||
end | ||
|
||
raise Errno.new("getpwuid_r") if ret != 0 | ||
|
||
from_struct(pwd) if pwd_pointer | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{% if flag?(:unix) %} | ||
require "./unix/user" | ||
{% else %} | ||
{% raise "No Crystal::System::User implementation available" %} | ||
{% end %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,3 +62,4 @@ require "./uuid" | |
require "./uuid/json" | ||
require "./zip" | ||
require "./zlib" | ||
require "./system/*" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
lib LibC | ||
struct Group | ||
gr_name : Char* | ||
gr_passwd : Char* | ||
gr_gid : GidT | ||
gr_mem : Char** | ||
end | ||
|
||
fun getgrnam_r(name : Char*, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
fun getgrgid_r(gid : GidT, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
lib LibC | ||
struct Passwd | ||
pw_name : Char* | ||
pw_passwd : Char* | ||
pw_uid : UidT | ||
pw_gid : GidT | ||
pw_gecos : Char* | ||
pw_dir : Char* | ||
pw_shell : Char* | ||
end | ||
|
||
fun getpwnam_r(login : Char*, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int | ||
fun getpwuid_r(uid : UidT, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
lib LibC | ||
struct Group | ||
gr_name : Char* | ||
gr_passwd : Char* | ||
gr_gid : GidT | ||
gr_mem : Char** | ||
end | ||
|
||
fun getgrnam_r(name : Char*, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
fun getgrgid_r(gid : GidT, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
lib LibC | ||
struct Passwd | ||
pw_name : Char* | ||
pw_passwd : Char* | ||
pw_uid : UidT | ||
pw_gid : GidT | ||
pw_gecos : Char* | ||
pw_dir : Char* | ||
pw_shell : Char* | ||
end | ||
|
||
fun getpwnam_r(login : Char*, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int | ||
fun getpwuid_r(uid : UidT, pwstore : Passwd*, buf : Char*, bufsize : SizeT, result : Passwd**) : Int | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
lib LibC | ||
struct Group | ||
gr_name : Char* | ||
gr_passwd : Char* | ||
gr_gid : GidT | ||
gr_mem : Char** | ||
end | ||
|
||
fun getgrnam_r(name : Char*, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
fun getgrgid_r(gid : GidT, grp : Group*, buf : Char*, bufsize : SizeT, result : Group**) : Int | ||
end |
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
out
be used here instead if taking pointer of pointer? Also, in examples I see they use two variables and they don't point to each other, but I'm not familiar with this API.