So I have to make a UNIX shell that can basically take in any UNIX command and then keep a record of the time each process took, by forking off children processes, recording their time, and recording the parent's time, and then return that time into a linked list or vector so that i can print out all processes and their times when someone wants to see them.

Now the problem I am having is that I have no idea how a regular shell parses out bad input. Say for instance you type "pig" into the command line of your standar tcsh. It will say "pig": Bad command name or something to that effect. I want mine to do that but right now it is just accepting "pig" and calling it another running instance of my current shell. I know i have to do some sort of search for $PATH but no one has ever told me what functions do that kind of search, or even how to search that kind of thing so that I can error check. I pasted my code in here in hopes that perhaps someone can show me where and what the hell to do with making sure bad input stays bad input and doesn't get logged as a process.

/*
 * Program: This is a shell that reports stats of running times of processes initiated in 
 * the shell as well as the shell itself's process time 
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <iomanip>
#include <cstdlib>

using namespace std;

/**
 *  method displayID
 *  returns: nothing
 *  parameters: none
 *  Description: This method displays the author information
 */

void displayID()
{
}

/**
 *  method displayInfo
 *  returns: nothing
 *  parameters: none
 *  Description: This method displays the Game info
 */

void displayInfo()
{
        cout << "The function of the shell is to operate child processes" << endl;
	cout << "And return statistics of process time of each process" << endl;
	cout << "That has been running in the shell as well as the shell's" << endl;
	cout << "process time" << endl;
}


/**
 *  method takeInpt
 *  returns: nothing
 *  parameters: none
 *  Description: This method takes the input for the shell
 */

void takeInpt(char* cmnd, char* cmd[], char inpt[])
{
	//ask for and get the input	
        cout << "statsh$: ";
        cin.getline(inpt,81);
	cmnd = strtok(inpt, " ");
	int c = 0;

	while(cmnd != NULL)
	{
	    cmd[c] = cmnd;
	    c++;
	    cmnd = strtok(NULL, " ");
	}
}

/**
 *  method runProc  
 *  returns: nothing
 *  parameters: none
 *  Description: This method runs exec and forking commands
 */

void runProc(char* cmd[])
{
    pid_t pid;

    // fork
    / /do i check for bad input here??!! 
    pid = fork();
    if(pid < 0)
    {
	cout << "Fork Failed" << endl;
	exit(-1);
    }
    else if( pid == 0)
    {
	execvp(cmd[0], cmd);
    }
    else 
    {
	wait(NULL);
	cout << "Job's Done" << endl;
    }
    
}

int main()
{
   char* cmnd;
   char* cmd[40];
   char inpt[81]; 

   displayID();
   displayInfo();

   while(1)
   {
        //clean out the array each time
        for(int m=0; m < 40; m++)
        {
             cmd[m] = NULL;
        }

   	takeInpt(cmnd, cmd, inpt);

        if(cmd[0] == NULL)
        {
             cout << "Enter a command please" << endl;
        }
   	else if(strcmp(cmd[0], "exit") == 0 )
   	{
             break;
   	}
	else
	{
            runProc(cmd);
	}
	/*
	 * testing print statement to see user input is taken and tokenized   
   	for(int i=0; i < 40; i++)
   	{
       	     if(cmd[i])
       	     {
           	  cout<< cmd[i] << endl;
       	     }
   	}
	*/
   }

   return 1;

}

If anyone could help me I would greatly appreciate it. In the meantime I'll keep hittin away at it and try to get it right.

Recommended Answers

All 6 Replies

You can use getenv to get the contents of a certain environment value (such as "PATH"). Use man getenv for details.

The reason it seems like it's running another instance of your current shell is because you've forked your shell into a new process. So the program's doing exactly what you've asked it to do. If execvp fails, it simply returns -1. (If it succeeds, it never returns!) Then your child process keeps running until it finds a reason to exit, and then the parent's wait( ) call comes through, and the parent continues.

If you want to handle this, simply include something to handle it after your execvp call. If the call fails, you can write, "Bad command or filename\n", or whatever you want, and then exit() that process. Then the parent's wait() returns, and the parent continues running.

You can use getenv to get the contents of a certain environment value (such as "PATH"). Use man getenv for details.

