Unix Shell background processing

So I made my own unix shell, but i want to make a background process when using the & appended to the end, so far most of the commands seem to work (except cd, but thats another story)
right now here is what I have got.

Im thinking maybe I shouldn't be using switch and maybe switch it to something else, and Im also unsure of how to actually CHECK to see if theirs an &, because im not sure if my parsearg function will do the trick

thanks

Rather than using your own ParseArgs function, C has a built-in function called "getopt". It is a lot cleaner and meant for parsing command line arguments. (getopt(3): Parse options - Linux man page)

Oh....well crap.
Oh well I mean the parsing function works....
I checked with some extra debugging to make sure it's parsing and it is, however Im just not so sure how to do a background process, but after re-looking at my code...i can def. find the & just by cycling through the parse function again and checking for an &......

However Im still clueless as to what to do for background processing.

I tried to checkfor &...but keep getting a seg fault, i tried a for loop but it wouldnt work either. any ideas?

If you can now properly parse out the ampersand, I would suggest forking a process, daemonizing it, then executing whatever shell command is needed.

Well if I could get that while loop to run id be able to get the & out....but for some reason i get a seg fault in the commented out section of the code....

[edit] hang on

---------- Post updated at 04:04 PM ---------- Previous update was at 04:00 PM ----------

It could be crashing because you don't necessarily have 20 arguments. in which case you'll be feeding strcmp either garbage or NULL, either of which would cause it to crash. You'll need to remember how many arguments parseargs found(maybe put it through its return value instead of 'void'?)

In any case I don't think strcmp will do what you want. It returns 0 when the two strings are exactly the same, meaning, it'll only match on a line with no newlines containing nothing but &. I think you want strstr or strchr instead, see their man pages.

---------- Post updated at 04:08 PM ---------- Previous update was at 04:04 PM ----------

    switch(pid)
    {
        case -1:  
            cout << "DEBUG:Fork Failure" << endl;
            exit(-1);
            break;

        case  0:
            execvp(cmd[0], cmd);
            
            if(execvp(cmd[0], cmd) == -1)
            {
                cout << "Command Not Found" << endl;
                exit(0);
            }
            break;
   
        default:  
            wait(NULL);
            cout << "DEBUG:Child Finished" << endl;
            break;
    }

I'm surprised this even compiled, a break in the default section is mandatory in some common compilers.

Imagine case 0 without the break. If execvp() fails and doesn't catch the error it might keep going, and print "DEBUG: child finished" because the case statements don't tell it where to end, just where to begin. But with the break, it will leave that section of code entirely like you expected it to do.

EDIT WOOT IT WORKS.
check dis out

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <stdlib.h> 

using namespace std;

bool ParseArg(char* cmnd, char* cmd[], char input[],bool BG)
{
		
        cout << "myshell> ";
        cin.getline(input,50);
	cmnd = strtok(input, " ");
	int i = 0;

	while(cmnd != NULL)
	{
	    
	    cmd = cmnd;
	    if(strcmp(cmd, "&") == 0){
//Debug	    cout << "& found";
	    cmd = NULL;
	    return true;
	}
	    
//Debug	    cout << cmd << " ";
	    i++;
	    cmnd = strtok(NULL, " ");
	}
	return false;
}

void Clean(char* cmd[])
{
//Clean Array
        for(int a=0; a < 40; a++)
        {
             cmd[a] = NULL;
        }
}

void Execute(char* cmd[])
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
        case -1:  
            cout << "DEBUG:Fork Failure" << endl;
            exit(-1);
        case  0:
            execvp(cmd[0], cmd);
            
            if(execvp(cmd[0], cmd) == -1)
            {
                cout << "Command Not Found" << endl;
                exit(0);
            }
   
        default:  
            wait(NULL);
            cout << "DEBUG:Child Finished" << endl;
    }
    
}

