Search code examples
cshellunixexecvgetenv

Writing own Unix shell in C - Problems with PATH and execv


I'm writing my own shell in C. It needs to be able to display the users current directory, execute commands based on the full path (must use execv), and allow the user to change the directory with cd.

This IS homework. The teacher only gave us a basic primer on C and a very brief skeleton on how the program should work. Since I'm not one to give up easily I've been researching how to do this for three days, but now I'm stumped.

This is what I have so far:

  • Displays the user's username, computername, and current directory (defaults to home directory).
  • Prompts the user for input, and gets the input
  • Splits the user's input by " " into an array of arguments
  • Splits the environment variable PATH by ":" into an array of tokens

I'm not sure how to proceed from here. I know I've got to use the execv command but in my research on google I haven't really found an example I understand. For instance, if the command is bin/ls, how does execv know the display all files/folders from the home directory? How do I tell the system I changed the directory?

I've been using this site a lot which has been helpful: http://linuxgazette.net/111/ramankutty.html but again, I'm stumped.

Thanks for your help. Let me know if I should post some of my existing code, I'm wasn't sure if it was necessary though.


Solution

  • To implement the cd command you just need the system call chdir.

    #include <unistd.h>
    
    int chdir(
        const char *path /* the path name */
    );
    

    So you can just call something like:

    int ret1 = chdir("../foo/bar");
    

    The return value of chdir is 0 when it was possible to change into that directory and -1 if an error occurred. For the error you should consolidate the man page.

    The current directory can be checked by any program, so if you execute ls without any arguments, then ls checks in which directory it is running and uses this directory as the only argument. This is a feature of ls and not of the execv call.

    For the second part.

    #include <unistd.h>
    int execv(
         const char *path, /* programm path*/
         char *const argv[]/* argument vector*/
    );
    

    execv executes a executable file at the given path and with the arguments given in argv. So if you want to execute /bin/ls ../foo /bar, you need something similar to

    char *cmd_str = "/bin/ls";
    char *argv[] = {cmd_str, "../foo", "/bar", NULL };
    if (execv(cmd_str, argv) == -1 ){
        /* an error occurred */
    }
    

    The error returned by execv is -1. If you want to know why it did not execute the comand check out the man pages.

    The NULL in char *argv[] = {cmd_str, "../foo", "/bar", NULL }; is there to indicate that there are no other arguments after the NULL.

    The third part. Unix based system normally treat commands with a / in it as commands that can be executed directly. Which means you first check if there is a slash in the given command string.

    int ret_value;
    if (strchr(cmd_str, '/')
        if (execv(cmd_str, argv) == -1 ){
            /* an error occurred */
        }
    

    If there is no slash then you need to go through all directories in PATH and check if you can execute the command. So the given command is ls ../foo /bar and lets assume the value of PATH is ".:/sbin:/bin:/usr/bin". We would then try to first execute ./ls ../foo /bar then /usr/bin/ls ../foo /bar and at last /bin/ls ../foo /bar.

    Hope this helps.