/*++ Copyright (c) Microsoft. All rights reserved. Module Name: WaitPid.c Abstract: This file is a WaitPid test. --*/ #include "lxtcommon.h" #include "unittests.h" #include #include #include #include #include #include #include #include #include #include "lxtutil.h" #define LXT_NAME "WaitPid" #define WAITPID_DEFAULT_WAIT_TIMEOUT_US 100000 #define WAITPID_DEFAULT_WAIT_COUNT 20 #define WAITPID_THREADGROUP_LEADER_UID 1044 #define WAITPID_PTHREAD_UID 1055 bool g_VmMode = false; int GetPPidPoll(pid_t ExpectedPid); LXT_VARIATION_HANDLER WaitPidVariationExitStatusBlock; int WaitPidVariationExitStatusHelper(PLXT_ARGS Args, int Blocking); LXT_VARIATION_HANDLER WaitPidVariationExitStatusPoll; LXT_VARIATION_HANDLER WaitPidVariationInitPid; LXT_VARIATION_HANDLER WaitPidVariationParentChild; LXT_VARIATION_HANDLER WaitPidVariationProcessGroup; LXT_VARIATION_HANDLER WaitPidVariationInvalidParameter; LXT_VARIATION_HANDLER WaitPidVariationWaitId; LXT_VARIATION_HANDLER WaitPidVariationCloneParent; LXT_VARIATION_HANDLER WaitPidVariationZombie; LXT_VARIATION_HANDLER WaitPidVariationZombieStress; // // Global constants // // FIXME: Enable parent\child test when clone gs issue is resolved. // static const LXT_VARIATION g_LxtVariations[] = { {"WaitPidVariation - Exit status poll", WaitPidVariationExitStatusPoll}, {"WaitPidVariation - Exit status block", WaitPidVariationExitStatusBlock}, {"WaitPidVariation - Init pid", WaitPidVariationInitPid}, {"WaitPidVariation - Process groups", WaitPidVariationProcessGroup}, {"WaitPidVariation - Invalid parameter", WaitPidVariationInvalidParameter}, {"WaitPidVariation - waitid", WaitPidVariationWaitId}, {"WaitPidVariation - CLONE_PARENT", WaitPidVariationCloneParent}, {"WaitPidVariation - zombie support", WaitPidVariationZombie}, {"WaitPidVariation - zombie stress", WaitPidVariationZombieStress}}; int WaitPidTestEntry(int Argc, char* Argv[]) /*++ --*/ { LXT_ARGS Args; int Result; if (LxtWslVersion() == 2) { g_VmMode = true; } LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME)); LXT_SYNCHRONIZATION_POINT_INIT(); LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations))); ErrorExit: LXT_SYNCHRONIZATION_POINT_DESTROY(); LxtUninitialize(); return !LXT_SUCCESS(Result); } int GetPPidPoll(pid_t ExpectedPPid) /*++ --*/ { pid_t CurrentPPid; int Result; int WaitCount; // // Wait for the current ppid to reach the expected ppid. // for (WaitCount = 0; WaitCount < WAITPID_DEFAULT_WAIT_COUNT; ++WaitCount) { CurrentPPid = getppid(); if (CurrentPPid == ExpectedPPid) { break; } usleep(WAITPID_DEFAULT_WAIT_TIMEOUT_US); } if (WaitCount == WAITPID_DEFAULT_WAIT_COUNT) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected pid, %d != %d", CurrentPPid, ExpectedPPid); goto ErrorExit; } Result = LXT_RESULT_SUCCESS; ErrorExit: return Result; } int LxtWaitPidHelper(pid_t ChildPid, int ExpectedWaitStatus, int Blocking) /*++ --*/ { int SecondWaitPidStatus; int Result; int WaitCount; int WaitPidResult; int WaitPidStatus; if (Blocking == 0) { LxtCheckResult(LxtWaitPidPoll(ChildPid, ExpectedWaitStatus)); } else { LxtCheckErrno((WaitPidResult = waitpid(ChildPid, &WaitPidStatus, 0))); if ((WaitPidStatus & 0xFFFF0000) != 0) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected high short status: %d - %d", WaitPidStatus, ExpectedWaitStatus); goto ErrorExit; } if (WaitPidStatus != ExpectedWaitStatus) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected status: %d != %d", WaitPidStatus, ExpectedWaitStatus); goto ErrorExit; } if (WIFEXITED(WaitPidStatus) != 0) { LxtCheckErrnoFailure(waitpid(ChildPid, &SecondWaitPidStatus, WNOHANG), ECHILD); } } ErrorExit: return Result; } int WaitPidVariationInitPid(PLXT_ARGS Args) /*++ --*/ { pid_t ChildPid; int ExitCodeIndex; unsigned char ExitCode; int ExpectedPid; int ExpectedWaitStatus; pid_t ParentParentPid; LXT_PIPE Pipe = {-1, -1}; int Result; pid_t WaitPidResult; int WaitPidStatus; int WaitPipe; // // Determine who the subreaper of the test is. On WSL 1 it will be init, on // WSL 2 it will be the relay process (parent of this process). // ExpectedPid = LXT_INIT_PID; if (g_VmMode != false) { ExpectedPid = getppid(); } LxtCheckResult(LxtCreatePipe(&Pipe)); // // Check when a parent dies, the child has a parent pid of the subreaper. // ExitCode = 0; LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { LxtCheckResult(GetPPidPoll(ExpectedPid)); LxtCheckErrno(write(Pipe.Write, &WaitPipe, sizeof(WaitPipe))); } _exit(ExitCode); } else { LxtCheckResult(LxtWaitPidPoll(-1, ExitCode)); LxtCheckErrno(read(Pipe.Read, &WaitPipe, sizeof(WaitPipe))); } // // The init process should never be a child. // LxtCheckErrnoFailure(waitpid(LXT_INIT_PID, &WaitPidStatus, WNOHANG), ECHILD); ErrorExit: LxtClosePipe(&Pipe); return Result; } int WaitPidVariationExitStatusBlock(PLXT_ARGS Args) /*++ --*/ { return WaitPidVariationExitStatusHelper(Args, 1); } int WaitPidVariationExitStatusHelper(PLXT_ARGS Args, int Blocking) /*++ --*/ { int ExitCodeIndex; unsigned char ExitCodes[] = {0, 1, 128, 255}; pid_t ChildPid[LXT_COUNT_OF(ExitCodes)]; int ExpectedWaitStatus; pid_t ParentParentPid; pid_t ParentPid; int Result; // // Check that the correct _exit status is returned to a parent process and // that it can only be checked once serially. // ParentParentPid = getppid(); ParentPid = getpid(); for (ExitCodeIndex = 0; ExitCodeIndex < LXT_COUNT_OF(ExitCodes); ++ExitCodeIndex) { LxtCheckErrno(ChildPid[ExitCodeIndex] = fork()); if (ChildPid[ExitCodeIndex] == 0) { ChildPid[ExitCodeIndex] = getpid(); if (getppid() != ParentPid) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected parent pid in child - %d != %d", ChildPid[ExitCodeIndex], ParentPid); goto ErrorExit; } _exit(ExitCodes[ExitCodeIndex]); } else { ExpectedWaitStatus = ExitCodes[ExitCodeIndex] << 8; LxtCheckResult(LxtWaitPidHelper(ChildPid[ExitCodeIndex], ExpectedWaitStatus, Blocking)); } } // // Recheck the results after launching the children in parallel. // LxtLogInfo("Running forks in parallel..."); for (ExitCodeIndex = 0; ExitCodeIndex < LXT_COUNT_OF(ExitCodes); ++ExitCodeIndex) { LxtCheckErrno(ChildPid[ExitCodeIndex] = fork()); if (ChildPid[ExitCodeIndex] == 0) { ChildPid[ExitCodeIndex] = getpid(); if (getppid() != ParentPid) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected parent pid in child - %d != %d", ChildPid[ExitCodeIndex], ParentPid); goto ErrorExit; } _exit(ExitCodes[ExitCodeIndex]); } } while (ExitCodeIndex--) { ExpectedWaitStatus = ExitCodes[ExitCodeIndex] << 8; LxtCheckResult(LxtWaitPidHelper(ChildPid[ExitCodeIndex], ExpectedWaitStatus, Blocking)); } ErrorExit: return Result; } int WaitPidVariationExitStatusPoll(PLXT_ARGS Args) /*++ --*/ { return WaitPidVariationExitStatusHelper(Args, 0); } typedef struct _WAIT_PID_PARENT_DATA { int Generation; pid_t ParentPid; pid_t ParentTid; LXT_PIPE Pipes[4]; LXT_CLONE_ARGS CloneArgs[2]; pid_t ForkPid; } WAIT_PID_PARENT_DATA, *PWAIT_PID_PARENT_DATA; void WaitPidPrintData(PWAIT_PID_PARENT_DATA CurrentData, char* Message) { int Index; pid_t WaitResult; int WaitStatus; // // TODO: Enable _CLONE // LxtLogInfo("***** %s", Message); WaitStatus = -1; for (Index = 0; Index < LXT_COUNT_OF(CurrentData->CloneArgs); ++Index) { LxtLogInfo("Before waitpid", Message); WaitResult = waitpid(CurrentData->CloneArgs[Index].CloneId, &WaitStatus, WNOHANG); LxtLogInfo("%s - Clone %d WNOHANG - %d, %d", Message, CurrentData->CloneArgs[Index].CloneId, WaitResult, WaitStatus); // WaitResult = waitpid(CurrentData->CloneArgs[Index].CloneId, &WaitStatus, WNOHANG | __WCLONE); // LxtLogInfo("%s - Clone %d WNOHANG | __WCLONE - %d, %d", Message, CurrentData->CloneArgs[Index].CloneId, WaitResult, WaitStatus); } WaitResult = waitpid(CurrentData->ForkPid, &WaitStatus, WNOHANG); LxtLogInfo("%s - Fork %d WNOHANG - %d, %d", Message, CurrentData->ForkPid, WaitResult, WaitStatus); // WaitResult = waitpid(CurrentData->ForkPid, &WaitStatus, WNOHANG | __WCLONE); // LxtLogInfo("%s - Fork %d WNOHANG | __WCLONE - %d, %d", Message, CurrentData->ForkPid, WaitResult, WaitStatus); LxtLogInfo("***** %s", Message); return; } int WaitPidVariationParentChildClone(void* Parameter) /*++ --*/ { int CloneIndex; PWAIT_PID_PARENT_DATA CurrentData; pid_t CurrentPid; pid_t CurrentTid; int Index; int ParentIndex; int Result; int WaitPipe; CurrentData = Parameter; CurrentTid = gettid(); for (Index = 0; Index < LXT_COUNT_OF(CurrentData->CloneArgs); ++Index) { if (CurrentData->CloneArgs[Index].CloneId == CurrentTid) { CloneIndex = Index; break; } } if (Index == LXT_COUNT_OF(CurrentData->CloneArgs)) { Result = LXT_RESULT_FAILURE; LxtLogError("Unable to find clone tid %d", CurrentTid); goto ErrorExit; } LxtCheckErrno(read(CurrentData->Pipes[CloneIndex].Read, &WaitPipe, sizeof(WaitPipe))); CurrentPid = getpid(); if (CurrentPid != CurrentData->ParentPid) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected pid for clone %d: %d != %d", CloneIndex, CurrentPid, CurrentData->ParentPid); goto ErrorExit; } WaitPidPrintData(CurrentData, "Clone"); ParentIndex = LXT_COUNT_OF(CurrentData->CloneArgs) + 1; LxtCheckErrno(write(CurrentData->Pipes[ParentIndex].Write, &WaitPipe, sizeof(WaitPipe))); LxtCheckErrno(read(CurrentData->Pipes[CloneIndex].Read, &WaitPipe, sizeof(WaitPipe))); ErrorExit: LxtLogInfo("Clone %d exit", CloneIndex); return Result; } int WaitPidVariationParentChildFork(PWAIT_PID_PARENT_DATA ParentData) /*++ --*/ { WAIT_PID_PARENT_DATA CurrentData; int ForkIndex; pid_t ParentPid; int ParentIndex; int Result; int WaitPipe; ForkIndex = LXT_COUNT_OF(ParentData->CloneArgs); LxtCheckErrno(read(ParentData->Pipes[ForkIndex].Read, &WaitPipe, sizeof(WaitPipe))); CurrentData.ParentPid = getpid(); CurrentData.ParentTid = gettid(); if (CurrentData.ParentPid != CurrentData.ParentTid) { Result = LXT_RESULT_FAILURE; LxtLogError("Current fork thread is not thread group leader %d != %d", CurrentData.ParentPid, CurrentData.ParentTid); goto ErrorExit; } ParentPid = getppid(); if (ParentPid != ParentData->ParentPid) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected ppid %d != %d", ParentPid, ParentData->ParentPid); goto ErrorExit; } if (CurrentData.ParentPid == ParentData->ParentPid) { Result = LXT_RESULT_FAILURE; LxtLogError("Unexpected pid %d == %d", CurrentData.ParentPid, ParentData->ParentPid); goto ErrorExit; } ParentData->ForkPid = getpid(); WaitPidPrintData(ParentData, "Fork"); ParentIndex = LXT_COUNT_OF(ParentData->CloneArgs) + 1; LxtCheckErrno(write(ParentData->Pipes[ParentIndex].Write, &WaitPipe, sizeof(WaitPipe))); LxtCheckErrno(read(ParentData->Pipes[ForkIndex].Read, &WaitPipe, sizeof(WaitPipe))); ErrorExit: _exit(Result); } int WaitPidVariationCreateParentData(PWAIT_PID_PARENT_DATA CurrentData) /*++ --*/ { int Index; int ParentIndex; int Result; int WaitPipe; pid_t WaitResult; int WaitStatus; CurrentData->Generation = CurrentData->Generation + 1; CurrentData->ParentPid = getpid(); CurrentData->ParentTid = gettid(); if (CurrentData->ParentPid != CurrentData->ParentTid) { Result = LXT_RESULT_FAILURE; LxtLogError("Current thread is not thread group leader %d != %d", CurrentData->ParentPid, CurrentData->ParentTid); goto ErrorExit; } for (Index = 0; Index < LXT_COUNT_OF(CurrentData->Pipes); ++Index) { LxtCheckResult(LxtCreatePipe(&CurrentData->Pipes[Index])); } // // TODO: Enable _CLONE // for (Index = 0; Index < LXT_COUNT_OF(CurrentData->CloneArgs); ++Index) { LxtCheckResult(LxtClone(WaitPidVariationParentChildClone, CurrentData, LXT_CLONE_FLAGS_DEFAULT, &CurrentData->CloneArgs[Index])); } LxtCheckErrno(CurrentData->ForkPid = fork()); if (CurrentData->ForkPid == 0) { WaitPidVariationParentChildFork(CurrentData); } WaitPidPrintData(CurrentData, "Parent"); ParentIndex = LXT_COUNT_OF(CurrentData->CloneArgs) + 1; for (Index = 0; Index < LXT_COUNT_OF(CurrentData->Pipes) - 1; ++Index) { LxtCheckErrno(write(CurrentData->Pipes[Index].Write, &WaitPipe, sizeof(WaitPipe))); LxtCheckErrno(read(CurrentData->Pipes[ParentIndex].Read, &WaitPipe, sizeof(WaitPipe))); } // // Release the waiters // for (Index = 0; Index < LXT_COUNT_OF(CurrentData->Pipes) - 1; ++Index) { LxtCheckErrno(write(CurrentData->Pipes[Index].Write, &WaitPipe, sizeof(WaitPipe))); } ErrorExit: for (Index = 0; Index < LXT_COUNT_OF(CurrentData->Pipes); ++Index) { LxtClosePipe(&CurrentData->Pipes[Index]); } return Result; } int WaitPidVariationParentChild(PLXT_ARGS Args) /*++ --*/ { WAIT_PID_PARENT_DATA CurrentData; int Result; memset(&CurrentData, 0, sizeof(CurrentData)); LxtCheckResult(WaitPidVariationCreateParentData(&CurrentData)); ErrorExit: return Result; } int WaitPidVariationProcessGroup(PLXT_ARGS Args) /*++ Description: This routine tests waiting on all children in the same process group. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { const int OtherGroupChildCount = 2; pid_t OtherGroupChild[OtherGroupChildCount]; int Result; pid_t SameGroupChild; int Status; int WaitResult; LxtCheckResult(LxtSignalBlock(SIGUSR1)); for (int Child = 0; Child < OtherGroupChildCount; Child++) { LxtCheckErrno(OtherGroupChild[Child] = fork()); if (OtherGroupChild[Child] == 0) { // // Change process group, then signal the parent. // LxtCheckErrnoZeroSuccess(setpgid(0, 0)); LxtCheckErrnoZeroSuccess(kill(getppid(), SIGUSR1)); _exit(0); } // // Wait to make sure the child changed its process group. // LxtCheckResult(LxtSignalWaitBlocked(SIGUSR1, OtherGroupChild[Child], 2)); } LxtCheckErrno(SameGroupChild = fork()); if (SameGroupChild == 0) { _exit(0); } // // Wait for the same process group, which should return the status of the // second child. // LxtCheckResult(WaitResult = LxtWaitPidPoll(0, 0)); LxtCheckEqual(WaitResult, SameGroupChild, "%d"); // // Wait again, which should fail because there are no more children in this // process group. // LxtCheckErrnoFailure(waitpid(0, &Status, WNOHANG), ECHILD); // // Wait on the specific process group of one of the remaining children. // LxtCheckResult(WaitResult = LxtWaitPidPoll(-OtherGroupChild[0], 0)); LxtCheckEqual(WaitResult, OtherGroupChild[0], "%d"); // // Wait on all children, which should return the child in the other process // group. // LxtCheckResult(WaitResult = LxtWaitPidPoll(-1, 0)); LxtCheckEqual(WaitResult, OtherGroupChild[1], "%d"); ErrorExit: return Result; } int WaitPidVariationInvalidParameter(PLXT_ARGS Args) /*++ Description: This routine tests invalid parameter handling for the waitpid system call. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { int Result; int Status; LxtCheckErrnoFailure(waitpid(0, &Status, WEXITED), EINVAL); LxtCheckErrnoFailure(waitpid(0, &Status, WNOWAIT), EINVAL); LxtCheckErrnoFailure(waitpid(0, NULL, WNOHANG), ECHILD); LxtCheckErrnoFailure(waitpid(0, (void*)-1, WNOHANG), ECHILD); ErrorExit: return Result; } void* WaitPidVariationWaitIdThread(void* Parameter) /*++ Description: This routine is the thread handler for the waitid test. Arguments: Parameter - Supplies the thread parameter. Return Value: Returns the thread id on success, -1 on failure. --*/ { int Result; LxtLogInfo("WaitPid child tid %d", gettid()); LxtCheckErrno(LxtSetUid(WAITPID_PTHREAD_UID)); // // Enter a very long sleep, this will be interrupted when the threadgroup // leader dies. // sleep(-1); Result = 0; ErrorExit: #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" return (void*)Result; #pragma GCC diagnostic pop } int WaitPidVariationWaitId(PLXT_ARGS Args) /*++ Description: This routine tests the waitid system call. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { int ChildPid; int ExpectedStatus; int Result; siginfo_t SigInfo; pthread_t Thread; struct rusage Usage; ExpectedStatus = 44; LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { // // Create a child thread, set the uid of the threadgroup leader, // and exit. // LxtCheckResultError(pthread_create(&Thread, NULL, WaitPidVariationWaitIdThread, NULL)); LxtLogInfo("Waitid parent tid %d", gettid()); // // Briefly sleep to allow the pthread to run. // sleep(1); LxtCheckErrno(LxtSetUid(WAITPID_THREADGROUP_LEADER_UID)); _exit(ExpectedStatus); } LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_ALL, 0, &SigInfo, WEXITED, &Usage)); LxtCheckEqual(Result, 0, "%d"); LxtCheckEqual(SigInfo.si_code, CLD_EXITED, "%d"); LxtCheckEqual(SigInfo.si_status, ExpectedStatus, "%d"); LxtCheckEqual(SigInfo.si_pid, ChildPid, "%d"); LxtCheckEqual(SigInfo.si_uid, WAITPID_THREADGROUP_LEADER_UID, "%d"); // // Wait for a specific child. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(ExpectedStatus); } LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_PID, ChildPid, &SigInfo, WEXITED, &Usage)); LxtCheckEqual(Result, 0, "%d"); LxtCheckEqual(SigInfo.si_code, CLD_EXITED, "%d"); LxtCheckEqual(SigInfo.si_status, ExpectedStatus, "%d"); LxtCheckEqual(SigInfo.si_pid, ChildPid, "%d"); // // Wait with WNOHANG specified. // // N.B. Parent must sleep to allow the child to exit before waiting. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(ExpectedStatus); } sleep(1); LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_PID, ChildPid, &SigInfo, (WEXITED | WNOHANG), &Usage)); LxtCheckEqual(Result, 0, "%d"); LxtCheckEqual(SigInfo.si_code, CLD_EXITED, "%d"); LxtCheckEqual(SigInfo.si_status, ExpectedStatus, "%d"); LxtCheckEqual(SigInfo.si_pid, ChildPid, "%d"); // // Wait with a null siginfo structure. // // N.B. The man page states that the pid of the child should be returned but // this is not the case. The wait should still be consumed so the // second wait should fail with ECHILD. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(0); } LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_ALL, 0, NULL, WEXITED, NULL)); LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, WEXITED, NULL), ECHILD); // // Wait with the WNOWAIT option supplied which means the wait is not // consumed. Wait again to consume the wait and verify it was not consumed // by the first call. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(ExpectedStatus); } LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_PID, ChildPid, &SigInfo, (WEXITED | WNOWAIT), &Usage)); LxtCheckEqual(Result, 0, "%d"); LxtCheckEqual(SigInfo.si_code, CLD_EXITED, "%d"); LxtCheckEqual(SigInfo.si_status, ExpectedStatus, "%d"); LxtCheckEqual(SigInfo.si_pid, ChildPid, "%d"); LxtCheckErrnoZeroSuccess(Result = LxtWaitId(P_PID, ChildPid, &SigInfo, WEXITED, &Usage)); LxtCheckEqual(Result, 0, "%d"); LxtCheckEqual(SigInfo.si_code, CLD_EXITED, "%d"); LxtCheckEqual(SigInfo.si_status, ExpectedStatus, "%d"); LxtCheckEqual(SigInfo.si_pid, ChildPid, "%d"); // // Call getrusage with the supported values. // LxtCheckErrno(getrusage(RUSAGE_SELF, &Usage)); LxtCheckErrno(getrusage(RUSAGE_THREAD, &Usage)); LxtCheckErrno(getrusage(RUSAGE_CHILDREN, &Usage)); // // Invalid parameter variations. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(0); } LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, WNOHANG, NULL), EINVAL); LxtCheckErrnoFailure(LxtWaitId(-1, 0, NULL, WEXITED, NULL), EINVAL); LxtCheckErrnoFailure(LxtWaitId(P_PGID + 5, 0, NULL, WEXITED, NULL), EINVAL); LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, 0x10, NULL), EINVAL); // // N.B. Providing an invalid pointer to a siginfo structure returns efault // but consumes the wait. // LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, (void*)-1, WEXITED, NULL), EFAULT); LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, WEXITED, NULL), ECHILD); LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { _exit(0); } // // N.B. Providing an invalid pointer to a rusage structure returns efault // but consumes the wait. // LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, WEXITED, (void*)-1), EFAULT); LxtCheckErrnoFailure(LxtWaitId(P_ALL, 0, NULL, WEXITED, NULL), ECHILD); ErrorExit: if (ChildPid == 0) { _exit(Result); } return Result; } int WaitPidVariationCloneParentChild(void) /*++ Description: This routine is the child process for WaitPidVariationCloneParent. Arguments: None.. Return Value: Returns 0 on success, -1 on failure. --*/ { pid_t ChildPid; pid_t GrandChildParent; pid_t ChildParent; int Result; int WaitPidStatus; Result = 0; // // Create a child process with the CLONE_PARENT flag. // // The new process should not be reported as a child. // ChildParent = getppid(); LxtLogInfo("ChildParent %d", ChildParent); LxtCheckResult(ChildPid = LxtCloneSyscall(CLONE_PARENT | SIGCHLD, NULL, NULL, NULL, NULL)); if (ChildPid == 0) { GrandChildParent = getppid(); LxtCheckEqual(ChildParent, GrandChildParent, "%d"); LxtLogInfo("Grand child %d exiting", LxtGetTid()); } else { LxtCheckErrnoFailure(waitpid(ChildPid, &WaitPidStatus, 0), ECHILD); LxtLogInfo("Child %d exiting", LxtGetTid()); } ErrorExit: _exit(Result); } int WaitPidVariationCloneParent(PLXT_ARGS Args) /*++ Description: This routine tests invalid parameter handling for the waitpid system call. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { pid_t ChildPid; pid_t GrandChildPid; int Result; int Status; ChildPid = -1; // // Create a child process, that in turn creates a grandchild process // with CLONE_PARENT. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { WaitPidVariationCloneParentChild(); } // // Wait for the child and the grandchild. // LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); LxtCheckResult(GrandChildPid = LxtWaitPidPoll(0, 0)); LxtLogInfo("Waited on grandchild %d", GrandChildPid); // // Check that the same scenario works when parent dies before // CLONE_PARENT. // LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { sleep(1); WaitPidVariationCloneParentChild(); } _exit(0); } // // Wait for the child and give the grandchild time to finish. // LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); LxtLogInfo("Waited on child %d", ChildPid); sleep(2); ErrorExit: if (ChildPid == 0) { _exit(Result); } return Result; } typedef struct _WAITPID_THREAD_PARAMETERS { pid_t ChildPid; pid_t GrandChildPid1; pid_t GrandChildPid2; LXT_SYNCHRONIZATION_POINT_DECLARE_FOR(ChildPid); LXT_SYNCHRONIZATION_POINT_DECLARE_FOR(GrandChildPid2); LXT_PIPE Pipe; int ChildLevel; int Variation; } WAITPID_THREAD_PARAMETERS, *PWAITPID_THREAD_PARAMETERS; int ThreadZombieThread(void* Context) /*++ Description: This routine is the thread proc used by WaitPidVariationThreadZombie. Arguments: Context - Supplies the thread context. Return Value: Returns 0 on success, -1 on failure. --*/ { int ChildLevel; pid_t ChildPid; int Flags; pid_t GrandChildPid1; pid_t GrandChildPid2; LXT_CLONE_ARGS CloneArgs; WAITPID_THREAD_PARAMETERS LocalParameter; PWAITPID_THREAD_PARAMETERS Parameter; int Result; int Status; LXT_SYNCHRONIZATION_POINT_DECLARE_FOR(ChildPid); LXT_SYNCHRONIZATION_POINT_DECLARE_FOR(GrandChildPid2); memcpy(&LocalParameter, Context, sizeof(LocalParameter)); ChildPid = 0; ChildLevel = LocalParameter.ChildLevel; if (ChildLevel != 0) { free(Context); } LxtSyncChildPidParent = LocalParameter.LxtSyncChildPidParent; LxtSyncChildPidChild = LocalParameter.LxtSyncChildPidChild; GrandChildPid1 = -1; GrandChildPid2 = -1; LxtSyncGrandChildPid2Parent = LocalParameter.LxtSyncGrandChildPid2Parent; LxtSyncGrandChildPid2Child = LocalParameter.LxtSyncGrandChildPid2Child; Parameter = NULL; LxtSignalInitializeThread(); if (ChildLevel == 0) { LxtLogInfo("Child %d starting, variation = %d...", getpid(), LocalParameter.Variation); LxtCheckResult(LxtSignalInitialize()); LxtCheckResult(LxtSignalSetupHandler(SIGCHLD, SA_SIGINFO)); LxtCheckErrno(setsid()); switch (LocalParameter.Variation) { case 0: Flags = SIGCHLD; break; case 1: Flags = CLONE_FS | CLONE_FILES | SIGCHLD; break; case 2: Flags = CLONE_FS | CLONE_FILES; break; case 3: Flags = CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES; break; }; Parameter = malloc(sizeof(*Parameter)); if (Parameter != NULL) { memcpy(Parameter, &LocalParameter, sizeof(*Parameter)); } LxtCheckNotEqual(Parameter, NULL, "%p"); Parameter->ChildLevel = 1; // // The clone stack is leaked but the current process will exit shortly. // LxtCheckErrno(LxtClone(ThreadZombieThread, Parameter, Flags, &CloneArgs)); Parameter = NULL; GrandChildPid1 = CloneArgs.CloneId; LocalParameter.GrandChildPid1 = GrandChildPid1; Parameter = malloc(sizeof(*Parameter)); if (Parameter != NULL) { memcpy(Parameter, &LocalParameter, sizeof(*Parameter)); } LxtCheckNotEqual(Parameter, NULL, "%p"); Parameter->ChildLevel = 2; // // The clone stack is leaked but the current process will exit shortly. // LxtCheckErrno(LxtClone(ThreadZombieThread, Parameter, Flags, &CloneArgs)); Parameter = NULL; LocalParameter.GrandChildPid2 = CloneArgs.CloneId; LxtSignalWait(); if (LocalParameter.Variation < 2) { LxtCheckResult(LxtSignalCheckSigChldReceived(CLD_EXITED, GrandChildPid1, getuid(), 0)); } else if (LocalParameter.Variation != 3) { LxtCheckResult(LxtSignalCheckNoSignal()); } LXT_SYNCHRONIZATION_POINT_CHILD(); LxtCheckErrno(write(LocalParameter.Pipe.Write, &LocalParameter, sizeof(LocalParameter))); LXT_SYNCHRONIZATION_POINT_CHILD(); // // Exiting with one zombie child, and one child waiting for an exit // signal. // LxtLogInfo("Exiting pid = %d", getpid()); Result = 0; goto ErrorExit; } else if (ChildLevel == 1) { // // First child created via this thread, grandchild of the original. // GrandChildPid1 = 0; LxtLogInfo("Grandchild %d starting...", getpid()); LxtLogInfo("Exiting pid = %d", getpid()); Result = 0; goto ErrorExit; } else { // // Second child created via this thread, second grandchild of the // original. // GrandChildPid2 = 0; LxtLogInfo("Grandchild %d starting...", getpid()); LxtCheckResult(LxtSignalInitialize()); LxtCheckResult(LxtSignalSetupHandler(SIGHUP, SA_SIGINFO)); LXT_SYNCHRONIZATION_POINT_CHILD_FOR(GrandChildPid2); LxtCheckResult(LxtSignalCheckNoSignal()); LxtLogInfo("Exiting pid = %d", getpid()); Result = 0; goto ErrorExit; } ErrorExit: if (Result != 0) { // // Intentionally orphaning the thread unless there was an error. // LXT_SYNCHRONIZATION_POINT_END_FOR(GrandChildPid2, FALSE); } if (ChildLevel == 0) { if (LocalParameter.Variation != 3) { LXT_SYNCHRONIZATION_POINT_END(); } else { LXT_SYNCHRONIZATION_POINT_PTHREAD_END_THREAD(); } } if (Parameter != NULL) { free(Parameter); } syscall(SYS_exit, Result); } int WaitPidVariationZombie(PLXT_ARGS Args) /*++ Description: This routine tests zombie handling with waitpid system call. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { char Buffer[50]; LXT_CLONE_ARGS ChildClone; int ChildDir; pid_t ChildPid; FILE* ChildStatusFile; pthread_t ChildThread; int GrandChildDir; pid_t GrandChildPid1; pid_t GrandChildPid2; FILE* GrandChildStatusFile; int Index; bool IsFork; char Path[32]; LXT_PIPE Pipe = {-1, -1}; void* PointerResult; int Result; struct stat StatBuffer; int Status; char StatusDescription[25]; char* StatusFileEntry; size_t StatusFileEntryLength; char StatusToken; WAITPID_THREAD_PARAMETERS ThreadParam; LXT_SYNCHRONIZATION_POINT_DECLARE_FOR(GrandChildPid2); ChildDir = -1; ChildPid = -1; ChildStatusFile = NULL; ChildThread = 0; GrandChildDir = -1; GrandChildPid1 = -1; GrandChildPid2 = -1; GrandChildStatusFile = NULL; IsFork = true; StatusFileEntry = NULL; LXT_SYNCHRONIZATION_POINT_INIT_FOR(GrandChildPid2); // // TODO: test needs to be debugged for WSL2. // if (g_VmMode != false) { Result = 0; goto ErrorExit; } LxtCheckResult(LxtSignalInitialize()); LxtCheckResult(LxtSignalSetupHandler(SIGCHLD, SA_SIGINFO)); LxtCheckResult(LxtSignalIgnore(SIGPIPE)); memset(&ThreadParam, 0, sizeof(ThreadParam)); ThreadParam.LxtSyncChildPidParent = LxtSyncChildPidParent; ThreadParam.LxtSyncChildPidChild = LxtSyncChildPidChild; ThreadParam.LxtSyncGrandChildPid2Parent = LxtSyncGrandChildPid2Parent; ThreadParam.LxtSyncGrandChildPid2Child = LxtSyncGrandChildPid2Child; for (Index = 0; Index < 4; Index += 1) { ChildPid = -1; GrandChildPid1 = -1; GrandChildPid2 = -1; LXT_SYNCHRONIZATION_POINT_START(); LXT_SYNCHRONIZATION_POINT_START_FOR(GrandChildPid2); ThreadParam.ChildPid = -1; ThreadParam.GrandChildPid2 = -1; ThreadParam.GrandChildPid2 = -1; LxtCheckResult(LxtCreatePipe(&Pipe)); memcpy(&ThreadParam.Pipe, &Pipe, sizeof(Pipe)); ThreadParam.Variation = Index; LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { return ThreadZombieThread(&ThreadParam); } ThreadParam.ChildPid = ChildPid; LXT_SYNCHRONIZATION_POINT(); LxtCheckErrno(read(Pipe.Read, &ThreadParam, sizeof(ThreadParam))); GrandChildPid1 = ThreadParam.GrandChildPid1; GrandChildPid2 = ThreadParam.GrandChildPid2; // // Check basic info of child after one of its children has entered // zombie state. // LxtCheckErrno(sprintf(Path, "/proc/%d", ChildPid)); LxtCheckErrno(ChildDir = open(Path, O_RDONLY)); LxtCheckErrno(fstat(ChildDir, &StatBuffer)); LxtCheckErrno(sprintf(Path, "/proc/%d/status", ChildPid)); LxtCheckNullErrno(ChildStatusFile = fopen(Path, "r")); while ((Result = getline(&StatusFileEntry, &StatusFileEntryLength, ChildStatusFile)) >= 0) { if (strncmp(StatusFileEntry, "State:\t", 7) == 0) { break; } free(StatusFileEntry); StatusFileEntry = NULL; } LxtCheckErrno(Result); Result = sscanf(StatusFileEntry, "State: %c (%s)", &StatusToken, StatusDescription); LxtCheckEqual(Result, 2, "%d"); if (StatusToken == 'R') { LxtCheckStringEqual(StatusDescription, "running)"); } else if (StatusToken == 'S') { LxtCheckStringEqual(StatusDescription, "sleeping)"); } else { LxtLogError("Unexpected status: %c (%s)", StatusToken, StatusDescription); Result = -1; goto ErrorExit; } LxtCheckErrno(getpgid(ChildPid)); LxtCheckErrno(kill(ChildPid, SIGWINCH)); LxtCheckErrno(sprintf(Path, "/proc/%d/ns/mnt", ChildPid)); LxtCheckErrno(readlink(Path, Buffer, sizeof(Buffer))); LxtCheckErrno(sprintf(Path, "/proc/%d/fd/0", ChildPid)); LxtCheckErrno(stat(Path, &StatBuffer)); // // Check basic info of first zombie. // LxtCheckErrno(sprintf(Path, "/proc/%d", GrandChildPid1)); if (Index != 3) { LxtCheckErrno(getpgid(GrandChildPid1)); LxtCheckErrno(kill(GrandChildPid1, SIGWINCH)); LxtCheckErrno(GrandChildDir = open(Path, O_RDONLY)); LxtCheckErrno(fstat(GrandChildDir, &StatBuffer)); LxtCheckErrno(sprintf(Path, "/proc/%d/fd/0", GrandChildPid1)); // // TODO_LX: Zombied procfs "fd" entry should be accessible but // empty. // // LxtCheckErrnoFailure(stat(Path, &StatBuffer), ENOENT); // LxtCheckErrno(sprintf(Path, "/proc/%d/status", GrandChildPid1)); LxtCheckNullErrno(GrandChildStatusFile = fopen(Path, "r")); free(StatusFileEntry); StatusFileEntry = NULL; while ((Result = getline(&StatusFileEntry, &StatusFileEntryLength, GrandChildStatusFile)) >= 0) { if (strncmp(StatusFileEntry, "State:\t", 7) == 0) { break; } free(StatusFileEntry); StatusFileEntry = NULL; } LxtCheckErrno(Result); Result = sscanf(StatusFileEntry, "State: %c (%s)", &StatusToken, StatusDescription); LxtCheckEqual(Result, 2, "%d"); LxtCheckEqual(StatusToken, 'Z', "%c"); LxtCheckStringEqual(StatusDescription, "zombie)"); LxtCheckErrno(sprintf(Path, "/proc/%d/fd/0", GrandChildPid1)); // // TODO_LX: Zombied procfs "fd" entry should be accessible but // empty. // // LxtCheckErrnoFailure(open(Path, O_RDONLY), ENOENT); // } else { LxtCheckErrnoFailure(GrandChildDir = open(Path, O_RDONLY), ENOENT); } // // Close the read end of the pipe. // close(Pipe.Read); Pipe.Read = -1; LxtCheckErrno(write(Pipe.Write, &Result, sizeof(Result))); // // Allow the child to exit. // LXT_SYNCHRONIZATION_POINT(); LxtSignalWait(); if (Index != 3) { LxtCheckResult(LxtSignalCheckSigChldReceived(CLD_EXITED, ChildPid, getuid(), 0)); LxtSignalResetReceived(); } else { // // The threadgroup leader should have exited but there is still a // thread running so the signal will not yet be sent. // LxtCheckResult(LxtSignalCheckNoSignal()); } // // Check basic info of running grandchild after child exit. // if (Index != 3) { // // TODO_LX: These calls fail due to lack of proper thread support. // LxtCheckErrno(getpgid(GrandChildPid2)); LxtCheckErrno(kill(GrandChildPid2, SIGWINCH)); } LxtCheckErrno(sprintf(Path, "/proc/%d/ns/mnt", GrandChildPid2)); LxtCheckErrno(readlink(Path, Buffer, sizeof(Buffer))); LxtCheckErrno(sprintf(Path, "/proc/%d/fd/0", GrandChildPid2)); LxtCheckErrno(stat(Path, &StatBuffer)); // // Signal last grandchild to exit. // LXT_SYNCHRONIZATION_POINT_PARENT_FOR(GrandChildPid2); LxtSignalWait(); if (Index == 3) { LxtCheckResult(LxtSignalCheckSigChldReceived(CLD_EXITED, ChildPid, getuid(), 0)); LxtSignalResetReceived(); } // // Check the child information after it is a zombie. // LxtCheckErrno(fstat(ChildDir, &StatBuffer)); rewind(ChildStatusFile); free(StatusFileEntry); StatusFileEntry = NULL; while ((Result = getline(&StatusFileEntry, &StatusFileEntryLength, ChildStatusFile)) >= 0) { if (strncmp(StatusFileEntry, "State:\t", 7) == 0) { break; } free(StatusFileEntry); StatusFileEntry = NULL; } LxtCheckErrno(Result); Result = sscanf(StatusFileEntry, "State: %c (%s)", &StatusToken, StatusDescription); LxtCheckEqual(Result, 2, "%d"); LxtCheckEqual(StatusToken, 'Z', "%c"); LxtCheckStringEqual(StatusDescription, "zombie)"); LxtCheckErrno(getpgid(ChildPid)); LxtCheckErrno(kill(ChildPid, SIGWINCH)); LxtCheckErrno(sprintf(Path, "/proc/%d/ns/mnt", ChildPid)); LxtCheckErrnoFailure(readlink(Path, Buffer, sizeof(Buffer)), ENOENT); sleep(2); LxtCheckErrnoFailure(write(Pipe.Write, &Result, sizeof(Result)), EPIPE); LXT_SYNCHRONIZATION_POINT_END(); ChildPid = -1; LxtCheckErrnoFailure(waitpid(-1, &Status, WNOHANG), ECHILD); LxtCheckEqual(Result, 0, "%d"); LxtClosePipe(&Pipe); } ErrorExit: free(StatusFileEntry); LXT_SYNCHRONIZATION_POINT_END(); if (GrandChildPid2 > 0) { LXT_SYNCHRONIZATION_POINT_PTHREAD_END_THREAD_FOR(GrandChildPid2); } LXT_SYNCHRONIZATION_POINT_DESTROY_FOR(GrandChildPid2); if (GrandChildStatusFile != NULL) { fclose(GrandChildStatusFile); } if (GrandChildDir >= 0) { close(GrandChildDir); } if (ChildStatusFile != NULL) { fclose(ChildStatusFile); } if (ChildDir >= 0) { close(ChildDir); } // // Intentionally leaking pipes from children / grandchildren to test // file descriptor cleanup on exit in the fork case. // LxtClosePipe(&Pipe); LxtSignalDefault(SIGPIPE); LxtSignalDefault(SIGCHLD); return Result; } int WaitPidVariationZombieStress(PLXT_ARGS Args) /*++ Description: This routine stress tests zombie handling. Arguments: Args - Supplies the command line arguments. Return Value: Returns 0 on success, -1 on failure. --*/ { pid_t ChildPid; pid_t GrandChildPid; pid_t GreatGrandChildPid; int Iterations; int Result; int Status; GrandChildPid = -1; GreatGrandChildPid = -1; for (Iterations = 0; Iterations < 100; Iterations += 1) { ChildPid = -1; LxtCheckErrno(ChildPid = fork()); if (ChildPid == 0) { LxtCheckErrno(setsid()); LxtCheckErrno(prctl(PR_SET_CHILD_SUBREAPER, 1)); for (Iterations = 0; Iterations < 3; Iterations += 1) { LxtCheckErrno(GrandChildPid = fork()); if (GrandChildPid == 0) { ChildPid = -1; LxtCheckErrno(GreatGrandChildPid = fork()); if (GreatGrandChildPid == 0) { usleep(random() % 100); goto ErrorExit; } usleep(random() % 100); (void)waitpid(GreatGrandChildPid, &Status, WNOHANG); goto ErrorExit; } (void)waitpid(GrandChildPid, &Status, WNOHANG); } usleep(random() % 100); (void)waitpid(GrandChildPid, &Status, WNOHANG); goto ErrorExit; } LXT_SYNCHRONIZATION_POINT_END(); ChildPid = -1; } ErrorExit: if ((GreatGrandChildPid == 0) || (GrandChildPid == 0)) { _exit(Result); } LXT_SYNCHRONIZATION_POINT_END(); return Result; }