Writing simple shell in C, compiles but won't run :(

I am writing a simple shell program for unix that 1) takes a command from a user via standard input, 2) parses the command into a data structure, 3) parses the PATH environment, 4) looks up the command within the PATH, and 5) forks a child process to execute the command while the parent waits to terminate.

I had the first half of it working great. I could enter user input, and it would parse it correctly and keep on 'a runnin. But when I implemented the path stuff, it no longer worked how I wanted it to. I do not see where I messed up, and my debug prints inside of main() are useless because NONE of them get printed :(. Can anyone see if I messed something up within *lookupPath() or parsePath()? Or anywhere else? Thanks!!
(I tried to comment as much and as clear as possible to make navigation and understanding easy)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define LINE_LEN      80
#define MAX_ARGS      64
#define MAX_ARG_LEN   16
#define MAX_PATHS     64
#define MAX_PATH_LEN  96
#define DELIM         " *.,\t\n"
 
#ifndef NULL
#define NULL ...
#endif
 
struct command_t {
  char *name;
  int  argc;
  char *argv[MAX_ARGS]; };
 
char *lookupPath(char **, char **);
int  parseCommand(char *, struct command_t *);
int  parsePath(char **);
void printPrompt();
void readCommand(char *);
 
/******************************************************************/
 
void printPrompt() {
  /* This code simply prints the shell prompt to standard out */
  char prompt[] = "jSh > ";
  printf("%s",prompt);
}
 
/******************************************************************/
 
void readCommand(char *buffer) {
  /* This code reads the entire command line into the buffer */
  fgets(buffer,LINE_LEN,stdin);
}
 
/******************************************************************/
 
int parsePath(char *dirs[]) {
  /* This function reads the PATH variable for the environment and
     then builds an array of all the directories in PATH */
  char *pathEnvVar;
  char *thePath;
  char *tmp;
  int i;
  int k;
 
  for(i=0; i<MAX_PATHS; i++)
    dirs[i] = NULL;
  pathEnvVar = (char *) getenv("PATH");
  if (pathEnvVar == NULL)
    return 0;
 
  thePath = (char *) malloc(strlen(pathEnvVar) + 1);
  strcpy(thePath, pathEnvVar);
 
  /* Parse thePath and construct dirs array while parsing*/
  for(tmp=strtok(thePath,":"); tmp!=NULL; strtok(NULL,":")) {
    dirs[k] = malloc(strlen(tmp)+1);
 
    /* Need to break out if k is NULL */
    if (dirs[k] == NULL)
      break;
 
    strcpy(dirs[k],tmp);
    k = k+1;
  }
 
  /* Return array size */
  return k;
}
 
/******************************************************************/
 
char *lookupPath(char **argv, char **dirs) {
  /* This function searches for program names within dirs */
  char *tmp;
  char *result;
  int i;
 
  /* Base case, see if file name is absolute path name */
  if(*argv[0] == '/') {
    result = argv[0];
    return result;
  }
 
  /* Look in PATH directories using access() to check for file */
  for(i=0;i<MAX_PATHS;i++) {
    if(dirs[i] == NULL)
      continue;
    /* Construct string for full PATH name */
    tmp = (char *) malloc(MAX_PATH_LEN*20);
    strcpy(tmp,dirs[i]);
    strncat(tmp,"/",MAX_PATH_LEN);
    strncat(tmp,argv[0],MAX_PATH_LEN);
 
    /* Check to see if file is in PATH and is executable */
    if(access(dirs[i],X_OK) == 0) {
      result = tmp;
      return result;
    }
  }
  
  /* Account for file not being found in any directories */
  fprintf(stderr,"%s: command not found\n",argv[0]);
  return NULL;
}
 
/******************************************************************/
 
int parseCommand(char *cLine, struct command_t *cmd) {
  /* parses a command from standard input and creates a command struct */
  int argc;
  int count;
  char **clPtr;
  clPtr = &cLine;
  argc  = 0;
 
  /* Need to fill up argv[] with all of the tokens */
  cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
  while((cmd->argv[argc] = strsep(clPtr,DELIM)) != NULL) {
    cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
  }
 
  /* need to decrement argc to reflect actual # of command args */
  cmd->argc = argc-1;
 
  /* create memory for name of command to be called, and store it in name */
  cmd->name = (char *) malloc(sizeof(cmd->argv[0]));
  strcpy(cmd->name,cmd->argv[0]);
 
  /* test for exit condition */
  if(strncasecmp(cmd->name,"exit",4)==0) {
    exit(0);
  }
 
  /* for testing purposes */
  printf("The command \"%s\" has %d argument(s)\n", cmd->name,cmd->argc);
  printf("argv0 = %s\n",cmd->argv[0]);
  for(count=0;count<argc;++count) {
    printf("argv[%d] = %s\n",count,cmd->argv[count]);
  }
 
  return 1;
}
 
