* Copyright 2014, Paweł Dziepak, pdziepak@quarnos.org.
* Copyright 2008-2016, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include <team.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <OS.h>
#include <AutoDeleter.h>
#include <FindDirectory.h>
#include <extended_system_info_defs.h>
#include <commpage.h>
#include <boot_device.h>
#include <elf.h>
#include <file_cache.h>
#include <find_directory_private.h>
#include <fs/KPath.h>
#include <heap.h>
#include <interrupts.h>
#include <kernel.h>
#include <kimage.h>
#include <kscheduler.h>
#include <ksignal.h>
#include <Notifications.h>
#include <port.h>
#include <posix/realtime_sem.h>
#include <posix/xsi_semaphore.h>
#include <safemode.h>
#include <sem.h>
#include <syscall_process_info.h>
#include <syscall_load_image.h>
#include <syscall_restart.h>
#include <syscalls.h>
#include <tls.h>
#include <tracing.h>
#include <user_mutex.h>
#include <user_runtime.h>
#include <user_thread.h>
#include <usergroup.h>
#include <vfs.h>
#include <vm/vm.h>
#include <vm/VMAddressSpace.h>
#include <util/AutoLock.h>
#include <util/ThreadAutoLock.h>
#include "TeamThreadTables.h"
#ifdef TRACE_TEAM
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
struct team_key {
team_id id;
};
struct team_arg {
char *path;
char **flat_args;
size_t flat_args_size;
uint32 arg_count;
uint32 env_count;
mode_t umask;
uint32 flags;
port_id error_port;
uint32 error_token;
};
#define TEAM_ARGS_FLAG_NO_ASLR 0x01
namespace {
class TeamNotificationService : public DefaultNotificationService {
public:
TeamNotificationService();
void Notify(uint32 eventCode, Team* team);
};
typedef BKernel::TeamThreadTable<Team> TeamTable;
struct ProcessGroupHashDefinition {
typedef pid_t KeyType;
typedef ProcessGroup ValueType;
size_t HashKey(pid_t key) const
{
return key;
}
size_t Hash(ProcessGroup* value) const
{
return HashKey(value->id);
}
bool Compare(pid_t key, ProcessGroup* value) const
{
return value->id == key;
}
ProcessGroup*& GetLink(ProcessGroup* value) const
{
return value->hash_next;
}
};
typedef BOpenHashTable<ProcessGroupHashDefinition> ProcessGroupHashTable;
}
static TeamTable sTeamHash;
static rw_spinlock sTeamHashLock = B_RW_SPINLOCK_INITIALIZER;
static ProcessGroupHashTable sGroupHash;
static spinlock sGroupHashLock = B_SPINLOCK_INITIALIZER;
static Team* sKernelTeam = NULL;
static bool sDisableUserAddOns = false;
static ProcessGroupList sOrphanedCheckProcessGroups;
static mutex sOrphanedCheckLock
= MUTEX_INITIALIZER("orphaned process group check");
static int32 sMaxTeams = 2048;
static int32 sUsedTeams = 1;
static TeamNotificationService sNotificationService;
static const size_t kTeamUserDataReservedSize = 128 * B_PAGE_SIZE;
static const size_t kTeamUserDataInitialSize = 4 * B_PAGE_SIZE;
TeamListIterator::TeamListIterator()
{
InterruptsWriteSpinLocker locker(sTeamHashLock);
sTeamHash.InsertIteratorEntry(&fEntry);
}
TeamListIterator::~TeamListIterator()
{
InterruptsWriteSpinLocker locker(sTeamHashLock);
sTeamHash.RemoveIteratorEntry(&fEntry);
}
Team*
TeamListIterator::Next()
{
InterruptsWriteSpinLocker locker(sTeamHashLock);
Team* team = sTeamHash.NextElement(&fEntry);
if (team != NULL)
team->AcquireReference();
return team;
}
#if TEAM_TRACING
namespace TeamTracing {
class TeamForked : public AbstractTraceEntry {
public:
TeamForked(thread_id forkedThread)
:
fForkedThread(forkedThread)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("team forked, new thread %" B_PRId32, fForkedThread);
}
private:
thread_id fForkedThread;
};
class ExecTeam : public AbstractTraceEntry {
public:
ExecTeam(const char* path, int32 argCount, const char* const* args,
int32 envCount, const char* const* env)
:
fArgCount(argCount),
fArgs(NULL)
{
fPath = alloc_tracing_buffer_strcpy(path, B_PATH_NAME_LENGTH,
false);
size_t argBufferSize = 0;
for (int32 i = 0; i < argCount; i++)
argBufferSize += strlen(args[i]) + 1;
fArgs = (char*)alloc_tracing_buffer(argBufferSize);
if (fArgs) {
char* buffer = fArgs;
for (int32 i = 0; i < argCount; i++) {
size_t argSize = strlen(args[i]) + 1;
memcpy(buffer, args[i], argSize);
buffer += argSize;
}
}
(void)envCount;
(void)env;
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("team exec, \"%p\", args:", fPath);
if (fArgs != NULL) {
char* args = fArgs;
for (int32 i = 0; !out.IsFull() && i < fArgCount; i++) {
out.Print(" \"%s\"", args);
args += strlen(args) + 1;
}
} else
out.Print(" <too long>");
}
private:
char* fPath;
int32 fArgCount;
char* fArgs;
};
static const char*
job_control_state_name(job_control_state state)
{
switch (state) {
case JOB_CONTROL_STATE_NONE:
return "none";
case JOB_CONTROL_STATE_STOPPED:
return "stopped";
case JOB_CONTROL_STATE_CONTINUED:
return "continued";
case JOB_CONTROL_STATE_DEAD:
return "dead";
default:
return "invalid";
}
}
class SetJobControlState : public AbstractTraceEntry {
public:
SetJobControlState(team_id team, job_control_state newState, Signal* signal)
:
fTeam(team),
fNewState(newState),
fSignal(signal != NULL ? signal->Number() : 0)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("team set job control state, team %" B_PRId32 ", "
"new state: %s, signal: %d",
fTeam, job_control_state_name(fNewState), fSignal);
}
private:
team_id fTeam;
job_control_state fNewState;
int fSignal;
};
class WaitForChild : public AbstractTraceEntry {
public:
WaitForChild(pid_t child, uint32 flags)
:
fChild(child),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("team wait for child, child: %" B_PRId32 ", "
"flags: %#" B_PRIx32, fChild, fFlags);
}
private:
pid_t fChild;
uint32 fFlags;
};
class WaitForChildDone : public AbstractTraceEntry {
public:
WaitForChildDone(const job_control_entry& entry)
:
fState(entry.state),
fTeam(entry.thread),
fStatus(entry.status),
fReason(entry.reason),
fSignal(entry.signal)
{
Initialized();
}
WaitForChildDone(status_t error)
:
fTeam(error)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
if (fTeam >= 0) {
out.Print("team wait for child done, team: %" B_PRId32 ", "
"state: %s, status: %#" B_PRIx32 ", reason: %#x, signal: %d\n",
fTeam, job_control_state_name(fState), fStatus, fReason,
fSignal);
} else {
out.Print("team wait for child failed, error: "
"%#" B_PRIx32 ", ", fTeam);
}
}
private:
job_control_state fState;
team_id fTeam;
status_t fStatus;
uint16 fReason;
uint16 fSignal;
};
}
# define T(x) new(std::nothrow) TeamTracing::x;
#else
# define T(x) ;
#endif
TeamNotificationService::TeamNotificationService()
: DefaultNotificationService("teams")
{
}
void
TeamNotificationService::Notify(uint32 eventCode, Team* team)
{
char eventBuffer[128];
KMessage event;
event.SetTo(eventBuffer, sizeof(eventBuffer), TEAM_MONITOR);
event.AddInt32("event", eventCode);
event.AddInt32("team", team->id);
event.AddPointer("teamStruct", team);
DefaultNotificationService::Notify(event, eventCode);
}
Team::Team(team_id id, bool kernel)
{
this->id = id;
visible = true;
hash_next = parent = NULL;
serial_number = -1;
group_id = session_id = -1;
group = NULL;
num_threads = 0;
state = TEAM_STATE_BIRTH;
flags = 0;
io_context = NULL;
user_mutex_context = NULL;
realtime_sem_context = NULL;
xsi_sem_context = NULL;
death_entry = NULL;
dead_children.condition_variable.Init(&dead_children, "team children");
dead_children.count = 0;
dead_children.kernel_time = 0;
dead_children.user_time = 0;
job_control_entry = new(nothrow) ::job_control_entry;
if (job_control_entry != NULL) {
job_control_entry->state = JOB_CONTROL_STATE_NONE;
job_control_entry->thread = id;
job_control_entry->team = this;
}
address_space = NULL;
main_thread = NULL;
loading_info = NULL;
list_init(&watcher_list);
list_init(&sem_list);
list_init_etc(&port_list, port_team_link_offset());
user_data = 0;
user_data_area = -1;
used_user_data = 0;
user_data_size = 0;
free_user_threads = NULL;
commpage_address = NULL;
clear_team_debug_info(&debug_info, true);
dead_threads_kernel_time = 0;
dead_threads_user_time = 0;
cpu_clock_offset = 0;
B_INITIALIZE_SPINLOCK(&time_lock);
saved_set_uid = real_uid = effective_uid = -1;
saved_set_gid = real_gid = effective_gid = -1;
exit.initialized = false;
B_INITIALIZE_SPINLOCK(&signal_lock);
if (kernel) {
mutex_init(&fLock, "Team:kernel");
} else {
char lockName[16];
snprintf(lockName, sizeof(lockName), "Team:%" B_PRId32, id);
mutex_init_etc(&fLock, lockName, MUTEX_FLAG_CLONE_NAME);
}
fName[0] = '\0';
fArgs[0] = '\0';
fQueuedSignalsCounter = new(std::nothrow) BKernel::QueuedSignalsCounter(
kernel ? -1 : MAX_QUEUED_SIGNALS);
memset(fSignalActions, 0, sizeof(fSignalActions));
fUserDefinedTimerCount = 0;
fCoreDumpCondition = NULL;
}
Team::~Team()
{
PrepareForDeletion();
if (io_context != NULL)
vfs_put_io_context(io_context);
delete_owned_ports(this);
sem_delete_owned_sems(this);
DeleteUserTimers(false);
fPendingSignals.Clear();
if (fQueuedSignalsCounter != NULL)
fQueuedSignalsCounter->ReleaseReference();
while (thread_death_entry* threadDeathEntry = dead_threads.RemoveHead())
free(threadDeathEntry);
while (::job_control_entry* entry = dead_children.entries.RemoveHead())
delete entry;
while (free_user_thread* entry = free_user_threads) {
free_user_threads = entry->next;
free(entry);
}
delete job_control_entry;
mutex_destroy(&fLock);
}
Team*
Team::Create(team_id id, const char* name, bool kernel)
{
Team* team = new(std::nothrow) Team(id, kernel);
if (team == NULL)
return NULL;
ObjectDeleter<Team> teamDeleter(team);
if (name != NULL)
team->SetName(name);
if (team->job_control_entry == NULL || team->fQueuedSignalsCounter == NULL)
return NULL;
if (arch_team_init_team_struct(team, kernel) != B_OK)
return NULL;
if (!kernel) {
status_t error = user_timer_create_team_timers(team);
if (error != B_OK)
return NULL;
}
team->start_time = system_time();
return teamDeleter.Detach();
}
Returns a reference to the team.
Team and thread spinlock must not be held.
*/
Team*
Team::Get(team_id id)
{
if (id == B_CURRENT_TEAM) {
Team* team = thread_get_current_thread()->team;
team->AcquireReference();
return team;
}
InterruptsReadSpinLocker locker(sTeamHashLock);
Team* team = sTeamHash.Lookup(id);
if (team != NULL)
team->AcquireReference();
return team;
}
Returns a reference to the team.
Team and thread spinlock must not be held.
*/
Team*
Team::GetAndLock(team_id id)
{
Team* team = Get(id);
if (team == NULL)
return NULL;
team->Lock();
if (team->state >= TEAM_STATE_SHUTDOWN) {
team->Unlock();
team->ReleaseReference();
return NULL;
}
return team;
}
The caller must hold a reference to the team or otherwise make sure that
it won't be deleted.
If the team doesn't have a parent, only the team itself is locked. If the
team's parent is the kernel team and \a dontLockParentIfKernel is \c true,
only the team itself is locked.
\param dontLockParentIfKernel If \c true, the team's parent team is only
locked, if it is not the kernel team.
*/
void
Team::LockTeamAndParent(bool dontLockParentIfKernel)
{
Lock();
while (true) {
Team* parent = this->parent;
if (parent == NULL || (dontLockParentIfKernel && parent == sKernelTeam)
|| parent->TryLock()) {
return;
}
BReference<Team> parentReference(parent);
Unlock();
parent->Lock();
Lock();
if (this->parent == parent)
return;
parent->Unlock();
}
}
*/
void
Team::UnlockTeamAndParent()
{
if (parent != NULL)
parent->Unlock();
Unlock();
}
The caller must hold a reference to the team or otherwise make sure that
it won't be deleted.
If the team doesn't have a parent, only the team itself is locked.
*/
void
Team::LockTeamParentAndProcessGroup()
{
LockTeamAndProcessGroup();
if (this->parent == NULL || this->parent->TryLock())
return;
Unlock();
LockTeamAndParent(false);
}
*/
void
Team::UnlockTeamParentAndProcessGroup()
{
group->Unlock();
if (parent != NULL)
parent->Unlock();
Unlock();
}
void
Team::LockTeamAndProcessGroup()
{
Lock();
while (true) {
ProcessGroup* group = this->group;
if (group == NULL || group->TryLock())
return;
BReference<ProcessGroup> groupReference(group);
Unlock();
group->Lock();
Lock();
if (this->group == group)
return;
group->Unlock();
}
}
void
Team::UnlockTeamAndProcessGroup()
{
group->Unlock();
Unlock();
}
void
Team::SetName(const char* name)
{
if (const char* lastSlash = strrchr(name, '/'))
name = lastSlash + 1;
strlcpy(fName, name, B_OS_NAME_LENGTH);
}
void
Team::SetArgs(const char* args)
{
strlcpy(fArgs, args, sizeof(fArgs));
}
void
Team::SetArgs(const char* path, const char* const* otherArgs, int otherArgCount)
{
fArgs[0] = '\0';
strlcpy(fArgs, path, sizeof(fArgs));
for (int i = 0; i < otherArgCount; i++) {
strlcat(fArgs, " ", sizeof(fArgs));
strlcat(fArgs, otherArgs[i], sizeof(fArgs));
}
}
void
Team::ResetSignalsOnExec()
{
for (uint32 i = 1; i <= MAX_SIGNAL_NUMBER; i++) {
struct sigaction& action = SignalActionFor(i);
if (action.sa_handler != SIG_IGN && action.sa_handler != SIG_DFL)
action.sa_handler = SIG_DFL;
action.sa_mask = 0;
action.sa_flags = 0;
action.sa_userdata = NULL;
}
}
void
Team::InheritSignalActions(Team* parent)
{
memcpy(fSignalActions, parent->fSignalActions, sizeof(fSignalActions));
}
ID.
The caller must hold the team's lock.
\param timer The timer to be added. If it doesn't have an ID yet, it is
considered user-defined and will be assigned an ID.
\return \c B_OK, if the timer was added successfully, another error code
otherwise.
*/
status_t
Team::AddUserTimer(UserTimer* timer)
{
if (state >= TEAM_STATE_SHUTDOWN)
return B_BAD_TEAM_ID;
if (timer->ID() < 0 && !CheckAddUserDefinedTimer())
return EAGAIN;
fUserTimers.AddTimer(timer);
return B_OK;
}
The caller must hold the team's lock.
\param timer The timer to be removed.
*/
void
Team::RemoveUserTimer(UserTimer* timer)
{
fUserTimers.RemoveTimer(timer);
if (timer->ID() >= USER_TIMER_FIRST_USER_DEFINED_ID)
UserDefinedTimersRemoved(1);
}
Timer's belonging to the team's threads are not affected.
The caller must hold the team's lock.
\param userDefinedOnly If \c true, only the user-defined timers are deleted,
otherwise all timers are deleted.
*/
void
Team::DeleteUserTimers(bool userDefinedOnly)
{
int32 count = fUserTimers.DeleteTimers(userDefinedOnly);
UserDefinedTimersRemoved(count);
}
\return \c true, if the limit wasn't reached yet, \c false otherwise.
*/
bool
Team::CheckAddUserDefinedTimer()
{
int32 oldCount = atomic_add(&fUserDefinedTimerCount, 1);
if (oldCount >= MAX_USER_TIMERS_PER_TEAM) {
atomic_add(&fUserDefinedTimerCount, -1);
return false;
}
return true;
}
\param count The count to subtract.
*/
void
Team::UserDefinedTimersRemoved(int32 count)
{
atomic_add(&fUserDefinedTimerCount, -count);
}
void
Team::DeactivateCPUTimeUserTimers()
{
while (TeamTimeUserTimer* timer = fCPUTimeUserTimers.Head())
timer->Deactivate();
while (TeamUserTimeUserTimer* timer = fUserTimeUserTimers.Head())
timer->Deactivate();
}
The caller must hold \c time_lock.
\param ignoreCurrentRun If \c true and the current thread is one team's
threads, don't add the time since the last time \c last_time was
updated. Should be used in "thread unscheduled" scheduler callbacks,
since although the thread is still running at that time, its time has
already been stopped.
\return The team's current total CPU time.
*/
bigtime_t
Team::CPUTime(bool ignoreCurrentRun, Thread* lockedThread) const
{
bigtime_t time = cpu_clock_offset + dead_threads_kernel_time
+ dead_threads_user_time;
Thread* currentThread = thread_get_current_thread();
bigtime_t now = system_time();
for (Thread* thread = thread_list.First(); thread != NULL;
thread = thread_list.GetNext(thread)) {
bool alreadyLocked = thread == lockedThread;
SpinLocker threadTimeLocker(thread->time_lock, alreadyLocked);
time += thread->kernel_time + thread->user_time;
if (thread->last_time != 0) {
if (!ignoreCurrentRun || thread != currentThread)
time += now - thread->last_time;
}
if (alreadyLocked)
threadTimeLocker.Detach();
}
return time;
}
The caller must hold \c time_lock.
\return The team's current user CPU time.
*/
bigtime_t
Team::UserCPUTime() const
{
bigtime_t time = dead_threads_user_time;
bigtime_t now = system_time();
for (Thread* thread = thread_list.First(); thread != NULL;
thread = thread_list.GetNext(thread)) {
SpinLocker threadTimeLocker(thread->time_lock);
time += thread->user_time;
if (thread->last_time != 0 && !thread->in_kernel)
time += now - thread->last_time;
}
return time;
}
ProcessGroup::ProcessGroup(pid_t id)
:
id(id),
fSession(NULL),
fInOrphanedCheckList(false)
{
char lockName[32];
snprintf(lockName, sizeof(lockName), "Group:%" B_PRId32, id);
mutex_init_etc(&fLock, lockName, MUTEX_FLAG_CLONE_NAME);
}
ProcessGroup::~ProcessGroup()
{
TRACE(("ProcessGroup::~ProcessGroup(): id = %" B_PRId32 "\n", id));
MutexLocker orphanedCheckLocker(sOrphanedCheckLock);
if (fInOrphanedCheckList)
sOrphanedCheckProcessGroups.Remove(this);
orphanedCheckLocker.Unlock();
if (fSession != NULL) {
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
sGroupHash.RemoveUnchecked(this);
groupHashLocker.Unlock();
fSession->ReleaseReference();
}
mutex_destroy(&fLock);
}
ProcessGroup*
ProcessGroup::Get(pid_t id)
{
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
ProcessGroup* group = sGroupHash.Lookup(id);
if (group != NULL)
group->AcquireReference();
return group;
}
The caller must not hold the process group hash lock.
*/
void
ProcessGroup::Publish(ProcessSession* session)
{
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
PublishLocked(session);
}
The caller must hold the process group hash lock.
*/
void
ProcessGroup::PublishLocked(ProcessSession* session)
{
ASSERT(sGroupHash.Lookup(this->id) == NULL);
fSession = session;
fSession->AcquireReference();
sGroupHash.InsertUnchecked(this);
}
The caller must hold the group's lock.
\return \c true, if the group is orphaned, \c false otherwise.
*/
bool
ProcessGroup::IsOrphaned() const
{
bool orphaned = true;
Team* team = teams.First();
while (orphaned && team != NULL) {
team->LockTeamAndParent(false);
Team* parent = team->parent;
if (parent != NULL && parent->group_id != id
&& parent->session_id == fSession->id) {
orphaned = false;
}
team->UnlockTeamAndParent();
team = teams.GetNext(team);
}
return orphaned;
}
void
ProcessGroup::ScheduleOrphanedCheck()
{
MutexLocker orphanedCheckLocker(sOrphanedCheckLock);
if (!fInOrphanedCheckList) {
sOrphanedCheckProcessGroups.Add(this);
fInOrphanedCheckList = true;
}
}
void
ProcessGroup::UnsetOrphanedCheck()
{
fInOrphanedCheckList = false;
}
ProcessSession::ProcessSession(pid_t id)
:
id(id),
controlling_tty(NULL),
foreground_group(-1)
{
char lockName[32];
snprintf(lockName, sizeof(lockName), "Session:%" B_PRId32, id);
mutex_init_etc(&fLock, lockName, MUTEX_FLAG_CLONE_NAME);
}
ProcessSession::~ProcessSession()
{
mutex_destroy(&fLock);
}
static void
_dump_team_info(Team* team)
{
kprintf("TEAM: %p\n", team);
kprintf("id: %" B_PRId32 " (%#" B_PRIx32 ")\n", team->id,
team->id);
kprintf("serial_number: %" B_PRId64 "\n", team->serial_number);
kprintf("name: '%s'\n", team->Name());
kprintf("args: '%s'\n", team->Args());
kprintf("hash_next: %p\n", team->hash_next);
kprintf("parent: %p", team->parent);
if (team->parent != NULL) {
kprintf(" (id = %" B_PRId32 ")\n", team->parent->id);
} else
kprintf("\n");
kprintf("children: %p\n", team->children.First());
kprintf("num_threads: %d\n", team->num_threads);
kprintf("state: %d\n", team->state);
kprintf("flags: 0x%" B_PRIx32 "\n", team->flags);
kprintf("io_context: %p\n", team->io_context);
if (team->address_space)
kprintf("address_space: %p\n", team->address_space);
kprintf("user data: %p (area %" B_PRId32 ")\n",
(void*)team->user_data, team->user_data_area);
kprintf("free user thread: %p\n", team->free_user_threads);
kprintf("main_thread: %p\n", team->main_thread);
kprintf("thread_list: %p\n", team->thread_list.First());
kprintf("group_id: %" B_PRId32 "\n", team->group_id);
kprintf("session_id: %" B_PRId32 "\n", team->session_id);
}
static int
dump_team_info(int argc, char** argv)
{
ulong arg;
bool found = false;
if (argc < 2) {
Thread* thread = thread_get_current_thread();
if (thread != NULL && thread->team != NULL)
_dump_team_info(thread->team);
else
kprintf("No current team!\n");
return 0;
}
arg = strtoul(argv[1], NULL, 0);
if (IS_KERNEL_ADDRESS(arg)) {
_dump_team_info((Team*)arg);
return 0;
}
for (TeamTable::Iterator it = sTeamHash.GetIterator();
Team* team = it.Next();) {
if ((team->Name() && strcmp(argv[1], team->Name()) == 0)
|| team->id == (team_id)arg) {
_dump_team_info(team);
found = true;
break;
}
}
if (!found)
kprintf("team \"%s\" (%" B_PRId32 ") doesn't exist!\n", argv[1], (team_id)arg);
return 0;
}
static int
dump_teams(int argc, char** argv)
{
kprintf("%-*s id %-*s name\n", B_PRINTF_POINTER_WIDTH, "team",
B_PRINTF_POINTER_WIDTH, "parent");
for (TeamTable::Iterator it = sTeamHash.GetIterator();
Team* team = it.Next();) {
kprintf("%p%7" B_PRId32 " %p %s\n", team, team->id, team->parent, team->Name());
}
return 0;
}
Used in the implementation of getppid (where a process can get its own
parent, only) as well as in user_process_info where the information is
available to anyone (allowing to display a tree of running processes)
*/
static pid_t
_getppid(pid_t id)
{
if (id < 0) {
errno = EINVAL;
return -1;
}
if (id == 0) {
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
if (team->parent == NULL) {
errno = EINVAL;
return -1;
}
return team->parent->id;
}
Team* team = Team::GetAndLock(id);
if (team == NULL) {
errno = ESRCH;
return -1;
}
pid_t parentID;
if (team->parent == NULL) {
errno = EINVAL;
parentID = -1;
} else
parentID = team->parent->id;
team->UnlockAndReleaseReference();
return parentID;
}
The caller must hold the lock of both \a parent and \a team.
\param parent The parent team.
\param team The team to be inserted into \a parent's child list.
*/
static void
insert_team_into_parent(Team* parent, Team* team)
{
ASSERT(parent != NULL);
parent->children.Add(team, false);
team->parent = parent;
}
The caller must hold the lock of both \a parent and \a team.
\param parent The parent team.
\param team The team to be removed from \a parent's child list.
*/
static void
remove_team_from_parent(Team* parent, Team* team)
{
parent->children.Remove(team);
team->parent = NULL;
}
The caller must hold the team's lock or its process group's lock.
*/
static bool
is_session_leader(Team* team)
{
return team->session_id == team->id;
}
The caller must hold the team's lock or its process group's lock.
*/
static bool
is_process_group_leader(Team* team)
{
return team->group_id == team->id;
}
The caller must hold the process group's lock, the team's lock, and the
team's parent's lock.
*/
static void
insert_team_into_group(ProcessGroup* group, Team* team)
{
team->group = group;
team->group_id = group->id;
team->session_id = group->Session()->id;
group->teams.Add(team, false);
group->AcquireReference();
}
The caller must hold the process group's lock, the team's lock, and the
team's parent's lock. Interrupts must be enabled.
\param team The team that'll be removed from its process group.
*/
static void
remove_team_from_group(Team* team)
{
ProcessGroup* group = team->group;
if (group == NULL)
return;
group->teams.Remove(team);
team->group = NULL;
team->group_id = -1;
group->ReleaseReference();
}
static status_t
create_team_user_data(Team* team, void* exactAddress = NULL)
{
void* address;
uint32 addressSpec;
if (exactAddress != NULL) {
address = exactAddress;
addressSpec = B_EXACT_ADDRESS;
} else {
address = (void*)KERNEL_USER_DATA_BASE;
addressSpec = B_RANDOMIZED_BASE_ADDRESS;
}
status_t result = vm_reserve_address_range(team->id, &address, addressSpec,
kTeamUserDataReservedSize, RESERVED_AVOID_BASE);
virtual_address_restrictions virtualRestrictions = {};
if (result == B_OK || exactAddress != NULL) {
if (exactAddress != NULL)
virtualRestrictions.address = exactAddress;
else
virtualRestrictions.address = address;
virtualRestrictions.address_specification = B_EXACT_ADDRESS;
} else {
virtualRestrictions.address = (void*)KERNEL_USER_DATA_BASE;
virtualRestrictions.address_specification = B_RANDOMIZED_BASE_ADDRESS;
}
physical_address_restrictions physicalRestrictions = {};
team->user_data_area = create_area_etc(team->id, "user area",
kTeamUserDataInitialSize, B_FULL_LOCK,
B_READ_AREA | B_WRITE_AREA | B_KERNEL_AREA, 0, 0,
&virtualRestrictions, &physicalRestrictions, &address);
if (team->user_data_area < 0)
return team->user_data_area;
team->user_data = (addr_t)address;
team->used_user_data = 0;
team->user_data_size = kTeamUserDataInitialSize;
team->free_user_threads = NULL;
return B_OK;
}
static void
delete_team_user_data(Team* team)
{
if (team->user_data_area >= 0) {
vm_delete_area(team->id, team->user_data_area, true);
vm_unreserve_address_range(team->id, (void*)team->user_data,
kTeamUserDataReservedSize);
team->user_data = 0;
team->used_user_data = 0;
team->user_data_size = 0;
team->user_data_area = -1;
while (free_user_thread* entry = team->free_user_threads) {
team->free_user_threads = entry->next;
free(entry);
}
}
}
static status_t
copy_user_process_args(const char* const* userFlatArgs, size_t flatArgsSize,
int32 argCount, int32 envCount, char**& _flatArgs)
{
if (argCount < 0 || envCount < 0)
return B_BAD_VALUE;
if (flatArgsSize > MAX_PROCESS_ARGS_SIZE)
return B_TOO_MANY_ARGS;
if ((argCount + envCount + 2) * sizeof(char*) > flatArgsSize)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userFlatArgs))
return B_BAD_ADDRESS;
char** flatArgs = (char**)malloc(_ALIGN(flatArgsSize));
if (flatArgs == NULL)
return B_NO_MEMORY;
if (user_memcpy(flatArgs, userFlatArgs, flatArgsSize) != B_OK) {
free(flatArgs);
return B_BAD_ADDRESS;
}
status_t error = B_OK;
const char* stringBase = (char*)flatArgs + argCount + envCount + 2;
const char* stringEnd = (char*)flatArgs + flatArgsSize;
for (int32 i = 0; i < argCount + envCount + 2; i++) {
if (i == argCount || i == argCount + envCount + 1) {
if (flatArgs[i] != NULL) {
error = B_BAD_VALUE;
break;
}
} else {
char* arg = (char*)flatArgs + (flatArgs[i] - (char*)userFlatArgs);
size_t maxLen = stringEnd - arg;
if (arg < stringBase || arg >= stringEnd
|| strnlen(arg, maxLen) == maxLen) {
error = B_BAD_VALUE;
break;
}
flatArgs[i] = arg;
}
}
if (error == B_OK)
_flatArgs = flatArgs;
else
free(flatArgs);
return error;
}
static void
free_team_arg(struct team_arg* teamArg)
{
if (teamArg != NULL) {
free(teamArg->flat_args);
free(teamArg->path);
free(teamArg);
}
}
static status_t
create_team_arg(struct team_arg** _teamArg, const char* path, char** flatArgs,
size_t flatArgsSize, int32 argCount, int32 envCount, mode_t umask,
port_id port, uint32 token)
{
struct team_arg* teamArg = (struct team_arg*)malloc(sizeof(team_arg));
if (teamArg == NULL)
return B_NO_MEMORY;
teamArg->path = strdup(path);
if (teamArg->path == NULL) {
free(teamArg);
return B_NO_MEMORY;
}
teamArg->flat_args = flatArgs;
teamArg->flat_args_size = flatArgsSize;
teamArg->arg_count = argCount;
teamArg->env_count = envCount;
teamArg->flags = 0;
teamArg->umask = umask;
teamArg->error_port = port;
teamArg->error_token = token;
const char* const* env = flatArgs + argCount + 1;
for (int32 i = 0; i < envCount; i++) {
if (strcmp(env[i], "DISABLE_ASLR=1") == 0) {
teamArg->flags |= TEAM_ARGS_FLAG_NO_ASLR;
break;
}
}
*_teamArg = teamArg;
return B_OK;
}
static status_t
team_create_thread_start_internal(void* args)
{
status_t err;
Thread* thread;
Team* team;
struct team_arg* teamArgs = (struct team_arg*)args;
const char* path;
addr_t entry;
char** userArgs;
char** userEnv;
struct user_space_program_args* programArgs;
uint32 argCount, envCount;
thread = thread_get_current_thread();
team = thread->team;
cache_node_launched(teamArgs->arg_count, teamArgs->flat_args);
TRACE(("team_create_thread_start: entry thread %" B_PRId32 "\n",
thread->id));
argCount = teamArgs->arg_count;
envCount = teamArgs->env_count;
programArgs = (struct user_space_program_args*)(thread->user_stack_base
+ thread->user_stack_size + TLS_SIZE);
userArgs = (char**)(programArgs + 1);
userEnv = userArgs + argCount + 1;
path = teamArgs->path;
if (user_strlcpy(programArgs->program_path, path,
sizeof(programArgs->program_path)) < B_OK
|| user_memcpy(&programArgs->arg_count, &argCount, sizeof(int32)) < B_OK
|| user_memcpy(&programArgs->args, &userArgs, sizeof(char**)) < B_OK
|| user_memcpy(&programArgs->env_count, &envCount, sizeof(int32)) < B_OK
|| user_memcpy(&programArgs->env, &userEnv, sizeof(char**)) < B_OK
|| user_memcpy(&programArgs->error_port, &teamArgs->error_port,
sizeof(port_id)) < B_OK
|| user_memcpy(&programArgs->error_token, &teamArgs->error_token,
sizeof(uint32)) < B_OK
|| user_memcpy(&programArgs->umask, &teamArgs->umask, sizeof(mode_t)) < B_OK
|| user_memcpy(&programArgs->disable_user_addons,
&sDisableUserAddOns, sizeof(bool)) < B_OK
|| user_memcpy(userArgs, teamArgs->flat_args,
teamArgs->flat_args_size) < B_OK) {
free_team_arg(teamArgs);
return B_BAD_ADDRESS;
}
free_team_arg(teamArgs);
TRACE(("team_create_thread_start: loading elf binary '%s'\n", path));
team->Lock();
team->state = TEAM_STATE_NORMAL;
team->Unlock();
area_id commPageArea = clone_commpage_area(team->id,
&team->commpage_address);
if (commPageArea < B_OK) {
TRACE(("team_create_thread_start: clone_commpage_area() failed: %s\n",
strerror(commPageArea)));
return commPageArea;
}
image_id commPageImage = get_commpage_image();
extended_image_info imageInfo;
err = get_image_info(commPageImage, &imageInfo.basic_info);
if (err != B_OK) {
TRACE(("team_create_thread_start: get_image_info() failed: %s\n",
strerror(err)));
return err;
}
imageInfo.basic_info.text = team->commpage_address;
imageInfo.text_delta = (ssize_t)(addr_t)team->commpage_address;
imageInfo.symbol_table = NULL;
imageInfo.symbol_hash = NULL;
imageInfo.string_table = NULL;
image_id image = register_image(team, &imageInfo, sizeof(imageInfo));
if (image < 0) {
TRACE(("team_create_thread_start: register_image() failed: %s\n",
strerror(image)));
return image;
}
user_debug_image_created(&imageInfo.basic_info);
{
KPath runtimeLoaderPath;
err = __find_directory(B_SYSTEM_DIRECTORY, gBootDevice, false,
runtimeLoaderPath.LockBuffer(), runtimeLoaderPath.BufferSize());
if (err < B_OK) {
TRACE(("team_create_thread_start: find_directory() failed: %s\n",
strerror(err)));
return err;
}
runtimeLoaderPath.UnlockBuffer();
err = runtimeLoaderPath.Append("runtime_loader");
if (err == B_OK) {
err = elf_load_user_image(runtimeLoaderPath.Path(), team, 0,
&entry);
}
}
if (err < B_OK) {
TRACE(("team_create_thread_start: elf_load_user_image() failed: "
"%s\n", strerror(err)));
return err;
}
TRACE(("team_create_thread_start: loaded elf. entry = %#lx\n", entry));
return thread_enter_userspace_new_team(thread, (addr_t)entry,
programArgs, team->commpage_address);
}
static status_t
team_create_thread_start(void* args)
{
team_create_thread_start_internal(args);
team_init_exit_info_on_error(thread_get_current_thread()->team);
thread_exit();
return B_OK;
}
static thread_id
load_image_internal(char**& _flatArgs, size_t flatArgsSize, int32 argCount,
int32 envCount, int32 priority, team_id parentID, uint32 flags,
port_id errorPort, uint32 errorToken)
{
char** flatArgs = _flatArgs;
thread_id thread;
status_t status;
struct team_arg* teamArgs;
struct team_loading_info loadingInfo;
ConditionVariableEntry loadingWaitEntry;
io_context* parentIOContext = NULL;
team_id teamID;
bool teamLimitReached = false;
if (flatArgs == NULL || argCount == 0)
return B_BAD_VALUE;
const char* path = flatArgs[0];
TRACE(("load_image_internal: name '%s', args = %p, argCount = %" B_PRId32
"\n", path, flatArgs, argCount));
const char* threadName = strrchr(path, '/');
if (threadName != NULL)
threadName++;
else
threadName = path;
Thread* mainThread;
status = Thread::Create(threadName, mainThread);
if (status != B_OK)
return status;
BReference<Thread> mainThreadReference(mainThread, true);
Team* team = Team::Create(mainThread->id, path, false);
if (team == NULL)
return B_NO_MEMORY;
BReference<Team> teamReference(team, true);
BReference<Team> teamLoadingReference;
if ((flags & B_WAIT_TILL_LOADED) != 0) {
loadingInfo.condition.Init(team, "image load");
loadingInfo.condition.Add(&loadingWaitEntry);
loadingInfo.result = B_ERROR;
team->loading_info = &loadingInfo;
teamLoadingReference = teamReference;
}
Team* parent = Team::Get(parentID);
if (parent == NULL)
return B_BAD_TEAM_ID;
BReference<Team> parentReference(parent, true);
parent->LockTeamAndProcessGroup();
team->Lock();
inherit_parent_user_and_group(team, parent);
parentIOContext = (parent->id == B_SYSTEM_TEAM) ? NULL : parent->io_context;
if (parentIOContext != NULL)
vfs_get_io_context(parentIOContext);
team->Unlock();
parent->UnlockTeamAndProcessGroup();
update_set_id_user_and_group(team, path);
status = create_team_arg(&teamArgs, path, flatArgs, flatArgsSize, argCount,
envCount, (mode_t)-1, errorPort, errorToken);
if (status != B_OK)
goto err1;
_flatArgs = NULL;
team->SetArgs(path, teamArgs->flat_args + 1, argCount - 1);
team->io_context = vfs_new_io_context(parentIOContext, true);
if (team->io_context == NULL) {
status = B_NO_MEMORY;
goto err2;
}
if (parentIOContext != NULL) {
vfs_put_io_context(parentIOContext);
parentIOContext = NULL;
}
status = VMAddressSpace::Create(team->id, USER_BASE, USER_SIZE, false,
&team->address_space);
if (status != B_OK)
goto err2;
team->address_space->SetRandomizingEnabled(
(teamArgs->flags & TEAM_ARGS_FLAG_NO_ASLR) == 0);
status = create_team_user_data(team);
if (status != B_OK)
goto err4;
parent->LockTeamAndProcessGroup();
team->Lock();
{
InterruptsWriteSpinLocker teamsLocker(sTeamHashLock);
sTeamHash.Insert(team);
teamLimitReached = sUsedTeams >= sMaxTeams;
if (!teamLimitReached)
sUsedTeams++;
}
insert_team_into_parent(parent, team);
insert_team_into_group(parent->group, team);
team->Unlock();
parent->UnlockTeamAndProcessGroup();
sNotificationService.Notify(TEAM_ADDED, team);
if (teamLimitReached) {
status = B_NO_MORE_TEAMS;
goto err6;
}
teamID = team->id;
{
ThreadCreationAttributes threadAttributes(team_create_thread_start,
threadName, B_NORMAL_PRIORITY, teamArgs, teamID, mainThread);
threadAttributes.additional_stack_size = sizeof(user_space_program_args)
+ teamArgs->flat_args_size;
thread = thread_create_thread(threadAttributes, false);
if (thread < 0) {
status = thread;
goto err6;
}
}
teamReference.Detach();
user_debug_team_created(teamID);
if ((flags & B_WAIT_TILL_LOADED) != 0) {
if (mainThread != NULL) {
thread_continue(mainThread);
}
loadingWaitEntry.Wait();
team->Lock();
ASSERT(team->loading_info == NULL);
team->Unlock();
teamLoadingReference.Unset();
if (loadingInfo.result < B_OK)
return loadingInfo.result;
}
return thread;
err6:
parent->LockTeamAndProcessGroup();
team->Lock();
remove_team_from_group(team);
remove_team_from_parent(team->parent, team);
team->Unlock();
parent->UnlockTeamAndProcessGroup();
{
InterruptsWriteSpinLocker teamsLocker(sTeamHashLock);
sTeamHash.Remove(team);
if (!teamLimitReached)
sUsedTeams--;
}
sNotificationService.Notify(TEAM_REMOVED, team);
delete_team_user_data(team);
err4:
team->address_space->Put();
err2:
free_team_arg(teamArgs);
err1:
if (parentIOContext != NULL)
vfs_put_io_context(parentIOContext);
return status;
}
*/
static void
team_kill_other_threads_locked(TeamLocker &teamLocker, thread_id threadNotBeKilled = -1)
{
Team* team = teamLocker.Get();
team_death_entry deathEntry;
deathEntry.condition.Init(team, "team death");
while (true) {
team->death_entry = &deathEntry;
deathEntry.remaining_threads = 0;
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
if (thread != team->main_thread && thread->id != threadNotBeKilled) {
Signal signal(SIGKILLTHR, SI_USER, B_OK, team->id);
send_signal_to_thread(thread, signal, B_DO_NOT_RESCHEDULE);
deathEntry.remaining_threads++;
}
}
if (deathEntry.remaining_threads == 0)
break;
ConditionVariableEntry entry;
deathEntry.condition.Add(&entry);
teamLocker.Unlock();
entry.Wait();
teamLocker.Lock();
}
team->death_entry = NULL;
}
If successful, this function does not return and will takeover ownership of
the arguments provided.
This function may only be called in a userland team (caused by one of the
exec*() syscalls).
*/
static status_t
exec_team(const char* path, char**& _flatArgs, size_t flatArgsSize,
int32 argCount, int32 envCount, mode_t umask)
{
char** flatArgs = _flatArgs;
Team* team = thread_get_current_thread()->team;
struct team_arg* teamArgs;
const char* threadName;
thread_id nubThreadID = -1;
TRACE(("exec_team(path = \"%s\", argc = %" B_PRId32 ", envCount = %"
B_PRId32 "): team %" B_PRId32 "\n", path, argCount, envCount,
team->id));
T(ExecTeam(path, argCount, flatArgs, envCount, flatArgs + argCount + 1));
if (team == team_get_kernel_team())
return B_NOT_ALLOWED;
Thread* currentThread = thread_get_current_thread();
if (currentThread != team->main_thread)
return B_NOT_ALLOWED;
TeamLocker teamLocker(team);
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED)
nubThreadID = team->debug_info.nub_thread;
debugInfoLocker.Unlock();
team_kill_other_threads_locked(teamLocker, nubThreadID);
team->DeleteUserTimers(true);
team->ResetSignalsOnExec();
teamLocker.Unlock();
status_t status = create_team_arg(&teamArgs, path, flatArgs, flatArgsSize,
argCount, envCount, umask, -1, 0);
if (status != B_OK)
return status;
_flatArgs = NULL;
team->SetArgs(path, teamArgs->flat_args + 1, argCount - 1);
thread_reset_for_exec();
user_debug_prepare_for_exec();
delete_team_user_data(team);
vm_delete_areas(team->address_space, false);
xsi_sem_undo(team);
delete_owned_ports(team);
sem_delete_owned_sems(team);
remove_images(team);
vfs_exec_io_context(team->io_context);
delete_user_mutex_context(team->user_mutex_context);
team->user_mutex_context = NULL;
delete_realtime_sem_context(team->realtime_sem_context);
team->realtime_sem_context = NULL;
team->address_space->SetRandomizingEnabled(
(teamArgs->flags & TEAM_ARGS_FLAG_NO_ASLR) == 0);
status = create_team_user_data(team);
if (status != B_OK) {
free_team_arg(teamArgs);
exit_thread(status);
return status;
}
user_debug_finish_after_exec();
team->Lock();
team->SetName(path);
team->Unlock();
threadName = strrchr(path, '/');
if (threadName != NULL)
threadName++;
else
threadName = path;
rename_thread(thread_get_current_thread_id(), threadName);
atomic_or(&team->flags, TEAM_FLAG_EXEC_DONE);
update_set_id_user_and_group(team, path);
user_debug_team_exec();
sNotificationService.Notify(TEAM_EXEC, team);
user_thread* userThread = team_allocate_user_thread(team);
ThreadLocker currentThreadLocker(currentThread);
currentThread->user_thread = userThread;
currentThreadLocker.Unlock();
status = thread_create_user_stack(currentThread->team, currentThread, NULL,
0, sizeof(user_space_program_args) + teamArgs->flat_args_size);
if (status == B_OK) {
team_create_thread_start(teamArgs);
} else
free_team_arg(teamArgs);
exit_thread(status);
return B_ERROR;
}
static thread_id
fork_team(void)
{
Thread* parentThread = thread_get_current_thread();
Team* parentTeam = parentThread->team;
Team* team;
arch_fork_arg* forkArgs;
struct area_info info;
thread_id threadID;
status_t status;
ssize_t areaCookie;
bool teamLimitReached = false;
TRACE(("fork_team(): team %" B_PRId32 "\n", parentTeam->id));
if (parentTeam == team_get_kernel_team())
return B_NOT_ALLOWED;
Thread* thread;
status = Thread::Create(parentThread->name, thread);
if (status != B_OK)
return status;
BReference<Thread> threadReference(thread, true);
team = Team::Create(thread->id, NULL, false);
if (team == NULL)
return B_NO_MEMORY;
parentTeam->LockTeamAndProcessGroup();
team->Lock();
team->SetName(parentTeam->Name());
team->SetArgs(parentTeam->Args());
team->commpage_address = parentTeam->commpage_address;
inherit_parent_user_and_group(team, parentTeam);
team->InheritSignalActions(parentTeam);
team->Unlock();
parentTeam->UnlockTeamAndProcessGroup();
team->debug_info.flags |= atomic_get(&parentTeam->debug_info.flags)
& B_TEAM_DEBUG_INHERITED_FLAGS;
forkArgs = (arch_fork_arg*)malloc(sizeof(arch_fork_arg));
if (forkArgs == NULL) {
status = B_NO_MEMORY;
goto err1;
}
team->io_context = vfs_new_io_context(parentTeam->io_context, false);
if (!team->io_context) {
status = B_NO_MEMORY;
goto err2;
}
if (parentTeam->realtime_sem_context) {
team->realtime_sem_context = clone_realtime_sem_context(
parentTeam->realtime_sem_context);
if (team->realtime_sem_context == NULL) {
status = B_NO_MEMORY;
goto err2;
}
}
status = VMAddressSpace::Create(team->id, USER_BASE, USER_SIZE, false,
&team->address_space);
if (status < B_OK)
goto err3;
areaCookie = 0;
while (get_next_area_info(B_CURRENT_TEAM, &areaCookie, &info) == B_OK) {
if (info.area == parentTeam->user_data_area) {
status = create_team_user_data(team, info.address);
if (status != B_OK)
break;
thread->user_thread = team_allocate_user_thread(team);
} else {
void* address;
area_id area = vm_copy_area(team->address_space->ID(), info.name,
&address, B_CLONE_ADDRESS, info.area);
if (area < B_OK) {
status = area;
break;
}
if (info.area == parentThread->user_stack_area)
thread->user_stack_area = area;
}
}
if (status < B_OK)
goto err4;
if (thread->user_thread == NULL) {
#if KDEBUG
panic("user data area not found, parent area is %" B_PRId32,
parentTeam->user_data_area);
#endif
status = B_ERROR;
goto err4;
}
thread->user_stack_base = parentThread->user_stack_base;
thread->user_stack_size = parentThread->user_stack_size;
thread->user_local_storage = parentThread->user_local_storage;
thread->sig_block_mask = parentThread->sig_block_mask;
thread->signal_stack_base = parentThread->signal_stack_base;
thread->signal_stack_size = parentThread->signal_stack_size;
thread->signal_stack_enabled = parentThread->signal_stack_enabled;
arch_store_fork_frame(forkArgs);
if (copy_images(parentTeam->id, team) != B_OK)
goto err5;
parentTeam->LockTeamAndProcessGroup();
team->Lock();
{
InterruptsWriteSpinLocker teamsLocker(sTeamHashLock);
sTeamHash.Insert(team);
teamLimitReached = sUsedTeams >= sMaxTeams;
if (!teamLimitReached)
sUsedTeams++;
}
insert_team_into_parent(parentTeam, team);
insert_team_into_group(parentTeam->group, team);
team->Unlock();
parentTeam->UnlockTeamAndProcessGroup();
sNotificationService.Notify(TEAM_ADDED, team);
if (teamLimitReached) {
status = B_NO_MORE_TEAMS;
goto err6;
}
{
ThreadCreationAttributes threadCreationAttributes(NULL,
parentThread->name, parentThread->priority, NULL, team->id, thread);
threadCreationAttributes.forkArgs = forkArgs;
threadCreationAttributes.flags |= THREAD_CREATION_FLAG_DEFER_SIGNALS;
threadCreationAttributes.signal_mask = thread->sig_block_mask;
threadID = thread_create_thread(threadCreationAttributes, false);
if (threadID < 0) {
status = threadID;
goto err6;
}
}
user_debug_team_created(team->id);
T(TeamForked(threadID));
resume_thread(threadID);
return threadID;
err6:
parentTeam->LockTeamAndProcessGroup();
team->Lock();
remove_team_from_group(team);
remove_team_from_parent(team->parent, team);
team->Unlock();
parentTeam->UnlockTeamAndProcessGroup();
{
InterruptsWriteSpinLocker teamsLocker(sTeamHashLock);
sTeamHash.Remove(team);
if (!teamLimitReached)
sUsedTeams--;
}
sNotificationService.Notify(TEAM_REMOVED, team);
err5:
remove_images(team);
err4:
team->address_space->RemoveAndPut();
err3:
delete_realtime_sem_context(team->realtime_sem_context);
err2:
free(forkArgs);
err1:
team->ReleaseReference();
return status;
}
process group with the specified ID \a groupID.
The caller must hold \a parent's lock.
*/
static bool
has_children_in_group(Team* parent, pid_t groupID)
{
for (Team* child = parent->children.First(); child != NULL;
child = parent->children.GetNext(child)) {
TeamLocker childLocker(child);
if (child->group_id == groupID)
return true;
}
return false;
}
\a id can be:
- \code > 0 \endcode: Matching an entry with that team ID.
- \code == -1 \endcode: Matching any entry.
- \code < -1 \endcode: Matching any entry with a process group ID of \c -id.
\c 0 is an invalid value for \a id.
The caller must hold the lock of the team that \a children belongs to.
\param children The job control entry list to check.
\param id The match criterion.
\return The first matching entry or \c NULL, if none matches.
*/
static job_control_entry*
get_job_control_entry(team_job_control_children& children, pid_t id)
{
for (JobControlEntryList::Iterator it = children.entries.GetIterator();
job_control_entry* entry = it.Next();) {
if (id > 0) {
if (entry->thread == id)
return entry;
} else if (id == -1) {
return entry;
} else {
pid_t processGroup
= (entry->team ? entry->team->group_id : entry->group_id);
if (processGroup == -id)
return entry;
}
}
return NULL;
}
stopped children which matches \a id.
\a id can be:
- \code > 0 \endcode: Matching an entry with that team ID.
- \code == -1 \endcode: Matching any entry.
- \code < -1 \endcode: Matching any entry with a process group ID of \c -id.
\c 0 is an invalid value for \a id.
The caller must hold \a team's lock.
\param team The team whose dead, stopped, and continued child lists shall be
checked.
\param id The match criterion.
\param flags Specifies which children shall be considered. Dead children
are considered when \a flags is ORed bitwise with \c WEXITED, stopped
children are considered when \a flags is ORed bitwise with \c WUNTRACED
or \c WSTOPPED, continued children when \a flags is ORed bitwise with
\c WCONTINUED.
\return The first matching entry or \c NULL, if none matches.
*/
static job_control_entry*
get_job_control_entry(Team* team, pid_t id, uint32 flags)
{
job_control_entry* entry = NULL;
if ((flags & WEXITED) != 0)
entry = get_job_control_entry(team->dead_children, id);
if (entry == NULL && (flags & WCONTINUED) != 0)
entry = get_job_control_entry(team->continued_children, id);
if (entry == NULL && (flags & (WUNTRACED | WSTOPPED)) != 0)
entry = get_job_control_entry(team->stopped_children, id);
return entry;
}
job_control_entry::job_control_entry()
:
has_group_ref(false)
{
}
job_control_entry::~job_control_entry()
{
if (has_group_ref) {
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
ProcessGroup* group = sGroupHash.Lookup(group_id);
if (group == NULL) {
panic("job_control_entry::~job_control_entry(): unknown group "
"ID: %" B_PRId32, group_id);
return;
}
groupHashLocker.Unlock();
group->ReleaseReference();
}
}
the dead state.
The caller must hold the owning team's lock and the scheduler lock.
*/
void
job_control_entry::InitDeadState()
{
if (team != NULL) {
ASSERT(team->exit.initialized);
group_id = team->group_id;
team->group->AcquireReference();
has_group_ref = true;
thread = team->id;
status = team->exit.status;
reason = team->exit.reason;
signal = team->exit.signal;
signaling_user = team->exit.signaling_user;
user_time = team->dead_threads_user_time
+ team->dead_children.user_time;
kernel_time = team->dead_threads_kernel_time
+ team->dead_children.kernel_time;
team = NULL;
}
}
job_control_entry&
job_control_entry::operator=(const job_control_entry& other)
{
state = other.state;
thread = other.thread;
signal = other.signal;
has_group_ref = false;
signaling_user = other.signaling_user;
team = other.team;
group_id = other.group_id;
status = other.status;
reason = other.reason;
user_time = other.user_time;
kernel_time = other.kernel_time;
return *this;
}
*/
static thread_id
wait_for_child(pid_t child, uint32 flags, siginfo_t& _info,
team_usage_info& _usage_info)
{
Thread* thread = thread_get_current_thread();
Team* team = thread->team;
struct job_control_entry foundEntry;
struct job_control_entry* freeDeathEntry = NULL;
status_t status = B_OK;
TRACE(("wait_for_child(child = %" B_PRId32 ", flags = %" B_PRId32 ")\n",
child, flags));
T(WaitForChild(child, flags));
if ((flags & (WEXITED | WUNTRACED | WSTOPPED | WCONTINUED)) == 0) {
T(WaitForChildDone(B_BAD_VALUE));
return B_BAD_VALUE;
}
pid_t originalChild = child;
bool ignoreFoundEntries = false;
bool ignoreFoundEntriesChecked = false;
while (true) {
TeamLocker teamLocker(team);
child = originalChild == 0 ? -team->group_id : originalChild;
job_control_entry* entry = get_job_control_entry(team, child, flags);
if (entry == NULL) {
bool childrenExist = false;
if (child == -1) {
childrenExist = !team->children.IsEmpty();
} else if (child < -1) {
childrenExist = has_children_in_group(team, -child);
} else if (child != team->id) {
if (Team* childTeam = Team::Get(child)) {
BReference<Team> childTeamReference(childTeam, true);
TeamLocker childTeamLocker(childTeam);
childrenExist = childTeam->parent == team;
}
}
if (!childrenExist) {
status = ECHILD;
} else {
status = B_WOULD_BLOCK;
}
} else {
foundEntry = *entry;
if ((flags & WNOWAIT) == 0 || ignoreFoundEntries) {
if (entry->state == JOB_CONTROL_STATE_DEAD) {
freeDeathEntry = entry;
team->dead_children.entries.Remove(entry);
team->dead_children.count--;
} else {
team_set_job_control_state(entry->team,
JOB_CONTROL_STATE_NONE, NULL);
}
}
}
ConditionVariableEntry deadWaitEntry;
if (status == B_WOULD_BLOCK && (flags & WNOHANG) == 0)
team->dead_children.condition_variable.Add(&deadWaitEntry);
teamLocker.Unlock();
if (status == B_OK) {
if (ignoreFoundEntries) {
delete freeDeathEntry;
freeDeathEntry = NULL;
continue;
}
break;
}
if (status != B_WOULD_BLOCK || (flags & WNOHANG) != 0) {
T(WaitForChildDone(status));
return status;
}
status = deadWaitEntry.Wait(B_CAN_INTERRUPT);
if (status == B_INTERRUPTED) {
T(WaitForChildDone(status));
return status;
}
if (!ignoreFoundEntriesChecked) {
teamLocker.Lock();
struct sigaction& handler = team->SignalActionFor(SIGCHLD);
if ((handler.sa_flags & SA_NOCLDWAIT) != 0
|| handler.sa_handler == SIG_IGN) {
ignoreFoundEntries = true;
}
teamLocker.Unlock();
ignoreFoundEntriesChecked = true;
}
}
delete freeDeathEntry;
memset(&_info, 0, sizeof(_info));
_info.si_signo = SIGCHLD;
_info.si_pid = foundEntry.thread;
_info.si_uid = foundEntry.signaling_user;
switch (foundEntry.state) {
case JOB_CONTROL_STATE_DEAD:
_info.si_code = foundEntry.reason;
_info.si_status = foundEntry.reason == CLD_EXITED
? foundEntry.status : foundEntry.signal;
_usage_info.user_time = foundEntry.user_time;
_usage_info.kernel_time = foundEntry.kernel_time;
break;
case JOB_CONTROL_STATE_STOPPED:
_info.si_code = CLD_STOPPED;
_info.si_status = foundEntry.signal;
break;
case JOB_CONTROL_STATE_CONTINUED:
_info.si_code = CLD_CONTINUED;
_info.si_status = 0;
break;
case JOB_CONTROL_STATE_NONE:
break;
}
TeamLocker teamLocker(team);
InterruptsSpinLocker signalLocker(team->signal_lock);
SpinLocker threadCreationLocker(gThreadCreationLock);
if (is_team_signal_blocked(team, SIGCHLD)) {
if (get_job_control_entry(team, child, flags) == NULL)
team->RemovePendingSignals(SIGNAL_TO_MASK(SIGCHLD));
}
threadCreationLocker.Unlock();
signalLocker.Unlock();
teamLocker.Unlock();
if (foundEntry.state == JOB_CONTROL_STATE_DEAD)
wait_for_thread(foundEntry.thread, NULL);
T(WaitForChildDone(foundEntry));
return foundEntry.thread;
}
Interrupts must be enabled. The team must not be locked.
*/
static status_t
fill_team_info(Team* team, team_info* info, size_t size)
{
if (size > sizeof(team_info))
return B_BAD_VALUE;
memset(info, 0, size);
info->team = team->id;
info->image_count = count_images(team);
TeamLocker teamLocker(team);
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
info->thread_count = team->num_threads;
info->debugger_nub_thread = team->debug_info.nub_thread;
info->debugger_nub_port = team->debug_info.nub_port;
info->uid = team->effective_uid;
info->gid = team->effective_gid;
strlcpy(info->args, team->Args(), sizeof(info->args));
info->argc = 1;
if (size > offsetof(team_info, real_uid)) {
info->real_uid = team->real_uid;
info->real_gid = team->real_gid;
info->group_id = team->group_id;
info->session_id = team->session_id;
if (team->parent != NULL)
info->parent = team->parent->id;
else
info->parent = -1;
strlcpy(info->name, team->Name(), sizeof(info->name));
info->start_time = team->start_time;
}
return B_OK;
}
The caller must hold the process group's lock.
*/
static bool
process_group_has_stopped_processes(ProcessGroup* group)
{
Team* team = group->teams.First();
while (team != NULL) {
team->LockTeamAndParent(false);
if (team->job_control_entry != NULL
&& team->job_control_entry->state == JOB_CONTROL_STATE_STOPPED) {
team->UnlockTeamAndParent();
return true;
}
team->UnlockTeamAndParent();
team = group->teams.GetNext(team);
}
return false;
}
those that are orphaned and have stopped processes.
The caller must not hold any team or process group locks.
*/
static void
orphaned_process_group_check()
{
while (true) {
MutexLocker orphanedCheckLocker(sOrphanedCheckLock);
ProcessGroup* group = sOrphanedCheckProcessGroups.RemoveHead();
if (group == NULL)
return;
group->UnsetOrphanedCheck();
BReference<ProcessGroup> groupReference(group);
orphanedCheckLocker.Unlock();
AutoLocker<ProcessGroup> groupLocker(group);
if (group->IsOrphaned() && process_group_has_stopped_processes(group)) {
Thread* currentThread = thread_get_current_thread();
Signal signal(SIGHUP, SI_USER, B_OK, currentThread->team->id);
send_signal_to_process_group_locked(group, signal, 0);
signal.SetNumber(SIGCONT);
send_signal_to_process_group_locked(group, signal, 0);
}
}
}
static status_t
common_get_team_usage_info(team_id id, int32 who, team_usage_info* info,
uint32 flags)
{
if (who != B_TEAM_USAGE_SELF && who != B_TEAM_USAGE_CHILDREN)
return B_BAD_VALUE;
Team* team = Team::GetAndLock(id);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
TeamLocker teamLocker(team, true);
if ((flags & B_CHECK_PERMISSION) != 0) {
uid_t uid = geteuid();
if (uid != 0 && uid != team->effective_uid)
return B_NOT_ALLOWED;
}
bigtime_t kernelTime = 0;
bigtime_t userTime = 0;
switch (who) {
case B_TEAM_USAGE_SELF:
{
for (Thread* thread = team->thread_list.First(); thread != NULL;
thread = team->thread_list.GetNext(thread)) {
InterruptsSpinLocker threadTimeLocker(thread->time_lock);
kernelTime += thread->kernel_time;
userTime += thread->user_time;
}
kernelTime += team->dead_threads_kernel_time;
userTime += team->dead_threads_user_time;
break;
}
case B_TEAM_USAGE_CHILDREN:
{
Team* child = team->children.First();
for (; child != NULL; child = team->children.GetNext(child)) {
TeamLocker childLocker(child);
for (Thread* thread = child->thread_list.First(); thread != NULL;
thread = child->thread_list.GetNext(thread)) {
InterruptsSpinLocker threadTimeLocker(thread->time_lock);
kernelTime += thread->kernel_time;
userTime += thread->user_time;
}
kernelTime += child->dead_threads_kernel_time;
userTime += child->dead_threads_user_time;
}
kernelTime += team->dead_children.kernel_time;
userTime += team->dead_children.user_time;
break;
}
}
info->kernel_time = kernelTime;
info->user_time = userTime;
return B_OK;
}
status_t
team_init(kernel_args* args)
{
new(&sTeamHash) TeamTable;
if (sTeamHash.Init(64) != B_OK)
panic("Failed to init team hash table!");
new(&sGroupHash) ProcessGroupHashTable;
if (sGroupHash.Init() != B_OK)
panic("Failed to init process group hash table!");
ProcessSession* session = new(std::nothrow) ProcessSession(1);
if (session == NULL)
panic("Could not create initial session.\n");
BReference<ProcessSession> sessionReference(session, true);
ProcessGroup* group = new(std::nothrow) ProcessGroup(1);
if (group == NULL)
panic("Could not create initial process group.\n");
BReference<ProcessGroup> groupReference(group, true);
group->Publish(session);
sKernelTeam = Team::Create(1, "kernel_team", true);
if (sKernelTeam == NULL)
panic("could not create kernel team!\n");
sKernelTeam->address_space = VMAddressSpace::Kernel();
sKernelTeam->SetArgs(sKernelTeam->Name());
sKernelTeam->state = TEAM_STATE_NORMAL;
sKernelTeam->saved_set_uid = 0;
sKernelTeam->real_uid = 0;
sKernelTeam->effective_uid = 0;
sKernelTeam->saved_set_gid = 0;
sKernelTeam->real_gid = 0;
sKernelTeam->effective_gid = 0;
sKernelTeam->supplementary_groups = NULL;
insert_team_into_group(group, sKernelTeam);
sKernelTeam->io_context = vfs_new_io_context(NULL, false);
if (sKernelTeam->io_context == NULL)
panic("could not create io_context for kernel team!\n");
if (vfs_resize_fd_table(sKernelTeam->io_context, 4096) != B_OK)
dprintf("Failed to resize FD table for kernel team!\n");
sTeamHash.Insert(sKernelTeam);
sDisableUserAddOns = get_safemode_boolean(B_SAFEMODE_DISABLE_USER_ADD_ONS,
false);
add_debugger_command_etc("team", &dump_team_info,
"Dump info about a particular team",
"[ <id> | <address> | <name> ]\n"
"Prints information about the specified team. If no argument is given\n"
"the current team is selected.\n"
" <id> - The ID of the team.\n"
" <address> - The address of the team structure.\n"
" <name> - The team's name.\n", 0);
add_debugger_command_etc("teams", &dump_teams, "List all teams",
"\n"
"Prints a list of all existing teams.\n", 0);
new(&sNotificationService) TeamNotificationService();
sNotificationService.Register();
return B_OK;
}
int32
team_max_teams(void)
{
return sMaxTeams;
}
int32
team_used_teams(void)
{
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
return sUsedTeams;
}
The caller must hold the team's lock.
\param team The team whose dead children list to check.
\param child The ID of the child for whose death entry to lock. Must be > 0.
\param _deleteEntry Return variable, indicating whether the caller needs to
delete the returned entry.
\return The death entry of the matching team, or \c NULL, if no death entry
for the team was found.
*/
job_control_entry*
team_get_death_entry(Team* team, thread_id child, bool* _deleteEntry)
{
if (child <= 0)
return NULL;
job_control_entry* entry = get_job_control_entry(team->dead_children,
child);
if (entry) {
if (team_get_current_team_id() == entry->thread) {
team->dead_children.entries.Remove(entry);
team->dead_children.count--;
*_deleteEntry = true;
} else {
*_deleteEntry = false;
}
}
return entry;
}
bool
team_is_valid(team_id id)
{
if (id <= 0)
return false;
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
return team_get_team_struct_locked(id) != NULL;
}
Team*
team_get_team_struct_locked(team_id id)
{
return sTeamHash.Lookup(id);
}
void
team_set_controlling_tty(void* tty)
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
ProcessSession* session = team->group->Session();
AutoLocker<ProcessSession> sessionLocker(session);
session->controlling_tty = tty;
session->foreground_group = -1;
}
void*
team_get_controlling_tty()
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
ProcessSession* session = team->group->Session();
AutoLocker<ProcessSession> sessionLocker(session);
return session->controlling_tty;
}
status_t
team_set_foreground_process_group(void* tty, pid_t processGroupID)
{
Thread* thread = thread_get_current_thread();
Team* team = thread->team;
TeamLocker teamLocker(team);
ProcessSession* session = team->group->Session();
AutoLocker<ProcessSession> sessionLocker(session);
if (session->controlling_tty != tty)
return ENOTTY;
{
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
ProcessGroup* group = sGroupHash.Lookup(processGroupID);
if (group == NULL || group->Session() != session)
return B_BAD_VALUE;
}
if (session->foreground_group != -1
&& session->foreground_group != team->group_id
&& team->SignalActionFor(SIGTTOU).sa_handler != SIG_IGN
&& (thread->sig_block_mask & SIGNAL_TO_MASK(SIGTTOU)) == 0) {
InterruptsSpinLocker signalLocker(team->signal_lock);
if (!is_team_signal_blocked(team, SIGTTOU)) {
pid_t groupID = team->group_id;
signalLocker.Unlock();
sessionLocker.Unlock();
teamLocker.Unlock();
Signal signal(SIGTTOU, SI_USER, B_OK, team->id);
send_signal_to_process_group(groupID, signal, 0);
return B_INTERRUPTED;
}
}
session->foreground_group = processGroupID;
return B_OK;
}
uid_t
team_geteuid(team_id id)
{
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
Team* team = team_get_team_struct_locked(id);
if (team == NULL)
return (uid_t)-1;
return team->effective_uid;
}
group, and from its parent.
It also moves all of its children to the kernel team.
The caller must hold the following locks:
- \a team's process group's lock,
- the kernel team's lock,
- \a team's parent team's lock (might be the kernel team), and
- \a team's lock.
*/
void
team_remove_team(Team* team, pid_t& _signalGroup)
{
Team* parent = team->parent;
parent->dead_children.kernel_time += team->dead_threads_kernel_time
+ team->dead_children.kernel_time;
parent->dead_children.user_time += team->dead_threads_user_time
+ team->dead_children.user_time;
InterruptsWriteSpinLocker teamsLocker(sTeamHashLock);
sTeamHash.Remove(team);
sUsedTeams--;
teamsLocker.Unlock();
team->state = TEAM_STATE_DEATH;
_signalGroup = -1;
bool isSessionLeader = false;
if (team->session_id == team->id
&& team->group->Session()->controlling_tty != NULL) {
isSessionLeader = true;
ProcessSession* session = team->group->Session();
AutoLocker<ProcessSession> sessionLocker(session);
session->controlling_tty = NULL;
_signalGroup = session->foreground_group;
}
remove_team_from_group(team);
while (Team* child = team->children.First()) {
TeamLocker childLocker(child);
remove_team_from_parent(team, child);
insert_team_into_parent(sKernelTeam, child);
sKernelTeam->stopped_children.entries.TakeFrom(
&team->stopped_children.entries);
sKernelTeam->continued_children.entries.TakeFrom(
&team->continued_children.entries);
if (isSessionLeader) {
ProcessGroup* childGroup = child->group;
if (childGroup->Session()->id == team->session_id
&& childGroup->id != team->group_id) {
childGroup->ScheduleOrphanedCheck();
}
}
}
remove_team_from_parent(parent, team);
}
debugging for it.
To be called on exit of the team's main thread. No locks must be held.
\param team The team in question.
\return The port of the debugger for the team, -1 if none. To be passed to
team_delete_team().
*/
port_id
team_shutdown_team(Team* team)
{
ASSERT(thread_get_current_thread() == team->main_thread);
TeamLocker teamLocker(team);
port_id debuggerPort = -1;
while (true) {
ConditionVariableEntry waitForDebuggerEntry;
bool waitForDebugger = false;
InterruptsSpinLocker debugInfoLocker(team->debug_info.lock);
if (team->debug_info.debugger_changed_condition != NULL) {
team->debug_info.debugger_changed_condition->Add(
&waitForDebuggerEntry);
waitForDebugger = true;
} else if (team->debug_info.flags & B_TEAM_DEBUG_DEBUGGER_INSTALLED) {
debuggerPort = team->debug_info.debugger_port;
}
debugInfoLocker.Unlock();
if (!waitForDebugger)
break;
teamLocker.Unlock();
waitForDebuggerEntry.Wait();
teamLocker.Lock();
}
team->state = TEAM_STATE_SHUTDOWN;
team->DeleteUserTimers(false);
InterruptsSpinLocker timeLocker(team->time_lock);
if (team->HasActiveCPUTimeUserTimers())
team->DeactivateCPUTimeUserTimers();
timeLocker.Unlock();
team_kill_other_threads_locked(teamLocker);
return debuggerPort;
}
resources associated with it.
The caller shouldn't hold any locks.
*/
void
team_delete_team(Team* team, port_id debuggerPort)
{
orphaned_process_group_check();
team_id teamID = team->id;
ASSERT(team->num_threads == 0);
TeamLocker teamLocker(team);
if (team->loading_info != NULL) {
team->loading_info->result = B_ERROR;
team->loading_info->condition.NotifyAll();
team->loading_info = NULL;
}
{
struct team_watcher* watcher;
while ((watcher = (struct team_watcher*)list_remove_head_item(
&team->watcher_list)) != NULL) {
watcher->hook(teamID, watcher->data);
free(watcher);
}
}
status_t exitStatus = -1;
int signal = -1;
switch (team->exit.reason) {
case CLD_EXITED:
exitStatus = team->exit.status;
break;
case CLD_KILLED:
signal = team->exit.signal;
break;
}
teamLocker.Unlock();
sNotificationService.Notify(TEAM_REMOVED, team);
InterruptsSpinLocker timeLocker(team->time_lock);
team_usage_info usageInfo;
usageInfo.kernel_time = team->dead_threads_kernel_time;
usageInfo.user_time = team->dead_threads_user_time;
timeLocker.Unlock();
delete_user_mutex_context(team->user_mutex_context);
delete_realtime_sem_context(team->realtime_sem_context);
xsi_sem_undo(team);
remove_images(team);
team->address_space->RemoveAndPut();
team->ReleaseReference();
user_debug_team_deleted(teamID, debuggerPort, exitStatus, signal, &usageInfo);
}
Team*
team_get_kernel_team(void)
{
return sKernelTeam;
}
team_id
team_get_kernel_team_id(void)
{
if (!sKernelTeam)
return 0;
return sKernelTeam->id;
}
team_id
team_get_current_team_id(void)
{
return thread_get_current_thread()->team->id;
}
status_t
team_get_address_space(team_id id, VMAddressSpace** _addressSpace)
{
if (id == sKernelTeam->id) {
*_addressSpace = VMAddressSpace::GetKernel();
return B_OK;
}
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
Team* team = team_get_team_struct_locked(id);
if (team == NULL)
return B_BAD_VALUE;
team->address_space->Get();
*_addressSpace = team->address_space;
return B_OK;
}
The caller must hold the parent team's lock. Interrupts are allowed to be
enabled or disabled.
\a team The team whose job control state shall be set.
\a newState The new state to be set.
\a signal The signal the new state was caused by. Can \c NULL, if none. Then
the caller is responsible for filling in the following fields of the
entry before releasing the parent team's lock, unless the new state is
\c JOB_CONTROL_STATE_NONE:
- \c signal: The number of the signal causing the state change.
- \c signaling_user: The real UID of the user sending the signal.
*/
void
team_set_job_control_state(Team* team, job_control_state newState,
Signal* signal)
{
if (team == NULL || team->job_control_entry == NULL)
return;
job_control_entry* entry = team->job_control_entry;
if (entry->state == newState || entry->state == JOB_CONTROL_STATE_DEAD)
return;
T(SetJobControlState(team->id, newState, signal));
switch (entry->state) {
case JOB_CONTROL_STATE_NONE:
break;
case JOB_CONTROL_STATE_DEAD:
break;
case JOB_CONTROL_STATE_STOPPED:
team->parent->stopped_children.entries.Remove(entry);
break;
case JOB_CONTROL_STATE_CONTINUED:
team->parent->continued_children.entries.Remove(entry);
break;
}
entry->state = newState;
if (signal != NULL) {
entry->signal = signal->Number();
entry->signaling_user = signal->SendingUser();
}
team_job_control_children* childList = NULL;
switch (entry->state) {
case JOB_CONTROL_STATE_NONE:
break;
case JOB_CONTROL_STATE_DEAD:
childList = &team->parent->dead_children;
team->parent->dead_children.count++;
break;
case JOB_CONTROL_STATE_STOPPED:
childList = &team->parent->stopped_children;
break;
case JOB_CONTROL_STATE_CONTINUED:
childList = &team->parent->continued_children;
break;
}
if (childList != NULL) {
childList->entries.Add(entry);
team->parent->dead_children.condition_variable.NotifyAll();
}
}
generic "killed" status.
The caller must not hold the team's lock. Interrupts must be enabled.
\param team The team whose exit info shall be initialized.
*/
void
team_init_exit_info_on_error(Team* team)
{
TeamLocker teamLocker(team);
if (!team->exit.initialized) {
team->exit.reason = CLD_KILLED;
team->exit.signal = SIGKILL;
team->exit.signaling_user = geteuid();
team->exit.status = 0;
team->exit.initialized = true;
}
}
This call might get public in the future.
*/
status_t
start_watching_team(team_id teamID, void (*hook)(team_id, void*), void* data)
{
if (hook == NULL || teamID < B_OK)
return B_BAD_VALUE;
team_watcher* watcher = (team_watcher*)malloc(sizeof(team_watcher));
if (watcher == NULL)
return B_NO_MEMORY;
watcher->hook = hook;
watcher->data = data;
Team* team = Team::GetAndLock(teamID);
if (team == NULL) {
free(watcher);
return B_BAD_TEAM_ID;
}
list_add_item(&team->watcher_list, watcher);
team->UnlockAndReleaseReference();
return B_OK;
}
status_t
stop_watching_team(team_id teamID, void (*hook)(team_id, void*), void* data)
{
if (hook == NULL || teamID < 0)
return B_BAD_VALUE;
Team* team = Team::GetAndLock(teamID);
if (team == NULL)
return B_BAD_TEAM_ID;
team_watcher* watcher = NULL;
while ((watcher = (team_watcher*)list_get_next_item(
&team->watcher_list, watcher)) != NULL) {
if (watcher->hook == hook && watcher->data == data) {
list_remove_item(&team->watcher_list, watcher);
break;
}
}
team->UnlockAndReleaseReference();
if (watcher == NULL)
return B_ENTRY_NOT_FOUND;
free(watcher);
return B_OK;
}
The team lock must be held, unless the function is called for the team's
main thread. Interrupts must be enabled.
*/
struct user_thread*
team_allocate_user_thread(Team* team)
{
if (team->user_data == 0)
return NULL;
if (struct free_user_thread* entry = team->free_user_threads) {
user_thread* thread = entry->thread;
team->free_user_threads = entry->next;
free(entry);
return thread;
}
while (true) {
size_t needed = ROUNDUP(sizeof(user_thread), CACHE_LINE_SIZE);
if (team->user_data_size - team->used_user_data < needed) {
if (resize_area(team->user_data_area,
team->user_data_size + B_PAGE_SIZE) != B_OK) {
return NULL;
}
team->user_data_size += B_PAGE_SIZE;
continue;
}
user_thread* thread
= (user_thread*)(team->user_data + team->used_user_data);
team->used_user_data += needed;
return thread;
}
}
The team's lock must not be held. Interrupts must be enabled.
\param team The team the user thread was allocated from.
\param userThread The user thread to free.
*/
void
team_free_user_thread(Team* team, struct user_thread* userThread)
{
if (userThread == NULL)
return;
free_user_thread* entry
= (free_user_thread*)malloc(sizeof(free_user_thread));
if (entry == NULL) {
return;
}
TeamLocker teamLocker(team);
entry->thread = userThread;
entry->next = team->free_user_threads;
team->free_user_threads = entry;
}
AssociatedData::AssociatedData()
:
fOwner(NULL)
{
}
AssociatedData::~AssociatedData()
{
}
void
AssociatedData::OwnerDeleted(AssociatedDataOwner* owner)
{
}
AssociatedDataOwner::AssociatedDataOwner()
{
mutex_init(&fLock, "associated data owner");
}
AssociatedDataOwner::~AssociatedDataOwner()
{
mutex_destroy(&fLock);
}
bool
AssociatedDataOwner::AddData(AssociatedData* data)
{
MutexLocker locker(fLock);
if (data->Owner() != NULL)
return false;
data->AcquireReference();
fList.Add(data);
data->SetOwner(this);
return true;
}
bool
AssociatedDataOwner::RemoveData(AssociatedData* data)
{
MutexLocker locker(fLock);
if (data->Owner() != this)
return false;
data->SetOwner(NULL);
fList.Remove(data);
locker.Unlock();
data->ReleaseReference();
return true;
}
void
AssociatedDataOwner::PrepareForDeletion()
{
MutexLocker locker(fLock);
DataList list;
list.TakeFrom(&fList);
for (DataList::Iterator it = list.GetIterator();
AssociatedData* data = it.Next();) {
data->SetOwner(NULL);
}
locker.Unlock();
while (AssociatedData* data = list.RemoveHead()) {
data->OwnerDeleted(this);
data->ReleaseReference();
}
}
When the team is deleted, the data object is notified.
The team acquires a reference to the object.
\param data The data object.
\return \c true on success, \c false otherwise. Fails only when the supplied
data object is already associated with another owner.
*/
bool
team_associate_data(AssociatedData* data)
{
return thread_get_current_thread()->team->AddData(data);
}
Balances an earlier call to team_associate_data().
\param data The data object.
\return \c true on success, \c false otherwise. Fails only when the data
object is not associated with the current team.
*/
bool
team_dissociate_data(AssociatedData* data)
{
return thread_get_current_thread()->team->RemoveData(data);
}
thread_id
load_image(int32 argCount, const char** args, const char** env)
{
return load_image_etc(argCount, args, env, B_NORMAL_PRIORITY,
B_CURRENT_TEAM, B_WAIT_TILL_LOADED);
}
thread_id
load_image_etc(int32 argCount, const char* const* args,
const char* const* env, int32 priority, team_id parentID, uint32 flags)
{
if (args == NULL)
return B_BAD_VALUE;
int32 argSize = 0;
for (int32 i = 0; i < argCount; i++)
argSize += strlen(args[i]) + 1;
int32 envCount = 0;
int32 envSize = 0;
while (env != NULL && env[envCount] != NULL)
envSize += strlen(env[envCount++]) + 1;
int32 size = (argCount + envCount + 2) * sizeof(char*) + argSize + envSize;
if (size > MAX_PROCESS_ARGS_SIZE)
return B_TOO_MANY_ARGS;
char** flatArgs = (char**)malloc(size);
if (flatArgs == NULL)
return B_NO_MEMORY;
char** slot = flatArgs;
char* stringSpace = (char*)(flatArgs + argCount + envCount + 2);
for (int32 i = 0; i < argCount; i++) {
int32 argSize = strlen(args[i]) + 1;
memcpy(stringSpace, args[i], argSize);
*slot++ = stringSpace;
stringSpace += argSize;
}
*slot++ = NULL;
for (int32 i = 0; i < envCount; i++) {
int32 envSize = strlen(env[i]) + 1;
memcpy(stringSpace, env[i], envSize);
*slot++ = stringSpace;
stringSpace += envSize;
}
*slot++ = NULL;
thread_id thread = load_image_internal(flatArgs, size, argCount, envCount,
B_NORMAL_PRIORITY, parentID, B_WAIT_TILL_LOADED, -1, 0);
free(flatArgs);
return thread;
}
status_t
wait_for_team(team_id id, status_t* _returnCode)
{
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
Team* team = team_get_team_struct_locked(id);
if (team == NULL)
return B_BAD_TEAM_ID;
id = team->id;
teamsLocker.Unlock();
return wait_for_thread(id, _returnCode);
}
status_t
kill_team(team_id id)
{
InterruptsReadSpinLocker teamsLocker(sTeamHashLock);
Team* team = team_get_team_struct_locked(id);
if (team == NULL)
return B_BAD_TEAM_ID;
id = team->id;
teamsLocker.Unlock();
if (team == sKernelTeam)
return B_NOT_ALLOWED;
return kill_thread(id);
}
status_t
_get_team_info(team_id id, team_info* info, size_t size)
{
Team* team = Team::Get(id);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
return fill_team_info(team, info, size);
}
status_t
_get_next_team_info(int32* cookie, team_info* info, size_t size)
{
int32 slot = *cookie;
if (slot < 1)
slot = 1;
InterruptsReadSpinLocker locker(sTeamHashLock);
team_id lastTeamID = peek_next_thread_id();
Team* team = NULL;
while (slot < lastTeamID && !(team = team_get_team_struct_locked(slot)))
slot++;
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team);
locker.Unlock();
*cookie = ++slot;
return fill_team_info(team, info, size);
}
status_t
_get_team_usage_info(team_id id, int32 who, team_usage_info* info, size_t size)
{
if (size != sizeof(team_usage_info))
return B_BAD_VALUE;
return common_get_team_usage_info(id, who, info, 0);
}
pid_t
getpid(void)
{
return thread_get_current_thread()->team->id;
}
pid_t
getppid()
{
return _getppid(0);
}
pid_t
getpgid(pid_t id)
{
if (id < 0) {
errno = EINVAL;
return -1;
}
if (id == 0) {
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
return team->group_id;
}
Team* team = Team::GetAndLock(id);
if (team == NULL) {
errno = ESRCH;
return -1;
}
pid_t groupID = team->group_id;
team->UnlockAndReleaseReference();
return groupID;
}
pid_t
getsid(pid_t id)
{
if (id < 0) {
errno = EINVAL;
return -1;
}
if (id == 0) {
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
return team->session_id;
}
Team* team = Team::GetAndLock(id);
if (team == NULL) {
errno = ESRCH;
return -1;
}
pid_t sessionID = team->session_id;
team->UnlockAndReleaseReference();
return sessionID;
}
status_t
_user_exec(const char* userPath, const char* const* userFlatArgs,
size_t flatArgsSize, int32 argCount, int32 envCount, mode_t umask)
{
char path[B_PATH_NAME_LENGTH];
if (!IS_USER_ADDRESS(userPath) || !IS_USER_ADDRESS(userFlatArgs)
|| user_strlcpy(path, userPath, sizeof(path)) < B_OK)
return B_BAD_ADDRESS;
char** flatArgs;
status_t error = copy_user_process_args(userFlatArgs, flatArgsSize,
argCount, envCount, flatArgs);
if (error == B_OK) {
error = exec_team(path, flatArgs, _ALIGN(flatArgsSize), argCount,
envCount, umask);
}
free(flatArgs);
return error;
}
thread_id
_user_fork(void)
{
return fork_team();
}
pid_t
_user_wait_for_child(thread_id child, uint32 flags, siginfo_t* userInfo,
team_usage_info* usageInfo)
{
if (userInfo != NULL && !IS_USER_ADDRESS(userInfo))
return B_BAD_ADDRESS;
if (usageInfo != NULL && !IS_USER_ADDRESS(usageInfo))
return B_BAD_ADDRESS;
siginfo_t info;
team_usage_info usage_info;
pid_t foundChild = wait_for_child(child, flags, info, usage_info);
if (foundChild < 0)
return syscall_restart_handle_post(foundChild);
if (userInfo != NULL && user_memcpy(userInfo, &info, sizeof(info)) != B_OK)
return B_BAD_ADDRESS;
if (usageInfo != NULL && user_memcpy(usageInfo, &usage_info,
sizeof(usage_info)) != B_OK) {
return B_BAD_ADDRESS;
}
return foundChild;
}
pid_t
_user_process_info(pid_t process, int32 which)
{
pid_t result;
switch (which) {
case SESSION_ID:
result = getsid(process);
break;
case GROUP_ID:
result = getpgid(process);
break;
case PARENT_ID:
result = _getppid(process);
break;
default:
return B_BAD_VALUE;
}
return result >= 0 ? result : errno;
}
pid_t
_user_setpgid(pid_t processID, pid_t groupID)
{
if (groupID < 0)
return B_BAD_VALUE;
Team* currentTeam = thread_get_current_thread()->team;
if (processID == 0)
processID = currentTeam->id;
if (groupID == 0)
groupID = processID;
while (true) {
ProcessGroup* group = ProcessGroup::Get(groupID);
bool newGroup = false;
if (group == NULL) {
if (groupID != processID)
return B_NOT_ALLOWED;
group = new(std::nothrow) ProcessGroup(groupID);
if (group == NULL)
return B_NO_MEMORY;
newGroup = true;
}
BReference<ProcessGroup> groupReference(group, true);
Team* team = Team::Get(processID);
if (team == NULL)
return ESRCH;
BReference<Team> teamReference(team, true);
for (Team* parentTeam = team; parentTeam != currentTeam; parentTeam = parentTeam->parent) {
if (parentTeam == NULL || parentTeam->id == 1)
return ESRCH;
}
if (is_session_leader(team))
return B_NOT_ALLOWED;
while (true) {
team->LockProcessGroup();
ProcessGroup* oldGroup = team->group;
if (oldGroup == NULL) {
ASSERT(team->state >= TEAM_STATE_SHUTDOWN);
return ESRCH;
}
if (oldGroup == group) {
oldGroup->Unlock();
return group->id;
}
oldGroup->AcquireReference();
if (newGroup || group->id > oldGroup->id) {
group->Lock();
break;
}
if (group->TryLock())
break;
oldGroup->Unlock();
group->Lock();
oldGroup->Lock();
TeamLocker teamLocker(team);
if (team->group == oldGroup)
break;
teamLocker.Unlock();
oldGroup->Unlock();
group->Unlock();
oldGroup->ReleaseReference();
}
BReference<ProcessGroup> oldGroupReference(team->group, true);
AutoLocker<ProcessGroup> oldGroupLocker(team->group, true);
AutoLocker<ProcessGroup> groupLocker(group, true);
team->LockTeamAndParent(false);
TeamLocker parentLocker(team->parent, true);
TeamLocker teamLocker(team, true);
if (team != currentTeam) {
if (team->session_id != currentTeam->session_id)
return B_NOT_ALLOWED;
if ((team->flags & TEAM_FLAG_EXEC_DONE) != 0)
return EACCES;
}
if (newGroup) {
InterruptsSpinLocker groupHashLocker(sGroupHashLock);
if (sGroupHash.Lookup(groupID)) {
continue;
}
group->PublishLocked(team->group->Session());
} else if (group->Session()->id != team->session_id) {
return B_NOT_ALLOWED;
}
remove_team_from_group(team);
insert_team_into_group(group, team);
team->parent->dead_children.condition_variable.NotifyAll();
return group->id;
}
}
pid_t
_user_setsid(void)
{
Team* team = thread_get_current_thread()->team;
ProcessGroup* group = new(std::nothrow) ProcessGroup(team->id);
if (group == NULL)
return B_NO_MEMORY;
BReference<ProcessGroup> groupReference(group, true);
AutoLocker<ProcessGroup> groupLocker(group);
ProcessSession* session = new(std::nothrow) ProcessSession(group->id);
if (session == NULL)
return B_NO_MEMORY;
BReference<ProcessSession> sessionReference(session, true);
team->LockTeamParentAndProcessGroup();
BReference<ProcessGroup> oldGroupReference(team->group);
AutoLocker<ProcessGroup> oldGroupLocker(team->group, true);
TeamLocker parentLocker(team->parent, true);
TeamLocker teamLocker(team, true);
if (is_process_group_leader(team))
return B_NOT_ALLOWED;
remove_team_from_group(team);
group->Publish(session);
insert_team_into_group(group, team);
team->parent->dead_children.condition_variable.NotifyAll();
return group->id;
}
status_t
_user_wait_for_team(team_id id, status_t* _userReturnCode)
{
status_t returnCode;
status_t status;
if (_userReturnCode != NULL && !IS_USER_ADDRESS(_userReturnCode))
return B_BAD_ADDRESS;
status = wait_for_team(id, &returnCode);
if (status >= B_OK && _userReturnCode != NULL) {
if (user_memcpy(_userReturnCode, &returnCode, sizeof(returnCode))
!= B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
return syscall_restart_handle_post(status);
}
thread_id
_user_load_image(const char* const* userFlatArgs, size_t flatArgsSize,
int32 argCount, int32 envCount, int32 priority, uint32 flags,
port_id errorPort, uint32 errorToken)
{
TRACE(("_user_load_image: argc = %" B_PRId32 "\n", argCount));
if (argCount < 1)
return B_BAD_VALUE;
char** flatArgs;
status_t error = copy_user_process_args(userFlatArgs, flatArgsSize,
argCount, envCount, flatArgs);
if (error != B_OK)
return error;
thread_id thread = load_image_internal(flatArgs, _ALIGN(flatArgsSize),
argCount, envCount, priority, B_CURRENT_TEAM, flags, errorPort,
errorToken);
free(flatArgs);
return thread;
}
void
_user_exit_team(status_t returnValue)
{
Thread* thread = thread_get_current_thread();
Team* team = thread->team;
thread->exit.status = returnValue;
TeamLocker teamLocker(team);
if (!team->exit.initialized) {
team->exit.reason = CLD_EXITED;
team->exit.signal = 0;
team->exit.signaling_user = 0;
team->exit.status = returnValue;
team->exit.initialized = true;
}
teamLocker.Unlock();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
if ((atomic_get(&team->debug_info.flags) & B_TEAM_DEBUG_PREVENT_EXIT) != 0)
user_debug_stop_thread();
#pragma GCC diagnostic pop
Signal signal(SIGKILL, SI_USER, B_OK, team->id);
send_signal_to_thread(thread, signal, 0);
}
status_t
_user_kill_team(team_id team)
{
return kill_team(team);
}
status_t
_user_get_team_info(team_id id, team_info* userInfo, size_t size)
{
status_t status;
team_info info;
if (size > sizeof(team_info))
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userInfo))
return B_BAD_ADDRESS;
status = _get_team_info(id, &info, size);
if (status == B_OK) {
if (user_memcpy(userInfo, &info, size) < B_OK)
return B_BAD_ADDRESS;
}
return status;
}
status_t
_user_get_next_team_info(int32* userCookie, team_info* userInfo, size_t size)
{
status_t status;
team_info info;
int32 cookie;
if (size > sizeof(team_info))
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_team_info(&cookie, &info, size);
if (status != B_OK)
return status;
if (user_memcpy(userCookie, &cookie, sizeof(int32)) < B_OK
|| user_memcpy(userInfo, &info, size) < B_OK)
return B_BAD_ADDRESS;
return status;
}
team_id
_user_get_current_team(void)
{
return team_get_current_team_id();
}
status_t
_user_get_team_usage_info(team_id team, int32 who, team_usage_info* userInfo,
size_t size)
{
if (size != sizeof(team_usage_info))
return B_BAD_VALUE;
team_usage_info info;
status_t status = common_get_team_usage_info(team, who, &info,
B_CHECK_PERMISSION);
if (userInfo == NULL || !IS_USER_ADDRESS(userInfo)
|| user_memcpy(userInfo, &info, size) != B_OK) {
return B_BAD_ADDRESS;
}
return status;
}
status_t
_user_get_extended_team_info(team_id teamID, uint32 flags, void* buffer,
size_t size, size_t* _sizeNeeded)
{
if ((buffer != NULL && !IS_USER_ADDRESS(buffer))
|| (buffer == NULL && size > 0)
|| _sizeNeeded == NULL || !IS_USER_ADDRESS(_sizeNeeded)) {
return B_BAD_ADDRESS;
}
Team* team = Team::Get(teamID);
if (team == NULL)
return B_BAD_TEAM_ID;
BReference<Team> teamReference(team, true);
uid_t uid = geteuid();
if (uid != 0 && uid != team->effective_uid)
return B_NOT_ALLOWED;
KMessage info;
if ((flags & B_TEAM_INFO_BASIC) != 0) {
struct ExtendedTeamData {
team_id id;
pid_t group_id;
pid_t session_id;
uid_t real_uid;
gid_t real_gid;
uid_t effective_uid;
gid_t effective_gid;
char name[B_OS_NAME_LENGTH];
} teamClone;
io_context* ioContext;
{
TeamLocker teamLocker(team);
teamClone.id = team->id;
strlcpy(teamClone.name, team->Name(), sizeof(teamClone.name));
teamClone.group_id = team->group_id;
teamClone.session_id = team->session_id;
teamClone.real_uid = team->real_uid;
teamClone.real_gid = team->real_gid;
teamClone.effective_uid = team->effective_uid;
teamClone.effective_gid = team->effective_gid;
ioContext = team->io_context;
vfs_get_io_context(ioContext);
}
CObjectDeleter<io_context, void, vfs_put_io_context>
ioContextPutter(ioContext);
if (info.AddInt32("id", teamClone.id) != B_OK
|| info.AddString("name", teamClone.name) != B_OK
|| info.AddInt32("process group", teamClone.group_id) != B_OK
|| info.AddInt32("session", teamClone.session_id) != B_OK
|| info.AddInt32("uid", teamClone.real_uid) != B_OK
|| info.AddInt32("gid", teamClone.real_gid) != B_OK
|| info.AddInt32("euid", teamClone.effective_uid) != B_OK
|| info.AddInt32("egid", teamClone.effective_gid) != B_OK) {
return B_NO_MEMORY;
}
dev_t cwdDevice;
ino_t cwdDirectory;
{
ReadLocker ioContextLocker(ioContext->lock);
vfs_vnode_to_node_ref(ioContext->cwd, &cwdDevice, &cwdDirectory);
}
if (info.AddInt32("cwd device", cwdDevice) != B_OK
|| info.AddInt64("cwd directory", cwdDirectory) != B_OK) {
return B_NO_MEMORY;
}
}
size_t sizeNeeded = info.ContentSize();
if (user_memcpy(_sizeNeeded, &sizeNeeded, sizeof(sizeNeeded)) != B_OK)
return B_BAD_ADDRESS;
if (sizeNeeded > size)
return B_BUFFER_OVERFLOW;
if (user_memcpy(buffer, info.Buffer(), sizeNeeded) != B_OK)
return B_BAD_ADDRESS;
return B_OK;
}