+ char buf[sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + sizeof(ULONG_PTR) * 4];
+ PJOBOBJECT_BASIC_PROCESS_ID_LIST pid_list = (JOBOBJECT_BASIC_PROCESS_ID_LIST *)buf;
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION ext_limit_info;
+ JOBOBJECT_BASIC_LIMIT_INFORMATION *basic_limit_info = &ext_limit_info.BasicLimitInformation;
+ DWORD dwret, ret_len;
+ PROCESS_INFORMATION pi[2];
+ HANDLE job;
+ BOOL ret;
+
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ /* Only active processes are returned */
+ create_process("exit", &pi[0]);
+ ret = pAssignProcessToJobObject(job, pi[0].hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+ dwret = WaitForSingleObject(pi[0].hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi[0].hProcess);
+ CloseHandle(pi[0].hThread);
+
+ create_process("wait", &pi[0]);
+ ret = pAssignProcessToJobObject(job, pi[0].hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ create_process("wait", &pi[1]);
+ ret = pAssignProcessToJobObject(job, pi[1].hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ SetLastError(0xdeadbeef);
+ ret = QueryInformationJobObject(job, JobObjectBasicProcessIdList, pid_list,
+ FIELD_OFFSET(JOBOBJECT_BASIC_PROCESS_ID_LIST, ProcessIdList), &ret_len);
+ ok(!ret, "QueryInformationJobObject expected failure\n");
+ todo_wine
+ expect_eq_d(ERROR_BAD_LENGTH, GetLastError());
+
+ SetLastError(0xdeadbeef);
+ memset(buf, 0, sizeof(buf));
+ pid_list->NumberOfAssignedProcesses = 42;
+ pid_list->NumberOfProcessIdsInList = 42;
+ ret = QueryInformationJobObject(job, JobObjectBasicProcessIdList, pid_list,
+ FIELD_OFFSET(JOBOBJECT_BASIC_PROCESS_ID_LIST, ProcessIdList[1]), &ret_len);
+ ok(!ret, "QueryInformationJobObject expected failure\n");
+ todo_wine
+ expect_eq_d(ERROR_MORE_DATA, GetLastError());
+ if (ret)
+ {
+ expect_eq_d(42, pid_list->NumberOfAssignedProcesses);
+ expect_eq_d(42, pid_list->NumberOfProcessIdsInList);
+ }
+
+ memset(buf, 0, sizeof(buf));
+ ret = pQueryInformationJobObject(job, JobObjectBasicProcessIdList, pid_list, sizeof(buf), &ret_len);
+ todo_wine
+ ok(ret, "QueryInformationJobObject error %u\n", GetLastError());
+ if(ret)
+ {
+ if (pid_list->NumberOfAssignedProcesses == 3) /* Win 8 */
+ win_skip("Number of assigned processes broken on Win 8\n");
+ else
+ {
+ ULONG_PTR *list = pid_list->ProcessIdList;
+
+ ok(ret_len == FIELD_OFFSET(JOBOBJECT_BASIC_PROCESS_ID_LIST, ProcessIdList[2]),
+ "QueryInformationJobObject returned ret_len=%u\n", ret_len);
+
+ expect_eq_d(2, pid_list->NumberOfAssignedProcesses);
+ expect_eq_d(2, pid_list->NumberOfProcessIdsInList);
+ expect_eq_d(pi[0].dwProcessId, list[0]);
+ expect_eq_d(pi[1].dwProcessId, list[1]);
+ }
+ }
+
+ /* test JobObjectBasicLimitInformation */
+ ret = pQueryInformationJobObject(job, JobObjectBasicLimitInformation, basic_limit_info,
+ sizeof(*basic_limit_info) - 1, &ret_len);
+ ok(!ret, "QueryInformationJobObject expected failure\n");
+ expect_eq_d(ERROR_BAD_LENGTH, GetLastError());
+
+ ret_len = 0xdeadbeef;
+ memset(basic_limit_info, 0x11, sizeof(*basic_limit_info));
+ ret = pQueryInformationJobObject(job, JobObjectBasicLimitInformation, basic_limit_info,
+ sizeof(*basic_limit_info), &ret_len);
+ ok(ret, "QueryInformationJobObject error %u\n", GetLastError());
+ ok(ret_len == sizeof(*basic_limit_info), "QueryInformationJobObject returned ret_len=%u\n", ret_len);
+ expect_eq_d(0, basic_limit_info->LimitFlags);
+
+ /* test JobObjectExtendedLimitInformation */
+ ret = pQueryInformationJobObject(job, JobObjectExtendedLimitInformation, &ext_limit_info,
+ sizeof(ext_limit_info) - 1, &ret_len);
+ ok(!ret, "QueryInformationJobObject expected failure\n");
+ expect_eq_d(ERROR_BAD_LENGTH, GetLastError());
+
+ ret_len = 0xdeadbeef;
+ memset(&ext_limit_info, 0x11, sizeof(ext_limit_info));
+ ret = pQueryInformationJobObject(job, JobObjectExtendedLimitInformation, &ext_limit_info,
+ sizeof(ext_limit_info), &ret_len);
+ ok(ret, "QueryInformationJobObject error %u\n", GetLastError());
+ ok(ret_len == sizeof(ext_limit_info), "QueryInformationJobObject returned ret_len=%u\n", ret_len);
+ expect_eq_d(0, basic_limit_info->LimitFlags);
+
+ TerminateProcess(pi[0].hProcess, 0);
+ CloseHandle(pi[0].hProcess);
+ CloseHandle(pi[0].hThread);
+
+ TerminateProcess(pi[1].hProcess, 0);
+ CloseHandle(pi[1].hProcess);
+ CloseHandle(pi[1].hThread);
+
+ CloseHandle(job);
+}
+
+static void test_CompletionPort(void)
+{
+ JOBOBJECT_ASSOCIATE_COMPLETION_PORT port_info;
+ PROCESS_INFORMATION pi;
+ HANDLE job, port;
+ DWORD dwret;
+ BOOL ret;
+
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ port = pCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+ ok(port != NULL, "CreateIoCompletionPort error %u\n", GetLastError());
+
+ port_info.CompletionKey = job;
+ port_info.CompletionPort = port;
+ ret = pSetInformationJobObject(job, JobObjectAssociateCompletionPortInformation, &port_info, sizeof(port_info));
+ ok(ret, "SetInformationJobObject error %u\n", GetLastError());
+
+ create_process("wait", &pi);
+
+ ret = pAssignProcessToJobObject(job, pi.hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ test_completion(port, JOB_OBJECT_MSG_NEW_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0);
+
+ TerminateProcess(pi.hProcess, 0);
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ test_completion(port, JOB_OBJECT_MSG_EXIT_PROCESS, (DWORD_PTR)job, pi.dwProcessId, 0);
+ test_completion(port, JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, (DWORD_PTR)job, 0, 100);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ CloseHandle(job);
+ CloseHandle(port);
+}
+
+static void test_KillOnJobClose(void)
+{
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
+ PROCESS_INFORMATION pi;
+ DWORD dwret;
+ HANDLE job;
+ BOOL ret;
+
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
+ if (!ret && GetLastError() == ERROR_INVALID_PARAMETER)
+ {
+ win_skip("Kill on job close limit not available\n");
+ return;
+ }
+ ok(ret, "SetInformationJobObject error %u\n", GetLastError());
+
+ create_process("wait", &pi);
+
+ ret = pAssignProcessToJobObject(job, pi.hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ CloseHandle(job);
+
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+ if (dwret == WAIT_TIMEOUT) TerminateProcess(pi.hProcess, 0);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+}
+
+static void test_WaitForJobObject(void)
+{
+ HANDLE job;
+ PROCESS_INFORMATION pi;
+ BOOL ret;
+ DWORD dwret;
+
+ /* test waiting for a job object when the process is killed */
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", dwret);
+
+ create_process("wait", &pi);
+
+ ret = pAssignProcessToJobObject(job, pi.hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", dwret);
+
+ ret = pTerminateJobObject(job, 123);
+ ok(ret, "TerminateJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 500);
+ ok(dwret == WAIT_OBJECT_0 || broken(dwret == WAIT_TIMEOUT),
+ "WaitForSingleObject returned %u\n", dwret);
+
+ if (dwret == WAIT_TIMEOUT) /* Win 2000/XP */
+ {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ CloseHandle(job);
+ win_skip("TerminateJobObject doesn't signal job, skipping tests\n");
+ return;
+ }
+
+ /* the object is not reset immediately */
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ /* creating a new process doesn't reset the signalled state */
+ create_process("wait", &pi);
+
+ ret = pAssignProcessToJobObject(job, pi.hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ ret = pTerminateJobObject(job, 123);
+ ok(ret, "TerminateJobObject error %u\n", GetLastError());
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ CloseHandle(job);
+
+ /* repeat the test, but this time the process terminates properly */
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", dwret);
+
+ create_process("exit", &pi);
+
+ ret = pAssignProcessToJobObject(job, pi.hProcess);
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ dwret = WaitForSingleObject(job, 100);
+ ok(dwret == WAIT_TIMEOUT, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ CloseHandle(job);
+}
+
+static HANDLE test_AddSelfToJob(void)
+{
+ HANDLE job;
+ BOOL ret;
+
+ job = pCreateJobObjectW(NULL, NULL);
+ ok(job != NULL, "CreateJobObject error %u\n", GetLastError());
+
+ ret = pAssignProcessToJobObject(job, GetCurrentProcess());
+ ok(ret, "AssignProcessToJobObject error %u\n", GetLastError());
+
+ return job;
+}
+
+static void test_jobInheritance(HANDLE job)
+{
+ char buffer[MAX_PATH];
+ PROCESS_INFORMATION pi;
+ STARTUPINFOA si = {0};
+ DWORD dwret;
+ BOOL ret, out;
+
+ if (!pIsProcessInJob)
+ {
+ win_skip("IsProcessInJob not available.\n");
+ return;
+ }
+
+ sprintf(buffer, "\"%s\" tests/process.c %s", selfname, "exit");
+
+ ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ ok(ret, "CreateProcessA error %u\n", GetLastError());
+
+ out = FALSE;
+ ret = pIsProcessInJob(pi.hProcess, job, &out);
+ ok(ret, "IsProcessInJob error %u\n", GetLastError());
+ ok(out, "IsProcessInJob returned out=%u\n", out);
+
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+}
+
+static void test_BreakawayOk(HANDLE job)
+{
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info;
+ PROCESS_INFORMATION pi;
+ STARTUPINFOA si = {0};
+ char buffer[MAX_PATH];
+ BOOL ret, out;
+ DWORD dwret;
+
+ if (!pIsProcessInJob)
+ {
+ win_skip("IsProcessInJob not available.\n");
+ return;
+ }
+
+ sprintf(buffer, "\"%s\" tests/process.c %s", selfname, "exit");
+
+ ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
+ ok(!ret, "CreateProcessA expected failure\n");
+ expect_eq_d(ERROR_ACCESS_DENIED, GetLastError());
+
+ if (ret)
+ {
+ TerminateProcess(pi.hProcess, 0);
+
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+
+ limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
+ ok(ret, "SetInformationJobObject error %u\n", GetLastError());
+
+ ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, &si, &pi);
+ ok(ret, "CreateProcessA error %u\n", GetLastError());
+
+ ret = pIsProcessInJob(pi.hProcess, job, &out);
+ ok(ret, "IsProcessInJob error %u\n", GetLastError());
+ ok(!out, "IsProcessInJob returned out=%u\n", out);
+
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ limit_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
+ ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
+ ok(ret, "SetInformationJobObject error %u\n", GetLastError());
+
+ ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ ok(ret, "CreateProcess error %u\n", GetLastError());
+
+ ret = pIsProcessInJob(pi.hProcess, job, &out);
+ ok(ret, "IsProcessInJob error %u\n", GetLastError());
+ ok(!out, "IsProcessInJob returned out=%u\n", out);
+
+ dwret = WaitForSingleObject(pi.hProcess, 1000);
+ ok(dwret == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", dwret);
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ /* unset breakaway ok */
+ limit_info.BasicLimitInformation.LimitFlags = 0;
+ ret = pSetInformationJobObject(job, JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info));
+ ok(ret, "SetInformationJobObject error %u\n", GetLastError());
+}
+
+static void test_StartupNoConsole(void)
+{
+#ifndef _WIN64