Untitled
unknown
plain_text
a year ago
10 kB
13
Indexable
/**
* Shell framework
* course Operating Systems
* Radboud University
* v22.09.05
Student names:
- ...
- ...
*/
/**
* Hint: in most IDEs (Visual Studio Code, Qt Creator, neovim) you can:
* - Control-click on a function name to go to the definition
* - Ctrl-space to auto complete functions and variables
*/
// function/class definitions you are going to use
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/param.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <vector>
#include <list>
#include <optional>
// although it is good habit, you don't have to type 'std' before many objects by including this line
using namespace std;
struct Command
{
vector<string> parts = {};
};
struct Expression
{
vector<Command> commands;
string inputFromFile;
string outputToFile;
bool background = false;
};
// Parses a string to form a vector of arguments. The separator is a space char (' ').
vector<string> split_string(const string &str, char delimiter = ' ')
{
vector<string> retval;
for (size_t pos = 0; pos < str.length();)
{
// look for the next space
size_t found = str.find(delimiter, pos);
// if no space was found, this is the last word
if (found == string::npos)
{
retval.push_back(str.substr(pos));
break;
}
// filter out consequetive spaces
if (found != pos)
retval.push_back(str.substr(pos, found - pos));
pos = found + 1;
}
return retval;
}
// wrapper around the C execvp so it can be called with C++ strings (easier to work with)
// always start with the command itself
// DO NOT CHANGE THIS FUNCTION UNDER ANY CIRCUMSTANCE
int execvp(const vector<string> &args)
{
// build argument list
const char **c_args = new const char *[args.size() + 1];
for (size_t i = 0; i < args.size(); ++i)
{
c_args[i] = args[i].c_str();
}
c_args[args.size()] = nullptr;
// replace current process with new process as specified
int rc = ::execvp(c_args[0], const_cast<char **>(c_args));
// if we got this far, there must be an error
int error = errno;
// in case of failure, clean up memory (this won't overwrite errno normally, but let's be sure)
delete[] c_args;
errno = error;
return rc;
}
// Executes a command with arguments. In case of failure, returns error code.
int execute_command(const Command &cmd)
{
auto &parts = cmd.parts;
if (parts.size() == 0)
return EINVAL;
// execute external commands
int retval = execvp(parts);
if (retval == -1)
{
if (errno == ENOENT)
{
cerr << "Command not found: " << parts[0] << endl;
}
else
{
perror("execvp");
}
}
return errno;
}
void display_prompt()
{
char buffer[512];
char *dir = getcwd(buffer, sizeof(buffer));
if (dir)
{
cout << "\e[32m" << dir << "\e[39m"; // the strings starting with '\e' are escape codes, that the terminal application interpets in this case as "set color to green"/"set color to default"
}
cout << "$ ";
flush(cout);
}
string request_command_line(bool showPrompt)
{
if (showPrompt)
{
display_prompt();
}
string retval;
getline(cin, retval);
return retval;
}
// note: For such a simple shell, there is little need for a full-blown parser (as in an LL or LR capable parser).
// Here, the user input can be parsed using the following approach.
// First, divide the input into the distinct commands (as they can be chained, separated by `|`).
// Next, these commands are parsed separately. The first command is checked for the `<` operator, and the last command for the `>` operator.
Expression parse_command_line(string commandLine)
{
Expression expression;
vector<string> commands = split_string(commandLine, '|');
for (size_t i = 0; i < commands.size(); ++i)
{
string &line = commands[i];
vector<string> args = split_string(line, ' ');
if (i == commands.size() - 1 && args.size() > 1 && args[args.size() - 1] == "&")
{
expression.background = true;
args.resize(args.size() - 1);
}
if (i == commands.size() - 1 && args.size() > 2 && args[args.size() - 2] == ">")
{
expression.outputToFile = args[args.size() - 1];
args.resize(args.size() - 2);
}
if (i == 0 && args.size() > 2 && args[args.size() - 2] == "<")
{
expression.inputFromFile = args[args.size() - 1];
args.resize(args.size() - 2);
}
expression.commands.push_back({args});
}
return expression;
}
int execute_expression(Expression &expression)
{
// Check if there are no commands
if (expression.commands.empty())
{
cerr << "Error: Empty command" << endl;
return errno; // Print our custom error only
}
const auto &firstCommand = expression.commands.front().parts;
if (firstCommand.empty())
{
cerr << "Error: Empty command" << endl;
return errno; // Print our custom error only
}
// Handle 'exit' command
if (firstCommand[0] == "exit")
{
cout << "Exiting shell..." << endl;
exit(0);
}
// Handle 'cd' command
if (firstCommand[0] == "cd")
{
if (firstCommand.size() != 2)
{
cerr << "Invalid usage of cd\nUsage: cd <directory>" << endl;
return errno;
}
if (chdir(firstCommand[1].c_str()) == -1)
{
return errno; // Prints the error message
}
return EXIT_SUCCESS;
}
// External commands, executed with fork():
int numCommands = expression.commands.size();
int pipefds[2 * (numCommands - 1)]; // Pipes to connect commands
// Create pipes for each command except the last one
for (int i = 0; i < numCommands - 1; i++)
{
if (pipe(pipefds + i * 2) == -1)
{
perror("pipe");
return errno;
}
}
for (int i = 0; i < numCommands; i++)
{
pid_t pid = fork();
if (pid == -1)
{
perror("fork");
return errno;
}
if (pid == 0)
{ // Child process
// Handle input redirection for the first command
if (i == 0 && !expression.inputFromFile.empty())
{
int fd = open(expression.inputFromFile.c_str(), O_RDONLY);
if (fd == -1)
{
perror("open input file");
exit(errno);
}
dup2(fd, STDIN_FILENO);
close(fd);
}
// Handle output redirection for the last command
if (i == numCommands - 1 && !expression.outputToFile.empty())
{
int fd = open(expression.outputToFile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
{
perror("open output file");
exit(errno);
}
dup2(fd, STDOUT_FILENO);
close(fd);
}
// Set up pipes between commands
if (numCommands > 1)
{
if (i > 0)
{
// Input from the previous command's pipe
dup2(pipefds[(i - 1) * 2], STDIN_FILENO);
}
if (i < numCommands - 1)
{
// Output to the next command's pipe
dup2(pipefds[i * 2 + 1], STDOUT_FILENO);
}
}
// Close all pipe file descriptors in the child process
for (int j = 0; j < 2 * (numCommands - 1); j++)
{
close(pipefds[j]);
}
// Execute the command (We already print the error message earlier)
if (execute_command(expression.commands[i]) != 0)
{
exit(EXIT_FAILURE);
}
}
}
// Close all pipe file descriptors in the parent process
for (int i = 0; i < 2 * (numCommands - 1); i++)
{
close(pipefds[i]);
}
// Wait for child processes to finish unless running in the background
if (!expression.background)
{
for (int i = 0; i < numCommands; i++)
{
wait(nullptr); // Parent waits for child process to complete
}
}
else
{
cout << "Process runs in background!" << endl;
}
return EXIT_SUCCESS;
}
// framework for executing "date | tail -c 5" using raw commands
// two processes are created, and connected to each other
int step1(bool showPrompt)
{
// create communication channel shared between the two processes
int pipefd[2];
if (pipe(pipefd) == -1)
{
cout << "no";
return -1;
}
pid_t child1 = fork();
if (child1 == 0)
{
// redirect standard output (STDOUT_FILENO) to the input of the shared communication channel
// free non used resources (why?)
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[0]);
close(pipefd[1]);
Command cmd = {{string("date")}};
execute_command(cmd);
// display nice warning that the executable could not be found
abort(); // if the executable is not found, we should abort. (why?)
}
pid_t child2 = fork();
if (child2 == 0)
{
// redirect the output of the shared communication channel to the standard input (STDIN_FILENO).
// free non used resources (why?)
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
close(pipefd[1]);
Command cmd = {{string("tail"), string("-c"), string("5")}};
execute_command(cmd);
abort(); // if the executable is not found, we should abort. (why?)
}
// Close read and write pipes
close(pipefd[0]);
close(pipefd[1]);
// wait on child processes to finish (why both?)
waitpid(child1, nullptr, 0);
waitpid(child2, nullptr, 0);
return 0;
}
int shell(bool showPrompt)
{
//* <- remove one '/' in front of the other '/' to switch from the normal code to step1 code
while (cin.good())
{
string commandLine = request_command_line(showPrompt);
Expression expression = parse_command_line(commandLine);
int rc = execute_expression(expression);
if (rc != 0)
cerr << strerror(rc) << endl;
}
return 0;
/*/
return step1(showPrompt);
//*/
}
Editor is loading...
Leave a Comment