we are writing a program that has to mimic the Linux shell.
It is consisted of some parts. Every part does what the previous part did, plus something extra.
Part one runs single commands like ls, pwd etc. No parameters no
redirection.
Part two runs single commands plus redirection.
This is where we are stuck.. We compile myShell 2 without errors. But when we are typing any command we get the segmentation fault.
We're sorry for writing almost all out code here(myShell 1 not included), we just wanted to make sure you have everything you need to tell us where we are wrong.
functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#define max 260
extern int argCounter(char *argLine,char *delim);
extern void myExec(char *argLine,int howMany,char *delim);
extern char **argArray(char* argLine,int howMany,char *args[],char *delim);
void myExecRedir(char **tokens,char* argLine, int howMany);
#endif
functions.c
#include "functions.h"
int argCounter(char *argLine,char *delim)
{
char *temp;
memcpy(temp,argLine,strlen(argLine));
char *pointer = strtok(temp, delim);
int counter = 0;
while (pointer != NULL)
{
counter++;
pointer = strtok(NULL, delim);
}
return counter;
}
void myExec(char *argLine,int howMany,char *delim)
{
char temp[max];
memset(temp,0,max);
char *args[howMany];
argLine[strlen(argLine)-1] = '\0';
//argArray breaks the argLine and returns an array that contains each argument
argArray(argLine,howMany,args,delim);
execvp(args[0],args);
perror("ERROR: wrong command!");
}
char **argArray(char *argLine,int howMany,char *args[],char *delim)
{
args[howMany] = NULL;
char *pointer = strtok(argLine, delim);
int counter = 0;
while (pointer != NULL)
{
args[counter]=pointer;
counter++;
pointer = strtok(NULL, delim);
}
return args;
}
void myExecRedir(char **tokens,char* argLine, int howMany)
{
char *delim3="<";
char *delim4=">";
int howManyIn= argCounter(argLine,delim3);
int howManyOut= argCounter(argLine,delim4);
if(howManyOut= howManyIn)
{
int fdin = open(tokens[1],O_RDWR);
int fdout = open(tokens[2],O_RDWR);
if(dup2(fdin,fdout) >= 0)
{
tokens[1]=NULL;
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
else if(howManyIn== 0) //means we only have > redirection
{
int fdout = open(tokens[1],O_RDWR);
if(dup2(2,fdout) >= 0)
{
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
else //means we only have redirection
{
int fdin = open(tokens[1],O_RDWR);
if(dup2(fdin,1) >= 0)
{
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
}
myShell2.c
#include "functions.h"
int main()
{
printf("myshell2>");
pid_t pid,waitPid;
//WE TRIED WITHOU ALLOCATING MEMORY AS WELL
char *argLine = (char *)malloc(max);
char **args = (char **)malloc(max);
char **args2 =( char **)malloc(max);
char **temp = (char **)malloc(max);
char *delim="><";
char *delim2=" ";
int i,howMany,howMany2,status;
while(fgets(argLine,max,stdin) != NULL)
{
howMany= argCounter(argLine,delim);//howMany redirections
args=argArray(argLine,howMany,args,delim);
if (howMany == 1)//means we are at myShell 1
{
howMany2= argCounter(argLine,delim2);
if(howMany2 ==1)//checking if the command has any parameters (like ls -l)
{
printf("myshell2>");
pid = fork();
if (pid < 0)
{
perror("ERROR: Fork failed.\n");
return -1;
}
else if(pid == 0)
{
myExec(args[0],howMany2,delim2);
perror("ERROR: Child should never arrive here.\n");
}
else
{
waitPid = wait(&status);
if (waitPid == -1)
{
perror("ERROR: Waitpid failed.\n");
return -1;
}
}
}
else
{
printf("ERROR: Wrong number of Arguments!\n");//can't run on myshell 2
return(0);
}
}
//means we have redirection (< or >)
for (i=0; i<howMany; i++)
{
argArray(args[i],2,args2,delim2);//args2 contains the tokens without spaces(delim2)
temp[i] = args2[0];
howMany2 = argCounter(args[i],delim2);
if(howMany2 > 1) // eg. ls -l should not run here
{
printf("ERROR: Wrong number of Arguments!\n");//myShell3 should be running this
return(0);
}
}
printf("myshell2>");
pid = fork();
if (pid < 0)
{
perror("ERROR: Fork failed.\n");
return -1;
}
else if(pid == 0)
{
myExecRedir(temp,argLine,howMany);
perror("ERROR: Child should never arrive here.\n");
}
else
{
waitPid = wait(&status);
if (waitPid == -1)
{
perror("ERROR: Waitpid failed.\n");
return -1;
}
}
}
}
Thank you in advance.
char *temp;
memcpy(temp,argLine,strlen(argLine));
char *pointer = strtok(temp, delim);
...
is wrong, you need to reserve space with malloc:
size_t len = strlen(argline);
char *temp = malloc(len + 1);
if (temp == NULL) return 0;
memcpy(temp, argLine, len);
/* NUL-terminate the string */
temp[len] = '\0';
char *pointer = strtok(temp, delim);
...
free(temp);
or you can use the non-standard function (but available on many implementations) strdup:
char *temp = strdup(argline);
if (temp == NULL) return 0;
char *pointer = strtok(temp, delim);
...
free(temp);
char **args = (char **)malloc(max);
is also wrong, if you need to reserve space for n pointers to char use:
char **args = malloc(sizeof(*args) * n); /* don't cast malloc */
or
char **args = malloc(sizeof(char *) * n);
Related
I want to pass a heap based array that contains sub arrays for arguments to a function that uses execvp, why a heap based array, because I splitting user input into a list of arguments
int split(char * str, const char * token , char *** arr)
{
int c = 1;
char * ptr = strtok(str , token);
(*arr)[0] = malloc(1024);
strcpy((*arr)[0], "-c");
while (ptr != NULL)
{
(*arr)[c] = malloc(1024);
strcpy((*arr)[c], ptr);
c++;
*arr = realloc(*arr, sizeof(char*)*(c+1));
ptr = strtok(NULL, token);
}
(*arr)[c] = NULL;
return c;
}
int run(char * args[])
{
int link[2];
pid_t pid;
char foo[4096];
if (pipe(link)==-1)
return -1;
if ((pid = fork()) == -1)
return -1;
if(pid == 0){
dup2 (link[1], STDOUT_FILENO);
close(link[0]);
close(link[1]);
execvp("/bin/bash", args);
} else {
close(link[1]);
int nbytes = read(link[0], foo, sizeof(foo));
printf("Output: (%.*s)\n", nbytes, foo);
wait(NULL);
}
return 0;
}
int main()
{
char * tmp = malloc(1024);
/* list of pointers */
char ** arr = malloc(16);
/* buffer to hold user input */
char input[1024];
scanf("%[^\n]", input);
//printf("%s\n",input);
int x = split(input, " ", &arr);
for (int i=0; i<x;i++)
{
printf("%s ",arr[i]);
}
run(arr);
char * arr2[] = {getenv("SHELL"), "-c" ,"ls","-la", NULL};
run(arr2);
}
split takes the user input and create a list of pointers where the first element is -c and the last is NULL and the user input is in the middle next it is being passed to the run function that attempts to run the command with /bin/bash but throws the error
ls -la
/usr/bin/ls: /usr/bin/ls: cannot execute binary file
-c ls -la Output: ()
where as if I where to create a stack based array of arrays then pass it to the run function everything works fine and I get the directory listings
I used getenv in the split function and then passed the whole array to run this way every thing is in the right order
int split(char * str, const char * token , char *** arr)
{
int c = 2;
char * ptr = strtok(str , token);
(*arr)[0] = malloc(1024);
/* added this as the first element in the array */
strcpy((*arr)[0], getenv("SHELL"));
(*arr)[1] = malloc(1024);
/* added this as the second element in the array */
strcpy((*arr)[1], "-c");
while (ptr != NULL)
{
(*arr)[c] = malloc(1024);
strcpy((*arr)[c], ptr);
c++;
*arr = realloc(*arr, sizeof(char*)*(c+1));
ptr = strtok(NULL, token);
}
(*arr)[c] = NULL;
return c;
}
int run(char * args[])
{
int link[2];
pid_t pid;
char foo[4096];
if (pipe(link)==-1)
return -1;
if ((pid = fork()) == -1)
return -1;
if(pid == 0){
dup2 (link[1], STDOUT_FILENO);
close(link[0]);
close(link[1]);
/* /bin/bash to args[0] */
execvp(args[0], args);
} else {
close(link[1]);
int nbytes = read(link[0], foo, sizeof(foo));
printf("Output: (%.*s)\n", nbytes, foo);
wait(NULL);
}
return 0;
}
I wrote the next function that tries to read and enter each line from text file into a string array in c :
int main(int argc,char* argv[])
{
char ** lines;
readFile(argv[1],lines);
}
int readFile(char* filePath,char** lines)
{
char file_char;
int letter_in_line=0;
int line=1;
char* line_string=malloc(1024);
int j=1;
int fd=open(filePath,O_RDONLY);
if (fd < 0)
{
return 0;
}
while (read(fd,&file_char,1) >0)
{
if(file_char != '\n' && file_char != '0x0')
{
line_string[letter_in_line] = file_char;
letter_in_line++;
}
else
{
if(lines != NULL)
{
lines=(char**)realloc(lines,sizeof(char*)*line);
}
else
{
lines=(char**)malloc(sizeof(char*));
}
char* line_s_copy=strdup(line_string);
lines[line-1]=line_s_copy;
line++;
letter_in_line=0;
memset(line_string,0,strlen(line_string));
}
j++;
}
printf("cell 0 : %s",lines[0]);
return 1;
}
I have 2 questions :
1)Whenever the code reaches the print of cell 0, I'm getting
Segmentation fault (core dumped) error. What is wrong ?
2)In case I
want to see the changes in the lines array in my main, I should pass
&lines to the func and get char*** lines as an argument ? In
addition, I will need to replace every 'line' keyword with '*line' ?
*I know that I can use fopen,fget, etc... I decided to implement it in this way for a reason.
There is many issues that make your code core dump.
Here a version very similar to your code. I hope it will help you to understand this.
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int read_file(const char *filename, char ***result)
{
/* open the file */
const int fd = open(filename, O_RDONLY);
if (fd < 0) {
*result = NULL;
return -1;
}
/* read the file characters by characters */
char *buffer = (char *)malloc(sizeof(char) * 1024);
char c;
int column = 0;
int line = 0;
*result = NULL;
/* for each characters in the file */
while (read(fd, &c, 1) > 0) {
/* check for end of line */
if (c != '\n' && c != 0 && column < 1024 - 1)
buffer[column++] = c;
else {
/* string are null terminated in C */
buffer[column] = 0;
column = 0;
/* alloc memory for this line in result */
*result = (char **)realloc(*result, sizeof(char *) *
(line + 1));
/* duplicate buffer and store it in result */
(*result)[line++] = strdup(buffer);
}
}
free(buffer);
return line;
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: %s [filename]", argv[0]);
return 1;
}
char **lines;
int line_count = read_file(argv[1], &lines);
if (line_count < 0) {
fprintf(stderr, "cannot open file %s\n", argv[1]);
return 1;
}
for(int i=0; i < line_count; i++)
printf("%s\n", lines[i]);
return 0;
}
Here an other version:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int read_file(const char *filename, char ***result)
{
/* init result */
*result = NULL;
/* open the file */
FILE *file = fopen(filename, "r");
if (file == NULL)
return -1;
/* read the file line by line */
char *buffer = (char *)malloc(sizeof(char) * 1024);
int line = 0;
while (fgets(buffer, 1024, file)) {
*result = (char **)realloc(*result, sizeof(char *) *
(line + 1));
(*result)[line++] = strdup(buffer);
}
free(buffer);
return line;
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: %s [filename]", argv[0]);
return 1;
}
char **lines;
int line_count = read_file(argv[1], &lines);
if (line_count < 0) {
fprintf(stderr, "cannot open file %s\n", argv[1]);
return 1;
}
for(int i=0; i < line_count; i++)
printf("%s\n", lines[i]);
return 0;
}
this is a piece of a shell that I'm creating. I'm having some trouble using libreadline, because when the shell gets loaded and I try to cd in a directory using autocompletion (so pressing TAB), after I press enter I access the directory but I get some strange output before another prompt is printed. I noticed that this happens only when the name of the directory starts with an upper case letter.
Example: "user:: ~ % cd Github" <-- written pressing tab to autocomplete Github
Next prompt is: "8b�/�user :: Github %"
I really cannot understand why, this is something really strange for me.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <readline/history.h>
#include <readline/readline.h>
#include "flush.h"
#include "env.h"
#include "fget.h"
char * flush_builtins[] =
{
"cd",
"help",
"exit",
"ver",
"fget"
};
int flush_num_builtins() {
return sizeof(flush_builtins) / sizeof(char *);
}
int (*flush_func[]) (char **) =
{
&flush_cd,
&help,
&exit_flush,
&ver,
&fget
};
static int flush_startp(char **args)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0)
{
if (execvp(args[0], args) == -1)
{
fprintf(stderr, "flush: command not found\n");
}
exit(1);
}
else if (pid < 0)
{
fprintf(stderr, "flush: command not found\n");
}
else
{
do
{
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
static int flush_exec(char **args)
{
int i;
if (args[0] == NULL)
{
return 1;
}
for (i = 0; i < flush_num_builtins(); i++)
{
if (strcmp(args[0], flush_builtins[i]) == 0) {
return (*flush_func[i])(args);
}
}
return flush_startp(args);
}
static char * flush_read(void)
{
fflush(stdout);
char *line_read = malloc(sizeof(char) * LINE_BUF);
char *prompt = malloc(sizeof(char) * LINE_BUF);
char *current, buffer[TOK_BUF];
current = getcwd(buffer, TOK_BUF);
strcat(prompt, get_user());
strcat(prompt, " :: ");
if (strcmp(current, get_home()) == 0)
{
strcat(prompt, "~");
}
else
{
strcat(prompt, get_cwd());
}
strcat(prompt, " % ");
line_read = readline(prompt);
if (line_read && *line_read)
{
add_history(line_read);
}
return line_read;
free(prompt);
free(line_read);
free(current);
}
static char **flush_getargs(char * line)
{
int bufsize = TOK_BUF;
int i = 0;
char **tokens = malloc(bufsize * sizeof(char *));
char **token;
if (!tokens)
{
fprintf(stderr, "allocation error\n");
exit(1);
}
token = strtok(line, DELIM);
while (token != NULL)
{
tokens[i] = token;
i++;
token = strtok(NULL, DELIM);
}
tokens[i] = NULL;
return tokens;
}
static void flush_loop(void)
{
char *line;
char **args;
int status;
do
{
line = flush_read();
args = flush_getargs(line);
status = flush_exec(args);
free(line);
free(args);
} while (status);
}
static void handler(int num)
{
signal(SIGINT, handler);
flush_loop();
fflush(stdout);
}
int main()
{
init();
signal(SIGINT, handler);
flush_loop();
return 0;
}
You can not use strcat with a non \0 terminated string:
char *prompt = malloc(sizeof(char) * LINE_BUF);
char *current, buffer[TOK_BUF];
current = getcwd(buffer, TOK_BUF);
strcat(prompt, get_user());
Use strcpy instead of strcat, or calloc instead of malloc
I am new to C programming and currently learning this into a course. I'm facing an issues while trying to practice the below history function.
I'm able to display the shell commands. However, when I type history, the past shell commands are not getting saved into the history buffer.
Can anyone help me to find where I went wrong?
Here is my code:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define BUFSIZE 20
#define MAX_WORD_IN_LINE 20
int tokenize(char *str, char **args)
{
int i, argc = 0;
char *token;
token = strtok(str," \t\n");
for(i=0; token!=NULL;i++)
{
args[i] = token;
printf("args[%d] = %s\n", i, args[i]);
token = strtok(NULL, " \t\n");
argc++;
}
return argc;
}
void display_strings(char **p)
{
if (p == NULL) return;
while(*p != NULL){
printf("%s\n",*p);
p++;
}
}
int history(char *hist[], int current){
int i = current;
int hist_num = 1;
do {
if (hist[i]) {
printf("%4d %s\n", hist_num, hist[i]);
hist_num++;
}
i = (i + 1) % BUFSIZE;
} while (i != current);
return 0;
}
int main(void){
char *args[MAX_WORD_IN_LINE];
char buffer[BUFSIZE];
char *hist[BUFSIZE];
int i,current=0;
pid_t pid;
int argc;
for(i=0;i<BUFSIZE;i++)
hist[i]= NULL;
while(1) {
memset(args,0,MAX_WORD_IN_LINE);
printf("osh> ");
fgets(buffer, BUFSIZE, stdin);
argc = tokenize(buffer, args);
//display_strings(args);
// skip on empty command
if (argc == 0) continue;
if (strcmp(args[0],"quit") == 0) break;
else if (strcmp(args[0], "hello") == 0) printf("Hello there. How are you?\n");
else if (strcmp(args[0],"history")==0) history(hist,current);
else {
pid = fork();
if (pid == 0) {
hist[current]=strdup(args[0]);
current++;
execvp(args[0], args);
return 0;
}
You need to make a copy of the string that args[0] points to when you save it in hist. Currently, you're just assigning the pointer to the current args[0], and it will be overwritten by the next command. When you print the history, you'll just get the last command repeatedly. So use:
hist[current] = strdup(args[0]);
My instructor gave us a basic shell in C to expand upon, and I'm currently working on getting the shell to change directories whenever the user enters 'cd [directory]' into the command line. I've gotten it to stop seg faulting, but it won't change directories. Can anyone tell me why it isn't working?
Here is my code so far:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/* Array holds arguments: args[0] is the command. */
static char *args[512];
pid_t pid;
int command_pipe[2];
#define READ 0
#define WRITE 1
int chdir(const char* path);
static int
command (int input, int first, int last)
{
int pipettes[2];
/* Invoke pipe */
pipe (pipettes);
pid = fork ();
if (pid == 0)
{
if (first == 1 && last == 0 && input == 0)
{
// 1st cmd
dup2 (pipettes[WRITE], STDOUT_FILENO);
}
else if (first == 0 && last == 0 && input != 0)
{
// Mid cmd
dup2 (input, STDIN_FILENO);
dup2 (pipettes[WRITE], STDOUT_FILENO);
}
else
{
// Last cmd
dup2 (input, STDIN_FILENO);
}
if (execvp (args[0], args) == -1)
_exit (EXIT_FAILURE); // If child fails
}
if (input != 0)
close (input);
close (pipettes[WRITE]);
// If last command, nothing more needs to be read
if (last == 1)
close (pipettes[READ]);
return pipettes[READ];
}
static void
cleanup (int n)
{
int i;
for (i = 0; i < n; ++i)
wait (NULL);
}
static int go (char *cmd, int input, int first, int last);
static char line[1024];
static int n = 0;
int
main (int argc, char* argv[])
{
while (1)
{
/* Initial Prompt */
printf ("?> ");
fflush (NULL);
/* Read in command */
if (!fgets (line, 1024, stdin))
return 0;
int input = 0;
int first = 1;
char *cmd = line;
char *next = strchr (cmd, '|'); /* Find initial '|' */
char *also = strchr (cmd, ';'); /* Find initial ';' */
char *directory = argv[1];
while (next != NULL)
{
/* 'next' points to '|' */
*next = '\0';
input = go (cmd, input, first, 0);
cmd = next + 1;
next = strchr (cmd, '|'); /* Find next '|' */
first = 0;
}
if(argv[0] == "cd"){
chdir(directory);
}
input = go (cmd, input, first, 1);
cleanup (n);
n = 0;
}
return 0;
}
static char *
skip_white_space (char *s)
{
while (isspace (*s))
++s;
return s;
}
static void
parse (char *cmd)
{
cmd = skip_white_space (cmd);
char *next = strchr (cmd, ' ');
int i = 0;
while (next != NULL)
{
next[0] = '\0';
args[i] = cmd;
++i;
cmd = skip_white_space (next + 1);
next = strchr (cmd, ' ');
}
if (cmd[0] != '\0')
{
args[i] = cmd;
next = strchr (cmd, '\n');
next[0] = '\0';
++i;
}
args[i] = NULL;
}
static int
go (char *cmd, int input, int first, int last)
{
parse (cmd);
if (args[0] != NULL)
{
if (strcmp (args[0], "exit") == 0)
exit (0);
n += 1;
return command (input, first, last);
}
return 0;
}
Your immediate problem seems to lie here:
if(argv[0] == "cd"){
chdir(directory);
I think you'll find that argv[0] is the implementation's representation of your program name, not the command you just entered, which is probably in args. Or cmd. Or somewhere.
Even once you fix that, you shouldn't be using == for string comparisons in C. One of the strcmp family is the correct way to do it.