-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshell.cpp
More file actions
178 lines (161 loc) · 5.13 KB
/
Copy pathshell.cpp
File metadata and controls
178 lines (161 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include "shell.hpp"
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <stack>
#include <string>
#include <vector>
#include "defaults.hpp"
#include "token.hpp"
#include "tokenizer.hpp"
using Type = Token::Type;
// Exit command, notify main loop to exit. Further arguments are ignored.
static inline int do_exit() { return -1; }
// No expression was given, do nothing.
static inline int do_nothing() { return 0; }
// CPPArgList is CD command. Update directory and notify main loop to update path.
// Returns 1 if main loop needs to refresh the current working directory.
// Returns 0 if main loop does not have to do anything (reprompt user).
static inline int do_cd(CPPArgList& expr) {
// Pop cd word.
lexer::pop(expr);
if (expr.size() != 1) {
std::cout << "Unexpected argument count, expected 1, received: " << expr.size() << '\n';
return 0;
} else if (chdir(lexer::pop(expr).val.c_str()) != 0) {
std::cout << "Path not found." << '\n';
return 0;
}
return 1;
}
// Attempts to execute a file. Returns -1 if there is an
// error while forking or running the file.
static inline int exec_file(CPPArgList& args, int in, int out) {
// Convert C++ vector to C vector and run executable.
CArgList argc = make_c_args(args);
if (execv(argc[0], (char* const*)argc.data()) == -1) {
std::cerr << "Could not run program." << '\n';
return -1;
}
return 0;
}
// Returns whether a file exists AND can be executed by user.
//
// 1st if: First cecks if file exists.
// 2nd if: Check if file is executable by comparing the permission bits.
// Is executable if it passes both checks.
static inline bool can_execute(std::string& path) {
struct stat buffer;
if (stat(path.c_str(), &buffer) != 0) return false;
if (!(buffer.st_mode & S_IXUSR)) return false;
return true;
}
// Searches in common (hard coded) directories and then tries
// paths given by getenv().
// It sources the paths from init_search_paths() and tries these
// until the executable is found or the stack is empty.
static int search_exec(CPPArgList& args, int in, int out) {
std::stack<std::string> paths_to_search = ex_paths;
// Search the paths.
while (paths_to_search.size()) {
std::string path = paths_to_search.top();
// Path to try.
std::string try_path = path + "/" + args.front();
paths_to_search.pop();
// Attempt to open folder.
DIR* dir = opendir(path.c_str());
// Try path.
if (dir == nullptr)
continue;
else if (can_execute(try_path)) {
args.front() = try_path;
return exec_file(args, in, out);
}
}
std::cerr << "No executable was found for " << args.front() << '\n';
return -1;
}
// Attempt to execute the given program. If user gave
// executable name instead of path, search executable.
static inline int execute(CPPArgList& args, int in, int out) {
switch (args.back()[0]) {
case '.':
case '/':
// Absolute or relative path.
return exec_file(args, in, out);
default:
// No path given, search for executable.
return search_exec(args, in, out);
}
}
// Attempt to pipe expression. We strip the expression until
// it's empty or we find a pipe symbol. Then we decide the
// input and ouput redirection and run the program on a child
// process. We repeat this until all commands are executed.
static int pipe_arguments(CPPArgList& input) {
bool first = true, abort = false;
std::vector<pid_t> pid_tracker{};
// Initiate to standard in and output.
int in = STDIN_FILENO, out = STDOUT_FILENO, fd[2];
while (input.size() && !abort) {
// Create pipe.
if (pipe(fd) == -1) return 3;
// Strip next command from expression vector. If empty,
// there was a syntax error. e.g. "ls | |"
CPPArgList args = pop_cmnd(input);
bool last = !input.size();
if (!args.size()) return 2;
// If we are at last command, set output back to standard.
out = last ? STDOUT_FILENO : fd[1];
// Fork and check for success.
pid_t pid = fork();
if (pid == -1) return 4;
// Now split program into child and parent processes.
if (pid == 0) {
// *= Child process =*
// Redirect in and output.
dup2(out, STDOUT_FILENO);
dup2(in, STDIN_FILENO);
// Close writing end of pipe.
close(fd[1]);
if (execute(args, in, out) == -1) {
abort = true;
// We need to exit the child process manually if it failed.
_exit(0);
}
} else {
// *= Parent process =*
// If not the last command, next input should be previous pipe.
if (!last) in = fd[0];
// Close writing end of pipe.
close(fd[1]);
// Keep track of pids to wait for.
pid_tracker.push_back(pid);
}
first = false;
}
// Wait until all processes are finished. This part is always executed
// by parent process.
for (pid_t pid : pid_tracker) waitpid(pid, NULL, 0);
return 0;
}
// Parse expression. Starts by reversing the expression to allow the use of
// 'pop_back()', which is an O(1) operation.'
int shell::process(CPPArgList& expr) {
std::reverse(expr.begin(), expr.end());
// Decide what to do.
switch (lexer::peek(expr).type) {
case Type::EXIT:
return do_exit();
case Type::CD:
return do_cd(expr);
case Type::STRING:
return pipe_arguments(expr);
default:
return do_nothing();
}
}