Как выбрать открытый режим с помощью fopen()?

У меня проблема с режимом открытия файла с fopen().

В моем случае я хочу свободно искать курсор, иногда даже за пределами EOF. Более того, я также хочу добавить к нему вместо усечения существующего файла. Я попытался открыть файл в режиме a+; однако я не могу свободно искать файловый курсор. Каждый раз, когда я ищу курсор за пределами EOF, новые данные о поступлении будут добавляться в конец файла, а не в указанную мной позицию. В то время как, если открыть в режиме w+, существующий файл будет обрезан. Есть ли идеальное решение этой проблемы?


ОБНОВИТЬ:

Один неясный момент заключается в том, что файл не всегда может существовать; В этом случае мне нужно создать новый файл.

На самом деле я хочу обработать файл конфигурации. Я не знаю, является ли это хорошей практикой для такой реализации. Или я должен сначала разместить пустой файл конфигурации. Таким образом, нет необходимости заботиться о том, что материалы дела не существуют?

Ниже приведен фрагмент кода:

FILE *f = fopen(FILE_PATH, "wb+");
struct record r;
if (f) {
    if (fread((void *)&r, 1, sizeof(struct record), f) {
        /* File exists, do the normal flow */
    } else {
        if (feof(f)) {
            /* File is newly created, do some initialization */
        }
    }
} else {
    /* issue a warning */
}

person Summer_More_More_Tea    schedule 13.05.2012    source источник
comment
Можете ли вы опубликовать соответствующий код здесь?   -  person P.P    schedule 13.05.2012
comment
Мне любопытно .. почему вы ищете / пишете за пределами EOF?   -  person OrionRogue    schedule 13.05.2012
comment
@OrionRogue Я использую файл для эмуляции жесткого диска. Данные считываются и записываются порциями. Значит изначально файл пустой, данные могут отставать EOF   -  person Summer_More_More_Tea    schedule 13.05.2012


Ответы (3)


Вам придется иметь дело с возможно несуществующим файлом в два этапа, сначала предполагая, что он есть, а затем разбираясь с его отсутствием:

if ((f = fopen(filename, "rb+") == 0)
    f = fopen(filename, "wb+");
if (f == 0)
    ...report error...

Режим "rb+" не сможет открыть несуществующий файл (но в остальном ведет себя так, как вы хотите). Если файл не существует, то вместо этого "wb+" сделает то, что вы хотите (хотя это все равно может привести к сбою, например, если файл существует, но у вас нет разрешения на запись в него). Вы должны надеяться, что вы не подверглись атаке TOCTOU (Time of Check, Time of Use) с двойной попыткой.

Альтернативный подход использует 3-аргументную версию системного вызова open() с соответствующими флагами для открытия файлового дескриптора, а затем использует fdopen() для создания файлового потока из файлового дескриптора:

#include <fcntl.h>

int fd;
if ((fd = open(filename, O_RDRW | O_CREAT, 0644)) >= 0)
    f = fdopen(fd, "rb+");

Вы получаете довольно точный контроль над open() с помощью флагов.

person Jonathan Leffler    schedule 13.05.2012
comment
Обычно вы можете передать 0666 в качестве битов разрешения и доверить пользователю установку umask. Это также означает, что пользователь может установить umask, вместо того, чтобы потом вручную chmod восстанавливать файлы. - person Dietrich Epp; 13.05.2012
comment
Подход fdopen является единственным безопасным. - person R.. GitHub STOP HELPING ICE; 13.05.2012
comment
@DietrichEpp Это правильное наблюдение - если вы доверяете своим пользователям, что они знают, что такое umask. работаю в охране; Я не доверяю своим пользователям. У нас нет большего контекста. Отмечу, что если файл существует, его разрешения не изменятся, поэтому, если пользователь хочет, чтобы все и каждый редактировали его конфигурацию, он может установить общедоступные и групповые разрешения на запись. Я не считаю, что разрешено другим людям (любым другим людям) изменять мою конфигурацию как безопасную, поэтому преднамеренное (но не прокомментированное) использование 0644 вместо 0666. - person Jonathan Leffler; 13.05.2012
comment
@R..: Это (open() плюс fdopen()) был бы механизм, который я бы использовал, но файловые дескрипторы - это честная игра в контексте, где я работаю. Если местный стандарт должен использовать только файловые потоки, то двойная попытка является ближайшим приближением к исправлению, проблемам TOCTOU и всему остальному. - person Jonathan Leffler; 13.05.2012
comment
Вот более безопасный вариант: используйте fopen с "ab+" для создания файла в случае, если он не существует, затем закройте и повторите попытку с самого начала. - person R.. GitHub STOP HELPING ICE; 13.05.2012
comment
@R..: Насколько это безопаснее, чем "rb+" и "wb+", если это не удается? - person Jonathan Leffler; 13.05.2012
comment
Использование 0666 будет соответствовать поведению fopen, использующего 0666 (по крайней мере, так делает glibc). Если ваш umask неверен, у вас все равно есть большие проблемы. - person Dietrich Epp; 13.05.2012
comment
@JonathanLeffler: В случае, если "rb+" не удалось создать файл, который был создан кем-то другим сразу после этого, последующий "wb+" усекает файл. Использование вместо этого "ab+" для создания файла позволит избежать усечения, и в следующем раунде "rb+" получит уже существующий файл. - person R.. GitHub STOP HELPING ICE; 14.05.2012

Файловые режимы четко задокументированы для fopen (попробуйте man 3 fopen в Unix/Linux/OS X).

r+ Открыт для чтения и записи. Поток располагается в начале файла.

person geekosaur    schedule 13.05.2012
comment
Режим "rb+" или "r+" не работает, если файл не существует; в этом случае вы должны вернуться и повторить попытку с помощью "wb+" или "w+", надеясь, что вы не подверглись атаке TOCTOU (время проверки, время использования). - person Jonathan Leffler; 13.05.2012
comment
Если вы хотите изменить, если существует; в противном случае создавайте пустую семантику на POSIX, fopen бесполезен. Вам понадобится open, а затем fdopen. - person R.. GitHub STOP HELPING ICE; 13.05.2012

Привет, вы можете использовать «w +» для чтения и записи с помощью fseek, я написал небольшую демонстрационную программу, сначала записывайте данные в файл и используйте fseek, чтобы сделать каждые данные интервалами некоторого байта, а затем прочитать его:

#include <stdio.h>
#include <unistd.h>

#define FILE_PATH "seek_test.txt"
#define STEP_SIZE 64

void set_data(FILE* fp)
{
    int i = 0;
    fseek(fp, 0, SEEK_SET);

    for ( ; i < 20; ++i)
    {
        fprintf(fp, "%d", i);
        fseek(fp, STEP_SIZE, SEEK_CUR);
    }
}

void get_data(FILE* fp)
{
    int i = 0;
    fseek(fp, 0, SEEK_SET);

    for ( ; i < 20; ++i)
    {
        fscanf(fp, "%d", &i);
        fprintf(stderr, "Cur Step: %5ld, value = %4d\n", i * STEP_SIZE, i);
        fseek(fp, STEP_SIZE, SEEK_CUR);
    }
}

int main(int argc, char* argv[])
{
    FILE* fp = fopen(FILE_PATH, "w+");
    if (fp == NULL)
    {
        printf("fopen Error\n");
        exit(0);
    }

    set_data(fp);
    get_data(fp);

    return 0;
}

=============================== результат следующим образом:

Текущий шаг: 0, значение = 0

Шаг шага: 64, значение = 1

Шаг шага: 128, значение = 2

Шаг шага: 192, значение = 3

Шаг шага: 256, значение = 4

Шаг шага: 320, значение = 5

Шаг шага: 384, значение = 6

Шаг шага: 448, значение = 7

Шаг шага: 512, значение = 8

Шаг шага: 576, значение = 9

Cur Step: 640, значение = 10

Шаг шага: 704, значение = 11

Cur Step: 768, значение = 12

Шаг шага: 832, значение = 13

Шаг шага: 896, значение = 14

Шаг Cur: 960, значение = 15

Шаг шага: 1024, значение = 16

Шаг шага: 1088, значение = 17

Шаг шага: 1152, значение = 18

Шаг шага: 1216, значение = 19

=============================

person vincent    schedule 14.05.2012
comment
Спасибо, все еще не могу удовлетворить все требования. Я наконец принимаю ответ Джонатана. - person Summer_More_More_Tea; 14.05.2012
comment
Это приведет к обрезанию файла. - person Barmar; 03.10.2017