Thread: Qustion about writing a shell

  1. #1
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13

    Qustion about writing a shell

    Hello! This is my first post and I have a question about some code I am working with. This is working code for a basic Linux shell. It works fine for running external commands,but I am wondering how I can add built in functions,such as cd and the like. Any help is greatly appreciated.

    Code:
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<signal.h>
    #include<string.h>
    #include<sys/wait.h>
    #include<sys/types.h>
    #include<ctype.h>
    #include<errno.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    #define NUM1 10
    #define NUM2 100
    #define NUM3 256
    
    extern int errno;
    
    typedef void(*sighandler_t)(int);
    
    static char buf[NUM3];
    static char *my_argv[NUM2],*my_envp[NUM2];
    static char *search_path[NUM1];
    
    void show_prompt() 
    {
    	getcwd(buf,sizeof buf);	
    	printf("%s:* ",buf);
    }
    
    void handle_signal(int signo) /
    {
    	printf("\n");
    	show_prompt();
    	fflush(stdout);
    }
    
    void fill_argv(char *tmp_argv) 
    	char *foo = tmp_argv;
    	int index = 0;
    	char ret[100];
    	bzero(ret,100);
    	while(*foo != '\0')
    	{
    		if(index == 10)
    			break;
    		if(*foo == ' ')
    		{
    		if(my_argv[index] == NULL)
    		my_argv[index]=(char*)malloc(sizeof(char)*strlen(ret)+1);
    		else
    		{
    			bzero(my_argv[index],strlen(my_argv[index]));
    		}
    		strncpy(my_argv[index],ret,strlen(ret));
    		strncat(my_argv[index],"\0",1);
    		bzero(ret,100);
    		index++;
    		}
    		else
    		{
    			strncat(ret,foo,1);
    		}
    		foo++;
    	}
    	my_argv[index]=(char*)malloc(sizeof(char)*strlen(ret)+1);
    	strncpy(my_argv[index],ret,strlen(ret));
    	strncat(my_argv[index],"\0",1);
    
    }
    
    void copy_envp(char **envp)
    {
    	int index = 0;
    	for(;envp[index]!=NULL;index++)
    	{
    	my_envp[index]=(char*)malloc(sizeof(char)*(strlen(envp[index])+1));
    	memcpy(my_envp[index],envp[index],strlen(envp[index]));
    	}
    }
    
    void get_path_string(char **tmp_envp,char *bin_path)
    {
    	int count = 0;
    	char *tmp;
    	while(1)
    	{
    		tmp = strstr(tmp_envp[count], "PATH");
    		if(tmp == NULL)
    		{
    			count++;
    		}
    		else
    		{
    			break;
    		}
    	}
    	strncpy(bin_path,tmp,strlen(tmp));
    }
    
    void insert_path_str_to_search(char *path_str)
    {
    	int index = 0;
    	char *tmp = path_str;
    	char ret[100];
    	
    	while(*tmp != '=')
    		tmp++;
    	tmp++;
    
    	while(*tmp != '\0')
    	{
    		if(*tmp==':')
    		{
    			strncat(ret,"/",1);
    			search_path[index]=(char*)malloc(sizeof(char)*(strlen(ret)+1));
    			strncat(search_path[index],ret,strlen(ret));
    			strncat(search_path[index],"\0",1);
    			index++;
    			bzero(ret,100);
    		}
    		else
    		{
    			strncat(ret,tmp,1);
    		}
    		tmp++;
    	}
    }
    
    int attach_path(char *cmd)
    {
    	char ret[100];
    	int index;
    	int fd;
    	bzero(ret,100);
    
    	for(index=0;search_path[index]!=NULL;index++)
    	{
    		strcpy(ret,search_path[index]);
    		strncat(ret,cmd,strlen(cmd));
    		if((fd=open(ret,O_RDONLY))>0)
    		{
    			strncpy(cmd,ret,strlen(ret));
    			close(fd);
    			return(0);
    		}
    	}
    	return(0);
    }
    
    void call_execve(char *cmd)
    {
    	int i;
    	printf("\n");
    	
    	if(fork() == 0)
    	{
    		i = execve(cmd,my_argv,my_envp);
    		if(i<0)
    		{
    			printf("%s:command not found\n",cmd);
    			exit(1);
    		}
    	}
    	else
    	{
    		wait(NULL);
    	}
    }
    
    void free_argv()
    {
    	int index;
    	
    	for(index=0;my_argv[index]!=NULL;index++)
    	{
    		bzero(my_argv[index],strlen(my_argv[index])+1);
    		my_argv[index]=NULL;
    		free(my_argv[index]);
    	}
    }
    
    
    int main(int argc, char *argv[], char *envp[])
    {
    	char c;
    	int i,fd;
    	char *tmp=(char*)malloc(sizeof(char)*100);
    	char *path_str=(char*)malloc(sizeof(char)*256);
    	char *cmd=(char*)malloc(sizeof(char)*100);
    	signal(SIGINT,SIG_IGN);
    	signal(SIGINT,handle_signal);
    	
    	copy_envp(envp);
    	get_path_string(my_envp,path_str);
    	insert_path_str_to_search(path_str);
    
    	if(fork() == 0)
    	{
    		execve("/usr/bin/clear",argv,my_envp);
    		exit(1);
    	}
    	else
    	{
    		wait(NULL);
    	}
    	show_prompt();
    	fflush(stdout);
    
    	while(c!=EOF)
    	{
    		c=getchar();
    		switch(c){
    			case '\n':if(tmp[0]=='\0'){
    				show_prompt();
    			}else{
    				fill_argv(tmp);
    				strncpy(cmd,my_argv[0],strlen(my_argv[0]));
    				strncat(cmd,"\0",1);
    				if(index(cmd,'/')==NULL){
    				  if(attach_path(cmd)==0){
    				    call_execve(cmd);
    				   }else{
    				    printf("%s:command not found\n",cmd);
    				   }
    				}else{
    				  if((fd=open(cmd,O_RDONLY))>0){
    				    close(fd);
    				    call_execve(cmd);
    				  
    				}else{
    					printf("%s:command not found\n",cmd);
    					}
    				}
    			free_argv();
    			printf("\n");
    			show_prompt();
    			bzero(cmd,100);
    			}
    		bzero(tmp,100);
    		break;
    		default:strncat(tmp,&c,1);
    		}
    	}   
    	
    	free(tmp);
    	free(path_str);
    	for(i=0;my_envp[i]!=NULL;i++)
    		free(my_envp[i]);
    	for(i=0;i<10;i++)
    		free(search_path[i]);
    	printf("\n");
    	return(0);
    }

  2. #2
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    I promise I will help if you get stuck, but I'd like to point out that you learn much more from doing it yourself.

    What do you think you need to do? How would you know if it's an internal command or an external command?

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  3. #3
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Here's what I was thinking. The function fill_argv() takes the user input and uses it to fill the internal my_argv[] array, as opposed to using main's argv[] array. Is it possible to test argv[0] to find out if that value matches the name of an interally defined function and execute it,and if the test fails,then the shell knows it is executing an external command. That is what I have been looking at doing,just not quite sure how to do it.

  4. #4
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by bchaffin72 View Post
    Here's what I was thinking. The function fill_argv() takes the user input and uses it to fill the internal my_argv[] array, as opposed to using main's argv[] array. Is it possible to test argv[0] to find out if that value matches the name of an interally defined function and execute it,and if the test fails,then the shell knows it is executing an external command. That is what I have been looking at doing,just not quite sure how to do it.
    That is on the right track. Of course, function names are removed by the compiler, so you have to come up with another method of connecting the command typed in by the user with the function of the internal command.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  5. #5
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    In order to make the internal commands behave the same as external ones (in terms of being able to pipe/redirect them and use process control), you should fork() first (as if you were about to launch an external command) before checking if the command is internal. If it is, just execute it by calling the internal function which implements the command, then call exit().
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  6. #6
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by brewbuck View Post
    In order to make the internal commands behave the same as external ones (in terms of being able to pipe/redirect them and use process control), you should fork() first (as if you were about to launch an external command) before checking if the command is internal. If it is, just execute it by calling the internal function which implements the command, then call exit().
    That is a very good point (and one I hadn't thought of).

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  7. #7
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Thanks guys. I'll try tackling it from that angle, and if I really ball things up,I'll let you know I have an idea what to do, I just need to figure out how the actual implememtation will work.

  8. #8
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Not all internal commands should fork. Especially, those that modify the current environment (like cd, or set if you have such a command) most definitely must not fork.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  9. #9
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by CornedBee View Post
    Not all internal commands should fork. Especially, those that modify the current environment (like cd, or set if you have such a command) most definitely must not fork.
    Of course not, but the user expects to be able to do:

    Code:
    $ echo foo > blah.txt
    $ history > hist.txt
    etc. (echo is usually a builtin)
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #10
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Thanks all. Between your advice and some various code examples,I'm getting a handle of what to do. I'm going to bury my head in some code,but I'll try to poke it up and look around again soon.

  11. #11
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Thanks again all. I got it. Now I can work on expanding it. I'll keep you posted.

  12. #12
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Here is the current version of the code, for those who are curious. It's not what you would call feature rich,but it works. Any comments,suggestions, or constructive criticisms are welcome. Thanks again for the help and suggestions that got me this far.

    Code:
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<signal.h>
    #include<string.h>
    #include<sys/wait.h>
    #include<sys/types.h>
    #include<ctype.h>
    #include<errno.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    
    
    extern int errno;
    
    typedef void(*sighandler_t)(int);
    
    static char buf[100];
    static char *my_argv[100],*my_envp[100];
    
    void show_prompt()
    {
    	getcwd(buf,sizeof buf);	
    	printf("%s* ",buf);
    }
    	
    void handle_signal(int signo)
    {
    	printf("\n");
    	show_prompt();
    	fflush(stdout);
    }
    
    int cd()
    {
    	if(strchr(my_argv[1],'~'))
    		chdir(getenv("HOME"));
    	else
    		chdir(my_argv[1]);
    	return(0);
    }
    
    int run_pipe()
    {
    	puts("Sorry,this shell does not support pipes.");
    	return(0);
    }
    
    int redirect()
    {
    	puts("Sorry,this shell does not support redirection.");
    	return(0);
    }
    
    void fill_argv(char *tmp_argv)
    {
    	char *tmp = tmp_argv;
    	int index = 0;
    	char ret[100];
    	bzero(ret,100);
    	while(*tmp != '\0')
    	{
    		if(index == 30)
    			break;
    		if(*tmp == ' ')
    		{
    		if(my_argv[index] == NULL)
    		my_argv[index]=(char*)malloc(sizeof(char)*strlen(ret)+1);
    		else
    		{
    			bzero(my_argv[index],strlen(my_argv[index]));
    		}
    		strncpy(my_argv[index],ret,strlen(ret));
    		strncat(my_argv[index],"\0",1);
    		bzero(ret,100);
    		index++;
    		}
    		else
    		{
    			strncat(ret,tmp,1);
    		}
    		tmp++;
    	}
    	my_argv[index]=(char*)malloc(sizeof(char)*strlen(ret)+1);
    	strncpy(my_argv[index],ret,strlen(ret));
    	strncat(my_argv[index],"\0",1);
    
    }
    
    void copy_envp(char **envp)
    {
    	int index = 0;
    	for(;envp[index]!=NULL;index++)
    	{
    	my_envp[index]=(char*)malloc(sizeof(char)*(strlen(envp[index])+1));
    	memcpy(my_envp[index],envp[index],strlen(envp[index]));
    	}
    }
    
    void call_execvp()
    {
    	int i;
    	
    	if(fork() == 0)
    	{
    		i = execvp(my_argv[0],&my_argv[0]);
    		if(i<0)
    		{
    			printf("%s:command not found\n",my_argv[0]);
    			exit(1);
    		}
    	}
    	else
    	{
    		wait(NULL);
    	}
    }
    
    void free_argv()
    {
    	int index;
    	
    	for(index=0;my_argv[index]!=NULL;index++)
    	{
    		bzero(my_argv[index],strlen(my_argv[index])+1);
    		my_argv[index]=NULL;
    		free(my_argv[index]);
    	}
    }
    
    
    int main(int argc, char *argv[], char *envp[])
    {
    	char c;
    	int i;
    	char *tmp=(char*)malloc(sizeof(char)*100);
    
    	signal(SIGINT,SIG_IGN);
    	signal(SIGINT,handle_signal);
    	
    	copy_envp(envp);
    
    	show_prompt();
    	fflush(stdout);
    
    	while(c!=EOF)
    	{
    		c=getchar();
    		switch(c){
    			case '\n':if(tmp[0]=='\0'){
    				show_prompt();
    			}else{
    				fill_argv(tmp);
    				if(strchr(tmp,'|'))
    				{
    					run_pipe();
    				}
    				else if(strchr(tmp,'>') || strchr(tmp,'<'))
    				{
    					redirect();
    				}
    				else if(strcmp(my_argv[0],"cd")==0)
    				{
    					/*chdir(my_argv[1]);*/
    					cd();
    				}
    				else if(strcmp(my_argv[0],"version") == 0)
    				{
    					puts("Version 0.5");
    				}
    				else if(strcmp(my_argv[0],"exit")==0)
    				{
    					exit(0);
    				}
    				else
    				{
    					call_execvp();
    				}
    			free_argv();
    			show_prompt();
    			}
    		bzero(tmp,100);
    		break;
    		default:strncat(tmp,&c,1);
    		}
    	}   
    	
    	free(tmp);
    	for(i=0;my_envp[i]!=NULL;i++)
    		free(my_envp[i]);
    	printf("\n");
    	return(0);
    }

  13. #13
    Hurry Slowly vart's Avatar
    Join Date
    Oct 2006
    Location
    Rishon LeZion, Israel
    Posts
    6,788
    Code:
    while(c!=EOF)
    	{
    		c=getchar();
    1. c is not initialized
    2. you should check condition after call to getchar before processing with the loop
    3. c should be int to distinguish EOF return value from regular character

    Correct version will be
    Code:
    int c;
    while((c = getchar()) != EOF)
    {
    All problems in computer science can be solved by another level of indirection,
    except for the problem of too many layers of indirection.
    – David J. Wheeler

  14. #14
    Registered User bchaffin72's Avatar
    Join Date
    Mar 2009
    Location
    Ontario
    Posts
    13
    Thanks. Moving getchar() inside the while statement works fine,but changing c to int produces a compiler warning, so I'll leave that as is for now.

  15. #15
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by bchaffin72 View Post
    Thanks. Moving getchar() inside the while statement works fine,but changing c to int produces a compiler warning, so I'll leave that as is for now.
    Changing c to int is the correct thing to do. What is the compiler warning?
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Folding@Home Cboard team?
    By jverkoey in forum A Brief History of Cprogramming.com
    Replies: 398
    Last Post: 10-11-2005, 08:44 AM
  2. help! fifo read problem
    By judoman in forum C Programming
    Replies: 1
    Last Post: 08-16-2004, 09:19 AM
  3. What shell is more powerful?
    By xddxogm3 in forum Tech Board
    Replies: 10
    Last Post: 07-24-2004, 10:47 PM
  4. Writing A Dos Shell AkA Win 3.11 Mark 2 :p
    By Arius Myst in forum A Brief History of Cprogramming.com
    Replies: 5
    Last Post: 06-22-2002, 04:45 PM