I am trying to create a shell that takes user input and can execute Linux and built-in commands, and up until this point everything has been fine. My problem is that my function for executing piping commands sometimes core dumps. I know that it is core dumping when I'm iterating through the char** array to free each index, but it only core dumps every now and again.
Here is my function with some debug print statements.
int getCommand(int index, char** commands, char **args, int size){
int i = 0;
for(i; i < size && args[index] != NULL && strcmp(args[index],"|"); i++{
commands[i] = strdup(args[index]);
index++;
}
if(i < size)
commands[i] = NULL;
if(index < size && args[index] != NULL && !strcmp(args[index],"|"))
index++;
return index;
}
void pipe(char **args, int size, int numOfPipes){
char **commands = malloc(size*sizeof(char**));
int index = 0;
int new_fd[2], old_fd[2], status;
pid_t pid;
for(int i = 0; i < (numOfPipes+1); i++){
index = getCommand(index, commands, args, size);
if(pipe(new_fd[2] == -1){
printf("Pipe failed\n");
exit(1);
}
else if((pid = fork()) < 0){
printf("Fork failed\n");
exit(1);
}
else if(pid == 0){
if(i > 0){
dup2(old_fd[0],0);
close(old_fd[1]);
}
if(i < numOfPipes){
dup2(new_fd[1],1);
close(new_fd[0]);
}
if(execvp(*commands,commands) < 0){
printf("Command could not be executed.");
exit(1);
}
}
else{
close(old_fd[0]);
close(old_fd[1]);
wait(&status);
old_fd[0] = new_fd[0];
old_fd[1] = new_fd[1];
}
printf("Free strings\n");
/* Here is where it core dumps */
for(int i = 0; i < size; i++){
printf("%d\n",i);
free(commands[i]);
}
printf("Freeing pointer\n");
free(commands);
}
Can anyone help me see where I am going wrong?
I think that there are two problems :
because of the condition in the for-loop in getCommand, strdup is not guaranteed to be called size times, and therefore not all elements of the array commands will be initialized.
I suggest that you check if the elements aren't NULL before freeing them :
for(int i = 0; i < size; i++){
if (command[i] != NULL) {
printf("%d\n",i);
free(commands[i]);
}
}
2.
Because of the for-loop in the pipe function, getCommand is being called numOfPipes times and possibly allocating new memory every time for the command array elements , without freeing the previous allocated memory.
I suggest you check and free the memory before callinggetCommand (or strdup)
Related
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <libio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <ctype.h>
int main(int argc, char** argv) {
int shmID;
char* shmptr, *array, *filearray;
FILE* infile = fopen(argv[1], "r");
if (!infile) {
printf("No file exists\n");
}
int length = 0;
char ch;
ch = getc(infile);
while ((ch = getc(infile)) != EOF) {
filearray[length] = ch;
length++;
}
length++;
fclose(infile);
shmID = shmget(IPC_PRIVATE, length, IPC_CREAT | 0666);
if (shmID < 0) {
printf("There is an error while creating memory \n");
}
int pid = fork();
if (pid > 0) {
shmptr = shmat(shmID, NULL, 0);
if (shmptr == (char * ) - 1) {
printf("There is an error while attaching memory \n");
}
int j = 0;
while ( * array != '!') {
if (array[j] >= 'A' && array[j] <= 'Z') {
array[j] = tolower(array[j]);
} else if (array[j] >= 'a' && array[j] <= 'z') {
array[j] = toupper(array[j]);
} else if (array[j] >= '0' && array[j] <= '9') {
j--;
}
j++;
}
* shmptr = '#';
shmdt(shmptr);
shmctl(shmID, IPC_RMID, NULL);
} else if (pid == 0) {
shmptr = shmat(shmID, NULL, 0);
if (shmptr == (char * ) - 1) {
printf("There is an error while attaching memory \n");
}
array = shmptr;
int x;
for (x = 0; x <= length; x++) {
* array = filearray[x];
array++;
}
* array = '!';
while ( * shmptr != '#') {
sleep(1);
}
int k = 0;
//here we restore the values back into file
while (*array != '!') {
printf("%c", array[k]);
k++;
}
shmdt(shmptr);
} else if (pid < 0) {
printf("Error\n");
}
return 0;
}
This is the code. What I intend to do is take the data from the file and input it into an array. We do this so that we have a temporary data point to store into. I then apply the appropriate checks to see if there is an error while creating the shared memory or the fork command.
After this we enter the child where we intend to
Attach memory
Check error for 1.
Get the array(tried with another char array poiinter but still ran into problems) pointed at shmptr which should initially hold NULL at first but with a length of L(number of characters in file)
Copy the values from the file array into the array(acts as a moving head similar to in a link list) and then attach as a final block a ! to tell the parent the array is over.
Use a # as a char to be added to array so we can know waiting period is over.
In he parent:
Attach memory
Get array
Upper,lower case and check if value is an integer(remove integer by j-- goes back 1 location, then j++ moves back to same location)
attach a # at the end.
As child was rerun it saw this # and should print the array
Hope I was clear. Thank you for the help.
Several problems with your code:
You check if the file open worked, but then you proceed still with the NULL pointer in such a case.
It should be:
FILE *infile = fopen(argv[1], "r");
if(!infile)
{
printf("No file exists\n");
return 1;
}
Pointers are not initialized but you already access them, which invokes undefined behavior.
You need to initialize these pointers before you access them.
char *shmptr;
char *array;
char *filearray;
You can determine the filesize like this:
fseek(infile, 0L, SEEK_END);
length = ftell(infile);
fseek(infile, 0L, SEEK_SET);
Now you can allocate the memory for the file and read it in one go, instead of that loop:
filearray = malloc(length);
if (!filearray)
{
// error
return 1;
}
if(fread(filearray, length, 1, infile) != 1)
{
// error
return 1;
}
As said above you access uninitialized pointers you do this also for array but fro your example it is not clear where this comes from, so you have to allocate memory for it, and initialize it, before you read it.
and sorry for the title I couldn't think of a better way to phrase it.
so I have a C assignment working with fork and exec.
I have three programs called ps, echo and history all of them take different arguments. The final program is called shell and it takes in commands from stdin and calls for exec upon taking a proper command.
example:
ps -a
echo Hello World
history 1.txt
once it reads a line and find it's a valid command it makes a child process and calls for exec.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
const int MAX_LINE = 100;
const char *HISTORY = "./history";
const char *PS = "./ps";
const char *ECHO = "./echo";
void call_cmd(int cmd, char *const argv[]);
/* main function */
int main(int argc, char** argv)
{
FILE * out;
char line[MAX_LINE], line_print[MAX_LINE], seps[] = " \n", rm[80];
char *first, *tmp, ** params;
pid_t pid;
int cmd = -1, i = 0,j= 0;
if (argc != 2)
{
printf("Invalid arguments");
return EXIT_FAILURE;
}
out = fopen(argv[1],"w");
if (out == NULL)
{
perror("Couldn't open file to write");
return EXIT_FAILURE;
}
while(fgets(line,sizeof(line),stdin) != NULL)
{
strcpy(line_print,line);
params = (char**) malloc(sizeof(char*));
tmp = strtok(line,seps);
while (tmp != NULL)
{
if(i != 0)
params = (char**) realloc(params,sizeof(char*) * (i + 1));
params[i] = tmp;
j++;
tmp = strtok(NULL,seps);
i++;
}
first = params[0];
if (strcmp("exit",first) == 0)
{
sprintf(rm,"rm %s",argv[1]);
system(rm);
exit(0);
}
if(strcmp("echo",first) == 0)
cmd = 0;
if(strcmp("history",first) == 0)
cmd = 1;
if(strcmp("ps",first) == 0)
cmd = 2;
if(cmd == -1){
perror("\nInvalid Command\n");
}
if(cmd >= 0)
{
fprintf(out,"%s",line_print);
pid = fork();
if (pid == -1)
{
perror("Error Creating Child");
return EXIT_FAILURE;
}
if(pid == 0)
{
call_cmd(cmd,params);
exit(0);
}
}
for (i = 0; i < j ; i++)
free(params[i]);
free(params);
i = j = 0;
cmd = -1;
}
fclose(out);
return EXIT_SUCCESS;
}
void call_cmd(int cmd, char *const argv[])
{
switch(cmd)
{
case 0:
execv(ECHO, argv);
break;
case 1:
execv(HISTORY, argv);
break;
default:
execv(PS, argv);
break;
}
}
that is my code so far, it behaves in a weird way causing segmentation faults,
I'm pretty sure it's because of the way I split the parameters and free them.
example output:
*** Error in `./shell': double free or corruption (out): 0x00007ffe58f1a630 ***
Parent Id: 1928
Aborted (core dumped)
so I keep editing the for loop
for (i = 0; i < j ; i++)
free(params[i]);
all that does is just jump from double free to segmentation faults or I write a command like ps or history and it does nothing, so I must be doing something but I'm truly lost been trying to fix it for two days with, so if you see what I did wrong please point it out.
Thank you.
strtok parses a string in-place so you should not free the individual results. They are portions of the original string. You can use the POSIX function strdup to make copies that can be free'd, and will persist beyond the life of the original buffer contents.
You should add
params[0] = NULL;
right after the initial malloc (or use calloc) otherwise you'll be using an unitialized pointer if the line is empty. Then at the end
free(params);
you don't need to free any of params[i] since those are pointers into the local line[] buffer.
I'm trying to get my output to be
Starting Professor 1
Starting Professor 2
Starting Professor 3
...
but I never get "Starting Professor 1" when num_professors = 2. I thought making an array of ids would save the whole passing in of the address of id, but apparently not. There's about 70 other things I have to do for this project and having a roadblock on this simple thing (that probably takes a few seconds to fix) is quite frustrating to say the least. Thanks is greatly appreciated
void * professorFunc(void *p){
sem_wait(&workAssignment);
if(buffer == 0){
buffer++;
Professor *professor = (Professor*)p;
fprintf(stdout,"Starting Professor %d\n", *professor->id);
}
buffer = 0;
sem_post(&workAssignment);
pthread_exit(0);
}
int main(int argc, char ** argv){
//Semaphore intialization
buffer = 0;
if(sem_init(&workAssignment, 0, 1)){
printf("Could not initialize semaphore.\n");
exit(1);
}
//Creating threads
pthread_t professor[num_professors];
Professor *p;
int ids[num_professors];
int i;
p = malloc (sizeof (*p) * num_professors);
for(i = 0; i < num_professors; ++i){
ids[i] = i + 1;
p->id = &ids[i];
//printf("Id: %d\n", *p->id);
if(pthread_create(&professor[i], NULL, professorFunc, p) != 0){
perror("pthread_create");
exit(1);
}
//printf("yo I'm here after function now\n");
}
for(i = 0; i < num_professors; ++i){
if(pthread_join(professor[i], NULL) != 0){
perror("pthread_join");
exit(1);
}
}
free(p);
}
This line:
if(pthread_create(&professor[i], NULL, professorFunc, p) != 0){
should be:
if(pthread_create(&professor[i], NULL, professorFunc, &p[i]) != 0){
So I have this code for a shell:
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_ARGS 256
#define SIZE 256
void orders(char *command[SIZE]);
int main() {
char buffer[SIZE]= "";
//char input_args[MAX_NUM_ARGS];
char **input_args = NULL;
int i = 0;// counting variable
int blah = 0;
printf("Welcome to the AY shell.\n");
while(1){
//initialize array of strings
//first free any prevously allocated memory
if (input_args != NULL)
{ //memory has been allocated free it
for (i = 0; i <MAX_NUM_ARGS; i++)
{
free(input_args[i]);
}
}
//free array of strings
free(input_args);
//new allocate memory
input_args = (char**) malloc(sizeof(char*) * MAX_NUM_ARGS);
//check return value for error
if (input_args == NULL)
{
printf("We are out of memory. =(\n");
continue;
//print error: out of memory, exit with a error code
exit(0);
}
//allocate memory for each string
for (i = 0; i <MAX_NUM_ARGS; i++)
{
input_args[i]= (char*)malloc(sizeof(char) * MAX_NUM_ARGS);
if(input_args[i] == NULL)
{//error
printf("Error, the input is empty.");
continue;
}//end of if statement
}//end of for loop
printf("~$: "); //prompts the user for input
fgets(buffer, sizeof(buffer), stdin);
//if the user types in exit, quit
if (strcmp(buffer, "exit\n") == 0){
exit(0);
} //end of if statement
//if user types in clear, wipe the screen and repeat the lop
else if(strcmp(buffer, "clear\n")==0){
system("clear");
continue;
}//end of else if
//should the user punch in nothing, repeat the loop
else if (strcmp(buffer, "\n") == 0) {
continue;
}//end of else if
input_args[1] = NULL;
for (i = 0; i < SIZE; i++) {
if(buffer[i] != '\n' && buffer[i] != ' ' && buffer[i] != '\t'){
input_args[0][i] = buffer[i];
} //end of if statement
else{
input_args[0][i] = '\0';
}//end of else statment
}//end of for loop
//if the input doesn't fall under the conditionals above, execute orders.
orders(input_args);
} //end of while loop
return 0;
}//end of main function
void orders(char *command[SIZE]){
//handles the commands of the shell
int retval = 0; //return value
int pid = 0;
int childValue = 0;
pid = fork();
if (pid != 0){
// printf("I'm the parent, waiting on the child.\n");//debug
pid = waitpid(-1, &childValue,0);
// printf("Child %d returned a value of %x in hex.\n", pid, childValue);
return;//return backs to the main prompt
}//end of if statement
else{
// printf("I am the first child.\n");
retval = execvp(command[0], command);
exit(2);
if (retval != -1){
//print error!
printf("Invalid command!\n");
exit(2);
}
}//end of else block
}//end of orders function
Now, it executes clear, exit, and single word commands just well, like ls, or pwd. However, multi-line commands such as "vim " don't work, nor changing directories.
What am I doing wrong?
I'm suspecting the retval = execvp(command[0], command); is causing problems, but I'm not too entirely sure. Any thoughts? I don't want a direct answer, since this is homework, just a push in the right direction.
This section:
input_args[1] = NULL;
for (i = 0; i < SIZE; i++) {
if(buffer[i] != '\n' && buffer[i] != ' ' && buffer[i] != '\t'){
input_args[0][i] = buffer[i];
} //end of if statement
else{
input_args[0][i] = '\0';
}//end of else statment
}//end of for loop
limits input_args to only have the first index be used. I assume this is where you would find a way to have a j++; inside the else clause and use input_args[j][i] or something similar...
And your last comment matches this, since your retval = execvp(command[0], command); is also only using the first item from the list.
for my class we are to implement a shell with output redirection. I have the output redirection working, except my first command is always corrupted see:
$ echo this doesn't work
H<#?4echo
No such file or directory
$ echo this does work
this does work
but every command afterwards seems fine. What technique do I use to find the bug that is causing this problem?
I think it has something to do with not fflushing properly. I sprinkled it around my code (which was stupid) to see if it would help during the loop but it did not. I've also tried printing out my OrderedIds list which is just a list of commands to check if I could find H<#?4 anywhere, but even when I initialized it, it did not work.
Thanks for your help.
#define LENGTH 1000
#define MAXCMD 11
#define MAX_STR_LEN 20
void init(char *temp);
void clean(char **orderedIds);
void init_pid(int *temp);
void reap(int *temp,int ret_status);
void jobs(int *pid_list, char **orderedIds);
int ioRedir(char **orderedIds);
void reap(int *temp,int ret_status){//chainsaws all zombies
int a;
for (a=0; a<LENGTH; a++ ){
waitpid(temp[a],&ret_status,WNOHANG) == temp[a];
}
}
void init(char *temp){//Function to initialize/reset the cmd array
int i;
for(i=0; i<LENGTH; i++){
temp[i] = 0;
}
}
void init_pid(int *temp){//Function to initialize/reset the pid list
int i;
for(i=0; i<LENGTH; i++){
temp[i] = -777;
}
}
void clean(char **orderedIds){//garbage collection
int i;
for(i=0; i<MAXCMD; i++){
free(orderedIds[i]);
}
free(orderedIds);
}
void jobs(int *pid_list, char **orderedIds){//function to check for background proccesses
printf("Jobs:\n");
int y;
for(y=0; y<LENGTH; y++){
if(kill(pid_list[y], 0) == 0){
printf("%d\n", pid_list[y]);
}
}
clean(orderedIds);
printf("$ ");
}
int ioRedir(char **orderedIds){
int i;
for ( i = 0; i<MAXCMD; i++){
if(orderedIds[i] == NULL){
return -1;
}
if(strcmp(orderedIds[i],">")==0){
return (i+1);
}
}
}
int main (int argc, char *argv[], char *envp[])
{
char temp[LENGTH];
char * tok;
char c = '\0';
int saved_stdout;
int pid_list[LENGTH];
int ret_status;
int numFile;
int pid_counter = 0;
int outputfd = -1;
char outputFile[MAX_STR_LEN];
pid_t pid;
printf("$ ");
int i, j, y, background= 0;
init_pid(pid_list);
while(c !=EOF) { //while not ^D // Source: LinuxGazzette Ramankutty
outputfd = -1;
fflush(0);
c = getchar();
if(c=='\n'){ //entered command
reap(pid_list, ret_status);
char **orderedIds = malloc(MAXCMD * sizeof(char*));
for (i=0; i<MAXCMD; i++){
orderedIds[i] = malloc(MAXCMD * sizeof(char*));
}
int k=0;
tok = strtok(temp, " \n\t\r");
while (tok !=NULL){
strcpy(orderedIds[k], tok);
k++;
tok = strtok (NULL, " \n\t\r");
}
orderedIds[k] = NULL; //END with NULL
init(temp); //initialize the array
if(orderedIds[0] ==NULL){
printf("\n$ ");
continue;
}
numFile = ioRedir(orderedIds);
if(strcmp(orderedIds[0],"exit")==0){// if exit
printf("now exiting...\n");
break;
}
if(strcmp(orderedIds[k-1], "&")==0){//if background
orderedIds[k-1] = NULL;
background = 1;
}else background = 0;
if(strcmp(orderedIds[0], "jobs") == 0){//if jobs command
jobs(pid_list, orderedIds);
continue;
}
if(strcmp(orderedIds[0], "cd") == 0){ //if change directory command
chdir(orderedIds[1]);
printf("$ ");
continue;
}
pid = fork();
if (pid!=0 && background == 1)
{
//go to end of list in pid and put it in
pid_list[pid_counter] = pid;
pid_counter++;
printf("To the background: %d\n", pid);
} else if (pid==0 && background == 1) {
fclose(stdin); //close child's stdin
fopen("/dev/null", "r"); //open a new stdin that is always empty.
if(execvp(orderedIds[0], orderedIds)){
printf("%s\n", orderedIds[0]);
puts(strerror(errno));
exit(127);
}
}
if (pid != 0 && !background){
//printf("Waiting for child (%d)\n", pid);
fflush(0);
pid = wait(&ret_status);
} else if (pid == 0 && !background) {
if(numFile > 0){
strncpy(outputFile, orderedIds[numFile], strlen(orderedIds[numFile]));
numFile = 0;
//open the output file
outputfd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRGRP | S_IROTH);
if (outputfd < 0) {
exit(EXIT_FAILURE);
}
//close STDOUT
if(close(STDOUT_FILENO) < 0 ){
perror("close(2) file: STDOUT_FILENO");
close(outputfd);
exit(EXIT_FAILURE);
}
//use dup to rerout the output
if(saved_stdout = dup(outputfd) != STDOUT_FILENO){
perror("dup(2)");
close(outputfd);
exit(EXIT_FAILURE);
}
close(outputfd);
}
if (execvp(orderedIds[0], orderedIds)){
printf("%s\n", orderedIds[0]);
puts(strerror(errno));
exit(127);
}
}
dup2(saved_stdout,outputfd);
clean(orderedIds);
fflush(0);
printf("$ ");
} else {
strncat(temp, &c, 1);
}
}
fflush(0);
return 0;
}
The reason for the garbage is that you never initialized temp to an empty string at the beginning of main(). You call init(temp) after processing each command.
There are lots of other problems in your code:
orderedIds[i] = malloc(MAXCMD * sizeof(char*));
Since orderedIds[i] is an array of char, not char*, you should multiply the size by sizeof(char). Also, it's not clear why you're using MAXCMD as the size -- on the previous line this was the maximum number of words on a line, not the number of characters in a word.
strcpy(orderedIds[k], tok);
You should use strncpy() to ensure that you don't copy more than the size of orderedIds[k].
Another option would be not to preallocate all the orderedIds[i] in the first place. Instead of using strcpy(), use strdup() and assign this to orderedIds[k]; if you do this, you have to remember to free() all these strings.
A third option is not to copy the strings at all. Just assign the pointers returned by strtok() to orderedIds[k]. But in this case you mustn't call init(tmp) until after you've forked.
strncpy(outputFile, orderedIds[numFile], strlen(orderedIds[numFile]));
The limit should be the size of outputFile, not the length of orderedIds[numFile]. strncpy() will never copy more than the length of the source, you need to tell it the maximum size of the destination to prevent a buffer overflow.
outputfd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRGRP | S_IROTH);
if (outputfd < 0) {
exit(EXIT_FAILURE);
}
You should call perror() to report the reason that open() failed.
puts(strerror(errno));
Call perror(), like you do elsewhere.