I've written a shell in C that does has some basic functionality. I've implemented a 'foreground-only mode' in my program which is toggled with SIGTSTP or CTRL-Z. However, I am able to trap the CTRL-Z signal, but not the SIGTSTP signal. How is this possible? Isn't CTRL-Z the same thing as SIGTSTP? Shouldn't trapping one trap the other?
I trap the signal (I realize signal() is deprecated, but I have tried sigaction() as well, and the problem persists.):
signal(SIGTSTP, trapTstp);
And handle it with this function:
void trapTstp() {
if(foregroundMode == 0) {
write(1, "Entering foreground-only mode (& is now ignored)\n", 49);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 1;
} else {
write(1, "Exiting foreground-only mode\n", 29);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 0;
}
}
If I run my program and hit CTRL-Z, I can successfully toggle in and out of foreground-only mode. However, I cannot trap SIGTSTP. For example, if I run sleep 100, get the PID for it, and run kill -SIGTSTP sleep_pid, the sleep 100 process gets killed early with the output Terminated: 15 indicating it was in fact killed, and thus the SIGTSTP signal was not trapped.
Here is my full shell program:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int statusCode;
int foregroundMode = 0;
int bg = 0;
int bgPsArray[20];
int bgPsCount = 0;
int i;
char line[256];
pid_t popBgProcess() {
int size = sizeof(bgPsArray)/sizeof(bgPsArray[0]);
if (size > 0) {
return bgPsArray[size+1];
} else {
return 0;
}
}
void trapInterrupt(int _) {
int childStatus;
pid_t child;
while ((child = popBgProcess())) {
if(child != getpid()) {
kill(child, SIGKILL);
waitpid(child, &childStatus, 0);
}
}
}
void trapTstp() {
if(foregroundMode == 0) {
write(1, "Entering foreground-only mode (& is now ignored)\n", 49);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 1;
} else {
write(1, "Exiting foreground-only mode\n", 29);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 0;
}
}
int getCommand() {
printf(": ");
fflush(stdout);
if(fgets(line, sizeof(line), stdin) != NULL) {
char *position = strchr(line, '\n');
*position = '\0'; // Replace '\n' with '\0'
if(foregroundMode == 1) { // Foreground mode on
if((position = strchr(line, '&')) != NULL) {
*position = '\0'; // Replace '&' with '\0'
}
bg = 0; // Ignore '&' so do not create background process
} else { // Foreground mode off
if((position = strchr(line, '&')) != NULL) {
*position = '\0'; // Replace '&' with '\0'
bg = 1; // Is a background process
} else {
bg = 0;
}
}
} else { // If input is null
return 0;
}
return 1;
}
void checkProcessCompletion() {
int status;
for(i=0; i<bgPsCount; i++) {
if(waitpid(bgPsArray[i], &status, WNOHANG) > 0) {
if(WIFEXITED(status)) { // If exit
printf("Background PID %d is done: exit value %d\n", bgPsArray[i], WEXITSTATUS(status));
fflush(stdout);
} else if(WIFSIGNALED(status)) { // If signal
printf("Background PID %d is done: terminated by signal %d\n", bgPsArray[i], WTERMSIG(status));
fflush(stdout);
}
}
}
}
int runCommand(int cmd) {
if(cmd == 0) { // Return if there was no command
return 0;
} else if(strcmp(line, "exit") == 0) {
exit(0);
} else if(strstr(line, "#")) { // Comment input (do nothing)
} else if(strcmp(line, "status") == 0) {
printf("exit value %d\n", statusCode);
fflush(stdout);
}
else if(strncmp("cd", line, strlen("cd")) == 0) {
if(line[2] == ' ') { // If space after 'cd' expect directory
char cwd[1024];
getcwd(cwd, sizeof(cwd));
char *path = strstr(line, " ");
if(path) {
path += 1;
char *value;
value = malloc(strlen(path));
memcpy(value, path, strlen(path));
*(value + strlen(path)) = 0;
sprintf(cwd, "%s/%s", cwd, value); // Directory to change to
free(value);
}
chdir(cwd); // cd to new directory
} else { // cd with no argument
char *home = getenv("HOME");
chdir(home); // cd to HOME directory
}
}
else { // System commands
pid_t pid, ppid;
int status;
char *command;
char *args[256];
int argCount;
command = strtok(line, " ");
// Create args array for execvp
args[0] = command;
argCount = 1;
args[argCount] = strtok(NULL, " ");
while(args[argCount] != NULL) { // Add arguments to array
argCount++;
args[argCount] = strtok(NULL, " ");
}
if((pid = fork()) < 0) { // Fork fails
perror("fork");
fflush(stdout);
exit(1);
}
if(pid == 0) { // Child process
for(i=0; i<argCount; i++) {
if(strcmp(args[i], "<") == 0) { // Redirecting input
if(access(args[i+1], R_OK) == -1) { // File is unreadable
perror("access");
fflush(stdout);
} else { // File is readable
int file = open(args[i+1], O_RDONLY, 0);
dup2(file, STDIN_FILENO);
close(file);
execvp(command, &command);
}
}
else if(strcmp(args[i], ">") == 0) { // Redirecting output
int file = creat(args[i+1], 7777);
dup2(file, STDOUT_FILENO);
close(file);
execvp(command, args);
} else { // No redirection
execvp(command, args);
}
}
perror("execvp"); // Error for execvp
exit(1);
} else { // Parent process
if (bg == 1) { // Background process
int status;
int process;
printf("Background PID: %d\n", pid);
fflush(stdout);
bgPsArray[bgPsCount] = pid; // Add process to background process array
bgPsCount++;
process = waitpid(pid, &status, WNOHANG);
} else { // Foreground process
int status;
waitpid(pid, &status, 0); // Wait on the process
if(WIFEXITED(status)) {
statusCode = WEXITSTATUS(status);
}
}
}
}
return 1;
}
int main(int argc, char *argv[], char *envp[]) {
// Creating 'junk' manually is necessary because output redirection is broken,
// and a large portion of the grading script is depedent upon it's existence.
FILE *fp = fopen("junk", "ab+");
const char *text;
fprintf(fp, "Junk in junkfile\n");
fclose(fp);
signal(SIGINT, trapInterrupt);
signal(SIGTSTP, trapTstp);
while(1) {
checkProcessCompletion(); //Check the processes
int cmd = getCommand(); // Get command from user
int result = runCommand(cmd);
if (result == 0) {
break;
}
}
return 0;
}
First you must not trap SIGTSTP for the shell itself (it should ignore it), only for its child. Second if you really want to write a job controller shell, you need to manage children with the help of process group and correctly set the foreground group. Writing a shell that behave correctly with job control is a heavy task. Read POSIX standard about shells, groups, sessions, terminal control.
About your current problem. If your sub process does an exec then each handled signal is reset to its default behavior. This is because exec recovers the old code with the new one, so the previously set handler is no more available. Now you must let the child behave normally against TSTP and just let the parent track its status either with a synchronous call to wait/waitpid or with the asynchronous help of SIGCHLD. When the child stops or terminates, the parent is able to see it in the returned status (WIFEXITED, WIFSIGNALED, WIFSTOPPED).
Related
I am doing a simple shell program in Linux. I am trying to implement the pipe operator for shell. I cant seem to find the problem and solution. The execvp didnt read from the previous pipe pipi. So if i do ls -l | more, the more prints bad usage instead of the list of files
Here is my code below.
execCommands handles the commands.
command.h parse the command.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <wait.h>
#include "modules/token.h"
#include "modules/command.h"
#define BUFSIZE 10000
int tokenSize = 0;
int cmdSize = 0;
char promptSymbol[BUFSIZE] = "%";
char cwd[BUFSIZE];
// execute commands accordingly to the apropriate function
void execCommands(Command command[]){
int fd = 0;
char dir[BUFSIZE];
pid_t pid, p2;
int pipi[2];
for(int i = 0;i < cmdSize;i++){
//start executing commands
if(strcmp(command[i].argv[0], "prompt") == 0){
strcpy(promptSymbol, command[i].argv[1]); //change prompt symbol
}else if(strcmp(command[i].argv[0], "pwd") == 0){
if(getcwd(cwd, sizeof(cwd)) != NULL){
printf("%s\n", cwd );
}else{
perror("getcwd() error\n");
}
}else if(strcmp(command[i].argv[0], "cd") == 0){
if(command[i].argv[1] == NULL){
strcpy(dir, "/home");
}else if(strcmp(command[i].argv[1], "~") == 0 || strcmp(command[i].argv[1], "~/") == 0){
strcpy(dir, "/home");
}else{
strcpy(dir, command[i].argv[1]);
}
if(chdir(dir) < 0){
printf("No such directory/file %s found\n", dir);
}
}else{
pid = fork();
if(pid < 0){
perror("pid fork failed\n");
}
if(pid == 0){
if(command[i].stdin_file || command[i].stdout_file){
//check for stdin redirection
if(command[i].stdin_file){
fd = open(command[i].stdin_file, O_RDONLY);
dup2(fd, STDIN_FILENO);
}
//check for stdout redirection
if(command[i].stdout_file){
fd = open(command[i].stdout_file, O_WRONLY | O_CREAT, 0777);
dup2(fd, STDOUT_FILENO);
}
}
if(strcmp(command[i].sep,"|") == 0){
if(pipe(pipi) == -1){
perror("pipi pipe failed\n");
}
close(pipi[0]);
dup2(pipi[1], STDOUT_FILENO);
close(pipi[1]);
execvp(command[i].argv[0], command[i].argv);
p2 = fork();
if(p2 > 0){
close(pipi[1]);
dup2(pipi[0], STDIN_FILENO);
close(pipi[0]);
execvp(command[i].argv[0], command[i].argv);
}
}else{
execvp(command[i].argv[0], command[i].argv);
}
close(fd);
exit(0);
}else{
if(strcmp(command[i].sep, "&") != 0){
wait(0);
}
} // end of first fork()
} //end of command check
} //end of for loop
} //end of execCommands
//handles SIGINT, SIGTSTP and SIGQUIT
void handler(int num){
printf("\nEnter 'exit' to end shell program\n");
}
main code
int main(){
char cmdLine[BUFSIZE];
char *token[BUFSIZE];
Command command[BUFSIZE];
//Runs untill user wants to exit
while(strcmp(cmdLine, "exit") != 0){
signal(SIGTSTP, handler); //Ignores SIGTSTP signal(ctrl+z)
signal(SIGINT, handler); //Ignores SIGINT signal(ctrl+c)
signal(SIGQUIT, handler); //Ignores SIGQUIT signal(ctrl+\)
//get current directory
if(getcwd(cwd, sizeof(cwd)) != NULL){
strcat(cwd, promptSymbol);
}else{
perror("getcwd error");
return 1;
}
//prompts user for icommand
printf("%s ", cwd);
scanf(" %[^\n]", cmdLine);
// split command line into tokens
tokenSize = tokenise(cmdLine, token);
//split the tokens into commands
initCommand(command);
cmdSize = seperateCommands(token, command);
//execute commands accordingly
execCommands(command);
}
return 0;
}
I've written a basic shell in C, and I'm trying to catch a SIGTSTP signal from a child process. To do this, I've set up a handler for SIGCHLD, but the signal number being returned is 20, when it should be 24.
I have my SIGCHLD handler:
signal(SIGCHLD, trapChld);
void trapChld(int signo) {
printf("%d", signo);
}
This prints signal 20 when kill -SIGTSTP child_pid is run. Why might this be happening?
Here's my full code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int statusCode;
int foregroundMode = 0;
int bg = 0;
int bgPsArray[20];
int bgPsCount = 0;
int i;
char line[256];
pid_t popBgProcess() {
int size = sizeof(bgPsArray)/sizeof(bgPsArray[0]);
if (size > 0) {
return bgPsArray[size+1];
} else {
return 0;
}
}
void trapInterrupt(int _) {
int childStatus;
pid_t child;
while ((child = popBgProcess())) {
if(child != getpid()) {
kill(child, SIGKILL);
waitpid(child, &childStatus, 0);
}
}
}
void trapChld(int signo) {
printf("%d", signo);
if(signo == 24) {
if(foregroundMode == 0) {
write(1, "Entering foreground-only mode (& is now ignored)\n", 49);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 1;
} else {
write(1, "Exiting foreground-only mode\n", 29);
write(1, ": ", 2);
fflush(stdout);
foregroundMode = 0;
}
}
}
int getCommand() {
printf(": ");
fflush(stdout);
if(fgets(line, sizeof(line), stdin) != NULL) {
char *position = strchr(line, '\n');
*position = '\0'; // Replace '\n' with '\0'
if(foregroundMode == 1) { // Foreground mode on
if((position = strchr(line, '&')) != NULL) {
*position = '\0'; // Replace '&' with '\0'
}
bg = 0; // Ignore '&' so do not create background process
} else { // Foreground mode off
if((position = strchr(line, '&')) != NULL) {
*position = '\0'; // Replace '&' with '\0'
bg = 1; // Is a background process
} else {
bg = 0;
}
}
} else { // If input is null
return 0;
}
return 1;
}
void checkProcessCompletion() {
int status;
for(i=0; i<bgPsCount; i++) {
if(waitpid(bgPsArray[i], &status, WNOHANG) > 0) {
if(WIFEXITED(status)) { // If exit
printf("Background PID %d is done: exit value %d\n", bgPsArray[i], WEXITSTATUS(status));
fflush(stdout);
} else if(WIFSIGNALED(status)) { // If signal
printf("Background PID %d is done: terminated by signal %d\n", bgPsArray[i], WTERMSIG(status));
fflush(stdout);
}
}
}
}
int runCommand(int cmd) {
if(cmd == 0) { // Return if there was no command
return 0;
} else if(strcmp(line, "exit") == 0) {
exit(0);
} else if(strstr(line, "#")) { // Comment input (do nothing)
} else if(strcmp(line, "status") == 0) {
printf("exit value %d\n", statusCode);
fflush(stdout);
}
else if(strncmp("cd", line, strlen("cd")) == 0) {
if(line[2] == ' ') { // If space after 'cd' expect directory
char cwd[1024];
getcwd(cwd, sizeof(cwd));
char *path = strstr(line, " ");
if(path) {
path += 1;
char *value;
value = malloc(strlen(path));
memcpy(value, path, strlen(path));
*(value + strlen(path)) = 0;
sprintf(cwd, "%s/%s", cwd, value); // Directory to change to
free(value);
}
chdir(cwd); // cd to new directory
} else { // cd with no argument
char *home = getenv("HOME");
chdir(home); // cd to HOME directory
}
}
else { // System commands
pid_t pid, ppid;
int status;
char *command;
char *args[256];
int argCount;
command = strtok(line, " ");
// Create args array for execvp
args[0] = command;
argCount = 1;
args[argCount] = strtok(NULL, " ");
while(args[argCount] != NULL) { // Add arguments to array
argCount++;
args[argCount] = strtok(NULL, " ");
}
if((pid = fork()) < 0) { // Fork fails
perror("fork");
fflush(stdout);
exit(1);
}
if(pid == 0) { // Child process
for(i=0; i<argCount; i++) {
if(strcmp(args[i], "<") == 0) { // Redirecting input
if(access(args[i+1], R_OK) == -1) { // File is unreadable
perror("access");
fflush(stdout);
} else { // File is readable
int file = open(args[i+1], O_RDONLY, 0);
dup2(file, STDIN_FILENO);
close(file);
execvp(command, &command);
}
}
else if(strcmp(args[i], ">") == 0) { // Redirecting output
int file = creat(args[i+1], 7777);
dup2(file, STDOUT_FILENO);
close(file);
execvp(command, args);
} else { // No redirection
execvp(command, args);
}
}
perror("execvp"); // Error for execvp
exit(1);
} else { // Parent process
if (bg == 1) { // Background process
int status;
int process;
printf("Background PID: %d\n", pid);
fflush(stdout);
bgPsArray[bgPsCount] = pid; // Add process to background process array
bgPsCount++;
process = waitpid(pid, &status, WNOHANG);
} else { // Foreground process
int status;
waitpid(pid, &status, 0); // Wait on the process
if(WIFEXITED(status)) {
statusCode = WEXITSTATUS(status);
}
}
}
}
return 1;
}
int main(int argc, char *argv[], char *envp[]) {
// Creating 'junk' manually is necessary because output redirection is broken,
// and a large portion of the grading script is depedent upon it's existence.
FILE *fp = fopen("junk", "ab+");
const char *text;
fprintf(fp, "Junk in junkfile\n");
fclose(fp);
signal(SIGINT, trapInterrupt);
signal(SIGCHLD, trapChld);
while(1) {
checkProcessCompletion(); //Check the processes
int cmd = getCommand(); // Get command from user
int result = runCommand(cmd);
if (result == 0) {
break;
}
}
return 0;
}
You haven't told us what platform you're running on, so this is just a guess, but perhaps it's because that platform defines SIGTSTP as 20?
Linux does, for example:
$ grep SIGTSTP /usr/include/asm/signal.h
#define SIGTSTP 20
A better question is why do you think it should be 24? On AIX it's 18. On HP-UX it's 25. Various Cygwin headers define it as 8, 18, or 24 (because the Cygwin headers come from glib and are full of platform-specific conditional-compilation shenanigans); 18 is the actual value used at runtime.
On Solaris, now, it happens to be 24. I believe Solaris 2 inherited that from SVR4, and subsequent Solaris releases kept it. But the signal numbers are not standardized by any of the applicable specifications (SUS and its ancestors, such as POSIX and XPG3).
Don't assume the signal values are fixed. That's why you have signal.h.
Oh, and sigaction(2) is preferable to signal(2) on platforms that support it, which is most of them.
I have a problem with pipes. My program is a Shell program in C. I want to execute for example ls | wc, but what I get after running is:
ls: cannot access |: no such file or directory ls: cannot access wc: no such file or directory.
What am I doing wrong?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#define MAX_CMD_LENGTH 100
#define MAX_NUM_PARAMS 10
int parsecmd(char* cmd, char** params) { //split cmd into array of params
int i,n=-1;
for(i=0; i<MAX_NUM_PARAMS; i++) {
params[i] = strsep(&cmd, " ");
n++;
if(params[i] == NULL) break;
}
return(n);
};
int executecmd(char** params) {
pid_t pid = fork(); //fork process
if (pid == -1) { //error
char *error = strerror(errno);
printf("error fork!!\n");
return 1;
} else if (pid == 0) { // child process
execvp(params[0], params); //exec cmd
char *error = strerror(errno);
printf("unknown command\n");
return 0;
} else { // parent process
int childstatus;
waitpid(pid, &childstatus, 0);
return 1;
}
};
int execpipe (char ** argv1, char ** argv2) {
int fds[2];
pipe(fds);
int i;
pid_t pid = fork();
for (i=0; i<2; i++) {
if (pid == -1) { //error
char *error = strerror(errno);
printf("error fork!!\n");
return 1;
} else
if (pid == 0) {
if(i ==0){
close(fds[1]);
dup2(fds[0], 0);
close(fds[0]);
execvp(argv1[0], argv1);
char *error = strerror(errno);
printf("unknown command\n");
return 0;
} else if(i == 1) {
close(fds[0]);
dup2(fds[1], 1);
close(fds[1]);
execvp(argv2[0], argv2);
char *error = strerror(errno);
printf("unknown command\n");
return 0;
}
} else { // parent process
int childstatus;
waitpid(pid, &childstatus, 0);
return 1;
}
} // end for
};
int main() {
char cmd[MAX_CMD_LENGTH+1];
char * params[MAX_NUM_PARAMS+1];
char * argv1[MAX_NUM_PARAMS+1];
char * argv2[MAX_NUM_PARAMS+1];
int k, y, x;
int f = 1;
while(1) {
printf("$"); //prompt
if(fgets(cmd, sizeof(cmd), stdin) == NULL) break; //read command, ctrl+D exit
if(cmd[strlen(cmd)-1] == '\n') { //remove newline char
cmd[strlen(cmd)-1] = '\0';
}
int j=parsecmd(cmd, params); //split cmd into array of params
if (strcmp(params[0], "exit") == 0) break; //exit
for (k=0; k <j; k++) { //elegxos gia uparksi pipes
if (strcmp(params[k], "|") == 0) {
f = 0; y = k;
printf("pipe found\n");
}
}
if (f==0) {
for (x=0; x<k; x++) {
argv1[x]=params[x];
}
int z = 0;
for (x=k+1; x< j; x++) {
argv2[z]=params[x];
z++;
}
if (execpipe(argv1, argv2) == 0) break;
} else if (f==1) {
if (executecmd(params) == 0) break;
}
} // end while
return 0;
}
Updated your code with following corrections.
Removed for() loop that iterated two times after fork() call.
Removed incorrect close of pipe FDs after dup2 calls for both parent and child processes.
Aligned the command that needed to be run as per the file descriptors that were duplicated in dup2() calls for parent and child. Basically I needed to swap execvp(argv2[0], argv2) and execvp(argv1[0], argv1) calls.
Added a break; statement in the for loop that searched for pipe character.
The updated code is as below.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_CMD_LENGTH 100
#define MAX_NUM_PARAMS 10
int parsecmd(char* cmd, char** params) { //split cmd into array of params
int i,n=-1;
for(i=0; i<MAX_NUM_PARAMS; i++) {
params[i] = strsep(&cmd, " ");
n++;
if(params[i] == NULL) break;
}
return(n);
};
int executecmd(char** params) {
pid_t pid = fork(); //fork process
if (pid == -1) { //error
char *error = strerror(errno);
printf("error fork!!\n");
return 1;
} else if (pid == 0) { // child process
execvp(params[0], params); //exec cmd
char *error = strerror(errno);
printf("unknown command\n");
return 0;
} else { // parent process
int childstatus;
waitpid(pid, &childstatus, 0);
return 1;
}
};
int execpipe (char ** argv1, char ** argv2) {
int fds[2];
pipe(fds);
int i;
pid_t pid = fork();
if (pid == -1) { //error
char *error = strerror(errno);
printf("error fork!!\n");
return 1;
}
if (pid == 0) { // child process
close(fds[1]);
dup2(fds[0], 0);
//close(fds[0]);
execvp(argv2[0], argv2); // run command AFTER pipe character in userinput
char *error = strerror(errno);
printf("unknown command\n");
return 0;
} else { // parent process
close(fds[0]);
dup2(fds[1], 1);
//close(fds[1]);
execvp(argv1[0], argv1); // run command BEFORE pipe character in userinput
char *error = strerror(errno);
printf("unknown command\n");
return 0;
}
};
int main() {
char cmd[MAX_CMD_LENGTH+1];
char * params[MAX_NUM_PARAMS+1];
char * argv1[MAX_NUM_PARAMS+1] = {0};
char * argv2[MAX_NUM_PARAMS+1] = {0};
int k, y, x;
int f = 1;
while(1) {
printf("$"); //prompt
if(fgets(cmd, sizeof(cmd), stdin) == NULL) break; //read command, ctrl+D exit
if(cmd[strlen(cmd)-1] == '\n') { //remove newline char
cmd[strlen(cmd)-1] = '\0';
}
int j=parsecmd(cmd, params); //split cmd into array of params
if (strcmp(params[0], "exit") == 0) break; //exit
for (k=0; k <j; k++) { //elegxos gia uparksi pipes
if (strcmp(params[k], "|") == 0) {
f = 0; y = k;
printf("pipe found\n");
break;
}
}
if (f==0) {
for (x=0; x<k; x++) {
argv1[x]=params[x];
}
int z = 0;
for (x=k+1; x< j; x++) {
argv2[z]=params[x];
z++;
}
if (execpipe(argv1, argv2) == 0) break;
} else if (f==1) {
if (executecmd(params) == 0) break;
}
} // end while
return 0;
}
If you are interested only in changes I made, here is the diff between your code and the above updated code:
--- original.c
+++ updated.c
## -4,6 +4,7 ##
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
+#include <sys/wait.h>
#define MAX_CMD_LENGTH 100
## -43,44 +44,36 ##
pipe(fds);
int i;
pid_t pid = fork();
- for (i=0; i<2; i++) {
if (pid == -1) { //error
char *error = strerror(errno);
printf("error fork!!\n");
return 1;
- } else
- if (pid == 0) {
- if(i ==0){
+ }
+ if (pid == 0) { // child process
close(fds[1]);
dup2(fds[0], 0);
- close(fds[0]);
- execvp(argv1[0], argv1);
+ //close(fds[0]);
+ execvp(argv2[0], argv2); // run command AFTER pipe character in userinput
char *error = strerror(errno);
printf("unknown command\n");
return 0;
- } else if(i == 1) {
+ } else { // parent process
close(fds[0]);
dup2(fds[1], 1);
- close(fds[1]);
- execvp(argv2[0], argv2);
+ //close(fds[1]);
+ execvp(argv1[0], argv1); // run command BEFORE pipe character in userinput
char *error = strerror(errno);
printf("unknown command\n");
return 0;
}
- } else { // parent process
- int childstatus;
- waitpid(pid, &childstatus, 0);
- return 1;
- }
- } // end for
};
int main() {
char cmd[MAX_CMD_LENGTH+1];
char * params[MAX_NUM_PARAMS+1];
- char * argv1[MAX_NUM_PARAMS+1];
- char * argv2[MAX_NUM_PARAMS+1];
+ char * argv1[MAX_NUM_PARAMS+1] = {0};
+ char * argv2[MAX_NUM_PARAMS+1] = {0};
int k, y, x;
int f = 1;
while(1) {
## -95,6 +88,7 ##
if (strcmp(params[k], "|") == 0) {
f = 0; y = k;
printf("pipe found\n");
+ break;
}
}
if (f==0) {
execv* procedure doesn't interpret shell script string. It merely starts an executable file and passes an array of arguments to it. Thus, it cannot organize a pipeline.
If you need "normal" shell command execution, you may want to use system(char*) procedure instead of execvp.
Otherwise, if you need to do the pipes yourself, you may want to parse the string with '|' special characters and use pipe(), fork() and I/O redirection. Like here How to run a command using pipe?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
void tokenizer(char* input, char** output) { //My tokenizer
char* input_dup = strdup(input);
output[0] = strtok(input_dup, " ");
int i = 1;
while ((output[i] = strtok(NULL, " ")) != NULL) {
i++;
}
}
void run_command(char** args, int* fd) { //no pipe
pid_t pid = fork();
if (pid < 0) {
printf("Forking failed...\n");
}
else if (pid == 0) {
close(fd[0]);
if (fd[1] != 1)
dup2(fd[1], 1);
execvp(args[0], args);
printf("Command failed...\n");
exit(1);
}
else {
close(fd[1]);
wait(pid);
char buff[1];
while (read(fd[0], buff, 1) > 0) {
if (buff[0] == EOF || buff[0] == '\0') {
printf("Caught something, returning out...");
return;
}
else {
printf("%c", buff[0]);
}
}
}
}
//pipeline function
void run_pipe(char** args, int* fd) {
pid_t pid = fork();
if (pid < 0) {
printf("Forking failed...\n");
}
else if (pid == 0) {
if (fd[1] != 1) {
dup2(fd[1], 1);
}
execvp(args[0], args);
printf("Command failed...\n");
exit(1);
}
else {
close(fd[1]);
if (fd[0] != 0) {
dup2(fd[0], 0);
}
wait(pid);
}
}
int main(int argc, char** argv) {
printf ("Starting myshell (mysh) \n..\n..\n");
while (1) {
char cwd[1024];
printf ("mysh :: %s -> ", getcwd(cwd, sizeof(cwd)));
char ch[1024];
memset(ch, 0, 1023); //for cleanup
char c = 0;
int i = 0;
while (c != '\n') {
c = getchar();
if (c == EOF) {
printf ("EOF Received, exiting...\n");
return 0;
}
if (c != '\n')
ch[i] = c;
i++;
}
if (ch[0] != '\0') {
char* tokens[128];
tokenizer(ch, tokens);
//first check for keywords
if (strcmp(tokens[0], "cd") == 0) {
if (chdir(tokens[1]) < 0) {
printf("ERROR: Directory %s does not exist\n", tokens[1]);
}
}
else if (strcmp(tokens[0], "exit") == 0) {
printf("Leaving shell...\n");
return 0;
}
else {
char* commands[50];
memset(commands, 0, sizeof(commands));
int j = 0;
int k = 0;
int fd[2];
//try something different...
while (tokens[j] != NULL) {
if (strcmp(tokens[j], "|") == 0) {
commands[k] = NULL;
pipe(fd);
run_pipe(commands, fd);
j++;
k = 0;
}
//more cases here
else { //nothing special
commands[k] = tokens[j];
j++;
k++;
}
}
commands[k] = NULL;
pipe(fd);
run_command(commands, fd);
}
}
}
}
The above code is meant to simulate a shell. It handles single commands and it handles the pipelining properly (i.e. ps | sort | wc is returning the correct output) however when the pipelining is done it returns an EOF which is caught by the condition in the loop with getchar(). If I try to ignore this EOF it segfaults. Am I leaving a pipe open somewhere and stdin is getting flooded? Any help is appreciated.
Compilation fixes
You need to add #include <sys/wait.h> and then fix the calls to wait(). I used (twice):
int status;
int corpse = wait(&status);
printf("PID %d status 0x%.4X\n", corpse, status);
Arguably, that should be a loop looking for a specific PID, or you should use waitpid() instead. While debugging a shell, you want to know about every PID that exits and its status.
I ran `ps | wc' and got:
Starting myshell (mysh)
..
..
mysh :: /usr/local/google/home/jleffler/soq -> ps | wc
PID 25960 status 0x0000
PID 25961 status 0x0000
4 16 117
mysh :: /usr/local/google/home/jleffler/soq -> EOF Received, exiting...
If you mean "the code should have continued instead of getting EOF", then there's some more work to do.
Tangential issues
I note the line:
if (buff[0] == EOF || buff[0] == '\0')
The character in buff[0] is from a read() call. It will never be EOF meaningfully; EOF is distinct from every character (hence getchar() returns an int). This becomes significant later:
char c = 0;
while (c != '\n')
{
c = getchar();
if (c == EOF)
Since c is a char, you cannot reliably compare it with EOF. You must store the result of getchar() in an int.
I'm not yet convinced these are the cause of the trouble, but you must be careful.
Probable cause
I think the trouble is in run_pipe() in the parent code (as amended):
else
{
close(fd[1]);
if (fd[0] != 0)
{
dup2(fd[0], 0);
}
int status;
int corpse = wait(&status);
printf("PID %d status 0x%.4X\n", corpse, status);
}
The fd[0] != 0 condition will always be true (very improbable that it will be false), so you then change your shell's input to read from fd[0]. You should review that; it means that you're reading standard input from the read end of the pipe to the child. That's bad; you've lost your original input!
Your code also seems to have the parent waiting for the child to die, and then reads the pipe and echoes to standard output. This is not a good idea; it is better to make the child (last child in the pipeline) write to the standard output directly. There are two reasons for this:
The child might write more data than fits in a pipe, so it will block waiting for something to read its output, but the reader will be blocked waiting for the child to die, so you'll have a deadlock.
It slows things up, and output from the child may well be buffered instead of appearing timely on the terminal.
I'm a little sceptical about how a three-part pipeline would be handled. You need two pipes created before you run the middle process of the three; I don't see that in your code.
I was able to fix this issue. It's probably not the right way to do it, but I saved a copy of stdin and used dup2 to reset it when the pipelining was finished.
int in_bak = dup(0);
//stuff
dup2(in_bak, 0);
close(in_bak);
This question already has answers here:
How to bring a child process running in the background to the foreground
(3 answers)
Closed 9 years ago.
I write similarity of the command bash interpreter.
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <wait.h>
#include "shell.h"
#include <string.h>
char *infile;
char *outfile;
char *appfile;
struct command cmds[MAXCMDS];
char bkgrnd;
int k = 0;
static void my_wait(pid_t pid, pid_t bkgrnds[]);
int main(int argc,char *argv[])
{
int i;
char line[1024]; /* allow large command lines */
int ncmds;
char prompt[50]; /* shell prompt */
pid_t bkgrnds[1024]; /* for fg */
/* PLACE SIGNAL CODE HERE */
signal(SIGINT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
sprintf(prompt,"[%s] ",argv[0]);
while (promptline(prompt, line, sizeof(line)) > 0)
{ /* l eof */
if (0 >= (ncmds = parseline(line)))
{
continue; /* read next line */
}
#ifdef DEBUG
{
int i, j;
for (i = 0; i < ncmds; i++)
{
for (j = 0; NULL != cmds[i].cmdargs[j]; j++)
{
fprintf(stderr, "cmd[%d].cmdargs[%d] = %s\n", i, j, cmds[i].cmdargs[j]);
}
fprintf(stderr, "cmds[%d].cmdflag = %o\n", i, [i].cmdflag);
}
}
#endif
{
int previous_pipe_output = -1;
int pipeline_start = 0;
pid_t pids[MAXCMDS];
fprintf(stderr, "%d %s\n", ncmds, cmds[0].cmdargs[0]);
if (1 == ncmds && 0 == strcmp(cmds[0].cmdargs[0], "fg"))
{
if (0 == k)
{
fprintf(stderr, "k == 0\n");
continue;
}
int num = -1;
signal(SIGTTOU, SIG_IGN);
if (0 == cmds[0].cmdargs[1])
{
num = k - 1;
}
else
{
num = atoi(cmds[0].cmdargs[1]);
}
fprintf(stderr, "%d\n", bkgrnds[num]);
if(-1 == tcsetpgrp(STDIN_FILENO, bkgrnds[num]))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
my_wait(bkgrnds[num], bkgrnds);
signal(SIGTTOU, SIG_IGN);
if (-1 == tcsetpgrp(STDIN_FILENO, getpgrp()))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
continue;
}
for (i = 0; i < ncmds; i++)
{
int pipefd[2] = {-1, -1};
if(cmds[i].cmdflag & OUTPIP)
{
if(-1 == pipe(pipefd))
{
perror("Couldn't create pipe");
return EXIT_FAILURE;
}
}
{
/* FORK AND EXECUTE */
pids[i] = fork();
if (0 == pids[i])
{
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
if(-1 == setpgid(0, (cmds[i].cmdflag & INPIP)? pids[pipeline_start]:0))
{
perror("Couldn't set process group ID");
return EXIT_FAILURE;
}
if (!bkgrnd && !(cmds[i].cmdflag & INPIP))
{
signal(SIGTTOU, SIG_IGN);
if(-1 == tcsetpgrp(STDIN_FILENO, getpgrp()))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
}
//....
execvp(cmds[i].cmdargs[0], cmds[i].cmdargs);
perror("Couldn't execute command");
return EXIT_FAILURE;
}
else if (-1 != pids[i])
{
if (!bkgrnd)
{
if (!(cmds[i].cmdflag & OUTPIP))
{
{
int j = 0;
for (j = pipeline_start; j <= i; j++)
{
my_wait(pids[j], bkgrnds);
}
}
signal(SIGTTOU, SIG_IGN);
if (-1 == tcsetpgrp(STDIN_FILENO, getpgrp()))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
}
}
else
{
printf("Background process ID: %ld\n", (long)pids[i]);
}
}
else
{
perror("Couldn't create process");
}
}
}
}
} /* close while */
return EXIT_SUCCESS;
}
static void my_wait(pid_t pid, pid_t bkgrnds[])
{
int status = 0;
if (-1 != waitpid(pid, &status, WUNTRACED))
{
if (WIFSTOPPED(status) && (SIGTSTP == WSTOPSIG(status)))
{
bkgrnds[k++] = pid;
//printf("%d %d\n", pid, k);
kill(pid, SIGCONT);
bkgrnd = 1;
printf("Background process ID: %ld\n", (long)pid);
}
}
else
{
perror("Couldn't wait for child process termination");
}
}
Now I want to implement a command fg.
if (1 == ncmds && 0 == strcmp(cmds[0].cmdargs[0], "fg"))
{
if (0 == k)
{
fprintf(stderr, "k == 0\n");
continue;
}
int num = -1;
signal(SIGTTOU, SIG_IGN);
if (0 == cmds[0].cmdargs[1])
{
num = k - 1;
}
else
{
num = atoi(cmds[0].cmdargs[1]);
}
fprintf(stderr, "%d\n", bkgrnds[num]);
if(-1 == tcsetpgrp(STDIN_FILENO, bkgrnds[num]))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
my_wait(bkgrnds[num], bkgrnds);
signal(SIGTTOU, SIG_IGN);
if (-1 == tcsetpgrp(STDIN_FILENO, getpgrp()))
{
perror("Couldn't set terminal foreground process group");
return EXIT_FAILURE;
}
signal(SIGTTOU, SIG_DFL);
continue;
}
Question : PID I know how do I bring to the foreground?
That is if I import
cat
CTRL + Z
fg
<- here shall continue cat
Thanks in advance.
Sorry. I just forgot to send SIGCONT process and everything. Added and working. kill(bkgrnds[num], SIGCONT);