/******************************************************************/
 
int main() {
  /* TEST */
  printf("inside main");
 
  char *pathv[MAX_PATHS];
  char commandLine[LINE_LEN];
  struct command_t command;
  int chPID;
  int status;
  pid_t childPID;
 
  /* TEST */
  printf("almost to parsePath()");
 
  /* Get all the PATH directories */
  parsePath(pathv);
 
  /* TEST */
  printf("got to parsePath()");
 
  /* loop to accept lines from standard input and parse them */
  while(1) {
    printPrompt();
    readCommand(commandLine);
    parseCommand(commandLine, &command);
    command.argv[command.argc] = NULL;
 
    /* Get the full pathname for the file */
    command.name = lookupPath(command.argv, pathv);
    if(command.name == NULL) {
      fprintf(stderr,"command \"%s\" unknown",command.name);
      continue;
    }
 
  /* Create child and execute the command */
    if((chPID = fork()) == 0) {
      printf("Child executing: %s\n", command.name);
      execvp(command.name, command.argv);
    }
 
  /* Wait for the child to terminate */
    printf("Parent waiting for child to terminate\n");
    childPID = wait(&status);
    /* Free dynamic storage in command structure */
    free(command.name);
  }
 
/* Shell termination */
  return 0;
}

Open in new window

bvjens31Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ozoCommented:
I don't see k initialized before doing     dirs[k] = malloc(strlen(tmp)+1);
0
bvjens31Author Commented:
ok, inistialized k at line 59 with:

k = 0;


Still the same result (compiles ok but seg fault upon running)
I still get these 'warnings' during compile:
jsh.c: In function main:
jsh.c:206: warning: implicit declaration of function wait

But I doubt that has anything to do with the seg fault. I have debug print statements that are supposed to execute right after main() begins, but they don't appear.
0
ozoCommented:
tmp never changes in the loop, so it never exits until  dirs[k]  overflows


you might print "\n" or turn off line buffering so your prints show up as sooner
0
Cloud Class® Course: Certified Penetration Testing

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

