1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Qt3Support module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "qplatformdefs.h"
43 #include "q3process.h"
47 #include "qapplication.h"
48 #include "q3cstring.h"
49 #include "q3ptrqueue.h"
52 #include "private/q3membuf_p.h"
53 #include "qt_windows.h"
56 #define STARTF_USESTDHANDLES 1
61 //#define QT_Q3PROCESS_DEBUG
63 /***********************************************************************
67 **********************************************************************/
68 class Q3ProcessPrivate
71 Q3ProcessPrivate( Q3Process
*proc
)
80 exitValuesCalculated
= false;
82 lookup
= new QTimer( proc
);
83 qApp
->connect( lookup
, SIGNAL(timeout()),
84 proc
, SLOT(timeout()) );
96 while ( !stdinBuf
.isEmpty() ) {
97 delete stdinBuf
.dequeue();
107 exitValuesCalculated
= false;
114 if( pipeStdin
[1] != 0 ) {
115 CloseHandle( pipeStdin
[1] );
118 if( pipeStdout
[0] != 0 ) {
119 CloseHandle( pipeStdout
[0] );
122 if( pipeStderr
[0] != 0 ) {
123 CloseHandle( pipeStderr
[0] );
131 CloseHandle( pid
->hProcess
);
132 CloseHandle( pid
->hThread
);
141 pid
= new PROCESS_INFORMATION
;
142 memset( pid
, 0, sizeof(PROCESS_INFORMATION
) );
148 Q3PtrQueue
<QByteArray
> stdinBuf
;
151 HANDLE pipeStdout
[2];
152 HANDLE pipeStderr
[2];
155 PROCESS_INFORMATION
*pid
;
158 bool exitValuesCalculated
;
162 /***********************************************************************
166 **********************************************************************/
167 void Q3Process::init()
169 d
= new Q3ProcessPrivate( this );
174 void Q3Process::reset()
179 d
->bufStdout
.clear();
180 d
->bufStderr
.clear();
183 Q3Membuf
* Q3Process::membufStdout()
185 if( d
->pipeStdout
[0] != 0 )
187 return &d
->bufStdout
;
190 Q3Membuf
* Q3Process::membufStderr()
192 if( d
->pipeStderr
[0] != 0 )
194 return &d
->bufStderr
;
197 Q3Process::~Q3Process()
202 bool Q3Process::start( QStringList
*env
)
204 #if defined(QT_Q3PROCESS_DEBUG)
205 qDebug( "Q3Process::start()" );
209 if ( _arguments
.isEmpty() )
212 // Open the pipes. Make non-inheritable copies of input write and output
213 // read handles to avoid non-closable handles (this is done by the
214 // DuplicateHandle() call).
215 SECURITY_ATTRIBUTES secAtt
= { sizeof( SECURITY_ATTRIBUTES
), NULL
, TRUE
};
217 // I guess there is no stdin stdout and stderr on Q_OS_WINCE to dup
218 // CreatePipe and DupilcateHandle aren't available for Q_OS_WINCE
219 HANDLE tmpStdin
, tmpStdout
, tmpStderr
;
220 if ( comms
& Stdin
) {
221 if ( !CreatePipe( &d
->pipeStdin
[0], &tmpStdin
, &secAtt
, 0 ) ) {
225 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdin
, GetCurrentProcess(), &d
->pipeStdin
[1], 0, FALSE
, DUPLICATE_SAME_ACCESS
) ) {
229 if ( !CloseHandle( tmpStdin
) ) {
234 if ( comms
& Stdout
) {
235 if ( !CreatePipe( &tmpStdout
, &d
->pipeStdout
[1], &secAtt
, 0 ) ) {
239 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdout
, GetCurrentProcess(), &d
->pipeStdout
[0], 0, FALSE
, DUPLICATE_SAME_ACCESS
) ) {
243 if ( !CloseHandle( tmpStdout
) ) {
248 if ( comms
& Stderr
) {
249 if ( !CreatePipe( &tmpStderr
, &d
->pipeStderr
[1], &secAtt
, 0 ) ) {
253 if ( !DuplicateHandle( GetCurrentProcess(), tmpStderr
, GetCurrentProcess(), &d
->pipeStderr
[0], 0, FALSE
, DUPLICATE_SAME_ACCESS
) ) {
257 if ( !CloseHandle( tmpStderr
) ) {
262 if ( comms
& DupStderr
) {
263 CloseHandle( d
->pipeStderr
[1] );
264 d
->pipeStderr
[1] = d
->pipeStdout
[1];
268 // construct the arguments for CreateProcess()
271 QStringList::Iterator it
= _arguments
.begin();
274 if ( args
.endsWith( QLatin1String(".bat") ) && args
.contains( QLatin1Char(' ') ) ) {
275 // CreateProcess() seems to have a strange semantics (see also
276 // http://www.experts-exchange.com/Programming/Programming_Platforms/Win_Prog/Q_11138647.html):
277 // If you start a batch file with spaces in the filename, the first
278 // argument to CreateProcess() must be the name of the batchfile
279 // without quotes, but the second argument must start with the same
280 // argument with quotes included. But if the same approach is used for
281 // .exe files, it doesn't work.
283 args
= QLatin1Char('"') + args
+ QLatin1Char('"');
285 for ( ; it
!= _arguments
.end(); ++it
) {
287 // escape a single " because the arguments will be parsed
288 tmp
.replace( QLatin1Char('\"'), QLatin1String("\\\"") );
289 if ( tmp
.isEmpty() || tmp
.contains( QLatin1Char(' ') ) || tmp
.contains( QLatin1Char('\t') ) ) {
290 // The argument must not end with a \ since this would be interpreted
291 // as escaping the quote -- rather put the \ behind the quote: e.g.
292 // rather use "foo"\ than "foo\"
293 QString
endQuote( QLatin1String("\"") );
294 int i
= tmp
.length();
295 while ( i
>0 && tmp
.at( i
-1 ) == QLatin1Char('\\') ) {
297 endQuote
+= QLatin1Char('\\');
299 args
+= QLatin1String(" \"") + tmp
.left( i
) + endQuote
;
301 args
+= QLatin1Char(' ') + tmp
;
304 #if defined(QT_Q3PROCESS_DEBUG)
305 qDebug( "Q3Process::start(): args [%s]", args
.latin1() );
312 STARTUPINFOW startupInfo
= {
313 sizeof( STARTUPINFO
), 0, 0, 0,
314 (ulong
)CW_USEDEFAULT
, (ulong
)CW_USEDEFAULT
, (ulong
)CW_USEDEFAULT
, (ulong
)CW_USEDEFAULT
,
316 STARTF_USESTDHANDLES
,
318 d
->pipeStdin
[0], d
->pipeStdout
[1], d
->pipeStderr
[1]
320 wchar_t *applicationName
;
321 if ( appName
.isNull() )
324 applicationName
= _wcsdup( (wchar_t*)appName
.utf16() );
325 wchar_t *commandLine
= _wcsdup( (wchar_t*)args
.utf16() );
329 // add PATH if necessary (for DLL loading)
330 QByteArray path
= qgetenv( "PATH" );
331 if ( env
->grep( QRegExp(QLatin1String("^PATH="),FALSE
) ).empty() && !path
.isNull() ) {
332 QString tmp
= QString::fromLatin1("PATH=%1").arg(QLatin1String(path
.constData()));
333 uint tmpSize
= sizeof(wchar_t) * (tmp
.length() + 1);
334 envlist
.resize( envlist
.size() + tmpSize
);
335 memcpy( envlist
.data() + pos
, tmp
.utf16(), tmpSize
);
338 // add the user environment
339 for ( QStringList::Iterator it
= env
->begin(); it
!= env
->end(); it
++ ) {
341 uint tmpSize
= sizeof(wchar_t) * (tmp
.length() + 1);
342 envlist
.resize( envlist
.size() + tmpSize
);
343 memcpy( envlist
.data() + pos
, tmp
.utf16(), tmpSize
);
346 // add the 2 terminating 0 (actually 4, just to be on the safe side)
347 envlist
.resize( envlist
.size()+4 );
353 success
= CreateProcess( applicationName
, commandLine
,
354 0, 0, TRUE
, ( comms
== 0 ? CREATE_NEW_CONSOLE
: CREATE_NO_WINDOW
)
356 | CREATE_UNICODE_ENVIRONMENT
358 , env
== 0 ? 0 : envlist
.data(),
359 (wchar_t*)QDir::toNativeSeparators(workingDir
.absPath()).utf16(),
360 &startupInfo
, d
->pid
);
362 free( applicationName
);
372 CloseHandle( d
->pipeStdin
[0] );
373 if ( comms
& Stdout
)
374 CloseHandle( d
->pipeStdout
[1] );
375 if ( (comms
& Stderr
) && !(comms
& DupStderr
) )
376 CloseHandle( d
->pipeStderr
[1] );
379 if ( ioRedirection
|| notifyOnExit
) {
380 d
->lookup
->start( 100 );
383 // cleanup and return
387 static BOOL CALLBACK
qt_terminateApp( HWND hwnd
, LPARAM procId
)
390 GetWindowThreadProcessId( hwnd
, &procId_win
);
391 if( procId_win
== (DWORD
)procId
)
392 PostMessage( hwnd
, WM_CLOSE
, 0, 0 );
397 void Q3Process::tryTerminate() const
400 EnumWindows( qt_terminateApp
, (LPARAM
)d
->pid
->dwProcessId
);
403 void Q3Process::kill() const
406 TerminateProcess( d
->pid
->hProcess
, 0xf291 );
409 bool Q3Process::isRunning() const
414 if ( WaitForSingleObject( d
->pid
->hProcess
, 0) == WAIT_OBJECT_0
) {
415 // there might be data to read
416 Q3Process
*that
= (Q3Process
*)this;
417 that
->socketRead( 1 ); // try stdout
418 that
->socketRead( 2 ); // try stderr
419 // compute the exit values
420 if ( !d
->exitValuesCalculated
) {
422 if ( GetExitCodeProcess( d
->pid
->hProcess
, &exitCode
) ) {
423 if ( exitCode
!= STILL_ACTIVE
) { // this should ever be true?
424 that
->exitNormal
= exitCode
!= 0xf291;
425 that
->exitStat
= exitCode
;
428 d
->exitValuesCalculated
= true;
438 bool Q3Process::canReadLineStdout() const
440 if( !d
->pipeStdout
[0] )
441 return d
->bufStdout
.size() != 0;
443 Q3Process
*that
= (Q3Process
*)this;
444 return that
->membufStdout()->scanNewline( 0 );
447 bool Q3Process::canReadLineStderr() const
449 if( !d
->pipeStderr
[0] )
450 return d
->bufStderr
.size() != 0;
452 Q3Process
*that
= (Q3Process
*)this;
453 return that
->membufStderr()->scanNewline( 0 );
456 void Q3Process::writeToStdin( const QByteArray
& buf
)
458 d
->stdinBuf
.enqueue( new QByteArray(buf
) );
462 void Q3Process::closeStdin( )
464 if ( d
->pipeStdin
[1] != 0 ) {
465 CloseHandle( d
->pipeStdin
[1] );
470 void Q3Process::socketRead( int fd
)
472 // fd == 1: stdout, fd == 2: stderr
475 dev
= d
->pipeStdout
[0];
476 } else if ( fd
== 2 ) {
477 dev
= d
->pipeStderr
[0];
482 // get the number of bytes that are waiting to be read
485 if ( !PeekNamedPipe( dev
, &dummy
, 1, &r
, &i
, 0 ) ) {
486 return; // ### is it worth to dig for the reason of the error?
489 unsigned long i
= 1000;
494 buffer
= &d
->bufStdout
;
496 buffer
= &d
->bufStderr
;
498 QByteArray
*ba
= new QByteArray( i
);
499 uint sz
= readStddev( dev
, ba
->data(), i
);
507 buffer
->append( ba
);
509 emit
readyReadStdout();
511 emit
readyReadStderr();
515 void Q3Process::socketWrite( int )
518 while ( !d
->stdinBuf
.isEmpty() && isRunning() ) {
519 if ( !WriteFile( d
->pipeStdin
[1],
520 d
->stdinBuf
.head()->data() + d
->stdinBufRead
,
521 qMin( 8192, int(d
->stdinBuf
.head()->size() - d
->stdinBufRead
) ),
523 d
->lookup
->start( 100 );
526 d
->stdinBufRead
+= written
;
527 if ( d
->stdinBufRead
== (DWORD
)d
->stdinBuf
.head()->size() ) {
529 delete d
->stdinBuf
.dequeue();
530 if ( wroteToStdinConnected
&& d
->stdinBuf
.isEmpty() )
536 void Q3Process::flushStdin()
542 Use a timer for polling misc. stuff.
544 void Q3Process::timeout()
546 // Disable the timer temporary since one of the slots that are connected to
547 // the readyRead...(), etc. signals might trigger recursion if
548 // processEvents() is called.
551 // try to write pending data to stdin
552 if ( !d
->stdinBuf
.isEmpty() )
555 if ( ioRedirection
) {
556 socketRead( 1 ); // try stdout
557 socketRead( 2 ); // try stderr
561 // enable timer again, if needed
562 if ( !d
->stdinBuf
.isEmpty() || ioRedirection
|| notifyOnExit
)
563 d
->lookup
->start( 100 );
564 } else if ( notifyOnExit
) {
565 emit
processExited();
572 uint
Q3Process::readStddev( HANDLE dev
, char *buf
, uint bytes
)
576 if ( ReadFile( dev
, buf
, bytes
, &r
, 0 ) )
583 Used by connectNotify() and disconnectNotify() to change the value of
584 ioRedirection (and related behaviour)
586 void Q3Process::setIoRedirection( bool value
)
588 ioRedirection
= value
;
589 if ( !ioRedirection
&& !notifyOnExit
)
591 if ( ioRedirection
) {
593 d
->lookup
->start( 100 );
598 Used by connectNotify() and disconnectNotify() to change the value of
599 notifyOnExit (and related behaviour)
601 void Q3Process::setNotifyOnExit( bool value
)
603 notifyOnExit
= value
;
604 if ( !ioRedirection
&& !notifyOnExit
)
606 if ( notifyOnExit
) {
608 d
->lookup
->start( 100 );
613 Used by connectNotify() and disconnectNotify() to change the value of
614 wroteToStdinConnected (and related behaviour)
616 void Q3Process::setWroteStdinConnected( bool value
)
618 wroteToStdinConnected
= value
;
621 Q3Process::PID
Q3Process::processIdentifier()
628 #endif // QT_NO_PROCESS