cfilestreamfile-descriptorfile-accessfcntl

How to get the mode of a file descriptor?


I mean to use fdopen

FILE *fdopen(int fd, const char *mode);

In man pages, it is stated that "The mode of the stream (one of the values "r", "r+", "w", "w+", "a", "a+") must be compatible with the mode of the file descriptor." So I have to first know the mode of fd (which I guess is an int) to choose an appropriate const char *mode for the stream.

I understand I should use fcntl

int fcntl(int fd, int cmd);

to "manipulate file descriptor" (in the following, I quote from this official source). It can operate on:

File descriptor flags

The following commands manipulate the flags associated with a file descriptor.
...

File status flags

Each open file description has certain associated status flags, initialized by open(2)...

(I wouldn't know the difference between the two. Given that fcntl refers entirely to file descriptors, I guess the second title should be "File descriptor status flags", and thus we would have "flags" and "status flags"... confusing to me. I haven't seen any specification of this). I mention this in passing here, I am putting together a specific question on this.

From the description, I guess I should go for the latter. In this case, when cmd=F_GETFL, the return value is "the file access mode and the file status flags". "The file status flags and their semantics are described in open(2)".

Now I couldn't make sense of, after reading the sources cited:

  1. What are all possible modes (ints) for the fd

  2. Consequently, what are all combinations mode(fd) <-> mode(stream) that are "compatible".

I guess one should be able to put together two list and join with arrows.

Related:

Can I get the access mode of a `FILE*`?

Specification of file descriptors (I asked this)

How to catch file mode?

I Wanna know the Internal Members of struct FILE, the latest ones

How to make sense of O_RDONLY = 0? (I asked this)

https://www.gnu.org/software/libc/manual/html_node/Access-Modes.html

https://www.gnu.org/software/libc/manual/html_node/File-Status-Flags.html#File-Status-Flags


Solution

  • After learning from answers and comments, here and in How to make sense of O_RDONLY = 0?, I put together code below. From there, I obtained the following info about file descriptor status "words" (I wouldn't like to use the term "flags", see Note below, taken from this comment) and file opening modes.

    *** Flag                       O_RDONLY =     0 =            0 = x0000
    *** Flag                       O_WRONLY =     1 =            1 = x0001
    *** Flag                         O_RDWR =     2 =           10 = x0002
    *** Flag                        O_CREAT =    64 =      1000000 = x0040
    *** Flag                        O_TRUNC =   512 =   1000000000 = x0200
    *** Flag                       O_APPEND =  1024 =  10000000000 = x0400
    *** Flag   O_WRONLY | O_CREAT | O_TRUNC =   577 =   1001000001 = x0241
    *** Flag  O_WRONLY | O_CREAT | O_APPEND =  1089 =  10001000001 = x0441
    *** Flag     O_RDWR | O_CREAT | O_TRUNC =   578 =   1001000010 = x0242
    *** Flag    O_RDWR | O_CREAT | O_APPEND =  1090 =  10001000010 = x0442
    *** Mode  r  F_GETFL -> 32768 = 1000000000000000 = x8000
    *** Mode  w  F_GETFL -> 32769 = 1000000000000001 = x8001
    *** Mode  a  F_GETFL -> 33793 = 1000010000000001 = x8401
    *** Mode r+  F_GETFL -> 32770 = 1000000000000010 = x8002
    *** Mode w+  F_GETFL -> 32770 = 1000000000000010 = x8002
    *** Mode a+  F_GETFL -> 33794 = 1000010000000010 = x8402
    

    Numbers in the three columns are in decimal, binary and hex. Looking for the "strange" x8000, I found in fcntl-linux.h

    # ifdef __USE_GNU
    ...
    #  define AT_RECURSIVE      0x8000  /* Apply to the entire subtree.  */
    ...
    # endif
    

    So, except for that flag, present in all modes, the association would be

    r   <->  O_RDONLY
    w   <->  O_WRONLY
    a   <->  O_WRONLY | O_APPEND
    r+  <->  O_RDWR
    w+  <->  O_RDWR
    a+  <->  O_RDWR | O_APPEND
    

    Now this provides a couple of intriguing findings to me:

    1. The list does not coincide with the table given by Tony Tannous.

    2. The word for r+ is the same as for w+. This provides a challenge for the coder, as to which mode to use with fdopen when the word is O_RDWR (both r+ and w+ would be ok). As per this, I expected w+ to have also O_CREAT (as in the table mentioned above). I also expected w to have it.

    3. To write completely portable code, it seems that whenever using fdopen one has to write code as I wrote to automatically find the connection mode <-> word. (actually, part of the work I did was a manual identification, and further code is needed).

    EDIT: The explanation for points 1 and 2 as per comments is that the table shows the match between fopen modes and open flags, i.e., during creation. But what I obtained with fcntl is the flags persistent after creation, not those used during creation. As also explained here, O_CREAT and O_TRUNC belong to the category of File creation flags and thus are not persistent. On the other hand, O_APPEND belongs to the category File status flags and is persistent. "The distinction between these two groups of flags is that the file creation flags affect the semantics of the open operation itself, while the file status flags affect the semantics of subsequent I/O operations." [ref]

    Note: The man page for open(2) first describes the file access modes, and then adds "In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags...." But it (correctly) does not mention that file access mode can be bitwise operated on. For me, the word "flag" is an absolute misnomer, and misleading.


    Code (any function to_binary to get the binary form can be used):

    int main() {
        const char fname[100] = "test.txt";
        const char modes[][4] = { "r", "w", "a", "r+", "w+", "a+" };
        const size_t nmodes = sizeof(modes) / sizeof(modes[0]);
        const int flags[] = { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND,
                O_WRONLY | O_CREAT | O_TRUNC,
                O_WRONLY | O_CREAT | O_APPEND,
                O_RDWR | O_CREAT | O_TRUNC,
                O_RDWR | O_CREAT | O_APPEND
        };
        const char flags_str[][100] = { "O_RDONLY", "O_WRONLY", "O_RDWR", "O_CREAT", "O_TRUNC", "O_APPEND",
                "O_WRONLY | O_CREAT | O_TRUNC",
                "O_WRONLY | O_CREAT | O_APPEND",
                "O_RDWR | O_CREAT | O_TRUNC",
                "O_RDWR | O_CREAT | O_APPEND"
        };
        const size_t nflags = sizeof(flags) / sizeof(flags[0]);
        for (size_t iflag = 0 ; iflag < nflags ; iflag++) {
            const int flag = flags[iflag];
            const char * flag_str = flags_str[iflag];
            char nbin[33];
            to_binary(flag, nbin);
            printf( "*** Flag %30s = %5d = %12s = x%04x\n", flag_str, flag, nbin, flag);
        }
        for (size_t imode = 0 ; imode < nmodes ; imode++) {
            const char * mode = modes[imode];
            FILE * fp1 = fopen(fname, mode);
            int fd1 = fileno(fp1);
            int retval = fcntl(fd1, F_GETFL);
            char nbin[33];
            to_binary(retval, nbin);
            printf( "*** Mode %2s  F_GETFL -> %5d = %12s = x%04x", mode, retval, nbin, retval);
            fclose(fp1);
        }
        return 0;
    }