source: trunk/src/qt3support/other/q3process_win.cpp@ 755

Last change on this file since 755 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 16.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt3Support module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
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.
23**
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.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qplatformdefs.h"
43#include "q3process.h"
44
45#ifndef QT_NO_PROCESS
46
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"
54
55#ifdef Q_OS_WINCE
56#define STARTF_USESTDHANDLES 1
57#endif
58
59QT_BEGIN_NAMESPACE
60
61//#define QT_Q3PROCESS_DEBUG
62
63/***********************************************************************
64 *
65 * Q3ProcessPrivate
66 *
67 **********************************************************************/
68class Q3ProcessPrivate
69{
70public:
71 Q3ProcessPrivate( Q3Process *proc )
72 {
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;
81
82 lookup = new QTimer( proc );
83 qApp->connect( lookup, SIGNAL(timeout()),
84 proc, SLOT(timeout()) );
85
86 pid = 0;
87 }
88
89 ~Q3ProcessPrivate()
90 {
91 reset();
92 }
93
94 void reset()
95 {
96 while ( !stdinBuf.isEmpty() ) {
97 delete stdinBuf.dequeue();
98 }
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;
108
109 deletePid();
110 }
111
112 void closeHandles()
113 {
114 if( pipeStdin[1] != 0 ) {
115 CloseHandle( pipeStdin[1] );
116 pipeStdin[1] = 0;
117 }
118 if( pipeStdout[0] != 0 ) {
119 CloseHandle( pipeStdout[0] );
120 pipeStdout[0] = 0;
121 }
122 if( pipeStderr[0] != 0 ) {
123 CloseHandle( pipeStderr[0] );
124 pipeStderr[0] = 0;
125 }
126 }
127
128 void deletePid()
129 {
130 if ( pid ) {
131 CloseHandle( pid->hProcess );
132 CloseHandle( pid->hThread );
133 delete pid;
134 pid = 0;
135 }
136 }
137
138 void newPid()
139 {
140 deletePid();
141 pid = new PROCESS_INFORMATION;
142 memset( pid, 0, sizeof(PROCESS_INFORMATION) );
143 }
144
145 Q3Membuf bufStdout;
146 Q3Membuf bufStderr;
147
148 Q3PtrQueue<QByteArray> stdinBuf;
149
150 HANDLE pipeStdin[2];
151 HANDLE pipeStdout[2];
152 HANDLE pipeStderr[2];
153 QTimer *lookup;
154
155 PROCESS_INFORMATION *pid;
156 uint stdinBufRead;
157
158 bool exitValuesCalculated;
159};
160
161
162/***********************************************************************
163 *
164 * Q3Process
165 *
166 **********************************************************************/
167void Q3Process::init()
168{
169 d = new Q3ProcessPrivate( this );
170 exitStat = 0;
171 exitNormal = false;
172}
173
174void Q3Process::reset()
175{
176 d->reset();
177 exitStat = 0;
178 exitNormal = false;
179 d->bufStdout.clear();
180 d->bufStderr.clear();
181}
182
183Q3Membuf* Q3Process::membufStdout()
184{
185 if( d->pipeStdout[0] != 0 )
186 socketRead( 1 );
187 return &d->bufStdout;
188}
189
190Q3Membuf* Q3Process::membufStderr()
191{
192 if( d->pipeStderr[0] != 0 )
193 socketRead( 2 );
194 return &d->bufStderr;
195}
196
197Q3Process::~Q3Process()
198{
199 delete d;
200}
201
202bool Q3Process::start( QStringList *env )
203{
204#if defined(QT_Q3PROCESS_DEBUG)
205 qDebug( "Q3Process::start()" );
206#endif
207 reset();
208
209 if ( _arguments.isEmpty() )
210 return false;
211
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;
224 }
225 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdin, GetCurrentProcess(), &d->pipeStdin[1], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
226 d->closeHandles();
227 return false;
228 }
229 if ( !CloseHandle( tmpStdin ) ) {
230 d->closeHandles();
231 return false;
232 }
233 }
234 if ( comms & Stdout ) {
235 if ( !CreatePipe( &tmpStdout, &d->pipeStdout[1], &secAtt, 0 ) ) {
236 d->closeHandles();
237 return false;
238 }
239 if ( !DuplicateHandle( GetCurrentProcess(), tmpStdout, GetCurrentProcess(), &d->pipeStdout[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
240 d->closeHandles();
241 return false;
242 }
243 if ( !CloseHandle( tmpStdout ) ) {
244 d->closeHandles();
245 return false;
246 }
247 }
248 if ( comms & Stderr ) {
249 if ( !CreatePipe( &tmpStderr, &d->pipeStderr[1], &secAtt, 0 ) ) {
250 d->closeHandles();
251 return false;
252 }
253 if ( !DuplicateHandle( GetCurrentProcess(), tmpStderr, GetCurrentProcess(), &d->pipeStderr[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) {
254 d->closeHandles();
255 return false;
256 }
257 if ( !CloseHandle( tmpStderr ) ) {
258 d->closeHandles();
259 return false;
260 }
261 }
262 if ( comms & DupStderr ) {
263 CloseHandle( d->pipeStderr[1] );
264 d->pipeStderr[1] = d->pipeStdout[1];
265 }
266#endif
267
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('"');
284 }
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('\\');
298 }
299 args += QLatin1String(" \"") + tmp.left( i ) + endQuote;
300 } else {
301 args += QLatin1Char(' ') + tmp;
302 }
303 }
304#if defined(QT_Q3PROCESS_DEBUG)
305 qDebug( "Q3Process::start(): args [%s]", args.latin1() );
306#endif
307
308 // CreateProcess()
309 bool success;
310 d->newPid();
311
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]
319 };
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;
337 }
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;
345 }
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;
352 }
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 );
361
362 free( applicationName );
363 free( commandLine );
364
365 if ( !success ) {
366 d->deletePid();
367 return false;
368 }
369
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
378
379 if ( ioRedirection || notifyOnExit ) {
380 d->lookup->start( 100 );
381 }
382
383 // cleanup and return
384 return true;
385}
386
387static BOOL CALLBACK qt_terminateApp( HWND hwnd, LPARAM procId )
388{
389 DWORD procId_win;
390 GetWindowThreadProcessId( hwnd, &procId_win );
391 if( procId_win == (DWORD)procId )
392 PostMessage( hwnd, WM_CLOSE, 0, 0 );
393
394 return TRUE;
395}
396
397void Q3Process::tryTerminate() const
398{
399 if ( d->pid )
400 EnumWindows( qt_terminateApp, (LPARAM)d->pid->dwProcessId );
401}
402
403void Q3Process::kill() const
404{
405 if ( d->pid )
406 TerminateProcess( d->pid->hProcess, 0xf291 );
407}
408
409bool Q3Process::isRunning() const
410{
411 if ( !d->pid )
412 return false;
413
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;
426 }
427 }
428 d->exitValuesCalculated = true;
429 }
430 d->deletePid();
431 d->closeHandles();
432 return false;
433 } else {
434 return true;
435 }
436}
437
438bool Q3Process::canReadLineStdout() const
439{
440 if( !d->pipeStdout[0] )
441 return d->bufStdout.size() != 0;
442
443 Q3Process *that = (Q3Process*)this;
444 return that->membufStdout()->scanNewline( 0 );
445}
446
447bool Q3Process::canReadLineStderr() const
448{
449 if( !d->pipeStderr[0] )
450 return d->bufStderr.size() != 0;
451
452 Q3Process *that = (Q3Process*)this;
453 return that->membufStderr()->scanNewline( 0 );
454}
455
456void Q3Process::writeToStdin( const QByteArray& buf )
457{
458 d->stdinBuf.enqueue( new QByteArray(buf) );
459 socketWrite( 0 );
460}
461
462void Q3Process::closeStdin( )
463{
464 if ( d->pipeStdin[1] != 0 ) {
465 CloseHandle( d->pipeStdin[1] );
466 d->pipeStdin[1] = 0;
467 }
468}
469
470void Q3Process::socketRead( int fd )
471{
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;
480 }
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?
487 }
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;
497
498 QByteArray *ba = new QByteArray( i );
499 uint sz = readStddev( dev, ba->data(), i );
500 if ( sz != i )
501 ba->resize( i );
502
503 if ( sz == 0 ) {
504 delete ba;
505 return;
506 }
507 buffer->append( ba );
508 if ( fd == 1 )
509 emit readyReadStdout();
510 else
511 emit readyReadStderr();
512 }
513}
514
515void Q3Process::socketWrite( int )
516{
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;
525 }
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();
532 }
533 }
534}
535
536void Q3Process::flushStdin()
537{
538 socketWrite( 0 );
539}
540
541/*
542 Use a timer for polling misc. stuff.
543*/
544void Q3Process::timeout()
545{
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();
550
551 // try to write pending data to stdin
552 if ( !d->stdinBuf.isEmpty() )
553 socketWrite( 0 );
554
555 if ( ioRedirection ) {
556 socketRead( 1 ); // try stdout
557 socketRead( 2 ); // try stderr
558 }
559
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();
566 }
567}
568
569/*
570 read on the pipe
571*/
572uint Q3Process::readStddev( HANDLE dev, char *buf, uint bytes )
573{
574 if ( bytes > 0 ) {
575 ulong r;
576 if ( ReadFile( dev, buf, bytes, &r, 0 ) )
577 return r;
578 }
579 return 0;
580}
581
582/*
583 Used by connectNotify() and disconnectNotify() to change the value of
584 ioRedirection (and related behaviour)
585*/
586void Q3Process::setIoRedirection( bool value )
587{
588 ioRedirection = value;
589 if ( !ioRedirection && !notifyOnExit )
590 d->lookup->stop();
591 if ( ioRedirection ) {
592 if ( isRunning() )
593 d->lookup->start( 100 );
594 }
595}
596
597/*
598 Used by connectNotify() and disconnectNotify() to change the value of
599 notifyOnExit (and related behaviour)
600*/
601void Q3Process::setNotifyOnExit( bool value )
602{
603 notifyOnExit = value;
604 if ( !ioRedirection && !notifyOnExit )
605 d->lookup->stop();
606 if ( notifyOnExit ) {
607 if ( isRunning() )
608 d->lookup->start( 100 );
609 }
610}
611
612/*
613 Used by connectNotify() and disconnectNotify() to change the value of
614 wroteToStdinConnected (and related behaviour)
615*/
616void Q3Process::setWroteStdinConnected( bool value )
617{
618 wroteToStdinConnected = value;
619}
620
621Q3Process::PID Q3Process::processIdentifier()
622{
623 return d->pid;
624}
625
626QT_END_NAMESPACE
627
628#endif // QT_NO_PROCESS
Note: See TracBrowser for help on using the repository browser.