Skip to content

Instantly share code, notes, and snippets.

@cwshu
Created December 7, 2016 16:50
Show Gist options
  • Save cwshu/721007e2bff7361f4cf046f014eed1e0 to your computer and use it in GitHub Desktop.
Save cwshu/721007e2bff7361f4cf046f014eed1e0 to your computer and use it in GitHub Desktop.
APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new,
const char *progname,
const char * const *args,
const char * const *env,
apr_procattr_t *attr,
apr_pool_t *pool)
{
apr_status_t rv;
apr_size_t i;
const char *argv0;
char *cmdline;
char *pEnvBlock;
PROCESS_INFORMATION pi;
DWORD dwCreationFlags = 0;
new->in = attr->parent_in;
new->out = attr->parent_out;
new->err = attr->parent_err;
if (attr->detached) {
/* If we are creating ourselves detached, then we should hide the
* window we are starting in. And we had better redefine our
* handles for STDIN, STDOUT, and STDERR. Do not set the
* detached attribute for Win9x. We have found that Win9x does
* not manage the stdio handles properly when running old 16
* bit executables if the detached attribute is set.
*/
if (apr_os_level >= APR_WIN_NT) {
/*
* XXX DETACHED_PROCESS won't on Win9x at all; on NT/W2K
* 16 bit executables fail (MS KB: Q150956)
*/
dwCreationFlags |= DETACHED_PROCESS;
}
}
/* progname must be unquoted, in native format, as there are all sorts
* of bugs in the NT library loader code that fault when parsing '/'.
* XXX progname must be NULL if this is a 16 bit app running in WOW
*/
if (progname[0] == '\"') {
progname = apr_pstrmemdup(pool, progname + 1, strlen(progname) - 2);
}
if (attr->cmdtype == APR_PROGRAM || attr->cmdtype == APR_PROGRAM_ENV) {
char *fullpath = NULL;
if ((rv = apr_filepath_merge(&fullpath, attr->currdir, progname,
APR_FILEPATH_NATIVE, pool)) != APR_SUCCESS) {
if (attr->errfn) {
attr->errfn(pool, rv,
apr_pstrcat(pool, "filepath_merge failed.",
" currdir: ", attr->currdir,
" progname: ", progname, NULL));
}
return rv;
}
progname = fullpath;
}
else {
/* Do not fail if the path isn't parseable for APR_PROGRAM_PATH
* or APR_SHELLCMD. We only invoke apr_filepath_merge (with no
* left hand side expression) in order to correct the path slash
* delimiters. But the filename doesn't need to be in the CWD,
* nor does it need to be a filename at all (it could be a
* built-in shell command.)
*/
char *fullpath = NULL;
if ((rv = apr_filepath_merge(&fullpath, "", progname,
APR_FILEPATH_NATIVE, pool)) == APR_SUCCESS) {
progname = fullpath;
}
}
if (has_space(progname)) {
argv0 = apr_pstrcat(pool, "\"", progname, "\"", NULL);
}
else {
argv0 = progname;
}
/* Handle the args, seperate from argv0 */
cmdline = "";
for (i = 1; args && args[i]; ++i) {
if (has_space(args[i]) || !args[i][0]) {
cmdline = apr_pstrcat(pool, cmdline, " \"", args[i], "\"", NULL);
}
else {
cmdline = apr_pstrcat(pool, cmdline, " ", args[i], NULL);
}
}
#ifndef _WIN32_WCE
if (attr->cmdtype == APR_SHELLCMD || attr->cmdtype == APR_SHELLCMD_ENV) {
char *shellcmd = getenv("COMSPEC");
if (!shellcmd) {
if (attr->errfn) {
attr->errfn(pool, APR_EINVAL, "COMSPEC envar is not set");
}
return APR_EINVAL;
}
if (shellcmd[0] == '"') {
progname = apr_pstrmemdup(pool, shellcmd + 1, strlen(shellcmd) - 2);
}
else {
progname = shellcmd;
if (has_space(shellcmd)) {
shellcmd = apr_pstrcat(pool, "\"", shellcmd, "\"", NULL);
}
}
/* Command.com does not support a quoted command, while cmd.exe demands one.
*/
i = strlen(progname);
if (i >= 11 && strcasecmp(progname + i - 11, "command.com") == 0) {
cmdline = apr_pstrcat(pool, shellcmd, " /C ", argv0, cmdline, NULL);
}
else {
cmdline = apr_pstrcat(pool, shellcmd, " /C \"", argv0, cmdline, "\"", NULL);
}
}
else
#endif
{
#if defined(_WIN32_WCE)
{
#else
/* Win32 is _different_ than unix. While unix will find the given
* program since it's already chdir'ed, Win32 cannot since the parent
* attempts to open the program with it's own path.
* ###: This solution isn't much better - it may defeat path searching
* when the path search was desired. Open to further discussion.
*/
i = strlen(progname);
if (i >= 4 && (strcasecmp(progname + i - 4, ".bat") == 0
|| strcasecmp(progname + i - 4, ".cmd") == 0))
{
char *shellcmd = getenv("COMSPEC");
if (!shellcmd) {
if (attr->errfn) {
attr->errfn(pool, APR_EINVAL, "COMSPEC envar is not set");
}
return APR_EINVAL;
}
if (shellcmd[0] == '"') {
progname = apr_pstrmemdup(pool, shellcmd + 1, strlen(shellcmd) - 2);
}
else {
progname = shellcmd;
if (has_space(shellcmd)) {
shellcmd = apr_pstrcat(pool, "\"", shellcmd, "\"", NULL);
}
}
i = strlen(progname);
if (i >= 11 && strcasecmp(progname + i - 11, "command.com") == 0) {
/* XXX: Still insecure - need doubled-quotes on each individual
* arg of cmdline. Suspect we need to postpone cmdline parsing
* until this moment in all four code paths, with some flags
* to toggle 'which flavor' is needed.
*/
cmdline = apr_pstrcat(pool, shellcmd, " /C ", argv0, cmdline, NULL);
}
else {
/* We must protect the cmdline args from any interpolation - this
* is not a shellcmd, and the source of argv[] is untrusted.
* Notice we escape ALL the cmdline args, including the quotes
* around the individual args themselves. No sense in allowing
* the shift-state to be toggled, and the application will
* not see the caret escapes.
*/
cmdline = apr_caret_escape_args(pool, cmdline);
/*
* Our app name must always be quoted so the quotes surrounding
* the entire /c "command args" are unambigious.
*/
if (*argv0 != '"') {
cmdline = apr_pstrcat(pool, shellcmd, " /C \"\"", argv0, "\"", cmdline, "\"", NULL);
}
else {
cmdline = apr_pstrcat(pool, shellcmd, " /C \"", argv0, cmdline, "\"", NULL);
}
}
}
else {
#endif
/* A simple command we are directly invoking. Do not pass
* the first arg to CreateProc() for APR_PROGRAM_PATH
* invocation, since it would need to be a literal and
* complete file path. That is; "c:\bin\aprtest.exe"
* would succeed, but "c:\bin\aprtest" or "aprtest.exe"
* can fail.
*/
cmdline = apr_pstrcat(pool, argv0, cmdline, NULL);
if (attr->cmdtype == APR_PROGRAM_PATH) {
progname = NULL;
}
}
}
if (!env || attr->cmdtype == APR_PROGRAM_ENV ||
attr->cmdtype == APR_SHELLCMD_ENV) {
pEnvBlock = NULL;
}
else {
apr_size_t iEnvBlockLen;
/*
* Win32's CreateProcess call requires that the environment
* be passed in an environment block, a null terminated block of
* null terminated strings.
*/
i = 0;
iEnvBlockLen = 1;
while (env[i]) {
iEnvBlockLen += strlen(env[i]) + 1;
i++;
}
if (!i)
++iEnvBlockLen;
#if APR_HAS_ANSI_FS
ELSE_WIN_OS_IS_ANSI
{
char *pNext;
pEnvBlock = (char *)apr_palloc(pool, iEnvBlockLen);
i = 0;
pNext = pEnvBlock;
while (env[i]) {
strcpy(pNext, env[i]);
pNext = strchr(pNext, '\0') + 1;
i++;
}
if (!i)
*(pNext++) = '\0';
*pNext = '\0';
}
#endif /* APR_HAS_ANSI_FS */
}
new->invoked = cmdline;
#if APR_HAS_ANSI_FS
ELSE_WIN_OS_IS_ANSI
{
STARTUPINFOA si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
if (attr->detached) {
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
}
if ((attr->child_in && attr->child_in->filehand)
|| (attr->child_out && attr->child_out->filehand)
|| (attr->child_err && attr->child_err->filehand))
{
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = (attr->child_in)
? attr->child_in->filehand
: GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = (attr->child_out)
? attr->child_out->filehand
: GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = (attr->child_err)
? attr->child_err->filehand
: GetStdHandle(STD_ERROR_HANDLE);
}
rv = CreateProcessA(progname, cmdline, /* Command line */
NULL, NULL, /* Proc & thread security attributes */
TRUE, /* Inherit handles */
dwCreationFlags, /* Creation flags */
pEnvBlock, /* Environment block */
attr->currdir, /* Current directory name */
&si, &pi);
}
#endif /* APR_HAS_ANSI_FS */
/* Check CreateProcess result
*/
if (!rv)
return apr_get_os_error();
/* XXX Orphaned handle warning - no fix due to broken apr_proc_t api.
*/
new->hproc = pi.hProcess;
new->pid = pi.dwProcessId;
if ((attr->child_in) && (attr->child_in != &no_file)) {
apr_file_close(attr->child_in);
}
if ((attr->child_out) && (attr->child_out != &no_file)) {
apr_file_close(attr->child_out);
}
if ((attr->child_err) && (attr->child_err != &no_file)) {
apr_file_close(attr->child_err);
}
CloseHandle(pi.hThread);
return APR_SUCCESS;
}
@cwshu
Copy link
Author

cwshu commented Dec 7, 2016

@cwshu
Copy link
Author

cwshu commented Dec 7, 2016

win32 spawn process without UNICODE support
we can compare with posix version: https://github.com/apache/apr/blob/1.5.2/threadproc/unix/proc.c#L344

  1. CreateProcessA(): fork()+exec()
  2. char *pEnvBlock: environment variable
  3. STARTUPINFOA si: IO redirection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment