Trying to bind to pwd.h/getpwent_r

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 ?

  1. 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)
  2. 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