Skip to content

Handle shell built-in commands #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 107 additions & 2 deletions dollarskip.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,103 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>

#ifndef COMMAND_MAX_SIZE
#define COMMAND_MAX_SIZE 8096
#endif

/*** wrapped shell builtins ***/
int easter_egg() {
printf("You found the DollarSkip easter egg!\n");
return 0;
}
Comment on lines +12 to +15
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I skipped past this part, i think this is pretty pointless, fun but pointless and dollarskip is made to merely pass the commands through, not be an actual application.

It should be a possibility that we remove this.

Copy link
Collaborator Author

@Itai-Nelken Itai-Nelken Dec 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with removing it. I added it just as an example of how to override shell commands that are important enough to implement ourselves.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also rename it to dollarskip and make it print about and version info. If that's what we do, I'll remove the overriding shell commands as we can check for arguments in main(). That will also improve speed as we remove one loop.
A faster way to do this would be with a hash table (map in go), but I didn't want to add my implementation (that is pretty big and not so readable).


/*** handled and unhandled shell builtins checks ***/
const char *unhandled_shell_builtins[] = {
"cd",
"alias",
"unalias",
"pushd",
"popd",
"dirs",
"read",
"set",
"readonly",
"unset",
"typeset",
"declare",
"disown",
"enable",
"export",
"exit", // doesn't do anything when run through DollarSkip
"fc",
"history",
"fg",
"getopts",
"hash", // we don't want it to report fake data for the current shell (true for the shell in which it is run)
"jobs", // same reason as the one for 'hash'
"times", // same as above
"local"
};
#define UNHANDLED_SHELL_BUILTINS_SIZE (sizeof(unhandled_shell_builtins) / sizeof(unhandled_shell_builtins[0]))

typedef int (*wrapperFn)();

typedef struct wrapper {
wrapperFn func;
const char *name; // name HAS to be a string literal
} Wrapper;

Wrapper handled_shell_builtins[] = {
{easter_egg, "e_egg"} // DollarSkip easter egg
};
#define HANDLED_SHELL_BUILTINS_SIZE (sizeof(handled_shell_builtins) / sizeof(handled_shell_builtins[0]))

bool handled_shell_builtin(const char *cmd, int *return_value) {
for(int i = 0; i < (int)HANDLED_SHELL_BUILTINS_SIZE; ++i) {
if(!strcmp(cmd, handled_shell_builtins[i].name)) {
if(return_value != NULL) {
*return_value = handled_shell_builtins[i].func();
} else {
handled_shell_builtins[i].func();
}
return true;
}
}
return false;
}

bool is_unhandled_shell_builtin(const char *cmd) {
for(int i = 0; i < (int)UNHANDLED_SHELL_BUILTINS_SIZE; ++i) {
if(!strcmp(cmd, unhandled_shell_builtins[i])) {
return true;
}
}
return false;
}

/*** Argument collecting and proccessing ***/
void check_args(char *args) {
char *argsCopy = strdup(args);
char *p = strtok(argsCopy, " ");
int retval = 0;
while(p != NULL) {
if(handled_shell_builtin(p, &retval)) {
free(args);
free(argsCopy);
exit(retval);
} else if(is_unhandled_shell_builtin(p)) {
fprintf(stderr, "\x1b[31;1m[ERROR: DollarSkip]: '\x1b[97m%s\x1b[0;1;31m': built-in shell commands aren't supported!\x1b[0m\n", p);
free(args);
free(argsCopy);
exit(1);
}
p = strtok(NULL, " ");
}
free(argsCopy);
}

/******
* Collect all the arguments (argv) excluding the first one (argv[0]) into a single string.
* returned string is heap allocated so you have to free() it.
Expand All @@ -26,11 +118,13 @@ char *collect_args(int argc, char **argv, size_t *argv_size) {
argv_len += strlen(argv[i]) + 1; // 1 for the space and null terminator at the end
}
// if argv_size isn't NULL, dereference it and put argv_len in it.
if(argv_size != NULL) *argv_size = argv_len;
if(argv_size != NULL)
*argv_size = argv_len;

// allocate memory for the argument string
args = malloc(argv_len);
if(args == NULL) exit(1);
if(args == NULL)
exit(1);

// copy all of argv (exluding argv[0]) to args and add a space after each argument
for(int i = 1; i < argc; ++i) {
Expand All @@ -42,6 +136,7 @@ char *collect_args(int argc, char **argv, size_t *argv_size) {
return args;
}

/*** main ***/
int main(int argc, char **argv) {
// if arguments are provided
if(argc > 1) {
Expand All @@ -53,6 +148,13 @@ int main(int argc, char **argv) {
size_t argv_len = 0;
// collect arguments
char *args = collect_args(argc, argv, &argv_len);
// we check if the arguments are something we can execute
// for example, we don't want to run built-in shell commands
// (for example: cd) because running them won't change anything for the user
// as they run in a separate shell.
// but we want to handle shell built-ins we wrapped (for example: pwd)/
// check_args() does that for us.
check_args(args);
// check that there is enough space in 'command'
assert(argv_len + strlen(shell_env) + 6 < COMMAND_MAX_SIZE); // 6 = strlen(" -c ''")
// and format them
Expand All @@ -62,6 +164,9 @@ int main(int argc, char **argv) {
size_t argv_len = 0;
// collect the arguments
char *args = collect_args(argc, argv, &argv_len);
if(args == NULL) {
return 1;
}
// check that there is enough space in 'command'
assert(argv_len < COMMAND_MAX_SIZE);
// and copy them to the command variable unformatted
Expand Down