I am trying to make a simple shell. When I execute a command in the child and wait for it, the code works fine. However, when I want to execute it in the background, the program just hangs there, and the prompt symbol did not show up. What else code should I add to make my child process do its work, while the parent continues on receiving another user command?
int
lsh_launch(char **args)
{
pid = fork();
if (pid == 0) {
// child process
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(0);
}
else if (pid < 0) {
perror("lsh");
}
else {
// parent process
if (!background) {
wait(NULL); // works fine
}
else {
printf("[PID]: %d\n", pid);
// after printing the pid, the prompt symbol does not show again
}
}
return 1;
}
My caller function: After lsh_execute, the code will go back to main, and go back to the top of the while loop, and print another command prompt.
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered, return
return 1;
}
//if the command is in my predefined function list, execute
for (i = 0; i < lsh_num_builtins(); i ) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
//if not, go to launch and fork a child and execute
return lsh_launch(args);
}
Code Link: myshell The problem is when I type "ls &", the program do output a list of file names in the current directory, but it hangs there.
CodePudding user response:
Okay, I've got it working ...
There were a few issues:
- After the
printf
for the prompt, we have tofflush(stdout)
to force out the text because there is no newline. - There is no reaping of background children, so they become zombies.
- This would interfere with
wait(NULL)
for the next foreground command. (e.g.)sleep 5 & ; pwd
would mess up the wait for the second command. - To fix this, the program should periodically call
waitpid(-1,NULL,WNOHANG)
to reap the zombie background children. - Also, with background jobs, a previous background job might complete before the next foreground job completes. So, we want to change the foreground from
wait
towaitpid
with the PID of the foreground job. - Doing
signal(SIGCHLD,SIG_IGN)
preventswait
et. al. from seeing/reaping a background pid. - This would interfere with subsequent
wait(NULL)
for foreground commands. - The code for the
replay
command that attaches to the linked list was wrong. The compiler flagged this when compiled with warnings (e.g.-Wall
). - A number of compiler warnings about "unused" or "set but not used" variables.
- We should
fflush
bothstdout
andstderr
prior to afork
.
In addition to fixing the above, I add some improvements:
- Added "job control" for background jobs to implement reaping of background pids.
- Added
jobs
andwait
commands. - Added debug printing with
dbgprt
.
In the code below, I've used cpp
conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Note: this can be cleaned up by running the file through unifdef -k
Here is the refactored code. It is annotated with the bugs and fixes:
Edit: Updated to use waitpid
instead of wait
on foreground jobs.
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <dirent.h>
#define BUFSIZE 1024
#define TOK_BUFSIZE 64
#define TOK_DELIM " \t\r\n\a"
char *currentdir;
int tokencount;
typedef struct node {
char str[64];
struct node *next;
} node;
node *top = NULL;
node *cur = NULL;
#if 1
#ifdef DEBUG
#define dbgprt(_fmt...) \
do { \
int sverr = errno; \
printf(_fmt); \
errno = sverr; \
} while (0)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
#endif
// NOTE/FIX: implement job control
#if 1
struct jobctl {
int job_jobno; // job number
pid_t job_pid; // child pid
int job_status; // child status
struct jobctl *job_next; // list link
};
struct jobctl *joblist; // list of background jobs
int jobcount; // highest job number
// jobreap -- reap all detached/background jobs (children)
void
jobreap(void)
{
// wait for all background jobs
while (joblist != NULL) {
int status;
// reap any child (non-blocking)
pid_t pid = waitpid(-1,&status,WNOHANG);
// more children are pending [but not yet finished]
if (pid == 0)
break;
dbgprt("jobreap: WAITPID pid=%d status=%8.8X\n",pid,status);
if (pid < 0) {
printf("jobreap: error -- %s\n",strerror(errno));
break;
}
// find it in our list of detached/background jobs
struct jobctl *cur;
struct jobctl *prev = NULL;
for (cur = joblist; cur != NULL; cur = cur->job_next) {
if (cur->job_pid == pid)
break;
prev = cur;
}
// reaping child of one of our children (i.e. grandchild) that is _not_
// in our list
if (cur == NULL) {
printf("[grandchild]: pid %d, status %8.8X\n", pid, status);
continue;
}
printf("[background]: pid %d, %8.8X, job %d\n",
pid, status, cur->job_jobno);
// we don't do anything with this but ...
cur->job_status = status;
// remove our child from list
if (prev != NULL)
prev->job_next = cur->job_next;
else
joblist = cur->job_next;
// release storage
free(cur);
// reset job count if we've reaped _all_ of our direct children
if (joblist == NULL)
jobcount = 0;
}
}
int
lsh_jobs(char **args)
{
for (struct jobctl *cur = joblist; cur != NULL; cur = cur->job_next)
printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
return 1;
}
int
lsh_wait(char **args)
{
while (1) {
jobreap();
if (joblist == NULL)
break;
}
#if 0
for (struct jobctl *cur = joblist; cur != NULL; cur = cur->job_next)
printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
#endif
return 1;
}
#endif
char *
read_line(void)
{
int bufsize = BUFSIZ;
int position = 0;
char *buffer = (char *) malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
while (1) {
c = getchar();
if (c == EOF || c == '\n') {
buffer[position] = '\0';
return buffer;
}
else {
buffer[position] = c;
}
position ;
if (position >= bufsize) {
bufsize = BUFSIZ;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
char **
split_line(char *line)
{
int bufsize = BUFSIZ;
int position = 0;
char **tokens = (char **) malloc(sizeof(char *) * bufsize);
char *token;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
token = strtok(line, TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position ;
if (position >= bufsize) {
bufsize = TOK_BUFSIZE;
tokens = realloc(tokens, bufsize * sizeof(char *));
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, TOK_DELIM);
}
tokens[position] = NULL;
tokencount = position;
return tokens;
}
void
catHeadTail(char *filename, int headtail, char *line)
{
char *ptr;
long num;
num = strtol(line, &ptr, 10);
num *= -1;
char cwd[256];
if (getcwd(cwd, sizeof(cwd)) == NULL) {
perror("getcwd() error");
}
else {
strcat(cwd, "/");
strcat(cwd, filename);
}
char lines[1000][256];
int linecount = 0;
FILE *input = fopen(cwd, "r");
if (!input) {
perror("lsh: file open error");
}
while (fgets(lines[linecount], 256, input) != NULL) {
linecount ;
}
fclose(input);
if (headtail == 1) {
for (int i = 0; i < num; i ) {
printf("%s", lines[i]);
}
}
else {
for (int i = linecount - num; i <= linecount; i ) {
printf("%s", lines[i]);
}
}
}
int
lsh_launch(char **args)
{
pid_t pid;
// NOTE/BUG: unused
#if 0
pid_t wpid;
int state;
#endif
int background = 0;
if (!strcmp(args[tokencount - 1], "&")) {
background = 1;
args[tokencount - 1] = '\0';
}
dbgprt("lsh_launch: PARENT pid=%d pgrp=%d\n",getpid(),getpgrp());
#if 1
fflush(stdout);
fflush(stderr);
#endif
pid = fork();
if (pid == 0) {
// child process
dbgprt("lsh_launch: BEF pgid=%d\n",getpgrp());
setpgid(0, 0);
dbgprt("lsh_launch: AFT pgid=%d\n",getpgrp());
if (tokencount >= 4 && !strcmp(args[2], "|")) // pipleline
{
if (!strcmp(args[0], "cat") && !strcmp(args[2], "|")) {
if (!strcmp(args[3], "head"))
catHeadTail(args[1], 1, args[4]); // head
else
catHeadTail(args[1], 0, args[4]); // tail
}
}
else if (tokencount >= 3 && !strcmp(args[1], "<")) {
char *temp[3];
temp[0] = args[0];
temp[1] = args[2];
temp[2] = NULL;
if (execvp(temp[0], temp) == -1) {
perror("lsh");
}
}
else if (tokencount >= 4 && !strcmp(args[2], ">")) {
char lines[1000][256];
int linecount = 0;
FILE *input = fopen(args[1], "r");
FILE *output = fopen(args[3], "w");
if (!input || !output) {
perror("lsh: file open error");
}
while (fgets(lines[linecount], 256, input) != NULL) {
linecount ;
}
for (int i = 0; i < linecount; i ) {
fputs(lines[i], output);
}
}
else if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(0);
}
else if (pid < 0) {
perror("lsh");
}
else {
// parent process
if (! background) {
dbgprt("lsh_launch: WAIT/BEF pid=%d\n",pid);
int status;
// NOTE/BUG: with detached jobs, a detached job might complete _before_ this
// one -- so wait for pid specifically
#if 0
pid = wait(&status);
#else
pid = waitpid(pid,&status,0);
#endif
dbgprt("lsh_launch: WAIT/AFT pid=%d status=%8.8X\n",pid,status);
}
else {
// NOTE/BUG: doing this prevents wait from working correctly on the child
#if 0
signal(SIGCHLD, SIG_IGN);
#endif
// NOTE/FIX: implement "job control" for background job
#if 1
// get new job control
struct jobctl *job = calloc(1,sizeof(*job));
job->job_pid = pid;
job->job_jobno = jobcount;
printf("[PID]: %d job %d\n", pid, job->job_jobno);
// add it to list
job->job_next = joblist;
joblist = job;
#endif
}
}
return 1;
}
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_echo(char **args);
int lsh_record(char **args);
int lsh_replay(char **args);
int lsh_mypid(char **args);
/*
List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
"cd",
"help",
"exit",
"echo",
"record",
"replay",
"mypid",
#if 1
"jobs",
"wait",
#endif
};
/* all the buildin func*/
int (*builtin_func[]) (char **) = {
lsh_cd,
lsh_help, lsh_exit, lsh_echo, lsh_record, lsh_replay, lsh_mypid,
#if 1
lsh_jobs,
lsh_wait,
#endif
};
int
lsh_num_builtins()
{
return sizeof(builtin_str) / sizeof(char *);
}
int
lsh_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "lsh: expected argument to \"cd\"\n");
}
else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
int
lsh_help(char **args)
{
int i;
printf("Stephen Brennan's LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i ) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
int
lsh_exit(char **args)
{
return 0;
}
int
lsh_echo(char **args)
{
if (!strcmp(args[1], "-n")) {
for (int i = 2; i < tokencount; i ) {
printf("%s ", args[i]);
}
}
else {
for (int i = 1; i < tokencount; i ) {
printf("%s ", args[i]);
}
printf("\n");
}
return 1;
}
int
lsh_record(char **args)
{
int i = 1;
node *go = top;
while (go != NULL) {
printf("%d: %s\n", i, go->str);
go = go->next;
i ;
}
return 1;
}
int
lsh_replay(char **args)
{
node *go;
go = top;
for (int i = 0; i < atoi(args[1]) - 1; i ) {
go = go->next;
}
node *add = (node *) malloc(sizeof(node));
add->next = NULL;
strcpy(add->str, go->str);
cur->next = add;
cur = cur->next;
char **command = split_line(go->str);
pid_t wpid;
int state;
#if 1
fflush(stdout);
fflush(stderr);
#endif
pid_t pd = fork();
if (pd == 0) {
if (execvp(command[0], command) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
}
else if (pd < 0) {
perror("lsh");
}
else {
do {
wpid = waitpid(pd, &state, WUNTRACED);
dbgprt("lsh_replay: pd=%d wpid=%d state=%8.8X\n",
pd,wpid,state);
// NOTE/FIX: wpid is set but not used -- this is a dummy test
#if 1
if (wpid != pd)
continue;
#endif
} while (!WIFEXITED(state) && !WIFSIGNALED(state));
}
return 1;
}
int
lsh_mypid(char **args)
{
int pid = getpid();
if (!strcmp(args[1], "-i")) {
printf("%d", pid);
}
else if (!strcmp(args[1], "-p")) {
#if 0
int c;
#endif
FILE *file;
char path[30] = "/proc/";
char content[512];
char *token;
strcat(path, args[2]);
strcat(path, "/stat");
file = fopen(path, "r");
if (file) {
int i = 1;
fgets(content, 512, file);
token = strtok(content, TOK_DELIM);
while (i < 4) {
token = strtok(NULL, TOK_DELIM);
i ;
}
fclose(file);
}
else {
printf("not existed");
}
printf("%d", atoi(token));
}
else if (!strcmp(args[1], "-c")) {
#if 0
int c;
#endif
FILE *file;
char path[30] = "/proc/";
#if 0
char pids[10];
#endif
char content[512];
char *token;
strcat(path, args[2]);
strcat(path, "/task/");
strcat(path, args[2]);
strcat(path, "/children");
file = fopen(path, "r");
if (file) {
fgets(content, 512, file);
token = strtok(content, TOK_DELIM);
printf("%s", token);
while (token != NULL) {
token = strtok(NULL, TOK_DELIM);
printf("%s", token);
}
fclose(file);
}
else {
fprintf(stderr, "can not open the file\n");
}
return 1;
}
return 1;
}
int
lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for (i = 0; i < lsh_num_builtins(); i ) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i]) (args);
}
}
return lsh_launch(args);
}
void
shell_loop()
{
int counter = 0;
char *line;
char **args;
char *allline = malloc(sizeof(char) * 64);
int state = 1;
while (state == 1) {
#if 1
jobreap();
#endif
printf(">>>$ ");
// NOTE/FIX: must flush the output because the printf does _not_ end in newline
#if 1
fflush(stdout);
#endif
line = read_line();
#if 1
jobreap();
#endif
strcpy(allline, line);
args = split_line(line);
if (args[0] == NULL) { // space or \t
continue;
}
else if (strcmp(args[0], "replay")) // not replay
{
if (counter == 0) {
top = (node *) malloc(sizeof(node));
strcpy(top->str, allline);
top->next = NULL;
cur = top;
}
else {
node *m = (node *) malloc(sizeof(node));
strcpy(m->str, allline);
m->next = NULL;
cur->next = m;
cur = m;
}
if (counter >= 16) {
node *del;
del = top;
top = top->next;
free(del);
}
}
else if (!strcmp(args[0], "replay")) // replay
{
node *go = top;
for (int i = 0; i < atoi(args[1]) - 1; i ) {
go = go->next;
}
node *add = (node *) malloc(sizeof(node));
strcpy(add->str, go->str);
add->next = NULL;
cur->next = add;
if (counter >= 16) {
node *del = (node *) malloc(sizeof(node));
// NOTE/BUG: this is flagged by the compiler (i.e.) it sees that the value
// is discarded
#if 0
del = top;
top = top->next;
#else
del->next = top;
top = del;
#endif
// free(del);
}
}
state = lsh_execute(args);
free(line);
free(args);
// free(allline); don't really know why it can not be freed
counter ;
}
}
int
main()
{
shell_loop();
return EXIT_SUCCESS;
}