Variable suddenly changing inside fork() child process C - very weird behaviour - c

I'm hoping someone here could help me cause I'm witnessing some weird behavior from my C code.
I have a Linux shell-like program in C. The code is 250 lines long, so I can't be posting it here.
In short, I have this function for non-built in commands:
int not_builtin(char** args, int background, char* line) {
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork failed\n");
}
else if (pid == 0) {
if (execvp(args[0], args) == -1) {
printf("exec failed\n");
}
return 0;
}
else {
if (!background) {
if (waitpid(pid, &status, 0) < 0) {
printf("An error occurred\n");
}
}
add_job(line, pid, background);
}
return 1;
}
When I run my shell on my PC (Macbook), everything works as expected. BUT, when I run this on my university server (Linux), I get a bug. I cannot properly debug this since on my computer everything works fine. The bug is it outputs: "exec failed" for any command (line 9).
By printing through the code, I was able to detect the following behavior:
The char** args are good throughout the code, I printed them until just before the "if (pid == 0)".
Once you enter the if statement mentioned above, printing the args returns some garbage.
Specifically, for an input: $ ls -l
I got printed ls -l before the if, and ?\?? after the if.
Like this for any other command string.
What could be the reason?
The whole day the program worked fine. It started to happen once I changed my "parse-line" function, to copy the line before editing. It now looks like this:
char** parse_line(char *line, int *background) {
char line_copy[BUFF_SIZE];
strcpy(line_copy, line);
char **tokens = malloc(BUFF_SIZE*sizeof(char*));
if (!tokens) {
printf("An error occurred\n");
exit(EXIT_FAILURE);
}
int position = 0;
char *token;
token = strtok(line_copy, DELIM);
while (token) {
tokens[position] = token;
position++;
token = strtok(NULL, DELIM);
}
// change the background option
if (position > 0 && strcmp(tokens[position-1], "&")==0) {
*background = 1;
tokens[position-1]=NULL;
line[strlen(line)-2] = '\0';
} else {
*background = 0;
tokens[position] = NULL;
}
return tokens;
}
Before the change I would perform the strtok directly on line, but now I figured I need that value intact. But how is this connect in any way?
Any help would be MUCH appreciated

Related

C: How do I pass a value from child process to parent process via pipes before execv?

