Untitled

mail@pastecode.io avatar
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