?
Solved

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

Posted on 2008-02-11
19
Medium Priority
?
2,510 Views
Last Modified: 2013-12-26
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

0
Comment
Question by:bvjens31
  • 6
  • 5
  • 4
  • +1
17 Comments
 
LVL 85

Expert Comment

by:ozo
ID: 20871493
I don't see k initialized before doing     dirs[k] = malloc(strlen(tmp)+1);
0
 

Author Comment

by:bvjens31
ID: 20871675
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
 
LVL 85

Expert Comment

by:ozo
ID: 20871815
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
[Webinar] Kill tickets & tabs using PowerShell

Are you tired of cycling through the same browser tabs everyday to close the same repetitive tickets? In this webinar JumpCloud will show how you can leverage RESTful APIs to build your own PowerShell modules to kill tickets & tabs using the PowerShell command Invoke-RestMethod.

 

Author Comment

by:bvjens31
ID: 20871893
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
 
LVL 85

Expert Comment

by:ozo
ID: 20871914
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
 

Author Comment

by:bvjens31
ID: 20871925
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
 
LVL 85

Accepted Solution

by:
ozo earned 536 total points
ID: 20871962
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
 

Author Comment

by:bvjens31
ID: 20871976
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
 

Author Comment

by:bvjens31
ID: 20871995
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
 

Author Comment

by:bvjens31
ID: 20872444
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
 
LVL 53

Assisted Solution

by:Infinity08
Infinity08 earned 532 total points
ID: 20872885
>> 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
 
LVL 35

Expert Comment

by:Duncan Roe
ID: 20873462
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
 
LVL 35

Expert Comment

by:Duncan Roe
ID: 20873551
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
 
LVL 35

Expert Comment

by:Duncan Roe
ID: 20873754
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
 
LVL 35

Assisted Solution

by:Duncan Roe
Duncan Roe earned 532 total points
ID: 20874021
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
 
LVL 35

Expert Comment

by:Duncan Roe
ID: 25651036
I suggest split ozo http:#20871962, Infinity08 http:#20872885 and duncan_roe http:#20874021

we all helped
0
 
LVL 53

Expert Comment

by:Infinity08
ID: 25651309
That sounds fair. Seconded :)
0

Featured Post

[Webinar] Improve your customer journey

A positive customer journey is important in attracting and retaining business. To improve this experience, you can use Google Maps APIs to increase checkout conversions, boost user engagement, and optimize order fulfillment. Learn how in this webinar presented by Dito.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
In this post we will learn different types of Android Layout and some basics of an Android App.
The goal of this video is to provide viewers with basic examples to understand and use structures in the C programming language.
Video by: Grant
The goal of this video is to provide viewers with basic examples to understand and use while-loops in the C programming language.

592 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question