File indexing completed on 2024-05-12 03:54:55

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kcoreaddons_debug.h"
0011 #include "kprocess_p.h"
0012 
0013 #include <QStandardPaths>
0014 #include <kshell.h>
0015 #include <qplatformdefs.h>
0016 #ifdef Q_OS_WIN
0017 #include <kshell_p.h>
0018 #include <qt_windows.h>
0019 #endif
0020 
0021 #include <QFile>
0022 
0023 /////////////////////////////
0024 // public member functions //
0025 /////////////////////////////
0026 
0027 KProcess::KProcess(QObject *parent)
0028     : QProcess(parent)
0029     , d_ptr(new KProcessPrivate(this))
0030 {
0031     setOutputChannelMode(ForwardedChannels);
0032 }
0033 
0034 KProcess::KProcess(KProcessPrivate *d, QObject *parent)
0035     : QProcess(parent)
0036     , d_ptr(d)
0037 {
0038     d_ptr->q_ptr = this;
0039     setOutputChannelMode(ForwardedChannels);
0040 }
0041 
0042 KProcess::~KProcess() = default;
0043 
0044 void KProcess::setOutputChannelMode(OutputChannelMode mode)
0045 {
0046     QProcess::setProcessChannelMode(static_cast<ProcessChannelMode>(mode));
0047 }
0048 
0049 KProcess::OutputChannelMode KProcess::outputChannelMode() const
0050 {
0051     return static_cast<OutputChannelMode>(QProcess::processChannelMode());
0052 }
0053 
0054 void KProcess::setNextOpenMode(QIODevice::OpenMode mode)
0055 {
0056     Q_D(KProcess);
0057 
0058     d->openMode = mode;
0059 }
0060 
0061 #define DUMMYENV "_KPROCESS_DUMMY_="
0062 
0063 void KProcess::clearEnvironment()
0064 {
0065     setEnvironment(QStringList{QStringLiteral(DUMMYENV)});
0066 }
0067 
0068 void KProcess::setEnv(const QString &name, const QString &value, bool overwrite)
0069 {
0070     QStringList env = environment();
0071     if (env.isEmpty()) {
0072         env = systemEnvironment();
0073         env.removeAll(QStringLiteral(DUMMYENV));
0074     }
0075     QString fname(name);
0076     fname.append(QLatin1Char('='));
0077     auto it = std::find_if(env.begin(), env.end(), [&fname](const QString &s) {
0078         return s.startsWith(fname);
0079     });
0080     if (it != env.end()) {
0081         if (overwrite) {
0082             *it = fname.append(value);
0083             setEnvironment(env);
0084         }
0085         return;
0086     }
0087 
0088     env.append(fname.append(value));
0089     setEnvironment(env);
0090 }
0091 
0092 void KProcess::unsetEnv(const QString &name)
0093 {
0094     QStringList env = environment();
0095     if (env.isEmpty()) {
0096         env = systemEnvironment();
0097         env.removeAll(QStringLiteral(DUMMYENV));
0098     }
0099     QString fname(name);
0100     fname.append(QLatin1Char('='));
0101 
0102     auto it = std::find_if(env.begin(), env.end(), [&fname](const QString &s) {
0103         return s.startsWith(fname);
0104     });
0105     if (it != env.end()) {
0106         env.erase(it);
0107         if (env.isEmpty()) {
0108             env.append(QStringLiteral(DUMMYENV));
0109         }
0110         setEnvironment(env);
0111     }
0112 }
0113 
0114 void KProcess::setProgram(const QString &exe, const QStringList &args)
0115 {
0116     QProcess::setProgram(exe);
0117     QProcess::setArguments(args);
0118 #ifdef Q_OS_WIN
0119     setNativeArguments(QString());
0120 #endif
0121 }
0122 
0123 void KProcess::setProgram(const QStringList &argv)
0124 {
0125     if (argv.isEmpty()) {
0126         qCWarning(KCOREADDONS_DEBUG) << "KProcess::setProgram(const QStringList &argv) called on an empty string list, no process will be started.";
0127         clearProgram();
0128         return;
0129     }
0130 
0131     QStringList args = argv;
0132     QProcess::setProgram(args.takeFirst());
0133     QProcess::setArguments(args);
0134 #ifdef Q_OS_WIN
0135     setNativeArguments(QString());
0136 #endif
0137 }
0138 
0139 KProcess &KProcess::operator<<(const QString &arg)
0140 {
0141     if (QProcess::program().isEmpty()) {
0142         QProcess::setProgram(arg);
0143     } else {
0144         setArguments(arguments() << arg);
0145     }
0146     return *this;
0147 }
0148 
0149 KProcess &KProcess::operator<<(const QStringList &args)
0150 {
0151     if (QProcess::program().isEmpty()) {
0152         setProgram(args);
0153     } else {
0154         setArguments(arguments() << args);
0155     }
0156     return *this;
0157 }
0158 
0159 void KProcess::clearProgram()
0160 {
0161     QProcess::setProgram({});
0162     QProcess::setArguments({});
0163 #ifdef Q_OS_WIN
0164     setNativeArguments(QString());
0165 #endif
0166 }
0167 
0168 // #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh
0169 #if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__)              \
0170     && !defined(__APPLE__)
0171 const static bool s_nonFreeUnix = true;
0172 #else
0173 const static bool s_nonFreeUnix = false;
0174 #endif
0175 
0176 void KProcess::setShellCommand(const QString &cmd)
0177 {
0178     KShell::Errors err = KShell::NoError;
0179     auto args = KShell::splitArgs(cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err);
0180     if (err == KShell::NoError && !args.isEmpty()) {
0181         QProcess::setProgram(QStandardPaths::findExecutable(args.takeFirst()));
0182         if (!QProcess::program().isEmpty()) {
0183             setArguments(args);
0184 #ifdef Q_OS_WIN
0185             setNativeArguments(QString());
0186 #endif
0187             return;
0188         }
0189     }
0190 
0191     setArguments({});
0192 
0193 #ifdef Q_OS_UNIX
0194     if (s_nonFreeUnix) {
0195         // If /bin/sh is a symlink, we can be pretty sure that it points to a
0196         // POSIX shell - the original bourne shell is about the only non-POSIX
0197         // shell still in use and it is always installed natively as /bin/sh.
0198         QString shell = QFile::symLinkTarget(QStringLiteral("/bin/sh"));
0199         if (shell.isEmpty()) {
0200             // Try some known POSIX shells.
0201             static const char *s_knownShells[] = {"ksh", "ash", "bash", "zsh"};
0202             for (const auto str : s_knownShells) {
0203                 shell = QStandardPaths::findExecutable(QLatin1String(str));
0204                 if (!shell.isEmpty()) {
0205                     break;
0206                 }
0207             }
0208         }
0209         if (shell.isEmpty()) { // We're pretty much screwed, to be honest ...
0210             shell = QStringLiteral("/bin/sh");
0211         }
0212         QProcess::setProgram(shell);
0213     } else {
0214         QProcess::setProgram((QStringLiteral("/bin/sh")));
0215     }
0216 
0217     setArguments(arguments() << QStringLiteral("-c") << cmd);
0218 #else // Q_OS_UNIX
0219     // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and
0220     // KShell::joinArgs() may generate these for security reasons.
0221     setEnv(PERCENT_VARIABLE, QStringLiteral("%"));
0222 
0223 #ifndef _WIN32_WCE
0224     WCHAR sysdir[MAX_PATH + 1];
0225     UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1);
0226     QProcess::setProgram(QString::fromUtf16((const char16_t *)sysdir, size) + QLatin1String("\\cmd.exe"));
0227     setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"'));
0228 #else
0229     QProcess::setProgram(QStringLiteral("\\windows\\cmd.exe"));
0230     setNativeArguments(QStringLiteral("/S /C \"") + cmd + QLatin1Char('"'));
0231 #endif
0232 #endif
0233 }
0234 
0235 QStringList KProcess::program() const
0236 {
0237     QStringList argv = arguments();
0238     argv.prepend(QProcess::program());
0239     return argv;
0240 }
0241 
0242 void KProcess::start()
0243 {
0244     Q_D(KProcess);
0245 
0246     QProcess::start(d->openMode);
0247 }
0248 
0249 int KProcess::execute(int msecs)
0250 {
0251     start();
0252     if (!waitForFinished(msecs)) {
0253         kill();
0254         waitForFinished(-1);
0255         return -2;
0256     }
0257     return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1;
0258 }
0259 
0260 // static
0261 int KProcess::execute(const QString &exe, const QStringList &args, int msecs)
0262 {
0263     KProcess p;
0264     p.setProgram(exe, args);
0265     return p.execute(msecs);
0266 }
0267 
0268 // static
0269 int KProcess::execute(const QStringList &argv, int msecs)
0270 {
0271     KProcess p;
0272     p.setProgram(argv);
0273     return p.execute(msecs);
0274 }
0275 
0276 int KProcess::startDetached()
0277 {
0278     qint64 pid;
0279     if (!QProcess::startDetached(QProcess::program(), QProcess::arguments(), workingDirectory(), &pid)) {
0280         return 0;
0281     }
0282     return static_cast<int>(pid);
0283 }
0284 
0285 // static
0286 int KProcess::startDetached(const QString &exe, const QStringList &args)
0287 {
0288     qint64 pid;
0289     if (!QProcess::startDetached(exe, args, QString(), &pid)) {
0290         return 0;
0291     }
0292     return static_cast<int>(pid);
0293 }
0294 
0295 // static
0296 int KProcess::startDetached(const QStringList &argv)
0297 {
0298     if (argv.isEmpty()) {
0299         qCWarning(KCOREADDONS_DEBUG) << "KProcess::startDetached(const QStringList &argv) called on an empty string list, no process will be started.";
0300         return 0;
0301     }
0302 
0303     QStringList args = argv;
0304     QString prog = args.takeFirst();
0305     return startDetached(prog, args);
0306 }
0307 
0308 #include "moc_kprocess.cpp"