Почему fallocate() в linux создает непустой файл, когда в нем недостаточно места?

Представьте, что у меня есть следующий фрагмент кода:

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(int argc, char** argv) {

    if (argc > 2) {
        int fd = open(argv[1], O_CREAT|O_WRONLY, 0777);
        size_t size = atoi(argv[2]);

        if (fd > 0) {

            //int result = fallocate(fd, 0, 0, size);
            //printf("itak: %d, errno: %d (%s)\n", result, errno, strerror(errno));
            int result = posix_fallocate(fd, 0, size);
            printf("itak: %d, errno: %d (%s)\n", result, result, strerror(result));

        } else {
            printf("failed opening file\n");
        }

    } else {
        printf("Usage blah\n");
    }


}

Это простая версия /usr/bin/fallocate, которую я сделал, чтобы проверить свои предположения. Я обнаружил, что если я использую его для создания файла, который больше, чем свободное пространство файловой системы, он вернет -1 и правильный errno, но все равно создаст файл максимально допустимого размера. Мне это кажется странным, потому что команда явно вернула -1, что должно быть сигналом об ошибке, но она все равно что-то сделала. И более того, он не соответствует тому, о чем я просил, - он создал файл неизвестного (на данный момент я его запускаю) размера. Если я использую fallocate(), чтобы зарезервировать место для фотографий котенка, я не знаю, для меня будет бесполезно, если он зарезервирует меньше места, чем я просил.

Да, fallocate() и posix_fallocate() ведут себя безопасно, я проверил оба, как вы можете видеть.

Естественно, я думал, что делаю что-то не так. Потому что, если вы столкнетесь с проблемами во время программирования, это то, что происходит в 99,9%. Поэтому я попробовал утилиту /usr/bin/fallocate и, да, она "не работает", но все равно создает файл.

Вот пример того, как я запускаю утилиту:

rakul@lucky-star /tmp $ strace fallocate -l 10G /tmp/test 2>&1 | grep fallocate
execve("/usr/bin/fallocate", ["fallocate", "-l", "10G", "/tmp/test"], [/* 47 vars */]) = 0
fallocate(3, 0, 0, 10737418240)         = -1 ENOSPC (No space left on device)
write(2, "fallocate: ", 11fallocate: )             = 11
write(2, "fallocate failed", 16fallocate failed)        = 16
rakul@lucky-star /tmp $ ls -l /tmp/test
-rw-r--r-- 1 rakul rakul 9794732032 сен 26 19:15 /tmp/test
rakul@lucky-star

Как вы можете видеть, у него не было определенного режима, установленного в вызове fallocate(), он завершился ошибкой, но файл был создан, но его размер неожиданный.

Я узнал, что некоторые ребята в Интернете видят противоположное поведение:

rxxxx@home/tmp> fallocate -l 10G test.img
fallocate: fallocate failed: На устройстве не осталось свободного места
rxxxx@home/tmp> ls -l test.img 
-rw-r--r-- 1 rogue rogue 0 Врс 26 17:36 test.img

(на русском языке написано "недостаточно места")

Я пробовал ext4 и tmpfs с одинаковыми результатами. У меня есть Gentoo Linux, 3.18 ядро. Однако первоначально я видел эту проблему на современном SLES12.

Мой вопрос: почему есть разные результаты и как я могу предотвратить создание файла /usr/bin/fallocate или fallocate(), если не хватает

1 ответ

Решение

Чтение "man 2 fallocate" НЕ дает никаких гарантий относительно поведения библиотечного вызова в случае, когда на диске недостаточно места, за исключением того, что он вернет -1, и ошибка будет ENOSPC,

В POSIX.1-2001 никакие побочные эффекты не предъявляются к posix_fallocate позвони либо.

Таким образом, реализация имеет право создавать файл половинного размера, если она этого желает.

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

Вам просто придется иметь дело с поведением вызова как есть; Вы не можете изменить код реализации (ну, вы можете, отправив патч!). Гораздо проще изменить вашу программу, чтобы правильно обрабатывать все разрешенные режимы сбоев.

Другие вопросы по тегам