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"