1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
5 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kcoreaddons_debug.h"
11#include "kprocess_p.h"
12
13#include <QStandardPaths>
14#include <kshell.h>
15#include <qplatformdefs.h>
16#ifdef Q_OS_WIN
17#include <kshell_p.h>
18#include <qt_windows.h>
19#endif
20
21#include <QFile>
22
23/////////////////////////////
24// public member functions //
25/////////////////////////////
26
27KProcess::KProcess(QObject *parent)
28 : QProcess(parent)
29 , d_ptr(new KProcessPrivate(this))
30{
31 setOutputChannelMode(ForwardedChannels);
32}
33
34KProcess::KProcess(KProcessPrivate *d, QObject *parent)
35 : QProcess(parent)
36 , d_ptr(d)
37{
38 d_ptr->q_ptr = this;
39 setOutputChannelMode(ForwardedChannels);
40}
41
42KProcess::~KProcess() = default;
43
44void KProcess::setOutputChannelMode(OutputChannelMode mode)
45{
46 QProcess::setProcessChannelMode(static_cast<ProcessChannelMode>(mode));
47}
48
49KProcess::OutputChannelMode KProcess::outputChannelMode() const
50{
51 return static_cast<OutputChannelMode>(QProcess::processChannelMode());
52}
53
54void KProcess::setNextOpenMode(QIODevice::OpenMode mode)
55{
56 Q_D(KProcess);
57
58 d->openMode = mode;
59}
60
61#define DUMMYENV "_KPROCESS_DUMMY_="
62
63void KProcess::clearEnvironment()
64{
65 setEnvironment(QStringList{QStringLiteral(DUMMYENV)});
66}
67
68void KProcess::setEnv(const QString &name, const QString &value, bool overwrite)
69{
70 QStringList env = environment();
71 if (env.isEmpty()) {
72 env = systemEnvironment();
73 env.removeAll(QStringLiteral(DUMMYENV));
74 }
75 QString fname(name);
76 fname.append(c: QLatin1Char('='));
77 auto it = std::find_if(first: env.begin(), last: env.end(), pred: [&fname](const QString &s) {
78 return s.startsWith(s: fname);
79 });
80 if (it != env.end()) {
81 if (overwrite) {
82 *it = fname.append(s: value);
83 setEnvironment(env);
84 }
85 return;
86 }
87
88 env.append(t: fname.append(s: value));
89 setEnvironment(env);
90}
91
92void KProcess::unsetEnv(const QString &name)
93{
94 QStringList env = environment();
95 if (env.isEmpty()) {
96 env = systemEnvironment();
97 env.removeAll(QStringLiteral(DUMMYENV));
98 }
99 QString fname(name);
100 fname.append(c: QLatin1Char('='));
101
102 auto it = std::find_if(first: env.begin(), last: env.end(), pred: [&fname](const QString &s) {
103 return s.startsWith(s: fname);
104 });
105 if (it != env.end()) {
106 env.erase(pos: it);
107 if (env.isEmpty()) {
108 env.append(QStringLiteral(DUMMYENV));
109 }
110 setEnvironment(env);
111 }
112}
113
114void KProcess::setProgram(const QString &exe, const QStringList &args)
115{
116 QProcess::setProgram(exe);
117 QProcess::setArguments(args);
118#ifdef Q_OS_WIN
119 setNativeArguments(QString());
120#endif
121}
122
123void KProcess::setProgram(const QStringList &argv)
124{
125 if (argv.isEmpty()) {
126 qCWarning(KCOREADDONS_DEBUG) << "KProcess::setProgram(const QStringList &argv) called on an empty string list, no process will be started.";
127 clearProgram();
128 return;
129 }
130
131 QStringList args = argv;
132 QProcess::setProgram(args.takeFirst());
133 QProcess::setArguments(args);
134#ifdef Q_OS_WIN
135 setNativeArguments(QString());
136#endif
137}
138
139KProcess &KProcess::operator<<(const QString &arg)
140{
141 if (QProcess::program().isEmpty()) {
142 QProcess::setProgram(arg);
143 } else {
144 setArguments(arguments() << arg);
145 }
146 return *this;
147}
148
149KProcess &KProcess::operator<<(const QStringList &args)
150{
151 if (QProcess::program().isEmpty()) {
152 setProgram(args);
153 } else {
154 setArguments(arguments() << args);
155 }
156 return *this;
157}
158
159void KProcess::clearProgram()
160{
161 QProcess::setProgram({});
162 QProcess::setArguments({});
163#ifdef Q_OS_WIN
164 setNativeArguments(QString());
165#endif
166}
167
168// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh
169#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) \
170 && !defined(__APPLE__)
171const static bool s_nonFreeUnix = true;
172#else
173const static bool s_nonFreeUnix = false;
174#endif
175
176void KProcess::setShellCommand(const QString &cmd)
177{
178 KShell::Errors err = KShell::NoError;
179 auto args = KShell::splitArgs(cmd, flags: KShell::AbortOnMeta | KShell::TildeExpand, err: &err);
180 if (err == KShell::NoError && !args.isEmpty()) {
181 QProcess::setProgram(QStandardPaths::findExecutable(executableName: args.takeFirst()));
182 if (!QProcess::program().isEmpty()) {
183 setArguments(args);
184#ifdef Q_OS_WIN
185 setNativeArguments(QString());
186#endif
187 return;
188 }
189 }
190
191 setArguments({});
192
193#ifdef Q_OS_UNIX
194 if (s_nonFreeUnix) {
195 // If /bin/sh is a symlink, we can be pretty sure that it points to a
196 // POSIX shell - the original bourne shell is about the only non-POSIX
197 // shell still in use and it is always installed natively as /bin/sh.
198 QString shell = QFile::symLinkTarget(QStringLiteral("/bin/sh"));
199 if (shell.isEmpty()) {
200 // Try some known POSIX shells.
201 static const char *s_knownShells[] = {"ksh", "ash", "bash", "zsh"};
202 for (const auto str : s_knownShells) {
203 shell = QStandardPaths::findExecutable(executableName: QLatin1String(str));
204 if (!shell.isEmpty()) {
205 break;
206 }
207 }
208 }
209 if (shell.isEmpty()) { // We're pretty much screwed, to be honest ...
210 shell = QStringLiteral("/bin/sh");
211 }
212 QProcess::setProgram(shell);
213 } else {
214 QProcess::setProgram((QStringLiteral("/bin/sh")));
215 }
216
217 setArguments(arguments() << QStringLiteral("-c") << cmd);
218#else // Q_OS_UNIX
219 // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and
220 // KShell::joinArgs() may generate these for security reasons.
221 setEnv(PERCENT_VARIABLE, QStringLiteral("%"));
222
223#ifndef _WIN32_WCE
224 WCHAR sysdir[MAX_PATH + 1];
225 UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1);
226 QProcess::setProgram(QString::fromUtf16((const char16_t *)sysdir, size) + QLatin1String("\\cmd.exe"));
227 setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"'));
228#else
229 QProcess::setProgram(QStringLiteral("\\windows\\cmd.exe"));
230 setNativeArguments(QStringLiteral("/S /C \"") + cmd + QLatin1Char('"'));
231#endif
232#endif
233}
234
235QStringList KProcess::program() const
236{
237 QStringList argv = arguments();
238 argv.prepend(t: QProcess::program());
239 return argv;
240}
241
242void KProcess::start()
243{
244 Q_D(KProcess);
245
246 QProcess::start(mode: d->openMode);
247}
248
249int KProcess::execute(int msecs)
250{
251 start();
252 if (!waitForFinished(msecs)) {
253 kill();
254 waitForFinished(msecs: -1);
255 return -2;
256 }
257 return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1;
258}
259
260// static
261int KProcess::execute(const QString &exe, const QStringList &args, int msecs)
262{
263 KProcess p;
264 p.setProgram(exe, args);
265 return p.execute(msecs);
266}
267
268// static
269int KProcess::execute(const QStringList &argv, int msecs)
270{
271 KProcess p;
272 p.setProgram(argv);
273 return p.execute(msecs);
274}
275
276int KProcess::startDetached()
277{
278 qint64 pid;
279 if (!QProcess::startDetached(program: QProcess::program(), arguments: QProcess::arguments(), workingDirectory: workingDirectory(), pid: &pid)) {
280 return 0;
281 }
282 return static_cast<int>(pid);
283}
284
285// static
286int KProcess::startDetached(const QString &exe, const QStringList &args)
287{
288 qint64 pid;
289 if (!QProcess::startDetached(program: exe, arguments: args, workingDirectory: QString(), pid: &pid)) {
290 return 0;
291 }
292 return static_cast<int>(pid);
293}
294
295// static
296int KProcess::startDetached(const QStringList &argv)
297{
298 if (argv.isEmpty()) {
299 qCWarning(KCOREADDONS_DEBUG) << "KProcess::startDetached(const QStringList &argv) called on an empty string list, no process will be started.";
300 return 0;
301 }
302
303 QStringList args = argv;
304 QString prog = args.takeFirst();
305 return startDetached(exe: prog, args);
306}
307
308#include "moc_kprocess.cpp"
309

source code of kcoreaddons/src/lib/io/kprocess.cpp