1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2017 Intel Corporation.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtCore module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 //#define QPROCESS_DEBUG
42 #include "qprocess.h"
43 #include "qprocess_p.h"
44 #include "qwindowspipereader_p.h"
45 #include "qwindowspipewriter_p.h"
46
47 #include <qdatetime.h>
48 #include <qdir.h>
49 #include <qelapsedtimer.h>
50 #include <qfileinfo.h>
51 #include <qrandom.h>
52 #include <qwineventnotifier.h>
53 #include <private/qsystemlibrary_p.h>
54 #include <private/qthread_p.h>
55 #include <qdebug.h>
56
57 #include "private/qfsfileengine_p.h" // for longFileName
58
59 #ifndef PIPE_REJECT_REMOTE_CLIENTS
60 #define PIPE_REJECT_REMOTE_CLIENTS 0x08
61 #endif
62
63 QT_BEGIN_NAMESPACE
64
systemEnvironment()65 QProcessEnvironment QProcessEnvironment::systemEnvironment()
66 {
67 QProcessEnvironment env;
68 // Calls to setenv() affect the low-level environment as well.
69 // This is not the case the other way round.
70 if (wchar_t *envStrings = GetEnvironmentStringsW()) {
71 for (const wchar_t *entry = envStrings; *entry; ) {
72 const int entryLen = int(wcslen(entry));
73 // + 1 to permit magic cmd variable names starting with =
74 if (const wchar_t *equal = wcschr(entry + 1, L'=')) {
75 int nameLen = equal - entry;
76 QString name = QString::fromWCharArray(entry, nameLen);
77 QString value = QString::fromWCharArray(equal + 1, entryLen - nameLen - 1);
78 env.d->vars.insert(QProcessEnvironmentPrivate::Key(name), value);
79 }
80 entry += entryLen + 1;
81 }
82 FreeEnvironmentStringsW(envStrings);
83 }
84 return env;
85 }
86
87 #if QT_CONFIG(process)
88
qt_create_pipe(Q_PIPE * pipe,bool isInputPipe)89 static void qt_create_pipe(Q_PIPE *pipe, bool isInputPipe)
90 {
91 // Anomymous pipes do not support asynchronous I/O. Thus we
92 // create named pipes for redirecting stdout, stderr and stdin.
93
94 // The write handle must be non-inheritable for input pipes.
95 // The read handle must be non-inheritable for output pipes.
96 SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), 0, false };
97
98 HANDLE hServer;
99 wchar_t pipeName[256];
100 unsigned int attempts = 1000;
101 forever {
102 _snwprintf(pipeName, sizeof(pipeName) / sizeof(pipeName[0]),
103 L"\\\\.\\pipe\\qt-%lX-%X", long(QCoreApplication::applicationPid()),
104 QRandomGenerator::global()->generate());
105
106 DWORD dwOpenMode = FILE_FLAG_OVERLAPPED;
107 DWORD dwOutputBufferSize = 0;
108 DWORD dwInputBufferSize = 0;
109 const DWORD dwPipeBufferSize = 1024 * 1024;
110 if (isInputPipe) {
111 dwOpenMode |= PIPE_ACCESS_OUTBOUND;
112 dwOutputBufferSize = dwPipeBufferSize;
113 } else {
114 dwOpenMode |= PIPE_ACCESS_INBOUND;
115 dwInputBufferSize = dwPipeBufferSize;
116 }
117 DWORD dwPipeFlags = PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
118 hServer = CreateNamedPipe(pipeName,
119 dwOpenMode,
120 dwPipeFlags,
121 1, // only one pipe instance
122 dwOutputBufferSize,
123 dwInputBufferSize,
124 0,
125 &secAtt);
126 if (hServer != INVALID_HANDLE_VALUE)
127 break;
128 DWORD dwError = GetLastError();
129 if (dwError != ERROR_PIPE_BUSY || !--attempts) {
130 qErrnoWarning(dwError, "QProcess: CreateNamedPipe failed.");
131 return;
132 }
133 }
134
135 secAtt.bInheritHandle = TRUE;
136 const HANDLE hClient = CreateFile(pipeName,
137 (isInputPipe ? (GENERIC_READ | FILE_WRITE_ATTRIBUTES)
138 : GENERIC_WRITE),
139 0,
140 &secAtt,
141 OPEN_EXISTING,
142 FILE_FLAG_OVERLAPPED,
143 NULL);
144 if (hClient == INVALID_HANDLE_VALUE) {
145 qErrnoWarning("QProcess: CreateFile failed.");
146 CloseHandle(hServer);
147 return;
148 }
149
150 // Wait until connection is in place.
151 OVERLAPPED overlapped;
152 ZeroMemory(&overlapped, sizeof(overlapped));
153 overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
154 if (ConnectNamedPipe(hServer, &overlapped) == 0) {
155 DWORD dwError = GetLastError();
156 switch (dwError) {
157 case ERROR_PIPE_CONNECTED:
158 break;
159 case ERROR_IO_PENDING:
160 WaitForSingleObject(overlapped.hEvent, INFINITE);
161 break;
162 default:
163 qErrnoWarning(dwError, "QProcess: ConnectNamedPipe failed.");
164 CloseHandle(overlapped.hEvent);
165 CloseHandle(hClient);
166 CloseHandle(hServer);
167 return;
168 }
169 }
170 CloseHandle(overlapped.hEvent);
171
172 if (isInputPipe) {
173 pipe[0] = hClient;
174 pipe[1] = hServer;
175 } else {
176 pipe[0] = hServer;
177 pipe[1] = hClient;
178 }
179 }
180
duplicateStdWriteChannel(Q_PIPE * pipe,DWORD nStdHandle)181 static void duplicateStdWriteChannel(Q_PIPE *pipe, DWORD nStdHandle)
182 {
183 pipe[0] = INVALID_Q_PIPE;
184 HANDLE hStdWriteChannel = GetStdHandle(nStdHandle);
185 HANDLE hCurrentProcess = GetCurrentProcess();
186 DuplicateHandle(hCurrentProcess, hStdWriteChannel, hCurrentProcess,
187 &pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
188 }
189
190 /*
191 Create the pipes to a QProcessPrivate::Channel.
192
193 This function must be called in order: stdin, stdout, stderr
194 */
openChannel(Channel & channel)195 bool QProcessPrivate::openChannel(Channel &channel)
196 {
197 Q_Q(QProcess);
198
199 if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) {
200 return DuplicateHandle(GetCurrentProcess(), stdoutChannel.pipe[1], GetCurrentProcess(),
201 &stderrChannel.pipe[1], 0, TRUE, DUPLICATE_SAME_ACCESS);
202 }
203
204 switch (channel.type) {
205 case Channel::Normal:
206 // we're piping this channel to our own process
207 if (&channel == &stdinChannel) {
208 if (inputChannelMode != QProcess::ForwardedInputChannel) {
209 qt_create_pipe(channel.pipe, true);
210 } else {
211 channel.pipe[1] = INVALID_Q_PIPE;
212 HANDLE hStdReadChannel = GetStdHandle(STD_INPUT_HANDLE);
213 HANDLE hCurrentProcess = GetCurrentProcess();
214 DuplicateHandle(hCurrentProcess, hStdReadChannel, hCurrentProcess,
215 &channel.pipe[0], 0, TRUE, DUPLICATE_SAME_ACCESS);
216 }
217 } else {
218 if (&channel == &stdoutChannel) {
219 if (processChannelMode != QProcess::ForwardedChannels
220 && processChannelMode != QProcess::ForwardedOutputChannel) {
221 if (!stdoutChannel.reader) {
222 stdoutChannel.reader = new QWindowsPipeReader(q);
223 q->connect(stdoutChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardOutput()));
224 }
225 } else {
226 duplicateStdWriteChannel(channel.pipe, STD_OUTPUT_HANDLE);
227 }
228 } else /* if (&channel == &stderrChannel) */ {
229 if (processChannelMode != QProcess::ForwardedChannels
230 && processChannelMode != QProcess::ForwardedErrorChannel) {
231 if (!stderrChannel.reader) {
232 stderrChannel.reader = new QWindowsPipeReader(q);
233 q->connect(stderrChannel.reader, SIGNAL(readyRead()), SLOT(_q_canReadStandardError()));
234 }
235 } else {
236 duplicateStdWriteChannel(channel.pipe, STD_ERROR_HANDLE);
237 }
238 }
239 if (channel.reader) {
240 qt_create_pipe(channel.pipe, false);
241 channel.reader->setHandle(channel.pipe[0]);
242 channel.reader->startAsyncRead();
243 }
244 }
245 return true;
246 case Channel::Redirect: {
247 // we're redirecting the channel to/from a file
248 SECURITY_ATTRIBUTES secAtt = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
249
250 if (&channel == &stdinChannel) {
251 // try to open in read-only mode
252 channel.pipe[1] = INVALID_Q_PIPE;
253 channel.pipe[0] =
254 CreateFile((const wchar_t*)QFSFileEnginePrivate::longFileName(channel.file).utf16(),
255 GENERIC_READ,
256 FILE_SHARE_READ | FILE_SHARE_WRITE,
257 &secAtt,
258 OPEN_EXISTING,
259 FILE_ATTRIBUTE_NORMAL,
260 NULL);
261
262 if (channel.pipe[0] != INVALID_Q_PIPE)
263 return true;
264
265 setErrorAndEmit(QProcess::FailedToStart,
266 QProcess::tr("Could not open input redirection for reading"));
267 } else {
268 // open in write mode
269 channel.pipe[0] = INVALID_Q_PIPE;
270 channel.pipe[1] =
271 CreateFile((const wchar_t *)QFSFileEnginePrivate::longFileName(channel.file).utf16(),
272 GENERIC_WRITE,
273 FILE_SHARE_READ | FILE_SHARE_WRITE,
274 &secAtt,
275 channel.append ? OPEN_ALWAYS : CREATE_ALWAYS,
276 FILE_ATTRIBUTE_NORMAL,
277 NULL);
278
279 if (channel.pipe[1] != INVALID_Q_PIPE) {
280 if (channel.append) {
281 SetFilePointer(channel.pipe[1], 0, NULL, FILE_END);
282 }
283 return true;
284 }
285
286 setErrorAndEmit(QProcess::FailedToStart,
287 QProcess::tr("Could not open output redirection for writing"));
288 }
289 cleanup();
290 return false;
291 }
292 case Channel::PipeSource: {
293 Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
294 // we are the source
295 Channel *source = &channel;
296 Channel *sink = &channel.process->stdinChannel;
297
298 if (source->pipe[1] != INVALID_Q_PIPE) {
299 // already constructed by the sink
300 // make it inheritable
301 HANDLE tmpHandle = source->pipe[1];
302 if (!DuplicateHandle(GetCurrentProcess(), tmpHandle,
303 GetCurrentProcess(), &source->pipe[1],
304 0, TRUE, DUPLICATE_SAME_ACCESS)) {
305 return false;
306 }
307
308 CloseHandle(tmpHandle);
309 return true;
310 }
311
312 Q_ASSERT(source == &stdoutChannel);
313 Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink);
314
315 qt_create_pipe(source->pipe, /* in = */ false); // source is stdout
316 sink->pipe[0] = source->pipe[0];
317 source->pipe[0] = INVALID_Q_PIPE;
318
319 return true;
320 }
321 case Channel::PipeSink: { // we are the sink;
322 Q_ASSERT_X(channel.process, "QProcess::start", "Internal error");
323 Channel *source = &channel.process->stdoutChannel;
324 Channel *sink = &channel;
325
326 if (sink->pipe[0] != INVALID_Q_PIPE) {
327 // already constructed by the source
328 // make it inheritable
329 HANDLE tmpHandle = sink->pipe[0];
330 if (!DuplicateHandle(GetCurrentProcess(), tmpHandle,
331 GetCurrentProcess(), &sink->pipe[0],
332 0, TRUE, DUPLICATE_SAME_ACCESS)) {
333 return false;
334 }
335
336 CloseHandle(tmpHandle);
337 return true;
338 }
339 Q_ASSERT(sink == &stdinChannel);
340 Q_ASSERT(source->process == this && source->type == Channel::PipeSource);
341
342 qt_create_pipe(sink->pipe, /* in = */ true); // sink is stdin
343 source->pipe[1] = sink->pipe[1];
344 sink->pipe[1] = INVALID_Q_PIPE;
345
346 return true;
347 }
348 } // switch (channel.type)
349 return false;
350 }
351
destroyPipe(Q_PIPE pipe[2])352 void QProcessPrivate::destroyPipe(Q_PIPE pipe[2])
353 {
354 if (pipe[0] != INVALID_Q_PIPE) {
355 CloseHandle(pipe[0]);
356 pipe[0] = INVALID_Q_PIPE;
357 }
358 if (pipe[1] != INVALID_Q_PIPE) {
359 CloseHandle(pipe[1]);
360 pipe[1] = INVALID_Q_PIPE;
361 }
362 }
363
364 template <class T>
deleteWorker(T * & worker)365 void deleteWorker(T *&worker)
366 {
367 if (!worker)
368 return;
369 worker->stop();
370 worker->deleteLater();
371 worker = nullptr;
372 }
373
closeChannel(Channel * channel)374 void QProcessPrivate::closeChannel(Channel *channel)
375 {
376 if (channel == &stdinChannel)
377 deleteWorker(channel->writer);
378 else
379 deleteWorker(channel->reader);
380 destroyPipe(channel->pipe);
381 }
382
qt_create_commandline(const QString & program,const QStringList & arguments,const QString & nativeArguments)383 static QString qt_create_commandline(const QString &program, const QStringList &arguments,
384 const QString &nativeArguments)
385 {
386 QString args;
387 if (!program.isEmpty()) {
388 QString programName = program;
389 if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' ')))
390 programName = QLatin1Char('\"') + programName + QLatin1Char('\"');
391 programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
392
393 // add the prgram as the first arg ... it works better
394 args = programName + QLatin1Char(' ');
395 }
396
397 for (int i=0; i<arguments.size(); ++i) {
398 QString tmp = arguments.at(i);
399 // Quotes are escaped and their preceding backslashes are doubled.
400 int index = tmp.indexOf(QLatin1Char('"'));
401 while (index >= 0) {
402 // Escape quote
403 tmp.insert(index++, QLatin1Char('\\'));
404 // Double preceding backslashes (ignoring the one we just inserted)
405 for (int i = index - 2 ; i >= 0 && tmp.at(i) == QLatin1Char('\\') ; --i) {
406 tmp.insert(i, QLatin1Char('\\'));
407 index++;
408 }
409 index = tmp.indexOf(QLatin1Char('"'), index + 1);
410 }
411 if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
412 // The argument must not end with a \ since this would be interpreted
413 // as escaping the quote -- rather put the \ behind the quote: e.g.
414 // rather use "foo"\ than "foo\"
415 int i = tmp.length();
416 while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\'))
417 --i;
418 tmp.insert(i, QLatin1Char('"'));
419 tmp.prepend(QLatin1Char('"'));
420 }
421 args += QLatin1Char(' ') + tmp;
422 }
423
424 if (!nativeArguments.isEmpty()) {
425 if (!args.isEmpty())
426 args += QLatin1Char(' ');
427 args += nativeArguments;
428 }
429
430 return args;
431 }
432
qt_create_environment(const QProcessEnvironmentPrivate::Map & environment)433 static QByteArray qt_create_environment(const QProcessEnvironmentPrivate::Map &environment)
434 {
435 QByteArray envlist;
436 if (!environment.isEmpty()) {
437 QProcessEnvironmentPrivate::Map copy = environment;
438
439 // add PATH if necessary (for DLL loading)
440 QProcessEnvironmentPrivate::Key pathKey(QLatin1String("PATH"));
441 if (!copy.contains(pathKey)) {
442 QByteArray path = qgetenv("PATH");
443 if (!path.isEmpty())
444 copy.insert(pathKey, QString::fromLocal8Bit(path));
445 }
446
447 // add systemroot if needed
448 QProcessEnvironmentPrivate::Key rootKey(QLatin1String("SystemRoot"));
449 if (!copy.contains(rootKey)) {
450 QByteArray systemRoot = qgetenv("SystemRoot");
451 if (!systemRoot.isEmpty())
452 copy.insert(rootKey, QString::fromLocal8Bit(systemRoot));
453 }
454
455 int pos = 0;
456 auto it = copy.constBegin();
457 const auto end = copy.constEnd();
458
459 static const wchar_t equal = L'=';
460 static const wchar_t nul = L'\0';
461
462 for ( ; it != end; ++it) {
463 uint tmpSize = sizeof(wchar_t) * (it.key().length() + it.value().length() + 2);
464 // ignore empty strings
465 if (tmpSize == sizeof(wchar_t) * 2)
466 continue;
467 envlist.resize(envlist.size() + tmpSize);
468
469 tmpSize = it.key().length() * sizeof(wchar_t);
470 memcpy(envlist.data()+pos, it.key().utf16(), tmpSize);
471 pos += tmpSize;
472
473 memcpy(envlist.data()+pos, &equal, sizeof(wchar_t));
474 pos += sizeof(wchar_t);
475
476 tmpSize = it.value().length() * sizeof(wchar_t);
477 memcpy(envlist.data()+pos, it.value().utf16(), tmpSize);
478 pos += tmpSize;
479
480 memcpy(envlist.data()+pos, &nul, sizeof(wchar_t));
481 pos += sizeof(wchar_t);
482 }
483 // add the 2 terminating 0 (actually 4, just to be on the safe side)
484 envlist.resize( envlist.size()+4 );
485 envlist[pos++] = 0;
486 envlist[pos++] = 0;
487 envlist[pos++] = 0;
488 envlist[pos++] = 0;
489 }
490 return envlist;
491 }
492
callCreateProcess(QProcess::CreateProcessArguments * cpargs)493 bool QProcessPrivate::callCreateProcess(QProcess::CreateProcessArguments *cpargs)
494 {
495 if (modifyCreateProcessArgs)
496 modifyCreateProcessArgs(cpargs);
497 bool success = CreateProcess(cpargs->applicationName, cpargs->arguments,
498 cpargs->processAttributes, cpargs->threadAttributes,
499 cpargs->inheritHandles, cpargs->flags, cpargs->environment,
500 cpargs->currentDirectory, cpargs->startupInfo,
501 cpargs->processInformation);
502 if (stdinChannel.pipe[0] != INVALID_Q_PIPE) {
503 CloseHandle(stdinChannel.pipe[0]);
504 stdinChannel.pipe[0] = INVALID_Q_PIPE;
505 }
506 if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) {
507 CloseHandle(stdoutChannel.pipe[1]);
508 stdoutChannel.pipe[1] = INVALID_Q_PIPE;
509 }
510 if (stderrChannel.pipe[1] != INVALID_Q_PIPE) {
511 CloseHandle(stderrChannel.pipe[1]);
512 stderrChannel.pipe[1] = INVALID_Q_PIPE;
513 }
514 return success;
515 }
516
startProcess()517 void QProcessPrivate::startProcess()
518 {
519 Q_Q(QProcess);
520
521 bool success = false;
522
523 if (pid) {
524 CloseHandle(pid->hThread);
525 CloseHandle(pid->hProcess);
526 delete pid;
527 pid = 0;
528 }
529 pid = new PROCESS_INFORMATION;
530 memset(pid, 0, sizeof(PROCESS_INFORMATION));
531
532 q->setProcessState(QProcess::Starting);
533
534 if (!openChannel(stdinChannel) ||
535 !openChannel(stdoutChannel) ||
536 !openChannel(stderrChannel)) {
537 QString errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
538 cleanup();
539 setErrorAndEmit(QProcess::FailedToStart, errorString);
540 q->setProcessState(QProcess::NotRunning);
541 return;
542 }
543
544 const QString args = qt_create_commandline(program, arguments, nativeArguments);
545 QByteArray envlist;
546 if (environment.d.constData())
547 envlist = qt_create_environment(environment.d.constData()->vars);
548
549 #if defined QPROCESS_DEBUG
550 qDebug("Creating process");
551 qDebug(" program : [%s]", program.toLatin1().constData());
552 qDebug(" args : %s", args.toLatin1().constData());
553 qDebug(" pass environment : %s", environment.isEmpty() ? "no" : "yes");
554 #endif
555
556 // We cannot unconditionally set the CREATE_NO_WINDOW flag, because this
557 // will render the stdout/stderr handles connected to a console useless
558 // (this typically affects ForwardedChannels mode).
559 // However, we also do not want console tools launched from a GUI app to
560 // create new console windows (behavior consistent with UNIX).
561 DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
562 dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
563 STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
564 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
565 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
566 0, 0, 0,
567 STARTF_USESTDHANDLES,
568 0, 0, 0,
569 stdinChannel.pipe[0], stdoutChannel.pipe[1], stderrChannel.pipe[1]
570 };
571
572 const QString nativeWorkingDirectory = QDir::toNativeSeparators(workingDirectory);
573 QProcess::CreateProcessArguments cpargs = {
574 nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())),
575 nullptr, nullptr, true, dwCreationFlags,
576 environment.isEmpty() ? nullptr : envlist.data(),
577 nativeWorkingDirectory.isEmpty()
578 ? nullptr : reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16()),
579 &startupInfo, pid
580 };
581 success = callCreateProcess(&cpargs);
582
583 QString errorString;
584 if (!success) {
585 // Capture the error string before we do CloseHandle below
586 errorString = QProcess::tr("Process failed to start: %1").arg(qt_error_string());
587 }
588
589 if (!success) {
590 cleanup();
591 setErrorAndEmit(QProcess::FailedToStart, errorString);
592 q->setProcessState(QProcess::NotRunning);
593 return;
594 }
595
596 q->setProcessState(QProcess::Running);
597 // User can call kill()/terminate() from the stateChanged() slot
598 // so check before proceeding
599 if (!pid)
600 return;
601
602 if (threadData.loadRelaxed()->hasEventDispatcher()) {
603 processFinishedNotifier = new QWinEventNotifier(pid->hProcess, q);
604 QObject::connect(processFinishedNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_processDied()));
605 processFinishedNotifier->setEnabled(true);
606 }
607
608 _q_startupNotification();
609 }
610
processStarted(QString *)611 bool QProcessPrivate::processStarted(QString * /*errorMessage*/)
612 {
613 return processState == QProcess::Running;
614 }
615
bytesAvailableInChannel(const Channel * channel) const616 qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const
617 {
618 Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
619 Q_ASSERT(channel->reader);
620
621 DWORD bytesAvail = channel->reader->bytesAvailable();
622 #if defined QPROCESS_DEBUG
623 qDebug("QProcessPrivate::bytesAvailableInChannel(%d) == %lld",
624 int(channel - &stdinChannel), qint64(bytesAvail));
625 #endif
626 return bytesAvail;
627 }
628
readFromChannel(const Channel * channel,char * data,qint64 maxlen)629 qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint64 maxlen)
630 {
631 Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE);
632 Q_ASSERT(channel->reader);
633 return channel->reader->read(data, maxlen);
634 }
635
qt_terminateApp(HWND hwnd,LPARAM procId)636 static BOOL QT_WIN_CALLBACK qt_terminateApp(HWND hwnd, LPARAM procId)
637 {
638 DWORD currentProcId = 0;
639 GetWindowThreadProcessId(hwnd, ¤tProcId);
640 if (currentProcId == (DWORD)procId)
641 PostMessage(hwnd, WM_CLOSE, 0, 0);
642
643 return TRUE;
644 }
645
terminateProcess()646 void QProcessPrivate::terminateProcess()
647 {
648 if (pid) {
649 EnumWindows(qt_terminateApp, (LPARAM)pid->dwProcessId);
650 PostThreadMessage(pid->dwThreadId, WM_CLOSE, 0, 0);
651 }
652 }
653
killProcess()654 void QProcessPrivate::killProcess()
655 {
656 if (pid)
657 TerminateProcess(pid->hProcess, 0xf291);
658 }
659
waitForStarted(int)660 bool QProcessPrivate::waitForStarted(int)
661 {
662 if (processStarted())
663 return true;
664
665 if (processError == QProcess::FailedToStart)
666 return false;
667
668 setError(QProcess::Timedout);
669 return false;
670 }
671
drainOutputPipes()672 bool QProcessPrivate::drainOutputPipes()
673 {
674 if (!stdoutChannel.reader && !stderrChannel.reader)
675 return false;
676
677 bool someReadyReadEmitted = false;
678 forever {
679 bool readyReadEmitted = false;
680 bool readOperationActive = false;
681 if (stdoutChannel.reader) {
682 readyReadEmitted |= stdoutChannel.reader->waitForReadyRead(0);
683 readOperationActive = stdoutChannel.reader && stdoutChannel.reader->isReadOperationActive();
684 }
685 if (stderrChannel.reader) {
686 readyReadEmitted |= stderrChannel.reader->waitForReadyRead(0);
687 readOperationActive |= stderrChannel.reader && stderrChannel.reader->isReadOperationActive();
688 }
689 someReadyReadEmitted |= readyReadEmitted;
690 if (!readOperationActive || !readyReadEmitted)
691 break;
692 QThread::yieldCurrentThread();
693 }
694
695 return someReadyReadEmitted;
696 }
697
waitForReadyRead(int msecs)698 bool QProcessPrivate::waitForReadyRead(int msecs)
699 {
700 QIncrementalSleepTimer timer(msecs);
701
702 forever {
703 if (!writeBuffer.isEmpty() && !_q_canWrite())
704 return false;
705 if (stdinChannel.writer && stdinChannel.writer->waitForWrite(0))
706 timer.resetIncrements();
707
708 if ((stdoutChannel.reader && stdoutChannel.reader->waitForReadyRead(0))
709 || (stderrChannel.reader && stderrChannel.reader->waitForReadyRead(0)))
710 return true;
711
712 if (!pid)
713 return false;
714 if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) {
715 bool readyReadEmitted = drainOutputPipes();
716 if (pid)
717 _q_processDied();
718 return readyReadEmitted;
719 }
720
721 Sleep(timer.nextSleepTime());
722 if (timer.hasTimedOut())
723 break;
724 }
725
726 setError(QProcess::Timedout);
727 return false;
728 }
729
waitForBytesWritten(int msecs)730 bool QProcessPrivate::waitForBytesWritten(int msecs)
731 {
732 QIncrementalSleepTimer timer(msecs);
733
734 forever {
735 bool pendingDataInPipe = stdinChannel.writer && stdinChannel.writer->bytesToWrite();
736
737 // If we don't have pending data, and our write buffer is
738 // empty, we fail.
739 if (!pendingDataInPipe && writeBuffer.isEmpty())
740 return false;
741
742 // If we don't have pending data and we do have data in our
743 // write buffer, try to flush that data over to the pipe
744 // writer. Fail on error.
745 if (!pendingDataInPipe) {
746 if (!_q_canWrite())
747 return false;
748 }
749
750 // Wait for the pipe writer to acknowledge that it has
751 // written. This will succeed if either the pipe writer has
752 // already written the data, or if it manages to write data
753 // within the given timeout. If the write buffer was non-empty
754 // and the stdinChannel.writer is now dead, that means _q_canWrite()
755 // destroyed the writer after it successfully wrote the last
756 // batch.
757 if (!stdinChannel.writer || stdinChannel.writer->waitForWrite(0))
758 return true;
759
760 // If we wouldn't write anything, check if we can read stdout.
761 if (stdoutChannel.pipe[0] != INVALID_Q_PIPE
762 && bytesAvailableInChannel(&stdoutChannel) != 0) {
763 tryReadFromChannel(&stdoutChannel);
764 timer.resetIncrements();
765 }
766
767 // Check if we can read stderr.
768 if (stderrChannel.pipe[0] != INVALID_Q_PIPE
769 && bytesAvailableInChannel(&stderrChannel) != 0) {
770 tryReadFromChannel(&stderrChannel);
771 timer.resetIncrements();
772 }
773
774 // Check if the process died while reading.
775 if (!pid)
776 return false;
777
778 // Wait for the process to signal any change in its state,
779 // such as incoming data, or if the process died.
780 if (WaitForSingleObjectEx(pid->hProcess, 0, false) == WAIT_OBJECT_0) {
781 _q_processDied();
782 return false;
783 }
784
785 // Only wait for as long as we've been asked.
786 if (timer.hasTimedOut())
787 break;
788 }
789
790 setError(QProcess::Timedout);
791 return false;
792 }
793
waitForFinished(int msecs)794 bool QProcessPrivate::waitForFinished(int msecs)
795 {
796 #if defined QPROCESS_DEBUG
797 qDebug("QProcessPrivate::waitForFinished(%d)", msecs);
798 #endif
799
800 QIncrementalSleepTimer timer(msecs);
801
802 forever {
803 if (!writeBuffer.isEmpty() && !_q_canWrite())
804 return false;
805 if (stdinChannel.writer && stdinChannel.writer->waitForWrite(0))
806 timer.resetIncrements();
807 if (stdoutChannel.reader && stdoutChannel.reader->waitForReadyRead(0))
808 timer.resetIncrements();
809 if (stderrChannel.reader && stderrChannel.reader->waitForReadyRead(0))
810 timer.resetIncrements();
811
812 if (!pid) {
813 drainOutputPipes();
814 return true;
815 }
816
817 if (WaitForSingleObject(pid->hProcess, timer.nextSleepTime()) == WAIT_OBJECT_0) {
818 drainOutputPipes();
819 if (pid)
820 _q_processDied();
821 return true;
822 }
823
824 if (timer.hasTimedOut())
825 break;
826 }
827
828 setError(QProcess::Timedout);
829 return false;
830 }
831
832
findExitCode()833 void QProcessPrivate::findExitCode()
834 {
835 DWORD theExitCode;
836 Q_ASSERT(pid);
837 if (GetExitCodeProcess(pid->hProcess, &theExitCode)) {
838 exitCode = theExitCode;
839 crashed = (exitCode == 0xf291 // our magic number, see killProcess
840 || (theExitCode >= 0x80000000 && theExitCode < 0xD0000000));
841 }
842 }
843
flushPipeWriter()844 void QProcessPrivate::flushPipeWriter()
845 {
846 if (stdinChannel.writer && stdinChannel.writer->bytesToWrite() > 0)
847 stdinChannel.writer->waitForWrite(ULONG_MAX);
848 }
849
pipeWriterBytesToWrite() const850 qint64 QProcessPrivate::pipeWriterBytesToWrite() const
851 {
852 return stdinChannel.writer ? stdinChannel.writer->bytesToWrite() : qint64(0);
853 }
854
writeToStdin()855 bool QProcessPrivate::writeToStdin()
856 {
857 Q_Q(QProcess);
858
859 if (!stdinChannel.writer) {
860 stdinChannel.writer = new QWindowsPipeWriter(stdinChannel.pipe[1], q);
861 QObject::connect(stdinChannel.writer, &QWindowsPipeWriter::bytesWritten,
862 q, &QProcess::bytesWritten);
863 QObjectPrivate::connect(stdinChannel.writer, &QWindowsPipeWriter::canWrite,
864 this, &QProcessPrivate::_q_canWrite);
865 } else {
866 if (stdinChannel.writer->isWriteOperationActive())
867 return true;
868 }
869
870 stdinChannel.writer->write(writeBuffer.read());
871 return true;
872 }
873
874 // Use ShellExecuteEx() to trigger an UAC prompt when CreateProcess()fails
875 // with ERROR_ELEVATION_REQUIRED.
startDetachedUacPrompt(const QString & programIn,const QStringList & arguments,const QString & nativeArguments,const QString & workingDir,qint64 * pid)876 static bool startDetachedUacPrompt(const QString &programIn, const QStringList &arguments,
877 const QString &nativeArguments,
878 const QString &workingDir, qint64 *pid)
879 {
880 typedef BOOL (WINAPI *ShellExecuteExType)(SHELLEXECUTEINFOW *);
881
882 static const ShellExecuteExType shellExecuteEx = // XP ServicePack 1 onwards.
883 reinterpret_cast<ShellExecuteExType>(QSystemLibrary::resolve(QLatin1String("shell32"),
884 "ShellExecuteExW"));
885 if (!shellExecuteEx)
886 return false;
887
888 const QString args = qt_create_commandline(QString(), // needs arguments only
889 arguments, nativeArguments);
890 SHELLEXECUTEINFOW shellExecuteExInfo;
891 memset(&shellExecuteExInfo, 0, sizeof(SHELLEXECUTEINFOW));
892 shellExecuteExInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
893 shellExecuteExInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE | SEE_MASK_FLAG_NO_UI | SEE_MASK_CLASSNAME;
894 shellExecuteExInfo.lpClass = L"exefile";
895 shellExecuteExInfo.lpVerb = L"runas";
896 const QString program = QDir::toNativeSeparators(programIn);
897 shellExecuteExInfo.lpFile = reinterpret_cast<LPCWSTR>(program.utf16());
898 if (!args.isEmpty())
899 shellExecuteExInfo.lpParameters = reinterpret_cast<LPCWSTR>(args.utf16());
900 if (!workingDir.isEmpty())
901 shellExecuteExInfo.lpDirectory = reinterpret_cast<LPCWSTR>(workingDir.utf16());
902 shellExecuteExInfo.nShow = SW_SHOWNORMAL;
903
904 if (!shellExecuteEx(&shellExecuteExInfo))
905 return false;
906 if (pid)
907 *pid = qint64(GetProcessId(shellExecuteExInfo.hProcess));
908 CloseHandle(shellExecuteExInfo.hProcess);
909 return true;
910 }
911
pipeOrStdHandle(Q_PIPE pipe,DWORD handleNumber)912 static Q_PIPE pipeOrStdHandle(Q_PIPE pipe, DWORD handleNumber)
913 {
914 return pipe != INVALID_Q_PIPE ? pipe : GetStdHandle(handleNumber);
915 }
916
startDetached(qint64 * pid)917 bool QProcessPrivate::startDetached(qint64 *pid)
918 {
919 static const DWORD errorElevationRequired = 740;
920
921 if ((stdinChannel.type == Channel::Redirect && !openChannel(stdinChannel))
922 || (stdoutChannel.type == Channel::Redirect && !openChannel(stdoutChannel))
923 || (stderrChannel.type == Channel::Redirect && !openChannel(stderrChannel))) {
924 closeChannel(&stdinChannel);
925 closeChannel(&stdoutChannel);
926 closeChannel(&stderrChannel);
927 return false;
928 }
929
930 QString args = qt_create_commandline(program, arguments, nativeArguments);
931 bool success = false;
932 PROCESS_INFORMATION pinfo;
933
934 void *envPtr = nullptr;
935 QByteArray envlist;
936 if (environment.d.constData()) {
937 envlist = qt_create_environment(environment.d.constData()->vars);
938 envPtr = envlist.data();
939 }
940
941 DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
942 dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
943 STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
944 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
945 (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
946 0, 0, 0,
947 STARTF_USESTDHANDLES,
948 0, 0, 0,
949 pipeOrStdHandle(stdinChannel.pipe[0], STD_INPUT_HANDLE),
950 pipeOrStdHandle(stdoutChannel.pipe[1], STD_OUTPUT_HANDLE),
951 pipeOrStdHandle(stderrChannel.pipe[1], STD_ERROR_HANDLE)
952 };
953
954 QProcess::CreateProcessArguments cpargs = {
955 nullptr, reinterpret_cast<wchar_t *>(const_cast<ushort *>(args.utf16())),
956 nullptr, nullptr, true, dwCreationFlags, envPtr,
957 workingDirectory.isEmpty()
958 ? nullptr : reinterpret_cast<const wchar_t *>(workingDirectory.utf16()),
959 &startupInfo, &pinfo
960 };
961 success = callCreateProcess(&cpargs);
962
963 if (success) {
964 CloseHandle(pinfo.hThread);
965 CloseHandle(pinfo.hProcess);
966 if (pid)
967 *pid = pinfo.dwProcessId;
968 } else if (GetLastError() == errorElevationRequired) {
969 if (envPtr)
970 qWarning("QProcess: custom environment will be ignored for detached elevated process.");
971 if (!stdinChannel.file.isEmpty() || !stdoutChannel.file.isEmpty()
972 || !stderrChannel.file.isEmpty()) {
973 qWarning("QProcess: file redirection is unsupported for detached elevated processes.");
974 }
975 success = startDetachedUacPrompt(program, arguments, nativeArguments,
976 workingDirectory, pid);
977 }
978
979 closeChannel(&stdinChannel);
980 closeChannel(&stdoutChannel);
981 closeChannel(&stderrChannel);
982 return success;
983 }
984
985 #endif // QT_CONFIG(process)
986
987 QT_END_NAMESPACE
988