Comp 374 program 3: Adding pipes to the "little shell" lsh in NT Due: December 6, 2000 (last day of class is Monday, Dec 4) The lsh program is a tiny version of a unix-style shell. I have ported it to NT, so that simple commands are launched with CreateProcess() rather than with fork() and then exec(). Some of the notation is also NTified, although this isn't universal. However, file descriptors *are* renamed as "handles". You are to add support for *pipes*, so that you can execute commands like ls -l |grep Nov output | input I will supply a directory H:\prof_pub\pld\374\utils that contains these actual commands, ls, grep, output, input, and a few more. You also need to add support for having the shell wait for commands to complete. THis is relatively easy; make the appropriate call to _cwait() inside the lsh function waitfor(). As "pid" (process id) you should use the hProcess field of the PROCESS_INFORMATION structure that is set by CreateProcess; this is the structure called piProcInfo. The field dwProcessId does *not* work. Note that hProcess is actually of type HANDLE; you may wish to cast it to int. _cwait() can be used then to wait for the process. Recall that under unix this kind of redirection would be handled as follows: create the pipe fork() Child shell (to become upstream end of pipe): close stdout close(reading-end-of-pipe) dup(writing-end-of-pipe, stdout) (set stdout to be a pipe copy) exec(upstream-command); fork() Child shell that becomes downstream end of pipe: close stdout close writing-end-of-pipe dup(reading-end-of-pipe, stdin) exec(downstream-command) The basic strategy under NT is as follows. A pipe is created with CreatePipe(). Under NT, one file handle is *marked* as standard input, using SetStdHandle(). So you will: Create the pipe set stdout to the writing end of the pipe (actually a copy, below) start the upstream end of the pipe restore stdout set stdin to (a copy of) the reading end of the pipe start the downstream end of the pipe restore stdin Actually, what happens is that the scommand() routine figures out what pipes are needed, and sets parameters srcfd and dstfd to represent the possibly revised stdin and stdout, and then calls invoke, which checks to see which of stdin and stdout need redirection (possibly both) and handles that as above. Note also that the "restore stdout" and "restore stdin" above are being done in the main shell; if you fail to do them, the main shell will not be able to continue! Note that if anything goes wrong between the time you change stdout and the time you change it back again, you cannot print an error message to the "real" stdout (although the real "stderr" should still be available). One last complication: you do *not* want the child process receiving any "extra" handles, eg the shell's original stdin if stdin is being redirected, or the other end of a pipe. (Recall that if any writing ends of the pipe are left open and unaccounted for, then the reading end will never see end-of-file and the process will hang.) To do this, NT allows you to mark a given handle as either "inheritable" or not. Thus, before you launch the new child, you will have to mark as inheritable the handles you want inherited, and unmark any others. Dealing with handle inheritance is logically straightforward, but a pain. You use SECURITY_ATTRIBUTES structures to supply the necessary information. Let sa be of type SECURITY_ATTRIBUTES, and make sa.bInheritHandle = FALSE. Then pass &sa as a parameter at the appropriate place in CreatePipe(). You *must* create a pipe with *un*inheritable handles, by setting sa.bInheritHandle=FALSE in the call to CreatePipe(); otherwise you end up with extra ends of the pipe hanging around and this can lead to the EOF problem. After you create the uninheritable handles, use DuplicateHandle() to create inheritable duplicates for each end, one at a time, and then immediately close the handles no longer needed after you launch the child process. Here is a routine hdup(), which given a handle returns an inheritable duplicate: HANDLE hdup(HANDLE orig) { HANDLE duphandle; BOOL success; success = DuplicateHandle( GetCurrentProcess(), orig, GetCurrentProcess(), &duphandle, 0, TRUE, // inherited! DUPLICATE_SAME_ACCESS ); if (!success) { fprintf(stderr, "duplication of handle %d failed\n", orig); return (HANDLE) 0; } return duphandle; } The program consists of three .c files, cd.c, env.c, and lsh.c, plus lsh.h. You will need all four; only the .c files get added explicitly to your project. You may wish to set UTILDIR to the directory you will be using for these (containing ls, grep, etc). It is set to "H:\\prof_pub\\pld\\374\\utils" currently.