int main()
{
   char* cmnd;
   char* cmd[40];
   char input[50]; 
   bool BG = false;

	

   while(cmd[0] != NULL)
   {
    	Clean(cmd);
   	BG = ParseArg(cmnd, cmd, input, BG);
	cout << BG;
   	if(strcmp(cmd[0], "exit") == 0 || strcmp(cmd[0], "quit") == 0 )
   	{
             break;
   	}
	else
	{
            Execute(cmd);
	}
	
   }

   return 1;

}

It returns a bool & when it finds one, then removes it. I checked with some debug statements but it works and removes the & correctly so it can do the statements normally.

Problem is now......getting background process to run.......which im derping on bigtime

here is a copy of the output, notice the 0 next to DEBUG, and the 1 when & is found. it means the Bool BG is turned on, when it finds an &.

I

myshell> ls -a
. .. a.out fork.c Hello .hi shell shell.c
0DEBUG:Child Finished
myshell> ls -l &
total 36
-rwxr-xr-x 1 matt matt 8435 2010-09-26 20:43 a.out
-rw-r--r-- 1 matt matt 943 2010-09-26 20:44 fork.c
drwxr-xr-x 2 matt matt 4096 2010-09-27 02:13 Hello
-rwxr-xr-x 1 matt matt 8292 2010-09-27 18:06 shell
-rw-r--r-- 1 matt matt 1570 2010-09-27 18:06 shell.c
1DEBUG:Child Finished
myshell>

Please read my reply to you. "strcmp" doesn't do what you think it does.

1 Like

It's working? however?
I mean..the bool return is changing correctly when an & is found?
Im not saying it's the best thing to be using....but for a shell such as this (being extremely simple) it's only meant for maybe 5 to 6 string characters on a line.

but about the background process. I saw another thread here Background processes in a dummy shell...
that was almost the exact same thing, so maybe I could modify my while(cmd[0] != NULL) loop to do this? or my execute function?

...I keep forgetting the strtok. Yes, it would.

That's simple enough. To run things without waiting for them, don't wait for them.

void Execute(int background, char* cmd[])
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
        case -1:  
            cout << "DEBUG:Fork Failure" << endl;
            exit(-1);
        case  0:
            execvp(cmd[0], cmd);
            
            if(execvp(cmd[0], cmd) == -1)
            {
                cout << "Command Not Found" << endl;
                exit(0);
            }
   
        default:
            if(background == 0)
            {
                    wait(NULL);
                    cout << "DEBUG:Child Finished" << endl;
            }
    }
    
}

...which will work, but leave zombie processes around until your shell quits. You should set up a SIGCHLD handler to handle them as they quit so you don't have to wait() for them.

1 Like

Ya I had heard about the zombie process thing, altho i'll prolly do that in my "prettying" up after im done with it all. Since im also sure using SIGCHLD is probably a decent addition, especially since i've never used it before.
Funny however I had the idea for the Switch statement just not waiting, and had it......but for some reason I thought it wouldn't work.

Try running a 'sleep 60 &' then a 'ls'. If it works, it won't wait a full minute for the sleep to finish before running ls.

Nope. You've already created a child with fork(). Fork clones a process, creating an independent copy that's almost identical except for fork()'s return value. Your identical copy goes and does what it wants while your main one does what it wants -- that is, your copy replaces itself with a command via exec, while your main program waits for it to finish.

The point is, it was in the background all along. The only reason it waits is because you call wait().

1 Like

Oh well I guess I just thought there was more to background.

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <stdlib.h> 

using namespace std;

bool ParseArg(char* cmnd, char* cmd[], char input[],bool BG)
{
		
        cout << "myshell> ";
        cin.getline(input,50);
	cmnd = strtok(input, " ");
	int i = 0;

	while(cmnd != NULL)
	{
	    
	    cmd = cmnd;
	    if(strcmp(cmd, "&") == 0){
//Debug	    cout << "& found";
	    cmd = NULL;
	    return true;
	}
	    
//Debug	    cout << cmd << " ";
	    i++;
	    cmnd = strtok(NULL, " ");
	}
	return false;
}