bvjens31Author Commented:
Doesn't tmp change during the first condition of the for loop:
/* Parse thePath and construct dirs array while parsing*/
  for(tmp=strtok(thePath,":"); tmp!=NULL; strtok(NULL,":")) {
    dirs[k] = malloc(strlen(tmp)+1);

Doesn't it grab the next token each iteration?
0
ozoCommented:
the only thing that changes tmp is tmp=strtok(thePath,":") which only happens before the first iteration

did you mean to do tmp=strtok(NULL,":")
0
bvjens31Author Commented:
Well I need the tmp=strtok(thePath,":" to initialize the tokenizer (first call to strtok()), and I use the 3rd condition of the for loop to advance token with strtok(NULL,":") (second call to strtok())
0
ozoCommented:
that's fine, but what you forgot to do is to update tmp in the loop
if tmp!=NULL  is true once, it will be true forever since tmp will never change
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
bvjens31Author Commented:
aaahhh! now i get what youre saying...so how would i update tmp? if I do a: tmp = strtok(NULL,":"), won't that force me to skip every-other token, since my for loop iteration condition already takes care of the token advancement?? thanks for the help so far!
0
bvjens31Author Commented:
ok i finally changed my debug print statements inside of main and redirected them to stderr so i would really know if they were executing, and they are! (most of them). where i get my core dump is during the printout of my PATH within the parsePath() command. so now i really can see that there is something wrong with my loop. Here is what a run of my shell looks like now (with stderr debugs):

chris@virulence:~/osys$ ./jSh
inside main
almost to parsePath()
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
Segmentation fault (core dumped)
0
bvjens31Author Commented:
i was doing several things wrong (it works wonders to be able to walk away from the computer for an hour to reorganize your thoughts :) )  

so i wanted to jot them down here in case anyone in the future has similar problems...first of all, ozo was correct and i was not updating tmp in my parsePath() function, which was quickly fixed by inserting     tmp = strtok(NULL,":") in between lines 71 and 72. But the key to this statement is that I must also had to take out the 3rd condition in my for loop. The iteration condition was advancing the token, so my dirs[] array was missing values that were getting skipped.

in my lookupPath() function, i was idiotically calling the access() function with the wrong string!! so I changed it to access(tmp,X_OK) from access(dirs[i],X_OK)       duh! (it was pretty late when i did most of this :P )

that, and i monkeyed around with the debug print msgs to localize points of failure. she's working now!!
/* the jenson shell
 
   this program accepts a command from standard input, parses the 
    string, searches the PATH environment for the appropriate program,
    and creates a child process to execute the command along with args. 
    (type 'exit' to quit The Jenson Shell)
    
chris jenson, operating systems, spring08 */
 
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define LINE_LEN      80
#define MAX_ARGS      64
#define MAX_ARG_LEN   16
#define MAX_PATHS     64
#define MAX_PATH_LEN  96
#define DELIM         " *.,\t\n"
 
#ifndef NULL
#define NULL ...
#endif
 
struct command_t {
  char *name;
  int  argc;
  char *argv[MAX_ARGS]; };
 
char *lookupPath(char **, char **);
int  parseCommand(char *, struct command_t *);
int  parsePath(char **);
void printPrompt();
void readCommand(char *);
 
/************************************************************************/
 
void printPrompt() {
  /* This code simply prints the shell prompt to standard out */
  char prompt[] = "jSh > ";
  printf("%s",prompt);
}/* end printPrompt */
 
/************************************************************************/
 
void readCommand(char *buffer) {
  /* This code reads the entire command line into the buffer */
  fgets(buffer,LINE_LEN,stdin);
}/* end readCommand */
 
/************************************************************************/
 
int parsePath(char *dirs[]) {
  /* This function reads the PATH variable for the environment and
     then builds an array of all the directories in PATH */
  char *pathEnvVar;
  char *thePath;
  char *tmp;
  int i;
  int k;
  k = 0;
 
  for(i=0; i<MAX_PATHS; i++)
    dirs[i] = NULL;
  pathEnvVar = (char *) getenv("PATH");
  if (pathEnvVar == NULL)
    return 0;
 
  thePath = (char *) malloc(strlen(pathEnvVar) + 1);
  strcpy(thePath, pathEnvVar);
 
  /* DEBUG
     fprintf(stderr,"PATH: %s\n", thePath);*/
 
  /* Parse thePath and construct dirs array while parsing*/
  tmp = thePath;
  for(tmp=strtok(thePath,":"); tmp!=NULL;) {
    dirs[k] = malloc(strlen(tmp)+1);
 
    /* Need to break out if k is NULL */
    if (dirs[k] == NULL)
      break;
 
    strcpy(dirs[k],tmp);
    tmp = strtok(NULL,":");
 
    /* DEBUG
       fprintf(stderr,"dirs[%d] = %s\n",k,dirs[k]); */
 
    ++k;
  }
 
  /* Return array size */
  return k;
}/* end parsePath */
 
/************************************************************************/
 
char *lookupPath(char **argv, char **dirs) {
  /* This function searches for program names within dirs */
 
  char *tmp;
  char *result;
  int i;
 
  /* Base case, see if file name is absolute path name */
  if(*argv[0] == '/') {
    result = argv[0];
    return result;
  }
 
  /* Look in PATH directories using access() to check for file */
  for(i=0;i<MAX_PATHS;i++) {
    if(dirs[i] == NULL)
      continue;
    /* Construct string for full PATH name */
    tmp = (char *) malloc(MAX_PATH_LEN*20);
    strcpy(tmp,dirs[i]);
    strncat(tmp,"/",MAX_PATH_LEN);
    strncat(tmp,argv[0],MAX_PATH_LEN);
 
    /* Check to see if file is in PATH and is executable */
    if(access(tmp,X_OK) == 0) {
      result = tmp;
      return result;
    }
  }
  
  /* Account for file not being found in any directories */
  fprintf(stderr,"%s: command not found\n",argv[0]);
  free(tmp);
  return NULL;
}/* end lookupPath */
 
/************************************************************************/
 
int parseCommand(char *cLine, struct command_t *cmd) {
  /* parses a command from standard input and creates a command struct */
 
  int argc;
  char **clPtr;
  /* DEBUG
     int count;  */
 
  clPtr = &cLine;
  argc  = 0;
 
  /* Need to fill up argv[] with all of the tokens, will NOT account
	for consecutive DELIMS like strtok() does */
  cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
  while((cmd->argv[argc] = strsep(clPtr,DELIM)) != NULL) {
    cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
  }
 
  /* need to decrement argc to reflect actual # of command args */
  cmd->argc = argc-1;
 
  /* create memory for command name, and store it in cmd->name */
  cmd->name = (char *) malloc(sizeof(cmd->argv[0]));
  strcpy(cmd->name,cmd->argv[0]);
 
  /* test for exit condition */
  if(strncasecmp(cmd->name,"exit",4)==0) {
    printf("\nBye bye!\n\n");
    exit(0);
  }
 
  /* DEBUG
  printf("The command \"%s\" has %d argument(s)\n", cmd->name,cmd->argc-1);
  for(count=0;count<argc;++count) {
    printf("argv[%d] = %s\n",count,cmd->argv[count]);
    } */
 
  return 1;
}/* end parseCommand */
 
/**************************************************************************/
 
int main() {
 
  /* DEBUG
     fprintf(stderr,"Inside the main())"); */
  
  char *pathv[MAX_PATHS];
  char commandLine[LINE_LEN];
  struct command_t command;
  int numChildren;
  int i;
  int status;
  pid_t pid;
 
  /* DEBUG
     fprintf(stderr,"almost to parsePath()\n"); */
 
  /* Get all the PATH directories */
  parsePath(pathv);
 
  /* DEBUG
     printf("parsing of PATH complete\n"); */
 
  /* Print welcome message */
  printf("\nWelcome to the Jenson Shell! Hope you have fun!\n");
  printf("(type \"exit\" to quit the shell and return home)\n\n");
 
  /* loop to accept lines from standard input and parse them */
  while(1) {
    printPrompt();
    readCommand(commandLine);
    parseCommand(commandLine, &command);
    command.argv[command.argc] = NULL;
 
    /* Get the full pathname for the file */
    command.name = lookupPath(command.argv, pathv);
    if(command.name == NULL) {
      continue;
    }
 
    numChildren = 0;
    /* Create child and execute the command */
    if((pid = fork()) == 0) {
      fprintf(stderr,"Child executing: %s\n", command.name);
      fprintf(stderr,"Child pid = %d\n",getpid());
      execv(command.name, command.argv);
    }
    numChildren++;
 
  /* Wait for the child(ren) to terminate */
    fprintf(stderr,"Parent waiting for child to terminate\n\n");
    for(i=0;i<numChildren;i++) {
      wait(&status);
    /* Free dynamic storage in command structure */
      free(command.name);
    }/* end for */
    
  }/* end while */
 
  return 0;
}/* end main() */

Open in new window

0
Infinity08Commented:
>> which was quickly fixed by inserting     tmp = strtok(NULL,":") in between lines 71 and 72.

You could have placed that in the increment part of the for loop :

        for (tmp = strtok(thePath, ":"); tmp != NULL; tmp = strtok(NULL, ":")) {
            /* ... */
        }


Also :

1) Your lookupPath function sometimes allocates memory for the return string, sometimes it doesn't. The calling code doesn't know whether it has to free the returned value or not ... You should probably always allocate, or change the design a bit to avoid this problem.


