require 'ffi' require 'mach/functions' module Mach # Michael Weber's "Some Fun with Mach Ports" was an indispensable # resource in learning the Mach ports API. # # @see http://www.foldr.org/~michaelw/log/computers/macosx/task-info-fun-with-mach class Port include Functions class SendRightMsg < FFI::Struct include Functions layout(:header, MsgHeader, :body, MsgBody, :port, MsgPortDescriptor) end class ReceiveRightMsg < FFI::Struct include Functions layout(:header, MsgHeader, :body, MsgBody, :port, MsgPortDescriptor, :trailer, MsgTrailer) end attr_reader :ipc_space, :port # @param [Hash] opts # # @option opts [Integer] :ipc_space defaults to +mach_task_self+ # # @option opts [MachPortRight] :right defaults to +:receive+ # # @option opts [Port, Integer] :port if given, the existing port # is wrapped in a new Port object; otherwise a new port is # allocated according to the other options def initialize(opts = {}) @ipc_space = opts[:ipc_space] || mach_task_self right = opts[:right] || :receive @port = if opts[:port] opts[:port].to_i else mem = new_memory_pointer(:mach_port_right_t) mach_port_allocate(@ipc_space.to_i, right, mem) mem.get_uint(0) end end # With this alias, we can call #to_i on either bare Integer ports # or wrapped Port objects when passing the arg to a foreign # function. alias_method :to_i, :port def to_s "#<#{self.class} #{to_i}>" 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.to_i, @port) end # Insert +right+ into another ipc space. The current task must # have sufficient rights to insert the requested right. # # @param [MsgType] right # # @param [Hash] opts # # @option opts [Port,Integer] :ipc_space the space (task port) into which # +right+ will be inserted; defaults to this port's ipc_space # # @option opts [Port,Integer] :port the name the port right should # have in :ipc_space; defaults to the same name as this port def insert_right(right, opts = {}) ipc_space = opts[:ipc_space] || @ipc_space port_name = opts[:port_name] || @port mach_port_insert_right(ipc_space.to_i, port_name.to_i, @port, right) end # Send +right+ on this Port to +remote_port+. The current task # must already have the requisite rights allowing it to send # +right+. def send_right(right, remote_port) msg = SendRightMsg.new msg[:header].tap do |h| h[:remote_port] = remote_port.to_i h[:local_port] = PORT_NULL h[:bits] = (MsgType[right] | (0 << 8)) | 0x80000000 # MACH_MSGH_BITS_COMPLEX h[:size] = 40 # msg.size end msg[:body][:descriptor_count] = 1 msg[:port].tap do |p| p[:name] = port p[:disposition] = MsgType[right] p[:type] = 0 # MSG_PORT_DESCRIPTOR; end mach_msg_send msg end # Copy the send right on this port and send it in a message to # +remote_port+. The current task must have an existing send # right on this Port. def copy_send(remote_port) send_right(:copy_send, remote_port) end # Create a new Port by receiving a port right message on this # port. def receive_right msg = ReceiveRightMsg.new mach_msg(msg, 2, # RCV_MSG, 0, msg.size, port, MSG_TIMEOUT_NONE, PORT_NULL) self.class.new :port => msg[:port][:name] end end end