Question

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

Asked by: bvjens31

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;
}

                                  
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:

Select allOpen in new window

This Question has been solved and asker verified All Experts Exchange premium technology solutions are available to subscription members.

Subscribe now for full access to Experts Exchange and get

Instant Access to this Solution

  • Plus...
  • 30 Day FREE access, no risk, no obligation
  • Collaborate with the world's top tech experts
  • Unlimited access to our exclusive solution database
  • Never be left without tech help again

Subscribe Now

Asked On
2008-02-11 at 15:43:07ID23154763
Tags

C

,

Segmentation fault (core dumped)

Topics

C Programming Language

,

Linux Programming

,

Unix Systems Programming

Participating Experts
3
Points
400
Comments
19

Trusted by hundreds of thousands everyday for fast, accurate and reliable tech support.

  • "The time we save is the biggest benefit of Experts Exchange to Warner Bros. What could take multiple guys 2 hours or more each to find is accessed in around 15 minutes on Experts Exchange." Mike Kapnisakis, Warner Bros.
  • "Our team likes having a resource that is more secure than just using Google and most experts using this service really know their stuff. It's nice to look here first versus using Google." Dayna Sellner, Lockheed Martin
  • "Anytime that I've been stumped with a problem, 9 out of 10 times Experts Exchange has either the accepted solution or an open discussion of the potential solution to the problem." Kenny Red, eBay Inc.

See what Experts Exchange can do for you.

Got a question?

We've got the answer.

Experts Exchange has been collecting answers to technology questions since 1996…3 million and counting! If you have a question, chances are we already have your answer.

Screenshot of Experts Exchange Knowledgebase

Need individual assistance?

Our experts are ready to help.

If you can't find the exact answer you're looking for, ask our exclusive community of 50,000 experts. You’ll get a personalized answer from a trusted professional.

Screenshot of Experts Exchange Knowledgebase

Want to learn from the best?

Read articles from industry experts.

Thousands of free tech tips, tricks, how-to’s and tutorials are available in our peer reviewed articles section. See for yourself how smart our experts are, no login required.

Screenshot of an Article

Working on a long term project?

Store your work and research.

Save solutions to your questions, answers you’ve discovered through searching plus helpful articles in your personal knowledgebase for easy future access.

Screenshot of Experts Exchange Knowledgebase

Access the answers to your technology questions today.

Subscribe Now

30-day free trial. Register in 60 seconds.

What Makes Experts Exchange Unique?

Members of the expert community talk about why the experience at Experts Exchange is different than what you will find anywhere else.

Trusted by the world's most respected brands.

image of each brand's logo

Faithfully serving IT professionals since 1996.

Experts Exchange Logo

Try it out and discover for yourself.

Subscribe Now

30-day free trial. Register in 60 seconds.

