Untitled
unknown
plain_text
16 days ago
10 kB
6
Indexable
Never
/** * 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); //*/ }
Leave a Comment