4

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.

user1287523
  • 967
  • 3
  • 14
  • 31
  • The chdir() function is used to change the current working directory. – Vaughn Cato Sep 29 '12 at 05:07
  • Yes, the rubric explicitly states not to use execvp. @VaughnCato thanks. So am I to call the execv command like `exevc(args[0], args)` ? If the user types bin/ls instead of /bin/ls am I to append it to the working directory? – user1287523 Sep 29 '12 at 05:10
  • 2
    If you type bin/ls, I would expect "bin/ls" to be passed as the first argument to execv. Only in the special case where there are no slashes in the command would you try the path. – Vaughn Cato Sep 29 '12 at 05:20
  • @VaughnCato okay, that makes sense. Just a couple more questions. Assuming the user just types for example "ls", am I supposed to search through the PATH directories to find the program's path? Also, I've implementd chdir but it doesn't seem to be working. How do I use chdir with execv, do I need to put the new directory in the arguments somehow? Thanks. – user1287523 Sep 29 '12 at 05:30
  • 2
    if the user types "ls", you should scan through the PATH directories to find one which includes an executable file called 'ls'. You don't try the current directory (unless PATH contains "." as a directory, which is legal but discouraged; you don't have to worry about that, though, because the file system understands the meaning of directory ".". So chdir is irrelevant here. But, when you execv the program, it will start in the current working directory, so in that sense the chdir is automatic. – rici Sep 29 '12 at 05:34
  • 1
    Yes, if the user types "ls" and your PATH is /bin:/usr/bin, then you would first try /bin/ls, and if that doesn't work, try /usr/bin/ls. – Vaughn Cato Sep 29 '12 at 05:34
  • 2
    If the user types "cd /home/john", then you are going to execute chdir("/home/john"), and that's about it. – Vaughn Cato Sep 29 '12 at 05:38
  • @VaughnCato thank you so much this is so helpful. I'm really becoming to understand this now. So, three scenarios: user types ls, search for ls in PATH directories and run it. User types /bin/ls, run it standalone. The only thing that's bothering me is if the user types bin/ls, are you sure it's not supposed to be appended to the current directory? If I type bin/ls in a standard console it doesn't work, but /bin/ls does. – user1287523 Sep 29 '12 at 05:42
  • 1
    You don't need to check whether each directory contains the executable; you simply create the executable name as if the directory did contain the executable and try to execute it. If the `execv()` returns, you know there was a problem, so you try the next entry in the PATH instead, only stopping when there are no directories left to try. This is a variation on the theme of 'easier to ask forgiveness than permission'. All else apart, working out whether someone can execute a command is tough — doubly so if there are ACLs in play. – Jonathan Leffler Sep 29 '12 at 06:15
  • If the user types bin/ls, you don't need to append it to the current directory. When you execute execv("bin/ls",args), the operating system will automatically assume you are talking about bin/ls relative to the current directory, so if your current directory is /home/john, then it will look for /home/john/bin/ls automatically. Unless you have an ls command in /home/john/bin/ls, it will then fail, just like it does in a regular shell. – Vaughn Cato Sep 29 '12 at 14:17

4 Answers4

1

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?

Every process has a current working directory, which may be modified using chdir. Child processes will inherit the working directory from their parent. So in general your shell will manage its current working directory in response to cd commands entered by the user. When a command is entered which is not a builtin, then you'll fork to create a child process and then call execv there to execute the binary.

If you ant to take the PATH into account for program names that contain no directory part, then you should try all possible combinations of a PATH element and the program name. You can either check whether the named file does exist, or simply try to execute it and continue with the next if that fails. If all execv calls failed, you will have to call _exit in order to terminate the child process.

Note that most shells will treat any command which contains a / as a path which gets passed to execv directly. If the path does not start with a /, then it is a relative path, and the operating system will resolve it with respect to the current working directory. In other words, the bin/ls from your example would refer to the ls binary in the bin directory which is a subdirectory of the current working directory. Only commands which do not contain any / at all are either interpreted as a builtin command (like cd) or the name of some binary located on the PATH.

The first argument to execv is the path as you computed it. The first element of the argv list traditionally equals the name as it was enetered, i.e. without an added PATH directory. After that first argument, any additional command line parameters are passed, followed by a NULL to terminate the list.

MvG
  • 57,380
  • 22
  • 148
  • 276
1

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.

Raphael Ahrens
  • 953
  • 15
  • 29
1

I think the issue is that you believe the shell is responsible for doing the work of ls. ls isn't really a "part" of the shell (in this case at least). The shell executes a program called ls. Most of the comments seem to be explaining how to find ls, but I don't believe that's what you're confused about.

You should consider carefully what the point of the shell is before you write it. The comments have indirectly pointed out the fact that the shell "simply" has to "call" programs like ls and chdir, not perform their tasks.

bubchi89
  • 11
  • 1
  • [`ls`](http://linux.die.net/man/1/ls) is a program, but [`chdir`](http://linux.die.net/man/3/chdir) is a library function, encapsulating a system call. Don't mix these concepts. – MvG Sep 29 '12 at 12:29
  • Yes I realized this fact (well, sort of. I didn't remember the details, just that `chdir` is "special") when I thought about it later, but I think fundamentally his confusion is over the purpose of a shell rather than how to write it. – bubchi89 Sep 29 '12 at 20:57
1

ls knows by itself that, if not given any arguments, it is supposed to list files in the current working directory as returned by getcwd

idefixs
  • 712
  • 1
  • 4
  • 14