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