Created
November 6, 2016 13:32
-
-
Save htfy96/7522921f26501af724d381af3770dc53 to your computer and use it in GitHub Desktop.
Simple Toy shell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile with -std=c++11 -pthread | |
#include <iostream> | |
#include <cstdio> | |
#include <cstddef> | |
#include <csignal> | |
#include <cstdlib> | |
#include <unistd.h> | |
#include <sys/wait.h> | |
#include <string> | |
#include <vector> | |
#include <list> | |
#include <algorithm> | |
#include <thread> | |
std::vector<pid_t> pids; | |
static const std::string WELCOME_STR = "Welcome to htShell!"; | |
struct ParsedCmd | |
{ | |
std::vector<std::string> args; | |
bool isBackground; | |
}; | |
struct ParsedInput | |
{ | |
std::vector<ParsedCmd> pipedCmd; | |
}; | |
ParsedCmd parseCmd(std::string cmd) | |
{ | |
ParsedCmd pc; | |
pc.isBackground = false; | |
std::string cur; | |
for (std::size_t i=0; i<cmd.size(); ++i) | |
{ | |
if (cmd[i] == ' ' || cmd[i] == '&') | |
{ | |
if (!cur.empty()) | |
{ | |
pc.args.push_back(cur); | |
cur.clear(); | |
} | |
if (cmd[i] == '&') | |
pc.isBackground = true; | |
} | |
else | |
cur += cmd[i]; | |
} | |
if (!cur.empty()) | |
pc.args.push_back(cur); | |
return pc; | |
} | |
ParsedInput parseInput(std::string cmd) | |
{ | |
std::string cur; | |
ParsedInput pi; | |
for (std::size_t i=0; i<cmd.size(); ++i) | |
{ | |
if (cmd[i] == '|') | |
{ | |
if (std::count(cur.cbegin(), cur.cend(), ' ') != cur.length()) | |
pi.pipedCmd.push_back(parseCmd(cur)); | |
cur = ""; | |
} | |
else | |
cur += cmd[i]; | |
} | |
if (std::count(cur.cbegin(), cur.cend(), ' ') != cur.length()) | |
pi.pipedCmd.push_back(parseCmd(cur)); | |
return pi; | |
} | |
void setup() | |
{ | |
std::cout << WELCOME_STR << std::endl; | |
} | |
std::string readInput() | |
{ | |
std::string ans; | |
getline(std::cin, ans); | |
return ans; | |
} | |
void handle_ch_SIGINT(int signal) | |
{ | |
exit(EXIT_FAILURE); | |
} | |
struct Task | |
{ | |
pid_t tpid; | |
std::string input; | |
bool isBackground; | |
std::thread thr; | |
}; | |
std::list<Task> tasks; | |
void task_watch(pid_t tpid, std::string input, bool isBackground) | |
{ | |
Task t { tpid, input, isBackground}; | |
auto it = tasks.insert(tasks.cbegin(), std::move(t)); | |
it->thr = std::move(std::thread([tpid, isBackground, it]() { | |
waitpid(tpid, NULL, 0); | |
if (isBackground) | |
tasks.erase(it); | |
})); | |
if (isBackground) | |
tasks.rbegin()->thr.detach(); | |
if (!isBackground) | |
{ | |
it->thr.join(); | |
tasks.erase(it); | |
} | |
} | |
void handleInput(const ParsedInput &pi) | |
{ | |
// handle null input | |
if (pi.pipedCmd.empty()) | |
return; | |
if (pi.pipedCmd.size() == 1) | |
{ | |
ParsedCmd pc = pi.pipedCmd[0]; | |
if (pc.args[0] == "cd" && pc.args.size() == 2) | |
{ | |
if (chdir(pc.args[1].c_str()) < 0) | |
perror("cd"); | |
return; | |
} | |
if (pc.args[0] == "jobs" && pc.args.size() == 1) | |
{ | |
int cnt = 0; | |
std::for_each(tasks.begin(), tasks.end(), [&cnt](const Task& ta) | |
{ | |
std::cout << cnt++ << "\t" << "[" << ta.tpid << "]" << ta.input << std::endl; | |
}); | |
return; | |
} | |
if (pc.args[0] == "fg" && pc.args.size() == 2) | |
{ | |
int gpid = std::atoi(pc.args[1].c_str()); | |
pid_t stdinpgrp = tcgetpgrp(STDIN_FILENO); | |
signal(SIGTTOU, SIG_IGN); | |
if (tcsetpgrp(STDIN_FILENO, gpid)) | |
{ | |
perror("tcsetpgrp: stdin"); | |
} | |
if (kill(-gpid, SIGCONT)) | |
perror("kill"); | |
waitpid(gpid, NULL, 0); | |
if (tcsetpgrp(STDIN_FILENO, stdinpgrp)) | |
{ | |
perror("tcsetpgrp: restore"); | |
} | |
signal(SIGTTOU, SIG_DFL); | |
return; | |
} | |
} | |
pid_t stdinpgrp = tcgetpgrp(STDIN_FILENO); | |
pid_t gpid; | |
if (gpid = fork()) // top-level ch3 program | |
{ | |
setpgid(gpid, gpid); | |
signal(SIGTTOU, SIG_IGN); | |
if (tcsetpgrp(STDIN_FILENO, gpid)) | |
{ | |
perror("tcsetpgrp: stdin"); | |
} | |
bool isBackground = false; | |
std::for_each(pi.pipedCmd.begin(), pi.pipedCmd.end(), [&](const ParsedCmd& pc){ | |
isBackground |= pc.isBackground; | |
}); | |
task_watch(gpid, pi.pipedCmd[0].args[0], isBackground); | |
if (tcsetpgrp(STDIN_FILENO, stdinpgrp)) | |
{ | |
perror("tcsetpgrp: restore"); | |
} | |
signal(SIGTTOU, SIG_DFL); | |
} else | |
{ | |
pids.clear(); | |
// Process in charge of whole task | |
setpgid(0, 0); | |
pid_t pgid = getpgid(0); | |
bool isBackground = false; | |
std::vector<std::pair<int, int> > pipes; | |
for (std::size_t i=0; i<pi.pipedCmd.size() -1; ++i) | |
{ | |
int flides[2]; | |
pipe(flides); | |
pipes.push_back(std::make_pair(flides[1], flides[0])); | |
} | |
for (std::size_t i=0; i<pi.pipedCmd.size(); ++i) | |
{ | |
ParsedCmd cmd = pi.pipedCmd[i]; | |
isBackground |= cmd.isBackground; | |
char* args_arr[1024]; | |
for (std::size_t j=0; j<cmd.args.size(); ++j) | |
args_arr[j] = const_cast<char*>(cmd.args[j].c_str()); | |
args_arr[cmd.args.size()] = NULL; | |
if (cmd.args.empty()) | |
continue; | |
pid_t pid2; | |
if (pid2 = fork()) | |
{ | |
pids.push_back(pid2); | |
} else | |
{ // Process of each command | |
signal(SIGTTOU, SIG_DFL); | |
setpgid(0, pgid); | |
if (i>0) | |
dup2(pipes[i-1].second, STDIN_FILENO); | |
if (i<pi.pipedCmd.size() -1) | |
{ | |
dup2(pipes[i].first, STDOUT_FILENO); | |
} | |
// Close unused pipes | |
for (std::size_t j=0; j<pipes.size(); ++j) | |
{ | |
if (i-1 != j) | |
close(pipes[j].second); | |
if (i!=j) | |
close(pipes[j].first); | |
} | |
if (execvp(args_arr[0], args_arr) < 0) | |
perror("Execvp"); | |
} | |
} | |
int cnt = pids.size(); | |
std::vector<bool> is_closed(pipes.size()); | |
while (cnt > 0) | |
{ | |
pid_t chpid = wait(NULL); | |
int idx = std::find(pids.begin(), pids.end(), chpid) - pids.begin(); | |
if (idx>0 && !is_closed[idx-1]) | |
{ | |
is_closed[idx-1] = true; | |
if (close(pipes[idx-1].first)) | |
perror("close pipe"); | |
if (close(pipes[idx-1].second)) | |
perror("close pipe"); | |
} | |
if (idx < pids.size() - 1 && !is_closed[idx]) | |
{ | |
is_closed[idx] = true; | |
if ( close(pipes[idx].first) ) | |
perror("close pipe"); | |
if (close(pipes[idx].second)) | |
perror("close pipe"); | |
} | |
--cnt; | |
std::cout << "[pid="<<chpid<<"] terminated" << std::endl; | |
} | |
exit(0); | |
} | |
} | |
std::vector<std::string> history; | |
void handle_default(int signal) | |
{ | |
int cnt = history.size() < 5 ? history.size() : 5; | |
int idx = 0; | |
std::cout << std::endl; | |
std::cout << "History:" << std::endl; | |
for (std::size_t i=history.size() - cnt; i<history.size(); ++i) | |
{ | |
std::cout << idx << ". " << history[i] << std::endl; | |
++idx; | |
} | |
} | |
int main() | |
{ | |
const std::size_t MAX_CWDBUF = 4096; | |
char cwdbuf[MAX_CWDBUF]; | |
signal(SIGINT, handle_default); | |
setup(); | |
while (!std::cin.eof()) | |
{ | |
char *pCwd = getcwd(cwdbuf, MAX_CWDBUF); | |
std::fflush(stdout); | |
if (!pCwd) | |
std::perror("getCwd"); | |
else | |
std::cout << pCwd << " >> "; | |
std::string input = readInput(); | |
history.push_back(input); | |
ParsedInput pc = parseInput(input); | |
handleInput(pc); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment