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 | |