While trying to use fcntl()
with command F_GETFL
and F_SETFL
, I got some questions:
Why the flag returned by fcntl(fd, F_GETFL)
only include a subset of bits of what I set when open file? Does it only show the ones that are modifiable?
When use fcntl(fd, F_SETFL, flag)
, how should I pass the flag param, do I need to read flag via fcntl(fd, F_GETFL)
first, then modify it and pass it? Or internally it just do a bit &
operation with the new param?
Where can I find a full list of the 32 (or less) bits of open file flags?
Code - [dup_fd_share.c]:
// prove duplicated fd shared file offset and open file status,
// TLPI exercise 5.5
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define BUF_SIZE 100
void fd_share() {
char *fp = "/tmp/fd_share.txt";
char *buf = "abc\n";
int write_size = 4;
int fd, fd2;
off_t cur, cur2;
int open_flag, open_flag2;
// open
int flag = O_RDWR | O_CREAT | O_TRUNC | O_APPEND;
printf("file flag param: %x\n", flag);
fd = open(fp, flag, 0644);
// dup
fd2 = dup(fd);
// initial offset
cur = lseek(fd, 0, SEEK_CUR);
printf("fd[%d] offset: %ld\n", fd, cur);
cur2= lseek(fd2, 0, SEEK_CUR);
printf("fd[%d] offset: %ld\n", fd2, cur2);
// write, offset change,
write(fd, buf, 4);
printf("write %d chars\n", write_size);
// new offset
cur = lseek(fd, 0, SEEK_CUR);
printf("fd[%d] offset: %ld\n", fd, cur);
cur2= lseek(fd2, 0, SEEK_CUR);
printf("fd[%d] offset: %ld\n", fd2, cur2);
// get original open file flag,
open_flag = fcntl(fd, F_GETFL);
printf("fd[%d] open flag: %x\n", fd, open_flag);
open_flag2 = fcntl(fd2, F_GETFL);
printf("fd[%d] open flag: %x\n", fd2, open_flag2);
// change open file flag,
open_flag &= ~O_APPEND;
if(fcntl(fd, F_SETFL, open_flag) == -1) {
printf("failed to set flag\n");
return;
}
printf("change open file flag, remove %s\n", "O_APPEND");
open_flag = fcntl(fd, F_GETFL);
printf("fd[%d] open flag: %x\n", fd, open_flag);
open_flag2 = fcntl(fd2, F_GETFL);
printf("fd[%d] open flag: %x\n", fd2, open_flag2);
close(fd);
}
int main(int argc, char *argv[]) {
fd_share();
return 0;
}
Output:
file flag param: 642
fd[3] offset: 0
fd[4] offset: 0
write 4 chars
fd[3] offset: 4
fd[4] offset: 4
fd[3] open flag: 402
fd[4] open flag: 402
change open file flag, remove O_APPEND
fd[3] open flag: 2
fd[4] open flag: 2
You asked:
Why the flag returned by fcntl(fd, F_GETFL) only include a subset of bits of what I set when open file? Does it only show the ones that are modifiable?
No; it only shows the ones that are "remembered" by the system, such as O_RDWR
. These can really be called "flags". Some of the other bits ORed into the oflag
parameter are more like "imperative instructions" to the open
system call: for example, O_CREAT
says "please create this file if it doesn't exist" and O_TRUNC
says "please truncate it", neither of which are "flags". A file that was truncated on creation is indistinguishable from a file that was not truncated on creation: they're both just "files". So after open
is done creating or truncating the file, it doesn't bother to "remember" that history. It only "remembers" important things, like whether the file is open for reading or writing.
Edited to add: These different kinds of flags have semi-official names. O_RDWR
is the "access mode" (remembered, non-modifiable); O_APPEND
is an "operating mode" (remembered, usually modifiable); O_TRUNC
is an "open-time flag" (pertains to the open
operation itself, not to the file descriptor; therefore not remembered). Notice that the "access mode" is not modifiable — you can't use fcntl
to turn a read-only fd into a write-only fd.
When use
fcntl(fd, F_SETFL, flag)
, how should I pass the flag param, do I need to read flag viafcntl(fd, F_GETFL)
first, then modify it and pass it? Or internally it just do a bit&
operation with the new param?
F_SETFL
overwrites the flags with exactly what you pass in (although it will ignore your puny attempts to set bits-that-aren't-really-flags, such as O_TRUNC
). IF you want to set a specific flag and leave the other flags as-is, then you must F_GETFL
the old flags, |
the new flag in, and then F_SETFL
the result. This must be done as two separate system calls; there is no atomic or thread-safe way to accomplish it as far as I know.
Where can I find a full list of the 32 (or less) bits of open file flags?
In fcntl.h
or its documentation (man fcntl
). For example, on my MacBook the man page says:
The flags for the F_GETFL and F_SETFL commands are as follows:
O_NONBLOCK Non-blocking I/O; if no data is available to a read call, or if a write operation would block, the read or write
call returns -1 with the error EAGAIN.
O_APPEND Force each write to append at the end of file; corresponds to the O_APPEND flag of open(2).
O_ASYNC Enable the SIGIO signal to be sent to the process group when I/O is possible, e.g., upon availability of data to be
read.
In other words, there are exactly three bits you can set (or unset) on OS X. Whereas on Linux, the man page says this:
On Linux this command can change only the O_APPEND, O_ASYNC,
O_DIRECT, O_NOATIME, and O_NONBLOCK flags.
Incidentally, some Linux filesystems have the concept of an "append-only file" at the filesystem level; if you open one of those files and then try to clear the resulting descriptor's O_APPEND
flag, you'll get an EPERM
error. The "append-only"-ness of a file can be controlled at the filesystem level using the chattr
utility.
Here's a more systematic version of your test program. It might not be of interest to you, but I learned something by writing it, so I'm leaving it here. :)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("/tmp/fd_share.txt", O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0644);
// append to empty file
write(fd, "aaaaaaaaaa", 10);
off_t cur = lseek(fd, 1, SEEK_SET);
printf("offset after being set to 1: %ld\n", (long)cur);
// append
write(fd, "bbbbbbbb", 8);
cur = lseek(fd, 0, SEEK_CUR);
printf("offset after appending bbbbbbbb: %ld\n", (long)cur);
cur = lseek(fd, 2, SEEK_SET);
printf("offset after being set to 2: %ld\n", (long)cur);
// now toggle "append mode" to FALSE
int open_flag = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, open_flag & ~O_APPEND) == -1) {
printf("failed to set flag\n");
return 0;
}
cur = lseek(fd, 0, SEEK_CUR);
printf("offset after unsetting O_APPEND: %ld\n", (long)cur);
cur = lseek(fd, 3, SEEK_SET);
printf("offset after being set to 3: %ld\n", (long)cur);
// write without appending
write(fd, "cccc", 4);
cur = lseek(fd, 0, SEEK_CUR);
printf("offset after writing cccc: %ld\n", (long)cur);
// now toggle "append mode" to TRUE
open_flag = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, open_flag | O_APPEND) == -1) {
printf("failed to set flag\n");
return 0;
}
cur = lseek(fd, 0, SEEK_CUR);
printf("offset after unsetting O_APPEND: %ld\n", (long)cur);
// append
write(fd, "dd", 2);
cur = lseek(fd, 0, SEEK_CUR);
printf("offset after appending dd: %ld\n", (long)cur);
close(fd);
}
The output of this program on my MacBook (as it should be on any POSIX system AFAIK) is:
offset after being set to 1: 1
offset after appending bbbbbbbb: 18
offset after being set to 2: 2
offset after unsetting O_APPEND: 2
offset after being set to 3: 3
offset after writing cccc: 7
offset after unsetting O_APPEND: 7
offset after appending dd: 20