Hey everyone,
This is my first attempt to write a binding to LibC, I appreciate any guidance.
Specifically, I am trying to write a C binding to getpwent_r - the documentation is here: getpwent_r(3) - Linux manual page
I have been trying to use crystal/src/crystal/system/unix/user.cr at master · crystal-lang/crystal · GitHub as reference.
Here’s what I have so far:
lib LibC
fun getpwent_r(pwstore : Passwd*, buf : Int32*, bufsize : LibC::SizeT, result : Passwd**) : LibC::Int
end
pwd = uninitialized LibC::Passwd
pwd_pointer = Pointer(LibC::Passwd).null
buf = uninitialized Int32
LibC.getpwent_r(pointerof(pwd), pointerof(buf), 4096, pointerof(pwd_pointer)).tap do
puts pwd_pointer.value
end
I run it with a simple:
crystal run src/lib_nss3/lib_nss3.cr
Output:
LibC::Passwd(@pw_name=Pointer(UInt8)@0x7ffe150b721c, @pw_passwd=Pointer(UInt8)@0x7ffe150b7221, @pw_uid=0, @pw_gid=0, @pw_gecos=Pointer(UInt8)@0x7ffe150b7227, @pw_dir=Pointer(UInt8)@0x7ffe150b722e, @pw_shell=Pointer(UInt8)@0x7ffe150b722e)
I was expecting to get a list of users from the system, what am I missing to get to that ?
- It seems like it’s replying with what you want, most often
Pointer(UInt8)
is actually a string, try reading it like that using String.new
(docs)
- You may want to bind
getpwent
instead as the docs for getpwent_r
warn that
The function getpwent_r() is not really reentrant since it shares
the reading position in the stream with all other threads.
1 Like
thank you so much @sol.vin !
So now I have this:
lib LibC
fun setpwent
fun getpwent_r(pwstore : Passwd*, buf : Int32*, bufsize : LibC::SizeT, result : Passwd**) : LibC::Int
fun endpwent
end
pwd = uninitialized LibC::Passwd
pwd_pointer = Pointer(LibC::Passwd).null
buf = uninitialized Int32
LibC.setpwent # start from the beginning of the file
while LibC.getpwent_r(pointerof(pwd), pointerof(buf), 4096, pointerof(pwd_pointer)) == 0
username = String.new(pwd_pointer.value.@pw_name)
uid = pwd_pointer.value.@pw_uid
puts "#{username},#{uid}"
end
LibC.endpwent # close password database
and output:
crystal run src/lib_nss3/lib_nss3.cr
root(�2�,0
bin,2
sys,3
man,6
lp,7
mail(�2�,8
news(�2�,9
irc,39
Invalid memory access (signal 11) at address 0x6e69676f6c
[0x55f195df9b36] *Exception::CallStack::print_backtrace:Nil +118 in /home/weirdbricks/.cache/crystal/crystal-run-lib_nss3.tmp
[0x55f195de9016] ~procProc(Int32, Pointer(LibC::SiginfoT), Pointer(Void), Nil) +310 in /home/weirdbricks/.cache/crystal/crystal-run-lib_nss3.tmp
Program exited because of an invalid memory access
The list of users is incomplete. Would you happen to know why I see those weird characters and why I’m getting that error?
buf
needs to be bigger. Int32
is just 4 bytes. But you’re telling getpwent_r
the buffer size would be 4096 bytes. This leads to corrupted strings and eventually invalid memory access.
Try buf = uninitialized StaticArray(UInt8, 4096)
instead.
Also the data type of buf
is char*
which equals to UInt8*
in Crystal.
This works:
lib LibC
fun setpwent
fun getpwent_r(pwstore : Passwd*, buf : LibC::Char*, bufsize : LibC::SizeT, result : Passwd**) : LibC::Int
fun endpwent
end
pwd = uninitialized LibC::Passwd
pwd_pointer = Pointer(LibC::Passwd).null
buf = uninitialized StaticArray(UInt8, 4096)
LibC.setpwent # start from the beginning of the file
while LibC.getpwent_r(pointerof(pwd), buf, buf.size, pointerof(pwd_pointer)) == 0
username = String.new(pwd_pointer.value.@pw_name)
uid = pwd_pointer.value.@pw_uid
puts "#{username},#{uid}"
end
LibC.endpwent # close password database
1 Like
it works! thank you so much @straight-shoota