• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24084061007

07 Apr 2026 01:31PM UTC coverage: 20.115% (+0.3%) from 19.861%
24084061007

push

github

web-flow
fix: kill stale GPG agents before key generation (#895)

* fix: kill stale GPG agents before key generation (#815)

Before generating GPG keys, kill any stale gpg-agent processes that
might be holding locks on the key database. This helps prevent
'timeout while waiting on locked key database' errors.

The issue occurs when another GPG process (possibly from another
application) holds a lock on the key database, causing key generation
to fail with timeout errors.

* test: add test for gpgconf --kill gpg-agent

* fix: use gpgconf directly instead of gpg with args

* fix: derive gpgconf path from gpg executable directory

* fix: handle WSL and Windows paths in gpgconf resolution

- Add resolveGpgconfCommand() helper for path resolution
- Handle WSL wrappers (wsl gpg2, wsl -d Ubuntu gpg2)
- Detect shell commands (wsl sh -c) and fallback to PATH
- Check .exe extension on Windows
- Add unit tests for all path cases
- Resolves CodeRabbit review finding

* Update tests/auto/executor/tst_executor.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update tests/auto/executor/tst_executor.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Translations update from Hosted Weblate (#893)

* Translated using Weblate (Estonian)

Currently translated at 100.0% (215 of 215 strings)

Translation: QtPass/QtPass
Translate-URL: https://hosted.weblate.org/projects/qtpass/qtpass/et/

* Translated using Weblate (Russian)

Currently translated at 98.1% (211 of 215 strings)

Translation: QtPass/QtPass
Translate-URL: https://hosted.weblate.org/projects/qtpass/qtpass/ru/

---------

Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Сергей <asvmail.as@gmail.com>

* fix: add Russian translation for Signing Key integrity message (#896)

* fix: restore preprocessor directives broken by rebase

* fix: address remaining CodeRabbit review comments

- Return... (continued)

22 of 45 new or added lines in 3 files covered. (48.89%)

7 existing lines in 1 file now uncovered.

1052 of 5230 relevant lines covered (20.11%)

7.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.83
/src/executor.cpp
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

18
/**
19
 * @brief Executor::Executor executes external applications
20
 * @param parent
21
 */
22
Executor::Executor(QObject *parent) : QObject(parent), running(false) {
11✔
23
  connect(&m_process,
11✔
24
          static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
25
              &QProcess::finished),
26
          this,
27
          static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
11✔
28
              &Executor::finished));
29
  connect(&m_process, &QProcess::started, this, &Executor::starting);
11✔
30
}
11✔
31

32
/**
33
 * @brief Executor::startProcess starts the internal process, handling WSL
34
 * prefixes.
35
 * @param app Executable path (may start with "wsl ").
36
 * @param args Arguments to pass to the executable.
37
 */
38
void 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

49
/**
50
 * @brief Executor::startProcessBlocking starts a given process, handling WSL
51
 * prefixes.
52
 * @param internal QProcess reference to start.
53
 * @param app Executable path (may start with "wsl ").
54
 * @param args Arguments to pass to the executable.
55
 */
56
void Executor::startProcessBlocking(QProcess &internal, const QString &app,
14✔
57
                                    const QStringList &args) {
58
  if (app.startsWith("wsl ")) {
28✔
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);
14✔
65
  }
66
}
14✔
67

68
/**
69
 * @brief Executor::executeNext consumes executable tasks from the queue
70
 */
71
void 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

103
/**
104
 * @brief Executor::execute execute an app
105
 * @param id
106
 * @param app
107
 * @param args
108
 * @param readStdout
109
 * @param readStderr
110
 */
111
void 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

116
/**
117
 * @brief Executor::execute executes an app from a workDir
118
 * @param id
119
 * @param workDir
120
 * @param app
121
 * @param args
122
 * @param readStdout
123
 * @param readStderr
124
 */
125
void 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

131
/**
132
 * @brief Executor::execute an app, takes input and presents it as stdin
133
 * @param id
134
 * @param app
135
 * @param args
136
 * @param input
137
 * @param readStdout
138
 * @param readStderr
139
 */
140
void 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

145
/**
146
 * @brief Executor::execute  executes an app from a workDir, takes input and
147
 * presents it as stdin
148
 * @param id
149
 * @param workDir
150
 * @param app
151
 * @param args
152
 * @param input
153
 * @param readStdout
154
 * @param readStderr
155
 */
156
void 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

178
/**
179
 * @brief decodes the input into a string assuming UTF-8 encoding.
180
 * If this fails (which is likely if it is not actually UTF-8)
181
 * it will then fall back to Qt's decoding function, which
182
 * will try based on BOM and if that fails fall back to local encoding.
183
 * This should not be needed in Qt6
184
 *
185
 * @param in input data
186
 * @return Input bytes decoded to string
187
 */
188
static auto decodeAssumingUtf8(const QByteArray &in) -> QString {
20✔
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);
20✔
201
  if (!converter.hasError()) {
20✔
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

210
/**
211
 * @brief Executor::executeBlocking blocking version of the executor,
212
 * takes input and presents it as stdin
213
 * @param app
214
 * @param args
215
 * @param input
216
 * @param process_out
217
 * @param process_err
218
 * @return
219
 *
220
 * Note: Returning error code instead of throwing to maintain compatibility
221
 * with the existing error handling pattern used throughout QtPass.
222
 */
223
auto Executor::executeBlocking(QString app, const QStringList &args,
14✔
224
                               const QString &input, QString *process_out,
225
                               QString *process_err) -> int {
226
  QProcess internal;
14✔
227
  startProcessBlocking(internal, app, args);
14✔
228
  if (!internal.waitForStarted(-1)) {
14✔
229
#ifdef QT_DEBUG
230
    dbg() << "Process failed to start:" << app;
231
#endif
232
    return -1;
233
  }
234
  if (!input.isEmpty()) {
10✔
235
    QByteArray data = input.toUtf8();
236
    if (internal.write(data) != data.length()) {
1✔
237
#ifdef QT_DEBUG
238
      dbg() << "Not all input written:" << app;
239
#endif
240
    }
241
    internal.closeWriteChannel();
1✔
242
  }
243
  internal.waitForFinished(-1);
10✔
244
  if (internal.exitStatus() == QProcess::NormalExit) {
10✔
245
    QString pout = decodeAssumingUtf8(internal.readAllStandardOutput());
10✔
246
    QString perr = decodeAssumingUtf8(internal.readAllStandardError());
10✔
247
    if (process_out != nullptr) {
10✔
248
      *process_out = pout;
10✔
249
    }
250
    if (process_err != nullptr) {
10✔
251
      *process_err = perr;
3✔
252
    }
253
    return internal.exitCode();
10✔
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
}
14✔
259

260
/**
261
 * @brief Executor::executeBlocking blocking version of the executor
262
 * @param app
263
 * @param args
264
 * @param process_out
265
 * @param process_err
266
 * @return
267
 */
268
auto Executor::executeBlocking(QString app, const QStringList &args,
3✔
269
                               QString *process_out, QString *process_err)
270
    -> int {
271
  return executeBlocking(std::move(app), args, QString(), process_out,
6✔
272
                         process_err);
3✔
273
}
274

275
/**
276
 * @brief Executor::executeBlocking blocking version with custom environment
277
 * @param env Environment variables to set
278
 * @param app Executable path
279
 * @param args Arguments
280
 * @param process_out Standard output
281
 * @param process_err Standard error
282
 * @return Exit code
283
 */
NEW
284
auto Executor::executeBlocking(const QStringList &env, QString app,
×
285
                               const QStringList &args, QString *process_out,
286
                               QString *process_err) -> int {
NEW
287
  QProcess process;
×
NEW
288
  QProcessEnvironment penv;
×
NEW
289
  for (const QString &var : env) {
×
NEW
290
    int idx = var.indexOf('=');
×
NEW
291
    if (idx > 0) {
×
NEW
292
      penv.insert(var.left(idx), var.mid(idx + 1));
×
293
    }
294
  }
NEW
295
  process.setProcessEnvironment(penv);
×
NEW
296
  startProcessBlocking(process, app, args);
×
NEW
297
  if (!process.waitForStarted(-1)) {
×
298
#ifdef QT_DEBUG
299
    dbg() << "Process failed to start:" << app;
300
#endif
301
    return -1;
302
  }
NEW
303
  process.waitForFinished(-1);
×
NEW
304
  if (process.exitStatus() != QProcess::NormalExit) {
×
305
    return -1;
306
  }
NEW
307
  if (process_out) {
×
NEW
308
    *process_out = decodeAssumingUtf8(process.readAllStandardOutput());
×
309
  }
NEW
310
  if (process_err) {
×
NEW
311
    *process_err = decodeAssumingUtf8(process.readAllStandardError());
×
312
  }
NEW
313
  return process.exitCode();
×
NEW
314
}
×
315

316
/**
317
 * @brief Executor::setEnvironment set environment variables
318
 * for executor processes
319
 * @param env
320
 */
321
void Executor::setEnvironment(const QStringList &env) {
×
322
  m_process.setEnvironment(env);
×
323
}
×
324

325
/**
326
 * @brief Executor::cancelNext  cancels execution of first process in queue
327
 *                              if it's not already running
328
 *
329
 * @return  id of the cancelled process or -1 on error
330
 */
331
auto 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

339
/**
340
 * @brief Executor::finished called when an executed process finishes
341
 * @param exitCode
342
 * @param exitStatus
343
 */
344
void 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
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc