Skip to content

Instantly share code, notes, and snippets.

@gkhays
Last active March 25, 2020 20:46
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save gkhays/c1d9214341207fd71d5db2bba50562c2 to your computer and use it in GitHub Desktop.

Simple Shell

Call execv in a C program.

Child Process

int status = execv("/bin/ls", (char *[]) { "ls", "-l", NULL } );

/* If execv returns, something happened. */
printf("Exec returns %d\n", status);
perror("Unknown command\n");
exit(1);

See complete listing below.

Compile and link with:

gcc -Wall -o testexec.c

If you have to parse the shell commands, consider strtok, e.g.

// Dynamically size cmd[]
char delim[] = " ";
char *index = strtok(cmd, delim);

Pipeline

To support a pipeline, you will need to invoke the pipe() call and use dup() to copy descriptors. There is a nice example on StackOverflow. But the idea is to set up a pipeline and then redirect STDOUT to the pipe's read descriptor. Then use exec to start a second process and redirect STDIN to the pipe's write descriptor. You can do this implicitly with dup or explicitly with dup2.

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define IN_BUFSIZE 1024
#define TOK_BUFSIZE 64
#define DELIM " \t\r\n\a" /* White-space delimiters */
/* Forward declarations */
int invoke_cmd(char *args[]);
char *get_input(void);
char **parse_input(char *line);
char *done_str[] = {
"exit",
"quit"};
int done_str_size()
{
return sizeof(done_str) / sizeof(char *);
}
int main(int argc, char *argv[])
{
int status;
char *line;
char **args;
/* Main program loop -- use infinite trick. */
for (;;)
{
/* TODO: Explicitly catch Ctrl + C (SIGINT) to terminate. */
printf("eshell> ");
line = get_input();
args = parse_input(line);
/* Terminate on exit or quit. */
for (int i = 0; i < done_str_size(); i++)
{
if (strcmp(args[0], done_str[i]) == 0)
{
printf("Received %s, terminating... Bye!\n", args[0]);
return 0;
}
}
status = invoke_cmd(args);
free(line);
free(args);
if (!status)
{
break;
}
}
return 0;
}
int invoke_cmd(char *args[])
{
int status;
pid_t cpid;
cpid = fork();
if (cpid == 0)
{
/* This is the child process. */
if (execvp(args[0], args) == -1)
{
perror("eshell");
}
/*
* Note: An exit code of one (1) indicates failure but is non-portable.
* For POSIX compliance, use the EXIT_FAILURE constant instead.
*/
exit(EXIT_FAILURE);
}
else
{
/* This the parent process. */
do
{
waitpid(cpid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
char *get_input(void)
{
int c;
int index = 0;
int buf_size = IN_BUFSIZE;
char *buf = malloc(sizeof(char) * buf_size);
// TODO: Check to see that malloc succeded or failed.
while (1)
{
c = getchar();
if (c == EOF)
{
exit(EXIT_SUCCESS);
}
else if (c == '\n')
{
buf[index] = '\0';
return buf;
}
else
{
buf[index] = c;
}
index++;
// Check to see that we have enough space in the buffer.
if (index >= buf_size)
{
buf_size += IN_BUFSIZE;
buf = realloc(buf, buf_size);
if (!buf)
{
fprintf(stderr, "eshell: buffer resize error\n");
exit(1); // EXIT_FAILURE
}
}
}
}
char **parse_input(char *line)
{
int index = 0;
int buf_size = TOK_BUFSIZE;
char *tok;
char **tmp;
char **list = malloc(buf_size * sizeof(char *));
/* You see this convention in C. If a pointer has a value, it evaluates to true. */
if (!list)
{
fprintf(stderr, "eshell: parsing error\n");
exit(1); // EXIT_FAILURE
}
tok = strtok(line, DELIM);
while (tok != NULL)
{
list[index] = tok;
index++;
if (index >= buf_size)
{
buf_size += TOK_BUFSIZE;
tmp = list;
list = realloc(list, buf_size * sizeof(char *));
if (!list)
{
free(tmp);
fprintf(stderr, "eshell: parsing allocation error\n");
exit(1); // EXIT_FAILURE
}
}
tok = strtok(NULL, DELIM);
}
list[index] = NULL;
return list;
}
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int child_status;
char cmd[10];
printf("EShell>");
fgets(cmd, 10, stdin);
printf("You typed %s\n", cmd);
pid_t child = fork();
if (child == 0)
{
/* This is the child process. */
int status = execv("/bin/ls", (char *[]) { "ls", "-l", NULL } );
/* If execv returns, something happened. */
printf("Exec returns %d\n", status);
printf("Unknown command\n");
exit(0);
}
else
{
printf("Hi, I'm a parent!\n");
/* This is the parent; remember to pick up the kids! */
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment