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