/*
 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


//
// mach++ - C++ bindings for useful Mach primitives
//
#include <security_utilities/mach++.h>
#include <mach/mach_error.h>
#include <security_utilities/debugging.h>
#include <servers/bootstrap_defs.h>	// debug

namespace Security {
namespace MachPlusPlus {


//
// Mach subsystem exceptions, a subclass of CssmCommonError
//
Error::Error(kern_return_t err) : error(err)
{
	IFDEBUG(debugDiagnose(this));
}

Error::~Error() throw()
{ }


OSStatus Error::osStatus() const
{
	return -1;	//@@@CONV
}

int Error::unixError() const
{
	return -1;			//@@@CONV
}

void Error::check(kern_return_t status)
{
	if (status != KERN_SUCCESS)
		Error::throwMe(status);
}

void Error::throwMe(kern_return_t err)
{
	throw Error(err);
}


#if !defined(NDEBUG)
void Error::debugDiagnose(const void *id) const
{
	const char *name;
	switch (error) {
	default:
		name = mach_error_string(error); break;
	case BOOTSTRAP_UNKNOWN_SERVICE:
		name = "BOOTSTRAP_UNKNOWN_SERVICE"; break;
	case BOOTSTRAP_NAME_IN_USE:	
		name = "BOOTSTRAP_NAME_IN_USE"; break;
	case BOOTSTRAP_NOT_PRIVILEGED:
		name = "BOOTSTRAP_NOT_PRIVILEGED"; break;
	case BOOTSTRAP_SERVICE_ACTIVE:
		name = "BOOTSTRAP_SERVICE_ACTIVE"; break;
	}
    secdebug("exception", "%p Mach Error %s (%d) osStatus %ld",
		id, name, error, osStatus());
}
#endif //NDEBUG


//
// Memory management
//
void *allocate(size_t size)
{
	vm_address_t address;
	check(vm_allocate(mach_task_self(), &address, size, true));
	return reinterpret_cast<void *>(address);
}

void deallocate(vm_address_t address, size_t size)
{
	check(vm_deallocate(mach_task_self(), address, size));
}


//
// Port functions
//
mach_port_urefs_t Port::getRefs(mach_port_right_t right)
{
	mach_port_urefs_t count;
	check(::mach_port_get_refs(self(), mPort, right, &count));
	return count;
}

mach_port_t Port::requestNotify(mach_port_t notify, mach_msg_id_t type, mach_port_mscount_t sync)
{
    mach_port_t previous;
    check(mach_port_request_notification(self(), mPort, type, sync, notify,
        MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous));

#if !defined(NDEBUG)
	const char *typeName;
	switch (type) {
	case MACH_NOTIFY_PORT_DELETED:	typeName = "port deleted"; break;
	case MACH_NOTIFY_PORT_DESTROYED:typeName = "port destroyed"; break;
	case MACH_NOTIFY_NO_SENDERS:	typeName = "no senders"; break;
	case MACH_NOTIFY_SEND_ONCE:		typeName = "send once"; break;
	case MACH_NOTIFY_DEAD_NAME:		typeName = "dead name"; break;
	default:						typeName = "???"; break;
	}
	if (notify == MACH_PORT_NULL)
		secdebug("port", "%d cancel notify %s", port(), typeName);
	else
		secdebug("port", "%d request notify %s to %d (sync %d)", port(), typeName, notify, sync);
#endif //!NDEBUG

    return previous;
}

mach_port_t Port::cancelNotify(mach_msg_id_t type)
{
    // Mach won't let us unset the DPN port if we are already dead
    // (EVEN if the DPN has already been sent!) So just ignore that case...
    if (isDead())
        return MACH_PORT_NULL;
	return requestNotify(MACH_PORT_NULL, type);
}

mach_port_msgcount_t Port::qlimit() const
{
	mach_port_limits_t limits;
	mach_msg_type_number_t infoCount = 1;
	check(::mach_port_get_attributes(self(), mPort, MACH_PORT_LIMITS_INFO,
		mach_port_info_t(&limits), &infoCount));
	assert(infoCount == 1);
	return limits.mpl_qlimit;
}

void Port::qlimit(mach_port_msgcount_t limit)
{
	mach_port_limits_t limits;
	limits.mpl_qlimit = limit;
	check(::mach_port_set_attributes(self(), mPort, MACH_PORT_LIMITS_INFO,
		mach_port_info_t(&limits), MACH_PORT_LIMITS_INFO_COUNT));
}


//
// PortSet features
//
set<Port> PortSet::members() const
{
	mach_port_array_t members;
	mach_msg_type_number_t count;
	check(::mach_port_get_set_status(self(), mPort, &members, &count));
	try {
		set<Port> result;
		copy(members, members+count, inserter(result, result.begin()));
		vm_deallocate(self(), vm_address_t(members), count * sizeof(members[0]));
		return result;
	} catch (...) {
		vm_deallocate(self(), vm_address_t(members), count * sizeof(members[0]));
		throw;
	}
}


bool PortSet::contains(Port member) const
{
	set<Port> memberSet = members();
	return memberSet.find(member) != memberSet.end();
}


//
// Task port features
//
pid_t TaskPort::pid() const
{
    pid_t pid;
    check(::pid_for_task(mPort, &pid));
    return pid;
}


//
// Bootstrap port management
//
mach_port_t Bootstrap::checkIn(const char *name) const
{
	mach_port_t port;
	check(::bootstrap_check_in(mPort, makeName(name), &port));
	return port;
}

mach_port_t Bootstrap::checkInOptional(const char *name) const
{
	mach_port_t port;
	switch (kern_return_t err = ::bootstrap_check_in(mPort, makeName(name), &port)) {
	case BOOTSTRAP_SERVICE_ACTIVE:
    case BOOTSTRAP_UNKNOWN_SERVICE:
	case BOOTSTRAP_NOT_PRIVILEGED:
        return MACH_PORT_NULL;
	default:
		check(err);
	}
	return port;
}

void Bootstrap::registerAs(mach_port_t port, const char *name) const
{
	secdebug("bootstrap", "creating service port %d in %d:%s", port, this->port(), name);
	check(::bootstrap_register(mPort, makeName(name), port));
}

mach_port_t Bootstrap::lookup(const char *name) const
{
	mach_port_t port;
	check(::bootstrap_look_up(mPort, makeName(name), &port));
	return port;
}

mach_port_t Bootstrap::lookupOptional(const char *name) const
{
	mach_port_t port;
	kern_return_t err = ::bootstrap_look_up(mPort, makeName(name), &port);
    if (err == BOOTSTRAP_UNKNOWN_SERVICE)
        return 0;
    check(err);
	return port;
}


Bootstrap Bootstrap::subset(Port requestor)
{
    mach_port_t sub;
    check(::bootstrap_subset(mPort, requestor, &sub));
    return sub;
}


//
// ReceivePorts
//
ReceivePort::ReceivePort(const char *name, const Bootstrap &bootstrap)
{
	mPort = bootstrap.checkInOptional(name);
	if (!mPort) {
		allocate();
		// Bootstrap registration requires a send right to (copy) send.
		// Make a temporary one, send it, then take it away again, to avoid
		// messing up the caller's send right accounting.
		insertRight(MACH_MSG_TYPE_MAKE_SEND);
		bootstrap.registerAs(mPort, name);
		modRefs(MACH_PORT_RIGHT_SEND, -1);
	}
}


//
// Stack-based bootstrap switcher
//
ModuleNexus<Mutex> StBootstrap::critical;

StBootstrap::StBootstrap(const Bootstrap &newBoot, const TaskPort &task) 
    : mTask(task), locker(critical())
{
    mOldBoot = Bootstrap();
    mTask.bootstrap(newBoot);
    secdebug("StBoot", "bootstrap for %d switched to %d", mTask.port(), newBoot.port());
}

StBootstrap::~StBootstrap()
{
    mTask.bootstrap(mOldBoot);
    secdebug("StBoot", "bootstrap for %d returned to %d", mTask.port(), mOldBoot.port());
}


//
// Mach message buffers
//
Message::Message(void *buffer, size_t size)
	: mBuffer(NULL), mRelease(false)
{
	setBuffer(buffer, size);
}

Message::Message(size_t size)
	: mBuffer(NULL), mRelease(false)
{
	setBuffer(size);
}

Message::Message()
	: mBuffer(NULL), mRelease(false)
{ }


Message::~Message()
{
	release();
}


void Message::setBuffer(void *buffer, size_t size)
{
	release();
	mBuffer = reinterpret_cast<mig_reply_error_t *>(buffer);
	mSize = size;
	mRelease = false;
}

void Message::setBuffer(size_t size)
{
	assert(size >= sizeof(mach_msg_header_t));
	release();
	mSize = size + MAX_TRAILER_SIZE;
	mBuffer = reinterpret_cast<mig_reply_error_t *>(new char[mSize]);
	mRelease = true;
}


void Message::release()
{
	if (mRelease)
		delete[] reinterpret_cast<char *>(mBuffer);
}
	

bool Message::check(kern_return_t status)
{
    switch (status) {
    case KERN_SUCCESS:
        return true;
    case MACH_RCV_TIMED_OUT:
    case MACH_SEND_TIMED_OUT:
        return false;
    default:
        Error::throwMe(status);
    }
}


bool Message::send(mach_msg_option_t options,
    mach_msg_timeout_t timeout,
    mach_port_name_t notify)
{
    return check(mach_msg_overwrite(*this,
        options | MACH_SEND_MSG,
        length(),
        0, MACH_PORT_NULL,
        timeout, notify,
        NULL, 0));
}

bool Message::receive(mach_port_t receivePort,
    mach_msg_option_t options,
    mach_msg_timeout_t timeout,
    mach_port_name_t notify)
{
    return check(mach_msg_overwrite(*this,
        options | MACH_RCV_MSG,
        length(),
        mSize, receivePort,
        timeout, notify,
        NULL, 0));
}

bool Message::sendReceive(mach_port_t receivePort,
    mach_msg_option_t options,
    mach_msg_timeout_t timeout,
    mach_port_name_t notify)
{
    return check(mach_msg_overwrite(*this,
        options | MACH_SEND_MSG | MACH_RCV_MSG,
        length(),
        mSize, receivePort,
        timeout, notify,
        NULL, 0));
}


//
// Debug dumping of ports etc.
//
#if defined(DEBUGDUMP)

void Port::dump(const char *descr)
{
    if (mPort == MACH_PORT_NULL) {
        Debug::dump("[%s==NULL]\n", descr ? descr : "port");
    } else {
        Debug::dump("[%s(%d)", descr ? descr : "port", mPort);
        mach_port_type_t type;
        if (kern_return_t err = mach_port_type(self(), mPort, &type)) {
            Debug::dump(" !%s", mach_error_string(err));
        } else {
            if (type & MACH_PORT_TYPE_SEND)
                Debug::dump(" send(%d)", getRefs(MACH_PORT_RIGHT_SEND));
            if (type & MACH_PORT_TYPE_RECEIVE)
                Debug::dump(" rcv");
            if (type & MACH_PORT_TYPE_SEND_ONCE)
                Debug::dump(" once(%d)", getRefs(MACH_PORT_RIGHT_SEND));
            if (type & MACH_PORT_TYPE_PORT_SET)
                Debug::dump(" set");
            if (type & MACH_PORT_TYPE_DEAD_NAME)
                Debug::dump(" dead(%d)", getRefs(MACH_PORT_RIGHT_SEND));        
            if (type & MACH_PORT_TYPE_DNREQUEST)
                Debug::dump(" dnreq");
            // handle unknown/unexpected type flags
            if (type & ~(MACH_PORT_TYPE_SEND|MACH_PORT_TYPE_RECEIVE|MACH_PORT_TYPE_SEND_ONCE|
                    MACH_PORT_TYPE_PORT_SET|MACH_PORT_TYPE_DEAD_NAME|MACH_PORT_TYPE_DNREQUEST))
                Debug::dump(" type(0x%x)", type);
        }
        Debug::dump("]\n");
    }
}


void Bootstrap::dump()
{
    name_array_t services, servers;
    bool_array_t active;
    mach_msg_type_number_t nServices, nServers, nActive;
    check(bootstrap_info(mPort, &services, &nServices,
        &servers, &nServers, &active, &nActive));
    Port::dump();
    Debug::dump(" %d services\n", nServices);
    for (mach_msg_type_number_t n = 0; n < nServices; n++)
        Debug::dump("%s\n", services[n]);
}

#endif //DEBUGDUMP


} // end namespace MachPlusPlus
} // end namespace Security
