Anton Kovalenko
4 years ago
commit
dbbf0b2f84
4 changed files with 212 additions and 0 deletions
@ -0,0 +1,26 @@ |
|||||||
|
## PIJ - Run process tree in a job and wait for completion. |
||||||
|
|
||||||
|
## Usage |
||||||
|
`pij my-program.exe arguments...` |
||||||
|
|
||||||
|
## Purpose |
||||||
|
Use it when you want to run `my-program.exe` in a batch file, but |
||||||
|
my-program.exe starts another process(es) and returns before they |
||||||
|
complete, which is not what you want: batch file should continue only |
||||||
|
when all additional processes also exit. PIJ runs initial process in a |
||||||
|
job and waits for this job to become empty, before exiting. |
||||||
|
|
||||||
|
## Result code (ERRORLEVEL) |
||||||
|
PIJ terminates with non-zero exit code if there's some problem during |
||||||
|
initial process creation or waiting phase. Zero exit code is returned |
||||||
|
when the last process in the job exits. |
||||||
|
|
||||||
|
## Restrictions |
||||||
|
All arguments are passed to CreateProcess'es lpCommandLine, hence some |
||||||
|
restrictions on what you can start: it should be a real EXE file |
||||||
|
(please start cmd.exe if you want to run another batch file), standard |
||||||
|
directories and PATH are used, registry AppPaths aren't. |
||||||
|
|
||||||
|
## Author |
||||||
|
Written by Anton Kovalenko <anton@sw4me.com>. My code is public domain. |
||||||
|
Uses some code by Raymond Chen. |
@ -0,0 +1 @@ |
|||||||
|
x86_64-w64-mingw32-gcc -O2 -s pij.c pijhelp.cc -o pij.exe -DUNICODE |
@ -0,0 +1,161 @@ |
|||||||
|
#ifndef UNICODE |
||||||
|
#define UNICODE |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <windows.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
// Skip initial argument in a command line. Adapted from my winserv
|
||||||
|
// http://www.sw4me.com/wiki/Winserv
|
||||||
|
static LPTSTR skip_arg(LPTSTR p) |
||||||
|
{ |
||||||
|
int inquote, slashes; |
||||||
|
|
||||||
|
// skip spaces
|
||||||
|
while (isspace(*p)) { p++; } |
||||||
|
if (*p == TEXT('\0')) { return p; } |
||||||
|
inquote = 0; slashes = 0; |
||||||
|
// we found where the argument begins
|
||||||
|
for (;;) { |
||||||
|
while (*p == TEXT('\\')) { |
||||||
|
slashes++; |
||||||
|
p++; |
||||||
|
} |
||||||
|
if (*p == TEXT('"')) { |
||||||
|
if ((slashes & 1) == 0) { |
||||||
|
if ((inquote) && (p[1] == TEXT('"'))) { |
||||||
|
p++; |
||||||
|
} else { |
||||||
|
inquote = !inquote; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
slashes=0; |
||||||
|
if ((*p == TEXT('\0')) || (!inquote && isspace(*p))) { |
||||||
|
while (isspace(*p)) |
||||||
|
p++; |
||||||
|
return p; |
||||||
|
} |
||||||
|
p++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Terminate unsuccessfully with an error message
|
||||||
|
static void Fail(const char *desc) { |
||||||
|
fprintf(stderr, "Error %u in %s\n", GetLastError(), desc); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
// Raymond Chen's CreateProcessInJob
|
||||||
|
// see https://devblogs.microsoft.com/oldnewthing/20131209-00/?p=2433
|
||||||
|
BOOL CreateProcessInJob( |
||||||
|
HANDLE hJob, |
||||||
|
LPCTSTR lpApplicationName, |
||||||
|
LPTSTR lpCommandLine, |
||||||
|
LPSECURITY_ATTRIBUTES lpProcessAttributes, |
||||||
|
LPSECURITY_ATTRIBUTES lpThreadAttributes, |
||||||
|
BOOL bInheritHandles, |
||||||
|
DWORD dwCreationFlags, |
||||||
|
LPVOID lpEnvironment, |
||||||
|
LPCTSTR lpCurrentDirectory, |
||||||
|
LPSTARTUPINFO lpStartupInfo, |
||||||
|
LPPROCESS_INFORMATION ppi) |
||||||
|
{ |
||||||
|
BOOL fRc = CreateProcess( |
||||||
|
lpApplicationName, |
||||||
|
lpCommandLine, |
||||||
|
lpProcessAttributes, |
||||||
|
lpThreadAttributes, |
||||||
|
bInheritHandles, |
||||||
|
dwCreationFlags | CREATE_SUSPENDED, |
||||||
|
lpEnvironment, |
||||||
|
lpCurrentDirectory, |
||||||
|
lpStartupInfo, |
||||||
|
ppi); |
||||||
|
if (fRc) { |
||||||
|
fRc = AssignProcessToJobObject(hJob, ppi->hProcess); |
||||||
|
if (fRc && !(dwCreationFlags & CREATE_SUSPENDED)) { |
||||||
|
fRc = ResumeThread(ppi->hThread) != (DWORD)-1; |
||||||
|
} |
||||||
|
if (!fRc) { |
||||||
|
TerminateProcess(ppi->hProcess, 0); |
||||||
|
CloseHandle(ppi->hProcess); |
||||||
|
CloseHandle(ppi->hThread); |
||||||
|
ppi->hProcess = ppi->hThread = NULL; |
||||||
|
} |
||||||
|
} |
||||||
|
return fRc; |
||||||
|
} |
||||||
|
|
||||||
|
// defined in pijhelp.cc, so I can use C++ raw literals
|
||||||
|
extern const char *Help; |
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char*argv[]) |
||||||
|
{ |
||||||
|
if (argc <= 1) { |
||||||
|
printf("%s",Help); |
||||||
|
exit(0); |
||||||
|
} |
||||||
|
LPTSTR cmdline = GetCommandLineW(); |
||||||
|
LPTSTR restCmdLine = skip_arg(cmdline); |
||||||
|
HANDLE hJob = CreateJobObject(NULL,NULL); |
||||||
|
if (hJob == NULL) { |
||||||
|
Fail("CreateJobObject"); |
||||||
|
} |
||||||
|
HANDLE hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); |
||||||
|
if (hPort == NULL) { |
||||||
|
Fail("CreateIoCompletionPort"); |
||||||
|
} |
||||||
|
|
||||||
|
// see https://devblogs.microsoft.com/oldnewthing/20130405-00/?p=4743
|
||||||
|
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp; |
||||||
|
jacp.CompletionKey = hJob; |
||||||
|
jacp.CompletionPort = hPort; |
||||||
|
if (!SetInformationJobObject(hJob, |
||||||
|
JobObjectAssociateCompletionPortInformation, |
||||||
|
&jacp, sizeof(jacp))) { |
||||||
|
Fail("SetInformationJobObject(JobObjectAssociateCompletionPortInformation)"); |
||||||
|
} |
||||||
|
|
||||||
|
STARTUPINFO si = { sizeof si }; |
||||||
|
PROCESS_INFORMATION pi; |
||||||
|
if (!CreateProcessInJob(hJob, |
||||||
|
NULL, |
||||||
|
restCmdLine, |
||||||
|
NULL, |
||||||
|
NULL, |
||||||
|
FALSE, |
||||||
|
0, |
||||||
|
NULL, |
||||||
|
NULL, |
||||||
|
&si, |
||||||
|
&pi)) { |
||||||
|
Fail("CreateProcess"); |
||||||
|
} |
||||||
|
CloseHandle(pi.hProcess); |
||||||
|
CloseHandle(pi.hThread); |
||||||
|
|
||||||
|
|
||||||
|
DWORD completionCode; |
||||||
|
ULONG_PTR completionKey; |
||||||
|
LPOVERLAPPED overlapped; |
||||||
|
|
||||||
|
for (;;) { |
||||||
|
BOOL rc = GetQueuedCompletionStatus(hPort, |
||||||
|
&completionCode, |
||||||
|
&completionKey, |
||||||
|
&overlapped, |
||||||
|
INFINITE); |
||||||
|
if (!rc) { |
||||||
|
Fail("GetQueuedCompletionStatus"); |
||||||
|
} |
||||||
|
if ((HANDLE)completionKey == hJob && |
||||||
|
completionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
CloseHandle(hJob); |
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
const char *Help = R"HELP( |
||||||
|
PIJ - Run process tree in a job and wait for completion. |
||||||
|
|
||||||
|
Usage: |
||||||
|
pij my-program.exe arguments... |
||||||
|
|
||||||
|
Use it when you want to run my-program.exe in a batch file, but |
||||||
|
my-program.exe starts another process(es) and returns before they |
||||||
|
complete, which is not what you want: batch file should continue only |
||||||
|
when all additional processes also exit. PIJ runs initial process in a |
||||||
|
job and waits for this job to become empty, before exiting. |
||||||
|
|
||||||
|
PIJ terminates with non-zero exit code if there's some problem during |
||||||
|
initial process creation or waiting phase. Zero exit code is returned |
||||||
|
when the last process in the job exits. |
||||||
|
|
||||||
|
All arguments are passed to CreateProcess'es lpCommandLine, hence some |
||||||
|
restrictions on what you can start: it should be a real EXE file |
||||||
|
(please start cmd.exe if you want to run another batch file), standard |
||||||
|
directories and PATH are used, registry AppPaths aren't. |
||||||
|
|
||||||
|
Written by Anton Kovalenko <anton@sw4me.com>. Public domain. |
||||||
|
)HELP"; |
||||||
|
|
Loading…
Reference in new issue