Related Solutions

  1. Shell
    How do I pause the execution of my program durring a shell() function?
  2. shell?
    1) I was trying to compile the source of a program [ make -f unix/Makefile generic]on one of my machine, I was prompted with the error "Couldn't load shell". I am the root. 2) How can I change from one shell to another? korn to C shell or to borne 3) What is this ...
  3. fork and execl
    Hi, Does anyone have any idea how to call gmake clean and gmake using fork and execl? Instead of having the following in my C program, I would like to use fork a child process and get the child process to do it system("gmake clean > dev/null "); system("g...

Free Tech Articles

  1. WARNING: 5 Reasons why you should NEVER fix a computer for free.
    It is in our nature to love the puzzle. We are obsessed. The lot of us. We love puzzles. We love the challenge. We thrive on finding the answer. We hate disarray. It bothers us deep in our soul. W...
  2. SCCM OSD Basic troubleshooting
    SCCM 2007 OSD is a fantastic way to deploy operating systems, however, like most things SCCM issues can sometimes be difficult to resolve due to the sheer volume of logs to sift through and the dispe...
  3. Migrate Small Business Server 2003 to Exchange 2010 and Windows 2008 R2
    This guide is intended to provide step by step instructions on how to migrate from Small Business Server 2003 to Windows 2008 R2 with Exchange 2010. For this migration to work you will need the fo...
  4. Create a Win7 Gadget
    This article shows you how to create a simple "Gadget" -- a sort of mini-application supported by Windows 7 and Vista. Gadgets can be dropped anywhere on the desktop to provide instant information, ...
  5. Outlook continually prompting for username and password
    There have been a lot of questions recently regarding Outlook prompting for a username and password whilst using Exchange 2007. There are a few reasons why this would happen and I will try to cover t...
  6. Backup Exchange 2010 Information Store using Windows Backup
    There seems to be quite a lot of confusion around the ability to backup Exchange 2010 using the built in Windows Backup feature. This stems from the omission of this feature prior to Exchange 2007 s...

Cloud Class Webinars

  1. Avoiding Bugs in Microsoft Access
    Alison Balter takes and in-depth look at avoiding bugs in Access. In this webinar you will learn about using the immediate window to debug your applications, invoking the debugger, using breakpoints to troubleshoot, stepping through code, setting the next statement to execute, ...
  2. Top 10 Best New Features in Visio 2010
    Scott Helmers gives live demonstrations of the top 10 new features in Visio 2010. This webinar will teach you how to create compelling diagrams by adding shapes to the page with a single click, linking the shapes in a diagram to data in Excel (or SQL Server, or SharePoint), ...
  3. IT Consultant Business Secrets Revealed
    Michael Munger, Experts Exchange tech pro and IT consultant, pulls back the curtain on his very successful businesses and answers question on every IT consultant and business owner should know about. He shares secrets on what he did to solve the 5 most common problems in IT, ...
  4. Disaster Recovery and Business Continuity
    Quest CTO, Mike Billon, gives an overview of the steps involved in building a dunamic disaster recovery plan. Through case studies and an examination of software/hardware tooles for monitoring and testing, you'll gain a better understandin of where you are, where you want ...
  5. Organize Your Visio Diagrams with Containers and Lists
    Scott Helmers uses cross functional flowcharts, wireframe diagrams, data graphic legends and seating charts to teach you: how to ustilize all three new structured diagram components in Visio 2010, the best practices for organizeing shapes in previous version of Visio, how to organize ...
  6. How to Us Objects, Properties, Events and Methods in Microsoft Access
    Alison Dalter gives an in-depbth look at objects, properties, events and methods in Microsoft Access. In this webinar you will learn about using the object browser, referring to objects, working with properties and methods, working with object variables, understanding the ...

Join the Community

Give a Little. Get a Lot.

Join the community of experts here and help other tech pros by answering question in your area of expertise. You can earn FREE access to all Experts Exchange's premium features and resources.

Join the Community

Answers

 

by: ozoPosted on 2008-02-11 at 15:55:04ID: 20871493

I don't see k initialized before doing     dirs[k] = malloc(strlen(tmp)+1);

 

by: bvjens31Posted on 2008-02-11 at 16:32:33ID: 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.

 

by: ozoPosted on 2008-02-11 at 17:00:30ID: 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

 

by: bvjens31Posted on 2008-02-11 at 17:18:50ID: 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?

 

by: ozoPosted on 2008-02-11 at 17:23:55ID: 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,":")

 

by: bvjens31Posted on 2008-02-11 at 17:26:56ID: 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())

 

by: ozoPosted on 2008-02-11 at 17:39:19ID: 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

 

by: bvjens31Posted on 2008-02-11 at 17:41:44ID: 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!

 

by: bvjens31Posted on 2008-02-11 at 17:46:49ID: 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)

 

by: bvjens31Posted on 2008-02-11 at 20:04:08ID: 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() */
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:

Select allOpen in new window

 

by: Infinity08Posted on 2008-02-11 at 22:46:14ID: 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.

 

by: duncan_roePosted on 2008-02-12 at 01:37:03ID: 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

 

by: duncan_roePosted on 2008-02-12 at 01:56:50ID: 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())"); */
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:

Select allOpen in new window

 

by: duncan_roePosted on 2008-02-12 at 02:42:11ID: 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() */
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:

Select allOpen in new window

 

by: duncan_roePosted on 2008-02-12 at 03:52:06ID: 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
                                              
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:

Select allOpen in new window

 

by: duncan_roePosted on 2009-10-23 at 23:16:51ID: 25651036

I suggest split ozo http:#20871962, Infinity08 http:#20872885 and duncan_roe http:#20874021

we all helped

 

by: Infinity08Posted on 2009-10-24 at 01:42:24ID: 25651309

That sounds fair. Seconded :)

20120131-EE-VQP-002

3 Ways to Join

30-Day Free Trial

The Experts

98% positive feedback on 31,087 answers since March 2000. angeliii is a Microsoft Most Valuable Professional for his work with MS SQL Server & Develoment.

He has also proven his knowledge of Visual Basic Programming, PHP Scripting and Oracle Databases.

The Experts

97% positive feedback on 10,752 answers since July 2000. lrmoore has more than 18 years experience in the networking industry.

The six-time Mircosoft MVPs specialties include firewalls, virtual private networking, and network management.

Testimonials

"...and excellent source for support... Kind of like having your very own IT dept." Electriciansnet

Testimonials

"I was apprehensive at signing up at first. However... it has already made my life as an IT administrator much easier." JaCrews

Testimonials

"WOW! You guys have great, active, and knowledgeable people on here." moore50

Business Clients

Business Clients

In the Press

"If you’ve got a question... Experts Exchange can supply an answer.”

In the Press

"...an invaluable aid for both IT professionals and those who require tech support."

In the Press

"where IT professionals provide quick answers on just about any topic"

Business Account Plans

Loading Advertisement...