| 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 | |
| 27 | KProcess::KProcess(QObject *parent) |
| 28 | : QProcess(parent) |
| 29 | , d_ptr(new KProcessPrivate(this)) |
| 30 | { |
| 31 | setOutputChannelMode(ForwardedChannels); |
| 32 | } |
| 33 | |
| 34 | KProcess::KProcess(KProcessPrivate *d, QObject *parent) |
| 35 | : QProcess(parent) |
| 36 | , d_ptr(d) |
| 37 | { |
| 38 | d_ptr->q_ptr = this; |
| 39 | setOutputChannelMode(ForwardedChannels); |
| 40 | } |
| 41 | |
| 42 | KProcess::~KProcess() = default; |
| 43 | |
| 44 | void KProcess::setOutputChannelMode(OutputChannelMode mode) |
| 45 | { |
| 46 | QProcess::setProcessChannelMode(static_cast<ProcessChannelMode>(mode)); |
| 47 | } |
| 48 | |
| 49 | KProcess::OutputChannelMode KProcess::outputChannelMode() const |
| 50 | { |
| 51 | return static_cast<OutputChannelMode>(QProcess::processChannelMode()); |
| 52 | } |
| 53 | |
| 54 | void KProcess::setNextOpenMode(QIODevice::OpenMode mode) |
| 55 | { |
| 56 | Q_D(KProcess); |
| 57 | |
| 58 | d->openMode = mode; |
| 59 | } |
| 60 | |
| 61 | #define DUMMYENV "_KPROCESS_DUMMY_=" |
| 62 | |
| 63 | void KProcess::clearEnvironment() |
| 64 | { |
| 65 | setEnvironment(QStringList{QStringLiteral(DUMMYENV)}); |
| 66 | } |
| 67 | |
| 68 | void 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 | |
| 92 | void 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 | |
| 114 | void 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 | |
| 123 | void 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 | |
| 139 | KProcess &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 | |
| 149 | KProcess &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 | |
| 159 | void 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__) |
| 171 | const static bool s_nonFreeUnix = true; |
| 172 | #else |
| 173 | const static bool s_nonFreeUnix = false; |
| 174 | #endif |
| 175 | |
| 176 | void 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 | |
| 235 | QStringList KProcess::program() const |
| 236 | { |
| 237 | QStringList argv = arguments(); |
| 238 | argv.prepend(t: QProcess::program()); |
| 239 | return argv; |
| 240 | } |
| 241 | |
| 242 | void KProcess::start() |
| 243 | { |
| 244 | Q_D(KProcess); |
| 245 | |
| 246 | QProcess::start(mode: d->openMode); |
| 247 | } |
| 248 | |
| 249 | int 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 |
| 261 | int 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 |
| 269 | int KProcess::execute(const QStringList &argv, int msecs) |
| 270 | { |
| 271 | KProcess p; |
| 272 | p.setProgram(argv); |
| 273 | return p.execute(msecs); |
| 274 | } |
| 275 | |
| 276 | int 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 |
| 286 | int 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 |
| 296 | int 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 | |