COMP 3000 Winter 2020 Assignment 1 Solutions 1. [1] Complete support for redirecting standard input. A: Support for standart input is just like support for standard output, except we use open (for reading) and specify file descriptor 0 rather than 1: *** 3000run.c 2020-01-28 12:00:26.778285601 -0500 --- 3000run-q1.c 2020-01-28 12:14:08.671553186 -0500 *************** *** 55,61 **** exit(-3); } dup2(fd, 1); ! execve(command, argv, environ); fprintf(stderr, "execve of %s failed.\n", command); return(-1); --- 55,70 ---- exit(-3); } dup2(fd, 1); ! ! close(0); ! fd = open(input_fn, O_RDONLY); ! if (fd == -1) { ! fprintf(stderr, "Could not open %s for input.\n", ! output_fn); ! exit(-4); ! } ! dup2(fd, 0); ! execve(command, argv, environ); fprintf(stderr, "execve of %s failed.\n", command); return(-1); 2. [1] What input and output filenames should be given so executed programs receive input and send output to a specific terminal? A: Terminals should have a pseudo-tty of pts/? where ? is a small number. This is true for graphical terminals and ssh connections. (For virtual terminals on the Linux console, they are tty?.) So, to redirect standard input and output to pts/0, specify /dev/pts/0 for the input and output. 3. [1] How does envp[] in main() compare to environ in child()? Be precise. How did you compare them? A: Their values are exactly the same. You can verify this by running 3000run under gdb and setting breakpoints at main and child (after "set follow-fork-mode child") and print'ing envp (in main) and environ (in child). 4. [5] Which library function calls in 3000run.c generate one or more system calls? List each library call and what system call (if any) it produces (once main() starts and until main() exits). Note the library calls are as follows: close, creat, dup2, execve, exit, fork, fprintf, printf, puts, snprintf, wait A: close(): close creat(): creat dup2(): dup2 execve(): execve exit(): exit_group fork(): clone fprintf(): write printf(): write puts(): fstat, brk, write snprintf(): NONE wait(): wait4 I got these by running 3000run under gdb, setting a breakpoint on main, and then "catch syscall" after that to see the syscalls, with bt to see where the call came from (and typing c to continue or n or s to step line by line). Also changing follow-fork-mode helped with seeing what the child did. For snprintf I noted that there were no syscalls between the wait() and puts() calls. It is possible that fstat and brk also showed up for printf() and fprintf(). (CHECK ON Ubuntu 18.04!) 5. [2] Add support for running binaries without specifying the full pathname. Implement only using code and methods shown in class (lecture or tutorial). Other solutions will not be accepted. A: Copy the find_binary() and find_env() functions from 3000shell.c and necessary declarations (BUFFER_SIZE, default_path, path, bin_fn). Use find_env() to get the path and find_binary() to search for command in the directories listed in the path. You could also use getenv() to get the path, but you need to either copy or implement your own path searching logic. These calls should happen in child() before execve, and the execve should use the result of find_binary (which is in the bin_fn string buffer passed to it). 6. [2] Add support for reporting when the child terminates with a signal and the signal number that caused the termination. (Note that processes with custom signal handlers may terminate normally using exit, so be sure to test with a program that actually terminates with a signal.) A: Following the man page for wait(), we just need to add the following to the parent at line 28 in the original code (replacing the close brace that is there): } else if (WIFSIGNALED(result_status)) { count += snprintf(buf + count, bufsize, " with signal %d", WTERMSIG(result_status)); } This code checks whether the process terminated with a signal and reports the signal that terminated the process. 7. [2] Add support for command line arguments. For example, to run ls -l /tmp with input from A and output to B, you would type "./3000run /bin/ls A B -l /tmp". A: This is straightforward once you see that you can reuse the original argv in the child, just skipping the name of the binary, the command, and the input file and replacing the output file with the name of the command to be run. Specifically, add argv[] as the last parameter to child() and remove any declarations or modifications for argv[] in child (it should just be used in the execve call). Then, in main change the child call portion as follows: argv[3] = command; return child(command, input_fn, output_fn, argv + 3); Alternately you can construct a new argv[] from scratch, allocating the right size and copying each argument, but that is more work. 8. [6] Change the parent process so that it only allows the child to run for three seconds before sending it a SIGTERM, as follows: (a) [1] Replace the call to wait in parent() with a sleep for three seconds. (b) [2] Calls wait in a signal handler for SIGCHLD. This signal handler stores result_status and result_pid in global variables. It also sets a global flag child_exited to 1 (this flag defaults to 0). (c) [2] If the child has not exited after the sleep (as indicated by child_exited), send the child a SIGTERM signal, then sleep for another three seconds. (d) [1] The parent terminates as before, conditionally on result_pid (but it is now global, not local). A: Solution to this is straightforward if you copy the signal handling code from 3000shell. We set up the signal handler in main (so it is set up before the call to fork), change the signal handler to store the results of wait in global variables and sets child_exited to 1 when a child has terminated, and replace the wait with sleep in the parent. You should have noticed that the calls to sleep get terminated early, such that short-lived processes return immediately and longer-lived processes terminate after three seconds. This is because any received signal (including SIGCHLD) cancels any current sleep.