Don't try to mmap past EOF
[qt-netbsd.git] / src / qt3support / other / q3process_win.cpp
blob47c5e7077dad3ab1c7da37446b27c7e9de21fcc7
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt3Support module of the Qt Toolkit.
8 **
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
14 ** this package.
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.
38 ** $QT_END_LICENSE$
40 ****************************************************************************/
42 #include "qplatformdefs.h"
43 #include "q3process.h"
45 #ifndef QT_NO_PROCESS
47 #include "qapplication.h"
48 #include "q3cstring.h"
49 #include "q3ptrqueue.h"
50 #include "qtimer.h"
51 #include "qregexp.h"
52 #include "private/q3membuf_p.h"
53 #include "qt_windows.h"
55 #ifdef Q_OS_WINCE
56 #define STARTF_USESTDHANDLES 1
57 #endif
59 QT_BEGIN_NAMESPACE
61 //#define QT_Q3PROCESS_DEBUG
63 /***********************************************************************
65 * Q3ProcessPrivate
67 **********************************************************************/
68 class Q3ProcessPrivate
70 public:
71 Q3ProcessPrivate( Q3Process *proc )
73 stdinBufRead = 0;
74 pipeStdin[0] = 0;
75 pipeStdin[1] = 0;
76 pipeStdout[0] = 0;
77 pipeStdout[1] = 0;
78 pipeStderr[0] = 0;
79 pipeStderr[1] = 0;
80 exitValuesCalculated = false;
82 lookup = new QTimer( proc );
83 qApp->connect( lookup, SIGNAL(timeout()),
84 proc, SLOT(timeout()) );
86 pid = 0;
89 ~Q3ProcessPrivate()
91 reset();
94 void reset()
96 while ( !stdinBuf.isEmpty() ) {
97 delete stdinBuf.dequeue();
99 closeHandles();
100 stdinBufRead = 0;
101 pipeStdin[0] = 0;
102 pipeStdin[1] = 0;
103 pipeStdout[0] = 0;
104 pipeStdout[1] = 0;
105 pipeStderr[0] = 0;
106 pipeStderr[1] = 0;
107 exitValuesCalculated = false;
109 deletePid();
112 void closeHandles()
114 if( pipeStdin[1] != 0 ) {
115 CloseHandle( pipeStdin[1] );
116 pipeStdin[1] = 0;
118 if( pipeStdout[0] != 0 ) {
119 CloseHandle( pipeStdout[0] );
120 pipeStdout[0] = 0;
122 if( pipeStderr[0] != 0 ) {
123 CloseHandle( pipeStderr[0] );
124 pipeStderr[0] = 0;
128 void deletePid()
130 if ( pid ) {
131 CloseHandle( pid->hProcess );
132 CloseHandle( pid->hThread );
133 delete pid;
134 pid = 0;
138 void newPid()
140 deletePid();
141 pid = new PROCESS_INFORMATION;
142 memset( pid, 0, sizeof(PROCESS_INFORMATION) );
145 Q3Membuf bufStdout;
146 Q3Membuf bufStderr;
148 Q3PtrQueue<QByteArray> stdinBuf;
150 HANDLE pipeStdin[2];
151 HANDLE pipeStdout[2];
152 HANDLE pipeStderr[2];
153 QTimer *lookup;
155 PROCESS_INFORMATION *pid;
156 uint stdinBufRead;
158 bool exitValuesCalculated;
162 /***********************************************************************
164 * Q3Process
166 **********************************************************************/
167 void Q3Process::init()
169 d = new Q3ProcessPrivate( this );
170 exitStat = 0;
171 exitNormal = false;
174 void Q3Process::reset()
176 d->reset();
177 exitStat = 0;
178 exitNormal = false;
179 d->bufStdout.clear();
180 d->bufStderr.clear();
183 Q3Membuf* Q3Process::membufStdout()
185 if( d->pipeStdout[0] != 0 )
186 socketRead( 1 );
187 return &d->bufStdout;
190 Q3Membuf* Q3Process::membufStderr()
192 if( d->pipeStderr[0] != 0 )
193 socketRead( 2 );
194 return &d->bufStderr;
197 Q3Process::~Q3Process()
199 delete d;
202 bool Q3Process::start( QStringList *env )
204 #if defined(QT_Q3PROCESS_DEBUG)
205 qDebug( "Q3Process::start()" );
206 #endif
207 reset();
209 if ( _arguments.isEmpty() )
210 return false;
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 };
216 #ifndef Q_OS_WINCE
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 ) ) {
222 d->closeHandles();
223 return false;
225 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdin, GetCurrentProcess(), &d->pipeStdin[1], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
226 d->closeHandles();
227 return false;
229 if ( !CloseHandle( tmpStdin ) ) {
230 d->closeHandles();
231 return false;
234 if ( comms & Stdout ) {
235 if ( !CreatePipe( &tmpStdout, &d->pipeStdout[1], &secAtt, 0 ) ) {
236 d->closeHandles();
237 return false;
239 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdout, GetCurrentProcess(), &d->pipeStdout[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
240 d->closeHandles();
241 return false;
243 if ( !CloseHandle( tmpStdout ) ) {
244 d->closeHandles();
245 return false;
248 if ( comms & Stderr ) {
249 if ( !CreatePipe( &tmpStderr, &d->pipeStderr[1], &secAtt, 0 ) ) {
250 d->closeHandles();
251 return false;
253 if ( !DuplicateHandle( GetCurrentProcess(), tmpStderr, GetCurrentProcess(), &d->pipeStderr[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
254 d->closeHandles();
255 return false;
257 if ( !CloseHandle( tmpStderr ) ) {
258 d->closeHandles();
259 return false;
262 if ( comms & DupStderr ) {
263 CloseHandle( d->pipeStderr[1] );
264 d->pipeStderr[1] = d->pipeStdout[1];
266 #endif
268 // construct the arguments for CreateProcess()
269 QString args;
270 QString appName;
271 QStringList::Iterator it = _arguments.begin();
272 args = *it;
273 ++it;
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.
282 appName = args;
283 args = QLatin1Char('"') + args + QLatin1Char('"');
285 for ( ; it != _arguments.end(); ++it ) {
286 QString tmp = *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('\\') ) {
296 --i;
297 endQuote += QLatin1Char('\\');
299 args += QLatin1String(" \"") + tmp.left( i ) + endQuote;
300 } else {
301 args += QLatin1Char(' ') + tmp;
304 #if defined(QT_Q3PROCESS_DEBUG)
305 qDebug( "Q3Process::start(): args [%s]", args.latin1() );
306 #endif
308 // CreateProcess()
309 bool success;
310 d->newPid();
312 STARTUPINFOW startupInfo = {
313 sizeof( STARTUPINFO ), 0, 0, 0,
314 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
315 0, 0, 0,
316 STARTF_USESTDHANDLES,
317 0, 0, 0,
318 d->pipeStdin[0], d->pipeStdout[1], d->pipeStderr[1]
320 wchar_t *applicationName;
321 if ( appName.isNull() )
322 applicationName = 0;
323 else
324 applicationName = _wcsdup( (wchar_t*)appName.utf16() );
325 wchar_t *commandLine = _wcsdup( (wchar_t*)args.utf16() );
326 QByteArray envlist;
327 if ( env != 0 ) {
328 int pos = 0;
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 );
336 pos += tmpSize;
338 // add the user environment
339 for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) {
340 QString tmp = *it;
341 uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
342 envlist.resize( envlist.size() + tmpSize );
343 memcpy( envlist.data() + pos, tmp.utf16(), tmpSize );
344 pos += tmpSize;
346 // add the 2 terminating 0 (actually 4, just to be on the safe side)
347 envlist.resize( envlist.size()+4 );
348 envlist[pos++] = 0;
349 envlist[pos++] = 0;
350 envlist[pos++] = 0;
351 envlist[pos++] = 0;
353 success = CreateProcess( applicationName, commandLine,
354 0, 0, TRUE, ( comms == 0 ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW )
355 #ifndef Q_OS_WINCE
356 | CREATE_UNICODE_ENVIRONMENT
357 #endif
358 , env == 0 ? 0 : envlist.data(),
359 (wchar_t*)QDir::toNativeSeparators(workingDir.absPath()).utf16(),
360 &startupInfo, d->pid );
362 free( applicationName );
363 free( commandLine );
365 if ( !success ) {
366 d->deletePid();
367 return false;
370 #ifndef Q_OS_WINCE
371 if ( comms & Stdin )
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] );
377 #endif
379 if ( ioRedirection || notifyOnExit ) {
380 d->lookup->start( 100 );
383 // cleanup and return
384 return true;
387 static BOOL CALLBACK qt_terminateApp( HWND hwnd, LPARAM procId )
389 DWORD procId_win;
390 GetWindowThreadProcessId( hwnd, &procId_win );
391 if( procId_win == (DWORD)procId )
392 PostMessage( hwnd, WM_CLOSE, 0, 0 );
394 return TRUE;
397 void Q3Process::tryTerminate() const
399 if ( d->pid )
400 EnumWindows( qt_terminateApp, (LPARAM)d->pid->dwProcessId );
403 void Q3Process::kill() const
405 if ( d->pid )
406 TerminateProcess( d->pid->hProcess, 0xf291 );
409 bool Q3Process::isRunning() const
411 if ( !d->pid )
412 return false;
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 ) {
421 DWORD exitCode;
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;
430 d->deletePid();
431 d->closeHandles();
432 return false;
433 } else {
434 return 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) );
459 socketWrite( 0 );
462 void Q3Process::closeStdin( )
464 if ( d->pipeStdin[1] != 0 ) {
465 CloseHandle( d->pipeStdin[1] );
466 d->pipeStdin[1] = 0;
470 void Q3Process::socketRead( int fd )
472 // fd == 1: stdout, fd == 2: stderr
473 HANDLE dev;
474 if ( fd == 1 ) {
475 dev = d->pipeStdout[0];
476 } else if ( fd == 2 ) {
477 dev = d->pipeStderr[0];
478 } else {
479 return;
481 #ifndef Q_OS_WINCE
482 // get the number of bytes that are waiting to be read
483 unsigned long i, r;
484 char dummy;
485 if ( !PeekNamedPipe( dev, &dummy, 1, &r, &i, 0 ) ) {
486 return; // ### is it worth to dig for the reason of the error?
488 #else
489 unsigned long i = 1000;
490 #endif
491 if ( i > 0 ) {
492 Q3Membuf *buffer;
493 if ( fd == 1 )
494 buffer = &d->bufStdout;
495 else
496 buffer = &d->bufStderr;
498 QByteArray *ba = new QByteArray( i );
499 uint sz = readStddev( dev, ba->data(), i );
500 if ( sz != i )
501 ba->resize( i );
503 if ( sz == 0 ) {
504 delete ba;
505 return;
507 buffer->append( ba );
508 if ( fd == 1 )
509 emit readyReadStdout();
510 else
511 emit readyReadStderr();
515 void Q3Process::socketWrite( int )
517 DWORD written;
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) ),
522 &written, 0 ) ) {
523 d->lookup->start( 100 );
524 return;
526 d->stdinBufRead += written;
527 if ( d->stdinBufRead == (DWORD)d->stdinBuf.head()->size() ) {
528 d->stdinBufRead = 0;
529 delete d->stdinBuf.dequeue();
530 if ( wroteToStdinConnected && d->stdinBuf.isEmpty() )
531 emit wroteToStdin();
536 void Q3Process::flushStdin()
538 socketWrite( 0 );
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.
549 d->lookup->stop();
551 // try to write pending data to stdin
552 if ( !d->stdinBuf.isEmpty() )
553 socketWrite( 0 );
555 if ( ioRedirection ) {
556 socketRead( 1 ); // try stdout
557 socketRead( 2 ); // try stderr
560 if ( isRunning() ) {
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();
570 read on the pipe
572 uint Q3Process::readStddev( HANDLE dev, char *buf, uint bytes )
574 if ( bytes > 0 ) {
575 ulong r;
576 if ( ReadFile( dev, buf, bytes, &r, 0 ) )
577 return r;
579 return 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 )
590 d->lookup->stop();
591 if ( ioRedirection ) {
592 if ( isRunning() )
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 )
605 d->lookup->stop();
606 if ( notifyOnExit ) {
607 if ( isRunning() )
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()
623 return d->pid;
626 QT_END_NAMESPACE
628 #endif // QT_NO_PROCESS