Can unexecuted code cause a segfault? - c

Yeah I know that sounds crazy but that is the only way I can describe it at this point. I’m writing a program for a class that mimics a terminal, in that it takes commands as inputs and executes them. (I’ll put some code below) As you will see, the program holds a history of commands historyArgs so that the user can execute recent commands.
Command history is listed when the user performs Ctrl-C. Recent commands are accessed with the command 'r' (for most recent) and r 'x' (where x is matches the first letter of a command in recent history). When I started implementing the 'r' command, I started getting this segfault. I then reverted all my changes and added one line at a time. I found that adding even primitive variable declaration causes a segfault (int temp = 10;) But this is where it gets stranger. I believe the line that causes a segfault (int temp = 10;) is never accessed. I put printf statements and flush the output at the beginning of the if block to see if the block has been entered, but they don't execute.
setup was provided for us. It takes the user input and puts it in char *args[] i.e. input = ls -a -C, args = {"ls", "-a", "-C", NULL, ... NULL}. I marked the line in main that somehow leads to a segfault.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#define BUFFER_SIZE 50
static char buffer[BUFFER_SIZE];
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
char *historyArgs[10][MAX_LINE/2 + 1];
int historyCount = 0;
int indexOfLatestCommand = 0;
/* the signal handler function */
void handle_SIGINT() {
//write(STDOUT_FILENO,buffer,strlen(buffer));
if(historyCount > 0){
printf("\n%i command(s), printing most recent:\n", historyCount);
int i;
for(i = 0; i < historyCount && i < 10; i++){
printf("%i.] %s ", i+1, historyArgs[i][0]);
//print args
int j = 1;
while(historyArgs[i][j] != NULL){
printf("%s ", historyArgs[i][j]);
j++;
}
printf("\n");
}
}
else{
printf("\nNo recent commands.\n");
}
fflush(stdout);
}
/**
* setup() reads in the next command line, separating it into distinct tokens
* using whitespace as delimiters. setup() sets the args parameter as a
* null-terminated string.
*/
void setup(char inputBuffer[], char *args[],int *background)
{
int length, /* # of characters in the command line */
i, /* loop index for accessing inputBuffer array */
start, /* index where beginning of next command parameter is */
ct; /* index of where to place the next parameter into args[] */
ct = 0;
/* read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
start = -1;
if (length == 0)
//exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading the command");
exit(-1); /* terminate with error code of -1 */
}
/* examine every character in the inputBuffer */
for (i = 0; i < length; i++) {
switch (inputBuffer[i]){
case ' ':
case '\t' : /* argument separators */
if(start != -1){
args[ct] = &inputBuffer[start]; /* set up pointer */
ct++;
}
inputBuffer[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
args[ct] = &inputBuffer[start];
ct++;
}
inputBuffer[i] = '\0';
args[ct] = NULL; /* no more arguments to this command */
break;
case '&':
*background = 1;
inputBuffer[i] = '\0';
break;
default : /* some other character */
if (start == -1)
start = i;
}
}
args[ct] = NULL; /* just in case the input line was > 80 */
}
int main(void)
{
int i;
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char* args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
int status;
struct sigaction handler;
handler.sa_handler = handle_SIGINT;
sigaction(SIGINT, &handler, NULL);
strcpy(buffer,"Caught <ctrl><c>\n");
while (1){ /* Program terminates normally inside setup */
background = 0;
printf("COMMAND->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
//If command wasn't empty
if(args[0] != NULL){
if(strcmp(args[0], "r") != 0){
//Copy into history if not a recent call
for(i = 0; i < MAX_LINE/2+1 && args[i] != NULL; i++){
historyArgs[historyCount%10][i] = malloc(strlen(args[i]));
strcpy(historyArgs[historyCount%10][i], args[i]);
}
indexOfLatestCommand = historyCount%10;
historyCount++;
}
//Create child process
int pid = fork();
//In child process
if(pid == 0){
if(strcmp(args[0], "r") == 0){
//If only "r" was entered, execute most recent command
if(args[1] == NULL){
printf("Entering recent?\n");
fflush(stdout);
int temp = 10; //SEGFAULTS HERE IF THIS IS INCLUDED
execvp(historyArgs[indexOfLatestCommand][0], &historyArgs[indexOfLatestCommand][0]);
}
else{
//Find in args[1][0] history, run if found
for(i = indexOfLatestCommand; i >= 0; i--){
if(historyArgs[i][0][0] == args[1][0]){
execvp(historyArgs[i][0], &historyArgs[i][0]);
break;
}
}
if(i == -1){
for(i = historyCount > HISTORY_SIZE ? HISTORY_SIZE : historyCount; i > indexOfLatestCommand; i--){
if(historyArgs[i][0][0] == args[1][0])
execvp(historyArgs[i][0], &historyArgs[i][0]);
break;
}
}
}
}
else execvp(args[0], &args[0]);
}
//In parent process
else if (pid > 0){
/*If child isn't a background process,
wait for child to terminate*/
if(background == 0)
while(wait(&status) != pid);
}
}
}
}
Another thing worth mentioning is that declaring a variable in that spot doesn't cause a segfault. Only assigning a value to a new variable does. Reassigning globals in that section also doesn't cause a segfault.
EDIT: What triggers a crash. Commands execute correctly. When you run it, you can type in any command, and that should work. It isn't until I perform Ctrl-C and print out the history that the program segfaults.
Example input:
ls
ls -a
grep
Ctrl-C
HEADS UP: if you decide to run this, know that to end the task, you will probably need to use the kill command because I haven't implement "q" to quit.

Symptoms like you see (unrelated code changes appear to affect the nature of a crash) usually mean that your program caused undefined behaviour earlier. The nature of the behaviour changes because your program is relying on garbage values it has read or written at some stage.
To debug it, try and remove all sources of undefined behaviour in your program. The most obvious one is the content of your void handle_SIGINT() function. The only things you can portably do in a signaler are:
Set a variable of type volatile sig_atomic_t, or other lock-free type, and return
Do other stuff and call _Exit, abort or similar.
Especially, you cannot call any library functions as they may not be re-entrant.
For a full specification see section 7.14.1 of the current C Standard. If you are also following some other standard, e.g. POSIX, it may specify some other things which are permitted in a signal handler.
If you do not intend to exit then you must set a flag , and then test that flag from your main "thread" later to see if a signal arose.

Related

Copying a c-string from 1 buffer to another in a child process using memcpy or strcpy does not seem to work

I am trying to write a mock shell that saves command line history and overrides the signal action for SIGINT to trigger printing the previous 10 commands entered by the user. As far as I am aware, everything from handling all of the signals to updating cursor and running commands using execvp works fine.
I however am running into 2 problems that I am having a hard time trying to wrap my head around.
Trouble trying to actually copy the contents of the input buffer to my own c-string vector, namely histv. After calling read and storing user input in buf, I try to copy the contents of buf to the location at histv[cursor % MAX_HISTORY] (the reason I am using modulus is because it seems easier to use a kind of circular array instead of handling the case that cursor rises to some number greater than MAX_HISTORY)
When I try running the process as a background process I always get an execvp error, even when the command is valid. I know it's happing after the parent process gets the SIGUSR2 signal and creates a new child to run the commands. So I am assuming it has something to do with what happens to argv after the child process kills itself and raises SIGUSR2
Below is the code for the entire program. It is a bit messy in some spots, but overall it's rather simple. I have used all of memcpy, strcpy, and strncpy to no avail. They all compile and run without errors, but none of them seem to do anything.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
// limits
#define MAX_LINE 80
#define MAX_HISTORY 10
// function headers
void print_history(unsigned int const, char**);
void handler_func(int);
void read_func(char*[], char[], char**, unsigned int const);
int parse_args(char*, char**, size_t);
// globals
volatile sig_atomic_t sig_caught = 0;
void print_history (const unsigned int cursor, char **histv) {
int temp = (cursor > MAX_HISTORY) ? (cursor - MAX_HISTORY) : 0;
puts("\n\nprinting the previous ten commands...");
printf("cursor %d", cursor);
for (int i = 1; temp < cursor; temp++) {
printf("%d%s\n", i++, histv[temp % MAX_HISTORY]);
}
}
void handler_func(int sig)
{
/* update loop control variable */
sig_caught = 1;
}
int main(void)
{
// declare sigaction struct
struct sigaction sigactor;
// initialize sigaction struct
sigactor.sa_handler = handler_func;
sigemptyset(&sigactor.sa_mask);
sigactor.sa_flags = 0;
// set up sigaction for SIGINT
if (sigaction(SIGINT, &sigactor, NULL) == -1) {
perror("siagction() failed");
_exit(EXIT_FAILURE);
}
// set the buffer to no buffering
setvbuf(stdout, NULL, _IONBF, 0);
unsigned int cursor = 0;
/* initlialize history vector */
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
// enter shell loop
while (1) {
/* fork process and get child pid */
int cpid;
char *argsv[MAX_LINE/2+1];
char buf[MAX_LINE];
while(!sig_caught) {
cpid = fork();
/* child */
if (cpid == 0) {
read_func(argsv, buf, histv, cursor);
}
/* fork error */
else if (cpid < 0) {
perror("Error forking process");
_exit(EXIT_FAILURE);
}
/* parent process begins here */
else {
/* variable to store status returned from child*/
int cstatus;
/* suspend parent until child exits *
* store return status in cstatus */
waitpid(cpid, &cstatus, 0);
/* get status from child process and check for SIGTERM *
* SIGTERM is raised by child when someone enters '!q' */
switch(WTERMSIG(cstatus))
{
/* user wants to quit */
case SIGTERM:
puts("User issued quit command");
for (int i = 0; i < MAX_HISTORY; i++)
free((void *)histv[i]);
free((void *)histv);
_exit(EXIT_SUCCESS);
/* invalid string length */
case SIGUSR1:
puts("Please enter a valid string");
break;
/* background process */
case SIGUSR2:
cpid = fork();
if (cpid < 0) perror("Error forking process...");
else if (cpid == 0) {
if (execvp(argsv[0], argsv) < 0) {
--cursor;
perror("execvp");
kill(getpid(), SIGUSR1);
}
}
}
if (!sig_caught) cursor++;
}
}// signal loop
kill (cpid, SIGTERM);
print_history(cursor, histv);
fflush(stdout);
sig_caught = 0;
}
}
void read_func(char *argsv[], char buf[], char *histv[], unsigned int const cursor)
{
printf("\nCMD > ");
int background = 0;
size_t length = read(STDIN_FILENO, buf, MAX_LINE);
if (length > 80 || length <= 0) kill(getpid(), SIGUSR1);
/* copy buffer into history and update cursor */
memcpy(histv[cursor % MAX_HISTORY], buf, length);
printf("cursor %d", cursor);
/* parse arguments and return number of arguments */
background = parse_args(buf, argsv, length);
/* user entered quit command or string is invalid */
if (background == -1) kill(getpid(), SIGTERM);
/* signal parent to run process in the background */
if (background == 1) kill(getpid(), SIGUSR2);
/* run command */
if (execvp(argsv[0], argsv) < 0) {
perror("execvp");
_exit(EXIT_FAILURE);
}
}
int parse_args(char buf[], char *argsv[], size_t length)
{
int i, /* loop index for accessing buf array */
start, /* index where beginning of next command parameter is */
ct, /* index of where to place the next parameter into args[] */
bckg; /* background flag */
/* read what the user enters on the command line */
ct = 0;
start = -1;
bckg = 0;
if (buf[0] == '!' && buf[1] == 'q') return -1;
/* examine every character in the buf */
for (i = 0; i < length; i++) {
switch (buf[i]){
case ' ':
case '\t': /* argument separators */
if(start != -1){
argsv[ct] = &buf[start]; /* set up pointer */
ct++;
}
buf[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
argsv[ct] = &buf[start];
ct++;
}
buf[i] = '\0';
argsv[ct] = NULL; /* no more arguments to this command */
break;
case '&':
bckg = 1;
buf[i] = '\0';
break;
default: /* some other character */
if (start == -1)
start = i;
}
}
argsv[ct] = NULL; /* just in case the input line was > 80 */
return bckg;
}
Side note, the parse_args function was initially given to us and I changed it a bit to work with the rest of program, but for the most part I did not write that function.
Please go easy on me, it's been a long time since I've used C for anything and a lot of what I am doing here took a lot of effort to begin understanding how this program behaves. (~:
So both of the problems was with the fact that the memory wasn't being shared, as o11c described. I fixed the issue by using mmap instead of malloc which in turn simplified my program as I no longer had to handle the memory management. Changes are described below.
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
was changed to
char **histv = (char**)mmap(NULL, (sizeof(char*) * MAX_HISTORY), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
for (int i = 0; i < MAX_HISTORY; i++) histv[i] = (char*)mmap(NULL, (sizeof(char) * MAX_LINE), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
While I do not know if this is the best way to do this, it solved my problem.
Also note that I did explicitly unmap the memory using munmap even though it technically should be handled automatically on exit.

background process in shell

I want to implement my own shell. I'm stucked on implementing background process . Actually ,I write some piece of code but I'm not sure if it is work or not .
my code :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
#define HISTORY_SIZE 5 /*keep track of 5 most recent commands*/
int cmd_count; /*global to keep track of most recent commands entered*/
char history[HISTORY_SIZE][MAX_LINE]; /* global so it can be accessed in interrupt handler. */
void viewHistory()
{
int i;
if (cmd_count < 1)
printf("No command history to show. \n");
else {
printf("\n\n");
for (i = (cmd_count >= HISTORY_SIZE) ? cmd_count - HISTORY_SIZE:0;
i < cmd_count; i++)
printf("%d: %s\n",i+1,history[i%HISTORY_SIZE]);
}
//printf("SystemsIIShell->");
}
int setup(char inputBuffer[], char *args[],int *background)
{
int length, /* # of characters in the command line */
i, /* loop index for accessing inputBuffer array */
start, /* index where beginning of next command parameter is */
ct; /* index of where to place the next parameter into args[] */
int temp;
ct = 0;
/* read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading the command");
exit(-1); /* terminate with error code of -1 */
}else{
inputBuffer[length]='\0';
if(inputBuffer[0]=='r'){
if(inputBuffer[1]=='r'){
if(cmd_count==0){
printf("No recent command can be found in the history. \n");
return 0;
}
strcpy(inputBuffer,history[(cmd_count)% HISTORY_SIZE]);
}else{
temp = atoi(&inputBuffer[1]);
if(temp < 1 || temp > cmd_count || temp <= cmd_count -HISTORY_SIZE){
printf("Command number cannot be found. \n");
return 0;
}
strcpy(inputBuffer,history[(temp-1)%HISTORY_SIZE]);
}
length = strlen(inputBuffer);
}
cmd_count++;
strcpy(history[(cmd_count-1)%HISTORY_SIZE], inputBuffer);
for (i = 0; i < length; i++) {
if (inputBuffer[i] == '&') {
inputBuffer[i] = '\0';
*background = 1;
--length;
break;
}
}
}
/* examine every character in the inputBuffer */
for (i = 0; i < length; i++) {
switch (inputBuffer[i]){
case ' ':
case '\t' : /* argument separators */
if(start != -1){
args[ct] = &inputBuffer[start]; /* set up pointer */
ct++;
}
inputBuffer[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
args[ct] = &inputBuffer[start];
ct++;
}
inputBuffer[i] = '\0';
args[ct] = NULL; /* no more arguments to this command */
break;
case '&':
*background = 1;
inputBuffer[i] = '\0';
break;
default : /* some other character */
if (start == -1)
start = i;
}
}
args[ct] = NULL; /* just in case the input line was > 80 */
}
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
while (1){ /* Program terminates normally inside setup */
background = 0;
printf("SystemsIIShell->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
pid_t child; /* process id for child */
int status; /* status for execvp */
child = fork(); /* create a child process*/
if(child < 0){ /* if the child process didn't return 0, the fork is failed */
printf("Fork failed! \n");
}else if(child==0){ /* child process */
if(inputBuffer[0]=='history' || inputBuffer[0] =='h'){
viewHistory();
return 0;
}
status = execvp(args[0],args);
if(status !=0){
printf("%s: command not found. \n", args[0]);
}
}else{ /* parent process */
if(background == 0)
waitpid(child,&background,0);
}
/* the steps are:
(1) fork a child process using fork()
(2) the child process will invoke execvp()
(3) if background == 0, the parent will wait,
otherwise returns to the setup() function. */
}return 0;
}
I don't add whole code but the other things are true. I call execv and it works. When I write on the console :
output terminal :
$ gedit ------->it works correctly because it is a foreground
$ gedit & -----> it opens a gedit file which name is "&"
$ firefox ---> it works correctly
$ firefox & ---> it opens a firefox window which url is www.&.com
How can fix it ?any suggestions ?
editing part : https://github.com/iqbalhasnan/CSE2431-System-II/blob/master/lab2/lab2.c --> I use this code as a reference
you're not implementing background processes, you're trying to start a background process using the syntax of the already-implemented-shell on your computer (but honestly, it's pretty hard to tell what's going on with that indentation. That's really bad. Can you make it readable, please?). That '&' character is recognised by your shell, not by execvp. Have a look at this similar looking question which was the first hit in a google search for your problem.

Process started by execvp() exits with some of commands

I use this code to run some of shell commands, but it exits after ls command.:
where is my wrong?
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#define MAX_LINE 80 /* The maximum length command */
void setup(char inputBuffer[], char *args[],int *background)
{
int length, i, start, ct;
ct = 0;
/* read what the user enters on the command line */
length = read(STDIN_FILENO,inputBuffer,MAX_LINE);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if ( (length < 0) && (errno != EINTR) ) {
perror("error reading the command");
exit(-1); /* terminate with error code of -1 */
}
printf(">>%s<<",inputBuffer);
for (i=0;i<length;i++){ /* examine every character in the inputBuffer */
switch (inputBuffer[i]){
case ' ':
case '\t' : /* argument separators */
if(start != -1){
args[ct] = &inputBuffer[start]; /* set up pointer */
ct++;
}
inputBuffer[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
args[ct] = &inputBuffer[start];
ct++;
}
inputBuffer[i] = '\0';
args[ct] = NULL; /* no more arguments to this command */
break;
default : /* some other character */
if (start == -1)
start = i;
if (inputBuffer[i] == '&'){
*background = 1;
inputBuffer[i-1] = '\0';
}
} /* end of switch */
} /* end of for */
args[ct] = NULL; /* just in case the input line was > 80 */
for (i = 0; i <= ct; i++)
printf("args %d = %s\n",i,args[i]);
} /* end of setup routine */
int main(void)
{
char inputBuffer[MAX_LINE]; /*buffer to hold command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2 + 1]; /*command line arguments */
int should_run = 1; /* flag to determine when to exit program */
while (should_run) {
//background=0;
printf("Msh>");
fflush(stdout);
setup(inputBuffer, args, &background);
execvp(args[0], args);
}
return 0;
}
As Kerrek SB already said, execvp does not return.
To add a little more info: the execv-family of functions replaces your process (running program) with another. This, in cooperation with fork is what happens inside a system() call.
To put it more bluntly:
If you want to run a system command from your C program, and carry on based on "return" value, you should use the system() call. See example.
If you want to spawn a child process, which should run another executable, you should fork, and inside the child process use execv. See the following example.
Remember that execvp does not return.

Adding history feature to shell [duplicate]

This question already has answers here:
Adding a history feature to my simple shell
(2 answers)
Closed 9 years ago.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
/**
* setup() reads in the next command line, separating it into distinct tokens
* using whitespace as delimiters. It also sets the args parameter as a
* null-terminated string.
*/
typedef struct list
{
int num;
int *ptr;
struct history * next;
}history;
void setup(char inputBuffer[], char *args[],int *background)
{
int length, /* Number of characters in the command line */
i, /* Loop index for inputBuffer array */
start, /* Index where beginning of next command parameter is */
ct; /* Index of where to place the next parameter into args[] */
ct = 0;
/* Read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading command");
exit(-1); /* terminate with error code of -1 */
}
/* Examine every character in the inputBuffer */
for (i = 0; i < length; i++) {
switch (inputBuffer[i]){
case ' ':
case '\t' : /* argument separators */
if(start != -1){
args[ct] = &inputBuffer[start]; /* set up pointer */
ct++;
}
inputBuffer[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
args[ct] = &inputBuffer[start];
ct++;
}
inputBuffer[i] = '\0';
args[ct] = NULL; /* no more arguments to this command */
break;
case '&':
*background = 1;
inputBuffer[i] = '\0';
break;
default : /* some other character */
if (start == -1)
start = i;
}
}
args[ct] = NULL; /* just in case the input line was > 80 */
}
int main(void)
{
char inputBuffer[MAX_LINE]; /* Buffer to hold the command entered */
int background; /* Equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* Command line (of 80) has max of 40 arguments */
while (1){ /* program terminates normally inside setup */
background = 0;
printf("CSE2431Sh->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
/* the steps are:
(1) fork a child process using fork()
(2) the child process will invoke execvp()
(3) if background == 0, the parent will wait,
otherwise returns to the setup() function. */
int child_pid;
int status;
int ph;
history *history = NULL;
child_pid = fork();
if(child_pid == 0)
{
ph++;
history->num = ph;
history->ptr = args;
execvp(args[0],args);
/* If execvp returns, it must have failed. */
printf("Execvp Failed\n");
exit(0);
}
else
{
if(background == 0)
{
int parent_pid;
while ((parent_pid = wait(&status)) != -1 && parent_$
;
}
else
{
setup(inputBuffer, args, &background);
}
}
}
}
I'm trying to add a history feature to a shell. The shell should store the command and number the. It should also be able to recover the last 8 commands to run again. For example if 35 commands have been entered by the user 28-35 should be able to be recovered. The user should be able to see the last 8 commands by typing history and run a previous command by typing x num, where num is the number of the command, or xr to run the most recent. My plan was to use a linked list but I'm having trouble with it and don't have but a few hours to complete it.
As we recommended in your previous post:
How about a linked list where you additionally store the length, first
and last item of the list? The items being the commands from your
inputBuffer.

Returning from Signal Handlers

Am I not leaving my signal handler function in the correct way? It does not seem to return to the program normally. Instead it goes into the loop and where it should wait for user input, it skips and reads the length of the "user input" to -1 and errors out. (Will make more sense in code.)
void handle_SIGINT() {
int k = recent;
int count = 0;
int stop;
if (stringSize >= 10) {
stop = 10;
}
else {
stop = p;
}
printf("\nCommand History:\n");
for (count = 0; count < stop; count++) {
if (k < 0) {
k += 10;
}
printf("%s", string[abs(k)]);
k -= 1;
}
}
void setup(char inputBuffer[], char *args[],int *background)
{
//char inputBuffer[MAX_LINE];
int length, /* # of characters in the command line */
i, /* loop index for accessing inputBuffer array */
start, /* index where beginning of next command parameter is */
ct; /* index of where to place the next parameter into args[] */
int add = 1;
ct = 0;
/* read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
printf("%i",length);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading the commanddddddddd");
exit(-1); /* terminate with error code of -1 */
}
}
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
FILE *inFile = fopen("pateljay.history", "r");
if (inFile != NULL) {
int count = 0;
char line[MAX_LINE];
while (fgets(line,sizeof line, inFile) != NULL) {
string[count] = strdup(line);
//string[count][strlen(line)] = '\n';
//string[count][strlen(line) + 1] = '\0';
printf("%s", string[count]);
count++;
stringSize++;
}
p = count % 10;
recent = abs(p - 1);
}
fclose(inFile);
/* set up the signal handler */
struct sigaction handler;
handler.sa_handler = handle_SIGINT;
sigaction(SIGINT, &handler, NULL);
while (1) {/* Program terminates normally inside setup */
background = 0;
printf("COMMAND->");
fflush(0);
setup(inputBuffer, args, &background);/* get next command */
}
}
So when ctrl+c is entered it should catch the signal and handle it. Once it returns back to main, it goes into setup and completely skips the read function and makes length equal to -1. This in turn errors out the program. I think the code inside handle_SIGINT is irrelevant as it is right now. Does anyone know any reason why it would be skipping the read function in setup?
read is blocking, waiting for input. SIGINT arrives. The kernel calls your signal handler. When your signal handler returns, the kernel makes read return -1 and set errno to EINTR. You need to check for this case and handle it by calling read again:
do {
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
} while (length == -1 && errno == EINTR);
The signal handler is supposed to take an int argument:
void handle_sigint(int signum) {}

Resources