* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2002-2015, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2001, Mark-Jan Bastian. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include <port.h>
#include <algorithm>
#include <ctype.h>
#include <iovec.h>
#include <stdlib.h>
#include <string.h>
#include <OS.h>
#include <AutoDeleter.h>
#include <StackOrHeapArray.h>
#include <arch/int.h>
#include <heap.h>
#include <kernel.h>
#include <Notifications.h>
#include <sem.h>
#include <syscall_restart.h>
#include <team.h>
#include <tracing.h>
#include <util/AutoLock.h>
#include <util/list.h>
#include <util/iovec_support.h>
#include <vm/vm.h>
#include <wait_for_objects.h>
#ifdef TRACE_PORTS
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
#if __GNUC__ >= 3
# define GCC_2_NRV(x)
// GCC >= 3.1 doesn't need it anymore
#else
# define GCC_2_NRV(x) return x;
#endif
namespace {
struct port_message : DoublyLinkedListLinkImpl<port_message> {
int32 code;
size_t size;
uid_t sender;
gid_t sender_group;
team_id sender_team;
char buffer[0];
};
typedef DoublyLinkedList<port_message> MessageList;
}
static void put_port_message(port_message* message);
namespace {
struct Port : public KernelReferenceable {
enum State {
kUnused = 0,
kActive,
kDeleted
};
struct list_link team_link;
Port* hash_link;
port_id id;
team_id owner;
Port* name_hash_link;
size_t name_hash;
int32 capacity;
mutex lock;
int32 state;
uint32 read_count;
int32 write_count;
ConditionVariable read_condition;
ConditionVariable write_condition;
int32 total_count;
select_info* select_infos;
MessageList messages;
Port(team_id owner, int32 queueLength, const char* name)
:
owner(owner),
name_hash(0),
capacity(queueLength),
state(kUnused),
read_count(0),
write_count(queueLength),
total_count(0),
select_infos(NULL)
{
mutex_init_etc(&lock, name, MUTEX_FLAG_CLONE_NAME);
read_condition.Init(this, "port read");
write_condition.Init(this, "port write");
}
virtual ~Port()
{
while (port_message* message = messages.RemoveHead())
put_port_message(message);
mutex_destroy(&lock);
}
};
struct PortHashDefinition {
typedef port_id KeyType;
typedef Port ValueType;
size_t HashKey(port_id key) const
{
return key;
}
size_t Hash(Port* value) const
{
return HashKey(value->id);
}
bool Compare(port_id key, Port* value) const
{
return value->id == key;
}
Port*& GetLink(Port* value) const
{
return value->hash_link;
}
};
typedef BOpenHashTable<PortHashDefinition> PortHashTable;
struct PortNameHashDefinition {
typedef const char* KeyType;
typedef Port ValueType;
size_t HashKey(const char* key) const
{
const size_t length = strlen(key);
size_t hash = 0;
for (size_t index = 0; index < length; index++)
hash = 31 * hash + key[index];
return hash;
}
size_t Hash(Port* value) const
{
size_t& hash = value->name_hash;
if (hash == 0)
hash = HashKey(value->lock.name);
return hash;
}
bool Compare(const char* key, Port* value) const
{
return (strcmp(key, value->lock.name) == 0);
}
Port*& GetLink(Port* value) const
{
return value->name_hash_link;
}
};
typedef BOpenHashTable<PortNameHashDefinition> PortNameHashTable;
class PortNotificationService : public DefaultNotificationService {
public:
PortNotificationService();
void Notify(uint32 opcode, port_id team);
};
}
#if PORT_TRACING
namespace PortTracing {
class Create : public AbstractTraceEntry {
public:
Create(Port* port)
:
fID(port->id),
fOwner(port->owner),
fCapacity(port->capacity)
{
fName = alloc_tracing_buffer_strcpy(port->lock.name, B_OS_NAME_LENGTH,
false);
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld created, name \"%s\", owner %ld, capacity %ld",
fID, fName, fOwner, fCapacity);
}
private:
port_id fID;
char* fName;
team_id fOwner;
int32 fCapacity;
};
class Delete : public AbstractTraceEntry {
public:
Delete(Port* port)
:
fID(port->id)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld deleted", fID);
}
private:
port_id fID;
};
class Read : public AbstractTraceEntry {
public:
Read(const BReference<Port>& portRef, int32 code, ssize_t result)
:
fID(portRef->id),
fReadCount(portRef->read_count),
fWriteCount(portRef->write_count),
fCode(code),
fResult(result)
{
Initialized();
}
Read(port_id id, int32 readCount, int32 writeCount, int32 code,
ssize_t result)
:
fID(id),
fReadCount(readCount),
fWriteCount(writeCount),
fCode(code),
fResult(result)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld read, read %ld, write %ld, code %lx: %ld",
fID, fReadCount, fWriteCount, fCode, fResult);
}
private:
port_id fID;
int32 fReadCount;
int32 fWriteCount;
int32 fCode;
ssize_t fResult;
};
class Write : public AbstractTraceEntry {
public:
Write(port_id id, int32 readCount, int32 writeCount, int32 code,
size_t bufferSize, ssize_t result)
:
fID(id),
fReadCount(readCount),
fWriteCount(writeCount),
fCode(code),
fBufferSize(bufferSize),
fResult(result)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld write, read %ld, write %ld, code %lx, size %ld: %ld",
fID, fReadCount, fWriteCount, fCode, fBufferSize, fResult);
}
private:
port_id fID;
int32 fReadCount;
int32 fWriteCount;
int32 fCode;
size_t fBufferSize;
ssize_t fResult;
};
class Info : public AbstractTraceEntry {
public:
Info(const BReference<Port>& portRef, int32 code, ssize_t result)
:
fID(portRef->id),
fReadCount(portRef->read_count),
fWriteCount(portRef->write_count),
fCode(code),
fResult(result)
{
Initialized();
}
Info(port_id id, int32 readCount, int32 writeCount, int32 code,
ssize_t result)
:
fID(id),
fReadCount(readCount),
fWriteCount(writeCount),
fCode(code),
fResult(result)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld info, read %ld, write %ld, code %lx: %ld",
fID, fReadCount, fWriteCount, fCode, fResult);
}
private:
port_id fID;
int32 fReadCount;
int32 fWriteCount;
int32 fCode;
ssize_t fResult;
};
class OwnerChange : public AbstractTraceEntry {
public:
OwnerChange(Port* port, team_id newOwner, status_t status)
:
fID(port->id),
fOldOwner(port->owner),
fNewOwner(newOwner),
fStatus(status)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("port %ld owner change from %ld to %ld: %s", fID, fOldOwner,
fNewOwner, strerror(fStatus));
}
private:
port_id fID;
team_id fOldOwner;
team_id fNewOwner;
status_t fStatus;
};
}
# define T(x) new(std::nothrow) PortTracing::x;
#else
# define T(x) ;
#endif
static const size_t kInitialPortBufferSize = 4 * 1024 * 1024;
static const size_t kTotalSpaceLimit = 64 * 1024 * 1024;
static const size_t kTeamSpaceLimit = 8 * 1024 * 1024;
static const size_t kBufferGrowRate = kInitialPortBufferSize;
#define MAX_QUEUE_LENGTH 4096
#define PORT_MAX_MESSAGE_SIZE (256 * 1024)
static int32 sMaxPorts = 4096;
static int32 sUsedPorts;
static PortHashTable sPorts;
static PortNameHashTable sPortsByName;
static ConditionVariable sNoSpaceCondition;
static int32 sTotalSpaceCommited;
static int32 sWaitingForSpace;
static port_id sNextPortID = 1;
static bool sPortsActive = false;
static rw_lock sPortsLock = RW_LOCK_INITIALIZER("ports list");
enum {
kTeamListLockCount = 8
};
static mutex sTeamListLock[kTeamListLockCount] = {
MUTEX_INITIALIZER("team ports list 1"),
MUTEX_INITIALIZER("team ports list 2"),
MUTEX_INITIALIZER("team ports list 3"),
MUTEX_INITIALIZER("team ports list 4"),
MUTEX_INITIALIZER("team ports list 5"),
MUTEX_INITIALIZER("team ports list 6"),
MUTEX_INITIALIZER("team ports list 7"),
MUTEX_INITIALIZER("team ports list 8")
};
static PortNotificationService sNotificationService;
PortNotificationService::PortNotificationService()
:
DefaultNotificationService("ports")
{
}
void
PortNotificationService::Notify(uint32 opcode, port_id port)
{
char eventBuffer[128];
KMessage event;
event.SetTo(eventBuffer, sizeof(eventBuffer), PORT_MONITOR);
event.AddInt32("event", opcode);
event.AddInt32("port", port);
DefaultNotificationService::Notify(event, opcode);
}
static int
dump_port_list(int argc, char** argv)
{
const char* name = NULL;
team_id owner = -1;
if (argc > 2) {
if (!strcmp(argv[1], "team") || !strcmp(argv[1], "owner"))
owner = strtoul(argv[2], NULL, 0);
else if (!strcmp(argv[1], "name"))
name = argv[2];
} else if (argc > 1)
owner = strtoul(argv[1], NULL, 0);
kprintf("port id cap read-cnt write-cnt total team "
"name\n");
for (PortHashTable::Iterator it = sPorts.GetIterator();
Port* port = it.Next();) {
if ((owner != -1 && port->owner != owner)
|| (name != NULL && strstr(port->lock.name, name) == NULL))
continue;
kprintf("%p %8" B_PRId32 " %4" B_PRId32 " %9" B_PRIu32 " %9" B_PRId32
" %8" B_PRId32 " %6" B_PRId32 " %s\n", port, port->id,
port->capacity, port->read_count, port->write_count,
port->total_count, port->owner, port->lock.name);
}
return 0;
}
static void
_dump_port_info(Port* port)
{
kprintf("PORT: %p\n", port);
kprintf(" id: %" B_PRId32 "\n", port->id);
kprintf(" name: \"%s\"\n", port->lock.name);
kprintf(" owner: %" B_PRId32 "\n", port->owner);
kprintf(" capacity: %" B_PRId32 "\n", port->capacity);
kprintf(" read_count: %" B_PRIu32 "\n", port->read_count);
kprintf(" write_count: %" B_PRId32 "\n", port->write_count);
kprintf(" total count: %" B_PRId32 "\n", port->total_count);
if (!port->messages.IsEmpty()) {
kprintf("messages:\n");
MessageList::Iterator iterator = port->messages.GetIterator();
while (port_message* message = iterator.Next()) {
kprintf(" %p %08" B_PRIx32 " %ld\n", message, message->code, message->size);
}
}
set_debug_variable("_port", (addr_t)port);
set_debug_variable("_portID", port->id);
set_debug_variable("_owner", port->owner);
}
static int
dump_port_info(int argc, char** argv)
{
ConditionVariable* condition = NULL;
const char* name = NULL;
if (argc < 2) {
print_debugger_command_usage(argv[0]);
return 0;
}
if (argc > 2) {
if (!strcmp(argv[1], "address")) {
_dump_port_info((Port*)parse_expression(argv[2]));
return 0;
} else if (!strcmp(argv[1], "condition"))
condition = (ConditionVariable*)parse_expression(argv[2]);
else if (!strcmp(argv[1], "name"))
name = argv[2];
} else if (parse_expression(argv[1]) > 0) {
int32 num = parse_expression(argv[1]);
Port* port = sPorts.Lookup(num);
if (port == NULL || port->state != Port::kActive) {
kprintf("port %" B_PRId32 " (%#" B_PRIx32 ") doesn't exist!\n",
num, num);
return 0;
}
_dump_port_info(port);
return 0;
} else
name = argv[1];
for (PortHashTable::Iterator it = sPorts.GetIterator();
Port* port = it.Next();) {
if ((name != NULL && port->lock.name != NULL
&& !strcmp(name, port->lock.name))
|| (condition != NULL && (&port->read_condition == condition
|| &port->write_condition == condition))) {
_dump_port_info(port);
return 0;
}
}
return 0;
}
The port must be locked.
*/
static void
notify_port_select_events(Port* port, uint16 events)
{
if (port->select_infos)
notify_select_events_list(port->select_infos, events);
}
static BReference<Port>
get_locked_port(port_id id) GCC_2_NRV(portRef)
{
#if __GNUC__ >= 3
BReference<Port> portRef;
#endif
{
ReadLocker portsLocker(sPortsLock);
portRef.SetTo(sPorts.Lookup(id));
}
if (portRef != NULL && portRef->state == Port::kActive) {
if (mutex_lock(&portRef->lock) != B_OK)
portRef.Unset();
} else
portRef.Unset();
return portRef;
}
static BReference<Port>
get_port(port_id id) GCC_2_NRV(portRef)
{
#if __GNUC__ >= 3
BReference<Port> portRef;
#endif
ReadLocker portsLocker(sPortsLock);
portRef.SetTo(sPorts.Lookup(id));
return portRef;
}
static inline bool
is_port_closed(Port* port)
{
return port->capacity == 0;
}
static void
put_port_message(port_message* message)
{
const size_t size = sizeof(port_message) + message->size;
free(message);
atomic_add(&sTotalSpaceCommited, -size);
if (sWaitingForSpace > 0)
sNoSpaceCondition.NotifyAll();
}
static status_t
get_port_message(int32 code, size_t bufferSize, uint32 flags, bigtime_t timeout,
port_message** _message, Port& port)
{
const size_t size = sizeof(port_message) + bufferSize;
while (true) {
int32 previouslyCommited = atomic_add(&sTotalSpaceCommited, size);
while (previouslyCommited + size > kTotalSpaceLimit) {
atomic_add(&sTotalSpaceCommited, -size);
if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0)
return B_WOULD_BLOCK;
ConditionVariableEntry entry;
sNoSpaceCondition.Add(&entry);
port_id portID = port.id;
mutex_unlock(&port.lock);
atomic_add(&sWaitingForSpace, 1);
status_t status = entry.Wait(flags, timeout);
atomic_add(&sWaitingForSpace, -1);
BReference<Port> newPortRef = get_locked_port(portID);
if (newPortRef.Get() != &port || is_port_closed(&port)) {
return B_BAD_PORT_ID;
}
if (status == B_TIMED_OUT)
return B_TIMED_OUT;
previouslyCommited = atomic_add(&sTotalSpaceCommited, size);
continue;
}
port_message* message = (port_message*)malloc(size);
if (message != NULL) {
message->code = code;
message->size = bufferSize;
*_message = message;
return B_OK;
}
atomic_add(&sTotalSpaceCommited, -size);
continue;
}
}
port.
The port's lock must be held when called.
*/
static void
fill_port_info(Port* port, port_info* info, size_t size)
{
info->port = port->id;
info->team = port->owner;
info->capacity = port->capacity;
info->queue_count = port->read_count;
info->total_count = port->total_count;
strlcpy(info->name, port->lock.name, B_OS_NAME_LENGTH);
}
static ssize_t
copy_port_message(port_message* message, int32* _code, void* buffer,
size_t bufferSize, bool userCopy)
{
size_t size = std::min(bufferSize, message->size);
if (_code != NULL)
*_code = message->code;
if (size > 0) {
if (userCopy) {
status_t status = user_memcpy(buffer, message->buffer, size);
if (status != B_OK)
return status;
} else
memcpy(buffer, message->buffer, size);
}
return size;
}
static void
uninit_port(Port* port)
{
MutexLocker locker(port->lock);
notify_port_select_events(port, B_EVENT_INVALID);
port->select_infos = NULL;
port->read_condition.NotifyAll(B_BAD_PORT_ID);
port->write_condition.NotifyAll(B_BAD_PORT_ID);
sNotificationService.Notify(PORT_REMOVED, port->id);
}
* holding a reference itself or by holding a lock on one of the data
* structures in which it is referenced.)
*/
static status_t
delete_port_logical(Port* port)
{
for (;;) {
const int32 oldState = atomic_test_and_set(&port->state,
Port::kDeleted, Port::kActive);
switch (oldState) {
case Port::kActive:
return B_OK;
case Port::kDeleted:
TRACE(("delete_port_logical: already deleted port_id %ld\n",
port->id));
return B_BAD_PORT_ID;
case Port::kUnused:
continue;
default:
panic("Invalid port state!\n");
}
}
}
*/
void
delete_owned_ports(Team* team)
{
TRACE(("delete_owned_ports(owner = %ld)\n", team->id));
list deletionList;
list_init_etc(&deletionList, port_team_link_offset());
const uint8 lockIndex = team->id % kTeamListLockCount;
MutexLocker teamPortsListLocker(sTeamListLock[lockIndex]);
Port* port = (Port*)list_get_first_item(&team->port_list);
while (port != NULL) {
status_t status = delete_port_logical(port);
Port* nextPort = (Port*)list_get_next_item(&team->port_list, port);
if (status == B_OK) {
list_remove_link(&port->team_link);
list_add_item(&deletionList, port);
}
port = nextPort;
}
teamPortsListLocker.Unlock();
{
WriteLocker portsLocker(sPortsLock);
for (Port* port = (Port*)list_get_first_item(&deletionList);
port != NULL;
port = (Port*)list_get_next_item(&deletionList, port)) {
sPorts.Remove(port);
sPortsByName.Remove(port);
port->ReleaseReference();
}
}
while (Port* port = (Port*)list_remove_head_item(&deletionList)) {
atomic_add(&sUsedPorts, -1);
uninit_port(port);
port->ReleaseReference();
}
}
int32
port_max_ports(void)
{
return sMaxPorts;
}
int32
port_used_ports(void)
{
return sUsedPorts;
}
size_t
port_team_link_offset()
{
Port* port = (Port*)0;
return (size_t)&port->team_link;
}
status_t
port_init(kernel_args *args)
{
new(&sPorts) PortHashTable;
if (sPorts.Init() != B_OK) {
panic("Failed to init port hash table!");
return B_NO_MEMORY;
}
new(&sPortsByName) PortNameHashTable;
if (sPortsByName.Init() != B_OK) {
panic("Failed to init port by name hash table!");
return B_NO_MEMORY;
}
sNoSpaceCondition.Init(&sPorts, "port space");
add_debugger_command_etc("ports", &dump_port_list,
"Dump a list of all active ports (for team, with name, etc.)",
"[ ([ \"team\" | \"owner\" ] <team>) | (\"name\" <name>) ]\n"
"Prints a list of all active ports meeting the given\n"
"requirement. If no argument is given, all ports are listed.\n"
" <team> - The team owning the ports.\n"
" <name> - Part of the name of the ports.\n", 0);
add_debugger_command_etc("port", &dump_port_info,
"Dump info about a particular port",
"(<id> | [ \"address\" ] <address>) | ([ \"name\" ] <name>) "
"| (\"condition\" <address>)\n"
"Prints info about the specified port.\n"
" <address> - Pointer to the port structure.\n"
" <name> - Name of the port.\n"
" <condition> - address of the port's read or write condition.\n", 0);
new(&sNotificationService) PortNotificationService();
sNotificationService.Register();
sPortsActive = true;
return B_OK;
}
port_id
create_port(int32 queueLength, const char* name)
{
TRACE(("create_port(queueLength = %ld, name = \"%s\")\n", queueLength,
name));
if (!sPortsActive) {
panic("ports used too early!\n");
return B_BAD_PORT_ID;
}
if (queueLength < 1 || queueLength > MAX_QUEUE_LENGTH)
return B_BAD_VALUE;
Team* team = thread_get_current_thread()->team;
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Port> port;
{
Port* newPort = new(std::nothrow) Port(team_get_current_team_id(),
queueLength, name != NULL ? name : "unnamed port");
if (newPort == NULL)
return B_NO_MEMORY;
port.SetTo(newPort, true);
}
const int32 previouslyUsed = atomic_add(&sUsedPorts, 1);
if (previouslyUsed + 1 >= sMaxPorts) {
atomic_add(&sUsedPorts, -1);
return B_NO_MORE_PORTS;
}
{
WriteLocker locker(sPortsLock);
do {
port->id = sNextPortID++;
if (sNextPortID < 0)
sNextPortID = 1;
} while (sPorts.Lookup(port->id) != NULL);
port->AcquireReference();
sPorts.Insert(port);
sPortsByName.Insert(port);
}
{
const uint8 lockIndex = port->owner % kTeamListLockCount;
MutexLocker teamPortsListLocker(sTeamListLock[lockIndex]);
port->AcquireReference();
list_add_item(&team->port_list, port);
}
T(Create(port));
const port_id id = port->id;
const int32 oldState = atomic_test_and_set(&port->state,
Port::kActive, Port::kUnused);
if (oldState != Port::kUnused) {
panic("Port state was modified during creation!\n");
}
TRACE(("create_port() done: port created %ld\n", id));
sNotificationService.Notify(PORT_ADDED, id);
return id;
}
status_t
close_port(port_id id)
{
TRACE(("close_port(id = %ld)\n", id));
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL) {
TRACE(("close_port: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
MutexLocker lock(&portRef->lock, true);
portRef->capacity = 0;
notify_port_select_events(portRef, B_EVENT_INVALID);
portRef->select_infos = NULL;
portRef->read_condition.NotifyAll(B_BAD_PORT_ID);
portRef->write_condition.NotifyAll(B_BAD_PORT_ID);
return B_OK;
}
status_t
delete_port(port_id id)
{
TRACE(("delete_port(id = %ld)\n", id));
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
BReference<Port> portRef = get_port(id);
if (portRef == NULL) {
TRACE(("delete_port: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
status_t status = delete_port_logical(portRef);
if (status != B_OK)
return status;
{
WriteLocker portsLocker(sPortsLock);
sPorts.Remove(portRef);
sPortsByName.Remove(portRef);
portRef->ReleaseReference();
}
{
const uint8 lockIndex = portRef->owner % kTeamListLockCount;
MutexLocker teamPortsListLocker(sTeamListLock[lockIndex]);
list_remove_link(&portRef->team_link);
portRef->ReleaseReference();
}
uninit_port(portRef);
T(Delete(portRef));
atomic_add(&sUsedPorts, -1);
return B_OK;
}
status_t
select_port(int32 id, struct select_info* info, bool kernel)
{
if (id < 0)
return B_BAD_PORT_ID;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL)
return B_BAD_PORT_ID;
MutexLocker locker(portRef->lock, true);
if (is_port_closed(portRef))
return B_BAD_PORT_ID;
if (!kernel && portRef->owner == team_get_kernel_team_id()) {
return B_NOT_ALLOWED;
}
info->selected_events &= B_EVENT_READ | B_EVENT_WRITE | B_EVENT_INVALID;
if (info->selected_events != 0) {
uint16 events = 0;
info->next = portRef->select_infos;
portRef->select_infos = info;
if ((info->selected_events & B_EVENT_READ) != 0
&& !portRef->messages.IsEmpty()) {
events |= B_EVENT_READ;
}
if (portRef->write_count > 0)
events |= B_EVENT_WRITE;
if (events != 0)
notify_select_events(info, events);
}
return B_OK;
}
status_t
deselect_port(int32 id, struct select_info* info, bool kernel)
{
if (id < 0)
return B_BAD_PORT_ID;
if (info->selected_events == 0)
return B_OK;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL)
return B_BAD_PORT_ID;
MutexLocker locker(portRef->lock, true);
select_info** infoLocation = &portRef->select_infos;
while (*infoLocation != NULL && *infoLocation != info)
infoLocation = &(*infoLocation)->next;
if (*infoLocation == info)
*infoLocation = info->next;
return B_OK;
}
port_id
find_port(const char* name)
{
TRACE(("find_port(name = \"%s\")\n", name));
if (!sPortsActive) {
panic("ports used too early!\n");
return B_NAME_NOT_FOUND;
}
if (name == NULL)
return B_BAD_VALUE;
ReadLocker locker(sPortsLock);
Port* port = sPortsByName.Lookup(name);
if (port != NULL && port->state == Port::kActive)
return port->id;
return B_NAME_NOT_FOUND;
}
status_t
_get_port_info(port_id id, port_info* info, size_t size)
{
TRACE(("get_port_info(id = %ld)\n", id));
if (info == NULL || size != sizeof(port_info))
return B_BAD_VALUE;
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL) {
TRACE(("get_port_info: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
MutexLocker locker(portRef->lock, true);
fill_port_info(portRef, info, size);
return B_OK;
}
status_t
_get_next_port_info(team_id teamID, int32* _cookie, struct port_info* info,
size_t size)
{
TRACE(("get_next_port_info(team = %ld)\n", teamID));
if (info == NULL || size != sizeof(port_info) || _cookie == NULL
|| teamID < 0) {
return B_BAD_VALUE;
}
if (!sPortsActive)
return B_BAD_PORT_ID;
Team* team = Team::Get(teamID);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
const uint8 lockIndex = teamID % kTeamListLockCount;
MutexLocker teamPortsListLocker(sTeamListLock[lockIndex]);
int32 stopIndex = *_cookie;
int32 index = 0;
Port* port = (Port*)list_get_first_item(&team->port_list);
while (port != NULL) {
if (!is_port_closed(port)) {
if (index == stopIndex)
break;
index++;
}
port = (Port*)list_get_next_item(&team->port_list, port);
}
if (port == NULL)
return B_BAD_PORT_ID;
BReference<Port> portRef = port;
teamPortsListLocker.Unlock();
MutexLocker locker(portRef->lock);
fill_port_info(portRef, info, size);
*_cookie = stopIndex + 1;
return B_OK;
}
ssize_t
port_buffer_size(port_id id)
{
return port_buffer_size_etc(id, 0, 0);
}
ssize_t
port_buffer_size_etc(port_id id, uint32 flags, bigtime_t timeout)
{
port_message_info info;
status_t error = get_port_message_info_etc(id, &info, flags, timeout);
return error != B_OK ? error : info.size;
}
status_t
_get_port_message_info_etc(port_id id, port_message_info* info,
size_t infoSize, uint32 flags, bigtime_t timeout)
{
if (info == NULL || infoSize != sizeof(port_message_info))
return B_BAD_VALUE;
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
flags &= B_CAN_INTERRUPT | B_KILL_CAN_INTERRUPT | B_RELATIVE_TIMEOUT
| B_ABSOLUTE_TIMEOUT;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL)
return B_BAD_PORT_ID;
MutexLocker locker(portRef->lock, true);
if (is_port_closed(portRef) && portRef->messages.IsEmpty()) {
T(Info(portRef, 0, B_BAD_PORT_ID));
TRACE(("_get_port_message_info_etc(): closed port %ld\n", id));
return B_BAD_PORT_ID;
}
while (portRef->read_count == 0) {
if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0)
return B_WOULD_BLOCK;
ConditionVariableEntry entry;
portRef->read_condition.Add(&entry);
locker.Unlock();
status_t status = entry.Wait(flags, timeout);
if (status != B_OK) {
T(Info(portRef, 0, status));
return status;
}
BReference<Port> newPortRef = get_locked_port(id);
if (newPortRef == NULL) {
T(Info(id, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
locker.SetTo(newPortRef->lock, true);
if (newPortRef != portRef
|| (is_port_closed(portRef) && portRef->messages.IsEmpty())) {
T(Info(id, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
}
port_message* message = portRef->messages.Head();
if (message == NULL) {
panic("port %" B_PRId32 ": no messages found\n", portRef->id);
return B_ERROR;
}
info->size = message->size;
info->sender = message->sender;
info->sender_group = message->sender_group;
info->sender_team = message->sender_team;
T(Info(portRef, message->code, B_OK));
portRef->read_condition.NotifyOne();
return B_OK;
}
ssize_t
port_count(port_id id)
{
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL) {
TRACE(("port_count: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
MutexLocker locker(portRef->lock, true);
return portRef->read_count;
}
ssize_t
read_port(port_id port, int32* msgCode, void* buffer, size_t bufferSize)
{
return read_port_etc(port, msgCode, buffer, bufferSize, 0, 0);
}
ssize_t
read_port_etc(port_id id, int32* _code, void* buffer, size_t bufferSize,
uint32 flags, bigtime_t timeout)
{
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
if ((buffer == NULL && bufferSize > 0) || timeout < 0)
return B_BAD_VALUE;
bool userCopy = (flags & PORT_FLAG_USE_USER_MEMCPY) != 0;
bool peekOnly = !userCopy && (flags & B_PEEK_PORT_MESSAGE) != 0;
flags &= B_CAN_INTERRUPT | B_KILL_CAN_INTERRUPT | B_RELATIVE_TIMEOUT
| B_ABSOLUTE_TIMEOUT;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL)
return B_BAD_PORT_ID;
MutexLocker locker(portRef->lock, true);
if (is_port_closed(portRef) && portRef->messages.IsEmpty()) {
T(Read(portRef, 0, B_BAD_PORT_ID));
TRACE(("read_port_etc(): closed port %ld\n", id));
return B_BAD_PORT_ID;
}
while (portRef->read_count == 0) {
if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0)
return B_WOULD_BLOCK;
ConditionVariableEntry entry;
portRef->read_condition.Add(&entry);
locker.Unlock();
status_t status = entry.Wait(flags, timeout);
BReference<Port> newPortRef = get_locked_port(id);
if (newPortRef == NULL) {
T(Read(id, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
locker.SetTo(newPortRef->lock, true);
if (newPortRef != portRef
|| (is_port_closed(portRef) && portRef->messages.IsEmpty())) {
T(Read(id, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
if (status != B_OK) {
T(Read(portRef, 0, status));
return status;
}
}
port_message* message = portRef->messages.Head();
if (message == NULL) {
panic("port %" B_PRId32 ": no messages found\n", portRef->id);
return B_ERROR;
}
if (peekOnly) {
size_t size = copy_port_message(message, _code, buffer, bufferSize,
userCopy);
T(Read(portRef, message->code, size));
portRef->read_condition.NotifyOne();
return size;
}
portRef->messages.RemoveHead();
portRef->total_count++;
portRef->write_count++;
portRef->read_count--;
notify_port_select_events(portRef, B_EVENT_WRITE);
portRef->write_condition.NotifyOne();
T(Read(portRef, message->code, std::min(bufferSize, message->size)));
locker.Unlock();
size_t size = copy_port_message(message, _code, buffer, bufferSize,
userCopy);
put_port_message(message);
return size;
}
status_t
write_port(port_id id, int32 msgCode, const void* buffer, size_t bufferSize)
{
iovec vec = { (void*)buffer, bufferSize };
return writev_port_etc(id, msgCode, &vec, 1, bufferSize, 0, 0);
}
status_t
write_port_etc(port_id id, int32 msgCode, const void* buffer,
size_t bufferSize, uint32 flags, bigtime_t timeout)
{
iovec vec = { (void*)buffer, bufferSize };
return writev_port_etc(id, msgCode, &vec, 1, bufferSize, flags, timeout);
}
status_t
writev_port_etc(port_id id, int32 msgCode, const iovec* msgVecs,
size_t vecCount, size_t bufferSize, uint32 flags, bigtime_t timeout)
{
if (!sPortsActive || id < 0)
return B_BAD_PORT_ID;
if (bufferSize > PORT_MAX_MESSAGE_SIZE)
return B_BAD_VALUE;
bool userCopy = (flags & PORT_FLAG_USE_USER_MEMCPY) != 0;
flags &= B_CAN_INTERRUPT | B_KILL_CAN_INTERRUPT | B_RELATIVE_TIMEOUT
| B_ABSOLUTE_TIMEOUT;
if ((flags & B_RELATIVE_TIMEOUT) != 0
&& timeout != B_INFINITE_TIMEOUT && timeout > 0) {
flags = (flags & ~B_RELATIVE_TIMEOUT) | B_ABSOLUTE_TIMEOUT;
timeout += system_time();
}
status_t status;
port_message* message = NULL;
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL) {
TRACE(("write_port_etc: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
MutexLocker locker(portRef->lock, true);
if (is_port_closed(portRef)) {
TRACE(("write_port_etc: port %ld closed\n", id));
return B_BAD_PORT_ID;
}
if (portRef->write_count <= 0) {
if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0)
return B_WOULD_BLOCK;
portRef->write_count--;
ConditionVariableEntry entry;
portRef->write_condition.Add(&entry);
locker.Unlock();
status = entry.Wait(flags, timeout);
BReference<Port> newPortRef = get_locked_port(id);
if (newPortRef == NULL) {
T(Write(id, 0, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
locker.SetTo(newPortRef->lock, true);
if (newPortRef != portRef || is_port_closed(portRef)) {
T(Write(id, 0, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
if (status != B_OK)
goto error;
} else
portRef->write_count--;
status = get_port_message(msgCode, bufferSize, flags, timeout,
&message, *portRef);
if (status != B_OK) {
if (status == B_BAD_PORT_ID) {
T(Write(id, 0, 0, 0, 0, B_BAD_PORT_ID));
return B_BAD_PORT_ID;
}
goto error;
}
message->sender = geteuid();
message->sender_group = getegid();
message->sender_team = team_get_current_team_id();
if (bufferSize > 0) {
size_t offset = 0;
for (uint32 i = 0; i < vecCount; i++) {
size_t bytes = msgVecs[i].iov_len;
if (bytes > bufferSize)
bytes = bufferSize;
if (userCopy) {
status_t status = user_memcpy(message->buffer + offset,
msgVecs[i].iov_base, bytes);
if (status != B_OK) {
put_port_message(message);
goto error;
}
} else
memcpy(message->buffer + offset, msgVecs[i].iov_base, bytes);
bufferSize -= bytes;
if (bufferSize == 0)
break;
offset += bytes;
}
}
portRef->messages.Add(message);
portRef->read_count++;
T(Write(id, portRef->read_count, portRef->write_count, message->code,
message->size, B_OK));
notify_port_select_events(portRef, B_EVENT_READ);
portRef->read_condition.NotifyOne();
return B_OK;
error:
T(Write(id, portRef->read_count, portRef->write_count, 0, 0, status));
portRef->write_count++;
notify_port_select_events(portRef, B_EVENT_WRITE);
portRef->write_condition.NotifyOne();
return status;
}
status_t
set_port_owner(port_id id, team_id newTeamID)
{
TRACE(("set_port_owner(id = %ld, team = %ld)\n", id, newTeamID));
if (id < 0)
return B_BAD_PORT_ID;
Team* team = Team::Get(newTeamID);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
BReference<Port> portRef = get_locked_port(id);
if (portRef == NULL) {
TRACE(("set_port_owner: invalid port_id %ld\n", id));
return B_BAD_PORT_ID;
}
MutexLocker locker(portRef->lock, true);
if (team->id != portRef->owner) {
uint8 firstLockIndex = portRef->owner % kTeamListLockCount;
uint8 secondLockIndex = team->id % kTeamListLockCount;
if (secondLockIndex < firstLockIndex) {
uint8 temp = secondLockIndex;
secondLockIndex = firstLockIndex;
firstLockIndex = temp;
}
MutexLocker oldTeamPortsListLocker(sTeamListLock[firstLockIndex]);
MutexLocker newTeamPortsListLocker;
if (firstLockIndex != secondLockIndex) {
newTeamPortsListLocker.SetTo(sTeamListLock[secondLockIndex],
false);
}
if (portRef->state == Port::kActive) {
list_remove_link(&portRef->team_link);
list_add_item(&team->port_list, portRef.Get());
portRef->owner = team->id;
} else {
return B_BAD_PORT_ID;
}
}
T(OwnerChange(portRef, team->id, B_OK));
return B_OK;
}
port_id
_user_create_port(int32 queueLength, const char *userName)
{
char name[B_OS_NAME_LENGTH];
if (userName == NULL)
return create_port(queueLength, NULL);
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return create_port(queueLength, name);
}
status_t
_user_close_port(port_id id)
{
return close_port(id);
}
status_t
_user_delete_port(port_id id)
{
return delete_port(id);
}
port_id
_user_find_port(const char *userName)
{
char name[B_OS_NAME_LENGTH];
if (userName == NULL)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return find_port(name);
}
status_t
_user_get_port_info(port_id id, struct port_info *userInfo)
{
struct port_info info;
status_t status;
if (userInfo == NULL)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userInfo))
return B_BAD_ADDRESS;
status = get_port_info(id, &info);
if (status == B_OK
&& user_memcpy(userInfo, &info, sizeof(struct port_info)) < B_OK)
return B_BAD_ADDRESS;
return status;
}
status_t
_user_get_next_port_info(team_id team, int32 *userCookie,
struct port_info *userInfo)
{
struct port_info info;
status_t status;
int32 cookie;
if (userCookie == NULL || userInfo == NULL)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userCookie) || !IS_USER_ADDRESS(userInfo)
|| user_memcpy(&cookie, userCookie, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
status = get_next_port_info(team, &cookie, &info);
if (user_memcpy(userCookie, &cookie, sizeof(int32)) < B_OK
|| (status == B_OK && user_memcpy(userInfo, &info,
sizeof(struct port_info)) < B_OK))
return B_BAD_ADDRESS;
return status;
}
ssize_t
_user_port_buffer_size_etc(port_id port, uint32 flags, bigtime_t timeout)
{
syscall_restart_handle_timeout_pre(flags, timeout);
status_t status = port_buffer_size_etc(port, flags | B_CAN_INTERRUPT,
timeout);
return syscall_restart_handle_timeout_post(status, timeout);
}
ssize_t
_user_port_count(port_id port)
{
return port_count(port);
}
status_t
_user_set_port_owner(port_id port, team_id team)
{
return set_port_owner(port, team);
}
ssize_t
_user_read_port_etc(port_id port, int32 *userCode, void *userBuffer,
size_t bufferSize, uint32 flags, bigtime_t timeout)
{
int32 messageCode;
ssize_t bytesRead;
syscall_restart_handle_timeout_pre(flags, timeout);
if (userBuffer == NULL && bufferSize != 0)
return B_BAD_VALUE;
if ((userCode != NULL && !IS_USER_ADDRESS(userCode))
|| (userBuffer != NULL && !IS_USER_ADDRESS(userBuffer)))
return B_BAD_ADDRESS;
bytesRead = read_port_etc(port, &messageCode, userBuffer, bufferSize,
flags | PORT_FLAG_USE_USER_MEMCPY | B_CAN_INTERRUPT, timeout);
if (bytesRead >= 0 && userCode != NULL
&& user_memcpy(userCode, &messageCode, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
return syscall_restart_handle_timeout_post(bytesRead, timeout);
}
status_t
_user_write_port_etc(port_id port, int32 messageCode, const void *userBuffer,
size_t bufferSize, uint32 flags, bigtime_t timeout)
{
iovec vec = { (void *)userBuffer, bufferSize };
syscall_restart_handle_timeout_pre(flags, timeout);
if (userBuffer == NULL && bufferSize != 0)
return B_BAD_VALUE;
if (userBuffer != NULL && !IS_USER_ADDRESS(userBuffer))
return B_BAD_ADDRESS;
status_t status = writev_port_etc(port, messageCode, &vec, 1, bufferSize,
flags | PORT_FLAG_USE_USER_MEMCPY | B_CAN_INTERRUPT, timeout);
return syscall_restart_handle_timeout_post(status, timeout);
}
status_t
_user_writev_port_etc(port_id port, int32 messageCode, const iovec *userVecs,
size_t vecCount, size_t bufferSize, uint32 flags, bigtime_t timeout)
{
syscall_restart_handle_timeout_pre(flags, timeout);
if (userVecs == NULL && bufferSize != 0)
return B_BAD_VALUE;
if (vecCount > IOV_MAX)
return B_BAD_VALUE;
BStackOrHeapArray<iovec, 16> vecs(vecCount);
if (!vecs.IsValid())
return B_NO_MEMORY;
if (userVecs != NULL && vecCount != 0) {
status_t status = get_iovecs_from_user(userVecs, vecCount, vecs);
if (status != B_OK)
return status;
}
status_t status = writev_port_etc(port, messageCode, vecs, vecCount,
bufferSize, flags | PORT_FLAG_USE_USER_MEMCPY | B_CAN_INTERRUPT,
timeout);
return syscall_restart_handle_timeout_post(status, timeout);
}
status_t
_user_get_port_message_info_etc(port_id port, port_message_info *userInfo,
size_t infoSize, uint32 flags, bigtime_t timeout)
{
if (userInfo == NULL || infoSize != sizeof(port_message_info))
return B_BAD_VALUE;
syscall_restart_handle_timeout_pre(flags, timeout);
port_message_info info;
status_t error = _get_port_message_info_etc(port, &info, sizeof(info),
flags | B_CAN_INTERRUPT, timeout);
if (error == B_OK && (!IS_USER_ADDRESS(userInfo)
|| user_memcpy(userInfo, &info, sizeof(info)) != B_OK)) {
error = B_BAD_ADDRESS;
}
return syscall_restart_handle_timeout_post(error, timeout);
}