diff --git a/sortix/process.cpp b/sortix/process.cpp index c9fff14f..f36e9706 100644 --- a/sortix/process.cpp +++ b/sortix/process.cpp @@ -22,1039 +22,1057 @@ *******************************************************************************/ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + #include -#include -#include -#include #include #include #include -#include -#include +#include +#include #include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include #include #include +#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "initrd.h" #include "elf.h" +#include "initrd.h" -namespace Sortix +namespace Sortix { + +bool ProcessSegment::Intersects(ProcessSegment* segments) { - bool ProcessSegment::Intersects(ProcessSegment* segments) + for ( ProcessSegment* tmp = segments; tmp != NULL; tmp = tmp->next ) { - for ( ProcessSegment* tmp = segments; tmp != NULL; tmp = tmp->next ) + if ( tmp->position < position + size && + position < tmp->position + tmp->size ) { - if ( tmp->position < position + size && - position < tmp->position + tmp->size ) - { - return true; - } + return true; + } + } + + if ( next ) { return next->Intersects(segments); } + + return false; +} + +ProcessSegment* ProcessSegment::Fork() +{ + ProcessSegment* nextclone = NULL; + if ( next ) + { + nextclone = next->Fork(); + if ( nextclone == NULL ) { return NULL; } + } + + ProcessSegment* clone = new ProcessSegment(); + if ( clone == NULL ) + { + while ( nextclone != NULL ) + { + ProcessSegment* todelete = nextclone; + nextclone = nextclone->next; + delete todelete; } - if ( next ) { return next->Intersects(segments); } - - return false; + return NULL; } - ProcessSegment* ProcessSegment::Fork() - { - ProcessSegment* nextclone = NULL; - if ( next ) - { - nextclone = next->Fork(); - if ( nextclone == NULL ) { return NULL; } - } + if ( nextclone ) + nextclone->prev = clone; + clone->next = nextclone; + clone->position = position; + clone->size = size; - ProcessSegment* clone = new ProcessSegment(); - if ( clone == NULL ) - { - while ( nextclone != NULL ) - { - ProcessSegment* todelete = nextclone; - nextclone = nextclone->next; - delete todelete; - } + return clone; +} - return NULL; - } +Process::Process() +{ + string_table = NULL; + string_table_length = 0; + symbol_table = NULL; + symbol_table_length = 0; + addrspace = 0; + segments = NULL; + parent = NULL; + prevsibling = NULL; + nextsibling = NULL; + firstchild = NULL; + zombiechild = NULL; + program_image_path = NULL; + parentlock = KTHREAD_MUTEX_INITIALIZER; + childlock = KTHREAD_MUTEX_INITIALIZER; + zombiecond = KTHREAD_COND_INITIALIZER; + zombiewaiting = 0; + iszombie = false; + nozombify = false; + firstthread = NULL; + threadlock = KTHREAD_MUTEX_INITIALIZER; + ptrlock = KTHREAD_MUTEX_INITIALIZER; + idlock = KTHREAD_MUTEX_INITIALIZER; + user_timers_lock = KTHREAD_MUTEX_INITIALIZER; + mmapfrom = 0x80000000UL; + exitstatus = -1; + pid = AllocatePID(); + uid = euid = 0; + gid = egid = 0; + umask = 0022; + Time::InitializeProcessClocks(this); + alarm_timer.Attach(Time::GetClock(CLOCK_MONOTONIC)); + Put(this); +} - if ( nextclone ) - nextclone->prev = clone; - clone->next = nextclone; - clone->position = position; - clone->size = size; +Process::~Process() +{ + delete[] string_table; + delete[] symbol_table; + if ( alarm_timer.IsAttached() ) + alarm_timer.Detach(); + if ( program_image_path ) + delete[] program_image_path; + assert(!zombiechild); + assert(!firstchild); + assert(!addrspace); + assert(!segments); + assert(!dtable); + assert(!mtable); + assert(!cwd); + assert(!root); - return clone; - } + Remove(this); +} - Process::Process() - { - string_table = NULL; - string_table_length = 0; - symbol_table = NULL; - symbol_table_length = 0; - addrspace = 0; - segments = NULL; - parent = NULL; - prevsibling = NULL; - nextsibling = NULL; - firstchild = NULL; - zombiechild = NULL; - program_image_path = NULL; - parentlock = KTHREAD_MUTEX_INITIALIZER; - childlock = KTHREAD_MUTEX_INITIALIZER; - zombiecond = KTHREAD_COND_INITIALIZER; - zombiewaiting = 0; - iszombie = false; - nozombify = false; - firstthread = NULL; - threadlock = KTHREAD_MUTEX_INITIALIZER; - ptrlock = KTHREAD_MUTEX_INITIALIZER; - idlock = KTHREAD_MUTEX_INITIALIZER; - user_timers_lock = KTHREAD_MUTEX_INITIALIZER; - mmapfrom = 0x80000000UL; - exitstatus = -1; - pid = AllocatePID(); - uid = euid = 0; - gid = egid = 0; - umask = 0022; - Time::InitializeProcessClocks(this); - alarm_timer.Attach(Time::GetClock(CLOCK_MONOTONIC)); - Put(this); - } +void Process::BootstrapTables(Ref dtable, Ref mtable) +{ + ScopedLock lock(&ptrlock); + assert(!this->dtable); + assert(!this->mtable); + this->dtable = dtable; + this->mtable = mtable; +} - Process::~Process() - { - delete[] string_table; - delete[] symbol_table; - if ( alarm_timer.IsAttached() ) - alarm_timer.Detach(); - if ( program_image_path ) - delete[] program_image_path; - assert(!zombiechild); - assert(!firstchild); - assert(!addrspace); - assert(!segments); - assert(!dtable); - assert(!mtable); - assert(!cwd); - assert(!root); +void Process::BootstrapDirectories(Ref root) +{ + ScopedLock lock(&ptrlock); + assert(!this->root); + assert(!this->cwd); + this->root = root; + this->cwd = root; +} - Remove(this); - } +void Process__OnLastThreadExit(void* user); - void Process::BootstrapTables(Ref dtable, Ref mtable) - { - ScopedLock lock(&ptrlock); - assert(!this->dtable); - assert(!this->mtable); - this->dtable = dtable; - this->mtable = mtable; - } +void Process::OnThreadDestruction(Thread* thread) +{ + assert(thread->process == this); + kthread_mutex_lock(&threadlock); + if ( thread->prevsibling ) + thread->prevsibling->nextsibling = thread->nextsibling; + if ( thread->nextsibling ) + thread->nextsibling->prevsibling = thread->prevsibling; + if ( thread == firstthread ) + firstthread = thread->nextsibling; + if ( firstthread ) + firstthread->prevsibling = NULL; + thread->prevsibling = thread->nextsibling = NULL; + bool threadsleft = firstthread; + kthread_mutex_unlock(&threadlock); - void Process::BootstrapDirectories(Ref root) - { - ScopedLock lock(&ptrlock); - assert(!this->root); - assert(!this->cwd); - this->root = root; - this->cwd = root; - } - - void Process__OnLastThreadExit(void* user); - - void Process::OnThreadDestruction(Thread* thread) - { - assert(thread->process == this); - kthread_mutex_lock(&threadlock); - if ( thread->prevsibling ) - thread->prevsibling->nextsibling = thread->nextsibling; - if ( thread->nextsibling ) - thread->nextsibling->prevsibling = thread->prevsibling; - if ( thread == firstthread ) - firstthread = thread->nextsibling; - if ( firstthread ) - firstthread->prevsibling = NULL; - thread->prevsibling = thread->nextsibling = NULL; - bool threadsleft = firstthread; - kthread_mutex_unlock(&threadlock); - - // We are called from the threads destructor, let it finish before we - // we handle the situation by killing ourselves. - if ( !threadsleft ) - ScheduleDeath(); - } - - void Process::ScheduleDeath() - { - // All our threads must have exited at this point. - assert(!firstthread); - Worker::Schedule(Process__OnLastThreadExit, this); - } - - // Useful for killing a partially constructed process without waiting for - // it to die and garbage collect its zombie. It is not safe to access this - // process after this call as another thread may garbage collect it. - void Process::AbortConstruction() - { - nozombify = true; + // We are called from the threads destructor, let it finish before we + // we handle the situation by killing ourselves. + if ( !threadsleft ) ScheduleDeath(); - } +} - void Process__OnLastThreadExit(void* user) - { - return ((Process*) user)->OnLastThreadExit(); - } +void Process::ScheduleDeath() +{ + // All our threads must have exited at this point. + assert(!firstthread); + Worker::Schedule(Process__OnLastThreadExit, this); +} - void Process::OnLastThreadExit() - { - LastPrayer(); - } +// Useful for killing a partially constructed process without waiting for +// it to die and garbage collect its zombie. It is not safe to access this +// process after this call as another thread may garbage collect it. +void Process::AbortConstruction() +{ + nozombify = true; + ScheduleDeath(); +} - static void SwitchCurrentAddrspace(addr_t addrspace, void* user) - { - ((Thread*) user)->SwitchAddressSpace(addrspace); - } +void Process__OnLastThreadExit(void* user) +{ + return ((Process*) user)->OnLastThreadExit(); +} - void Process::DeleteTimers() +void Process::OnLastThreadExit() +{ + LastPrayer(); +} + +static void SwitchCurrentAddrspace(addr_t addrspace, void* user) +{ + ((Thread*) user)->SwitchAddressSpace(addrspace); +} + +void Process::DeleteTimers() +{ + for ( timer_t i = 0; i < PROCESS_TIMER_NUM_MAX; i++ ) { - for ( timer_t i = 0; i < PROCESS_TIMER_NUM_MAX; i++ ) + if ( user_timers[i].timer.IsAttached() ) { - if ( user_timers[i].timer.IsAttached() ) - { - user_timers[i].timer.Cancel(); - user_timers[i].timer.Detach(); - } + user_timers[i].timer.Cancel(); + user_timers[i].timer.Detach(); } } +} - void Process::LastPrayer() +void Process::LastPrayer() +{ + assert(this); + // This must never be called twice. + assert(!iszombie); + + // This must be called from a thread using another address space as the + // address space of this process is about to be destroyed. + Thread* curthread = CurrentThread(); + assert(curthread->process != this); + + // This can't be called if the process is still alive. + assert(!firstthread); + + // Disarm and detach all the timers in the process. + DeleteTimers(); + if ( alarm_timer.IsAttached() ) { - assert(this); - // This must never be called twice. - assert(!iszombie); - - // This must be called from a thread using another address space as the - // address space of this process is about to be destroyed. - Thread* curthread = CurrentThread(); - assert(curthread->process != this); - - // This can't be called if the process is still alive. - assert(!firstthread); - - // Disarm and detach all the timers in the process. - DeleteTimers(); - if ( alarm_timer.IsAttached() ) - { - alarm_timer.Cancel(); - alarm_timer.Detach(); - } - - // We need to temporarily reload the correct addrese space of the dying - // process such that we can unmap and free its memory. - addr_t prevaddrspace = curthread->SwitchAddressSpace(addrspace); - - ResetAddressSpace(); - - if ( dtable ) dtable.Reset(); - if ( cwd ) cwd.Reset(); - if ( root ) root.Reset(); - if ( mtable ) mtable.Reset(); - - // Destroy the address space and safely switch to the replacement - // address space before things get dangerous. - Memory::DestroyAddressSpace(prevaddrspace, - SwitchCurrentAddrspace, - curthread); - addrspace = 0; - - // Unload the process symbol and string tables. - delete[] symbol_table; symbol_table = NULL; - delete[] string_table; string_table = NULL; - - // Init is nice and will gladly raise our orphaned children and zombies. - Process* init = Scheduler::GetInitProcess(); - assert(init); - kthread_mutex_lock(&childlock); - while ( firstchild ) - { - ScopedLock firstchildlock(&firstchild->parentlock); - ScopedLock initlock(&init->childlock); - Process* process = firstchild; - firstchild = process->nextsibling; - process->parent = init; - process->prevsibling = NULL; - process->nextsibling = init->firstchild; - if ( init->firstchild ) - init->firstchild->prevsibling = process; - init->firstchild = process; - } - // Since we have no more children (they are with init now), we don't - // have to worry about new zombie processes showing up, so just collect - // those that are left. Then we satisfiy the invariant !zombiechild that - // applies on process termination. - bool hadzombies = zombiechild; - while ( zombiechild ) - { - ScopedLock zombiechildlock(&zombiechild->parentlock); - ScopedLock initlock(&init->childlock); - Process* zombie = zombiechild; - zombiechild = zombie->nextsibling; - zombie->prevsibling = NULL; - zombie->nextsibling = init->zombiechild; - if ( init->zombiechild ) - init->zombiechild->prevsibling = zombie; - init->zombiechild = zombie; - } - kthread_mutex_unlock(&childlock); - - if ( hadzombies ) - init->NotifyNewZombies(); - - iszombie = true; - - bool zombify = !nozombify; - - // This class instance will be destroyed by our parent process when it - // has received and acknowledged our death. - kthread_mutex_lock(&parentlock); - if ( parent ) - parent->NotifyChildExit(this, zombify); - kthread_mutex_unlock(&parentlock); - - // If nobody is waiting for us, then simply commit suicide. - if ( !zombify ) - delete this; + alarm_timer.Cancel(); + alarm_timer.Detach(); } - void Process::ResetAddressSpace() + // We need to temporarily reload the correct addrese space of the dying + // process such that we can unmap and free its memory. + addr_t prevaddrspace = curthread->SwitchAddressSpace(addrspace); + + ResetAddressSpace(); + + if ( dtable ) dtable.Reset(); + if ( cwd ) cwd.Reset(); + if ( root ) root.Reset(); + if ( mtable ) mtable.Reset(); + + // Destroy the address space and safely switch to the replacement + // address space before things get dangerous. + Memory::DestroyAddressSpace(prevaddrspace, + SwitchCurrentAddrspace, + curthread); + addrspace = 0; + + // Unload the process symbol and string tables. + delete[] symbol_table; symbol_table = NULL; + delete[] string_table; string_table = NULL; + + // Init is nice and will gladly raise our orphaned children and zombies. + Process* init = Scheduler::GetInitProcess(); + assert(init); + kthread_mutex_lock(&childlock); + while ( firstchild ) { - assert(Memory::GetAddressSpace() == addrspace); - ProcessSegment* tmp = segments; + ScopedLock firstchildlock(&firstchild->parentlock); + ScopedLock initlock(&init->childlock); + Process* process = firstchild; + firstchild = process->nextsibling; + process->parent = init; + process->prevsibling = NULL; + process->nextsibling = init->firstchild; + if ( init->firstchild ) + init->firstchild->prevsibling = process; + init->firstchild = process; + } + // Since we have no more children (they are with init now), we don't + // have to worry about new zombie processes showing up, so just collect + // those that are left. Then we satisfiy the invariant !zombiechild that + // applies on process termination. + bool hadzombies = zombiechild; + while ( zombiechild ) + { + ScopedLock zombiechildlock(&zombiechild->parentlock); + ScopedLock initlock(&init->childlock); + Process* zombie = zombiechild; + zombiechild = zombie->nextsibling; + zombie->prevsibling = NULL; + zombie->nextsibling = init->zombiechild; + if ( init->zombiechild ) + init->zombiechild->prevsibling = zombie; + init->zombiechild = zombie; + } + kthread_mutex_unlock(&childlock); + + if ( hadzombies ) + init->NotifyNewZombies(); + + iszombie = true; + + bool zombify = !nozombify; + + // This class instance will be destroyed by our parent process when it + // has received and acknowledged our death. + kthread_mutex_lock(&parentlock); + if ( parent ) + parent->NotifyChildExit(this, zombify); + kthread_mutex_unlock(&parentlock); + + // If nobody is waiting for us, then simply commit suicide. + if ( !zombify ) + delete this; +} + +void Process::ResetAddressSpace() +{ + assert(Memory::GetAddressSpace() == addrspace); + ProcessSegment* tmp = segments; + while ( tmp != NULL ) + { + Memory::UnmapRange(tmp->position, tmp->size); + ProcessSegment* todelete = tmp; + tmp = tmp->next; + delete todelete; + } + + segments = NULL; +} + +void Process::NotifyChildExit(Process* child, bool zombify) +{ + kthread_mutex_lock(&childlock); + + if ( child->prevsibling ) + child->prevsibling->nextsibling = child->nextsibling; + if ( child->nextsibling ) + child->nextsibling->prevsibling = child->prevsibling; + if ( firstchild == child ) + firstchild = child->nextsibling; + if ( firstchild ) + firstchild->prevsibling = NULL; + + if ( zombify ) + { + if ( zombiechild ) + zombiechild->prevsibling = child; + child->prevsibling = NULL; + child->nextsibling = zombiechild; + zombiechild = child; + } + + kthread_mutex_unlock(&childlock); + + if ( zombify ) + NotifyNewZombies(); +} + +void Process::NotifyNewZombies() +{ + ScopedLock lock(&childlock); + // TODO: Send SIGCHLD here? + if ( zombiewaiting ) + kthread_cond_broadcast(&zombiecond); +} + +pid_t Process::Wait(pid_t thepid, int* status, int options) +{ + // TODO: Process groups are not supported yet. + if ( thepid < -1 || thepid == 0 ) { errno = ENOSYS; return -1; } + + ScopedLock lock(&childlock); + + // A process can only wait if it has children. + if ( !firstchild && !zombiechild ) + return errno = ECHILD, -1; + + // Processes can only wait for their own children to exit. + if ( 0 < thepid ) + { + // TODO: This is a slow but multithread safe way to verify that the + // target process has the correct parent. + bool found = false; + for ( Process* p = firstchild; !found && p; p = p->nextsibling ) + if ( p->pid == thepid ) + found = true; + for ( Process* p = zombiechild; !found && p; p = p->nextsibling ) + if ( p->pid == thepid ) + found = true; + if ( !found ) + return errno = ECHILD, -1; + } + + Process* zombie = NULL; + while ( !zombie ) + { + for ( zombie = zombiechild; zombie; zombie = zombie->nextsibling ) + if ( thepid == -1 || thepid == zombie->pid ) + break; + if ( zombie ) + break; + if ( options & WNOHANG ) + return 0; + zombiewaiting++; + unsigned long r = kthread_cond_wait_signal(&zombiecond, &childlock); + zombiewaiting--; + if ( !r ) + return errno = EINTR, -1; + } + + if ( zombie->prevsibling ) + zombie->prevsibling->nextsibling = zombie->nextsibling; + if ( zombie->nextsibling ) + zombie->nextsibling->prevsibling = zombie->prevsibling; + if ( zombiechild == zombie ) + zombiechild = zombie->nextsibling; + if ( zombiechild ) + zombiechild->prevsibling = NULL; + + thepid = zombie->pid; + + // It is safe to access these clocks directly as the child process is no + // longer running at this point and the values are nicely frozen. + child_execute_clock.Advance(zombie->child_execute_clock.current_time); + child_system_clock.Advance(zombie->child_system_clock.current_time); + + int exitstatus = zombie->exitstatus; + if ( exitstatus < 0 ) + exitstatus = W_EXITCODE(128 + SIGKILL, SIGKILL); + + // TODO: Validate that status is a valid user-space int! + if ( status ) + *status = exitstatus; + + // And so, the process was fully deleted. + delete zombie; + + return thepid; +} + +pid_t sys_waitpid(pid_t pid, int* status, int options) +{ + return CurrentProcess()->Wait(pid, status, options); +} + +void Process::Exit(int status) +{ + ScopedLock lock(&threadlock); + // Status codes can only contain 8 bits according to ISO C and POSIX. + if ( exitstatus == -1 ) + exitstatus = W_EXITCODE(status & 0xFF, 0); + + // Broadcast SIGKILL to all our threads which will begin our long path + // of process termination. We simply can't stop the threads as they may + // be running in kernel mode doing dangerous stuff. This thread will be + // destroyed by SIGKILL once the system call returns. + for ( Thread* t = firstthread; t; t = t->nextsibling ) + t->DeliverSignal(SIGKILL); +} + +int sys_exit(int status) +{ + CurrentProcess()->Exit(status); + return 0; +} + +bool Process::DeliverSignal(int signum) +{ + // TODO: How to handle signals that kill the process? + if ( firstthread ) + return firstthread->DeliverSignal(signum); + return errno = EINIT, false; +} + +void Process::AddChildProcess(Process* child) +{ + ScopedLock mylock(&childlock); + ScopedLock itslock(&child->parentlock); + assert(!child->parent); + assert(!child->nextsibling); + assert(!child->prevsibling); + child->parent = this; + child->nextsibling = firstchild; + child->prevsibling = NULL; + if ( firstchild ) + firstchild->prevsibling = child; + firstchild = child; +} + +Ref Process::GetMTable() +{ + ScopedLock lock(&ptrlock); + assert(mtable); + return mtable; +} + +Ref Process::GetDTable() +{ + ScopedLock lock(&ptrlock); + assert(dtable); + return dtable; +} + +Ref Process::GetRoot() +{ + ScopedLock lock(&ptrlock); + assert(root); + return root; +} + +Ref Process::GetCWD() +{ + ScopedLock lock(&ptrlock); + assert(cwd); + return cwd; +} + +void Process::SetRoot(Ref newroot) +{ + ScopedLock lock(&ptrlock); + assert(newroot); + root = newroot; +} + +void Process::SetCWD(Ref newcwd) +{ + ScopedLock lock(&ptrlock); + assert(newcwd); + cwd = newcwd; +} + +Ref Process::GetDescriptor(int fd) +{ + ScopedLock lock(&ptrlock); + assert(dtable); + return dtable->Get(fd); +} + +Process* Process::Fork() +{ + assert(CurrentProcess() == this); + + Process* clone = new Process; + if ( !clone ) + return NULL; + + ProcessSegment* clonesegments = NULL; + + // Fork the segment list. + if ( segments ) + { + clonesegments = segments->Fork(); + if ( clonesegments == NULL ) { delete clone; return NULL; } + } + + // Fork address-space here and copy memory. + clone->addrspace = Memory::Fork(); + if ( !clone->addrspace ) + { + // Delete the segment list, since they are currently bogus. + ProcessSegment* tmp = clonesegments; while ( tmp != NULL ) { - Memory::UnmapRange(tmp->position, tmp->size); ProcessSegment* todelete = tmp; tmp = tmp->next; delete todelete; } - segments = NULL; + delete clone; return NULL; } - void Process::NotifyChildExit(Process* child, bool zombify) + // Now it's too late to clean up here, if anything goes wrong, we simply + // ask the process to commit suicide before it goes live. + clone->segments = clonesegments; + + // Remember the relation to the child process. + AddChildProcess(clone); + + // Initialize everything that is safe and can't fail. + clone->mmapfrom = mmapfrom; + + kthread_mutex_lock(&ptrlock); + clone->root = root; + clone->cwd = cwd; + kthread_mutex_unlock(&ptrlock); + + // Initialize things that can fail and abort if needed. + bool failure = false; + + kthread_mutex_lock(&ptrlock); + if ( !(clone->dtable = dtable->Fork()) ) + failure = true; + //if ( !(clone->mtable = mtable->Fork()) ) + // failure = true; + clone->mtable = mtable; + kthread_mutex_unlock(&ptrlock); + + kthread_mutex_lock(&idlock); + clone->uid = uid; + clone->gid = gid; + clone->euid = euid; + clone->egid = egid; + clone->umask = umask; + kthread_mutex_unlock(&idlock); + + if ( !(clone->program_image_path = String::Clone(program_image_path)) ) + failure = false; + + if ( string_table && (clone->string_table = new char[string_table_length]) ) { - kthread_mutex_lock(&childlock); + memcpy(clone->string_table, string_table, string_table_length); + clone->string_table_length = string_table_length; + } - if ( child->prevsibling ) - child->prevsibling->nextsibling = child->nextsibling; - if ( child->nextsibling ) - child->nextsibling->prevsibling = child->prevsibling; - if ( firstchild == child ) - firstchild = child->nextsibling; - if ( firstchild ) - firstchild->prevsibling = NULL; - - if ( zombify ) + if ( clone->string_table && symbol_table && + (clone->symbol_table = new Symbol[symbol_table_length]) ) + { + for ( size_t i = 0; i < symbol_table_length; i++ ) { - if ( zombiechild ) - zombiechild->prevsibling = child; - child->prevsibling = NULL; - child->nextsibling = zombiechild; - zombiechild = child; + clone->symbol_table[i].address = symbol_table[i].address; + clone->symbol_table[i].size = symbol_table[i].size; + clone->symbol_table[i].name = + (const char*)((uintptr_t) symbol_table[i].name - + (uintptr_t) string_table + + (uintptr_t) clone->string_table); } - - kthread_mutex_unlock(&childlock); - - if ( zombify ) - NotifyNewZombies(); + clone->symbol_table_length = symbol_table_length; } - void Process::NotifyNewZombies() + if ( pid == 1) + assert(dtable->Get(1)); + + if ( pid == 1) + assert(clone->dtable->Get(1)); + + // If the proces creation failed, ask the process to commit suicide and + // not become a zombie, as we don't wait for it to exit. It will clean + // up all the above resources and delete itself. + if ( failure ) { - ScopedLock lock(&childlock); - // TODO: Send SIGCHLD here? - if ( zombiewaiting ) - kthread_cond_broadcast(&zombiecond); + clone->AbortConstruction(); + return NULL; } - pid_t Process::Wait(pid_t thepid, int* status, int options) - { - // TODO: Process groups are not supported yet. - if ( thepid < -1 || thepid == 0 ) { errno = ENOSYS; return -1; } - - ScopedLock lock(&childlock); - - // A process can only wait if it has children. - if ( !firstchild && !zombiechild ) { errno = ECHILD; return -1; } - - // Processes can only wait for their own children to exit. - if ( 0 < thepid ) - { - // TODO: This is a slow but multithread safe way to verify that the - // target process has the correct parent. - bool found = false; - for ( Process* p = firstchild; !found && p; p = p->nextsibling ) - if ( p->pid == thepid ) - found = true; - for ( Process* p = zombiechild; !found && p; p = p->nextsibling ) - if ( p->pid == thepid ) - found = true; - if ( !found ) { errno = ECHILD; return -1; } - } - - Process* zombie = NULL; - while ( !zombie ) - { - for ( zombie = zombiechild; zombie; zombie = zombie->nextsibling ) - if ( thepid == -1 || thepid == zombie->pid ) - break; - if ( zombie ) - break; - if ( options & WNOHANG ) - return 0; - zombiewaiting++; - unsigned long r = kthread_cond_wait_signal(&zombiecond, &childlock); - zombiewaiting--; - if ( !r ) { errno = EINTR; return -1; } - } - - if ( zombie->prevsibling ) - zombie->prevsibling->nextsibling = zombie->nextsibling; - if ( zombie->nextsibling ) - zombie->nextsibling->prevsibling = zombie->prevsibling; - if ( zombiechild == zombie ) - zombiechild = zombie->nextsibling; - if ( zombiechild ) - zombiechild->prevsibling = NULL; - - thepid = zombie->pid; - - // It is safe to access these clocks directly as the child process is no - // longer running at this point and the values are nicely frozen. - child_execute_clock.Advance(zombie->child_execute_clock.current_time); - child_system_clock.Advance(zombie->child_system_clock.current_time); - - int exitstatus = zombie->exitstatus; - if ( exitstatus < 0 ) - exitstatus = W_EXITCODE(128 + SIGKILL, SIGKILL); - - // TODO: Validate that status is a valid user-space int! - if ( status ) - *status = exitstatus; - - // And so, the process was fully deleted. - delete zombie; - - return thepid; - } - - pid_t SysWait(pid_t pid, int* status, int options) - { - return CurrentProcess()->Wait(pid, status, options); - } - - void Process::Exit(int status) - { - ScopedLock lock(&threadlock); - // Status codes can only contain 8 bits according to ISO C and POSIX. - if ( exitstatus == -1 ) - exitstatus = W_EXITCODE(status & 0xFF, 0); - - // Broadcast SIGKILL to all our threads which will begin our long path - // of process termination. We simply can't stop the threads as they may - // be running in kernel mode doing dangerous stuff. This thread will be - // destroyed by SIGKILL once the system call returns. - for ( Thread* t = firstthread; t; t = t->nextsibling ) - t->DeliverSignal(SIGKILL); - } - - int SysExit(int status) - { - CurrentProcess()->Exit(status); - return 0; - } - - bool Process::DeliverSignal(int signum) - { - // TODO: How to handle signals that kill the process? - if ( firstthread ) - return firstthread->DeliverSignal(signum); - errno = EINIT; - return false; - } - - void Process::AddChildProcess(Process* child) - { - ScopedLock mylock(&childlock); - ScopedLock itslock(&child->parentlock); - assert(!child->parent); - assert(!child->nextsibling); - assert(!child->prevsibling); - child->parent = this; - child->nextsibling = firstchild; - child->prevsibling = NULL; - if ( firstchild ) - firstchild->prevsibling = child; - firstchild = child; - } - - Ref Process::GetMTable() - { - ScopedLock lock(&ptrlock); - assert(mtable); - return mtable; - } - - Ref Process::GetDTable() - { - ScopedLock lock(&ptrlock); - assert(dtable); - return dtable; - } - - Ref Process::GetRoot() - { - ScopedLock lock(&ptrlock); - assert(root); - return root; - } - - Ref Process::GetCWD() - { - ScopedLock lock(&ptrlock); - assert(cwd); - return cwd; - } - - void Process::SetRoot(Ref newroot) - { - ScopedLock lock(&ptrlock); - assert(newroot); - root = newroot; - } - - void Process::SetCWD(Ref newcwd) - { - ScopedLock lock(&ptrlock); - assert(newcwd); - cwd = newcwd; - } - - Ref Process::GetDescriptor(int fd) - { - ScopedLock lock(&ptrlock); - assert(dtable); - return dtable->Get(fd); - } - - Process* Process::Fork() - { - assert(CurrentProcess() == this); - - Process* clone = new Process; - if ( !clone ) { return NULL; } - - ProcessSegment* clonesegments = NULL; - - // Fork the segment list. - if ( segments ) - { - clonesegments = segments->Fork(); - if ( clonesegments == NULL ) { delete clone; return NULL; } - } - - // Fork address-space here and copy memory. - clone->addrspace = Memory::Fork(); - if ( !clone->addrspace ) - { - // Delete the segment list, since they are currently bogus. - ProcessSegment* tmp = clonesegments; - while ( tmp != NULL ) - { - ProcessSegment* todelete = tmp; - tmp = tmp->next; - delete todelete; - } - - delete clone; return NULL; - } - - // Now it's too late to clean up here, if anything goes wrong, we simply - // ask the process to commit suicide before it goes live. - clone->segments = clonesegments; - - // Remember the relation to the child process. - AddChildProcess(clone); - - // Initialize everything that is safe and can't fail. - clone->mmapfrom = mmapfrom; - - kthread_mutex_lock(&ptrlock); - clone->root = root; - clone->cwd = cwd; - kthread_mutex_unlock(&ptrlock); - - // Initialize things that can fail and abort if needed. - bool failure = false; - - kthread_mutex_lock(&ptrlock); - if ( !(clone->dtable = dtable->Fork()) ) - failure = true; - //if ( !(clone->mtable = mtable->Fork()) ) - // failure = true; - clone->mtable = mtable; - kthread_mutex_unlock(&ptrlock); - - kthread_mutex_lock(&idlock); - clone->uid = uid; - clone->gid = gid; - clone->euid = euid; - clone->egid = egid; - clone->umask = umask; - kthread_mutex_unlock(&idlock); - - if ( !(clone->program_image_path = String::Clone(program_image_path)) ) - failure = false; - - if ( string_table && (clone->string_table = new char[string_table_length]) ) - { - memcpy(clone->string_table, string_table, string_table_length); - clone->string_table_length = string_table_length; - } - - if ( clone->string_table && symbol_table && - (clone->symbol_table = new Symbol[symbol_table_length]) ) - { - for ( size_t i = 0; i < symbol_table_length; i++ ) - { - clone->symbol_table[i].address = symbol_table[i].address; - clone->symbol_table[i].size = symbol_table[i].size; - clone->symbol_table[i].name = - (const char*)((uintptr_t) symbol_table[i].name - - (uintptr_t) string_table + - (uintptr_t) clone->string_table); - } - clone->symbol_table_length = symbol_table_length; - } - - - if ( pid == 1) - assert(dtable->Get(1)); - - if ( pid == 1) - assert(clone->dtable->Get(1)); - - // If the proces creation failed, ask the process to commit suicide and - // not become a zombie, as we don't wait for it to exit. It will clean - // up all the above resources and delete itself. - if ( failure ) - { - clone->AbortConstruction(); - return NULL; - } - - return clone; - } - - void Process::ResetForExecute() - { - // TODO: Delete all threads and their stacks. - - string_table_length = 0; - symbol_table_length = 0; - delete[] string_table; string_table = NULL; - delete[] symbol_table; symbol_table = NULL; - - DeleteTimers(); - - ResetAddressSpace(); - } - - int Process::Execute(const char* programname, const uint8_t* program, - size_t programsize, int argc, const char* const* argv, - int envc, const char* const* envp, - CPU::InterruptRegisters* regs) - { - (void) programname; - assert(CurrentProcess() == this); - - char* programname_clone = String::Clone(programname); - if ( !programname_clone ) - return -1; - - addr_t entry = ELF::Construct(CurrentProcess(), program, programsize); - if ( !entry ) { delete[] programname_clone; return -1; } - - delete[] program_image_path; - program_image_path = programname_clone; programname_clone = NULL; - - // TODO: This may be an ugly hack! - // TODO: Move this to x86/process.cpp. - - addr_t stackpos = CurrentThread()->stackpos + CurrentThread()->stacksize; - - // Alright, move argv onto the new stack! First figure out exactly how - // big argv actually is. - addr_t argvpos = stackpos - sizeof(char*) * (argc+1); - char** stackargv = (char**) argvpos; - - size_t argvsize = 0; - for ( int i = 0; i < argc; i++ ) - { - size_t len = strlen(argv[i]) + 1; - argvsize += len; - char* dest = ((char*) argvpos) - argvsize; - stackargv[i] = dest; - memcpy(dest, argv[i], len); - } - - stackargv[argc] = NULL; - - if ( argvsize % 16UL ) { argvsize += 16 - (argvsize % 16UL); } - - // And then move envp onto the stack. - addr_t envppos = argvpos - argvsize - sizeof(char*) * (envc+1); - char** stackenvp = (char**) envppos; - - size_t envpsize = 0; - for ( int i = 0; i < envc; i++ ) - { - size_t len = strlen(envp[i]) + 1; - envpsize += len; - char* dest = ((char*) envppos) - envpsize; - stackenvp[i] = dest; - memcpy(dest, envp[i], len); - } - - stackenvp[envc] = NULL; - - if ( envpsize % 16UL ) { envpsize += 16 - (envpsize % 16UL); } - - stackpos = envppos - envpsize; - - dtable->OnExecute(); - - ExecuteCPU(argc, stackargv, envc, stackenvp, stackpos, entry, regs); - - return 0; - } - - // TODO. This is a hack. Please remove this when execve is moved to another - // file/class, it doesn't belong here, it's a program loader ffs! - Ref Process::Open(ioctx_t* ctx, const char* path, int flags, mode_t mode) - { - // TODO: Locking the root/cwd pointers. How should that be arranged? - Ref dir = path[0] == '/' ? root : cwd; - return dir->open(ctx, path, flags, mode); - } - - int SysExecVE(const char* _filename, char* const _argv[], char* const _envp[]) - { - char* filename; - int argc; - int envc; - char** argv; - char** envp; - ioctx_t ctx; - Ref desc; - struct stat st; - size_t sofar; - size_t count; - uint8_t* buffer; - int result = -1; - Process* process = CurrentProcess(); - CPU::InterruptRegisters regs; - memset(®s, 0, sizeof(regs)); - - filename = String::Clone(_filename); - if ( !filename ) { goto cleanup_done; } - - for ( argc = 0; _argv && _argv[argc]; argc++ ); - for ( envc = 0; _envp && _envp[envc]; envc++ ); - - argv = new char*[argc+1]; - if ( !argv ) { goto cleanup_filename; } - memset(argv, 0, sizeof(char*) * (argc+1)); - - for ( int i = 0; i < argc; i++ ) - { - argv[i] = String::Clone(_argv[i]); - if ( !argv[i] ) { goto cleanup_argv; } - } - - envp = new char*[envc+1]; - if ( !envp ) { goto cleanup_argv; } - envc = envc; - memset(envp, 0, sizeof(char*) * (envc+1)); - - for ( int i = 0; i < envc; i++ ) - { - envp[i] = String::Clone(_envp[i]); - if ( !envp[i] ) { goto cleanup_envp; } - } - - SetupKernelIOCtx(&ctx); - - // TODO: Somehow mark the executable as busy and don't permit writes? - desc = process->Open(&ctx, filename, O_READ | O_WRITE, 0); - if ( !desc ) { goto cleanup_envp; } - - if ( desc->stat(&ctx, &st) ) { goto cleanup_desc; } - if ( st.st_size < 0 ) { errno = EINVAL; goto cleanup_desc; } - if ( SIZE_MAX < (uintmax_t) st.st_size ) { errno = ERANGE; goto cleanup_desc; } - - count = (size_t) st.st_size; - buffer = new uint8_t[count]; - if ( !buffer ) { goto cleanup_desc; } - sofar = 0; - while ( sofar < count ) - { - ssize_t bytesread = desc->read(&ctx, buffer + sofar, count - sofar); - if ( bytesread < 0 ) { goto cleanup_buffer; } - if ( bytesread == 0 ) { errno = EEOF; goto cleanup_buffer; } - sofar += bytesread; - } - - result = process->Execute(filename, buffer, count, argc, argv, envc, - envp, ®s); - - cleanup_buffer: - delete[] buffer; - cleanup_desc: - desc.Reset(); - cleanup_envp: - for ( int i = 0; i < envc; i++) { delete[] envp[i]; } - delete[] envp; - cleanup_argv: - for ( int i = 0; i < argc; i++) { delete[] argv[i]; } - delete[] argv; - cleanup_filename: - delete[] filename; - cleanup_done: - if ( !result ) { CPU::LoadRegisters(®s); } - return result; - } - - pid_t SysTFork(int flags, tforkregs_t* regs) - { - if ( Signal::IsPending() ) { errno = EINTR; return -1; } - - // TODO: Properly support tfork(2). - if ( flags != SFFORK ) { errno = ENOSYS; return -1; } - - CPU::InterruptRegisters cpuregs; - InitializeThreadRegisters(&cpuregs, regs); - - // TODO: Is it a hack to create a new kernel stack here? - Thread* curthread = CurrentThread(); - uint8_t* newkernelstack = new uint8_t[curthread->kernelstacksize]; - if ( !newkernelstack ) { return -1; } - - Process* clone = CurrentProcess()->Fork(); - if ( !clone ) { delete[] newkernelstack; return -1; } - - // If the thread could not be created, make the process commit suicide - // in a manner such that we don't wait for its zombie. - Thread* thread = CreateKernelThread(clone, &cpuregs); - if ( !thread ) - { - clone->AbortConstruction(); - return -1; - } - - thread->kernelstackpos = (addr_t) newkernelstack; - thread->kernelstacksize = curthread->kernelstacksize; - thread->kernelstackmalloced = true; - thread->stackpos = curthread->stackpos; - thread->stacksize = curthread->stacksize; - thread->sighandler = curthread->sighandler; - - StartKernelThread(thread); - - return clone->pid; - } - - pid_t SysGetPID() - { - return CurrentProcess()->pid; - } - - pid_t Process::GetParentProcessId() - { - ScopedLock lock(&parentlock); - if( !parent ) - return 0; - return parent->pid; - } - - pid_t SysGetParentPID() - { - return CurrentProcess()->GetParentProcessId(); - } - - pid_t nextpidtoallocate; - kthread_mutex_t pidalloclock; - - pid_t Process::AllocatePID() - { - ScopedLock lock(&pidalloclock); - return nextpidtoallocate++; - } - - // TODO: This is not thread safe. - pid_t Process::HackGetForegroundProcess() - { - for ( pid_t i = nextpidtoallocate; 1 <= i; i-- ) - { - Process* process = Get(i); - if ( !process ) - continue; - if ( process->pid <= 1 ) - continue; - if ( process->iszombie ) - continue; - return i; - } - return 0; - } - - int ProcessCompare(Process* a, Process* b) - { - if ( a->pid < b->pid ) { return -1; } - if ( a->pid > b->pid ) { return 1; } - return 0; - } - - int ProcessPIDCompare(Process* a, pid_t pid) - { - if ( a->pid < pid ) { return -1; } - if ( a->pid > pid ) { return 1; } - return 0; - } - - SortedList* pidlist; - - Process* Process::Get(pid_t pid) - { - ScopedLock lock(&pidalloclock); - size_t index = pidlist->Search(ProcessPIDCompare, pid); - if ( index == SIZE_MAX ) { return NULL; } - - return pidlist->Get(index); - } - - bool Process::Put(Process* process) - { - ScopedLock lock(&pidalloclock); - return pidlist->Add(process); - } - - void Process::Remove(Process* process) - { - ScopedLock lock(&pidalloclock); - size_t index = pidlist->Search(process); - assert(index != SIZE_MAX); - - pidlist->Remove(index); - } - - void* SysSbrk(intptr_t increment) - { - Process* process = CurrentProcess(); - ProcessSegment* dataseg = NULL; - for ( ProcessSegment* iter = process->segments; iter; iter = iter->next ) - { - if ( !iter->type == SEG_DATA ) { continue; } - if ( dataseg && iter->position < dataseg->position ) { continue; } - dataseg = iter; - } - if ( !dataseg ) { errno = ENOMEM; return (void*) -1UL; } - addr_t currentend = dataseg->position + dataseg->size; - addr_t newend = currentend + increment; - if ( newend < dataseg->position ) { errno = EINVAL; return (void*) -1UL; } - if ( newend < currentend ) - { - addr_t unmapfrom = Page::AlignUp(newend); - if ( unmapfrom < currentend ) - { - size_t unmapbytes = Page::AlignUp(currentend - unmapfrom); - Memory::UnmapRange(unmapfrom, unmapbytes); - } - } - else if ( currentend < newend ) - { - // TODO: HACK: Make a safer way of expanding the data segment - // without segments possibly colliding! - addr_t mapfrom = Page::AlignUp(currentend); - if ( mapfrom < newend ) - { - size_t mapbytes = Page::AlignUp(newend - mapfrom); - int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE; - if ( !Memory::MapRange(mapfrom, mapbytes, prot) ) - { - return (void*) -1UL; - } - } - } - dataseg->size += increment; - return (void*) newend; - } - - size_t SysGetPageSize() - { - return Page::Size(); - } - - mode_t sys_umask(mode_t newmask) - { - Process* process = CurrentProcess(); - ScopedLock lock(&process->idlock); - mode_t oldmask = process->umask; - process->umask = newmask & 0666; - return oldmask; - } - - void Process::Init() - { - Syscall::Register(SYSCALL_EXEC, (void*) SysExecVE); - Syscall::Register(SYSCALL_TFORK, (void*) SysTFork); - Syscall::Register(SYSCALL_GETPID, (void*) SysGetPID); - Syscall::Register(SYSCALL_GETPPID, (void*) SysGetParentPID); - Syscall::Register(SYSCALL_EXIT, (void*) SysExit); - Syscall::Register(SYSCALL_UMASK, (void*) sys_umask); - Syscall::Register(SYSCALL_WAIT, (void*) SysWait); - Syscall::Register(SYSCALL_SBRK, (void*) SysSbrk); - Syscall::Register(SYSCALL_GET_PAGE_SIZE, (void*) SysGetPageSize); - - pidalloclock = KTHREAD_MUTEX_INITIALIZER; - nextpidtoallocate = 0; - - pidlist = new SortedList(ProcessCompare); - if ( !pidlist ) { Panic("could not allocate pidlist\n"); } - } - - addr_t Process::AllocVirtualAddr(size_t size) - { - return (mmapfrom -= size); - } + return clone; } + +void Process::ResetForExecute() +{ + // TODO: Delete all threads and their stacks. + + string_table_length = 0; + symbol_table_length = 0; + delete[] string_table; string_table = NULL; + delete[] symbol_table; symbol_table = NULL; + + DeleteTimers(); + + ResetAddressSpace(); +} + +int Process::Execute(const char* programname, const uint8_t* program, + size_t programsize, int argc, const char* const* argv, + int envc, const char* const* envp, + CPU::InterruptRegisters* regs) +{ + assert(CurrentProcess() == this); + + char* programname_clone = String::Clone(programname); + if ( !programname_clone ) + return -1; + + addr_t entry = ELF::Construct(CurrentProcess(), program, programsize); + if ( !entry ) { delete[] programname_clone; return -1; } + + delete[] program_image_path; + program_image_path = programname_clone; programname_clone = NULL; + + // TODO: This may be an ugly hack! + // TODO: Move this to x86/process.cpp. + + addr_t stackpos = CurrentThread()->stackpos + CurrentThread()->stacksize; + + // Alright, move argv onto the new stack! First figure out exactly how + // big argv actually is. + addr_t argvpos = stackpos - sizeof(char*) * (argc+1); + char** stackargv = (char**) argvpos; + + size_t argvsize = 0; + for ( int i = 0; i < argc; i++ ) + { + size_t len = strlen(argv[i]) + 1; + argvsize += len; + char* dest = ((char*) argvpos) - argvsize; + stackargv[i] = dest; + memcpy(dest, argv[i], len); + } + + stackargv[argc] = NULL; + + if ( argvsize % 16UL ) + argvsize += 16 - argvsize % 16UL; + + // And then move envp onto the stack. + addr_t envppos = argvpos - argvsize - sizeof(char*) * (envc+1); + char** stackenvp = (char**) envppos; + + size_t envpsize = 0; + for ( int i = 0; i < envc; i++ ) + { + size_t len = strlen(envp[i]) + 1; + envpsize += len; + char* dest = ((char*) envppos) - envpsize; + stackenvp[i] = dest; + memcpy(dest, envp[i], len); + } + + stackenvp[envc] = NULL; + + if ( envpsize % 16UL ) + envpsize += 16 - envpsize % 16UL; + + stackpos = envppos - envpsize; + + dtable->OnExecute(); + + ExecuteCPU(argc, stackargv, envc, stackenvp, stackpos, entry, regs); + + return 0; +} + +// TODO. This is a hack. Please remove this when execve is moved to another +// file/class, it doesn't belong here, it's a program loader ffs! +Ref Process::Open(ioctx_t* ctx, const char* path, int flags, mode_t mode) +{ + // TODO: Locking the root/cwd pointers. How should that be arranged? + Ref dir = path[0] == '/' ? root : cwd; + return dir->open(ctx, path, flags, mode); +} + +int sys_execve(const char* _filename, char* const _argv[], char* const _envp[]) +{ + char* filename; + int argc; + int envc; + char** argv; + char** envp; + ioctx_t ctx; + Ref desc; + struct stat st; + size_t sofar; + size_t count; + uint8_t* buffer; + int result = -1; + Process* process = CurrentProcess(); + CPU::InterruptRegisters regs; + memset(®s, 0, sizeof(regs)); + + filename = String::Clone(_filename); + if ( !filename ) { goto cleanup_done; } + + for ( argc = 0; _argv && _argv[argc]; argc++ ); + for ( envc = 0; _envp && _envp[envc]; envc++ ); + + argv = new char*[argc+1]; + if ( !argv ) { goto cleanup_filename; } + memset(argv, 0, sizeof(char*) * (argc+1)); + + for ( int i = 0; i < argc; i++ ) + { + argv[i] = String::Clone(_argv[i]); + if ( !argv[i] ) { goto cleanup_argv; } + } + + envp = new char*[envc+1]; + if ( !envp ) { goto cleanup_argv; } + envc = envc; + memset(envp, 0, sizeof(char*) * (envc+1)); + + for ( int i = 0; i < envc; i++ ) + { + envp[i] = String::Clone(_envp[i]); + if ( !envp[i] ) { goto cleanup_envp; } + } + + SetupKernelIOCtx(&ctx); + + // TODO: Somehow mark the executable as busy and don't permit writes? + desc = process->Open(&ctx, filename, O_READ | O_WRITE, 0); + if ( !desc ) { goto cleanup_envp; } + + if ( desc->stat(&ctx, &st) ) { goto cleanup_desc; } + if ( st.st_size < 0 ) { errno = EINVAL; goto cleanup_desc; } + if ( SIZE_MAX < (uintmax_t) st.st_size ) { errno = ERANGE; goto cleanup_desc; } + + count = (size_t) st.st_size; + buffer = new uint8_t[count]; + if ( !buffer ) { goto cleanup_desc; } + sofar = 0; + while ( sofar < count ) + { + ssize_t bytesread = desc->read(&ctx, buffer + sofar, count - sofar); + if ( bytesread < 0 ) { goto cleanup_buffer; } + if ( bytesread == 0 ) { errno = EEOF; goto cleanup_buffer; } + sofar += bytesread; + } + + result = process->Execute(filename, buffer, count, argc, argv, envc, + envp, ®s); + +cleanup_buffer: + delete[] buffer; +cleanup_desc: + desc.Reset(); +cleanup_envp: + for ( int i = 0; i < envc; i++) + delete[] envp[i]; + delete[] envp; +cleanup_argv: + for ( int i = 0; i < argc; i++) + delete[] argv[i]; + delete[] argv; +cleanup_filename: + delete[] filename; +cleanup_done: + if ( !result ) { CPU::LoadRegisters(®s); } + return result; +} + +pid_t sys_tfork(int flags, tforkregs_t* regs) +{ + if ( Signal::IsPending() ) + return errno = EINTR, -1; + + // TODO: Properly support tfork(2). + if ( flags != SFFORK ) + return errno = ENOSYS, -1; + + CPU::InterruptRegisters cpuregs; + InitializeThreadRegisters(&cpuregs, regs); + + // TODO: Is it a hack to create a new kernel stack here? + Thread* curthread = CurrentThread(); + uint8_t* newkernelstack = new uint8_t[curthread->kernelstacksize]; + if ( !newkernelstack ) + return -1; + + Process* clone = CurrentProcess()->Fork(); + if ( !clone ) { delete[] newkernelstack; return -1; } + + // If the thread could not be created, make the process commit suicide + // in a manner such that we don't wait for its zombie. + Thread* thread = CreateKernelThread(clone, &cpuregs); + if ( !thread ) + { + clone->AbortConstruction(); + return -1; + } + + thread->kernelstackpos = (addr_t) newkernelstack; + thread->kernelstacksize = curthread->kernelstacksize; + thread->kernelstackmalloced = true; + thread->stackpos = curthread->stackpos; + thread->stacksize = curthread->stacksize; + thread->sighandler = curthread->sighandler; + + StartKernelThread(thread); + + return clone->pid; +} + +pid_t sys_getpid() +{ + return CurrentProcess()->pid; +} + +pid_t Process::GetParentProcessId() +{ + ScopedLock lock(&parentlock); + if( !parent ) + return 0; + return parent->pid; +} + +pid_t sys_getppid() +{ + return CurrentProcess()->GetParentProcessId(); +} + +pid_t nextpidtoallocate; +kthread_mutex_t pidalloclock; + +pid_t Process::AllocatePID() +{ + ScopedLock lock(&pidalloclock); + return nextpidtoallocate++; +} + +// TODO: This is not thread safe. +pid_t Process::HackGetForegroundProcess() +{ + for ( pid_t i = nextpidtoallocate; 1 <= i; i-- ) + { + Process* process = Get(i); + if ( !process ) + continue; + if ( process->pid <= 1 ) + continue; + if ( process->iszombie ) + continue; + return i; + } + return 0; +} + +int ProcessCompare(Process* a, Process* b) +{ + if ( a->pid < b->pid ) + return -1; + if ( a->pid > b->pid ) + return 1; + return 0; +} + +int ProcessPIDCompare(Process* a, pid_t pid) +{ + if ( a->pid < pid ) + return -1; + if ( a->pid > pid ) + return 1; + return 0; +} + +SortedList* pidlist; + +Process* Process::Get(pid_t pid) +{ + ScopedLock lock(&pidalloclock); + size_t index = pidlist->Search(ProcessPIDCompare, pid); + if ( index == SIZE_MAX ) + return NULL; + + return pidlist->Get(index); +} + +bool Process::Put(Process* process) +{ + ScopedLock lock(&pidalloclock); + return pidlist->Add(process); +} + +void Process::Remove(Process* process) +{ + ScopedLock lock(&pidalloclock); + size_t index = pidlist->Search(process); + assert(index != SIZE_MAX); + + pidlist->Remove(index); +} + +void* sys_sbrk(intptr_t increment) +{ + Process* process = CurrentProcess(); + ProcessSegment* dataseg = NULL; + for ( ProcessSegment* iter = process->segments; iter; iter = iter->next ) + { + if ( !iter->type == SEG_DATA ) + continue; + if ( dataseg && iter->position < dataseg->position ) + continue; + dataseg = iter; + } + if ( !dataseg ) + return errno = ENOMEM, (void*) -1UL; + addr_t currentend = dataseg->position + dataseg->size; + addr_t newend = currentend + increment; + if ( newend < dataseg->position ) + return errno = EINVAL, (void*) -1UL; + if ( newend < currentend ) + { + addr_t unmapfrom = Page::AlignUp(newend); + if ( unmapfrom < currentend ) + { + size_t unmapbytes = Page::AlignUp(currentend - unmapfrom); + Memory::UnmapRange(unmapfrom, unmapbytes); + } + } + else if ( currentend < newend ) + { + // TODO: HACK: Make a safer way of expanding the data segment + // without segments possibly colliding! + addr_t mapfrom = Page::AlignUp(currentend); + if ( mapfrom < newend ) + { + size_t mapbytes = Page::AlignUp(newend - mapfrom); + int prot = PROT_FORK | PROT_READ | PROT_WRITE | PROT_KREAD | PROT_KWRITE; + if ( !Memory::MapRange(mapfrom, mapbytes, prot) ) + return (void*) -1UL; + } + } + dataseg->size += increment; + return (void*) newend; +} + +size_t sys_getpagesize() +{ + return Page::Size(); +} + +mode_t sys_umask(mode_t newmask) +{ + Process* process = CurrentProcess(); + ScopedLock lock(&process->idlock); + mode_t oldmask = process->umask; + process->umask = newmask & 0666; + return oldmask; +} + +void Process::Init() +{ + Syscall::Register(SYSCALL_EXEC, (void*) sys_execve); + Syscall::Register(SYSCALL_EXIT, (void*) sys_exit); + Syscall::Register(SYSCALL_GET_PAGE_SIZE, (void*) sys_getpagesize); + Syscall::Register(SYSCALL_GETPID, (void*) sys_getpid); + Syscall::Register(SYSCALL_GETPPID, (void*) sys_getppid); + Syscall::Register(SYSCALL_SBRK, (void*) sys_sbrk); + Syscall::Register(SYSCALL_TFORK, (void*) sys_tfork); + Syscall::Register(SYSCALL_UMASK, (void*) sys_umask); + Syscall::Register(SYSCALL_WAIT, (void*) sys_waitpid); + + pidalloclock = KTHREAD_MUTEX_INITIALIZER; + nextpidtoallocate = 0; + + pidlist = new SortedList(ProcessCompare); + if ( !pidlist ) + Panic("could not allocate pidlist\n"); +} + +addr_t Process::AllocVirtualAddr(size_t size) +{ + return mmapfrom -= size; +} + +} // namespace Sortix