Exercise 3: exec() Leaves Open File Descriptors Untouched — Possible Solution ==================================================================== THE TEST ------------------------------ fd = syscalls.invoke(proc.pid, 'open', '/still_open') syscalls.invoke(proc.pid, 'write', fd, 0, b'SURVIVES') syscalls.invoke(proc.pid, 'exec', [('COMPLETELY_NEW_PROGRAM',)]) # does fd still work? syscalls.invoke(proc.pid, 'read', fd, 0, len(b'SURVIVES')) RESULT ------------------------------ after exec(), the process's own open fds are still: {0: '/still_open'} reading through the SAME fd after exec() still works: b'SURVIVES' The file descriptor opened before exec() still works perfectly afterward, reading back the exact data written before the program was replaced. WHY THE FD SURVIVES ------------------------------ _do_exec()'s own entire implementation is: def _do_exec(self, pid, new_program): self.programs[pid] = list(new_program) This touches exactly one thing: self.programs[pid], the process's own scheduled instruction list. It never reads from, writes to, or clears self.open_fds in any way -- so whatever was in that process's own file descriptor table before exec() is still sitting there, completely unmodified, immediately afterward. WHAT THIS DOES AND DOESN'T MODEL ABOUT REAL exec() ------------------------------ A real OS's own exec() genuinely DOES preserve open file descriptors across the call, by design -- this is exactly why, for example, a shell can open a file for redirected output, then exec() the actual command, and have that command inherit and use the already-open file without any extra work. So this implementation's own behavior happens to MATCH real semantics in the basic case. But a real OS also supports marking specific file descriptors as "close-on-exec" (O_CLOEXEC in POSIX terms) -- a per-descriptor flag that says "don't let a future exec() carry this one across." This matters for real security and correctness reasons: a process shouldn't accidentally hand a newly-exec'd, untrusted program access to file descriptors it was never supposed to see. This chapter's own Syscalls class has no such flag anywhere -- there is no way to mark any fd as close-on-exec, so EVERY open descriptor always survives every exec() unconditionally. The behavior verified here is honestly correct for the simple case this implementation covers, but the close-on-exec distinction real systems rely on is a genuine, un-modeled gap, not something this library deliberately chose to support. WHY THIS WORKS AS AN ANSWER ------------------------------ Testing whether the SPECIFIC fd from before exec() still functions correctly (not just checking whether the dict entry technically still exists) confirms the survival is functionally real, not just incidental bookkeeping -- while framing the result honestly against what a real, more complete syscall library would additionally need to support.