Skip to content

Commit 6b1d26e

Browse files
committed
Comprehensive multi-platform MyShell source file implementation.
1 parent fd03224 commit 6b1d26e

File tree

1 file changed

+377
-0
lines changed

1 file changed

+377
-0
lines changed

src/myshell.cpp

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
/*
2+
* Copyright (c) 2024 - Nathanne Isip
3+
* This file is part of MyShell.
4+
*
5+
* N8 is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published
7+
* by the Free Software Foundation, either version 3 of the License,
8+
* or (at your option) any later version.
9+
*
10+
* N8 is distributed in the hope that it will be useful, but
11+
* WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with N8. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include <chrono>
20+
#include <cstring>
21+
#include <myshell.hpp>
22+
#include <stdexcept>
23+
#include <system_error>
24+
25+
using namespace std::chrono_literals;
26+
27+
MyShell::MyShell(std::string command) :
28+
procHasExited(false),
29+
procExitCode(0),
30+
stopSignal(false)
31+
{
32+
#ifdef _WIN32
33+
this->createWindowsProcess(command);
34+
#else
35+
this->createUnixProcess(command);
36+
#endif
37+
38+
this->outputReader = std::thread(
39+
&MyShell::outputReaderThread,
40+
this
41+
);
42+
43+
this->errorReader = std::thread(
44+
&MyShell::errorReaderThread,
45+
this
46+
);
47+
}
48+
49+
MyShell::~MyShell() {
50+
this->stopSignal = true;
51+
52+
if(this->outputReader.joinable())
53+
this->outputReader.join();
54+
if(this->errorReader.joinable())
55+
this->errorReader.join();
56+
57+
#ifdef _WIN32
58+
59+
if(this->procHandle)
60+
CloseHandle(this->procHandle);
61+
if(this->inputWriteHandle)
62+
CloseHandle(this->inputWriteHandle);
63+
if(this->outputReadHandle)
64+
CloseHandle(this->outputReadHandle);
65+
if(this->errorReadHandle)
66+
CloseHandle(this->errorReadHandle);
67+
68+
#else
69+
this->closeUnixPipes();
70+
#endif
71+
}
72+
73+
#ifdef _WIN32
74+
void MyShell::createWindowsProcess(const std::string& command) {
75+
SECURITY_ATTRIBUTES saAttr;
76+
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
77+
saAttr.bInheritHandle = TRUE;
78+
saAttr.lpSecurityDescriptor = NULL;
79+
80+
HANDLE inputRead, outputWrite, errorWrite;
81+
if(!CreatePipe(&inputRead, &this->inputWriteHandle, &saAttr, 0) ||
82+
!CreatePipe(&this->outputReadHandle, &this->outputWrite, &saAttr, 0) ||
83+
!CreatePipe(&this->errorReadHandle, &this->errorWrite, &saAttr, 0))
84+
throw std::system_error(
85+
GetLastError(),
86+
std::system_category(),
87+
"Failed to create pipes"
88+
);
89+
90+
SetHandleInformation(this->inputWriteHandle, HANDLE_FLAG_INHERIT, 0);
91+
SetHandleInformation(this->outputReadHandle, HANDLE_FLAG_INHERIT, 0);
92+
SetHandleInformation(this->errorReadHandle, HANDLE_FLAG_INHERIT, 0);
93+
94+
STARTUPINFO siStartInfo;
95+
PROCESS_INFORMATION piProcInfo;
96+
97+
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
98+
siStartInfo.cb = sizeof(STARTUPINFO);
99+
siStartInfo.hStdInput = inputRead;
100+
siStartInfo.hStdOutput = outputWrite;
101+
siStartInfo.hStdError = errorWrite;
102+
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
103+
104+
if(!CreateProcessA(
105+
NULL,
106+
const_cast<LPSTR>(command.c_str()),
107+
NULL, NULL, TRUE,
108+
CREATE_NO_WINDOW,
109+
NULL, NULL,
110+
&siStartInfo, &piProcInfo
111+
)) throw std::system_error(
112+
GetLastError(),
113+
std::system_category(),
114+
"Failed to create process"
115+
);
116+
117+
this->procHandle = piProcInfo.hProcess;
118+
this->procId = piProcInfo.dwProcessId;
119+
120+
CloseHandle(piProcInfo.hThread);
121+
CloseHandle(inputRead);
122+
CloseHandle(outputWrite);
123+
CloseHandle(errorWrite);
124+
}
125+
126+
void MyShell::readFromPipe(HANDLE pipe, std::string& buffer, std::mutex& mutex) {
127+
std::vector<char> tempBuffer(this->bufferSize);
128+
DWORD bytesRead;
129+
130+
while(!stopSignal) {
131+
if(!PeekNamedPipe(pipe, NULL, 0, NULL, &bytesRead, NULL))
132+
return;
133+
134+
if(bytesRead > 0) {
135+
if(ReadFile(
136+
pipe,
137+
tempBuffer.data(),
138+
this->bufferSize - 1,
139+
&bytesRead,
140+
NULL
141+
) && bytesRead > 0) {
142+
tempBuffer[bytesRead] = '\0';
143+
144+
std::lock_guard<std::mutex> lock(mutex);
145+
buffer += tempBuffer.data();
146+
}
147+
}
148+
else std::this_thread::sleep_for(10ms);
149+
}
150+
}
151+
152+
#else
153+
154+
void MyShell::createUnixProcess(const std::string& command) {
155+
if(pipe(this->inputPipe) == -1 ||
156+
pipe(this->outputPipe) == -1 ||
157+
pipe(this->errorPipe) == -1)
158+
throw std::system_error(
159+
errno,
160+
std::system_category(),
161+
"Failed to create pipes"
162+
);
163+
164+
this->procId = fork();
165+
if(this->procId == -1) {
166+
this->closeUnixPipes();
167+
168+
throw std::system_error(
169+
errno,
170+
std::system_category(),
171+
"Failed to fork process"
172+
);
173+
}
174+
175+
if(this->procId == 0) {
176+
dup2(this->inputPipe[0], STDIN_FILENO);
177+
dup2(this->outputPipe[1], STDOUT_FILENO);
178+
dup2(this->errorPipe[1], STDERR_FILENO);
179+
180+
close(this->inputPipe[0]);
181+
close(this->inputPipe[1]);
182+
close(this->outputPipe[0]);
183+
close(this->outputPipe[1]);
184+
close(this->errorPipe[0]);
185+
close(this->errorPipe[1]);
186+
187+
execl("/bin/sh", "sh", "-c", command.c_str(), NULL);
188+
exit(1);
189+
}
190+
191+
close(this->inputPipe[0]);
192+
close(this->outputPipe[1]);
193+
close(this->errorPipe[1]);
194+
195+
fcntl(this->outputPipe[0], F_SETFL, O_NONBLOCK);
196+
fcntl(this->errorPipe[0], F_SETFL, O_NONBLOCK);
197+
}
198+
199+
void MyShell::closeUnixPipes() {
200+
close(this->inputPipe[1]);
201+
close(this->outputPipe[0]);
202+
close(this->errorPipe[0]);
203+
}
204+
205+
void MyShell::readFromPipe(int pipe, std::string& buffer, std::mutex& mutex) {
206+
std::vector<char> tempBuffer(this->bufferSize);
207+
ssize_t bytesRead;
208+
209+
while(!stopSignal) {
210+
bytesRead = read(
211+
pipe,
212+
tempBuffer.data(),
213+
this->bufferSize - 1
214+
);
215+
216+
if(bytesRead > 0) {
217+
tempBuffer[bytesRead] = '\0';
218+
219+
std::lock_guard<std::mutex> lock(mutex);
220+
buffer += tempBuffer.data();
221+
}
222+
else if(bytesRead == -1 && errno == EAGAIN)
223+
std::this_thread::sleep_for(10ms);
224+
}
225+
}
226+
227+
#endif
228+
229+
void MyShell::outputReaderThread() {
230+
#ifdef _WIN32
231+
this->readFromPipe(
232+
this->outputReadHandle,
233+
this->outputBuffer,
234+
this->outputMutex
235+
);
236+
#else
237+
this->readFromPipe(
238+
this->outputPipe[0],
239+
this->outputBuffer,
240+
this->outputMutex
241+
);
242+
#endif
243+
}
244+
245+
void MyShell::errorReaderThread() {
246+
#ifdef _WIN32
247+
248+
this->readFromPipe(
249+
this->errorReadHandle,
250+
this->errorBuffer,
251+
this->errorMutex
252+
);
253+
254+
#else
255+
256+
this->readFromPipe(
257+
this->errorPipe[0],
258+
this->errorBuffer,
259+
this->errorMutex
260+
);
261+
262+
#endif
263+
}
264+
265+
std::string MyShell::readShellOutputStream() {
266+
std::lock_guard<std::mutex> lock(this->outputMutex);
267+
std::string result = std::move(this->outputBuffer);
268+
269+
outputBuffer.clear();
270+
return result;
271+
}
272+
273+
std::string MyShell::readShellErrorStream() {
274+
std::lock_guard<std::mutex> lock(this->errorMutex);
275+
std::string result = std::move(this->errorBuffer);
276+
277+
this->errorBuffer.clear();
278+
return result;
279+
}
280+
281+
void MyShell::writeToShell(std::string input) {
282+
#ifdef _WIN32
283+
284+
DWORD bytesWritten;
285+
if(!WriteFile(
286+
this->inputWriteHandle,
287+
input.c_str(),
288+
static_cast<DWORD>(input.length()),
289+
&bytesWritten,
290+
NULL
291+
)) throw std::system_error(
292+
GetLastError(),
293+
std::system_category(),
294+
"Failed to write to process"
295+
);
296+
297+
#else
298+
299+
if(write(
300+
this->inputPipe[1],
301+
input.c_str(),
302+
input.length()
303+
) == -1)
304+
throw std::system_error(
305+
errno,
306+
std::system_category(),
307+
"Failed to write to process"
308+
);
309+
310+
#endif
311+
}
312+
313+
void MyShell::forceExit() {
314+
#ifdef _WIN32
315+
TerminateProcess(procHandle, 1);
316+
#else
317+
kill(this->procId, SIGTERM);
318+
#endif
319+
320+
this->procHasExited = true;
321+
this->procExitCode = 1;
322+
}
323+
324+
bool MyShell::hasExited() {
325+
if(this->procHasExited) {
326+
this->readShellOutputStream();
327+
this->readShellErrorStream();
328+
return true;
329+
}
330+
331+
#ifdef _WIN32
332+
333+
DWORD exitCode;
334+
if(GetExitCodeProcess(this->procHandle, &exitCode)) {
335+
if(exitCode != STILL_ACTIVE) {
336+
procHasExited = true;
337+
procExitCode = static_cast<int>(exitCode);
338+
339+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
340+
this->readShellOutputStream();
341+
this->readShellErrorStream();
342+
}
343+
}
344+
345+
#else
346+
347+
int status;
348+
pid_t result = waitpid(
349+
this->procId,
350+
&status,
351+
WNOHANG
352+
);
353+
354+
if(result > 0) {
355+
this->procHasExited = true;
356+
this->procExitCode = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
357+
358+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
359+
this->readShellOutputStream();
360+
this->readShellErrorStream();
361+
}
362+
363+
#endif
364+
365+
return this->procHasExited;
366+
}
367+
368+
int MyShell::exitCode() {
369+
if(!this->procHasExited)
370+
this->hasExited();
371+
372+
return this->procExitCode;
373+
}
374+
375+
int MyShell::processId() {
376+
return static_cast<int>(this->procId);
377+
}

0 commit comments

Comments
 (0)