Unix-Shell-Terminal / mysh.cpp
mysh.cpp
Raw
// Daniel Biller
// 2/10/2021
// Operating Systems - Spring 2021

// Compile with '-lstdc++fs'

#include <cstring>
#include <experimental/filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>

using namespace std;
namespace fs = std::experimental::filesystem;

#define HISTORY_FILE "history.txt"

class Shell
{
    public:
        // Methods
        Shell();
        void runCommand(string);
    
    private:
        // Fields
        string currentdir, input;
        vector<string> tokens, history;
        vector<vector<string>>processes;
        int tsize, hsize;

        // Methods
        void tokenize();
        void movetodir();
        void whereami();
        void getHistory();
        void byebye();
        void replay();
        pid_t start(bool, bool);
        void dalek();
        void dalekall();
        void repeat();
        void ps();
};

// Constructor
Shell::Shell()
{
    char buff[FILENAME_MAX];
    string line;

    // Init current directory
    getcwd(buff, FILENAME_MAX);
    string tempString(buff);
    currentdir = tempString;

    // Load history from file
    ifstream fin;
    fin.open(HISTORY_FILE);
    
    while (getline(fin, line))
        history.push_back(line);

    fin.close();

    // Update history size
    hsize = history.size();
}

void Shell::tokenize()
{
    int i;

    // Clear token vector
    tokens.clear();

    // Append null char for parsing
    input.push_back('\0');

    // While string has contents
    while (true)
    {
        // Find index of next space
        for (i = 0; input.at(i) != '\0' && input.at(i) != ' '; i++) 
        {;}

        // Save token
        if (i > 0)
            tokens.push_back(input.substr(0,i));

        // Delete saved token from cmd string unless final token
        if (input.at(i) == ' ')
            input.erase(0, i+1);
        else
            return;
    }
}

void Shell::movetodir()
{
    // Error checking
    if (tsize < 2)
    {
        cout << "Too few arguments.\n";
        return;
    }

    // Check if directory exists
    if (fs::is_directory(tokens.at(1)))
    {
        currentdir = tokens.at(1);
        
        // Strip final fw slash
        if (currentdir.back() == '/')
            currentdir.pop_back();
    }
    else
        cout << "Directory does not exist.\n";
}

void Shell::whereami()
{
    cout << currentdir << endl;
}

void Shell::getHistory()
{
    if (tsize < 2)
    {
        for (int i = 0; i < hsize; i++)
            cout << i << ":\t" << history.at(i) << endl;
    }
    else if (!tokens.at(1).compare("-c"))
    {
        history.clear();
        hsize = 0;
    }
    else
    {
        cout << "Incorrect parameters.\n";
    }
}

void Shell::byebye()
{
    // Save history to current file
    ofstream fout;
    fout.open(HISTORY_FILE);

    for (int i = 0; i < hsize; i++)
        fout << history.at(i) << endl;

    // Close file
    fout.close();

    // Terminate mysh
    exit(0);
}

void Shell::replay()
{
    // Error checking
    if (tsize != 2)
    {
        cout << "Argument error.\n";
        return;
    }

    // Add 1 to account for offset from current command
    int num = stoi(tokens.at(1)) + 1;

    if (num < 0 || num > hsize)
    {
        cout << "Parameter out of bounds of history.\n";
        return;
    }

    cout << history.at(num) << endl;

    runCommand(history.at(num));
}

