-
Notifications
You must be signed in to change notification settings - Fork 0
/
flourish.cc
316 lines (277 loc) · 10.8 KB
/
flourish.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <regex>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>
using namespace std;
void startUp();
void ctrlC(int s);
void parseInput(string inputString, vector<string> &parsedInput);
bool bangCommandRegex(string bangCommand, vector<string> &parsedInput, char* &buf);
void replaceInput(vector<string> &parsedInput);
void changeDirectory(vector<string> &parsedInput);
void setEnvironmentVariable(vector<string> &parsedInput, char* &buf);
void fileIORedirection(vector<string> &parsedInput, int index, char direction);
vector<string> splitString(char delimiter, string target);
bool isBangCommand(string command);
bool isExit(string command);
bool isChangeDirectory(string command);
bool isEnvVarAssignment(string command);
bool isInputRedirection(string command);
bool isOutputRedirection(string command);
void printMessage(string msg, int type);
void execIt(char** parsedInput);
const char *PROMPT = "PROMPT";
const char *DEFAULT_PROMPT = ">> ";
const string WELCOME_MESSAGE = "**************************************\n*********Welcome to FlouriSH!*********\n*********Type 'EXIT' to quit.*********\n**************************************\n";
int main(int argc, char *argv[]) {
char* buf;
vector<string> parsedInput;
startUp();
while ((buf = readline(getenv("PROMPT"))) != nullptr) { // readline library
if (strlen(buf) > 0) {
//split on ';' characters for executing queued commands, then execute each
vector<string> splitCommands = splitString(';', buf);
for (string command : splitCommands) {
parsedInput.clear();
strcpy(buf, command.c_str());
bool commandOK = true;
if (isBangCommand(buf)) {
commandOK = bangCommandRegex(splitString('!', buf)[1], parsedInput, buf);
}
if (commandOK) {
add_history(buf);
parseInput(buf, parsedInput);
if (isExit(parsedInput[0])) {
exit(0);
}
// replace environment variables and tilde
replaceInput(parsedInput);
if (isChangeDirectory(parsedInput[0])) {
changeDirectory(parsedInput);
}
else if (isEnvVarAssignment(parsedInput[0])) {
setEnvironmentVariable(parsedInput, buf);
}
// continue with normal exec
else {
switch (int id = fork()) {
case -1: { // failed fork
cout << "fork problems" << endl;
break;
}
case 0: { // child
// check size for possibility of file IO redirection
if (parsedInput.size() >= 3) {
for (int i = 1; i < parsedInput.size(); i++) {
if (isOutputRedirection(parsedInput[i])) {
fileIORedirection(parsedInput, i, '>');
}
else if (isInputRedirection(parsedInput[i])) {
fileIORedirection(parsedInput, i, '<');
}
}
}
// transfer everything to vector of char*
vector<char*> cParsedInput;
cParsedInput.reserve(parsedInput.size());
for(size_t i = 0; i < parsedInput.size(); ++i)
cParsedInput.push_back(const_cast<char*>(parsedInput[i].c_str()));
cParsedInput.push_back(NULL);
//exec
execIt(&cParsedInput[0]);
// exec failed if we get here
printMessage(parsedInput[0], 0);
exit(0);
break;
}
default: { // parent
waitpid(-1, NULL, 0);
break;
}
}
}
}
}
free(buf);
}
}
}
// Run on start up to display welcome message, set up catching CTRL+C,
// and to set prompt environment variable to default.
void startUp() {
cout << WELCOME_MESSAGE << endl;
signal(SIGINT, ctrlC);
if (!getenv(PROMPT)) {
setenv(PROMPT, DEFAULT_PROMPT, 1);
}
}
void ctrlC(int s) {
//cout << "ye" << endl;
}
// Parses input string (buf) from user and divides each chunk of the command
// into parsedInput vector.
void parseInput(string inputString, vector<string> &parsedInput) {
istringstream istream (inputString);
string inputChunk;
while (istream >> inputChunk) {
parsedInput.push_back(inputChunk);
}
}
// Returns a boolean indicating if the !bangcommand was a valid command in readline history
bool bangCommandRegex(string bangCommand, vector<string> &parsedInput, char* &buf) {
// get history
HISTORY_STATE *historyState = history_get_history_state ();
HIST_ENTRY **historyList = history_list();
// split ! from string
bangCommand = splitString('!', buf)[1];
if (regex_match(bangCommand, regex("([0-9]*)"))) { // if ![#]
int commandsToGoBack = stoi(bangCommand);
int historyListPos = historyState->length;
if (commandsToGoBack > historyListPos) {
printMessage(buf, 2);
return false;
}
else {
buf = historyList[historyListPos - commandsToGoBack]->line;
cout << buf << endl;
return true;
}
}
else { // it's a ![character]
for (int i = historyState->length - 1; i > 0; i--) {
string line = historyList[i]->line;
if (bangCommand == line.substr(0, bangCommand.length())) {
buf = historyList[i]->line;
cout << buf << endl;
return true;
}
if (i == 0) {
printMessage(buf, 2);
return false;
}
}
}
}
// Checks each entry in parsedInput for an environment variable or tilde and replaces
// them as needed.
void replaceInput(vector<string> &parsedInput) {
for (int i = 0; i < parsedInput.size(); i++) {
if (parsedInput[i][0] == '$') {
parsedInput[i] = parsedInput[i].substr(1, parsedInput[i].length() - 1);
if (getenv(parsedInput[i].c_str()) != NULL) {
parsedInput[i] = getenv(parsedInput[i].c_str());
}
else {
// if the environment variable does not exist, we just remove it from the command
parsedInput.erase(parsedInput.begin() + i);
}
}
int tildeReplaceLocation = parsedInput[i].find("~");
if (tildeReplaceLocation != string::npos) {
parsedInput[i].erase(tildeReplaceLocation, 1);
parsedInput[i].insert(tildeReplaceLocation, getenv("HOME"));
}
}
}
void changeDirectory(vector<string> &parsedInput) {
if (chdir(parsedInput[1].c_str()) == -1) {
printMessage(parsedInput[0], 1);
}
}
void setEnvironmentVariable(vector<string> &parsedInput, char* &buf) {
vector<string> splitAssignment = splitString('=', buf);
setenv(splitAssignment[0].c_str(), splitAssignment[1].c_str(), 1);
}
void fileIORedirection(vector<string> &parsedInput, int index, char direction) {
switch (direction) {
case '>': {
int fd = open(parsedInput[index+1].c_str(), O_RDWR | O_CREAT, 0660);
if (fd == -1) {
printMessage("open failed", 3);
exit(0);
}
parsedInput.erase(parsedInput.begin() + index, parsedInput.end());
dup2(fd, 1);
break;
}
case '<': {
int fd = open(parsedInput[index+1].c_str(), O_RDONLY);
if (fd == -1) {
printMessage("open failed", 3);
exit(0);
}
parsedInput.erase(parsedInput.begin() + index, parsedInput.end());
dup2(fd, 0);
break;
}
}
}
// Returns a vector of strings that are the contents of the target string
// split on the given delimiter.
vector<string> splitString(char delimiter, string target) {
vector<string> splitStrings;
istringstream istream(target);
string token;
while (getline(istream, token, delimiter)) {
splitStrings.push_back(token);
}
return splitStrings;
}
bool isBangCommand(string command) {
return regex_match(command, regex("(!)(.+)"));
}
bool isExit(string command) {
return (regex_match(command, regex("([[:space:]]*)(EXIT)([[:space:]]*)")) || command == "EXIT");
}
bool isChangeDirectory(string command) {
return (command == "cd");
}
bool isEnvVarAssignment(string command) {
return (regex_match(command, regex("(.+)(=)(.+)")));
}
bool isInputRedirection(string command) {
return (command == "<");
}
bool isOutputRedirection(string command) {
return (command == ">");
}
void printMessage(string msg, int type) {
switch(type) {
case 0: { // bad command
cout << "flourish: " << msg << ": command not found" << endl;
break;
}
case 1: { // bad chdir
cout << "flourish: " << msg << ": no such directory" << endl;
break;
}
case 2: { // bad !event
cout << "flourish: " << msg << ": event not found" << endl;
break;
}
case 3: { // bad file
cout << "flourish: " << msg << ": file not found" << endl;
break;
}
}
}
void execIt(char** parsedInput) {
// attempt to execv on input for explicit file path
execv(parsedInput[0], parsedInput);
// attempt to execv on all possible paths in PATH environment variable
vector<string> paths = splitString(':', getenv("PATH"));
for (const auto& path : paths) {
execv((path + "/" + parsedInput[0]).c_str(), parsedInput);
}
}