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

Last change on this file since 447 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 17.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the Qt3Support module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department 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( QLatin1String("\""), 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 += QLatin1String("\\");
298 }
299 args += QString( 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#ifdef UNICODE
312 if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) {
313 STARTUPINFOW startupInfo = {
314 sizeof( STARTUPINFO ), 0, 0, 0,
315 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
316 0, 0, 0,
317 STARTF_USESTDHANDLES,
318 0, 0, 0,
319 d->pipeStdin[0], d->pipeStdout[1], d->pipeStderr[1]
320 };
321 TCHAR *applicationName;
322 if ( appName.isNull() )
323 applicationName = 0;
324 else
325 applicationName = _wcsdup( (TCHAR*)appName.ucs2() );
326 TCHAR *commandLine = _wcsdup( (TCHAR*)args.ucs2() );
327 QByteArray envlist;
328 if ( env != 0 ) {
329 int pos = 0;
330 // add PATH if necessary (for DLL loading)
331 QByteArray path = qgetenv( "PATH" );
332 if ( env->grep( QRegExp(QLatin1String("^PATH="),FALSE) ).empty() && !path.isNull() ) {
333 QString tmp = QString( QLatin1String("PATH=%1") ).arg(QString::fromLatin1(path.constData()));
334 uint tmpSize = sizeof(TCHAR) * (tmp.length()+1);
335 envlist.resize( envlist.size() + tmpSize );
336 memcpy( envlist.data()+pos, tmp.ucs2(), tmpSize );
337 pos += tmpSize;
338 }
339 // add the user environment
340 for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) {
341 QString tmp = *it;
342 uint tmpSize = sizeof(TCHAR) * (tmp.length()+1);
343 envlist.resize( envlist.size() + tmpSize );
344 memcpy( envlist.data()+pos, tmp.ucs2(), tmpSize );
345 pos += tmpSize;
346 }
347 // add the 2 terminating 0 (actually 4, just to be on the safe side)
348 envlist.resize( envlist.size()+4 );
349 envlist[pos++] = 0;
350 envlist[pos++] = 0;
351 envlist[pos++] = 0;
352 envlist[pos++] = 0;
353 }
354 success = CreateProcessW( applicationName, commandLine,
355 0, 0, TRUE, ( comms==0 ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW )
356#ifndef Q_OS_WINCE
357 | CREATE_UNICODE_ENVIRONMENT
358#endif
359 , env==0 ? 0 : envlist.data(),
360 (TCHAR*)QDir::toNativeSeparators(workingDir.absPath()).ucs2(),
361 &startupInfo, d->pid );
362 free( applicationName );
363 free( commandLine );
364 } else
365#endif // UNICODE
366 {
367#ifndef Q_OS_WINCE
368 STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 0, 0, 0,
369 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
370 0, 0, 0,
371 STARTF_USESTDHANDLES,
372 0, 0, 0,
373 d->pipeStdin[0], d->pipeStdout[1], d->pipeStderr[1]
374 };
375 QByteArray envlist;
376 if ( env != 0 ) {
377 int pos = 0;
378 // add PATH if necessary (for DLL loading)
379 QByteArray path = qgetenv( "PATH" );
380 if ( env->grep( QRegExp(QLatin1String("^PATH="),FALSE) ).empty() && !path.isNull() ) {
381 Q3CString tmp = QString( QLatin1String("PATH=%1") ).arg(QString::fromLatin1(path.constData())).local8Bit();
382 uint tmpSize = tmp.length() + 1;
383 envlist.resize( envlist.size() + tmpSize );
384 memcpy( envlist.data()+pos, tmp.data(), tmpSize );
385 pos += tmpSize;
386 }
387 // add the user environment
388 for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) {
389 Q3CString tmp = (*it).local8Bit();
390 uint tmpSize = tmp.length() + 1;
391 envlist.resize( envlist.size() + tmpSize );
392 memcpy( envlist.data()+pos, tmp.data(), tmpSize );
393 pos += tmpSize;
394 }
395 // add the terminating 0 (actually 2, just to be on the safe side)
396 envlist.resize( envlist.size()+2 );
397 envlist[pos++] = 0;
398 envlist[pos++] = 0;
399 }
400 char *applicationName;
401 if ( appName.isNull() )
402 applicationName = 0;
403 else
404 applicationName = const_cast<char *>(appName.toLocal8Bit().data());
405 success = CreateProcessA( applicationName,
406 const_cast<char *>(args.toLocal8Bit().data()),
407 0, 0, TRUE, comms==0 ? CREATE_NEW_CONSOLE : DETACHED_PROCESS,
408 env==0 ? 0 : envlist.data(),
409 (const char*)QDir::toNativeSeparators(workingDir.absPath()).local8Bit(),
410 &startupInfo, d->pid );
411#endif // Q_OS_WINCE
412 }
413 if ( !success ) {
414 d->deletePid();
415 return false;
416 }
417
418#ifndef Q_OS_WINCE
419 if ( comms & Stdin )
420 CloseHandle( d->pipeStdin[0] );
421 if ( comms & Stdout )
422 CloseHandle( d->pipeStdout[1] );
423 if ( (comms & Stderr) && !(comms & DupStderr) )
424 CloseHandle( d->pipeStderr[1] );
425#endif
426
427 if ( ioRedirection || notifyOnExit ) {
428 d->lookup->start( 100 );
429 }
430
431 // cleanup and return
432 return true;
433}
434
435static BOOL CALLBACK qt_terminateApp( HWND hwnd, LPARAM procId )
436{
437 DWORD procId_win;
438 GetWindowThreadProcessId( hwnd, &procId_win );
439 if( procId_win == (DWORD)procId )
440 PostMessage( hwnd, WM_CLOSE, 0, 0 );
441
442 return TRUE;
443}
444
445void Q3Process::tryTerminate() const
446{
447 if ( d->pid )
448 EnumWindows( qt_terminateApp, (LPARAM)d->pid->dwProcessId );
449}
450
451void Q3Process::kill() const
452{
453 if ( d->pid )
454 TerminateProcess( d->pid->hProcess, 0xf291 );
455}
456
457bool Q3Process::isRunning() const
458{
459 if ( !d->pid )
460 return false;
461
462 if ( WaitForSingleObject( d->pid->hProcess, 0) == WAIT_OBJECT_0 ) {
463 // there might be data to read
464 Q3Process *that = (Q3Process*)this;
465 that->socketRead( 1 ); // try stdout
466 that->socketRead( 2 ); // try stderr
467 // compute the exit values
468 if ( !d->exitValuesCalculated ) {
469 DWORD exitCode;
470 if ( GetExitCodeProcess( d->pid->hProcess, &exitCode ) ) {
471 if ( exitCode != STILL_ACTIVE ) { // this should ever be true?
472 that->exitNormal = exitCode != 0xf291;
473 that->exitStat = exitCode;
474 }
475 }
476 d->exitValuesCalculated = true;
477 }
478 d->deletePid();
479 d->closeHandles();
480 return false;
481 } else {
482 return true;
483 }
484}
485
486bool Q3Process::canReadLineStdout() const
487{
488 if( !d->pipeStdout[0] )
489 return d->bufStdout.size() != 0;
490
491 Q3Process *that = (Q3Process*)this;
492 return that->membufStdout()->scanNewline( 0 );
493}
494
495bool Q3Process::canReadLineStderr() const
496{
497 if( !d->pipeStderr[0] )
498 return d->bufStderr.size() != 0;
499
500 Q3Process *that = (Q3Process*)this;
501 return that->membufStderr()->scanNewline( 0 );
502}
503
504void Q3Process::writeToStdin( const QByteArray& buf )
505{
506 d->stdinBuf.enqueue( new QByteArray(buf) );
507 socketWrite( 0 );
508}
509
510void Q3Process::closeStdin( )
511{
512 if ( d->pipeStdin[1] != 0 ) {
513 CloseHandle( d->pipeStdin[1] );
514 d->pipeStdin[1] = 0;
515 }
516}
517
518void Q3Process::socketRead( int fd )
519{
520 // fd == 1: stdout, fd == 2: stderr
521 HANDLE dev;
522 if ( fd == 1 ) {
523 dev = d->pipeStdout[0];
524 } else if ( fd == 2 ) {
525 dev = d->pipeStderr[0];
526 } else {
527 return;
528 }
529#ifndef Q_OS_WINCE
530 // get the number of bytes that are waiting to be read
531 unsigned long i, r;
532 char dummy;
533 if ( !PeekNamedPipe( dev, &dummy, 1, &r, &i, 0 ) ) {
534 return; // ### is it worth to dig for the reason of the error?
535 }
536#else
537 unsigned long i = 1000;
538#endif
539 if ( i > 0 ) {
540 Q3Membuf *buffer;
541 if ( fd == 1 )
542 buffer = &d->bufStdout;
543 else
544 buffer = &d->bufStderr;
545
546 QByteArray *ba = new QByteArray( i );
547 uint sz = readStddev( dev, ba->data(), i );
548 if ( sz != i )
549 ba->resize( i );
550
551 if ( sz == 0 ) {
552 delete ba;
553 return;
554 }
555 buffer->append( ba );
556 if ( fd == 1 )
557 emit readyReadStdout();
558 else
559 emit readyReadStderr();
560 }
561}
562
563void Q3Process::socketWrite( int )
564{
565 DWORD written;
566 while ( !d->stdinBuf.isEmpty() && isRunning() ) {
567 if ( !WriteFile( d->pipeStdin[1],
568 d->stdinBuf.head()->data() + d->stdinBufRead,
569 qMin( 8192, int(d->stdinBuf.head()->size() - d->stdinBufRead) ),
570 &written, 0 ) ) {
571 d->lookup->start( 100 );
572 return;
573 }
574 d->stdinBufRead += written;
575 if ( d->stdinBufRead == (DWORD)d->stdinBuf.head()->size() ) {
576 d->stdinBufRead = 0;
577 delete d->stdinBuf.dequeue();
578 if ( wroteToStdinConnected && d->stdinBuf.isEmpty() )
579 emit wroteToStdin();
580 }
581 }
582}
583
584void Q3Process::flushStdin()
585{
586 socketWrite( 0 );
587}
588
589/*
590 Use a timer for polling misc. stuff.
591*/
592void Q3Process::timeout()
593{
594 // Disable the timer temporary since one of the slots that are connected to
595 // the readyRead...(), etc. signals might trigger recursion if
596 // processEvents() is called.
597 d->lookup->stop();
598
599 // try to write pending data to stdin
600 if ( !d->stdinBuf.isEmpty() )
601 socketWrite( 0 );
602
603 if ( ioRedirection ) {
604 socketRead( 1 ); // try stdout
605 socketRead( 2 ); // try stderr
606 }
607
608 if ( isRunning() ) {
609 // enable timer again, if needed
610 if ( !d->stdinBuf.isEmpty() || ioRedirection || notifyOnExit )
611 d->lookup->start( 100 );
612 } else if ( notifyOnExit ) {
613 emit processExited();
614 }
615}
616
617/*
618 read on the pipe
619*/
620uint Q3Process::readStddev( HANDLE dev, char *buf, uint bytes )
621{
622 if ( bytes > 0 ) {
623 ulong r;
624 if ( ReadFile( dev, buf, bytes, &r, 0 ) )
625 return r;
626 }
627 return 0;
628}
629
630/*
631 Used by connectNotify() and disconnectNotify() to change the value of
632 ioRedirection (and related behaviour)
633*/
634void Q3Process::setIoRedirection( bool value )
635{
636 ioRedirection = value;
637 if ( !ioRedirection && !notifyOnExit )
638 d->lookup->stop();
639 if ( ioRedirection ) {
640 if ( isRunning() )
641 d->lookup->start( 100 );
642 }
643}
644
645/*
646 Used by connectNotify() and disconnectNotify() to change the value of
647 notifyOnExit (and related behaviour)
648*/
649void Q3Process::setNotifyOnExit( bool value )
650{
651 notifyOnExit = value;
652 if ( !ioRedirection && !notifyOnExit )
653 d->lookup->stop();
654 if ( notifyOnExit ) {
655 if ( isRunning() )
656 d->lookup->start( 100 );
657 }
658}
659
660/*
661 Used by connectNotify() and disconnectNotify() to change the value of
662 wroteToStdinConnected (and related behaviour)
663*/
664void Q3Process::setWroteStdinConnected( bool value )
665{
666 wroteToStdinConnected = value;
667}
668
669Q3Process::PID Q3Process::processIdentifier()
670{
671 return d->pid;
672}
673
674QT_END_NAMESPACE
675
676#endif // QT_NO_PROCESS
Note: See TracBrowser for help on using the repository browser.