1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // Copyright (C) 2022 Intel Corporation. |
3 | // Copyright (C) 2021 Alex Trotsenko. |
4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
5 | |
6 | //#define QPROCESS_DEBUG |
7 | #include "qdebug.h" |
8 | #include <private/qdebug_p.h> |
9 | #include "qplatformdefs.h" |
10 | |
11 | #include "qprocess.h" |
12 | #include "qprocess_p.h" |
13 | #include "qstandardpaths.h" |
14 | #include "private/qcore_unix_p.h" |
15 | #include "private/qlocking_p.h" |
16 | |
17 | #ifdef Q_OS_DARWIN |
18 | #include <private/qcore_mac_p.h> |
19 | #endif |
20 | |
21 | #include <private/qcoreapplication_p.h> |
22 | #include <private/qthread_p.h> |
23 | #include <qfile.h> |
24 | #include <qfileinfo.h> |
25 | #include <qdir.h> |
26 | #include <qlist.h> |
27 | #include <qmutex.h> |
28 | #include <qsocketnotifier.h> |
29 | #include <qthread.h> |
30 | |
31 | #ifdef Q_OS_QNX |
32 | # include <sys/neutrino.h> |
33 | #endif |
34 | |
35 | #include <errno.h> |
36 | #include <limits.h> |
37 | #include <stdlib.h> |
38 | #include <string.h> |
39 | #include <sys/resource.h> |
40 | #include <unistd.h> |
41 | |
42 | #if __has_include(<linux/close_range.h>) |
43 | // FreeBSD's is in <unistd.h> |
44 | # include <linux/close_range.h> |
45 | #endif |
46 | |
47 | #if QT_CONFIG(process) |
48 | #include <forkfd.h> |
49 | #endif |
50 | |
51 | #ifndef O_PATH |
52 | # define O_PATH 0 |
53 | #endif |
54 | |
55 | #ifdef Q_OS_FREEBSD |
56 | __attribute__((weak)) |
57 | #endif |
58 | extern char **environ; |
59 | |
60 | QT_BEGIN_NAMESPACE |
61 | |
62 | using namespace Qt::StringLiterals; |
63 | |
64 | namespace { |
65 | struct PThreadCancelGuard |
66 | { |
67 | #if defined(PTHREAD_CANCEL_DISABLE) |
68 | int oldstate; |
69 | PThreadCancelGuard() noexcept(false) |
70 | { |
71 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, oldstate: &oldstate); |
72 | } |
73 | ~PThreadCancelGuard() noexcept(false) |
74 | { |
75 | reenable(); |
76 | } |
77 | void reenable() noexcept(false) |
78 | { |
79 | // this doesn't touch errno |
80 | pthread_setcancelstate(state: oldstate, oldstate: nullptr); |
81 | } |
82 | #endif |
83 | }; |
84 | } |
85 | |
86 | #if !defined(Q_OS_DARWIN) |
87 | |
88 | QProcessEnvironment QProcessEnvironment::systemEnvironment() |
89 | { |
90 | QProcessEnvironment env; |
91 | const char *entry; |
92 | for (int count = 0; (entry = environ[count]); ++count) { |
93 | const char *equal = strchr(s: entry, c: '='); |
94 | if (!equal) |
95 | continue; |
96 | |
97 | QByteArray name(entry, equal - entry); |
98 | QByteArray value(equal + 1); |
99 | env.d->vars.insert(key: QProcessEnvironmentPrivate::Key(name), |
100 | value: QProcessEnvironmentPrivate::Value(value)); |
101 | } |
102 | return env; |
103 | } |
104 | |
105 | #endif // !defined(Q_OS_DARWIN) |
106 | |
107 | #if QT_CONFIG(process) |
108 | |
109 | namespace QtVforkSafe { |
110 | // Certain libc functions we need to call in the child process scenario aren't |
111 | // safe under vfork() because they do more than just place the system call to |
112 | // the kernel and set errno on return. For those, we'll create a function |
113 | // pointer like: |
114 | // static constexpr auto foobar = __libc_foobar; |
115 | // while for all other OSes, it'll be |
116 | // using ::foobar; |
117 | // allowing the code for the child side of the vfork to simply use |
118 | // QtVforkSafe::foobar(args); |
119 | // |
120 | // Currently known issues are: |
121 | // |
122 | // - FreeBSD's libthr sigaction() wrapper locks a rwlock |
123 | // https://github.com/freebsd/freebsd-src/blob/8dad5ece49479ba6cdcd5bb4c2799bbd61add3e6/lib/libthr/thread/thr_sig.c#L575-L641 |
124 | // - MUSL's sigaction() locks a mutex if the signal is SIGABR |
125 | // https://github.com/bminor/musl/blob/718f363bc2067b6487900eddc9180c84e7739f80/src/signal/sigaction.c#L63-L85 |
126 | // |
127 | // All other functions called in the child side are vfork-safe, provided that |
128 | // PThread cancellation is disabled and Unix signals are blocked. |
129 | #if defined(__MUSL__) |
130 | # define LIBC_PREFIX __libc_ |
131 | #elif defined(Q_OS_FREEBSD) |
132 | // will cause QtCore to link to ELF version "FBSDprivate_1.0" |
133 | # define LIBC_PREFIX _ |
134 | #endif |
135 | |
136 | #ifdef LIBC_PREFIX |
137 | # define CONCAT(x, y) CONCAT2(x, y) |
138 | # define CONCAT2(x, y) x ## y |
139 | # define DECLARE_FUNCTIONS(NAME) \ |
140 | extern decltype(::NAME) CONCAT(LIBC_PREFIX, NAME); \ |
141 | static constexpr auto NAME = std::addressof(CONCAT(LIBC_PREFIX, NAME)); |
142 | #else // LIBC_PREFIX |
143 | # define DECLARE_FUNCTIONS(NAME) using ::NAME; |
144 | #endif // LIBC_PREFIX |
145 | |
146 | extern "C" { |
147 | DECLARE_FUNCTIONS(sigaction) |
148 | } |
149 | |
150 | #undef LIBC_PREFIX |
151 | #undef DECLARE_FUNCTIONS |
152 | |
153 | // similar to qt_ignore_sigpipe() in qcore_unix_p.h, but vfork-safe |
154 | static void change_sigpipe(decltype(SIG_DFL) new_handler) |
155 | { |
156 | struct sigaction sa; |
157 | memset(s: &sa, c: 0, n: sizeof(sa)); |
158 | sa.sa_handler = new_handler; |
159 | sigaction(SIGPIPE, act: &sa, oact: nullptr); |
160 | } |
161 | } // namespace QtVforkSafe |
162 | |
163 | static int opendirfd(QByteArray encodedName) |
164 | { |
165 | // We append "/." to the name to ensure that the directory is actually |
166 | // traversable (i.e., has the +x bit set). This avoids later problems |
167 | // with fchdir(). |
168 | if (encodedName != "/" && !encodedName.endsWith(bv: "/." )) |
169 | encodedName += "/." ; |
170 | return qt_safe_open(pathname: encodedName, QT_OPEN_RDONLY | O_DIRECTORY | O_PATH); |
171 | } |
172 | |
173 | namespace { |
174 | struct AutoPipe |
175 | { |
176 | int pipe[2] = { -1, -1 }; |
177 | AutoPipe(int flags = 0) |
178 | { |
179 | qt_safe_pipe(pipefd: pipe, flags); |
180 | } |
181 | ~AutoPipe() |
182 | { |
183 | for (int fd : pipe) { |
184 | if (fd >= 0) |
185 | qt_safe_close(fd); |
186 | } |
187 | } |
188 | |
189 | explicit operator bool() const { return pipe[0] >= 0; } |
190 | int &operator[](int idx) { return pipe[idx]; } |
191 | int operator[](int idx) const { return pipe[idx]; } |
192 | }; |
193 | |
194 | struct ChildError |
195 | { |
196 | int code; |
197 | char function[12]; |
198 | }; |
199 | |
200 | // Used for argv and envp arguments to execve() |
201 | struct CharPointerList |
202 | { |
203 | std::unique_ptr<char *[]> pointers; |
204 | |
205 | CharPointerList(const QString &argv0, const QStringList &args); |
206 | explicit CharPointerList(const QProcessEnvironmentPrivate *env); |
207 | |
208 | private: |
209 | QByteArray data; |
210 | void updatePointers(qsizetype count); |
211 | }; |
212 | |
213 | struct QProcessPoller |
214 | { |
215 | QProcessPoller(const QProcessPrivate &proc); |
216 | |
217 | int poll(const QDeadlineTimer &deadline); |
218 | |
219 | pollfd &stdinPipe() { return pfds[0]; } |
220 | pollfd &stdoutPipe() { return pfds[1]; } |
221 | pollfd &stderrPipe() { return pfds[2]; } |
222 | pollfd &forkfd() { return pfds[3]; } |
223 | |
224 | enum { n_pfds = 4 }; |
225 | pollfd pfds[n_pfds]; |
226 | }; |
227 | |
228 | QProcessPoller::QProcessPoller(const QProcessPrivate &proc) |
229 | { |
230 | for (int i = 0; i < n_pfds; i++) |
231 | pfds[i] = qt_make_pollfd(fd: -1, POLLIN); |
232 | |
233 | stdoutPipe().fd = proc.stdoutChannel.pipe[0]; |
234 | stderrPipe().fd = proc.stderrChannel.pipe[0]; |
235 | |
236 | if (!proc.writeBuffer.isEmpty()) { |
237 | stdinPipe().fd = proc.stdinChannel.pipe[1]; |
238 | stdinPipe().events = POLLOUT; |
239 | } |
240 | |
241 | forkfd().fd = proc.forkfd; |
242 | } |
243 | |
244 | int QProcessPoller::poll(const QDeadlineTimer &deadline) |
245 | { |
246 | return qt_poll_msecs(fds: pfds, nfds: n_pfds, timeout: deadline.remainingTime()); |
247 | } |
248 | |
249 | CharPointerList::CharPointerList(const QString &program, const QStringList &args) |
250 | { |
251 | qsizetype count = 1 + args.size(); |
252 | pointers.reset(p: new char *[count + 1]); |
253 | pointers[count] = nullptr; |
254 | |
255 | // we abuse the pointer array to store offsets first (QByteArray will |
256 | // reallocate, after all) |
257 | pointers[0] = reinterpret_cast<char *>(0); |
258 | data = QFile::encodeName(fileName: program); |
259 | data += '\0'; |
260 | |
261 | const auto end = args.end(); |
262 | auto it = args.begin(); |
263 | for (qsizetype i = 1; it != end; ++it, ++i) { |
264 | pointers[i] = reinterpret_cast<char *>(data.size()); |
265 | data += QFile::encodeName(fileName: *it); |
266 | data += '\0'; |
267 | } |
268 | |
269 | updatePointers(count); |
270 | } |
271 | |
272 | CharPointerList::CharPointerList(const QProcessEnvironmentPrivate *environment) |
273 | { |
274 | if (!environment) |
275 | return; |
276 | |
277 | const QProcessEnvironmentPrivate::Map &env = environment->vars; |
278 | qsizetype count = env.size(); |
279 | pointers.reset(p: new char *[count + 1]); |
280 | pointers[count] = nullptr; |
281 | |
282 | const auto end = env.end(); |
283 | auto it = env.begin(); |
284 | for (qsizetype i = 0; it != end; ++it, ++i) { |
285 | // we abuse the pointer array to store offsets first (QByteArray will |
286 | // reallocate, after all) |
287 | pointers[i] = reinterpret_cast<char *>(data.size()); |
288 | |
289 | data += it.key(); |
290 | data += '='; |
291 | data += it->bytes(); |
292 | data += '\0'; |
293 | } |
294 | |
295 | updatePointers(count); |
296 | } |
297 | |
298 | void CharPointerList::updatePointers(qsizetype count) |
299 | { |
300 | char *const base = const_cast<char *>(data.constBegin()); |
301 | for (qsizetype i = 0; i < count; ++i) |
302 | pointers[i] = base + qptrdiff(pointers[i]); |
303 | } |
304 | } // anonymous namespace |
305 | |
306 | static bool qt_pollfd_check(const pollfd &pfd, short revents) |
307 | { |
308 | return pfd.fd >= 0 && (pfd.revents & (revents | POLLHUP | POLLERR | POLLNVAL)) != 0; |
309 | } |
310 | |
311 | static int qt_create_pipe(int *pipe) |
312 | { |
313 | if (pipe[0] != -1) |
314 | qt_safe_close(fd: pipe[0]); |
315 | if (pipe[1] != -1) |
316 | qt_safe_close(fd: pipe[1]); |
317 | int pipe_ret = qt_safe_pipe(pipefd: pipe); |
318 | if (pipe_ret != 0) { |
319 | qErrnoWarning(msg: "QProcessPrivate::createPipe: Cannot create pipe %p" , pipe); |
320 | } |
321 | return pipe_ret; |
322 | } |
323 | |
324 | void QProcessPrivate::destroyPipe(int *pipe) |
325 | { |
326 | if (pipe[1] != -1) { |
327 | qt_safe_close(fd: pipe[1]); |
328 | pipe[1] = -1; |
329 | } |
330 | if (pipe[0] != -1) { |
331 | qt_safe_close(fd: pipe[0]); |
332 | pipe[0] = -1; |
333 | } |
334 | } |
335 | |
336 | void QProcessPrivate::closeChannel(Channel *channel) |
337 | { |
338 | delete channel->notifier; |
339 | channel->notifier = nullptr; |
340 | |
341 | destroyPipe(pipe: channel->pipe); |
342 | } |
343 | |
344 | void QProcessPrivate::cleanup() |
345 | { |
346 | q_func()->setProcessState(QProcess::NotRunning); |
347 | |
348 | closeChannels(); |
349 | delete stateNotifier; |
350 | stateNotifier = nullptr; |
351 | destroyPipe(pipe: childStartedPipe); |
352 | pid = 0; |
353 | if (forkfd != -1) { |
354 | qt_safe_close(fd: forkfd); |
355 | forkfd = -1; |
356 | } |
357 | } |
358 | |
359 | /* |
360 | Create the pipes to a QProcessPrivate::Channel. |
361 | */ |
362 | bool QProcessPrivate::openChannel(Channel &channel) |
363 | { |
364 | Q_Q(QProcess); |
365 | |
366 | if (channel.type == Channel::Normal) { |
367 | // we're piping this channel to our own process |
368 | if (qt_create_pipe(pipe: channel.pipe) != 0) |
369 | return false; |
370 | |
371 | // create the socket notifiers |
372 | if (threadData.loadRelaxed()->hasEventDispatcher()) { |
373 | if (&channel == &stdinChannel) { |
374 | channel.notifier = new QSocketNotifier(QSocketNotifier::Write, q); |
375 | channel.notifier->setSocket(channel.pipe[1]); |
376 | QObject::connect(sender: channel.notifier, SIGNAL(activated(QSocketDescriptor)), |
377 | receiver: q, SLOT(_q_canWrite())); |
378 | } else { |
379 | channel.notifier = new QSocketNotifier(QSocketNotifier::Read, q); |
380 | channel.notifier->setSocket(channel.pipe[0]); |
381 | const char *receiver; |
382 | if (&channel == &stdoutChannel) |
383 | receiver = SLOT(_q_canReadStandardOutput()); |
384 | else |
385 | receiver = SLOT(_q_canReadStandardError()); |
386 | QObject::connect(sender: channel.notifier, SIGNAL(activated(QSocketDescriptor)), |
387 | receiver: q, member: receiver); |
388 | } |
389 | } |
390 | |
391 | return true; |
392 | } else if (channel.type == Channel::Redirect) { |
393 | // we're redirecting the channel to/from a file |
394 | QByteArray fname = QFile::encodeName(fileName: channel.file); |
395 | |
396 | if (&channel == &stdinChannel) { |
397 | // try to open in read-only mode |
398 | channel.pipe[1] = -1; |
399 | if ( (channel.pipe[0] = qt_safe_open(pathname: fname, O_RDONLY)) != -1) |
400 | return true; // success |
401 | setErrorAndEmit(error: QProcess::FailedToStart, |
402 | description: QProcess::tr(s: "Could not open input redirection for reading" )); |
403 | } else { |
404 | int mode = O_WRONLY | O_CREAT; |
405 | if (channel.append) |
406 | mode |= O_APPEND; |
407 | else |
408 | mode |= O_TRUNC; |
409 | |
410 | channel.pipe[0] = -1; |
411 | if ( (channel.pipe[1] = qt_safe_open(pathname: fname, flags: mode, mode: 0666)) != -1) |
412 | return true; // success |
413 | |
414 | setErrorAndEmit(error: QProcess::FailedToStart, |
415 | description: QProcess::tr(s: "Could not open input redirection for reading" )); |
416 | } |
417 | cleanup(); |
418 | return false; |
419 | } else { |
420 | Q_ASSERT_X(channel.process, "QProcess::start" , "Internal error" ); |
421 | |
422 | Channel *source; |
423 | Channel *sink; |
424 | |
425 | if (channel.type == Channel::PipeSource) { |
426 | // we are the source |
427 | source = &channel; |
428 | sink = &channel.process->stdinChannel; |
429 | |
430 | Q_ASSERT(source == &stdoutChannel); |
431 | Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); |
432 | } else { |
433 | // we are the sink; |
434 | source = &channel.process->stdoutChannel; |
435 | sink = &channel; |
436 | |
437 | Q_ASSERT(sink == &stdinChannel); |
438 | Q_ASSERT(source->process == this && source->type == Channel::PipeSource); |
439 | } |
440 | |
441 | if (source->pipe[1] != INVALID_Q_PIPE || sink->pipe[0] != INVALID_Q_PIPE) { |
442 | // already created, do nothing |
443 | return true; |
444 | } else { |
445 | Q_ASSERT(source->pipe[0] == INVALID_Q_PIPE && source->pipe[1] == INVALID_Q_PIPE); |
446 | Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE); |
447 | |
448 | Q_PIPE pipe[2] = { -1, -1 }; |
449 | if (qt_create_pipe(pipe) != 0) |
450 | return false; |
451 | sink->pipe[0] = pipe[0]; |
452 | source->pipe[1] = pipe[1]; |
453 | |
454 | return true; |
455 | } |
456 | } |
457 | } |
458 | |
459 | void QProcessPrivate::commitChannels() const |
460 | { |
461 | // copy the stdin socket if asked to (without closing on exec) |
462 | if (stdinChannel.pipe[0] != INVALID_Q_PIPE) |
463 | qt_safe_dup2(oldfd: stdinChannel.pipe[0], STDIN_FILENO, flags: 0); |
464 | |
465 | // copy the stdout and stderr if asked to |
466 | if (stdoutChannel.pipe[1] != INVALID_Q_PIPE) |
467 | qt_safe_dup2(oldfd: stdoutChannel.pipe[1], STDOUT_FILENO, flags: 0); |
468 | if (stderrChannel.pipe[1] != INVALID_Q_PIPE) { |
469 | qt_safe_dup2(oldfd: stderrChannel.pipe[1], STDERR_FILENO, flags: 0); |
470 | } else { |
471 | // merge stdout and stderr if asked to |
472 | if (processChannelMode == QProcess::MergedChannels) |
473 | qt_safe_dup2(STDOUT_FILENO, STDERR_FILENO, flags: 0); |
474 | } |
475 | } |
476 | |
477 | static QString resolveExecutable(const QString &program) |
478 | { |
479 | #ifdef Q_OS_DARWIN |
480 | // allow invoking of .app bundles on the Mac. |
481 | QFileInfo fileInfo(program); |
482 | if (program.endsWith(".app"_L1 ) && fileInfo.isDir()) { |
483 | QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, |
484 | QCFString(fileInfo.absoluteFilePath()), |
485 | kCFURLPOSIXPathStyle, true); |
486 | { |
487 | // CFBundle is not reentrant, since CFBundleCreate might return a reference |
488 | // to a cached bundle object. Protect the bundle calls with a mutex lock. |
489 | Q_CONSTINIT static QBasicMutex cfbundleMutex; |
490 | const auto locker = qt_scoped_lock(cfbundleMutex); |
491 | QCFType<CFBundleRef> bundle = CFBundleCreate(0, url); |
492 | // 'executableURL' can be either relative or absolute ... |
493 | QCFType<CFURLRef> executableURL = CFBundleCopyExecutableURL(bundle); |
494 | // not to depend on caching - make sure it's always absolute. |
495 | url = CFURLCopyAbsoluteURL(executableURL); |
496 | } |
497 | if (url) { |
498 | const QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); |
499 | return QString::fromCFString(str); |
500 | } |
501 | } |
502 | #endif |
503 | |
504 | if (!program.contains(c: u'/')) { |
505 | // findExecutable() returns its argument if it's an absolute path, |
506 | // otherwise it searches $PATH; returns empty if not found (we handle |
507 | // that case much later) |
508 | return QStandardPaths::findExecutable(executableName: program); |
509 | } |
510 | return program; |
511 | } |
512 | |
513 | static int (const QProcessPrivate::UnixExtras *) |
514 | { |
515 | #if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer) |
516 | // ASan writes to global memory, so we mustn't use vfork(). |
517 | return FFD_USE_FORK; |
518 | #endif |
519 | #if defined(Q_OS_LINUX) && !QT_CONFIG(forkfd_pidfd) |
520 | // some broken environments are known to have problems with the new Linux |
521 | // API, so we have a way for users to opt-out during configure time (see |
522 | // QTBUG-86285) |
523 | return FFD_USE_FORK; |
524 | #endif |
525 | #if defined(Q_OS_DARWIN) |
526 | // Using vfork() for startDetached() is causing problems. We don't know |
527 | // why: without the tools to investigate why it happens, we didn't bother. |
528 | return FFD_USE_FORK; |
529 | #endif |
530 | |
531 | if (!unixExtras || !unixExtras->childProcessModifier) |
532 | return 0; // no modifier was supplied |
533 | |
534 | // if a modifier was supplied, use fork() unless the user opts in to |
535 | // vfork() |
536 | auto flags = unixExtras->processParameters.flags; |
537 | if (flags.testFlag(flag: QProcess::UnixProcessFlag::UseVFork)) |
538 | return 0; |
539 | return FFD_USE_FORK; |
540 | } |
541 | |
542 | void QProcessPrivate::startProcess() |
543 | { |
544 | Q_Q(QProcess); |
545 | |
546 | #if defined (QPROCESS_DEBUG) |
547 | qDebug("QProcessPrivate::startProcess()" ); |
548 | #endif |
549 | |
550 | // Initialize pipes |
551 | if (!openChannels()) { |
552 | // openChannel sets the error string |
553 | return; |
554 | } |
555 | if (qt_create_pipe(pipe: childStartedPipe) != 0) { |
556 | setErrorAndEmit(error: QProcess::FailedToStart, description: "pipe: "_L1 + qt_error_string(errno)); |
557 | cleanup(); |
558 | return; |
559 | } |
560 | |
561 | if (threadData.loadRelaxed()->hasEventDispatcher()) { |
562 | // Set up to notify about startup completion (and premature death). |
563 | // Once the process has started successfully, we reconfigure the |
564 | // notifier to watch the fork_fd for expected death. |
565 | stateNotifier = new QSocketNotifier(childStartedPipe[0], |
566 | QSocketNotifier::Read, q); |
567 | QObject::connect(sender: stateNotifier, SIGNAL(activated(QSocketDescriptor)), |
568 | receiver: q, SLOT(_q_startupNotification())); |
569 | } |
570 | |
571 | int workingDirFd = -1; |
572 | if (!workingDirectory.isEmpty()) { |
573 | workingDirFd = opendirfd(encodedName: QFile::encodeName(fileName: workingDirectory)); |
574 | if (workingDirFd == -1) { |
575 | setErrorAndEmit(error: QProcess::FailedToStart, description: "chdir: "_L1 + qt_error_string()); |
576 | cleanup(); |
577 | return; |
578 | } |
579 | } |
580 | |
581 | // Start the process (platform dependent) |
582 | q->setProcessState(QProcess::Starting); |
583 | |
584 | // Prepare the arguments and the environment |
585 | const CharPointerList argv(resolveExecutable(program), arguments); |
586 | const CharPointerList envp(environment.d.constData()); |
587 | |
588 | // Disable PThread cancellation from this point on: we mustn't have it |
589 | // enabled when the child starts running nor while our state could get |
590 | // corrupted if we abruptly exited this function. |
591 | [[maybe_unused]] PThreadCancelGuard cancelGuard; |
592 | |
593 | // Start the child. |
594 | auto execChild1 = [this, workingDirFd, &argv, &envp]() { |
595 | execChild(workingDirectory: workingDirFd, argv: argv.pointers.get(), envp: envp.pointers.get()); |
596 | }; |
597 | auto execChild2 = [](void *lambda) { |
598 | static_cast<decltype(execChild1) *>(lambda)->operator()(); |
599 | return -1; |
600 | }; |
601 | |
602 | int ffdflags = FFD_CLOEXEC | useForkFlags(unixExtras: unixExtras.get()); |
603 | forkfd = ::vforkfd(flags: ffdflags, ppid: &pid, childFn: execChild2, token: &execChild1); |
604 | int lastForkErrno = errno; |
605 | |
606 | if (workingDirFd != -1) |
607 | close(fd: workingDirFd); |
608 | |
609 | if (forkfd == -1) { |
610 | // Cleanup, report error and return |
611 | #if defined (QPROCESS_DEBUG) |
612 | qDebug("fork failed: %ls" , qUtf16Printable(qt_error_string(lastForkErrno))); |
613 | #endif |
614 | q->setProcessState(QProcess::NotRunning); |
615 | setErrorAndEmit(error: QProcess::FailedToStart, |
616 | description: QProcess::tr(s: "Resource error (fork failure): %1" ).arg(a: qt_error_string(errorCode: lastForkErrno))); |
617 | cleanup(); |
618 | return; |
619 | } |
620 | |
621 | Q_ASSERT(pid > 0); |
622 | |
623 | // parent |
624 | // close the ends we don't use and make all pipes non-blocking |
625 | qt_safe_close(fd: childStartedPipe[1]); |
626 | childStartedPipe[1] = -1; |
627 | |
628 | if (stdinChannel.pipe[0] != -1) { |
629 | qt_safe_close(fd: stdinChannel.pipe[0]); |
630 | stdinChannel.pipe[0] = -1; |
631 | } |
632 | |
633 | if (stdinChannel.pipe[1] != -1) |
634 | ::fcntl(fd: stdinChannel.pipe[1], F_SETFL, ::fcntl(fd: stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK); |
635 | |
636 | if (stdoutChannel.pipe[1] != -1) { |
637 | qt_safe_close(fd: stdoutChannel.pipe[1]); |
638 | stdoutChannel.pipe[1] = -1; |
639 | } |
640 | |
641 | if (stdoutChannel.pipe[0] != -1) |
642 | ::fcntl(fd: stdoutChannel.pipe[0], F_SETFL, ::fcntl(fd: stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK); |
643 | |
644 | if (stderrChannel.pipe[1] != -1) { |
645 | qt_safe_close(fd: stderrChannel.pipe[1]); |
646 | stderrChannel.pipe[1] = -1; |
647 | } |
648 | if (stderrChannel.pipe[0] != -1) |
649 | ::fcntl(fd: stderrChannel.pipe[0], F_SETFL, ::fcntl(fd: stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); |
650 | } |
651 | |
652 | // we need an errno number to use to indicate the child process modifier threw, |
653 | // something the regular operations shouldn't set. |
654 | static constexpr int FakeErrnoForThrow = |
655 | #ifdef ECANCELED |
656 | ECANCELED |
657 | #else |
658 | ESHUTDOWN |
659 | #endif |
660 | ; |
661 | |
662 | // See IMPORTANT notice below |
663 | static void applyProcessParameters(const QProcess::UnixProcessParameters ¶ms) |
664 | { |
665 | // Apply Unix signal handler parameters. |
666 | // We don't expect signal() to fail, so we ignore its return value |
667 | bool ignore_sigpipe = params.flags.testFlag(flag: QProcess::UnixProcessFlag::IgnoreSigPipe); |
668 | if (ignore_sigpipe) |
669 | QtVforkSafe::change_sigpipe(SIG_IGN); |
670 | if (params.flags.testFlag(flag: QProcess::UnixProcessFlag::ResetSignalHandlers)) { |
671 | struct sigaction sa = {}; |
672 | sa.sa_handler = SIG_DFL; |
673 | for (int sig = 1; sig < NSIG; ++sig) { |
674 | if (!ignore_sigpipe || sig != SIGPIPE) |
675 | QtVforkSafe::sigaction(sig: sig, act: &sa, oact: nullptr); |
676 | } |
677 | |
678 | // and unmask all signals |
679 | sigset_t set; |
680 | sigemptyset(set: &set); |
681 | sigprocmask(SIG_SETMASK, set: &set, oset: nullptr); |
682 | } |
683 | |
684 | // Close all file descriptors above stderr. |
685 | // This isn't expected to fail, so we ignore close()'s return value. |
686 | if (params.flags.testFlag(flag: QProcess::UnixProcessFlag::CloseFileDescriptors)) { |
687 | int r = -1; |
688 | int fd = qMax(STDERR_FILENO + 1, b: params.lowestFileDescriptorToClose); |
689 | #if QT_CONFIG(close_range) |
690 | // On FreeBSD, this probably won't fail. |
691 | // On Linux, this will fail with ENOSYS before kernel 5.9. |
692 | r = close_range(fd: fd, INT_MAX, flags: 0); |
693 | #endif |
694 | if (r == -1) { |
695 | // We *could* read /dev/fd to find out what file descriptors are |
696 | // open, but we won't. We CANNOT use opendir() here because it |
697 | // allocates memory. Using getdents(2) plus either strtoul() or |
698 | // std::from_chars() would be acceptable. |
699 | int max_fd = INT_MAX; |
700 | if (struct rlimit limit; getrlimit(RLIMIT_NOFILE, rlimits: &limit) == 0) |
701 | max_fd = limit.rlim_cur; |
702 | for ( ; fd < max_fd; ++fd) |
703 | close(fd: fd); |
704 | } |
705 | } |
706 | } |
707 | |
708 | // the noexcept here adds an extra layer of protection |
709 | static const char *(const QProcessPrivate::UnixExtras *) noexcept |
710 | { |
711 | QT_TRY { |
712 | if (unixExtras->childProcessModifier) |
713 | unixExtras->childProcessModifier(); |
714 | } QT_CATCH (...) { |
715 | errno = FakeErrnoForThrow; |
716 | return "throw" ; |
717 | } |
718 | return nullptr; |
719 | } |
720 | |
721 | // this function doesn't return if the execution succeeds |
722 | static const char *(char **argv, char **envp, int workingDirFd, |
723 | const QProcessPrivate::UnixExtras *) noexcept |
724 | { |
725 | // enter the working directory |
726 | if (workingDirFd != -1 && fchdir(fd: workingDirFd) == -1) |
727 | return "fchdir" ; |
728 | |
729 | if (unixExtras) { |
730 | // FIRST we call the user modifier function, before we dropping |
731 | // privileges or closing non-standard file descriptors |
732 | if (const char *what = callChildProcessModifier(unixExtras)) |
733 | return what; |
734 | |
735 | // then we apply our other user-provided parameters |
736 | applyProcessParameters(params: unixExtras->processParameters); |
737 | } |
738 | |
739 | // execute the process |
740 | if (!envp) |
741 | qt_safe_execv(path: argv[0], argv); |
742 | else |
743 | qt_safe_execve(filename: argv[0], argv, envp); |
744 | return "execve" ; |
745 | } |
746 | |
747 | |
748 | // IMPORTANT: |
749 | // |
750 | // This function is called in a vfork() context on some OSes (notably, Linux |
751 | // with forkfd), so it MUST NOT modify any non-local variable because it's |
752 | // still sharing memory with the parent process. |
753 | void QProcessPrivate::execChild(int workingDir, char **argv, char **envp) const noexcept |
754 | { |
755 | QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored |
756 | |
757 | ChildError error = { .code: 0, .function: {} }; // force zeroing of function[8] |
758 | |
759 | // Render channels configuration. |
760 | commitChannels(); |
761 | |
762 | // make sure this fd is closed if execv() succeeds |
763 | qt_safe_close(fd: childStartedPipe[0]); |
764 | |
765 | const char *what = doExecChild(argv, envp, workingDirFd: workingDir, unixExtras: unixExtras.get()); |
766 | strcpy(dest: error.function, src: what); |
767 | |
768 | // notify failure |
769 | // don't use strerror or any other routines that may allocate memory, since |
770 | // some buggy libc versions can deadlock on locked mutexes. |
771 | error.code = errno; |
772 | qt_safe_write(fd: childStartedPipe[1], data: &error, len: sizeof(error)); |
773 | } |
774 | |
775 | bool QProcessPrivate::processStarted(QString *errorMessage) |
776 | { |
777 | Q_Q(QProcess); |
778 | |
779 | ChildError buf; |
780 | ssize_t ret = qt_safe_read(fd: childStartedPipe[0], data: &buf, maxlen: sizeof(buf)); |
781 | |
782 | if (stateNotifier) { |
783 | stateNotifier->setEnabled(false); |
784 | stateNotifier->disconnect(receiver: q); |
785 | } |
786 | qt_safe_close(fd: childStartedPipe[0]); |
787 | childStartedPipe[0] = -1; |
788 | |
789 | #if defined (QPROCESS_DEBUG) |
790 | qDebug("QProcessPrivate::processStarted() == %s" , ret <= 0 ? "true" : "false" ); |
791 | #endif |
792 | |
793 | if (ret <= 0) { // process successfully started |
794 | if (stateNotifier) { |
795 | QObject::connect(sender: stateNotifier, SIGNAL(activated(QSocketDescriptor)), |
796 | receiver: q, SLOT(_q_processDied())); |
797 | stateNotifier->setSocket(forkfd); |
798 | stateNotifier->setEnabled(true); |
799 | } |
800 | if (stdoutChannel.notifier) |
801 | stdoutChannel.notifier->setEnabled(true); |
802 | if (stderrChannel.notifier) |
803 | stderrChannel.notifier->setEnabled(true); |
804 | |
805 | return true; |
806 | } |
807 | |
808 | // did we read an error message? |
809 | if (errorMessage) { |
810 | if (buf.code == FakeErrnoForThrow) |
811 | *errorMessage = QProcess::tr(s: "childProcessModifier() function threw an exception" ); |
812 | else |
813 | *errorMessage = QLatin1StringView(buf.function) + ": "_L1 + qt_error_string(errorCode: buf.code); |
814 | } |
815 | |
816 | return false; |
817 | } |
818 | |
819 | qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const |
820 | { |
821 | Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE); |
822 | int nbytes = 0; |
823 | qint64 available = 0; |
824 | if (::ioctl(fd: channel->pipe[0], FIONREAD, (char *) &nbytes) >= 0) |
825 | available = (qint64) nbytes; |
826 | #if defined (QPROCESS_DEBUG) |
827 | qDebug("QProcessPrivate::bytesAvailableInChannel(%d) == %lld" , int(channel - &stdinChannel), available); |
828 | #endif |
829 | return available; |
830 | } |
831 | |
832 | qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint64 maxlen) |
833 | { |
834 | Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE); |
835 | qint64 bytesRead = qt_safe_read(fd: channel->pipe[0], data, maxlen); |
836 | #if defined QPROCESS_DEBUG |
837 | int save_errno = errno; |
838 | qDebug("QProcessPrivate::readFromChannel(%d, %p \"%s\", %lld) == %lld" , |
839 | int(channel - &stdinChannel), |
840 | data, QtDebugUtils::toPrintable(data, bytesRead, 16).constData(), maxlen, bytesRead); |
841 | errno = save_errno; |
842 | #endif |
843 | if (bytesRead == -1 && errno == EWOULDBLOCK) |
844 | return -2; |
845 | return bytesRead; |
846 | } |
847 | |
848 | /*! \reimp |
849 | */ |
850 | qint64 QProcess::writeData(const char *data, qint64 len) |
851 | { |
852 | Q_D(QProcess); |
853 | |
854 | if (d->stdinChannel.closed) { |
855 | #if defined QPROCESS_DEBUG |
856 | qDebug("QProcess::writeData(%p \"%s\", %lld) == 0 (write channel closing)" , |
857 | data, QtDebugUtils::toPrintable(data, len, 16).constData(), len); |
858 | #endif |
859 | return 0; |
860 | } |
861 | |
862 | d->write(data, size: len); |
863 | if (d->stdinChannel.notifier) |
864 | d->stdinChannel.notifier->setEnabled(true); |
865 | |
866 | #if defined QPROCESS_DEBUG |
867 | qDebug("QProcess::writeData(%p \"%s\", %lld) == %lld (written to buffer)" , |
868 | data, QtDebugUtils::toPrintable(data, len, 16).constData(), len, len); |
869 | #endif |
870 | return len; |
871 | } |
872 | |
873 | bool QProcessPrivate::_q_canWrite() |
874 | { |
875 | if (writeBuffer.isEmpty()) { |
876 | if (stdinChannel.notifier) |
877 | stdinChannel.notifier->setEnabled(false); |
878 | #if defined QPROCESS_DEBUG |
879 | qDebug("QProcessPrivate::canWrite(), not writing anything (empty write buffer)." ); |
880 | #endif |
881 | return false; |
882 | } |
883 | |
884 | const bool writeSucceeded = writeToStdin(); |
885 | |
886 | if (writeBuffer.isEmpty() && stdinChannel.closed) |
887 | closeWriteChannel(); |
888 | else if (stdinChannel.notifier) |
889 | stdinChannel.notifier->setEnabled(!writeBuffer.isEmpty()); |
890 | |
891 | return writeSucceeded; |
892 | } |
893 | |
894 | bool QProcessPrivate::writeToStdin() |
895 | { |
896 | const char *data = writeBuffer.readPointer(); |
897 | const qint64 bytesToWrite = writeBuffer.nextDataBlockSize(); |
898 | |
899 | qint64 written = qt_safe_write_nosignal(fd: stdinChannel.pipe[1], data, len: bytesToWrite); |
900 | #if defined QPROCESS_DEBUG |
901 | qDebug("QProcessPrivate::writeToStdin(), write(%p \"%s\", %lld) == %lld" , data, |
902 | QtDebugUtils::toPrintable(data, bytesToWrite, 16).constData(), bytesToWrite, written); |
903 | if (written == -1) |
904 | qDebug("QProcessPrivate::writeToStdin(), failed to write (%ls)" , qUtf16Printable(qt_error_string(errno))); |
905 | #endif |
906 | if (written == -1) { |
907 | // If the O_NONBLOCK flag is set and If some data can be written without blocking |
908 | // the process, write() will transfer what it can and return the number of bytes written. |
909 | // Otherwise, it will return -1 and set errno to EAGAIN |
910 | if (errno == EAGAIN) |
911 | return true; |
912 | |
913 | closeChannel(channel: &stdinChannel); |
914 | setErrorAndEmit(error: QProcess::WriteError); |
915 | return false; |
916 | } |
917 | writeBuffer.free(bytes: written); |
918 | if (!emittedBytesWritten && written != 0) { |
919 | emittedBytesWritten = true; |
920 | emit q_func()->bytesWritten(bytes: written); |
921 | emittedBytesWritten = false; |
922 | } |
923 | return true; |
924 | } |
925 | |
926 | void QProcessPrivate::terminateProcess() |
927 | { |
928 | #if defined (QPROCESS_DEBUG) |
929 | qDebug("QProcessPrivate::terminateProcess() pid=%jd" , intmax_t(pid)); |
930 | #endif |
931 | if (pid > 0) |
932 | ::kill(pid: pid, SIGTERM); |
933 | } |
934 | |
935 | void QProcessPrivate::killProcess() |
936 | { |
937 | #if defined (QPROCESS_DEBUG) |
938 | qDebug("QProcessPrivate::killProcess() pid=%jd" , intmax_t(pid)); |
939 | #endif |
940 | if (pid > 0) |
941 | ::kill(pid: pid, SIGKILL); |
942 | } |
943 | |
944 | bool QProcessPrivate::waitForStarted(const QDeadlineTimer &deadline) |
945 | { |
946 | const qint64 msecs = deadline.remainingTime(); |
947 | #if defined (QPROCESS_DEBUG) |
948 | qDebug("QProcessPrivate::waitForStarted(%lld) waiting for child to start (fd = %d)" , |
949 | msecs, childStartedPipe[0]); |
950 | #endif |
951 | |
952 | pollfd pfd = qt_make_pollfd(fd: childStartedPipe[0], POLLIN); |
953 | |
954 | if (qt_poll_msecs(fds: &pfd, nfds: 1, timeout: msecs) == 0) { |
955 | setError(error: QProcess::Timedout); |
956 | #if defined (QPROCESS_DEBUG) |
957 | qDebug("QProcessPrivate::waitForStarted(%lld) == false (timed out)" , msecs); |
958 | #endif |
959 | return false; |
960 | } |
961 | |
962 | bool startedEmitted = _q_startupNotification(); |
963 | #if defined (QPROCESS_DEBUG) |
964 | qDebug("QProcessPrivate::waitForStarted() == %s" , startedEmitted ? "true" : "false" ); |
965 | #endif |
966 | return startedEmitted; |
967 | } |
968 | |
969 | bool QProcessPrivate::waitForReadyRead(const QDeadlineTimer &deadline) |
970 | { |
971 | #if defined (QPROCESS_DEBUG) |
972 | qDebug("QProcessPrivate::waitForReadyRead(%lld)" , deadline.remainingTime()); |
973 | #endif |
974 | |
975 | forever { |
976 | QProcessPoller poller(*this); |
977 | |
978 | int ret = poller.poll(deadline); |
979 | |
980 | if (ret < 0) { |
981 | break; |
982 | } |
983 | if (ret == 0) { |
984 | setError(error: QProcess::Timedout); |
985 | return false; |
986 | } |
987 | |
988 | // This calls QProcessPrivate::tryReadFromChannel(), which returns true |
989 | // if we emitted readyRead() signal on the current read channel. |
990 | bool readyReadEmitted = false; |
991 | if (qt_pollfd_check(pfd: poller.stdoutPipe(), POLLIN) && _q_canReadStandardOutput()) |
992 | readyReadEmitted = true; |
993 | if (qt_pollfd_check(pfd: poller.stderrPipe(), POLLIN) && _q_canReadStandardError()) |
994 | readyReadEmitted = true; |
995 | |
996 | if (readyReadEmitted) |
997 | return true; |
998 | |
999 | if (qt_pollfd_check(pfd: poller.stdinPipe(), POLLOUT)) |
1000 | _q_canWrite(); |
1001 | |
1002 | // Signals triggered by I/O may have stopped this process: |
1003 | if (processState == QProcess::NotRunning) |
1004 | return false; |
1005 | |
1006 | // We do this after checking the pipes, so we cannot reach it as long |
1007 | // as there is any data left to be read from an already dead process. |
1008 | if (qt_pollfd_check(pfd: poller.forkfd(), POLLIN)) { |
1009 | processFinished(); |
1010 | return false; |
1011 | } |
1012 | } |
1013 | return false; |
1014 | } |
1015 | |
1016 | bool QProcessPrivate::waitForBytesWritten(const QDeadlineTimer &deadline) |
1017 | { |
1018 | #if defined (QPROCESS_DEBUG) |
1019 | qDebug("QProcessPrivate::waitForBytesWritten(%lld)" , deadline.remainingTime()); |
1020 | #endif |
1021 | |
1022 | while (!writeBuffer.isEmpty()) { |
1023 | QProcessPoller poller(*this); |
1024 | |
1025 | int ret = poller.poll(deadline); |
1026 | |
1027 | if (ret < 0) { |
1028 | break; |
1029 | } |
1030 | |
1031 | if (ret == 0) { |
1032 | setError(error: QProcess::Timedout); |
1033 | return false; |
1034 | } |
1035 | |
1036 | if (qt_pollfd_check(pfd: poller.stdinPipe(), POLLOUT)) |
1037 | return _q_canWrite(); |
1038 | |
1039 | if (qt_pollfd_check(pfd: poller.stdoutPipe(), POLLIN)) |
1040 | _q_canReadStandardOutput(); |
1041 | |
1042 | if (qt_pollfd_check(pfd: poller.stderrPipe(), POLLIN)) |
1043 | _q_canReadStandardError(); |
1044 | |
1045 | // Signals triggered by I/O may have stopped this process: |
1046 | if (processState == QProcess::NotRunning) |
1047 | return false; |
1048 | |
1049 | if (qt_pollfd_check(pfd: poller.forkfd(), POLLIN)) { |
1050 | processFinished(); |
1051 | return false; |
1052 | } |
1053 | } |
1054 | |
1055 | return false; |
1056 | } |
1057 | |
1058 | bool QProcessPrivate::waitForFinished(const QDeadlineTimer &deadline) |
1059 | { |
1060 | #if defined (QPROCESS_DEBUG) |
1061 | qDebug("QProcessPrivate::waitForFinished(%lld)" , deadline.remainingTime()); |
1062 | #endif |
1063 | |
1064 | forever { |
1065 | QProcessPoller poller(*this); |
1066 | |
1067 | int ret = poller.poll(deadline); |
1068 | |
1069 | if (ret < 0) { |
1070 | break; |
1071 | } |
1072 | if (ret == 0) { |
1073 | setError(error: QProcess::Timedout); |
1074 | return false; |
1075 | } |
1076 | |
1077 | if (qt_pollfd_check(pfd: poller.stdinPipe(), POLLOUT)) |
1078 | _q_canWrite(); |
1079 | |
1080 | if (qt_pollfd_check(pfd: poller.stdoutPipe(), POLLIN)) |
1081 | _q_canReadStandardOutput(); |
1082 | |
1083 | if (qt_pollfd_check(pfd: poller.stderrPipe(), POLLIN)) |
1084 | _q_canReadStandardError(); |
1085 | |
1086 | // Signals triggered by I/O may have stopped this process: |
1087 | if (processState == QProcess::NotRunning) |
1088 | return true; |
1089 | |
1090 | if (qt_pollfd_check(pfd: poller.forkfd(), POLLIN)) { |
1091 | processFinished(); |
1092 | return true; |
1093 | } |
1094 | } |
1095 | return false; |
1096 | } |
1097 | |
1098 | void QProcessPrivate::waitForDeadChild() |
1099 | { |
1100 | Q_ASSERT(forkfd != -1); |
1101 | |
1102 | // read the process information from our fd |
1103 | forkfd_info info; |
1104 | int ret; |
1105 | EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr)); |
1106 | |
1107 | exitCode = info.status; |
1108 | exitStatus = info.code == CLD_EXITED ? QProcess::NormalExit : QProcess::CrashExit; |
1109 | |
1110 | delete stateNotifier; |
1111 | stateNotifier = nullptr; |
1112 | |
1113 | EINTR_LOOP(ret, forkfd_close(forkfd)); |
1114 | forkfd = -1; // Child is dead, don't try to kill it anymore |
1115 | |
1116 | #if defined QPROCESS_DEBUG |
1117 | qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode" |
1118 | << exitCode << ", crashed?" << (info.code != CLD_EXITED); |
1119 | #endif |
1120 | } |
1121 | |
1122 | bool QProcessPrivate::startDetached(qint64 *pid) |
1123 | { |
1124 | |
1125 | #ifdef PIPE_BUF |
1126 | static_assert(PIPE_BUF >= sizeof(ChildError)); |
1127 | #else |
1128 | static_assert(_POSIX_PIPE_BUF >= sizeof(ChildError)); |
1129 | #endif |
1130 | ChildError childStatus = { .code: 0, .function: {} }; |
1131 | |
1132 | AutoPipe startedPipe, pidPipe; |
1133 | if (!startedPipe || !pidPipe) { |
1134 | setErrorAndEmit(error: QProcess::FailedToStart, description: "pipe: "_L1 + qt_error_string(errno)); |
1135 | return false; |
1136 | } |
1137 | |
1138 | if (!openChannelsForDetached()) { |
1139 | // openChannel sets the error string |
1140 | closeChannels(); |
1141 | return false; |
1142 | } |
1143 | |
1144 | int workingDirFd = -1; |
1145 | if (!workingDirectory.isEmpty()) { |
1146 | workingDirFd = opendirfd(encodedName: QFile::encodeName(fileName: workingDirectory)); |
1147 | if (workingDirFd == -1) { |
1148 | setErrorAndEmit(error: QProcess::FailedToStart, description: "chdir: "_L1 + qt_error_string(errno)); |
1149 | return false; |
1150 | } |
1151 | } |
1152 | |
1153 | const CharPointerList argv(resolveExecutable(program), arguments); |
1154 | const CharPointerList envp(environment.d.constData()); |
1155 | |
1156 | // see startProcess() for more information |
1157 | [[maybe_unused]] PThreadCancelGuard cancelGuard; |
1158 | |
1159 | auto doFork = [this]() { |
1160 | if (useForkFlags(unixExtras: unixExtras.get())) |
1161 | return fork; |
1162 | QT_IGNORE_DEPRECATIONS(return vfork;) |
1163 | }(); |
1164 | pid_t childPid = doFork(); |
1165 | if (childPid == 0) { |
1166 | QtVforkSafe::change_sigpipe(SIG_DFL); // reset the signal that we ignored |
1167 | ::setsid(); |
1168 | |
1169 | qt_safe_close(fd: startedPipe[0]); |
1170 | qt_safe_close(fd: pidPipe[0]); |
1171 | |
1172 | auto reportFailed = [&](const char *function) { |
1173 | childStatus.code = errno; |
1174 | strcpy(dest: childStatus.function, src: function); |
1175 | qt_safe_write(fd: startedPipe[1], data: &childStatus, len: sizeof(childStatus)); |
1176 | ::_exit(status: 1); |
1177 | }; |
1178 | |
1179 | pid_t doubleForkPid = doFork(); |
1180 | if (doubleForkPid == 0) { |
1181 | // Render channels configuration. |
1182 | commitChannels(); |
1183 | |
1184 | reportFailed(doExecChild(argv: argv.pointers.get(), envp: envp.pointers.get(), workingDirFd, |
1185 | unixExtras: unixExtras.get())); |
1186 | } else if (doubleForkPid == -1) { |
1187 | reportFailed("fork: " ); |
1188 | } |
1189 | |
1190 | // success |
1191 | qt_safe_write(fd: pidPipe[1], data: &doubleForkPid, len: sizeof(pid_t)); |
1192 | ::_exit(status: 1); |
1193 | } |
1194 | |
1195 | int savedErrno = errno; |
1196 | closeChannels(); |
1197 | if (workingDirFd != -1) |
1198 | close(fd: workingDirFd); |
1199 | |
1200 | if (childPid == -1) { |
1201 | setErrorAndEmit(error: QProcess::FailedToStart, description: "fork: "_L1 + qt_error_string(errorCode: savedErrno)); |
1202 | return false; |
1203 | } |
1204 | |
1205 | // close the writing ends of the pipes so we can properly get EOFs |
1206 | qt_safe_close(fd: pidPipe[1]); |
1207 | qt_safe_close(fd: startedPipe[1]); |
1208 | pidPipe[1] = startedPipe[1] = -1; |
1209 | |
1210 | // This read() will block until we're cleared to proceed. If it returns 0 |
1211 | // (EOF), it means the direct child has exited and the grandchild |
1212 | // successfully execve()'d the target process. If it returns any positive |
1213 | // result, it means one of the two children wrote an error result. Negative |
1214 | // values should not happen. |
1215 | ssize_t startResult = qt_safe_read(fd: startedPipe[0], data: &childStatus, maxlen: sizeof(childStatus)); |
1216 | |
1217 | // reap the intermediate child |
1218 | int result; |
1219 | qt_safe_waitpid(pid: childPid, status: &result, options: 0); |
1220 | |
1221 | bool success = (startResult == 0); // nothing written -> no error |
1222 | if (success && pid) { |
1223 | pid_t actualPid; |
1224 | if (qt_safe_read(fd: pidPipe[0], data: &actualPid, maxlen: sizeof(pid_t)) != sizeof(pid_t)) |
1225 | actualPid = 0; // this shouldn't happen! |
1226 | *pid = actualPid; |
1227 | } else if (!success) { |
1228 | if (pid) |
1229 | *pid = -1; |
1230 | QString msg; |
1231 | if (startResult == sizeof(childStatus)) |
1232 | msg = QLatin1StringView(childStatus.function) + qt_error_string(errorCode: childStatus.code); |
1233 | setErrorAndEmit(error: QProcess::FailedToStart, description: msg); |
1234 | } |
1235 | return success; |
1236 | } |
1237 | |
1238 | #endif // QT_CONFIG(process) |
1239 | |
1240 | QT_END_NAMESPACE |
1241 | |