| 1 | /* |
| 2 | * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) |
| 3 | * |
| 4 | * This program is free software: you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation, either version 3 of the License, or |
| 7 | * (at your option) any later version. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU General Public License |
| 15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 16 | */ |
| 17 | #include "executablerunner.h" |
| 18 | |
| 19 | #include <QDebug> |
| 20 | #include "compilermanager.h" |
| 21 | #include "../settings.h" |
| 22 | #include "../systemconsts.h" |
| 23 | #ifdef Q_OS_WIN |
| 24 | #include <QUuid> |
| 25 | #include <windows.h> |
| 26 | #elif defined(Q_OS_LINUX) |
| 27 | #include <sys/mman.h> |
| 28 | #include <sys/types.h> |
| 29 | #include <unistd.h> |
| 30 | #include <sys/stat.h> /* For mode constants */ |
| 31 | #include <fcntl.h> /* For O_* constants */ |
| 32 | #endif |
| 33 | |
| 34 | |
| 35 | ExecutableRunner::ExecutableRunner(const QString &filename, const QString &arguments, const QString &workDir |
| 36 | ,QObject* parent): |
| 37 | Runner(filename,arguments,workDir,parent), |
| 38 | mRedirectInput(false), |
| 39 | mStartConsole(false), |
| 40 | mQuitSemaphore(0) |
| 41 | { |
| 42 | setWaitForFinishTime(1000); |
| 43 | } |
| 44 | |
| 45 | bool ExecutableRunner::startConsole() const |
| 46 | { |
| 47 | return mStartConsole; |
| 48 | } |
| 49 | |
| 50 | void ExecutableRunner::setStartConsole(bool newStartConsole) |
| 51 | { |
| 52 | mStartConsole = newStartConsole; |
| 53 | } |
| 54 | |
| 55 | const QString &ExecutableRunner::shareMemoryId() const |
| 56 | { |
| 57 | return mShareMemoryId; |
| 58 | } |
| 59 | |
| 60 | void ExecutableRunner::setShareMemoryId(const QString &newShareMemoryId) |
| 61 | { |
| 62 | mShareMemoryId = newShareMemoryId; |
| 63 | } |
| 64 | |
| 65 | const QStringList &ExecutableRunner::binDirs() const |
| 66 | { |
| 67 | return mBinDirs; |
| 68 | } |
| 69 | |
| 70 | void ExecutableRunner::addBinDirs(const QStringList &binDirs) |
| 71 | { |
| 72 | mBinDirs.append(binDirs); |
| 73 | } |
| 74 | |
| 75 | void ExecutableRunner::addBinDir(const QString &binDir) |
| 76 | { |
| 77 | mBinDirs.append(binDir); |
| 78 | } |
| 79 | |
| 80 | bool ExecutableRunner::redirectInput() const |
| 81 | { |
| 82 | return mRedirectInput; |
| 83 | } |
| 84 | |
| 85 | void ExecutableRunner::setRedirectInput(bool isRedirect) |
| 86 | { |
| 87 | mRedirectInput = isRedirect; |
| 88 | } |
| 89 | |
| 90 | const QString &ExecutableRunner::redirectInputFilename() const |
| 91 | { |
| 92 | return mRedirectInputFilename; |
| 93 | } |
| 94 | |
| 95 | void ExecutableRunner::setRedirectInputFilename(const QString &newDataFile) |
| 96 | { |
| 97 | mRedirectInputFilename = newDataFile; |
| 98 | } |
| 99 | |
| 100 | void ExecutableRunner::run() |
| 101 | { |
| 102 | emit started(); |
| 103 | auto action = finally([this]{ |
| 104 | mProcess.reset(); |
| 105 | setPausing(false); |
| 106 | emit terminated(); |
| 107 | }); |
| 108 | mStop = false; |
| 109 | bool errorOccurred = false; |
| 110 | |
| 111 | mProcess = std::make_shared<QProcess>(); |
| 112 | mProcess->setProgram(mFilename); |
| 113 | mProcess->setArguments(splitProcessCommand(mArguments)); |
| 114 | //qDebug()<<splitProcessCommand(mArguments); |
| 115 | mProcess->setWorkingDirectory(mWorkDir); |
| 116 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); |
| 117 | QString path = env.value("PATH" ); |
| 118 | QStringList pathAdded = mBinDirs; |
| 119 | if (!path.isEmpty()) { |
| 120 | path = pathAdded.join(PATH_SEPARATOR) + PATH_SEPARATOR + path; |
| 121 | } else { |
| 122 | path = pathAdded.join(PATH_SEPARATOR); |
| 123 | } |
| 124 | env.insert("PATH" ,path); |
| 125 | mProcess->setProcessEnvironment(env); |
| 126 | connect( |
| 127 | mProcess.get(), &QProcess::errorOccurred, |
| 128 | [&errorOccurred](){ |
| 129 | errorOccurred= true; |
| 130 | }); |
| 131 | #ifdef Q_OS_WIN |
| 132 | mProcess->setCreateProcessArgumentsModifier([this](QProcess::CreateProcessArguments * args){ |
| 133 | if (mStartConsole) { |
| 134 | args->flags |= CREATE_NEW_CONSOLE; |
| 135 | args->flags &= ~CREATE_NO_WINDOW; |
| 136 | } |
| 137 | if (!mRedirectInput) { |
| 138 | args->startupInfo -> dwFlags &= ~STARTF_USESTDHANDLES; |
| 139 | } |
| 140 | }); |
| 141 | HANDLE hSharedMemory=INVALID_HANDLE_VALUE; |
| 142 | int BUF_SIZE=1024; |
| 143 | char* pBuf=nullptr; |
| 144 | if (mStartConsole) { |
| 145 | hSharedMemory = CreateFileMappingA( |
| 146 | INVALID_HANDLE_VALUE, |
| 147 | NULL, |
| 148 | PAGE_READWRITE, |
| 149 | 0, |
| 150 | 100, |
| 151 | mShareMemoryId.toLocal8Bit().data() |
| 152 | ); |
| 153 | if (hSharedMemory != NULL) |
| 154 | { |
| 155 | pBuf = (char*) MapViewOfFile(hSharedMemory, // handle to map object |
| 156 | FILE_MAP_ALL_ACCESS, // read/write permission |
| 157 | 0, |
| 158 | 0, |
| 159 | BUF_SIZE); |
| 160 | if (pBuf) { |
| 161 | pBuf[0]=0; |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | #elif defined(Q_OS_LINUX) |
| 166 | int BUF_SIZE=1024; |
| 167 | char* pBuf=nullptr; |
| 168 | int fd_shm = shm_open(mShareMemoryId.toLocal8Bit().data(),O_RDWR | O_CREAT,S_IRWXU); |
| 169 | if (fd_shm==-1) { |
| 170 | qDebug()<<QString("shm open failed %1:%2" ).arg(errno).arg(strerror(errno)); |
| 171 | } else { |
| 172 | if (ftruncate(fd_shm,BUF_SIZE)==-1){ |
| 173 | qDebug()<<QString("truncate failed %1:%2" ).arg(errno).arg(strerror(errno)); |
| 174 | } else { |
| 175 | pBuf = (char*)mmap(NULL,BUF_SIZE,PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm,0); |
| 176 | if (pBuf == MAP_FAILED) { |
| 177 | qDebug()<<QString("mmap failed %1:%2" ).arg(errno).arg(strerror(errno)); |
| 178 | pBuf = nullptr; |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | #endif |
| 183 | // if (!redirectInput()) { |
| 184 | // process.closeWriteChannel(); |
| 185 | // } |
| 186 | mProcess->start(); |
| 187 | mProcess->waitForStarted(5000); |
| 188 | if (mProcess->state()==QProcess::Running && redirectInput()) { |
| 189 | mProcess->write(readFileToByteArray(redirectInputFilename())); |
| 190 | mProcess->waitForFinished(0); |
| 191 | } |
| 192 | bool writeChannelClosed = false; |
| 193 | while (true) { |
| 194 | if (mProcess->bytesToWrite()==0 && redirectInput() && !writeChannelClosed) { |
| 195 | writeChannelClosed=true; |
| 196 | mProcess->closeWriteChannel(); |
| 197 | } |
| 198 | mProcess->waitForFinished(mWaitForFinishTime); |
| 199 | if (mProcess->state()!=QProcess::Running) { |
| 200 | break; |
| 201 | } |
| 202 | if (errorOccurred) |
| 203 | break; |
| 204 | if (mStop) { |
| 205 | mProcess->terminate(); |
| 206 | if (mProcess->waitForFinished(1000)) { |
| 207 | break; |
| 208 | } |
| 209 | for (int i=0;i<10;i++) { |
| 210 | mProcess->kill(); |
| 211 | if (mProcess->waitForFinished(500)) { |
| 212 | break; |
| 213 | } |
| 214 | } |
| 215 | break; |
| 216 | } |
| 217 | #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) |
| 218 | if (mStartConsole && !mPausing && pBuf) { |
| 219 | if (strncmp(pBuf,"FINISHED" ,sizeof("FINISHED" ))==0) { |
| 220 | #ifdef Q_OS_WIN |
| 221 | if (pBuf) { |
| 222 | UnmapViewOfFile(pBuf); |
| 223 | pBuf = nullptr; |
| 224 | } |
| 225 | if (hSharedMemory!=INVALID_HANDLE_VALUE && hSharedMemory!=NULL) { |
| 226 | hSharedMemory = INVALID_HANDLE_VALUE; |
| 227 | CloseHandle(hSharedMemory); |
| 228 | } |
| 229 | #elif defined(Q_OS_LINUX) |
| 230 | if (pBuf) { |
| 231 | munmap(pBuf,BUF_SIZE); |
| 232 | pBuf = nullptr; |
| 233 | } |
| 234 | if (fd_shm!=-1) { |
| 235 | shm_unlink(mShareMemoryId.toLocal8Bit().data()); |
| 236 | fd_shm = -1; |
| 237 | } |
| 238 | #endif |
| 239 | setPausing(true); |
| 240 | emit pausingForFinish(); |
| 241 | } |
| 242 | } |
| 243 | #endif |
| 244 | } |
| 245 | #ifdef Q_OS_WIN |
| 246 | if (pBuf) |
| 247 | UnmapViewOfFile(pBuf); |
| 248 | if (hSharedMemory!=INVALID_HANDLE_VALUE && hSharedMemory!=NULL) |
| 249 | CloseHandle(hSharedMemory); |
| 250 | #elif defined(Q_OS_LINUX) |
| 251 | if (pBuf) { |
| 252 | munmap(pBuf,BUF_SIZE); |
| 253 | } |
| 254 | if (fd_shm!=-1) { |
| 255 | shm_unlink(mShareMemoryId.toLocal8Bit().data()); |
| 256 | } |
| 257 | #endif |
| 258 | if (errorOccurred) { |
| 259 | //qDebug()<<"process error:"<<process.error(); |
| 260 | switch (mProcess->error()) { |
| 261 | case QProcess::FailedToStart: |
| 262 | emit runErrorOccurred(tr("The runner process '%1' failed to start." ).arg(mFilename)); |
| 263 | break; |
| 264 | // case QProcess::Crashed: |
| 265 | // if (!mStop) |
| 266 | // emit runErrorOccurred(tr("The runner process crashed after starting successfully.")); |
| 267 | // break; |
| 268 | case QProcess::Timedout: |
| 269 | emit runErrorOccurred(tr("The last waitFor...() function timed out." )); |
| 270 | break; |
| 271 | case QProcess::WriteError: |
| 272 | emit runErrorOccurred(tr("An error occurred when attempting to write to the runner process." )); |
| 273 | break; |
| 274 | case QProcess::ReadError: |
| 275 | emit runErrorOccurred(tr("An error occurred when attempting to read from the runner process." )); |
| 276 | break; |
| 277 | default: |
| 278 | break; |
| 279 | } |
| 280 | } |
| 281 | mQuitSemaphore.release(1); |
| 282 | } |
| 283 | |
| 284 | void ExecutableRunner::doStop() |
| 285 | { |
| 286 | mQuitSemaphore.acquire(1); |
| 287 | } |
| 288 | |