Before I start, I just want to say that this is for a school assignment of mine. I'm really close to finishing, except well, since I'm here, obvious to say, I'm stuck on a problem :(.
First of I'll explain what my assignment wants:
The assignment wants me to create a command-line program in C that allows the user to type in N number of /bin/ commands (E.g. > /bin/ps /bin/ls "/bin/which gcc"). Once the user enters the command and hits the enter key, the parent process (the parent process is the program) will create N child processes (i.e. no. of /bin/ commands entered = no. of child processes parent process will create). Each child will run one of the N commands. All the children will be running concurrently, with the parent waiting for each child to terminate.
Once a child terminates, the parent will print whether the command executed successfully or not (E.g. "Command /bin/ps has completed successfully" or "Command /bin/ps has not completed successfully") and once all children have been terminated, the parent will print "All done, bye!"
The issue:
So I've managed to get my child processes to run concurrently, the only issue is that I'm not sure how to pipe the value of the command (like /bin/ps or /bin/which gcc) from the child process to the parent process to print out the success or not message. I've tried putting the write pipe above my execv which allows me to pipe what I want but the execv won't output anything and I can't put my pipe code below my execv because in that case, then while my execv output will show, my pipe won't. I did think that it might be due to close(1) but commenting that out didn't change the result.
So what I am trying to achieve is something like this:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
/usr/bin/gcc
localdomain
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
a.txt b.c
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
All done, bye!
>
But right now, I'm getting:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
>
As you can see, my execv output for the /bin/ commands aren't shown in the output.
I've tried searching SO for people who faced this similar issue as me but none of their solutions managed to work for me which is why I'm asking here. If there's anything you're not clear about, please let me know and I will try my best to explain.
The code:
q1.c
#include "q1.h"
int main(int argc, char *argv[])
{
int
child_status,
pipe_array[2];
pid_t child;
char *success_or_fail;
char *msg_buffer = malloc(CHAR_MAX);
if (msg_buffer == NULL)
{
return -1;
}
struct timespec tw = {.tv_sec = 0, .tv_nsec = 10000000L};
Tuple *process_tuple;
size_t tuple_size = sizeof(*process_tuple) + sizeof(pid_t) + sizeof(char *);
process_tuple = calloc(argc, tuple_size);
if (process_tuple == NULL)
{
return -1;
}
// if (pipe(pipe_array) == -1)
// {
// perror("pipe: ");
// return -1;
// }
for (int j = 1; j < argc; j++)
{
child = fork();
if (child == 0)
{
int
executed,
num_of_words,
num_of_chars;
char
string[strlen(argv[j]) + 1],
*backup = argv[j];
snprintf(string, sizeof(string), "%s", argv[j]);
num_of_chars = get_num_of_chars(string);
num_of_words = get_num_of_words(string);
char *command[num_of_chars + 1];
preparing_the_command(num_of_words, string, command);
// close(pipe_array[0]);
// close(1);
// dup2(pipe_array[1], STDOUT_FILENO);
// write(pipe_array[1], backup, sizeof(backup));
process_tuple[j - 1].pid = getpid();
process_tuple[j - 1].command = backup;
printf(" %i-PID -> %i\n %i-Command -> %s\n\n", process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].command);
executed = execv(command[0], command);
nanosleep(&tw, 0);
if (executed == -1)
{
exit(EXIT_FAILURE);
}
else
{
exit(EXIT_SUCCESS);
}
}
else if (child == -1)
{
perror("fork() failed: ");
exit(EXIT_FAILURE);
}
}
printf(" PID -> %i\n Command -> %s\n\n", process_tuple[0].pid, process_tuple[0].command);
// while ((child = waitpid(-1, &child_status, 0)) != -1)
// {
// for (int o = 0; o < argc; o++)
// {
// printf(" PID -> %i\n Command -> %s\n\n", process_tuple[o].pid, process_tuple[o].command);
// }
// close(0);
// close(pipe_array[1]);
//
// dup2(pipe_array[0], STDIN_FILENO);
// char *recipient;
//
// read(pipe_array[0], recipient, sizeof(recipient));
// if (!(WIFEXITED(child_status) && (WEXITSTATUS(child_status) == 0)))
// {
// success_or_fail = "not completed successfully";
// }
// else
// {
// success_or_fail = "completed successfully";
// }
// snprintf(msg_buffer, CHAR_MAX, "Command %s has %s\n", recipient, success_or_fail);
// fputs(msg_buffer, stdout);
// }
fputs("All done, bye!\n", stdout);
free(msg_buffer);
return 0;
}
int get_num_of_chars(const char string[])
{
int
i = 0,
num_of_chars = 0;
while (string[i++] != '\0')
{
if (string[i] != ' ' && string[i] != '\t')
{
num_of_chars++;
}
}
return num_of_chars;
}
int get_num_of_words(const char string[])
{
int
i = 0,
num_of_words = 0;
bool is_not_separator = false;
while (string[i++] != '\0')
{
if (string[i] == ' ' || string[i] == '\t')
{
is_not_separator = false;
}
else if (!is_not_separator)
{
is_not_separator = true;
num_of_words++;
}
}
return num_of_words;
}
void preparing_the_command(int num_of_words, char string[], char *command[])
{
char *token;
for (int j = 0; j < num_of_words && (token = strtok_r(string, " ", &string)); j++)
{
command[j] = token;
}
command[num_of_words] = (void *) NULL;
}
q1.h
#ifndef ASSIGNMENT2Q1_Q1_H
#define ASSIGNMENT2Q1_Q1_H
/***************
** LIBRARIES **
***************/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
/*************
** STRUCTS **
*************/
typedef struct
{
pid_t pid;
char *command;
} Tuple;
/*************************
** FUNCTION PROTOTYPES **
*************************/
int get_num_of_chars(const char string[]);
int get_num_of_words(const char string[]);
void preparing_the_command(int num_of_words, char string[], char *command[]);
#endif //ASSIGNMENT2Q1_Q1_H

