// Daniel Biller // 2/10/2021 // Operating Systems - Spring 2021 // Compile with '-lstdc++fs' #include #include #include #include #include #include #include #include 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 tokens, history; vector>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 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(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 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< 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 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); } }