2) in your parseCommand function you have this loop :

        cmd->argv[argc] = (char *) malloc(MAX_ARG_LEN);
        while((cmd->argv[argc] = strsep(clPtr,DELIM)) != NULL) {
          cmd->argv[++argc] = (char *) malloc(MAX_ARG_LEN);
        }

    Be very careful here !! You're allocating memory, then store the pointer to that memory in cmd->argv[++argc], and on the next iteration, you overwrite that pointer with strsep(clPtr,DELIM) ... That's a memory leak !!


3) also in your parseCommand, you have this :

        cmd->name = (char *) malloc(sizeof(cmd->argv[0]));

    cmd->argv[0] is a char*. You can't use sizeof to calculate its size (that only works for arrays). Rather use strlen(cmd->argv[0]) + 1


4) you have a lot of malloc's, but almost no matching free's ?... That's a lot of memory leaks !!


5) you get this warning :

        jsh.c:206: warning: implicit declaration of function wait

    listen to it ... It's telling you that it doesn't know wait. You probably need to include :

        #include <sys/types.h>
        #include <sys/wait.h>


These are just a few remarks - just what I noticed after a quick look over your code ... There might be more problems.
0
Duncan RoeSoftware DeveloperCommented:
20:32:20$ gcc -o ee18 ee18.c -Wall -Wmissing-prototypes -Wstrict-prototypes -g3 -ggdb
ee18.c:37: warning: function declaration isn't a prototype
ee18.c:42: warning: function declaration isn't a prototype
ee18.c:183: warning: function declaration isn't a prototype
ee18.c: In function `main':
ee18.c:234: warning: implicit declaration of function `wait'

