c - Использование F_GETFL и F_SETFL

При попытке использовать fcntl() с командами F_GETFL и F_SETFL у меня возникли вопросы:

  1. Почему флаг, возвращаемый fcntl(fd, F_GETFL), включает только подмножество битов, которые я установил при открытии файла? Он показывает только те, которые можно изменить?

  2. При использовании fcntl(fd, F_SETFL, flag), как мне передать параметр флага, нужно ли мне сначала прочитать флаг через fcntl(fd, F_GETFL), а затем изменить его и передать? Или внутренне он просто выполняет небольшую операцию & с новым параметром?

  3. Где я могу найти полный список 32-битных (или менее) бит флагов открытых файлов?

Код — [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;
}

Вывод:

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

person user218867    schedule 07.05.2015    source источник
comment
O_CREAT и O_TRUNC — это модификаторы, управляющие поведением самого вызова open; они не являются режимами, применимыми к открытому файлу.   -  person R.. GitHub STOP HELPING ICE    schedule 07.05.2015


Ответы (2)


Ты спросил:

Почему флаг, возвращаемый fcntl(fd, F_GETFL), включает только подмножество битов, которые я установил при открытии файла? Он показывает только те, которые можно изменить?

Нет; он показывает только те, которые «запомнены» системой, например O_RDWR. Их действительно можно назвать «флагами». Некоторые другие биты, объединенные операцией ИЛИ в параметр oflag, больше похожи на «императивные инструкции» для системного вызова open: например, O_CREAT говорит «пожалуйста, создайте этот файл, если он не существует», а O_TRUNC говорит «пожалуйста, обрежьте его». из них «флаги». Файл, который был усечен при создании, неотличим от файла, который был не усечен при создании: они оба просто "файлы". Таким образом, после того, как open завершил создание или усечение файла, он не удосуживается "запомнить" эту историю. Он «помнит» только важные вещи, например, открыт ли файл для чтения или записи.

Отредактировано для добавления. Эти различные виды флагов имеют полуофициальные названия. O_RDWR — это «режим доступа» (запоминаемый, неизменяемый); O_APPEND - это "режим работы" (запоминаемый, обычно модифицируемый); O_TRUNC — это «флаг времени открытия» (относится к самой операции open, а не к файловому дескриптору, поэтому не запоминается). Обратите внимание, что «режим доступа» нельзя изменить — вы не можете использовать fcntl, чтобы превратить fd только для чтения в fd только для записи.

При использовании fcntl(fd, F_SETFL, flag), как мне передать параметр флага, нужно ли мне сначала прочитать флаг через fcntl(fd, F_GETFL), а затем изменить его и передать? Или внутренне он просто выполняет небольшую операцию & с новым параметром?

F_SETFL перезаписывает флаги в точности тем, что вы передаете (хотя и игнорирует ваши жалкие попытки установить биты, которые на самом деле не являются флагами, такие как O_TRUNC). ЕСЛИ вы хотите установить определенный флаг и оставить другие флаги как есть, то вы должны F_GETFL старые флаги, | новый флаг, а затем F_SETFL результат. Это должно выполняться как два отдельных системных вызова; насколько я знаю, нет атомарного или потокобезопасного способа выполнить это.

Где я могу найти полный список 32-битных (или менее) бит флагов открытых файлов?

В fcntl.h или его документации (man fcntl). Например, на моем MacBook справочная страница говорит:

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.

Другими словами, в OS X можно установить (или снять) ровно три бита. В то время как в Linux на странице руководства указано это:

On Linux this command can change only the O_APPEND, O_ASYNC,
O_DIRECT, O_NOATIME, and O_NONBLOCK flags.

Между прочим, некоторые файловые системы Linux имеют концепцию «файла только для добавления» на уровне файловой системы; если вы откроете один из этих файлов, а затем попытаетесь сбросить флаг O_APPEND результирующего дескриптора, вы получите ошибку EPERM. Доступность файла только для добавления можно контролировать на уровне файловой системы с помощью chattr полезность.


