Add FFI wrapper around some of the Mach API (in Mac OS X).
This commit is contained in:
parent
3d01ce1ed6
commit
252d606c09
|
@ -0,0 +1,2 @@
|
|||
module Mach
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
require 'ffi'
|
||||
|
||||
require 'mach'
|
||||
require 'mach/types'
|
||||
|
||||
module Mach
|
||||
module Bootstrap
|
||||
extend FFI::Library
|
||||
include Types
|
||||
|
||||
ffi_lib 'c'
|
||||
|
||||
attach_variable :port, :bootstrap_port, :mach_port_t
|
||||
|
||||
attach_function(:bootstrap_strerror,
|
||||
[:kern_return_t],
|
||||
:pointer)
|
||||
|
||||
attach_function(:register,
|
||||
:bootstrap_register,
|
||||
[:mach_port_t, :name_t, :mach_port_t],
|
||||
:kern_return_t)
|
||||
|
||||
error_check :register
|
||||
|
||||
# NOTE: api does not say this string must be freed; assuming it
|
||||
# does not
|
||||
#
|
||||
# @return [String] the error string or nil
|
||||
def self.strerror(errno)
|
||||
ptr = bootstrap_strerror(errno)
|
||||
ptr.null? ? nil : ptr.read_string()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
require 'ffi'
|
||||
|
||||
require 'mach/functions'
|
||||
|
||||
module Mach
|
||||
class Error < StandardError
|
||||
class INVALID_ADDRESS < Error; end
|
||||
class PROTECTION_FAILURE < Error; end
|
||||
class NO_SPACE < Error; end
|
||||
class INVALID_ARGUMENT < Error; end
|
||||
class INVALID_NAME < Error; end
|
||||
class FAILURE < Error; end
|
||||
|
||||
include Functions
|
||||
|
||||
def self.new(msg, errno)
|
||||
klass = case errno
|
||||
when 1; then INVALID_ADDRESS
|
||||
when 2; then PROTECTION_FAILURE
|
||||
when 3; then NO_SPACE
|
||||
when 4; then INVALID_ARGUMENT
|
||||
when 5; then FAILURE
|
||||
when 15; then INVALID_NAME
|
||||
else FAILURE
|
||||
end
|
||||
|
||||
e = klass.allocate
|
||||
e.send(:initialize, msg, errno)
|
||||
e
|
||||
end
|
||||
|
||||
attr_reader :errno
|
||||
|
||||
def initialize(msg, errno)
|
||||
super(msg)
|
||||
@errno = errno
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{super}: #{error_string(errno)}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# NOTE: api does not say this string must be freed; assuming it
|
||||
# does not
|
||||
def error_string(errno)
|
||||
ptr = mach_error_string(errno)
|
||||
ptr.null? ? nil : ptr.read_string()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,252 @@
|
|||
module Mach
|
||||
# FFI wrapper around a subset of the Mach API (likely Mac OS X
|
||||
# specific).
|
||||
module Functions
|
||||
extend FFI::Library
|
||||
|
||||
ffi_lib 'c'
|
||||
|
||||
typedef :__darwin_mach_port_t, :mach_port_t
|
||||
typedef :__darwin_natural_t, :natural_t
|
||||
|
||||
typedef :mach_port_t, :task_t
|
||||
typedef :mach_port_t, :ipc_space_t
|
||||
typedef :mach_port_t, :semaphore_t
|
||||
typedef :pointer, :mach_port_pointer_t
|
||||
|
||||
typedef :natural_t, :mach_port_name_t
|
||||
typedef :natural_t, :mach_port_right_t # MachPortRight
|
||||
typedef :pointer, :mach_port_name_array_t
|
||||
typedef :pointer, :mach_port_name_pointer_t
|
||||
|
||||
typedef :uint, :mach_msg_type_name_t
|
||||
|
||||
typedef :int, :kern_return_t # true for 64 bit??
|
||||
typedef :int, :mach_error_t
|
||||
typedef :int, :sync_policy_t # SyncPolicy
|
||||
|
||||
typedef :string, :name_t
|
||||
|
||||
SyncPolicy = enum( :fifo, 0x0,
|
||||
:fixed_priority, 0x1,
|
||||
:reversed, 0x2,
|
||||
:order_mask, 0x3,
|
||||
:lifo, 0x0 | 0x2, # um...
|
||||
:max, 0x7 )
|
||||
|
||||
MachPortRight = enum( :send, 0,
|
||||
:receive,
|
||||
:send_once,
|
||||
:port_set,
|
||||
:dead_name,
|
||||
:labelh,
|
||||
:number )
|
||||
|
||||
# port type
|
||||
def self.pt(*syms)
|
||||
acc = 0
|
||||
syms.each do |sym|
|
||||
acc |= (1 << (MachPortRight[sym] + 16))
|
||||
end
|
||||
acc
|
||||
end
|
||||
|
||||
MachPortType =
|
||||
enum(:none, 0,
|
||||
:send, pt(:send),
|
||||
:receive, pt(:receive),
|
||||
:send_once, pt(:send_once),
|
||||
:port_set, pt(:port_set),
|
||||
:dead_name, pt(:dead_name),
|
||||
:labelh, pt(:labelh),
|
||||
|
||||
:send_receive, pt(:send, :receive),
|
||||
:send_rights, pt(:send, :send_once),
|
||||
:port_rights, pt(:send, :send_once, :receive),
|
||||
:port_or_dead, pt(:send, :send_once, :receive, :dead_name),
|
||||
:all_rights, pt(:send, :send_once, :receive, :dead_name, :port_set))
|
||||
|
||||
MachMsgType =
|
||||
enum( :move_receive, 16, # must hold receive rights
|
||||
:move_send, # must hold send rights
|
||||
:move_send_once, # must hold sendonce rights
|
||||
:copy_send, # must hold send rights
|
||||
:make_send, # must hold receive rights
|
||||
:make_send_once, # must hold receive rights
|
||||
:copy_receive ) # must hold receive rights
|
||||
|
||||
MachSpecialPort =
|
||||
enum( :kernel, 1,
|
||||
:host,
|
||||
:name,
|
||||
:bootstrap )
|
||||
|
||||
class Timespec < FFI::ManagedStruct
|
||||
layout(:tv_sec, :uint,
|
||||
:tv_nsec, :int)
|
||||
end
|
||||
|
||||
KERN_SUCCESS = 0
|
||||
|
||||
# Replace methods in +syms+ with error checking wrappers that
|
||||
# invoke the original method and raise a {SystemCallError}.
|
||||
#
|
||||
# The original method is invoked, and it's return value is passed
|
||||
# to the block (or a default check). The block should return true
|
||||
# if the return value indicates an error state.
|
||||
def self.error_check(*syms, &is_err)
|
||||
unless block_given?
|
||||
is_err = lambda { |v| (v != KERN_SUCCESS) }
|
||||
end
|
||||
|
||||
syms.each do |sym|
|
||||
method = self.method(sym)
|
||||
|
||||
new_method_body = proc do |*args|
|
||||
ret = method.call(*args)
|
||||
if is_err.call(ret)
|
||||
raise Error.new("error in #{sym}", ret)
|
||||
else
|
||||
ret
|
||||
end
|
||||
end
|
||||
|
||||
define_singleton_method(sym, &new_method_body)
|
||||
define_method(sym, &new_method_body)
|
||||
end
|
||||
end
|
||||
|
||||
# Replace methods in +syms+ with error checking wrappers that
|
||||
# invoke the original method and raise a {SystemCallError}.
|
||||
#
|
||||
# The original method is invoked, and it's return value is passed
|
||||
# to the block (or a default check). The block should return true
|
||||
# if the return value indicates an error state.
|
||||
def self.error_check_bootstrap(*syms, &is_err)
|
||||
unless block_given?
|
||||
is_err = lambda { |v| (v != KERN_SUCCESS) }
|
||||
end
|
||||
|
||||
syms.each do |sym|
|
||||
method = self.method(sym)
|
||||
|
||||
new_method_body = proc do |*args|
|
||||
ret = method.call(*args)
|
||||
if is_err.call(ret)
|
||||
ptr = bootstrap_strerror(ret)
|
||||
msg = ptr.null? ? nil : ptr.read_string()
|
||||
raise "error in #{sym}: #{msg}"
|
||||
else
|
||||
ret
|
||||
end
|
||||
end
|
||||
|
||||
define_singleton_method(sym, &new_method_body)
|
||||
define_method(sym, &new_method_body)
|
||||
end
|
||||
end
|
||||
|
||||
# Attach a function as with +attach_function+, but check the
|
||||
# return value and raise an exception on errors.
|
||||
def self.attach_mach_function(sym, argtypes, rettype)
|
||||
attach_function(sym, argtypes, rettype)
|
||||
error_check(sym)
|
||||
end
|
||||
|
||||
def self.new_memory_pointer(type)
|
||||
FFI::MemoryPointer.new(find_type(type))
|
||||
end
|
||||
|
||||
def new_memory_pointer(type)
|
||||
Mach::Functions.new_memory_pointer(type)
|
||||
end
|
||||
|
||||
attach_function :mach_task_self, [], :task_t
|
||||
attach_function :mach_error_string, [:mach_error_t], :pointer
|
||||
|
||||
#######################
|
||||
# Bootstrap functions #
|
||||
#######################
|
||||
|
||||
attach_variable :bootstrap_port, :mach_port_t
|
||||
|
||||
attach_function(:bootstrap_strerror,
|
||||
[:kern_return_t],
|
||||
:pointer)
|
||||
|
||||
attach_function(:bootstrap_register,
|
||||
[:mach_port_t, :name_t, :mach_port_t],
|
||||
:kern_return_t)
|
||||
error_check_bootstrap :bootstrap_register
|
||||
|
||||
##################
|
||||
# Port functions #
|
||||
##################
|
||||
|
||||
attach_mach_function(:mach_port_allocate,
|
||||
[:ipc_space_t,
|
||||
MachPortRight,
|
||||
:mach_port_name_pointer_t],
|
||||
:kern_return_t)
|
||||
|
||||
attach_mach_function(:mach_port_destroy,
|
||||
[:ipc_space_t,
|
||||
:mach_port_name_t],
|
||||
:kern_return_t)
|
||||
|
||||
attach_mach_function(:mach_port_deallocate,
|
||||
[:ipc_space_t,
|
||||
:mach_port_name_t],
|
||||
:kern_return_t)
|
||||
|
||||
attach_mach_function(:mach_port_insert_right,
|
||||
[:ipc_space_t,
|
||||
:mach_port_name_t,
|
||||
:mach_port_t,
|
||||
MachMsgType],
|
||||
:kern_return_t)
|
||||
|
||||
##################
|
||||
# Task functions #
|
||||
##################
|
||||
|
||||
attach_mach_function(:task_get_special_port,
|
||||
[:task_t,
|
||||
:int,
|
||||
:mach_port_pointer_t],
|
||||
:kern_return_t)
|
||||
|
||||
attach_mach_function(:task_set_special_port,
|
||||
[:task_t,
|
||||
:int,
|
||||
:mach_port_t],
|
||||
:kern_return_t)
|
||||
|
||||
#######################
|
||||
# Semaphore functions #
|
||||
#######################
|
||||
|
||||
attach_mach_function(:semaphore_create,
|
||||
[:task_t, :pointer, SyncPolicy, :int],
|
||||
:kern_return_t)
|
||||
attach_mach_function(:semaphore_destroy,
|
||||
[:task_t, :semaphore_t],
|
||||
:kern_return_t)
|
||||
|
||||
attach_mach_function(:semaphore_signal,
|
||||
[:semaphore_t],
|
||||
:kern_return_t)
|
||||
attach_mach_function(:semaphore_signal_all,
|
||||
[:semaphore_t],
|
||||
:kern_return_t)
|
||||
attach_mach_function(:semaphore_wait,
|
||||
[:semaphore_t],
|
||||
:kern_return_t)
|
||||
attach_mach_function(:semaphore_timedwait,
|
||||
[:semaphore_t, Timespec.val],
|
||||
:kern_return_t)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'mach/error'
|
|
@ -0,0 +1,47 @@
|
|||
require 'mach/functions'
|
||||
|
||||
module Mach
|
||||
class Port
|
||||
include Functions
|
||||
|
||||
attr_reader :ipc_space, :port
|
||||
|
||||
# either initialize(port, opts) -or- initialize(opts)
|
||||
def initialize(opts = {}, opts2 = {})
|
||||
if opts.kind_of? Hash
|
||||
ipc_space = opts[:ipc_space] || mach_task_self
|
||||
right = opts[:right] || :receive
|
||||
|
||||
mem = new_memory_pointer(:mach_port_right_t)
|
||||
mach_port_allocate(ipc_space, right, mem)
|
||||
|
||||
@port = mem.get_uint(0)
|
||||
@ipc_space = ipc_space
|
||||
else
|
||||
@port = opts
|
||||
@ipc_space = opts2[:ipc_space] || mach_task_self
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
(port == other.port) && (ipc_space == other.ipc_space)
|
||||
end
|
||||
|
||||
def destroy(opts = {})
|
||||
ipc_space = opts[:ipc_space] || @ipc_space
|
||||
mach_port_destroy(ipc_space, @port)
|
||||
end
|
||||
|
||||
def deallocate(opts = {})
|
||||
ipc_space = opts[:ipc_space] || @ipc_space
|
||||
mach_port_deallocate(ipc_space, @port)
|
||||
end
|
||||
|
||||
def insert_right(msg_type, opts = {})
|
||||
ipc_space = opts[:ipc_space] || @ipc_space
|
||||
port_name = opts[:port_name] || @port
|
||||
|
||||
mach_port_insert_right(ipc_space, port_name, @port, msg_type)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
require 'mach/functions'
|
||||
require 'mach/port'
|
||||
|
||||
module Mach
|
||||
class Semaphore < Port
|
||||
include Functions
|
||||
|
||||
# Create a new Semaphore.
|
||||
#
|
||||
# @param [Integer] value the initial value of the semaphore
|
||||
#
|
||||
# @param [Hash] opts
|
||||
#
|
||||
# @option opts [Integer] :task the Mach task that owns the
|
||||
# semaphore (defaults to Mach.task_self)
|
||||
#
|
||||
# @options opts [Integer] :sync_policy the sync policy for this
|
||||
# semaphore (defaults to SyncPolicy::FIFO)
|
||||
#
|
||||
# @return [Integer] a semaphore port name
|
||||
def initialize(value = 1, opts = {})
|
||||
task = opts[:task] || ipc_space || mach_task_self
|
||||
sync_policy = opts[:sync_policy] || :fifo
|
||||
|
||||
mem = new_memory_pointer(:semaphore_t)
|
||||
semaphore_create(task, mem, sync_policy, value)
|
||||
super(mem.get_uint(0), :ipc_space => task)
|
||||
end
|
||||
|
||||
# Destroy a Semaphore.
|
||||
#
|
||||
# @param [Hash] opts
|
||||
#
|
||||
# @option opts [Integer] :task the Mach task that owns the
|
||||
# semaphore (defaults to the owning task)
|
||||
def destroy(opts = {})
|
||||
task = opts[:task] || ipc_space || mach_task_self
|
||||
semaphore_destroy(task, port)
|
||||
end
|
||||
|
||||
def signal
|
||||
semaphore_signal(port)
|
||||
end
|
||||
|
||||
def signal_all
|
||||
semaphore_signal_all(port)
|
||||
end
|
||||
|
||||
def wait
|
||||
semaphore_wait(port)
|
||||
end
|
||||
|
||||
# TODO: implement
|
||||
def timedwait(secs)
|
||||
semaphore_timedwait(port, timespec)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
require 'mach/functions'
|
||||
require 'mach/port'
|
||||
|
||||
module Mach
|
||||
class Task < Port
|
||||
include Functions
|
||||
|
||||
# @return [Task]
|
||||
def self.self
|
||||
new(Functions.mach_task_self)
|
||||
end
|
||||
|
||||
def initialize(task)
|
||||
super(task)
|
||||
end
|
||||
|
||||
alias_method :task, :port
|
||||
|
||||
def get_special_port(which_port)
|
||||
mem = FFI::MemoryPointer.new(:int)
|
||||
task_get_special_port(task, which_port, mem)
|
||||
Port.new(mem.get_int(0))
|
||||
end
|
||||
|
||||
# @param [Port,Integer] newport
|
||||
def set_special_port(which_port, newport)
|
||||
p = newport.respond_to?(:port) ? newport.port : newport
|
||||
task_set_special_port(task, which_port, p)
|
||||
end
|
||||
|
||||
def get_bootstrap_port
|
||||
get_special_port(:bootstrap)
|
||||
end
|
||||
|
||||
def set_bootstrap_port(port)
|
||||
set_special_port(:bootstrap, port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
require 'mach/port'
|
||||
|
||||
module Mach
|
||||
describe Port do
|
||||
it 'creates a port' do
|
||||
port = Port.new
|
||||
port.destroy
|
||||
end
|
||||
|
||||
it 'raises exception with invalid args' do
|
||||
p = proc { Port.new(:right => 1234) }
|
||||
p.must_raise Error::FAILURE
|
||||
end
|
||||
|
||||
it 'inserts rights' do
|
||||
port = Port.new
|
||||
port.insert_right(:make_send)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
require 'spec_helper'
|
||||
require 'mach/error'
|
||||
require 'mach/functions'
|
||||
require 'mach/semaphore'
|
||||
|
||||
module Mach
|
||||
describe 'low level semaphore functions' do
|
||||
include Functions
|
||||
|
||||
it 'raises exception with invalid args' do
|
||||
p = proc { semaphore_create(mach_task_self, nil, 1234, 1) }
|
||||
p.must_raise Error::INVALID_ARGUMENT
|
||||
end
|
||||
end
|
||||
|
||||
describe Semaphore do
|
||||
it 'creates a semaphore' do
|
||||
sem = Semaphore.new
|
||||
sem.destroy
|
||||
end
|
||||
|
||||
it 'raises exception with invalid args' do
|
||||
p = proc { Semaphore.new(1, :sync_policy => :no_such) }
|
||||
p.must_raise ArgumentError # Error::INVALID_ARGUMENT
|
||||
end
|
||||
|
||||
it 'signals/waits in same task' do
|
||||
sem = Semaphore.new(0)
|
||||
sem.signal
|
||||
sem.wait
|
||||
sem.destroy
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
require 'spec_helper'
|
||||
require 'mach'
|
||||
require 'mach/port'
|
||||
require 'mach/task'
|
||||
|
||||
module Mach
|
||||
describe Task do
|
||||
before :each do
|
||||
@task = Task.self
|
||||
end
|
||||
|
||||
it 'gets special ports' do
|
||||
bp = @task.get_special_port(:bootstrap)
|
||||
bp.must_equal @task.get_bootstrap_port
|
||||
end
|
||||
|
||||
it 'redefines bootstrap port' do
|
||||
bp = @task.get_bootstrap_port
|
||||
new_bp = Port.new
|
||||
bp.wont_equal(new_bp)
|
||||
|
||||
begin
|
||||
new_bp.insert_right(:make_send)
|
||||
@task.set_bootstrap_port(new_bp)
|
||||
@task.get_bootstrap_port.must_equal(new_bp)
|
||||
ensure
|
||||
@task.set_bootstrap_port(bp)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue