The last-close semantic is the semantic which requires that an open file remain available to any process which has the file open regardless of any changes in file or process characteristics which may take place after the file is opened. It is called the last-close semantic because the best known consequence of the last-close semantic is that when a file is deleted, the file is not removed until the file is closed by the last process which has it open.
Table 4.2: IEEE 1003.1-1990 functions with last-close semantic implications
Table 4.2 lists IEEE 1003.1-1990 functions which have implications for the last-close semantic. Once a file has been successfully opened, the following conditions hold under IEEE 1003.1-1990 and traditional Unix for regular files. The function names listed below are IEEE 1003.1-1990 function names.
Once a process has a file open, access to the file is not denied. For example, if Process A has a file open and Process B uses chmod to change the file permission bits of the open file to 0, Process A does not lose access to the file. Despite the fact that a process can not open a file with mode 0, Process A does not lose access to the open file because file access modes are only checked when a file is opened. Unlike Unix, under IEEE 1003.1-1990, whether or not Process A would lose access to the open file is implementation-defined.
The same example applies to chown(). If Process A has a file open and Process B uses chown() to change the ownership or group of the file, Process A retains access to the file.
The function unlink() removes a link to a file and decrements the link count of the file. The file is no longer accessible when the file's link count is zero and no process has the file open. When an unlink() is performed and the link count becomes zero, the file is removed from the directory entry but the file contents are not removed until the last process which has the file open closes the file. Processes which have the file open, do not lose access to the unlinked file.
A rename() can cause a file to be unlinked. A rename() which generates an implied unlink() of the file removes the directory entry for that file, but the removal of the file contents is deferred until all references to the file have been closed and the link count is zero. If any processes had the unlinked file open, access to the file is not lost until the file is closed.
A process can use the exec family of functions to overlay the current process image with a new process image. If Process A, which has a file open, calls execl() to execute fileB, the file descriptors open in Process A remain open. If fileB has its set-user-ID and/or set-group-ID mode bits set, then the effective user ID and/or the effective group ID of the process is changed. However, Process A does not lose access to open files inherited as a result of the execl().
If a process has appropriate privileges, the functions setuid() and setgid() can be used to set the user and group IDs for the process. Regardless of what the new user and group IDs are set to, access to an open file is not denied. File access modes are only checked when a file is opened.
To summarize, on a single system, access to a file by any process which has that file open is maintained regardless of any operation which changes the access conditions of the file (i.e. the owner, group, and access permissions), removes the file, or changes the effective user ID, effective group ID, or supplementary group IDs of the process. An exception to this is chmod under IEEE 1003.1-1990. Under IEEE 1003.1-1990, whether or not the last-close semantic is guaranteed for chmod is implementation defined.
The last-close semantic for directories is somewhat different than the last-close semantic for regular files. Directory streams are the file descriptor counterpart for directories in IEEE 1003.1-1990. IEEE 1003.1-1990 does not require that directories use file descriptors to implement directory streams. Note that if an implementation does not use file descriptors for directory streams, such file descriptors may not be used in functions, other than readdir(), rewinddir() , and closedir(), which have file descriptors as arguments (see IEEE 1003.1-1990 B5.1.2). As a result, directories may not have open file descriptions.
Moreover, the use of directory streams inherited from parent to child through exec() is undefined. IEEE 1003.1-1990 says that a directory stream inherited through a fork() may continue to be processed by either the parent or child but not by both. Thus, in a family tree of processes created by fork(), it is implementation-specific whether a directory steam in the root of the family tree is available to the root or to the processes which are the leaves of the family tree. If an inherited directory stream is usable by both parent and child, the result is undefined. Thus, if the last-close semantic is guaranteed for a directory, all open directory streams at the time a directory is removed are still available, but this availability among related processes may be considerably different from the application of the last-close semantic to regular files.
Consider the following examples:
In these two examples, had the directory been a regular file, the file descriptors for D would have remained available to all processes. It is implementation-specific as to whether the system behaves like the first example or the second example. Thus, if the last-close semantic is not guaranteed, the only thing that is certain is that if the process that removes a directory has its directory stream available, it retains that availability. The availability of that directory stream to any other related process is implementation-specific.
The meaning of the last-close semantic for a FIFO special file is the same as for a regular file. However, there is an additional aspect of the last close semantic for a FIFO. The last-close semantic for a FIFO also implies that when all file descriptors associated with a FIFO have been closed, any remaining data in the FIFO is discarded. If the last-close semantic fails for a FIFO, data may remain in the FIFO after all file descriptors referring to the FIFO have been closed.
There are several common uses of the last-close semantic. For example, it is common for an application to create a temporary file and then immediately unlink the file. The temporary file remains available to the application but if the application is aborted, the temporary file is closed and removed without any further action by the application.
The last-close semantic is also sometimes used for process synchronization and message passing. The use of the last-close semantic for such applications was more common in earlier versions of Unix where the only facilities available for interprocess communication and synchronization were pipes and signals. Pipes and signals require that the processes who wish to communicate know each others process IDs and/or share open file descriptions. The following example illustrates how the last-close semantic can be used by two processes, who know nothing about each other, to pass messages of arbitrary length. The two processes only know a file name, fileX. This example assumes that fileX exists and had a mode of 666.
This message passing example does not queue messages. Messages are written and read one at a time. Process A writes a message of arbitrary size to a file, fileX, and Process B reads a message in its entirety from fileX.
Process A and Process B proceed as follows (see programs
write_msg.c and read_msg.c in Appendix
).
When Process A wants to send a message to Process B,
Process A checks to see if the length of fileX
is zero. If the length is not zero, Process A waits for Process B to
read the message and truncate fileX to length zero.
If the length of fileX is zero, then Process A opens
file X and uses chmod() to change the mode of
fileX to zero. The chmod 0 prevents Process B from reading fileX
before Process A has finished writing the message. Because of the last-close
semantic, Process A does not lose access to fileX.
When Process A has finished writing the message, it uses chmod()
to restore the file permission bits of fileX.
When Process B wants to read a message from Process A, Process B checks to see if the length of fileX is 0. If the length is 0, Process B waits until there is a message to read. If the length of fileX is not zero, Process B proceeds to try to open fileX. If Process A has not finished writing the message, fileX will have mode 0 and Process B will have to wait for Process A to restore fileX's file permission bits before fileX can be opened. After Process B has read the message, it truncates fileX to indicate to Process A that it has read the message.
Demonstrations were developed to illustrate the behavior of the last-close semantic using NFS. These demonstrations are presented in the following sections. Section 4.5.1 demonstrates the behavior of the last-close semantic when an open file's ownership, group, and permissions bits are changed. Section 4.5.2 demonstrates the results of removing an open file. Section 4.5.3 shows the results of changing the identity of a process after a file has been opened. For the demonstrations, commands are displayed in italics and the output of those commands is displayed in bold.