pid_t Shell::start(bool background, bool verbose)
{
    string path;
    int status;

    // Error checking
    if (tsize < 2)
    {
        cout << "You must include a file path.\n";
        return -1;
    }

    // Fork process
    pid_t pid = fork();

    // Child process starts execution here

    // Child process
    if (pid == 0)
    {
        // Check for fully qualified path
        if (tokens.at(1).at(0) != '/')
        {
            // Relative path
            path = currentdir.append("/");
        }
        path.append(tokens.at(1));

        // Create vector of args
        vector<const char*> args;
        args.reserve(tsize-1);

        for (int i = 2; i < tsize; i++)
        {
            args.push_back(tokens.at(i).data());
        }

        args.push_back(NULL);

        // Convert tokens to vector of c_strings
        for (int i = 2; i < tsize; i++)
            args[i-2] = tokens.at(i).c_str();
        
        // Start specified program
        execv(path.c_str(), const_cast<char* const *>(args.data()));
        
        // Only make it here if execv() failed
        cout << "Error starting program\n";
        exit(0);
    }
    // Parent process
    else if (pid > 0)
    {
        // Check background flag
        if (background)
        {
            // Print PID
            if (verbose)
                cout << "PID: " << pid << endl;

            // Strip path from process name
            string pName = tokens.at(1);

            for (int i = 0; i < pName.length(); i++)
            {
                if (pName.at(i) == '/')
                {
                    pName = pName.substr(i+1);
                    i = 0;
                }
            }

            // Get time
            time_t curTime = time(NULL);
            string time = asctime(localtime(&curTime));
            time.pop_back();

            // Store process info
            vector<string> curProcess;
            curProcess.push_back(to_string(pid));
            curProcess.push_back(pName);
            curProcess.push_back(time);

            // Save to vector
            processes.push_back(curProcess);

            return pid;
        }
        else
        {
            waitpid(pid, &status, 0);
            return pid;
        }
    }
    // Fork failed
    else
    {
        cout << "Fork Failed.\n";
        return -1;
    }
}

void Shell::dalek()
{
    if (tsize != 2)
    {
        cout << "Argument error.\n";
        return;
    }

    pid_t pid = stoi(tokens.at(1));

    cout<<pid<<endl;

    int status = kill(pid, SIGKILL);

    if (status == -1)
    {
        cout << "Failed to kill process.\n";
    }
    else
    {
        cout << "Process killed successfully.\n";

        // Remove process from list
        for (int i = 0; i < processes.size(); i++)
        {
            if (stoi(processes.at(i).front()) == pid)
                processes.erase(processes.begin() + i);
        }
    }
}

void Shell::ps()
{
    int status, i = 0;

    // Prints info of processes spawned by shell
    cout << "PID\t\tProcess\t\tStart Time\n";

    for (vector<string> info : processes)
    {
        // Check if process is still alive
        if (!waitpid(stoi(info.at(0)), &status, WNOHANG))
        {
            // Print info
            for (string s : info)
            {
                cout << s << "\t\t";
            }
            cout << endl;
        }

        i++;
    }

    cout << endl;
}

void Shell::dalekall()
{
    int status;

    cout << "Exterminating " << processes.size() << " processes:";

    for (vector<string> pro : processes)
    {
        // Check if process is still alive
        if (!waitpid(stoi(pro.front()), &status, WNOHANG))
        {
            // Print
            cout << " " << pro.front();

            // Terminate process
            kill(stoi(pro.at(0)), SIGKILL);
        }
    }

    processes.clear();

    cout << endl;
}

void Shell::repeat()
{
    // Manipulte tokens
    tokens.erase(tokens.begin());
    tsize--;

    // Print
    cout << "PIDs:";

    // Start programs
    for (int i = stoi(tokens.front()); i > 0; i--)
    {
        cout << " " << start(true, false) << (i==1 ? "." : ",");
    }
    cout << endl;
}

void Shell::runCommand(string command)
{
    input = command;

    // Save to history
    history.insert(history.begin(), input);

    // Tokenize input
    tokenize();

    // Update size fields;
    tsize = tokens.size();
    hsize++;

    // Read command
    string cmd = tokens.front();

    // Call correct function
    if (!cmd.compare("movetodir"))
        movetodir();
    else if (!cmd.compare("whereami"))
        whereami();
    else if (!cmd.compare("history"))
        getHistory();
    else if (!cmd.compare("byebye"))
        byebye();
    else if (!cmd.compare("replay"))
        replay();
    else if (!cmd.compare("start"))
        start(false, true);
    else if (!cmd.compare("background"))
        start(true, true);
    else if (!cmd.compare("dalek"))
        dalek();
    else if (!cmd.compare("ps"))
        ps();
    else if (!cmd.compare("dalekall"))
        dalekall();
    else if (!cmd.compare("repeat"))
        repeat();
}





int main()
{
    string command;
    Shell sh;
    
    // Main program loop
    while (true)
    {
        // Print prompt
        cout << "# ";

        // Read command from user
        getline(cin, command);

        // Run the command
        sh.runCommand(command);
    }
}