3.13 fcntl Function
The fcntl function can change the properties of a file that is already open.
#include <sys/types.h> #include <unistd.h> #include <fcntl.h> int fcntl(int filedes, int
cmd, ... /* int arg */ ); Returns: depends on cmd if OK
(see following), -1 on error
In the examples we show in this section, the third argument is always an integer, corresponding to the comment in the function prototype just shown. But when we describe record locking in Section 12.3, the third argument becomes a pointer to a structure.
The fcntl function is used for five different purposes:
-
duplicate an existing descriptor (cmd = F_DUPFD),
-
get/set file descriptor flags (cmd = F_GETFD or F_SETFD),
-
get/set file status flags (cmd = F_GETFL or F_SETFL),
-
get/set asynchronous I/O ownership (cmd = F_GETOWN or F_SETOWN),
-
get/set record locks (cmd = F_GETLK, F_SETLK, or F_SETLKW).
We'll now describe the first seven of these 10 cmd values. (We'll wait until Section 12.3 to describe the last three, which deal with record locking.) Refer to Figure 3.2 since we'll be referring to both the file descriptor flags associated with each file descriptor in the process table entry and the file status flags associated with each file table entry.
F_DUPFD | Duplicate the file descriptor filedes. The new file descriptor is returned as the value of the function. It is the lowest numbered descriptor that is not already open, that is greater than or equal to the third argument (taken as an integer). The new descriptor shares the same file table entry as filedes. (Refer to Figure 3.4.) But the new descriptor has its own set of file descriptor flags and its FD_CLOEXEC file descriptor flag is cleared. (This means that the descriptor is left open across an exec, which we discuss in Chapter 8.) |
F_GETFD | Return the file descriptor flags for filedes as the value of the function. Currently only one file descriptor flag is defined: the FD_CLOEXEC flag. |
F_SETFD |
Set the file descriptor flags for filedes. The new flag value is set from the third argument (taken as an integer).
Be aware that many existing programs that deal with the file descriptor flags don't use the constant FD_CLOEXEC. Instead the programs set the flag to either 0 (don't close-on-exec, the default) or 1 (do close-on-exec).
|
F_GETFL | Return the file status flags for filedes as the value of the function. We described the file status flags when we described the open function. They are listed in Figure 3.5. |
Unfortunately, the three access mode flags (O_RDONLY, O_WRONLY, and O_RDWR) are not separate bits that can be tested. (As we mentioned earlier, these three often have the values 0,1, and 2, respectively, for historical reasons; also these three values are mutually exclusive—a file can have only one of the three enabled.) Therefore we must first use the O_ACCMODE mask to obtain the access mode bits and then compare the result against any of the three values. |
Figure 3.5. File status flags for fcntl.
F_SETFL | Set the file status flags to the value of the third argument (taken as an integer). The only flags that can be changed are O_APPEND, O_NONBLOCK, O_SYNC, and O_ASYNC. |
F_GETOWN | Get the process ID or process group ID currently receiving the SIGIO and SIGURG signals. We describe these 4.3+BSD asynchronous I/O signals in Section 12.6.2. |
F_SETOWN | Set the process ID or process group ID to receive the SIGIO and SIGURG signals. A positive arg specifies a process ID. A negative arg implies a process group ID equal to the absolute value of arg. |
The return value from fcntl depends on the command. All commands return -1 on an error or some other value if OK. The following four commands have special return values: F_DUPFD, F_GETFD, F_GETFL, and F_GETOWN. The first returns the new file descriptor, the next two return the corresponding flags, and the final one returns a positive process ID or a negative process group ID.
Example
Program 3.4 takes a single command-line argument that specifies a file descriptor and prints a description of the file flags for that descriptor.
Program 3.4 Print file flags for specified descriptor.
#include <sys/types.h> #include <fcntl.h> #include "ourhdr.h" int main(int argc, char *argv[]) { int accmode, val; if (argc != 2) err_quit("usage: a.out <descriptor#>"); if ( (val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) err_sys("fcntl error for fd %d", atoi(argv[1])); accmode = val & O_ACCMODE; if (accmode == O_RDONLY) printf("read only"); else if (accmode == O_WRONLY) printf("write only"); else if (accmode == O_RDWR) printf("read write"); else err_dump("unknown access mode"); if (val & O_APPEND) printf(", append"); if (val & O_NONBLOCK) printf(", nonblocking"); #if !defined(_POSIX_SOURCE) && defined(O_SYNC) if (val & O_SYNC) printf(", synchronous writes"); #endif putchar('\n'); exit(0); }
Notice that we use the feature test macro _POSIX_SOURCE and conditionally compile the file access flags that are not part of POSIX.1. The following script shows the operation of the program, when invoked from a KornShell.
$ a.out 0 < /dev/tty read only $ a.out 1 > temp.foo $ cat temp.foo write only $ a.out 2 2>>temp.foo write only, append $ a.out 5 5<>temp.foo read write
The KornShell clause 5<>temp.foo opens the file temp.foo for reading and writing on file descriptor 5.
Example
When we modify either the file descriptor flags or the file status flags we must be careful to fetch the existing flag value, modify it as desired, and then set the new flag value. We can't just do an F_SETFD or an F_SETFL, as this could turn off flag bits that were previously set.
Program 3.5 shows a function that sets one or more of the file status flags for a descriptor.
Program 3.5 Turn on one or more of the file status flags for a descriptor.
#include <fcntl.h> #include "ourhdr.h" void set_fl(int fd, int flags) /* flags are file status flags to turn on */ { int val; if ( (val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl F_GETFL error"); val |= flags; /* turn on flags */ if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); }
If we change the middle statement to
val &= ~flags; /* turn flags off */
we have a function named clr_fl that we'll use in some later examples. This statement logically ANDs the 1's-complement of flags with the current val.
If we call set_fl from Program 3.3 by adding the line
set_fl(STDOUT_FILENO, O_SYNC);
at the beginning of the program, we'll turn on the synchronous-write flag. This causes each write to wait for the data to be written to disk before returning. Normally in Unix, a write only queues the data for writing, and the actual I/O operation can take place sometime later. A database system is a likely candidate for using O_SYNC, so that it knows on return from a write that the data is actually on the disk, in case of a system crash.
We expect the O_SYNC flag to increase the clock time when the program runs. To test this we can run Program 3.3, copying a 1.5 Mbyte file from one file on disk to another and compare this with a version that does the same thing with the O_SYNC flag set. The results are in Figure 3.6.
Figure 3.6. Timing results using synchronous writes (O_SYNC).
The three rows in Figure 3.6 were all measured with a BUFFSIZE of 8192. The results in Figure 3.1 were measured reading a disk file and writing to /dev/null, so there was no disk output. The second row in Figure 3.6 corresponds to reading a disk file and writing to another disk file. This is why the first and second rows in Figure 3.6 are different. The system time increases when we write to a disk file because the kernel now copies the data from our process and queues the data to for writing by the disk driver. The clock time increases also when we write to a disk file. When we enable synchronous writes, the system time increases slightly and the clock time increases by a factor of 6.
With this example we see the need for fcntl. Our program operates on a descriptor (standard output), never knowing name of the file that was opened by the shell on that descriptor. We can't set the O_SYNC flag when the file is opened, since the shell opened the file, fcntl allows us to modify the properties of a descriptor, knowing only the descriptor for the open file. We'll see another need for fcntl when we describe nonblocking pipes (Section 14.2), since all we have with a pipe is a descriptor.