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
35ExecutableRunner::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
45bool ExecutableRunner::startConsole() const
46{
47 return mStartConsole;
48}
49
50void ExecutableRunner::setStartConsole(bool newStartConsole)
51{
52 mStartConsole = newStartConsole;
53}
54
55const QString &ExecutableRunner::shareMemoryId() const
56{
57 return mShareMemoryId;
58}
59
60void ExecutableRunner::setShareMemoryId(const QString &newShareMemoryId)
61{
62 mShareMemoryId = newShareMemoryId;
63}
64
65const QStringList &ExecutableRunner::binDirs() const
66{
67 return mBinDirs;
68}
69
70void ExecutableRunner::addBinDirs(const QStringList &binDirs)
71{
72 mBinDirs.append(binDirs);
73}
74
75void ExecutableRunner::addBinDir(const QString &binDir)
76{
77 mBinDirs.append(binDir);
78}
79
80bool ExecutableRunner::redirectInput() const
81{
82 return mRedirectInput;
83}
84
85void ExecutableRunner::setRedirectInput(bool isRedirect)
86{
87 mRedirectInput = isRedirect;
88}
89
90const QString &ExecutableRunner::redirectInputFilename() const
91{
92 return mRedirectInputFilename;
93}
94
95void ExecutableRunner::setRedirectInputFilename(const QString &newDataFile)
96{
97 mRedirectInputFilename = newDataFile;
98}
99
100void 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
284void ExecutableRunner::doStop()
285{
286 mQuitSemaphore.acquire(1);
287}
288