QtPass 1.6.0
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
executor.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
3#include "executor.h"
4#include <QCoreApplication>
5#include <QDir>
6#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
7#include <QTextCodec>
8#endif
9#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
10#include <QStringDecoder>
11#endif
12#include <utility>
13
14#ifdef QT_DEBUG
15#include "debughelper.h"
16#endif
17
22Executor::Executor(QObject *parent) : QObject(parent), running(false) {
23 connect(&m_process,
24 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
25 &QProcess::finished),
26 this,
27 static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
28 &Executor::finished));
29 connect(&m_process, &QProcess::started, this, &Executor::starting);
30}
31
38void Executor::startProcess(const QString &app, const QStringList &args) {
39 if (app.startsWith("wsl ")) {
40 QStringList wslArgs = args;
41 QString actualApp = app;
42 wslArgs.prepend(actualApp.remove(0, 4));
43 m_process.start("wsl", wslArgs);
44 } else {
45 m_process.start(app, args);
46 }
47}
48
56void Executor::startProcessBlocking(QProcess &internal, const QString &app,
57 const QStringList &args) {
58 if (app.startsWith("wsl ")) {
59 QStringList wslArgs = args;
60 QString actualApp = app;
61 wslArgs.prepend(actualApp.remove(0, 4));
62 internal.start("wsl", wslArgs);
63 } else {
64 internal.start(app, args);
65 }
66}
67
71void Executor::executeNext() {
72 if (!running) {
73 if (!m_execQueue.isEmpty()) {
74 const execQueueItem &i = m_execQueue.head();
75 running = true;
76 if (!i.workingDir.isEmpty()) {
77 m_process.setWorkingDirectory(i.workingDir);
78 }
79 startProcess(i.app, i.args);
80 if (!i.input.isEmpty()) {
81 if (!m_process.waitForStarted(-1)) {
82#ifdef QT_DEBUG
83 dbg() << "Process failed to start:" << i.id << " " << i.app;
84#endif
85 m_process.closeWriteChannel();
86 running = false;
87 m_execQueue.dequeue();
88 executeNext();
89 return;
90 }
91 QByteArray data = i.input.toUtf8();
92 if (m_process.write(data) != data.length()) {
93#ifdef QT_DEBUG
94 dbg() << "Not all data written to process:" << i.id << " " << i.app;
95#endif
96 }
97 }
98 m_process.closeWriteChannel();
99 }
100 }
101}
102
111void Executor::execute(int id, const QString &app, const QStringList &args,
112 bool readStdout, bool readStderr) {
113 execute(id, QString(), app, args, QString(), readStdout, readStderr);
114}
115
125void Executor::execute(int id, const QString &workDir, const QString &app,
126 const QStringList &args, bool readStdout,
127 bool readStderr) {
128 execute(id, workDir, app, args, QString(), readStdout, readStderr);
129}
130
140void Executor::execute(int id, const QString &app, const QStringList &args,
141 QString input, bool readStdout, bool readStderr) {
142 execute(id, QString(), app, args, std::move(input), readStdout, readStderr);
143}
144
156void Executor::execute(int id, const QString &workDir, const QString &app,
157 const QStringList &args, QString input, bool readStdout,
158 bool readStderr) {
159 // Happens a lot if e.g. git binary is not set.
160 // This will result in bogus "QProcess::FailedToStart" messages,
161 // also hiding legitimate errors from the gpg commands.
162 if (app.isEmpty()) {
163#ifdef QT_DEBUG
164 dbg() << "Trying to execute nothing...";
165#endif
166 return;
167 }
168 QString appPath = app;
169 if (!appPath.startsWith("wsl ")) {
170 appPath =
171 QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
172 }
173 m_execQueue.push_back(
174 {id, appPath, args, std::move(input), readStdout, readStderr, workDir});
175 executeNext();
176}
177
188static auto decodeAssumingUtf8(const QByteArray &in) -> QString {
189#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
190 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
191 QTextCodec::ConverterState state;
192 QString out = codec->toUnicode(in.constData(), in.size(), &state);
193 if (state.invalidChars == 0) {
194 return out;
195 }
196 codec = QTextCodec::codecForUtfText(in);
197 return codec->toUnicode(in);
198#else
199 auto converter = QStringDecoder(QStringDecoder::Utf8);
200 QString out = converter(in);
201 if (!converter.hasError()) {
202 return out;
203 }
204 // Fallback if UTF-8 decoding failed - try system encoding
205 auto fallback = QStringDecoder(QStringDecoder::System);
206 return fallback(in);
207#endif
208}
209
223auto Executor::executeBlocking(QString app, const QStringList &args,
224 const QString &input, QString *process_out,
225 QString *process_err) -> int {
226 QProcess internal;
227 startProcessBlocking(internal, app, args);
228 if (!internal.waitForStarted(-1)) {
229#ifdef QT_DEBUG
230 dbg() << "Process failed to start:" << app;
231#endif
232 return -1;
233 }
234 if (!input.isEmpty()) {
235 QByteArray data = input.toUtf8();
236 if (internal.write(data) != data.length()) {
237#ifdef QT_DEBUG
238 dbg() << "Not all input written:" << app;
239#endif
240 }
241 internal.closeWriteChannel();
242 }
243 internal.waitForFinished(-1);
244 if (internal.exitStatus() == QProcess::NormalExit) {
245 QString pout = decodeAssumingUtf8(internal.readAllStandardOutput());
246 QString perr = decodeAssumingUtf8(internal.readAllStandardError());
247 if (process_out != nullptr) {
248 *process_out = pout;
249 }
250 if (process_err != nullptr) {
251 *process_err = perr;
252 }
253 return internal.exitCode();
254 }
255 // Process failed to start or crashed; return -1 to indicate error.
256 // The calling code checks for non-zero exit codes for error handling.
257 return -1;
258}
259
268auto Executor::executeBlocking(QString app, const QStringList &args,
269 QString *process_out, QString *process_err)
270 -> int {
271 return executeBlocking(std::move(app), args, QString(), process_out,
272 process_err);
273}
274
284auto Executor::executeBlocking(const QStringList &env, QString app,
285 const QStringList &args, QString *process_out,
286 QString *process_err) -> int {
287 QProcess process;
288 QProcessEnvironment penv;
289 for (const QString &var : env) {
290 int idx = var.indexOf('=');
291 if (idx > 0) {
292 penv.insert(var.left(idx), var.mid(idx + 1));
293 }
294 }
295 process.setProcessEnvironment(penv);
296 startProcessBlocking(process, app, args);
297 if (!process.waitForStarted(-1)) {
298#ifdef QT_DEBUG
299 dbg() << "Process failed to start:" << app;
300#endif
301 return -1;
302 }
303 process.waitForFinished(-1);
304 if (process.exitStatus() != QProcess::NormalExit) {
305 return -1;
306 }
307 if (process_out) {
308 *process_out = decodeAssumingUtf8(process.readAllStandardOutput());
309 }
310 if (process_err) {
311 *process_err = decodeAssumingUtf8(process.readAllStandardError());
312 }
313 return process.exitCode();
314}
315
321void Executor::setEnvironment(const QStringList &env) {
322 m_process.setEnvironment(env);
323}
324
331auto Executor::cancelNext() -> int {
332 if (running || m_execQueue.isEmpty()) {
333 return -1; // Return -1 to indicate no process was cancelled
334 // (queue empty or currently executing).
335 }
336 return m_execQueue.dequeue().id;
337}
338
344void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
345 execQueueItem i = m_execQueue.dequeue();
346 running = false;
347 if (exitStatus == QProcess::NormalExit) {
348 QString output;
349 QString err;
350 if (i.readStdout) {
351 output = decodeAssumingUtf8(m_process.readAllStandardOutput());
352 }
353 if (i.readStderr || exitCode != 0) {
354 err = decodeAssumingUtf8(m_process.readAllStandardError());
355 if (exitCode != 0) {
356#ifdef QT_DEBUG
357 dbg() << exitCode << err;
358#endif
359 }
360 }
361 emit finished(i.id, exitCode, output, err);
362 }
363 // else: emit crashed with ID, which may give a chance to recover ?
364 executeNext();
365}
static auto executeBlocking(QString app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Executor::executeBlocking blocking version of the executor, takes input and presents it as stdin.
Definition executor.cpp:223
void execute(int id, const QString &app, const QStringList &args, bool readStdout, bool readStderr=true)
Executor::execute execute an app.
Definition executor.cpp:111
Executor(QObject *parent=nullptr)
Executor::Executor executes external applications.
Definition executor.cpp:22
void setEnvironment(const QStringList &env)
Executor::setEnvironment set environment variables for executor processes.
Definition executor.cpp:321
void starting()
starting signal that is emited when process starts
auto cancelNext() -> int
Executor::cancelNext cancels execution of first process in queue if it's not already running.
Definition executor.cpp:331
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
Definition debughelper.h:21