I'm working on a pretty basic UNIX shell in C. In this project I am attempting to use fork() and execvp() to execute the actual shell commands. I'm running into an issue though, where it seems to work fine with commands that have a single argument (for example ls -l and echo howareyoutoday work perfectly), but commands with multiple arguments fail to execute (echo how are you today does not run). I'll walk you through my code/rationale in an effort to help locate the reason behind this problem.
char *token;
int count=0;
pid_t childProc;
int childStatus;
pid_t tpid;
char* argv[256];
int i=1;
childProc = fork();
if (childProc==0) {
//CHILD PROCESS IS HERE
argv[0] = malloc(strlen(token));
argv[0] = token;
token=strtok(NULL," \n\t()<>|&;");
while(token!=NULL) {
argv[i]=malloc(strlen(token));
argv[i]=token;
token=strtok(NULL," \n\t()<>|&;");
i++;
}
execvp(argv[0],argv);
//if this executes execvp fails
printf("failure to execute\n");
i=0;
exit(0);
}
else {
//PARENT PROCESS IS HERE
do {
tpid = wait(&childStatus);
} while(tpid != childProc);
}
So it starts out with a basic fork() call to create the child process. In that child process, I allocate memory for the first element in my argv array. token which comes from a previous strtok call is assigned to argv[0]. A new token is generated and added to the next argv element. This process is repeated for the remaining tokens remaining.
Once the argv array has been completed, execvp is called, with the first argument containing the command name, and the second being the whole argv array. If the command fails to execute, execvp will return and a message indicating this will be printed.
I can't figure out why I am having the multiple arguments problem I mentioned above. Any help or suggestions will be greatly appreciated!
For reference, the full program's code is as follows:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
char buffer[256];
char *token;
int count=0;
pid_t childProc;
int childStatus;
pid_t tpid;
char* argv[256];
int i=1;
int j=0;
while(1) {
fgets(buffer, 256, stdin);
token=strtok(buffer," \n\t()<>|&;");
while (token==NULL) {
fgets(buffer,256,stdin);
token=strtok(buffer," \n\t()<>|&;");
}
if (strcmp(token,"exit")==0) {
exit(0);
}
else if (strcmp(token,"cd")==0) {
token = strtok(NULL, " \n\t()<>|&;");
if (chdir(token)!=0) {
perror("Error: ");
}
}
else {
childProc = fork();
if (childProc==0) {
argv[0] = malloc(strlen(token));
argv[0] = token;
token=strtok(NULL," \n\t()<>|&;");
while(token!=NULL) {
argv[i]=malloc(strlen(token));
argv[i]=token;
token=strtok(NULL," \n\t()<>|&;");
i++;
}
execvp(argv[0],argv);
//if this executes execvp fails
printf("failure to execute\n");
i=0;
exit(0);
}
else {
do {
tpid = wait(&childStatus);
} while(tpid != childProc);
}
}
}
}
You need to null terminate your argument strings to execvp.
if (childProc == 0)
{
argv[0] = malloc(strlen(token));
argv[0] = token;
token = strtok(NULL, " \n\t()<>|&;");
while (token != NULL)
{
argv[i] = malloc(strlen(token));
argv[i] = token;
token = strtok(NULL, " \n\t()<>|&;");
i++;
}
argv[i] = NULL; //<--- insert here
if (execvp(argv[0], argv) == -1)
{
printf("failure to execute because %s\n", strerror(errno));
exit(0);
}
}
Related
I am trying to write a simple shell in C which takes in a command and uses a child process to execute that command. For example, if I input:
ps -ael
my child process should execute that command along with its arguments. I print out my commands as they are stored in an array. This what I see:
Array[0] = ps
Array[1] = -ael
Array[2] = NULL
When I execute I get this:
error: unsupported SysV option
Usage:
ps [options]
Try 'ps --help <simple|list|output|threads|misc|all>'
or 'ps --help <s|l|o|t|m|a>'
for additional help text.
For more details see ps(1).
My code below.
int main(void)
{
char *args[MAX_LINE/2 +1]; // command line arguments
char *cmdLine;
int should_run = 1; // flag to determine when to exit the program
int i, x;
printf("osh> ");
fflush(stdout);
fgets(cmdLine, MAX_LINE, stdin);
char *token = strtok(cmdLine, " ");
int position = 0;
while (token != NULL)
{
args[position++] = token;
token = strtok(NULL, " ");
}
i = 0;
while (args[i] != NULL)
{
printf("Array[%d] = %s\n", i, args[i]);
i++;
}
if (args[i] == NULL) printf("Array[%d] = NULL", i);
x = 0;
pid_t pid;
/* fork a child process*/
pid = fork();
if (pid < 0)
{
/*Error occured*/
fprintf(stderr, "Fork failed.");
return 1;
}
else if (pid == 0)
{
/*child process*/
execvp(args[0], args); //error here
}
else
{
/*Parent process*/
wait(NULL);
printf("\nChild complete\n");
}
}
The string returned by fgets() includes the newline, but you're not removing it from the string. So you're setting args[1] to "-ael\n", and \n is not a valid option.
Include newline in your strtok() delimiters:
char *token = strtok(cmdLine, " \n");
int position = 0;
while (token != NULL)
{
args[position++] = token;
token = strtok(NULL, " \n");
}
and then it will not be included in the tokens.
You should have been able to see this in your output, I'll bet it printed:
Array[0] = ps
Array[1] = -ael
Array[2] = NULL
with a blank line there.
BTW, I don't see where you're ever setting the last argument to NULL. The while loop stops when strtok() returns NULL, so it never assigns that result to args[position++]. You need to add:
args[position] = NULL;
after the loop.
And there's no need for if (args[i] == NULL) -- the loop before that stops when that condition is met, so it's guaranteed to be true.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER 64
char *read_command(void);
char **parse_line(char *line);
int execute(char **arguments);
int main(void)
{
char *command = NULL;
char **arguments;
int status;
do
{
printf("protoulis_7968> ");
command = read_command();
arguments = parse_line(command);
status = execute(arguments);
free(arguments);
free(command);
}while(status);
}
char *read_command(void)
{
char *command = NULL;
ssize_t buf = 0;
getline(&command, &buf, stdin);
return command;
}
char **parse_line(char *line)
{
int buffer = BUFFER;
int pos = 0;
char **tokens = malloc(buffer * sizeof(char*));
char *token;
if (!tokens)
{
printf("Error allocating memory with malloc\n");
exit(0);
}
token = strtok(line, " \t\r\n\a");
while(token != NULL)
{
tokens[pos] = token;
pos++;
if (pos >= buffer)
{
buffer += BUFFER;
tokens = realloc(tokens, buffer * sizeof(char*));
if (!tokens)
{
printf("Error reallocating memory!\n");
exit(0);
}
}
token = strtok(NULL, " \t\r\n\a");
}
tokens[pos] = NULL;
return tokens;
}
int execute(char **arguments)
{
// printf("%*c\n", arguments);
int pid, waitPid, status;
pid = fork();
if(pid == 0) //child process
{
if (execvp(arguments[0], arguments) == -1)
perror("Error with EXECVP\n");
}
else if (pid < 0)
perror("Error PID < 0\n");
else //parent process
{
do
{
waitPid = waitpid(pid, &status, WUNTRACED);
}while(!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
Well, I have written the above code simulating a command line interpreter in C. I would like to be able to execute multiple commands by entering them in one line. I mean I want to pass as input for example the line: ls -l ; touch hello.c ; pwd. Having passed this entire line I want to separate the commands by the semicolon and let the system execute each command in any order. I believe I have to use the strtok function but have done many attempts and managed nothing. Any help would be really appreciated!
strtok will not suffice in your case. The reason is that it will take you to the next subcommand but to be able to execute this subcommand, you have to have it as a single string.
Two ways to solve this issue:
Count how many ';' there is, replace them by '\0' to have several contiguous strings in memory, then execute them one by one.
Write a function that splits your command string into a 2d array of subcommands, then execute them one by one.
Here's a code that does that if you need some inspiration:
Using linked lists: https://github.com/yoones/chelpers/blob/master/src/split.c
2d array version: https://github.com/yoones/hsn/blob/master/src/tools/split.c
I have to develop a simple shell in C using system calls fork()/execvp(). So far my code takes in a command, splits it up using strtok into an array argv and then I call fork to create a child and execute the command. Im working on this in ubuntu where most of the commands are in the /bin/ directory, so I append the program name (for example /bin/ls) and use that for the first arg of execvp and then I give it the argv array. My program works if I type in the command "ls", but when trying other commands or even "ls -l" I'm getting an "ls: invalid option." Im not sure what I'm doing wrong here.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_LEN 1024
int main(){
char line[BUFFER_LEN]; //get command line
char* argv[100]; //user command
char* path= "/bin/"; //set path at bin
char progpath[20]; //full file path
int argc; //arg count
while(1){
printf("My shell>> "); //print shell prompt
if(!fgets(line, BUFFER_LEN, stdin)){ //get command and put it in line
break; //if user hits CTRL+D break
}
if(strcmp(line, "exit\n")==0){ //check if command is exit
break;
}
char *token; //split command into separate strings
token = strtok(line," ");
int i=0;
while(token!=NULL){
argv[i]=token;
token = strtok(NULL," ");
i++;
}
argv[i]=NULL; //set last value to NULL for execvp
argc=i; //get arg count
for(i=0; i<argc; i++){
printf("%s\n", argv[i]); //print command/args
}
strcpy(progpath, path); //copy /bin/ to file path
strcat(progpath, argv[0]); //add program to path
for(i=0; i<strlen(progpath); i++){ //delete newline
if(progpath[i]=='\n'){
progpath[i]='\0';
}
}
int pid= fork(); //fork child
if(pid==0){ //Child
execvp(progpath,argv);
fprintf(stderr, "Child process could not do execvp\n");
}else{ //Parent
wait(NULL);
printf("Child exited\n");
}
}
}
The invalid option is because fgets() keeps the '\n' when you press enter, try this
if(!fgets(line, BUFFER_LEN, stdin))
break;
size_t length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
when you are trying to call ls -l you are passing "-l\n" as the option, hence the message.
You will have to change
strcmp(line, "exit\n")
to
strcmp(line, "exit")
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUFFER_LEN 1024
int main(){
char user_input[BUFFER_LEN]; //get command line
char* argv[120]; //user command
int argc ; //argument count
char* path= "/bin/"; //set path at bin
char file_path[50];//full file path
while(1){
printf("Simple Shell>> "); // Greeting shell during startup
if(!fgets(user_input,BUFFER_LEN, stdin)){
break; //break if the command length exceed the defined BUFFER_LEN
}
size_t length = strlen(user_input);
if(length == 0){
break;
}
if (user_input[length - 1] == '\n'){
user_input[length - 1] = '\0'; // replace last char by '\0' if it is new line char
}
//split command using spaces
char *token;
token = strtok(user_input," ");
int argc=0;
if(token == NULL){
continue;
}
while(token!=NULL){
argv[argc]=token;
token = strtok(NULL," ");
argc++;
}
argv[argc]=NULL;
strcpy(file_path, path); //Assign path to file_path
strcat(file_path, argv[0]); //conctanate command and file path
if (access(file_path,F_OK)==0){ //check the command is available in /bin
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) { //child process
if (execvp(file_path,argv) == -1) {
perror("Child proccess end");
}
exit(EXIT_FAILURE);
}
else if (pid > 0) { // parent process
wpid = waitpid(pid, &status, WUNTRACED);
while (!WIFEXITED(status) && !WIFSIGNALED(status)){
wpid = waitpid(pid, &status, WUNTRACED);
}
}
else {
perror("Fork Failed"); //process id can not be null
}
}
else {
printf("Command is not available in the bin\n"); //Command is not available in the bin
}
}
}
I am writing a C program to emulate a simple shell. This shell will basically evaluate commands like any other shell (ls, cat, etc.), as well as handle pipelining and redirection.
Currently, I am trying to start out by getting user input, tokenizing it, and executing the command provided (e.g. executing only "ls" and not "ls -l"). However, I am having a lot of difficulty with the forking. It seems that every time I fork, something goes wrong and hundreds of identical processes are created, leading to my computer freezing and me having to restart. The code appears to be correct, but I have no idea what is causing this behaviour. Below is the relevant portion of my code (main method and input tokenizer method).
int main() {
char inputLine[512]; //user input
char *args[10]; //arguments
char* pathVar = "/bin/";//path for argument
char programPath[512]; //pathVar + args[0]
int n; //count variable
//loop
while (1) {
//print prompt, get input
printf("input> ");
fgets(inputLine, 512, stdin);
n = tokenizer(inputLine, args);
//fork process
pid_t pid = fork();
if (pid != 0) { //if parent
wait(NULL);
} else { //if child
//format input for execution
strcpy(programPath, pathVar);
strcat(programPath, args[0]);
//execute user command
int returnVal = execv(programPath, args);
}
}
return 0;
}
int tokenizer(char *input, char *args[]) {
char *line; //current line
int i = 0; //count variable
line = input;
args[i] = strtok(line, " ");
do {
i++;
line = NULL;
args[i] = strtok(line, " ");
} while (args[i] != NULL);
return i;
}
Putting it all together:
You need to check fork and execv for failure.
You should exit after an execv failure (and perhaps after a fork failure).
And you need to add \n to the strtok delimiters (or remove the newline from the input line in some other way).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 10
#define PATH "/bin/"
int main() {
char inputLine[BUFSIZ];
char *args[MAXARGS];
char programPath[BUFSIZ + sizeof(PATH) + 10];
while (1) {
printf(":-> ");
if (fgets(inputLine, BUFSIZ, stdin) == NULL) /* ctrl-D entered */
break;
tokenize(inputLine, args);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid != 0) { /* parent */
wait(NULL);
} else { /* child */
strcpy(programPath, PATH);
strcat(programPath, args[0]);
execv(programPath, args); /* will not return unless it fails */
perror("execv");
exit(EXIT_FAILURE);
}
}
return 0;
}
int tokenize(char *input, char *args[]) {
int i = 0;
args[0] = strtok(input, " \n");
for (i = 0; args[i] && i < MAXARGS-1; ++i)
args[++i] = strtok(NULL, " \n");
return i;
}
You should check that execv doesn't fail and also be sure to exit() at the end of the child block.
//execute user command
int returnVal = execv(programPath, args);
// check return from execv
if (returnVal < 0) {
perror("execv");
exit(1);
}
Also, beware using functions like strcpy in this context since they may lead to buffer overflows. If an untrusted attacker type is talking to your shell this type of security issue could let them break out of the "sandbox".
I am very new to C and am in an OS class where I need to write a basic shell in C (yay). It's actually been going halfway decently, I am just trying to learn C basics while getting through the work.
I am trying to use exec after forking and call, for now, mkdir. The arguments required through me off a little, but I've been trying to figure it out and was hoping someone could tell me where I've gone wrong.
} else {
//fork exec
int pid = fork();
if (pid == 0) {
printf("%s",my_argv[0]);
execve("/bin/mkdir",my_argv,0);
} else wait(NULL);
}
This is the portion where I am responding to the mkdir call. Right now, I have a line[] that is input from the user, the command is taken with
command = strtok(line, DELIMITERS);
The arg is :
arg = strtok(0,DELIMITERS);
my_argv[0] = arg;
Everything compiles fine but the mkdir never works. Printing my_argv[0] gives the correct argument that I expect. I'm sure this is something stupid but any tips would be appreciated.
All Code:
int main(int argc, char *argv[])
{
char *command;
char line[MAXLINE];
char *arg = NULL;
char *my_argv[];
while(1) {
printf(PROMPT);
if (fgets(line,MAXLINE,stdin) != NULL) {
//take out \n
line[strlen(line)-1] = '\0';
}
//looks for first delimiter, saves as the command
command = strtok(line, DELIMITERS);
//start looking at what command it is by comparing
if (strcmp(command,"cd")==0) {
//if they equal zero, they match
//this is a cd command, must have following arg
if (argv[1] == NULL) chdir("/");
else chdir(argv[1]);//chdir is the system call for cd
} else if (strcmp(command,"exit")==0) {
break;
} else if (strcmp(command,"mkdir")==0){
arg = strtok(0,DELIMITERS);
my_argv[0] = arg;
my_argv[1] = NULL;
if (!arg) {
printf("Usage: mkdir missing arg\n");
} else {
//fork exec
int pid = fork();
if (pid == 0) {
printf("%s",my_argv[0]);
//mkdir(arg);
execve("/bin/mkdir",my_argv,0);
} else wait(NULL);
}
}
}
return 0;
}
argv[0] contains the name of the program
argv[1] is the first argument
argument list must be NULL terminated
You could use the mkdir syscall instead of execve