Why does strtok occasionally cause a bus error?

EDIT: I have made the info here more specific and executed some recommendations from the comments.
I have a shell written in C that works like a charm when used. However, I have some tests written for a function called pipe_exec that causes a bus error. I thought it was originally from strtok in my split function (and it may still be).
The pipe_exec func basically deals with commands with pipes like ls -a | wc -l or something. It always works fine when I'm using the actual shell but with the tests, there's always a bus error if there are any flags involved with the piped commands.
The issue could jut be with my test.
But I have no clue what the issue is. It's tracing back to the strtok in my split function, but it only has a bus issue with the tests and never in any actual equivalent situations.
Any help here is appreciated. Sorry for so much code to look at.
shell_exec_tests.c
static char *args1[20] = {"ls ", " wc"}; // works
static char *args2[20] = {"ls -a", "wc -l"}; // causes bus error
static int a = 0;
static int b = 0;
void test_setup(void)
{
a = pipe_exec(args1);
b = pipe_exec(args2);
}
void test_teardown(void)
{
// nothing
}
MU_TEST(test_check)
{
mu_check(a == EXIT_SUCCESS);
mu_check(b == EXIT_SUCCESS);
}
MU_TEST_SUITE(test_suite)
{
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
MU_RUN_TEST(test_check);
}
int main()
{
MU_RUN_SUITE(test_suite);
MU_REPORT();
return MU_EXIT_CODE;
}
pipe_exec.c
// make_proc: determine if a process goes to stdout or takes in data from stdin
void make_proc(int in, int out, char **cmd)
{
pid_t rc;
int status;
rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc == 0) {
if (in != STDIN_FILENO) {
dup2(in, STDIN_FILENO);
close(in);
}
if (out != STDOUT_FILENO) {
dup2(out, STDOUT_FILENO);
close(out);
}
execvp(*cmd, cmd);
errmsg(*cmd);
exit(1);
}
waitpid(rc, &status, WUNTRACED);
return;
}
// pipe_exec: loop through each command, connecting each through a pipe
int pipe_exec(char **args)
{
int in, status, return_val;
int pipe_no; // keep track of no. of cmds seperated by pipes
int pfd[2];
pid_t rc;
char **cmd;
return_val = EXIT_SUCCESS;
in = 0;
pipe_no = 0;
while (*args) {
cmd = split(*args, " \t\r\n");
if (!args[1]) {
break;
}
if (pipe(pfd) < 0) {
perror("pipe");
}
make_proc(in, pfd[1], cmd);
close(pfd[1]);
in = pfd[0];
args++;
pipe_no++;
}
// move pointer back
args -= pipe_no;
rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc == 0) {
if (in != 0) dup2(in, STDIN_FILENO);
execvp(*cmd, cmd);
errmsg(*cmd);
return_val = EXIT_FAILURE;
exit(1);
}
waitpid(rc, &status, WUNTRACED);
// pretty sure i need a pipe to get the EXIT_FAILURE from
// the child if the child fails, but for now im just working
// on finding that bus error issue
return return_val;
}
And lastly, my split function:
// trim: trim leading and trailing whitespace on a string
static char *trim(char *str)
{
char *end;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator character
end[1] = '\0';
return str;
}
// split: take a string and break it up into an array of strings based on delim
char **split(char *s, const char *delim)
{
char **split_s;
char *token;
size_t len;
int i;
len = strlen(s);
split_s = calloc(len*2, sizeof(char*));
if (split_s == NULL) {
fprintf(stderr, "split: could not allocate memory\n");
exit(EXIT_FAILURE);
}
i = 0;
token = strtok(s, delim);
while (token != NULL) {
split_s[i] = trim(token);
token = strtok(NULL, delim);
i++;
}
split_s[i] = NULL;
return split_s;
}

