/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
#include <unistd.h>
#include "talon_process.h"
#include "buffer.h"
#include <errno.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
//#include <strsafe.h>
#include "log.h"
#define RETURN(x) { retval=x; goto cleanup; }
#define CLEANUP() cleanup:
#define READSIZE 4096
typedef struct ReadOpStruct {
HANDLE semaphore;
HANDLE thread;
DWORD timeout;
DWORD error;
HANDLE file;
BOOL success;
char *space;
DWORD nbytes;
int id;
struct ReadOpStruct *next;
} ReadOp;
typedef struct {
ReadOp *first;
ReadOp *last;
HANDLE semaphore;
} ReadOpQ;
proc *process_new(void)
{
proc *p = malloc(sizeof(proc));
p->output = buffer_new();
if (!p->output)
{
free(p);
return NULL;
}
p->starttime = 0;
p->endtime = 0;
p->returncode = 1;
p->pid = 0;
p->causeofdeath = PROC_NORMALDEATH;
return p;
}
#define TALONMAXERRSTR 1024
void printlasterror(void)
{
LPTSTR msg;
DWORD err = GetLastError();
char buf[1024];
msg=buf;
DEBUG(("error %d\n",err));
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, // lpSource
err, // dwMessageId,
0,
//MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), // dwLanguageId,
msg,
0,
NULL
);
DEBUG(("%s\n",msg));
//LocalFree(msg);
}
typedef struct
{
HANDLE read, write;
} tl_stream;
/* Because windows is d**b, there is no way to avoid blocking on an anonymous
* pipe. We can't use CancelIO to stop reads since that's only in newer
* versions of Win***ws. So what we are left with is putting the read operation
* into a thread, timing out in the main body and ignoring this thread if we
* feel we have to.
* */
DWORD readpipe_thread(void *param)
{
ReadOpQ *io_ops = (ReadOpQ *)param;
ReadOp *iopipe_op;
/* have our own buffer since we don't want to risk that the
* caller's buffer might have disappeared by the time
* our readfile unblocks.
*/
while (1)
{
DWORD waitres = WaitForSingleObject(io_ops->semaphore, INFINITE);
iopipe_op = io_ops->last;
DEBUG(("readpipe_thread: pre-ReadFile%d: %d \n", iopipe_op->id, iopipe_op->nbytes));
iopipe_op->success = ReadFile(iopipe_op->file, iopipe_op->space, iopipe_op->nbytes, &iopipe_op->nbytes, NULL);
iopipe_op->error = GetLastError();
DEBUG(("readpipe_thread: post-ReadFile%d: %d read, err %d\n", iopipe_op->id, iopipe_op->nbytes,iopipe_op->error));
ReleaseSemaphore(iopipe_op->semaphore, 1, NULL);
}
}
proc *process_run(char executable[], char *args[], int timeout)
{
proc *retval = NULL;
char *text;
int status;
tl_stream stdout_p;
tl_stream stdin_p;
SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL createproc_success = FALSE;
BOOL timedout = FALSE;
TCHAR *commandline = NULL;
proc *p = process_new();
if (p == NULL)
return NULL;
/* Make sure pipe handles are inherited */
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
p->causeofdeath = PROC_PIPECREATE;
DEBUG(("making pipes \n"));
/* Child's Stdout */
if ( ! CreatePipe(&stdout_p.read, &stdout_p.write, &saAttr, 1) )
{
printlasterror();
RETURN(p);
}
DEBUG(("stdout done \n"));
/* read handle to the pipe for STDOUT is not inherited */
if ( ! SetHandleInformation(stdout_p.read, HANDLE_FLAG_INHERIT, 0) )
{
printlasterror();
RETURN(p);
}
DEBUG(("stdout noinherit \n"));
/* a pipe for the child process's STDIN */
if ( ! CreatePipe(&stdin_p.read, &stdin_p.write, &saAttr, 0) )
{
printlasterror();
RETURN(p);
}
DEBUG(("stdin done \n"));
/* write handle to the pipe for STDIN not inherited */
if ( ! SetHandleInformation(stdin_p.read, HANDLE_FLAG_INHERIT, 0) )
{
printlasterror();
RETURN(p);
}
DEBUG(("pipes done \n"));
p->causeofdeath = PROC_START;
ZeroMemory( &si, sizeof(STARTUPINFO) );
ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );
si.cb = sizeof(STARTUPINFO);
si.hStdError = stdout_p.write;
si.hStdOutput = stdout_p.write;
/*
Rather than use the stdin pipe, which would be
si.hStdInput = stdin_p.read;
Pass on talon's own standard input to the child process
This helps with programs like xcopy which demand that
they are attached to a console and not just any type of
input file.
*/
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
DEBUG(("pre commandline \n"));
/* create the commandline string */
int len = 0;
int i = 0;
while (args[i] != NULL)
{
len += strlen(args[i++]) + 1;
}
len+=2;
commandline = malloc(len*2);
if (! commandline)
RETURN(p);
commandline[0] = '\0';
i = 0;
while (args[i] != NULL)
{
strcat(commandline, args[i]);
strcat(commandline, " ");
i++;
}
/* Get the read thread ready to go before creating
* the process.
*/
ReadOpQ *ropq = malloc(sizeof(ReadOpQ));
ropq->first=NULL;
ropq->last=NULL;
ropq->semaphore = CreateSemaphore(NULL, 0, 1, NULL);
DEBUG(("Creating read thread. \n"));
DWORD readpipe_threadid;
HANDLE h_readpipe_thread = CreateThread(NULL, 8192, (LPTHREAD_START_ROUTINE) readpipe_thread, (void*)ropq, 0, &readpipe_threadid);
/* ready to run the process */
DEBUG(("process commandline:\n%s \n", commandline));
DEBUG(("\n"));
createproc_success = CreateProcess(executable,
commandline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si, // STARTUPINFO pointer
&pi); // receives PROCESS_INFORMATION
if (! createproc_success)
{
DEBUG(("Createprocess failed. \n"));
p->causeofdeath = PROC_SOMEODDDEATH;
RETURN(p);
}
int have_status = 0;
DEBUG(("Closing Handles. \n"));
if (!CloseHandle(stdout_p.write))
RETURN(p);
if (!CloseHandle(stdin_p.read))
RETURN(p);
DEBUG(("Closed Handles. \n"));
static int id=0;
do
{
char *space = buffer_makespace(p->output, READSIZE);
DWORD waitres;
ReadOp *iopipe_op = malloc(sizeof(ReadOp));
iopipe_op->semaphore = CreateSemaphore(NULL, 0, 1, NULL);
iopipe_op->thread = h_readpipe_thread;
iopipe_op->timeout = timeout;
iopipe_op->file = stdout_p.read;
iopipe_op->space = malloc(READSIZE);
iopipe_op->id = id++;
iopipe_op->nbytes = READSIZE;
iopipe_op->next = NULL;
if (!ropq->first)
{
ropq->first = iopipe_op;
ropq->last = iopipe_op;
} else {
ropq->last->next = iopipe_op;
ropq->last = iopipe_op;
}
ReleaseSemaphore(ropq->semaphore, 1, NULL);
DEBUG(("waiting for read %d\n", timeout));
waitres = WaitForSingleObject(iopipe_op->semaphore, timeout);
DEBUG(("read wait finished result= %d\n", waitres));
if (waitres != WAIT_OBJECT_0)
{
DEBUG(("timeout \n"));
timedout = TRUE;
break;
}
else
{
DEBUG(("read signalled: nbytes: %d \n", iopipe_op->nbytes));
if (iopipe_op->nbytes <= 0)
{
break;
}
memcpy(space, iopipe_op->space, iopipe_op->nbytes);
buffer_usespace(p->output, iopipe_op->nbytes);
DEBUG(("buffer took on nbytes: %d \n", iopipe_op->nbytes));
}
}
while (1);
if (timedout == FALSE)
{
DEBUG(("Wait for process exit\n"));
// Wait until child process exits.
WaitForSingleObject(pi.hProcess, INFINITE);
DEBUG(("Process exited\n"));
DWORD exitcode;
if (GetExitCodeProcess(pi.hProcess, &exitcode))
{
p->causeofdeath = PROC_NORMALDEATH;
p->returncode = exitcode;
DEBUG(("process exited normally = %d:\n", p->returncode));
RETURN(p);
} else {
p->causeofdeath = PROC_SOMEODDDEATH;
p->returncode = 128;
DEBUG(("process terminated \n"));
RETURN(p);
}
} else {
TerminateProcess(pi.hProcess,1);
p->causeofdeath = PROC_TIMEOUTDEATH;
p->returncode = 128;
DEBUG(("process timedout \n"));
RETURN(p);
}
/* Clean up the read operation queue
ReadOp *r = ropq.first;
do
{
CloseHandle(r->semaphore);
free(r->space);
free(r);
r = r->next;
} while (r != NULL); */
CLEANUP();
if (retval == NULL)
{
if (p)
process_free(&p);
}
if (commandline)
free(commandline);
return retval;
}
void process_free(proc **pp)
{
if (!pp)
return;
if (! *pp)
return;
if ((*pp)->output)
buffer_free(&((*pp)->output));
free(*pp);
*pp = NULL;
}