void Clean(char* cmd[])
{
//Clean Array
        for(int a=0; a < 40; a++)
        {
             cmd[a] = NULL;
        }
}

void Execute(char* cmd[],bool BG)
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
        case -1:  
            cout << "DEBUG:Fork Failure" << endl;
            exit(-1);
        case  0:
            execvp(cmd[0], cmd);
            
            if(execvp(cmd[0], cmd) == -1)
            {
                cout << "Command Not Found" << endl;
                exit(0);
            }
   
          default:
            if(BG == 0)
            {
                    wait(NULL);
                    cout << "DEBUG:Child Finished" << endl;
            }

    }
    
}

int main()
{
   char* cmnd;
   char* cmd[40];
   char input[50]; 
   bool BG = false;

	

   while(cmd[0] != NULL)
   {
    	Clean(cmd);
   	BG = ParseArg(cmnd, cmd, input, BG);
	cout << BG;
   	if(strcmp(cmd[0], "exit") == 0 || strcmp(cmd[0], "quit") == 0 )
   	{
             break;
   	}
	else
	{
            Execute(cmd,BG);
	}
	
   }

   return 1;

}

Thats my final code. I tested it.

here's my output, see if it "seems" right.
myshell> ls -l
total 36
-rwxr-xr-x 1 matt matt 8435 2010-09-26 20:43 a.out
-rw-r--r-- 1 matt matt 943 2010-09-26 20:44 fork.c
drwxr-xr-x 2 matt matt 4096 2010-09-27 02:13 Hello
-rwxr-xr-x 1 matt matt 8293 2010-09-27 18:50 shell
-rw-r--r-- 1 matt matt 1650 2010-09-27 18:50 shell.c
0DEBUG:Child Finished
myshell> ls -l &
1myshell> total 36
-rwxr-xr-x 1 matt matt 8435 2010-09-26 20:43 a.out
-rw-r--r-- 1 matt matt 943 2010-09-26 20:44 fork.c
drwxr-xr-x 2 matt matt 4096 2010-09-27 02:13 Hello
-rwxr-xr-x 1 matt matt 8293 2010-09-27 18:50 shell
-rw-r--r-- 1 matt matt 1650 2010-09-27 18:50 shell.c
ls -a
0DEBUG:Child Finished
myshell> . .. a.out fork.c Hello .hi shell shell.c

From what I can tell, when I call the & it doesn't wait for the myshell> to come back up, and just lets you call another command anyways. So it "seems" right.
Also I did the sleep 60 &, and it let me call ls right after, as opposed to without the & it had to wait. My only little issue "maybe" with it, is that after you call & once, the myshell>
doesn't come back up....although it still lets me type in commands. So I thought "well maybe it's treating them ALL as background processes"......but it's not, because I tried the sleep 60 thing after I had typed in an ls & previously and it made me wait 60 seconds..........so? I think it may just be a problem with the myshell> printout thing, altho im not sure what it is.

BTW, totally off topic, but is there any reason cd (change dir.) doesn't ever work on any of these "sample shells" i've seen around (when I was looking for help with mine, i looked at a ton of examples and tried a ton of them) is their something special about cd?

Seems quite on-topic to me.

cd is special because it's not a command you run, it's the system call chdir(), and it has to be done inside the shell itself, not in a forked copy. Remember that the forked copy is independent. If you chdir inside it, your original shell still remains where it is, defeating the point!

So the shell has to handle cd itself, feeding its parameters into the chdir() call directly, not forking anything.

1 Like

That makes sense.
Oh well I mean everything else looks correct, the myshell> printout is a little bit screwy when I use & BUT the & seems to be working correctly regardless (using the sleep 60 & thing to test it)