2 initial comments:

1. Fix *all* warnings. You might think some are of little consequence, but it's *so* much easier  to check for no warnings when you make changes than to check for a new one.

2. Learn to use gdb, the GNU source level debugger. It will save you heaps of time over inserting (and later removing) fprintf's
0
Duncan RoeSoftware DeveloperCommented:
Below is a context diff of the changes to eliminate the warnings.

Next I will reformat the program to my preferred style using GNU indent (I like opening braces on their own line - actually I think that style of having them trailing is hideous - too bad it caught on for Java IMO). Both styles are known to indent so you don't have to put up with one if you'd prefer the other - just use indent to convert it. (applies to C code, not necessarily java or even C++).
*** ee18.c      2008/02/12 09:39:38     1.2
--- ee18.c      2008/02/12 09:44:20
***************
*** 14,19 ****
--- 14,21 ----
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
   
  #define LINE_LEN      80
  #define MAX_ARGS      64
***************
*** 34,40 ****
  char *lookupPath(char **, char **);
  int  parseCommand(char *, struct command_t *);
  int  parsePath(char **);
! void printPrompt();
  void readCommand(char *);
   
  /************************************************************************/
--- 36,42 ----
  char *lookupPath(char **, char **);
  int  parseCommand(char *, struct command_t *);
  int  parsePath(char **);
! void printPrompt(void);
  void readCommand(char *);
   
  /************************************************************************/
***************
*** 180,186 ****
   
  /**************************************************************************/
   
