FFI, sysctl, pointer question

Hi,

I’m trying to get better at FFI, but I’m getting stumped on how to
create and pass pointers to functions.

In the example below, in the Sys::Uptime.seconds method, I’m trying to
figure out how to create and pass a pointer for the mib (a 2-element
integer array) and the timeval size (i.e. where I have a ‘?’ instead of
actual code).

What should the actual code be?

module Sys
class Uptime
extend FFI::Library

   attach_function :time, [:pointer], :ulong
   attach_function :sysctl,
      [:pointer, :uint, :pointer, :pointer, :pointer, :uint], :int

   CTL_KERN = 1 # Kernel
   KERN_BOOTTIME = 21 # Time kernel was booted

   class Timeval < FFI::Struct
      layout(
         :tv_sec,  :long,
         :tv_usec, :long
      )
   end

   # How do I create the mib? How do I pass the address of tv.size?
   def self.seconds
      tv  = Timeval.new
      mib = [CTL_KERN, KERN_BOOTTIME]

      # What's the proper way to call this?
      if sysctl(?, 2, tv, ?, nil, 0) != 0
         raise SystemCallError, 'sysctl()'
      end

      time(nil) - tv[:tv_sec]
   end
end

end

Regards,

Dan

On Fri, Aug 7, 2009 at 10:22 AM, Daniel B.[email protected]
wrote:
At first sight you need MemoryPointer, but I could not really get it
working, but probably I screwed the sysctl call, I did not find a
reference of it for my system. Well for what it is worth I’ll share
what I did.
N.B. This fails with
ffi1.rb:35:in seconds': unknown error - sysctl() (SystemCallError) from ffi1.rb:43:in

def self.seconds
    tv  = Timeval.new
    mib = [CTL_KERN, KERN_BOOTTIME]

    # What's the proper way to call this?
    tvp = FFI::MemoryPointer::new :pointer
    tvp.put_pointer 0, tv
    mip = FFI::MemoryPointer::new :int, 2
    mip.put_array_of_int 0, mib
    if sysctl(mip, 2, tv, tvp, nil, 0) != 0
       raise SystemCallError, 'sysctl()'
    end

    time(nil) - tv[:tv_sec]

end

Maybe somebody can spot my error(s) :wink:

Cheers
Robert

Daniel B. wrote:

Hi,

I’m trying to get better at FFI, but I’m getting stumped on how to
create and pass pointers to functions.

In the example below, in the Sys::Uptime.seconds method, I’m trying to
figure out how to create and pass a pointer for the mib (a 2-element
integer array) and the timeval size (i.e. where I have a ‘?’ instead of
actual code).

What should the actual code be?

You could explicity instantiate two MemoryPointer objects initialized
with
the proper values:

mib_ptr = FFI::MemoryPointer.new(:int, 2).write_array_of_int(mib)
tv_size_ptr = FFI::MemoryPointer.new(:int).write_int(tv.size)

Then you could pass those values to sysctl function:

sysctl(mib_ptr, 2, tv, tv_size_ptr, nil, 0)

To ask for more (and better) help feel free to post your questions to
the Ruby-FFI ml at [email protected]

2009/8/7 Andrea F. [email protected]:

[…]
tv_size_ptr = FFI::MemoryPointer.new(:int).write_int(tv.size)
[…]

Hello,

size_t in ANSI C is required to be unsigned and is usually defined as
unsigned long. Therefore the above code is always wrong in that it
stores a signed integer, and it is likely wrong on some 64 bit systems
where an int is 32 bits wide but a long is 64 bits wide.

cu,
Thomas

Andrea F. wrote:

sysctl(mib_ptr, 2, tv, tv_size_ptr, nil, 0)

Thank you, that worked.

To ask for more (and better) help feel free to post your questions to
the Ruby-FFI ml at [email protected]

If we want FFI to gain traction among Rubyists (we do, don’t we?), it’s
better to ask & answer here IMO.

Regards,

Dan

Thomas C. wrote:

2009/8/7 Andrea F. [email protected]:

�[…]
�tv_size_ptr = FFI::MemoryPointer.new(:int).write_int(tv.size)
[…]

Hello,

size_t in ANSI C is required to be unsigned and is usually defined as
unsigned long. Therefore the above code is always wrong in that it
stores a signed integer, and it is likely wrong on some 64 bit systems
where an int is 32 bits wide but a long is 64 bits wide.

cu,
Thomas

Hi Thomas,

I didn’t check for the sysctl prototype when replied to OP so I was not
aware about the type of the fourth argument (a pointer to a size_t
value). Thank you for pointing it out. That said, the MemoryPointer
object should be instantiated and filled in this way:

tv_size_ptr = FFI::MemoryPointer.new(:size_t).write_size_t(tv.size)

Doing this way, Ruby-FFI should use the right size for size_t.
Unfortunately, AFAIK, while :size_t type exists its accessors
(get_size_t, read_size_t, put_size_t, write_size_t) are not yet
implemented on Ruby-FFI. I’ll fire a JIRA ticket for this.

Please also note that my previous code is not always wrong. Well, it
may be conceptually wrong but not in practice for the considered case.
In fact, size of the Timeval struct is 8 bytes on a ILP32 system and 16
bytes on a LP64 one. Thus, writing/reading a pointer to a signed 8 (or
16) is the same as writing/reading a pointer to an unsigned 8 (or 16).

signed = FFI::MemoryPointer.new(:int).put_int(0, 8)
=> #
signed.get_int(0)
=> 8
signed.get_uint(0)
=> 8
unsigned = FFI::MemoryPointer.new(:uint).put_uint(0, 8)
=> #
unsigned.get_int(0)
=> 8
unsigned.get_uint(0)
=> 8

Andrea