You may be fighting buffers because you're using printf(). This keeps it in memory until it decides it's convenient to print it(usually, whenever it finds a newline). And when you fork(), you get a close-to-identical copy... So if you fork while anything's in the buffer it could even get printed twice! You might want to fflush(stdout); after you print, and before you fork(), to guarantee nothing's left in the buffers.

You can also do fprintf(stderr, "hello world\n"); to avoid it, since stderr never buffers. (it might be better to print to stderr anyway.)

1 Like

here's an example of what im talking about.
myshell> ls -a
. .. a.out fork.c Hello .hi shell shell.c
myshell> ls -l
total 36
-rwxr-xr-x 1 matt matt 8435 2010-09-26 20:43 a.out
-rw-r--r-- 1 matt matt 943 2010-09-26 20:44 fork.c
drwxr-xr-x 2 matt matt 4096 2010-09-27 02:13 Hello
-rwxr-xr-x 1 matt matt 8322 2010-09-28 16:21 shell
-rw-r--r-- 1 matt matt 1693 2010-09-28 16:21 shell.c
myshell> ls &
myshell> a.out fork.c Hello shell shell.c
ls -a
myshell> . .. a.out fork.c Hello .hi shell shell.c
<---- here is where the "pointer" ends up. i can still type commands, but it doesn't show "myshell>" anymore

I tried the fprintf(stderr, "hello world\n"); thing. but it didn't work, it only happens when the & happens for some reason...

Looking at my code it should go like this.

parse arg. which prints out myshell>
User inputs commands<---
parse arg parses it.

so we have myshell> ls -l
so we execute it. which goes on a newline (apparently) (since BG is 0) and we wait for it to finish.
we then go back in the loop and myshell is printed again.

But with &....it goes back to parse arg (since we can still type things in, and they work) but myshell> isn't printed....which makes zero sense. Im looking at my code, tracing through....and I just dont see why this is happening, even when we aren't "waiting" the the process to finish, it should still loop back around to the parsearg function which has the myshell> printout.

It seems like typing in the & for SOME reason....like...screws all my printouts up.

edit: I think I may know why now.....im not resetting the BG bool to false, so it's treating them all as "background" processes i think........

edit2: CRAP nevermind, i realize im never setting BG permanently, oh well there goes that theory.

Please don't PM me in the hope of a faster response. I am not on-call here.

On my system I get:

myshell> echo a
a
0DEBUG:Child Finished
myshell> echo a &
1myshell> a

It does print 'myshell' again. And then 'echo' stomps on it and moves to the next line. This is normal. Since we're not waiting for it, the background process can't be polite about when it prints what... Also watch where you're cout-ing BG, if the process is backgrounded it kind of just shoves it in there anyway.

In a real shell try

$ ( sleep 3 ; echo asdf ) &

Sorry, I just had a bunch of ideas was trying to figure stuff out.
also, i realize that is prints myshell again the first time, but if you try typing in another command, it'll just be a blank cursor (with no myshell printout)
like for instance the * is where the "blinking" cursor is.
myshell> ls
a.out fork.c Hello hello2 shell shell.c
myshell> ls &
myshell > a.out fork.c Hello hello2 shell shell.c
*
^ thats where the blinking cursor will be, i can still type in stuff....but there is no new myshell> printed out.

if I typed something else out again it's like....
ls
myshell>a.out fork.c Hello hello2 shell shell.c
ls &
myshell>a.out fork.c Hello hello2 shell shell.c
ls
myshell>a.out fork.c Hello hello2 shell shell.c

kinda weird...I guess it doesn't hurt anything, just thought it was a bit odd.

I think I've figured it out. Because you let the background process die without wait()-ing for it, there's more than one process left around to wait for, and you get the oldest first, i.e. the already-dead one. so wait() happens instantly, picking up the dead process, while the new process keeps going in the background and messes up your terminal again.

You could try waitpid instead of wait, in order to wait for a specific PID instead of any PID.