Normally, I/O can proceed until it is time to call *
close (fd);on a given file descriptor, fd. The most common reason for closing a stream is that an end-of-file condition has been encountered. The value of *
eof (fd)is true or false depending on whether an attempt to read past the end of the input available from fd has been made. The fd parameter is optional on the eof call; the value of *
eof -- trailing ``()'' optionalis true or false depending on whether the last input attempt on any stream failed on an end-of-file condition.
The responsibilities of close include flushing the output buffer if necessary, possibly waiting for a child process to complete, and releasing the buffer and kernel resources associated with the system-level file descriptor. It is also permitted to apply close to a file descriptor that is only open at the operating system level and not at the SETL level, as mentioned in Section 2.17.1 [I/O and File Descriptors].
For bidirectional streams, one direction may be closed without closing the other by calling *
shutdown (fd, how);where how is one of the predefined constants shut_rd, shut_wr, or shut_rdwr. Even if both directions are closed in this way, the file descriptor remains open at the SETL level. One use of shutdown is to cause an end-of-file condition to be signalled to a TCP peer (see Chapter 3 [Internet Sockets]), to indicate that the local process is done sending data but would still like to receive a reply. Stevens  mentions several others in conjunction with the Unix 98 shutdown routine (which shutdown also calls).
When open fails, it returns om instead of an integer file descriptor. Programs need not check for this possibility if a crash upon the first attempt to do I/O on om is acceptable behavior. When programs are run in subshells whose crashing does no harm to their environments, as is often the case when they are invoked through pump streams or by system or filter, such behavior may indeed be acceptable.
That possibility notwithstanding, he reason open tries to offer the caller a chance for recovery from errors instead of just crashing the process is that initiating access to external resources is something that can logically be expected to fail sometimes, whether because the system has run out of file descriptors or subprocesses available to the current process, or because a file was not found, many clients will want the chance to take specific recovery action.
Whatever the reason, the caller of open that chooses to check for om will find a rich variety of possibilities in what *
last_errorcan yield after an om return. Any SETL program that wishes to issue a detailed diagnostic for internally detected open failures does well to include the value of last_error in the error message, much as a C program obtains similar information from strerror.
clear_error;will restore last_error to its default of returning *
no_errorand the latest value of last_error will always depend on the latest setting of the Unix global errno variable by a system routine. For example, many of the low-level Unix (Posix) interface routines described in Section 2.17 [Low-Level System Interface] will express failure by setting last_error rather than by abending the SETL process. It is a good idea to execute clear_error just before calling any routine that can interact with the external environment in any way, if one intends to inspect last_error (i.e., compare it to no_error) after the call.
On the other hand, particularly where networks are involved, either because of explicit use of sockets or because of networked filesystems, it is possible for practically any regular I/O operation to fail catastrophically, and one of the main reasons for delegating I/O responsibilities to child processes in the software designs preferred throughout this dissertation is to limit the damage caused by unpredictable communications failures--if the child running under a pump stream crashes, the parent simply sees an end-of-file condition on the pump stream.