Вот более систематизированная версия вашей тестовой программы. Возможно, вам это будет неинтересно, но я кое-чему научился, написав это, поэтому оставлю это здесь. :)

#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);
}

Вывод этой программы на моем MacBook (как и должно быть в любой системе POSIX, AFAIK):

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
person Quuxplusone    schedule 08.08.2017

1) Возврат fcnl — это код, описывающий, удалось ли выполнить функцию и как:

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

   For a successful call, the return value depends on the operation:

   F_DUPFD  The new descriptor.

   F_GETFD  Value of file descriptor flags.

   F_GETFL  Value of file status flags.

   F_GETLEASE
            Type of lease held on file descriptor.

   F_GETOWN Value of descriptor owner.

   F_GETSIG Value of signal sent when read or write becomes possible, or
            zero for traditional SIGIO behavior.

   F_GETPIPE_SZ, F_SETPIPE_SZ
            The pipe capacity.

   F_GET_SEALS
            A bit mask identifying the seals that have been set for the
            inode referred to by fd.

   All other commands
            Zero.

   On error, -1 is returned, and errno is set appropriately.

ОШИБКИ

   EACCES or EAGAIN
          Operation is prohibited by locks held by other processes.

   EAGAIN The operation is prohibited because the file has been memory-
          mapped by another process.

   EBADF  fd is not an open file descriptor

   EBADF  cmd is F_SETLK or F_SETLKW and the file descriptor open mode
          doesn't match with the type of lock requested.

   EBUSY  cmd is F_SETPIPE_SZ and the new pipe capacity specified in arg
          is smaller than the amount of buffer space currently used to
          store data in the pipe.

   EBUSY  cmd is F_ADD_SEALS, arg includes F_SEAL_WRITE, and there
          exists a writable, shared mapping on the file referred to by
          fd.

   EDEADLK
          It was detected that the specified F_SETLKW command would
          cause a deadlock.

   EFAULT lock is outside your accessible address space.

   EINTR  cmd is F_SETLKW or F_OFD_SETLKW and the operation was
          interrupted by a signal; see signal(7).

   EINTR  cmd is F_GETLK, F_SETLK, F_OFD_GETLK, or F_OFD_SETLK, and the
          operation was interrupted by a signal before the lock was
          checked or acquired.  Most likely when locking a remote file
          (e.g., locking over NFS), but can sometimes happen locally.

   EINVAL The value specified in cmd is not recognized by this kernel.

   EINVAL cmd is F_ADD_SEALS and arg includes an unrecognized sealing
          bit.

   EINVAL cmd is F_ADD_SEALS or F_GET_SEALS and the filesystem
          containing the inode referred to by fd does not support
          sealing.

   EINVAL cmd is F_DUPFD and arg is negative or is greater than the
          maximum allowable value (see the discussion of RLIMIT_NOFILE
          in getrlimit(2)).

   EINVAL cmd is F_SETSIG and arg is not an allowable signal number.

   EINVAL cmd is F_OFD_SETLK, F_OFD_SETLKW, or F_OFD_GETLK, and l_pid
          was not specified as zero.

   EMFILE cmd is F_DUPFD and the process already has the maximum number
          of file descriptors open.

   ENOLCK Too many segment locks open, lock table is full, or a remote
          locking protocol failed (e.g., locking over NFS).

   ENOTDIR
          F_NOTIFY was specified in cmd, but fd does not refer to a
          directory.

   EPERM  Attempted to clear the O_APPEND flag on a file that has the
          append-only attribute set.

   EPERM  cmd was F_ADD_SEALS, but fd was not open for writing or the
          current set of seals on the file already includes F_SEAL_SEAL.

2) Флаги, которые будут установлены, на ваш выбор: :

F_SETFL (целое число)

   Set the file status flags to the value specified by arg.  File
   access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation
   flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are
   ignored.  On Linux this command can change only the O_APPEND,
   O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags.  It is not
   possible to change the O_DSYNC and O_SYNC flags; see BUGS,
   below.

3) ЗДЕСЬ у вас есть полное описание.

person LPs    schedule 07.05.2015