Carleton University COMP 3000A Fall 2019 Assignment 1 Solutions 1. [1] When you run 3000shell normally in a terminal windown on Linux, what is open on file descriptors 0, 1, and 2? How do you know? A: A tty of some sort is open on file descriptors 0,1, and 2, normally one from /dev/pts/ (e.g., /dev/pts/0). You can verify this by doing an ls -l in the fd directory in 3000shell's process /proc entry, /proc//fd where is the process ID of the 3000shell process. (Note tty's are associated with most interactive command lines on Linux.) 2. [1] How could you change run_program() so that it has as few arguments as possible, without defining any new global variables? For the arguments you remove, how could run_program() get those values? A: You can get rid of the path and envp arguments by instead defining them locally and assigning to them with something equivalent to the following char *path; extern char **environ; path = getenv("PATH"); The other arguments need to be passed in as their values cannot be obtained otherwise (without using another data passing mechanism such as shared globals). 3. [1] How can you change 3000shell so that it outputs the return status of every program run in the foreground (i.e., not backgrounded)? Note the return status is the value returned by main(). A: You just have to copy lines 207-217 to just after line 234, i.e. you have run_program() do the same thing after the call to wait() as signal_handler() does. 4. [1] How would the behaviour of 3000shell change if line 241 was removed? A: If the call to dup2() is removed, then we won't properly redirect standard out when a > operator is used. It will open the file but it will not be open on file descriptor 1. Thus the > will do nothing except overwriting the file specified (with an empty file). 5. [1] What do lines 171-175 do? Explain briefly. A: They remove the newline from the program name (read from /proc//comm). Without these lines we'd get extra blank lines when printing out the program names. 6. [1] When is the value set on line 317 used? How can you force this value to be used? A: The default_username value is used when the environment variable USER is not defined. You can cause this to happen by typing the command "unset USER" in bash and then running 3000shell. 7. [2] In line 240, what will be the contents of the file created by creat()? Also, what will be the owner, group, and permissions of the file created? A: The file will contain whatever the execve'd program outputs on standard out. Its permissions will be 666 or'd with the current umask (which is generally 022, so giving you permissions of 644, or read/write for the owner and read only for everyone else). 8. [2] What system calls does plist() generate? What are they for? A: plist generates the following system calls: openat to open the /proc directory and each comm file for each process fstat to get information on /proc (check permissions? Not clear) getdents64 to read the entries in /proc read to read the contents of the comm file for each process write to output the PID and program name for each process close to close each comm file and /proc 9. [2] How could you modify 3000shell so it only calls wait() once, in the signal handler? Specifically, how could it pause until the external command finishes without line 234? A: Instead of doing a wait() on line 234, it can just do the following: foreground_pid = pid; foreground_finished = 0; while (1) { nanosleep(&t, NULL); if (foreground_finished) { break; } } An #include should be added to the top, and t should be declared and assigned as follows at the top of run_program(): struct timespec t; t.tv_sec = 10; t.tv_nsec = 0; foreground_finished and foreground_pid should be declared globally. At line 211 in signal_handler(), add the following: if (pid == foreground_pid) { foreground_pid = 0; foreground_finished = 1; } Note the nanosleep will get interrupted by the sigchld signal, thus the exact length of the sleep isn't important. 10. [2] Implement the "cd" command. A: At line 285 or nearby in prompt_loop(), add the following lines to recognize the cd command: if (!strcmp(args[0], "cd") && nargs > 1) { do_cd(args[1]); continue; } And then implement the following function: void do_cd(char *newdir) { int result; result = chdir(newdir); if (result == -1) { fprintf(stderr, "Could not change directory to %s\n", newdir); } } 11. [3] Implement a "findhere" command that lists files in the current directory that contain the given pattern in the filename. The pattern allow for standard shell "glob" patterns, e.g., findhere book* should list all the files that start with the word book. A: First, recognize the command as before around line 285: if (!strcmp(args[0], "findhere") && nargs > 1) { findhere(args[1]); continue; } And then implement the findhere function: #include void findhere(char *pattern) { DIR *d; struct dirent *e; d = opendir("."); if (d == NULL) { fprintf(stderr, "ERROR: Couldn't open current directory.\n"); } for (e = readdir(d); e != NULL; e = readdir(d)) { if (fnmatch(pattern, e->d_name, 0) == 0) { printf("Found: %s\n", e->d_name); } } closedir(d); } Note you could also use glob() to do this. 12. [3] Implement a MS-DOS style pipe command. Make sure it allows for command-line arguments to be passed to the programs. You only need to support one pipe command at a time. For example, when you type ls | wc the shell should * write the output of ls to a temporary file by redirecting standard output when running ls, and * run wc, redirecting standard input so it reads from the temporary file written to by ls. A: You have to implement standard input redirection in run_program, recognize the pipe operator, and then run the two given commands separately, redirecting standard output for the first and standard input for the second. Change the declaration of run_program() to add a stdin_fn: void run_program(char *args[], int background, char *stdin_fn, char *stdout_fn) Implement support for redirecting standard input in run_program() by adding the following at line 238: if (stdin_fn != NULL) { fd = open(stdin_fn, O_RDONLY); dup2(fd, 0); close(fd); } Add a declaration for foundpipe around line 262: int foundpipe; Replace line 308 with the following: foundpipe = 0; for (i = 1; i < nargs; i++) { if (strcmp(args[i], "|") == 0) { foundpipe = 1; if (nargs <= i ) { fprintf(stderr, "No command after pipe\n"); break; } args[i] = NULL; run_program(args, 0, NULL, "/tmp/3000tmppipe"); run_program(args + i + 1, 0, "/tmp/3000tmppipe", NULL); break; } } if (!foundpipe) { run_program(args, background, NULL, stdout_fn); } Really, you should pick a random temp file rather than the same one every time, but it turns out that is much harder than you'd think to do properly.