The reason it seems like it's running another instance of your current shell is because you've forked your shell into a new process. So the program's doing exactly what you've asked it to do. If execvp fails, it simply returns -1. (If it succeeds, it never returns!) Then your child process keeps running until it finds a reason to exit, and then the parent's wait( ) call comes through, and the parent continues.

If you want to handle this, simply include something to handle it after your execvp call. If the call fails, you can write, "Bad command or filename\n", or whatever you want, and then exit() that process. Then the parent's wait() returns, and the parent continues running.

Thank you first of all for replying to my concern, but I'm afraid I still am not quite getting it. I did this to my procedure. Now it is saying that all of my input is bad input. Am I using getenv wrong? It should search the environment for that variable named there in and return NULL if it doesn't find it right? So says the man pages. So I'm trying to do taht, but it isn't doing it.

void runProc(char* cmd[], int argSz) //argSz should be the max amount of args they entered counted in another method
{
    tms strt, end;
    long s,e;
    pid_t pid;
    bool badEx = false;
 
    for(int i=0; i < argSz; i++)   
    {
        //cout << "What's wrong here" << endl;
        if(getenv(cmd[i]) == NULL)
        {
            cout << "The error occured at: " << i << endl;
            badEx = true;  
            break;
        }
    }
    if(badEx == true)
    {
        cerr << "Bad Command or file name" << endl;
    }
    else
    {
    // fork
    times(&strt);   
    pid = fork();   
    switch(pid) 
    {
        case -1:
            cout << "Fork Failed" << endl;
            exit(-1);
        case  0:   
            execvp(cmd[0], cmd);
   
        default: 
        
            wait(NULL);
            times(&end);
            cout << "Process took: " << (end.tms_utime-strt.tms_utime)/100;
            cout << " seconds" << endl;
            cout << "Child has finished" << endl;
    }
} //end else

And lastly, I don't get what you mean by putting a line after the execvp line. What would the line contain? An exit(0) statement? I'm a little confused about that.

You don't actually need getenv. If execvp fails, it returns. That's how you know it fails. But as a side note, you should be using getenv with

char * pathstr = getenv("PATH");

Then pathstr contains the null memory address, or it contains the memory address of a string such as "/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games". That's the output of this miniprogram:

#include <stdlib.h>
#include <stdio.h>

int main(void) {

  char * s = getenv("PATH");

  if (s) {
    printf("%s\n", s);
  }

  return 0;
}

And lastly, I don't get what you mean by putting a line after the execvp line. What would the line contain? An exit(0) statement? I'm a little confused about that.

If execvp returns, you still have two processes running. The parent is wait()ing for the child process to terminate. What do you want your child process to do?

You don't actually need getenv. If execvp fails, it returns. That's how you know it fails. But as a side note, you should be using getenv with

char * pathstr = getenv("PATH");

Then pathstr contains the null memory address, or it contains the memory address of a string such as "/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games". That's the output of this miniprogram:

#include <stdlib.h>
#include <stdio.h>

int main(void) {

  char * s = getenv("PATH");

  if (s) {
    printf("%s\n", s);
  }

  return 0;
}

Let me know if I that is wrong. I think it seems to be working right now, but I need to remember that it must work for later commands when I add piping and duping crap...whcih as of yet I don't konw how to do.

Thanks again for the help btw. Good show!

If execvp returns, you still have two processes running. The parent is wait()ing for the child process to terminate. What do you want your child process to do?

Hmm, did you mean something like the following. Since execvp returns -1 when it fails, then this should work for exiting the process that isn't a real process right?

// fork
    times(&strt);
    pid = fork();
    switch(pid)
    {
        case -1:  
            cout << "Fork Failed" << 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);
            times(&end);
            cout << "Process took: " << (end.tms_utime-strt.tms_utime)/100;
            cout << " seconds" << endl;   
            cout << "Child has finished" << endl;
    }

That looks like an appropriate change, though you don't need to check for execvp returning -1. If it returns, we already know something failed. It returns -1 from historic practice, but the return value has never been standardized. (Or so says the man page on my machine.)

That looks like an appropriate change, though you don't need to check for execvp returning -1. If it returns, we already know something failed. It returns -1 from historic practice, but the return value has never been standardized. (Or so says the man page on my machine.)

You surely are a king among kings. I thank you for your advice and counsel on the matter.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.