! int main() {
   
    /* DEBUG
       fprintf(stderr,"Inside the main())"); */
--- 182,188 ----
   
  /**************************************************************************/
   
! int main(int argc, char **argv) {
   
    /* DEBUG
       fprintf(stderr,"Inside the main())"); */

Open in new window

0
Duncan RoeSoftware DeveloperCommented:
This is the reformatted program. I added a comment with the compile line at the front (I always do that) and removed the commented-out DEBUG sections. I tried running it and it seems to work quite well. A few comments:

- It should terminate on end-of-file (Ctrl-D from a tty) as well as the "exit" command - I'll try to fix that, to give you an idea of the power of gdb.
- It does leak memory as you execute commands as I was able to verify with ps. The smart way to fix that is to use a malloc debugging package - I did put one of those together starting from Tcl/Tk's ckalloc, but I can't find it right now. Never mind, there are plenty of them out there.
- It behaves oddly on receiving a blank line, tries to execute the first directory on the PATH:

jSh > 
Child executing: /home/dunc/bin/

Luckily, ^D (eof) terminates the execution.

- It can't change directory because it doesn't have a built-in cd command. All shells need to have one of these - there's no point in having an external program change its directory:

jSh > cd ..
cd: command not found

Perhaps before reporting an error, jsh could check if the at_fault command was in fact cd, and action it if so.
/*
gcc -o ee18 ee18.c -Wall -Wmissing-prototypes -Wstrict-prototypes -g3 -ggdb
*/
/* the jenson shell
 
   this program accepts a command from standard input, parses the 
    string, searches the PATH environment for the appropriate program,
    and creates a child process to execute the command along with args. 
    (type 'exit' to quit The Jenson Shell)
    
chris jenson, operating systems, spring08 */
 
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
#define LINE_LEN      80
#define MAX_ARGS      64
#define MAX_ARG_LEN   16
#define MAX_PATHS     64
#define MAX_PATH_LEN  96
#define DELIM         " *.,\t\n"
 
#ifndef NULL
#define NULL ...
#endif
 
struct command_t
{
  char *name;
  int argc;
  char *argv[MAX_ARGS];
};
 
char *lookupPath(char **, char **);
int parseCommand(char *, struct command_t *);
int parsePath(char **);
void printPrompt(void);
void readCommand(char *);
 
/************************************************************************/
 
void
printPrompt()
{
  /* This code simply prints the shell prompt to standard out */
  char prompt[] = "jSh > ";
  printf("%s", prompt);
}                                  /* end printPrompt */
 
/************************************************************************/
 
void
readCommand(char *buffer)
{
  /* This code reads the entire command line into the buffer */
  fgets(buffer, LINE_LEN, stdin);
}                                  /* end readCommand */
 
/************************************************************************/
 
int
parsePath(char *dirs[])
{
  /* This function reads the PATH variable for the environment and
     then builds an array of all the directories in PATH */
  char *pathEnvVar;
  char *thePath;
  char *tmp;
  int i;
  int k;
  k = 0;
 
  for (i = 0; i < MAX_PATHS; i++)
    dirs[i] = NULL;
  pathEnvVar = (char *)getenv("PATH");
  if (pathEnvVar == NULL)
    return 0;
 
  thePath = (char *)malloc(strlen(pathEnvVar) + 1);
  strcpy(thePath, pathEnvVar);
 
  /* Parse thePath and construct dirs array while parsing */
  tmp = thePath;
  for (tmp = strtok(thePath, ":"); tmp != NULL;)
  {
    dirs[k] = malloc(strlen(tmp) + 1);
 
    /* Need to break out if k is NULL */
    if (dirs[k] == NULL)
      break;
 
    strcpy(dirs[k], tmp);
    tmp = strtok(NULL, ":");
 
    ++k;
  }
 
  /* Return array size */
  return k;
}                                  /* end parsePath */
 
/************************************************************************/
 
char *
lookupPath(char **argv, char **dirs)
{
  /* This function searches for program names within dirs */
 
  char *tmp;
  char *result;
  int i;
 
  /* Base case, see if file name is absolute path name */
  if (*argv[0] == '/')
  {
    result = argv[0];
    return result;
  }
 
  /* Look in PATH directories using access() to check for file */
  for (i = 0; i < MAX_PATHS; i++)
  {
    if (dirs[i] == NULL)
      continue;
    /* Construct string for full PATH name */
    tmp = (char *)malloc(MAX_PATH_LEN * 20);
    strcpy(tmp, dirs[i]);
    strncat(tmp, "/", MAX_PATH_LEN);
    strncat(tmp, argv[0], MAX_PATH_LEN);
 
    /* Check to see if file is in PATH and is executable */
    if (access(tmp, X_OK) == 0)
    {
      result = tmp;
      return result;
    }
  }
 
  /* Account for file not being found in any directories */
  fprintf(stderr, "%s: command not found\n", argv[0]);
  free(tmp);
  return NULL;
}                                  /* end lookupPath */
 
/************************************************************************/
 
int
parseCommand(char *cLine, struct command_t *cmd)
{
  /* parses a command from standard input and creates a command struct */
 
  int argc;
  char **clPtr;
 
  clPtr = &cLine;
  argc = 0;
 
  /* Need to fill up argv[] with all of the tokens, will NOT account
     for consecutive DELIMS like strtok() does */
  cmd->argv[argc] = (char *)malloc(MAX_ARG_LEN);
  while ((cmd->argv[argc] = strsep(clPtr, DELIM)) != NULL)
  {
    cmd->argv[++argc] = (char *)malloc(MAX_ARG_LEN);
  }
 
  /* need to decrement argc to reflect actual # of command args */
  cmd->argc = argc - 1;
 
  /* create memory for command name, and store it in cmd->name */
  cmd->name = (char *)malloc(sizeof(cmd->argv[0]));
  strcpy(cmd->name, cmd->argv[0]);
 
  /* test for exit condition */
  if (strncasecmp(cmd->name, "exit", 4) == 0)
  {
    printf("\nBye bye!\n\n");
    exit(0);
  }
 
  return 1;
}                                  /* end parseCommand */
 
/**************************************************************************/
 
int
main(int argc, char **argv)
{
 
  char *pathv[MAX_PATHS];
  char commandLine[LINE_LEN];
  struct command_t command;
  int numChildren;
  int i;
  int status;
  pid_t pid;
 
  /* Get all the PATH directories */
  parsePath(pathv);
 
  /* Print welcome message */
  printf("\nWelcome to the Jenson Shell! Hope you have fun!\n");
  printf("(type \"exit\" to quit the shell and return home)\n\n");
 
  /* loop to accept lines from standard input and parse them */
  while (1)
  {
    printPrompt();
    readCommand(commandLine);
    parseCommand(commandLine, &command);
    command.argv[command.argc] = NULL;
 
    /* Get the full pathname for the file */
    command.name = lookupPath(command.argv, pathv);
    if (command.name == NULL)
    {
      continue;
    }
 
    numChildren = 0;
    /* Create child and execute the command */
    if ((pid = fork()) == 0)
    {
      fprintf(stderr, "Child executing: %s\n", command.name);
      fprintf(stderr, "Child pid = %d\n", getpid());
      execv(command.name, command.argv);
    }
    numChildren++;
 
    /* Wait for the child(ren) to terminate */
    fprintf(stderr, "Parent waiting for child to terminate\n\n");
    for (i = 0; i < numChildren; i++)
    {
      wait(&status);
      /* Free dynamic storage in command structure */
      free(command.name);
    }                              /* end for */
 
  }                                /* end while */
 
  return 0;
}                                  /* end main() */

Open in new window

0
Duncan RoeSoftware DeveloperCommented:
After a little preliminary investigation, it seems you need to take special action after calling readCommand(), if either an empty command line was entered, or Ctrl-D was typed. So, in the gdb session below, I set a breakpoint on that function. "set print null-stop" is a useful thing to do when printing the contents of buffers that are expected to hold a character string: printing stops as soon as the first NUL (\0) character is encountered instead of printing the whole buffer.
Run the program and we get to the first call to readCommand. gdb's "n" (next line) command executes the fgets. This appears to first of all output the prompt from printPrompt() - a bit lucky that, maybe fgets() does an fflush() that you should have done in printPrompt(). Now I type "ls" and touch <Enter>, to see how a normal command is treated. Another "n" and readCommand() returns to main() ready to call parseCommand(). At this point the commandLine buffer holds "ls\n". I let the program run and /usr/bin/ls was executed nicely.
Next, I tried a blank line. That returned a buffer of "\n" (1 character) What would have happened if I'd typed some whitespace characters instead (say 5 spaces and a Tab). A quick test shows - really horrible things happen. But I'll leave that to you to fix - you'll need to do that in parseCommand(), so that function might as well also deal with the simple case of no whitespace characters as well.
Now I tried Ctrl-d (^D) that I said I'd try to fix. readCommand() returns an empty string. There can't be whitespace before a ^D or if there is then the tty driver won't interpret ^D as meaning end of file. What the empty return string tells you is thatread() (As called by fgets()) returned zero bytes, indicating end of file. I knew the program wouldn't work after that, so I quit.
What we need to do is, if readCommand() returns an empty string, then we exit.
Here's the change:

** ee18.c      2008/02/12 10:08:50     1.5
--- ee18.c      2008/02/12 11:44:49
***************
*** 210,215 ****
--- 210,220 ----
    {
      printPrompt();
      readCommand(commandLine);
+     if (strlen(commandLine) == 0)
+     {
+       printf("\n");
+       return 0;
+     }
      parseCommand(commandLine, &command);

The newline print is so that the shell prompt appears on its own line, rather than following the jSh prompt. If I had been in a hurry, I would have coded "if  (!*commandLine)" instead of using strlen (might have even missed the space out, but indent puts them back in).

Well, there you are. 1 little improvement done. The rest is up to you
22:01:56$ gdb ee18
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu"...Using host libthread_db library "/lib64/libthread_db.so.1".
 
(gdb) b readCommand
Breakpoint 1 at 0x400acb: file ee18.c, line 60.
(gdb) set print null-stop
(gdb) run
Starting program: /home/dunc/tests/ee18 
 
Welcome to the Jenson Shell! Hope you have fun!
(type "exit" to quit the shell and return home)
 
 
Breakpoint 1, readCommand (buffer=0x7fffd8af6c80 "") at ee18.c:60
60        fgets(buffer, LINE_LEN, stdin);
(gdb) n
jSh > ls
61      }                                  /* end readCommand */
(gdb) n
main (argc=1, argv=0x7fffd8af6fd8) at ee18.c:213
213         parseCommand(commandLine, &command);
(gdb) p commandLine
$1 = "ls\n"
(gdb) c
Continuing.
Detaching after fork from child process 2448.
Child executing: /usr/bin/ls
Child pid = 2448
Account.cpp               SendBuffer.c             add3o2.s    conftest.c                ee.sh.bu  ee18.c      filename         hello.i      mac           run.so            statfs.o     tiny              x.sh
Account.cpp.bu            SingleNoteChannel.class  add4.c      core                      ee10      ee18.c~     files            hello.o      main4         runstat.sh        stddef.c     tiny.c            xxftp
Account.h                 SingleNoteChannel.java   add4.h      corenotes                 ee10.cpp  ee2         find             hello.s      main4.c       scale1            strerror     tm                zlib
Account.h.bu              a                        add4.i      ctiming                   ee11      ee2.c       findit           hi           main4.i       scale1.c          strerror.c   try               zlib.c
Bordered$1.class          a.out                    aftp        ctiming.c                 ee11.c    ee3         findit.c         html.sh      main4.s       scale1.i          t            try.tcl           zsend.o
Bordered.class            abort                    array.sh    ctiming.o                 ee11.c~   ee3.c       flags            in.c         mainstat      scale1a.c         t.c          tryem.sh          zsendrcv.c
Bordered.java             abort.Makefile           bcast.c     ctiming.out               ee11.sh   ee4         flags.c          in.c.bu      mainstat.c    scale2            t5           tryf2c.c          zsendrcv.h
Bounds$1.class            abort_.f                 bcast.c.bu  darkstar                  ee11.t    ee4.pl      float.c          in.o         mainstat.h    scale2.c          t5.preserve  tryf2c.f          zsendrcv.o
Bounds$BoxedLabel.class   abort_.o                 beroot      deepc146.c                ee11.txt  ee5a.c      fortest.sh       iplines      mainstat.o    sendUDPMessage.c  t6           tryin.c
Bounds.class              abort___.c               beroot.c    deepc177.c                ee12      ee5a.c.bu   ft.c             keys         mycat         serial.c          t6.bak       tryme
Bounds.java               abort___.o               bug.c       deepc180.c                ee12.c    ee5a.c.old  func.sh          keys.c       optimtut1.ps  server            t6.bu        tryme.c
EtherLinkXL.c             add1.c                   ccmd        demo.sh                   ee13      ee6.sh      getg             lam_hello.c  pair          server.c          tar          tryme_linux32
Makefile                  add1.s                   cheryl.tcl  dlopen                    ee14.sh   ee7.sh      getg.c           ldd          pair.cpp      sigsegv           tbad         tryme_linux32.bu
Metrics$1.class           add1g.s                  client      dlopen.c                  ee14c.sh  ee9         gethostbyname.c  libipm.a     pcap          sigsegv.c         test         tryme_linux64
Metrics$BoxedLabel.class  add1o1.s                 client.c    drop_0_delay_CLIENT.dat   ee15.sh   ee9.c       gpg              libstat.c    pcap.c        simple-loader     test.C       tryme_linux64.bu
Metrics.class             add1o2.s                 cmd         drop_0_delay_SERVER.dath  ee16      ee9.c~      gpg.c            libstat.o    pipe          simple-loader.c   test.c       tst
Metrics.java              add2.c                   cmd.c       ee                        ee17      eedl.c      hee              libstat.so   pipe.c        simple-module.c   test2.f      whattime.pl
Q_22137834.txt            add2o2.s                 como        ee.cpp                    ee17.c    eedl.o      hello.c          linnum       pussy.c       simple-module.so  tgood        with_w
RCS                       add3.c                   conftest    ee.sh                     ee18      exptests    hello.f          log.file     pussy.o       statfs.c          tiger.c      without_w
Parent waiting for child to terminate
 
 
Breakpoint 1, readCommand (buffer=0x7fffd8af6c80 "ls") at ee18.c:60
60        fgets(buffer, LINE_LEN, stdin);
(gdb) # Try empty line
(gdb) n
jSh > 
61      }                                  /* end readCommand */
(gdb) 
main (argc=1, argv=0x7fffd8af6fd8) at ee18.c:213
213         parseCommand(commandLine, &command);
(gdb) p commandLine
$2 = "\n"
(gdb) c
Continuing.
Detaching after fork from child process 2449.
Child executing: /home/dunc/bin/
Child pid = 2449
Parent waiting for child to terminate
 
jSh > Parent waiting for child to terminate
 
 
Breakpoint 1, readCommand (buffer=0x7fffd8af6c80 "") at ee18.c:60
60        fgets(buffer, LINE_LEN, stdin);
(gdb) n
jSh > 61        }                                  /* end readCommand */
(gdb) n
main (argc=1, argv=0x7fffd8af6fd8) at ee18.c:213
213         parseCommand(commandLine, &command);
(gdb) p commandLine
$3 = ""
(gdb) q

Open in new window

0
Duncan RoeSoftware DeveloperCommented:
I suggest split ozo http:#20871962, Infinity08 http:#20872885 and duncan_roe http:#20874021

we all helped
0
Infinity08Commented:
That sounds fair. Seconded :)
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.