/*-- Copyright (c) 1989 Microsoft Corporation Module Name: srvtask.c Abstract: Implementation of PSX Process Structure APIs. Author: Mark Lucovsky (markl) 08-Mar-1989 Revision History: --*/ #include "psxsrv.h" #include "sesport.h" #ifdef _MIPS_ #include "psxmips.h" #endif #ifdef _ALPHA_ #include "psxalpha.h" #endif #ifdef _X86_ #include "psxi386.h" #endif #ifdef _PPC_ #include "psxppc.h" #endif #ifdef _IA64_ #include "psxia64.h" #endif #include extern VOID PsxFreeDirectories( IN PPSX_PROCESS ); VOID ConvertPathToWin(char *path); BOOLEAN PsxFork( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements posix fork() API Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_PROCESS ForkProcess, NewProcess; HANDLE NewProcessHandle, NewThreadHandle; THREAD_BASIC_INFORMATION ThreadBasicInfo; PVOID ExcptList; CONTEXT Context; ULONG Psp; NTSTATUS st; INITIAL_TEB InitialTeb; CLIENT_ID ClientId; PPSX_FORK_MSG args; HANDLE h; args = &m->u.Fork; if (p->Flags & P_NO_FORK) { // // This process may not fork; it's context has been // rearranged to make it call the PdxNullApiCaller. // m->Error = EINTR; m->Signal = SIGCONT; return TRUE; } // // Impersonate the client to insure that the new process will belong // to him instead of to us. // st = NtImpersonateClientOfPort(p->ClientPort, (PPORT_MESSAGE)m); if (!NT_SUCCESS(st)) { m->Error = EAGAIN; return TRUE; } // // Create a new process to be the child. The ExceptionPort is // initialized to PsxApiPort. // st = NtCreateProcess(&NewProcessHandle, PROCESS_ALL_ACCESS, NULL, p->Process, TRUE, NULL, NULL, PsxApiPort); if (!NT_SUCCESS(st)) { EndImpersonation(); m->Error = EAGAIN; return TRUE; } { ULONG HardErrorMode = 0; // disable popups st = NtSetInformationProcess( NewProcessHandle, ProcessDefaultHardErrorMode, (PVOID)&HardErrorMode, sizeof(HardErrorMode) ); ASSERT(NT_SUCCESS(st)); } Context.ContextFlags = CONTEXT_FULL; st = NtGetContextThread(p->Thread, &Context); if (!NT_SUCCESS(st)) { NtTerminateProcess(NewProcessHandle, STATUS_SUCCESS); NtWaitForSingleObject(NewProcessHandle, FALSE, NULL); NtClose(NewProcessHandle); EndImpersonation(); m->Error = EINVAL; return TRUE; } InitialTeb.OldInitialTeb.OldStackBase = NULL; InitialTeb.OldInitialTeb.OldStackLimit = NULL; #ifdef _IA64_ InitialTeb.OldInitialTeb.OldBStoreLimit = NULL; InitialTeb.BStoreLimit = args->BStoreLimit; #endif InitialTeb.StackBase = args->StackBase; InitialTeb.StackLimit = args->StackLimit; InitialTeb.StackAllocationBase = args->StackAllocationBase; SetPsxForkReturn(Context); st = NtCreateThread(&NewThreadHandle, THREAD_ALL_ACCESS, NULL, NewProcessHandle, &ClientId, &Context, &InitialTeb, TRUE); EndImpersonation(); if (!NT_SUCCESS(st)) { st = NtTerminateProcess(NewProcessHandle, STATUS_SUCCESS); ASSERT(NT_SUCCESS(st)); NtWaitForSingleObject(NewProcessHandle, FALSE, NULL); NtClose(NewProcessHandle); m->Error = EAGAIN; return TRUE; } // // Allocate a process structure. // NewProcess = PsxAllocateProcess(&ClientId); if (NULL == NewProcess) { st = NtTerminateProcess(NewProcessHandle, STATUS_SUCCESS); ASSERT(NT_SUCCESS(st)); st = NtResumeThread(NewThreadHandle,&Psp); ASSERT(NT_SUCCESS(st)); NtWaitForSingleObject(NewProcessHandle, FALSE, NULL); NtClose(NewProcessHandle); NtClose(NewThreadHandle); m->Error = EAGAIN; return TRUE; } // // Copy the ExceptionList pointer from the parent process TEB // to the child process TEB. // st = NtQueryInformationThread(p->Thread, ThreadBasicInformation, (PVOID)&ThreadBasicInfo, sizeof(ThreadBasicInfo), NULL); ASSERT(NT_SUCCESS(st)); // XXX.mjb: The actual address we want to read is // TebBaseAddress->NtTib.ExceptionList, but I happen to know // that these are equivalent. // st = NtReadVirtualMemory(p->Process, (PVOID)ThreadBasicInfo.TebBaseAddress, (PVOID)&ExcptList, sizeof(ExcptList), NULL); ASSERT(NT_SUCCESS(st)); st = NtQueryInformationThread(NewThreadHandle, ThreadBasicInformation, (PVOID)&ThreadBasicInfo, sizeof(ThreadBasicInfo), NULL); ASSERT(NT_SUCCESS(st)); st = NtWriteVirtualMemory(NewProcessHandle, (PVOID)ThreadBasicInfo.TebBaseAddress, (PVOID)&ExcptList, sizeof(ExcptList), NULL); ASSERT(NT_SUCCESS(st)); ForkProcess = p; // // The new process is allocated locked, but we need to lock the // parent (ForkProcess). // AcquireProcessLock(ForkProcess); st = PsxInitializeProcess(NewProcess, ForkProcess, 0L, NewProcessHandle, NewThreadHandle, NULL); if (!NT_SUCCESS(st)) { st = NtTerminateProcess(NewProcessHandle, STATUS_SUCCESS); ASSERT(NT_SUCCESS(st)); st = NtResumeThread(NewThreadHandle,&Psp); ASSERT(NT_SUCCESS(st)); st = NtWaitForSingleObject(NewProcessHandle, FALSE, NULL); NtClose(NewProcessHandle); NtClose(NewThreadHandle); m->Error = EAGAIN; return TRUE; } NewProcess->InitialPebPsxData.Length = sizeof(ForkProcess->InitialPebPsxData); NewProcess->InitialPebPsxData.ClientStartAddress = NULL; if (NULL != ForkProcess->PsxSession->Terminal) { NewProcess->InitialPebPsxData.SessionPortHandle = (HANDLE)ForkProcess->PsxSession->Terminal->UniqueId; } else { // // What if there are no open file descriptors? // if (NULL != ForkProcess->ProcessFileTable[0].SystemOpenFileDesc) NewProcess->InitialPebPsxData.SessionPortHandle = (HANDLE)ForkProcess->ProcessFileTable[0].SystemOpenFileDesc->Terminal->UniqueId; } m->ReturnValue = NewProcess->Pid; st = NtResumeThread(NewThreadHandle, &Psp); ASSERT(NT_SUCCESS(st)); return TRUE; } BOOLEAN PsxExec( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements posix execve() API Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { RTL_USER_PROCESS_INFORMATION ProcInfo; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PPSX_EXEC_MSG args; WCHAR ImageFileString[1024]; ANSI_STRING CommandLine; ANSI_STRING CWD; NTSTATUS Status; ULONG whocares; ULONG i; KERNEL_USER_TIMES ProcessTime; ULONG PosixTime, Remainder; UNICODE_STRING uCWD, uImageFileName; PUNICODE_STRING u; PVOID SaveDirectoryPrefix; HANDLE h; USHORT len; args = &m->u.Exec; // // If pathname is too big, then return error. This should // really allow for names that are longer because of PSX // prefixes // if (args->Path.Length > PATH_MAX) { m->Error = ENAMETOOLONG; return TRUE; } AcquireProcessLock(p); if (Exited == p->State) { ReleaseProcessLock(p); return FALSE; } // // Capture the pathname // Status = NtReadVirtualMemory(p->Process, args->Path.Buffer, &ImageFileString[0], args->Path.Length, NULL); if (!NT_SUCCESS(Status)) { ReleaseProcessLock(p); m->Error = ENOMEM; return TRUE; } uImageFileName.Buffer = &ImageFileString[0]; uImageFileName.Length = args->Path.Length; uImageFileName.MaximumLength = args->Path.Length; // // Propagate Current Working Directory // SaveDirectoryPrefix = (PVOID)p->DirectoryPrefix; if (!PsxPropagateDirectories(p)) { ReleaseProcessLock(p); p->DirectoryPrefix = SaveDirectoryPrefix; m->Error = ENOMEM; return TRUE; } // // Format the process parameters // CommandLine.Buffer = args->Args; CommandLine.Length = ARG_MAX; CommandLine.MaximumLength = ARG_MAX; PSX_GET_STRLEN(DOSDEVICE_A,len); CWD.Buffer = p->DirectoryPrefix->NtCurrentWorkingDirectory.Buffer + len; CWD.MaximumLength = p->DirectoryPrefix->NtCurrentWorkingDirectory.Length - len; CWD.Length = CWD.MaximumLength; Status = RtlAnsiStringToUnicodeString(&uCWD, &CWD, TRUE); if (!NT_SUCCESS(Status)) { PsxFreeDirectories(p); p->DirectoryPrefix = SaveDirectoryPrefix; ReleaseProcessLock(p); m->Error = ENOMEM; return TRUE; } // // Somewhere along the line someone changes the old process's // DllPath to Unicode. If we pass a NULL DllPath here, we find // that Ansi is expected, and we can't find the Dll. So here we // take the Unicode DllPath, convert to Ansi, and pass it // explicitly. // u = (PUNICODE_STRING)&NtCurrentPeb()->ProcessParameters->DllPath; Status = RtlCreateProcessParameters(&ProcessParameters, &uImageFileName, u, &uCWD, (PUNICODE_STRING)&CommandLine, NULL, NULL, NULL, NULL, NULL); #ifndef EXEC_FOREIGN RtlFreeUnicodeString(&uCWD); #endif if (!NT_SUCCESS(Status)) { #ifdef EXEC_FOREIGN RtlFreeUnicodeString(&uCWD); #endif PsxFreeDirectories(p); p->DirectoryPrefix = SaveDirectoryPrefix; ReleaseProcessLock(p); m->Error = ENOMEM; return TRUE; } // // Create the process and thread. We impersonate the client so that // they end up being owned by him, instead of owned by us. // Status = NtImpersonateClientOfPort(p->ClientPort, (PPORT_MESSAGE)m); if (NT_SUCCESS(Status)) { Status = RtlCreateUserProcess(&uImageFileName, 0, ProcessParameters, NULL, NULL, p->Process, FALSE, NULL, NULL, &ProcInfo); EndImpersonation(); } if (!NT_SUCCESS(Status)) { #ifdef EXEC_FOREIGN RtlFreeUnicodeString(&uCWD); #endif PsxFreeDirectories(p); p->DirectoryPrefix = SaveDirectoryPrefix; ReleaseProcessLock(p); if (STATUS_OBJECT_PATH_NOT_FOUND == Status) { m->Error = PsxStatusToErrnoPath(&uImageFileName); return TRUE; } m->Error = PsxStatusToErrno(Status); return TRUE; } RtlDestroyProcessParameters(ProcessParameters); // // Set the exception port for the new process so we'll find out // if he takes a fault. // Status = NtSetInformationProcess(ProcInfo.Process, ProcessExceptionPort, (PVOID)&PsxApiPort, sizeof(PsxApiPort)); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: NtSetInfoProcess: 0x%x\n", Status)); } ASSERT(NT_SUCCESS(Status)); { ULONG HardErrorMode = 0; // disable popups Status = NtSetInformationProcess( ProcInfo.Process, ProcessDefaultHardErrorMode, (PVOID)&HardErrorMode, sizeof(HardErrorMode) ); ASSERT(NT_SUCCESS(Status)); } // // check to make sure it is a POSIX app // if (ProcInfo.ImageInformation.SubSystemType != IMAGE_SUBSYSTEM_POSIX_CUI) { // // The image is not a Posix program. Tear down the // process we just created in the usual way, and then // call the windows subsystem to do the same thing // over. // PsxFreeDirectories(p); p->DirectoryPrefix = SaveDirectoryPrefix; #ifndef EXEC_FOREIGN ReleaseProcessLock(p); #endif Status = NtTerminateProcess(ProcInfo.Process, STATUS_SUCCESS); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: NtTerminateProcess: 0x%x\n", Status)); } Status = NtWaitForSingleObject(ProcInfo.Process, FALSE, NULL); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: NtWaitForSingleObject: 0x%x\n", Status)); } #ifdef EXEC_FOREIGN Status = ExecForeignImage(p, m, &uImageFileName, &uCWD); ReleaseProcessLock(p); RtlFreeUnicodeString(&uCWD); if (!NT_SUCCESS(Status)) { m->Error = ENOEXEC; return TRUE; } return FALSE; #else m->Error = ENOEXEC; return TRUE; #endif } #ifdef EXEC_FOREIGN RtlFreeUnicodeString(&uCWD); #endif // // Close open files that have their close-on-exec bit set. // for (i = 0; i < OPEN_MAX; ++i) { if (NULL != p->ProcessFileTable[i].SystemOpenFileDesc && p->ProcessFileTable[i].Flags & PSX_FD_CLOSE_ON_EXEC) { (void)DeallocateFd(p, i); } } AcquireProcessStructureLock(); // // Get the time for this process and add to the accumulated time for // the process // Status = NtQueryInformationProcess(p->Process, ProcessTimes, (PVOID)&ProcessTime, sizeof(KERNEL_USER_TIMES), NULL); ASSERT(NT_SUCCESS(Status)); PosixTime = RtlExtendedLargeIntegerDivide(ProcessTime.KernelTime, 10000, &Remainder).LowPart; p->ProcessTimes.tms_stime += PosixTime; PosixTime = RtlExtendedLargeIntegerDivide(ProcessTime.UserTime, 10000, &Remainder).LowPart; p->ProcessTimes.tms_utime += PosixTime; // // Terminate the current process, and munge in the // new process // RemoveEntryList(&p->ClientIdHashLinks); p->ClientIdHashLinks.Flink = p->ClientIdHashLinks.Blink = NULL; Status = NtTerminateProcess(p->Process, STATUS_SUCCESS); ASSERT(NT_SUCCESS(Status)); Status = NtWaitForSingleObject(p->Process, FALSE, NULL); ASSERT(NT_SUCCESS(Status)); Status = NtClose(p->ClientPort); ASSERT(NT_SUCCESS(Status)); Status = NtClose(p->Process); ASSERT(NT_SUCCESS(Status)); Status = NtClose(p->Thread); ASSERT(NT_SUCCESS(Status)); p->Process = ProcInfo.Process; p->Thread = ProcInfo.Thread; p->ClientId = ProcInfo.ClientId; p->ClientPort = NULL; InsertTailList(&ClientIdHashTable[CIDTOHASHINDEX(&p->ClientId)], &p->ClientIdHashLinks); p->Flags |= P_HAS_EXECED; ReleaseProcessStructureLock(); // // Restore signals being caught to SIG_DFL // --- Posix does not specify what to do w/ associated flags or mask // for (i = 0; i < _SIGMAXSIGNO; i++) { if (p->SignalDataBase.SignalDisposition[i].sa_handler != SIG_IGN) { p->SignalDataBase.SignalDisposition[i].sa_handler = SIG_DFL; } } // // Since we don't reply to the old process (he's gone now), we need // to start the new process's InPsx count at zero. // p->InPsx = 0; ReleaseProcessLock(p); ExecProcessFileTable(p); if (p->ProcessIsBeingDebugged && PsxpDebuggerActive) { Status = NtSetInformationProcess(p->Process, ProcessDebugPort, (PVOID)&PsxpDebugPort, sizeof(HANDLE)); if (!NT_SUCCESS(Status)) { p->ProcessIsBeingDebugged = FALSE; } } Status = NtResumeThread(p->Thread,&whocares); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: NtResumeThread: 0x%x\n", Status)); } ASSERT(NT_SUCCESS(Status) && whocares == 1); return FALSE; } BOOLEAN PsxGetIds( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function provides all the support needed to implement getpid(), getppid(), getuid(), geteuid(), getgid(), and getegid(). Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_GETIDS_MSG args; args = &m->u.GetIds; args->Pid = p->Pid; args->ParentPid = p->ParentPid; args->GroupId = p->ProcessGroupId; args->RealUid = p->RealUid; args->EffectiveUid = p->EffectiveUid; args->RealGid = p->RealGid; args->EffectiveGid = p->EffectiveGid; return TRUE; } BOOLEAN PsxExit( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the _exit() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_EXIT_MSG args; args = &m->u.Exit; Exit(p, (args->ExitStatus & 0xff) << 8); return FALSE; } VOID WaitPidHandler( IN PPSX_PROCESS p, IN PINTCB IntControlBlock, IN PSX_INTERRUPTREASON InterruptReason, IN int Signal ) /*++ Routine Description: This function is called whenever a process that is in a waitpid wait is sent a signal, or has a child stop/terminate that could possibly satisfy a wait. This function is responsible for unlocking the process. Arguments: p - Supplies the address of the process being interrupted. IntControlBlock - Supplies the address of the interrupt control block. InterruptReason - Supplies the reason that this process is being interrupted. Not used in this handler. Return Value: None. --*/ { PPSX_API_MSG m; PPSX_WAITPID_MSG args; PPSX_PROCESS cp; BOOLEAN WaitSatisfied; pid_t TargetProcess; pid_t TargetGroup; enum _WaitType { AnyProcess, SpecificProcess, SpecificGroup }; enum _WaitType WaitType; RtlLeaveCriticalSection(&BlockLock); m = IntControlBlock->IntMessage; args = &m->u.WaitPid; AcquireProcessStructureLock(); p->State = Active; if (InterruptReason == SignalInterrupt) { ReleaseProcessStructureLock(); RtlFreeHeap(PsxHeap, 0, (PVOID)IntControlBlock); m->Error = EINTR; m->Signal = Signal; ApiReply(p,m,NULL); RtlFreeHeap(PsxHeap, 0, (PVOID)m); return; } WaitSatisfied = FALSE; TargetProcess = SPECIALPID; TargetGroup = SPECIALPID; WaitType = AnyProcess; if (args->Pid <= 0 && args->Pid != (pid_t)-1) { WaitType = SpecificGroup; // // Process group id is specified. // if (args->Pid == 0) { TargetGroup = p->ProcessGroupId; } else { TargetGroup = -1 * args->Pid; } } else { if (args->Pid != (pid_t)-1) { TargetProcess = args->Pid; WaitType = SpecificProcess; } } // // Scan process table // for (cp = FirstProcess; cp < LastProcess; cp++) { if (cp->Flags & P_FREE) { continue; } // // Just look at processes that could possibly satisfy a wait. // - Processes that have exited // - Stopped processes that have not previously satisfied a // wait (if WUNTRACED was set) // if (cp->State == Exited || (cp->State == Stopped && (args->Options & WUNTRACED) && !(cp->Flags & P_WAITED))) { if (cp->ParentPid != p->Pid) { continue; } switch (WaitType) { case AnyProcess: m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; break; case SpecificProcess: if ( cp->Pid == TargetProcess ) { m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; } break; case SpecificGroup: if ( cp->ProcessGroupId == TargetGroup){ m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; } break; } if ( m->ReturnValue ) { // // wait was satisfied // if ( cp->State == Exited ) { p->ProcessTimes.tms_cstime += (cp->ProcessTimes.tms_stime + cp->ProcessTimes.tms_cstime); p->ProcessTimes.tms_cutime += (cp->ProcessTimes.tms_utime + cp->ProcessTimes.tms_cutime); // // Deallocate the process // cp->Flags |= P_FREE; RtlDeleteCriticalSection(&cp->ProcessLock); } else { // // set bit so this stopped process won't satisfy another // wait until it stops again or exits. // cp->Flags |= P_WAITED; args->StatLocValue = cp->ExitStatus | (1L << 30); } WaitSatisfied = TRUE; break; } } } if ( WaitSatisfied ) { ReleaseProcessStructureLock(); RtlFreeHeap(PsxHeap, 0, (PVOID)IntControlBlock); ApiReply(p,m,NULL); RtlFreeHeap(PsxHeap, 0, (PVOID)m); return; } // // Rewait // p->State = Waiting; (void)BlockProcess(p, NULL, WaitPidHandler, m, NULL, &PsxProcessStructureLock); } BOOLEAN PsxWaitPid( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the wait() and waitpid() APIs. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { NTSTATUS Status; PPSX_WAITPID_MSG args; PPSX_PROCESS cp; pid_t TargetProcess; pid_t TargetGroup; BOOLEAN EmitEchild; enum _WaitType { AnyProcess, SpecificProcess, SpecificGroup }; enum _WaitType WaitType; args = &m->u.WaitPid; // // Test for invalid options // if (args->Options & ~(WNOHANG|WUNTRACED)) { m->Error = EINVAL; return TRUE; } TargetProcess = SPECIALPID; TargetGroup = SPECIALPID; WaitType = AnyProcess; if (args->Pid <= 0 && args->Pid != (pid_t)-1) { WaitType = SpecificGroup; if (args->Pid == 0) { TargetGroup = p->ProcessGroupId; } else { TargetGroup = -1 * args->Pid; } } else { if (args->Pid != (pid_t)-1) { TargetProcess = args->Pid; WaitType = SpecificProcess; } } AcquireProcessStructureLock(); EmitEchild = TRUE; // // Scan process table // for (cp = FirstProcess; cp < LastProcess; cp++) { if (cp->Flags & P_FREE) { continue; } // // Until we know whether or not there is a process that // could possibly satisfy a wait, we have to keep looking // at all processes. // if (EmitEchild) { if (WaitType == SpecificGroup) { if (cp->ParentPid == p->Pid && cp->ProcessGroupId == TargetGroup) { EmitEchild = FALSE; } } else { if (cp->ParentPid == p->Pid) { if (WaitType == SpecificProcess) { if (cp->Pid == TargetProcess) { EmitEchild = FALSE; } } else { EmitEchild = FALSE; } } } } // // Just look at processes that could possibly satisfy a wait. // - Processes that have exited // - Stopped processes that have not previously satisfied // a wait (if WUNTRACED was set) // if (cp->State == Exited || (cp->State == Stopped && (args->Options & WUNTRACED) && !(cp->Flags & P_WAITED))) { if (cp->ParentPid != p->Pid) { continue; } switch (WaitType) { case AnyProcess: m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; break; case SpecificProcess: if (cp->Pid == TargetProcess) { m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; } break; case SpecificGroup: if (cp->ProcessGroupId == TargetGroup) { m->ReturnValue = cp->Pid; args->StatLocValue = cp->ExitStatus; } break; } if (m->ReturnValue) { // // wait was satisfied // if ( cp->State == Exited ) { p->ProcessTimes.tms_cstime += (cp->ProcessTimes.tms_stime + cp->ProcessTimes.tms_cstime); p->ProcessTimes.tms_cutime += (cp->ProcessTimes.tms_utime + cp->ProcessTimes.tms_cutime); // // Deallocate the process // cp->Flags |= P_FREE; RtlDeleteCriticalSection(&cp->ProcessLock); } else { // // Set a bit to keep this stopped process from satisfying // another wait. // cp->Flags |= P_WAITED; args->StatLocValue = cp->ExitStatus | (1L << 30); } ReleaseProcessStructureLock(); return TRUE; } } } if (EmitEchild) { m->Error = ECHILD; ReleaseProcessStructureLock(); return TRUE; } if (args->Options & WNOHANG) { m->ReturnValue = 0; args->StatLocValue = 0; ReleaseProcessStructureLock(); return TRUE; } // // Make the process sleep until the wait can be satisfied. // p->State = Waiting; Status = BlockProcess(p, NULL, WaitPidHandler, m, NULL, &PsxProcessStructureLock); if (!NT_SUCCESS(Status)) { m->Error = PsxStatusToErrno(Status); return TRUE; } // // The process has successfully been blocked. Don't reply to the api // request message. // return FALSE; } BOOLEAN PsxSetSid( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the setsid() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_SESSION OldSession; // // 1003.1-90 (4.3.2.4): EPERM when the calling process // is already a process group leader. // if (p->Pid == p->ProcessGroupId) { m->Error = EPERM; return TRUE; } // // Create a new session with no controlling tty and make // the calling process the session leader. // AcquireProcessStructureLock(); LockNtSessionList(); RemoveEntryList(&p->GroupLinks); InitializeListHead(&p->GroupLinks); // // Make the process the leader of his process group. // p->ProcessGroupId = p->Pid; OldSession = p->PsxSession; p->PsxSession = PsxAllocateSession(NULL, p->Pid); UnlockNtSessionList(); ReleaseProcessStructureLock(); DEREFERENCE_PSX_SESSION(OldSession, 0); m->ReturnValue = (pid_t)p->ProcessGroupId; return TRUE; } BOOLEAN PsxSetPGroupId( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the setpgid() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_SETPGROUPID_MSG args; pid_t TargetPid, TargetGroup; PPSX_PROCESS cp, Target; BOOLEAN EmitEsrch; args = &m->u.SetPGroupId; if (args->Pid < 0 || args->Pgid < 0) { m->Error = EINVAL; return TRUE; } TargetPid = (args->Pid ? args->Pid : p->Pid); TargetGroup = (args->Pgid ? args->Pgid : TargetPid); AcquireProcessStructureLock(); LockNtSessionList(); if ( p->Pid == TargetPid ) { Target = p; } else { // // Scan process table // EmitEsrch = TRUE; for (cp = FirstProcess ;cp < LastProcess ;cp++ ) { if ( cp->Flags & P_FREE ) { continue; } if ( cp->ParentPid == p->Pid && cp->Pid == TargetPid ) { EmitEsrch = FALSE; Target = cp; break; } } if ( EmitEsrch ) { m->Error = ESRCH; goto done; } } // // If target is a child who has executed an exec, then report error // if (Target->ParentPid == p->Pid && (Target->Flags & P_HAS_EXECED)) { m->Error = EACCES; goto done; } // // If target is a session leader, then report error // if ( Target->PsxSession->SessionLeader == TargetPid ) { m->Error = EPERM; goto done; } // // If target is not in the same session as the calling process, then // report error // if ( Target->PsxSession != p->PsxSession ) { m->Error = EPERM; goto done; } if ( TargetPid != TargetGroup ) { // // Scan process table looking for a pgrp id the same // as TargetGroup and is in the same session is the calling process // EmitEsrch = TRUE; for (cp = FirstProcess ;cp < LastProcess ;cp++ ) { if ( cp->Flags & P_FREE ) { continue; } if ( cp->ProcessGroupId == TargetGroup && p->PsxSession == cp->PsxSession ) { EmitEsrch = FALSE; break; } } if ( EmitEsrch ) { m->Error = EPERM; goto done; } } else { cp = Target; } // // Everything is ok, so set the Target's pgrp id to the specified id // RemoveEntryList(&Target->GroupLinks); if (cp != Target) { InsertHeadList(&cp->GroupLinks, &Target->GroupLinks); } else { InitializeListHead(&Target->GroupLinks); } Target->ProcessGroupId = TargetGroup; done: UnlockNtSessionList(); ReleaseProcessStructureLock(); return TRUE; } BOOLEAN PsxGetProcessTimes( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the times() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply --*/ { PPSX_GETPROCESSTIMES_MSG args; NTSTATUS Status; KERNEL_USER_TIMES ProcessTime; ULONG PosixTime, Remainder; ULONG LengthNeeded; args = &m->u.GetProcessTimes; { LARGE_INTEGER DelayInterval; DelayInterval.HighPart = 0; DelayInterval.LowPart = 100; NtDelayExecution(TRUE, &DelayInterval); } // // Get the time for this process and add to the accumulated time for // the process // Status = NtQueryInformationProcess(p->Process, ProcessTimes, (PVOID)&ProcessTime, sizeof(ProcessTime), &LengthNeeded); ASSERT(NT_SUCCESS(Status)); PosixTime = RtlExtendedLargeIntegerDivide(ProcessTime.UserTime, 10000, &Remainder).LowPart; args->ProcessTimes.tms_utime = p->ProcessTimes.tms_utime + PosixTime; PosixTime = RtlExtendedLargeIntegerDivide(ProcessTime.KernelTime, 10000, &Remainder).LowPart; args->ProcessTimes.tms_stime = p->ProcessTimes.tms_stime + PosixTime; args->ProcessTimes.tms_cutime = p->ProcessTimes.tms_cutime; args->ProcessTimes.tms_cstime = p->ProcessTimes.tms_cstime; return TRUE; } BOOLEAN PsxGetGroups( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the getgroups() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply XXX.mjb: NT essentially puts no limit on the number of supplementary groups a user may belong to. This is bad for Posix, since we want a limit, and we want it small enough that people don't mess themselves up by allocating an array[NGROUPS_MAX] of gid_t. --*/ { PPSX_GETGROUPS_MSG args; NTSTATUS Status; HANDLE TokenHandle; TOKEN_GROUPS *pGroups; ULONG outlen, i, j; gid_t *GroupList; args = &m->u.GetGroups; // // Check args->GroupList for group array address validity. // // // Examine the new process's token to figure out what the // uid's should be. // Status = NtOpenProcessToken(p->Process, GENERIC_READ, &TokenHandle); ASSERT(NT_SUCCESS(Status)); // // Get the supplemental groups. // Status = NtQueryInformationToken(TokenHandle, TokenGroups, NULL, 0, &outlen); ASSERT(STATUS_BUFFER_TOO_SMALL == Status); pGroups = RtlAllocateHeap(PsxHeap, 0, outlen); if (NULL == pGroups) { // // We don't have enough memory to hold the list of the process's // groups. What is there to do except return an error? // NtClose(TokenHandle); m->Error = ENOMEM; return TRUE; } Status = NtQueryInformationToken(TokenHandle, TokenGroups, (PVOID)pGroups, outlen, &outlen); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: NtQueryInformationToken failed: 0x%x\n", Status)); RtlFreeHeap(PsxHeap, 0, (PVOID)pGroups); NtClose(TokenHandle); m->Error = EACCES; return TRUE; } // // If the user has passed in a listsize of 0, then we only return // the number of supplementary groups, without writing anything in // the group array. // if (0 == args->NGroups) { m->ReturnValue = pGroups->GroupCount; m->Error = 0; NtClose(TokenHandle); RtlFreeHeap(PsxHeap, 0, (PVOID)pGroups); return TRUE; } if ((ULONG)args->NGroups < pGroups->GroupCount) { if (args->NGroups < NGROUPS_MAX) { m->Error = EINVAL; return TRUE; } // // XXX.mjb: We're having a problem here. The caller has // allocated space for the maximum number of groups that the // user can have, according to the _NGROUPS_MAX limit, but // the user actually has more groups than that. This can // happen because NT's limit is indeterminate. // // ignore the groups that we cannot return. pGroups->GroupCount = args->NGroups; } // // Make an array of gid_t's and copy it to the user address space. // GroupList = RtlAllocateHeap(PsxHeap, 0, pGroups->GroupCount * sizeof(gid_t)); if (NULL == GroupList) { RtlFreeHeap(PsxHeap, 0, (PVOID)pGroups); NtClose(TokenHandle); m->Error = ENOMEM; return TRUE; } for (i = 0, j = 0; i < pGroups->GroupCount; ++i) { PSID Sid = pGroups->Groups[i].Sid; GroupList[j] = MakePosixId(Sid); if (0 != GroupList[j]) { ++j; } } // // Copy GroupList to client's address space, at the place // specified. // Status = NtWriteVirtualMemory(p->Process, args->GroupList, GroupList, j * sizeof(gid_t), NULL); RtlFreeHeap(PsxHeap, 0, (PVOID)GroupList); if (!NT_SUCCESS(Status)) { m->Error = PsxStatusToErrno(Status); RtlFreeHeap(PsxHeap, 0, (PVOID)pGroups); NtClose(TokenHandle); return TRUE; } m->ReturnValue = j; RtlFreeHeap(PsxHeap, 0, (PVOID)pGroups); NtClose(TokenHandle); return TRUE; } BOOLEAN PsxGetLogin( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the getlogin() API. Arguments: p - Supplies the address of the calling process m - Supplies the address of the related message Return Value: TRUE - Always succeeds and generates a reply XXX.mjb: this routine is never called; getlogin() is implemented as "getpwuid(getuid())->pw_name", kind of. See client-side getlogin(). --*/ { PPSX_GETLOGIN_MSG args; NTSTATUS Status; args = &m->u.GetLogin; args->LoginName; // w.r.t. our address space m->Error = ENOSYS; return TRUE; } BOOLEAN PsxSysconf( IN PPSX_PROCESS p, IN PPSX_API_MSG m ) /*++ Routine Description: This function implements the sysconf() api. Arguments: p - Supplies the address of the calling process. m - Supplies the address of the related message. Return Value: TRUE - Always succeeds and generates a reply. --*/ { PPSX_SYSCONF_MSG args; NTSTATUS Status; long value; PPSX_PROCESS Process; args = &m->u.Sysconf; switch (args->Name) { case _SC_ARG_MAX: value = ARG_MAX; break; case _SC_CHILD_MAX: // // This is the reason sysconf is implemented in the // server. We don't bother to grab any locks, since the // result is out of date by the time it gets back to the // user anyway. // value = 1; for (Process = FirstProcess; Process < LastProcess; ++Process) { if (Process->Flags & P_FREE) { ++value; } } break; case _SC_CLK_TCK: value = CLK_TCK; break; case _SC_NGROUPS_MAX: value = NGROUPS_MAX; break; case _SC_OPEN_MAX: value = OPEN_MAX; break; case _SC_JOB_CONTROL: #ifdef _POSIX_JOB_CONTROL value = 1; break; #else value = 0; break; #endif case _SC_SAVED_IDS: #ifdef _POSIX_SAVED_IDS value = 1; #else value = 0; #endif break; case _SC_VERSION: value = _POSIX_VERSION; break; case _SC_STREAM_MAX: value = STREAM_MAX; break; case _SC_TZNAME_MAX: value = TZNAME_MAX; break; default: value = -1; m->Error = EINVAL; } m->ReturnValue = value; return TRUE; } #ifdef EXEC_FOREIGN #define UNICODE #include DWORD ForeignProcessWait(PVOID); NTSTATUS ExecForeignImage( PPSX_PROCESS p, PPSX_API_MSG m, PUNICODE_STRING Image, PUNICODE_STRING CurDir ) { PPSX_EXEC_MSG args; NTSTATUS Status; LPWSTR CommandLine; LPVOID Environment; STARTUPINFO StartInfo; PROCESS_INFORMATION ProcInfo; BOOL Success = FALSE; char **ppch; PWCHAR pwc; ULONG flags; ULONG ThreadId; HANDLE ForeignProc; ULONG len; args = &m->u.Exec; // // Convert argv array to command line format. // CommandLine = RtlAllocateHeap(PsxHeap, 0, ARG_MAX); if (NULL == CommandLine) { return STATUS_NO_MEMORY; } CommandLine[0] = 0; ppch = (PVOID)args->Args; for (ppch = (PVOID)args->Args; NULL != *ppch; ++ppch) { ANSI_STRING A; UNICODE_STRING U; // this breaks on args with spaces in them A.Buffer = *ppch + (ULONG)args->Args; A.Length = A.MaximumLength = strlen(A.Buffer); U.Buffer = &CommandLine[wcslen(CommandLine)]; U.Length = 0; U.MaximumLength = ARG_MAX; Status = RtlAnsiStringToUnicodeString(&U, &A, FALSE); ASSERT(NT_SUCCESS(Status)); wcscat((PVOID)CommandLine, L" "); } Status = RtlCreateEnvironment(FALSE, &Environment); if (!NT_SUCCESS(Status)) { RtlFreeHeap(PsxHeap, 0, CommandLine); return Status; } for (++ppch; NULL != *ppch; ++ppch) { ANSI_STRING aName, aValue; UNICODE_STRING nU, vU; char *pch; pch = strchr(*ppch + (ULONG)args->Args, '='); ASSERT(NULL != pch); *pch = '\0'; aName.Buffer = *ppch + (ULONG)args->Args; aName.Length = strlen(aName.Buffer); aName.MaximumLength = aName.Length; aValue.Buffer = pch + 1; aValue.Length = strlen(aValue.Buffer); aValue.MaximumLength = aName.Length; if (0 == stricmp("PATH", aName.Buffer)) { ConvertPathToWin(aValue.Buffer); aValue.Length = strlen(aValue.Buffer); } *pch = '='; Status = RtlAnsiStringToUnicodeString(&nU, &aName, TRUE); if (!NT_SUCCESS(Status)) { RtlFreeHeap(PsxHeap, 0, CommandLine); RtlDestroyEnvironment(Environment); return Status; } Status = RtlAnsiStringToUnicodeString(&vU, &aValue, TRUE); if (!NT_SUCCESS(Status)) { RtlFreeHeap(PsxHeap, 0, nU.Buffer); RtlFreeHeap(PsxHeap, 0, CommandLine); RtlDestroyEnvironment(Environment); return Status; } Status = RtlSetEnvironmentVariable_U(&Environment, &nU, &vU); RtlFreeHeap(PsxHeap, 0, nU.Buffer); RtlFreeHeap(PsxHeap, 0, vU.Buffer); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: RtlSetEnvVar: 0x%x\n", Status)); RtlFreeHeap(PsxHeap, 0, CommandLine); RtlDestroyEnvironment(Environment); return Status; } } // Make Image into correct format: "X:\path", return to original // after we're done. pwc = Image->Buffer; PSX_GET_SIZEOF(DOSDEVICE_W,len); Image->Buffer += (len - 2)/2; Image->Length -= (len - 2); flags = CREATE_SUSPENDED|CREATE_NEW_CONSOLE| CREATE_NEW_PROCESS_GROUP|CREATE_UNICODE_ENVIRONMENT; // set up StartInfo StartInfo.cb = sizeof(STARTUPINFO); StartInfo.lpReserved = 0; StartInfo.lpDesktop = NULL; StartInfo.lpTitle = NULL; StartInfo.dwX = StartInfo.dwY = 0; StartInfo.dwXSize = StartInfo.dwYSize = 400; StartInfo.dwFlags = 0; StartInfo.wShowWindow = SW_SHOWDEFAULT; StartInfo.cbReserved2 = 0; StartInfo.lpReserved2 = NULL; Status = NtImpersonateClientOfPort(p->ClientPort, (PPORT_MESSAGE)m); if (NT_SUCCESS(Status)) { Success = CreateProcess( Image->Buffer, // address of module name CommandLine, // command line NULL, // process security attr NULL, // thread security attr FALSE, // inherit handles flags, // creation flags Environment, // address of new environ CurDir->Buffer, // current working dir &StartInfo, // startup info &ProcInfo // process information ); EndImpersonation(); } // restore image name Image->Buffer = pwc; (void)RtlDestroyEnvironment(Environment); // ASSERT(NT_SUCCESS(Status)); RtlFreeHeap(PsxHeap, 0, CommandLine); if (!Success) { KdPrint(("PSXSS: CreateProcess: %d\n", GetLastError())); return STATUS_UNSUCCESSFUL; } p->Thread = ProcInfo.hThread; p->Process = ProcInfo.hProcess; // set bit in the process to indicate it's a foreign // image type p->Flags |= P_FOREIGN_EXEC; // create additional thread in psxss to wait for new // process to exit. p->BlockingThread = NULL; Status = NtImpersonateClientOfPort(p->ClientPort, (PPORT_MESSAGE)m); if (NT_SUCCESS(Status)) { p->BlockingThread = CreateThread( NULL, 0, ForeignProcessWait, (PVOID)p, CREATE_SUSPENDED, &ThreadId ); EndImpersonation(); if (NULL == p->BlockingThread) { //XXX.mjb: clean process Status = STATUS_UNSUCCESSFUL; } } if (!NT_SUCCESS(Status)) { return Status; } Success = ResumeThread(p->BlockingThread); ASSERT(Success); Success = ResumeThread(p->Thread); ASSERT(Success); return STATUS_SUCCESS; } DWORD ForeignProcessWait(PVOID arg) { PPSX_PROCESS p = arg; ULONG ExitStatus; WaitForSingleObject(p->Process, (DWORD)-1); Exit(p, ExitStatus); p->BlockingThread = NULL; ExitThread(0); //NOTREACHED ASSERT(0); return 0; } // // Change a posix-type path variable to win32 path. The given // buffer is modified in place. // VOID ConvertPathToWin(char *path) { char *pch; pch = path; while (*path) { // change ':' to ';' if (':' == *path) { *pch = ';'; ++path; ++pch; continue; } // change "//X" to "X:" if ('/' == *path && '/' == path[1]) { path += 2; *pch = *path; ++pch; *pch = ':'; ++path; ++pch; continue; } // change slash to backslash if ('/' == *path) { *pch = '\\'; ++path; ++pch; continue; } *pch = *path; ++pch; ++path; } *pch = '\0'; } #endif /* EXEC_FOREIGN */