Having trouble with execvp

I am having some trouble with a basic shell program I am trying to write in c. Whenever I try calling exec in a function such as ls, execvp will return with an error stating that the file or directory could not be found. I think that the problem is with the parsing because in main, the command can be printed but in the function it is blank. Thanks
Here is the code for the function :
int extern_process(char *arg[]){
pid_t pid;
int errnum, ifFail;
printf("i%si\n",arg[0]);
pid = fork();
if(pid == -1){
errnum = errno;
fprintf(stderr,"Error: fork %s", strerror(errnum));
return FAIL;
} else if(pid == 0){
ifFail = execvp(arg[0],arg);
if(ifFail < 0){
errnum = errno;
fprintf(stderr,"Error: exec %s", strerror(errnum));
return FAIL;
}
} else {
pid = wait(NULL);
}
return SUCCESS;
}
Here is the code for the parsing function just in case:
void parse_cmd(char *retval[], char *cmd){
char *tmp;
char a[100];
strcpy(a,cmd);
int i = 0;
tmp = strtok(a," \n\t\0");
if(retval == NULL){
fprintf(stderr, "Error with allocation\n");
return;
}
if(tmp == NULL){
printf("Error with parsing.\n");
return;
}
while(tmp != NULL){
retval[i] = tmp;
tmp = strtok(NULL," \n\t\0");
i++;
}
retval[i] = NULL;
}
Here is the output:
shell> ls
ls
i i
Error: exec no file or directory found
I'm pretty sure strtok returns a pointer that refers to that first argument which, in your case, is a stack allocation. Returning an array of pointers to that stack allocation would result in undefined behavior, I believe. This may or may not be the cause of your problem. It's difficult to know without seeing more of the code. To test, try changing this part of your code like this:
void parse_cmd(char *retval[], char *cmd){
char *tmp;
char *a = strdup(cmd);
int i = 0;
Before using it in production, you need to work out some way to ensure that you free "a" or you'll get a leak. Maybe you could just return it instead of void and free it from elsewhere, or you could actually strdup() each token and write a function to free them all or whatever works for you.
If there are other problems, they may be in other code. I don't really see anything else wrong here.

Signal Handler running multiples times

Currently writing a basic shell that handles signal interrupt with variety of other functions.
int numberOfCommands = 0;
int signalHandlerFired = 0;
char history[HISTORY_DEPTH][COMMAND_LENGTH];
void handle_SIGINT(){
writeHistory();
signalHandlerFired = 1;
}
int main(int argc, char* argv[])
{
char input_buffer[COMMAND_LENGTH];
char *tokens[NUM_TOKENS];
char cwd[COMMAND_LENGTH];
struct sigaction handler;
handler.sa_handler = handle_SIGINT;
sigaction(SIGINT, &handler, NULL);
while (true) {
signalHandlerFired = 0;
getcwd(cwd, sizeof(cwd));
write(STDOUT_FILENO, cwd, strlen(cwd));
write(STDOUT_FILENO, "> ", strlen("> "));
_Bool in_background = false;
read_command(input_buffer, tokens, &in_background);
if (signalHandlerFired == 1) {
continue;
}
char exclamationCommand[2];
exclamationCommand[0] = tokens[0][0];
exclamationCommand[1] = '\0';
char* hcNumber = &tokens[0][1];
if (strcmp(&exclamationCommand[0], "!") == 0) {
if (atoi(hcNumber) == 0) {
} else {
retrieveHistory(hcNumber, numberOfCommands, input_buffer, tokens, in_background);
}
continue;
}
insertHistory(&numberOfCommands, input_buffer, tokens);
//exit
if (strcmp(tokens[0], "exit") == 0) {
exit(0);
}
//pwd
if (strcmp(tokens[0], "pwd") == 0) {
getcwd(cwd, sizeof(cwd));
write(STDOUT_FILENO, cwd, strlen(cwd));
write(STDOUT_FILENO, "\n", strlen("\n"));
continue;
}
//cd
if (strcmp(tokens[0], "cd") == 0) {
int dcSuccess = chdir(tokens[1]);
if (dcSuccess == -1) {
write(STDOUT_FILENO, "Invalid Directory", strlen("Invalid Directory"));
write(STDOUT_FILENO, "\n", strlen("\n"));
}
continue;
}
//history
if (strcmp(tokens[0], "history") == 0) {
writeHistory(tokens[0]);
}
executeCommand(numberOfCommands, input_buffer, tokens, in_background);
}
return 0;
}
In the while loop in main() which gets inputs from the command line and does processing depending on the type of command entered, the signal handler will run twice or more depending on...is what I don't know.
The purpose of SIGINT [Ctrl+C] interrupt is to display the command history. This works fine if i run it once, but after..
/home/ahn/> ls
Token: ls
a.out Makefile.txt~ shell shell.c~ shellsample.c~
Makefile mystring_sol.c shell.c shell.o
/home/ahn/> ^C
History:
1 ls
---------------Second Run
/home/ahn/> ks
Token: ks
Unknown Command
/home/ahn/> ^C
History:
Number Of Commands: 2
1 ls
2 ks
/home/ahn/>
History:
Number Of Commands: 2
1 ls
2 ks
/home/ahn/>
I think I don't have a clear understanding when interrupts happen, at which point in the program is returning too. Moreover, I have no idea why it is running twice. I will provide any function definitions if needed.
Also, the char ks comes out corrupted. Full core here if needed.

Simple Linux Shell - execvp() failing

I need some help with a simple shell for class, and I'm worried I don't quite understand how the execvp() function works.
The shell does not do much, does not support piping, redirection, scripting or anything fancy like that. It only reads a command, reads in the options (with the command as option[0]), and forks.
It worked a few times, then started giving me errors about not being able to find commands. Other similar questions posted here had to do with piping or redirection.
Please forgive the noobcode, it's not pretty, but I hope it's legible:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define OPT_AMT 10
const size_t SIZE = 256;
int i = 0;
int o = 0;
int main(void) {
// initializing data
int exit = 0;
char cwd[SIZE];
char cmd[SIZE];
char input[SIZE * OPT_AMT];
char *opt[OPT_AMT];
getcwd(cwd, SIZE);
// main loop
while (exit == 0) {
// reset everything
o = 1;
i = 0;
cmd[0] = "\0";
while (i < OPT_AMT) {
opt[i++] = "\0";
}
// get input
printf("%s $ ", cwd);
scanf("%s", cmd);
gets(input);
opt[0] = cmd;
char *t = strtok(input, " ");
while (t != NULL) {
opt[o++] = t;
t = strtok(NULL, " ");
}
// if exit, exit
if (strcmp(cmd, "exit") == 0) {
exit = 1;
}
// else fork and execute
else {
pid_t pID = fork();
if (pID == 0) { // child process
execvp(cmd, opt);
} else if (pID < 0) { // failed to fork
printf("\nFailed to fork\n");
} else { // parent process
wait(0);
}
}
}
// cleanup
printf("\nFinished! Exiting...\n");
return 0;
}
Anything blatantly wrong? I most recently added the exit condition and the resetting the options array.
Also, this is my first question, so remind me of any rules I may have broken.
For starters, this
cmd[0] = "\0";
ought to be
cmd[0] = '\0';
Listen to your compiler's warnings.
To enable them use the options -Wall -Wextra -pedantic (for gcc).
Also you might better want to initalise opt's elements to point to "nothing", that is NULL but to the literal "\0":
while (i < OPT_AMT) {
opt[i++] = NULL;
}
as execvp() requires opt to be a NULL-terminated array of C-"strings" (thanks Paul for mentioning/wording the relevant background).
Also^2: Do not use gets(), as it's evil and not even part of the C Standard anymore. Instead of
gets(input);
use
fgets(input, sizeof input, stdin);
gets() easyly let's the user overflow the (input) buffer passed. (Mentioning this came to my mind without Paul, btw ... ;-))

Resources