Почему stat :: st_size 0 для устройств, но в то же время lseek правильно определяет размер устройства?

Я заметил, что когда я запрашиваю размер устройства с помощью open + lseek, все в порядке, но когда я stat устройство, я получаю ноль вместо реального размера устройства. Устройство чистое без какой-либо файловой системы, и первые байты устройства начинаются с некоторого текста, такого как "1234567890ABC". Что случилось?

Код:

#include <sys/stat.h>
#include <dirent.h>

bool
GetFileSize(const char* pPath, uint64_t& Size)
{
    pPath = "/home/sw/.bashrc";
    pPath = "/dev/sda";

    struct stat buffer;
    if (stat(pPath, &buffer))
    {
        printf("Failed to stat file. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        return false;
    }

    printf("File size by stat: %" PRIu64 " WTF?\n", buffer.st_size);

    //
    // Note: It strange, but stat::st_size from the stat call is zero for devices
    //

    int File = open(pPath, O_RDONLY);
    if (File < 0)
    {
        printf("Failed to open file. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        return false;
    }

    long off = lseek(File, 0, SEEK_END);
    if (off == (off_t)-1)
    {
        printf("Failed to get file size. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        close(File);
        return false;
    }
    close(File);

    printf("File size by lseek: %" PRIu64 "\n", off);
    fflush(stdout);

    Size = off;
    return true;
}

Выход:

File size by stat: 0 Huh?
File size by lseek: 34359738368

Если я использую stat для обычного файла, тогда все в порядке (закомментируйте строку с "/dev/sda"):

File size by stat: 4019 Huh?
File size by lseek: 4019

Ответ 1

Дьявол кроется в деталях... Для начала, есть фундаментальный принцип Unix-дизайна: все это файл, хорошо объяснено здесь.

Во-вторых, вызов stat (2) дает вам статистику inode, хранящуюся в файловой системе о специальном файле устройства, который имеет нулевой размер (воспринимайте его как lstat(2)). Если у вас есть блочное устройство с файловой системой, вы получите информацию о нем с помощью statfs(2) или getfsstat(2) или statvfs(2) независимо от файловой системы/устройства.

Работа со специальными файлами (обычно они находятся в /dev) всегда зависела от системы, а страницы руководства находятся в разделе 4. Поэтому, если вы хотите напрямую манипулировать устройством, вам следует ознакомиться с его спецификой. Например, в Linux man 4 hd покажет вам, как программно взаимодействовать с блочными устройствами IDE. В то время как man 4 sd даст вам, как взаимодействовать с дисками SCSI и т.д.

В-третьих, системные вызовы не должны быть несовместимыми по своим функциональным возможностям и не иметь ограничений.

Надеюсь, это помогло.

Ответ 2

из этого вопроса Unix Stack Exchange:

Файлы устройства не являются файлами сами по себе. Это интерфейс ввода/вывода для использования устройств в Unix-подобных операционных системах. Они не используют места на диске, однако, они все еще используют inode, как сообщается командой stat:

$ stat /dev/sda
      File: /dev/sda
      Size: 0               Blocks: 0          IO Block: 4096   block special file
Device: 6h/6d   Inode: 14628       Links: 1     Device type: 8,0

Это решает часть stat.

То, что вы можете искать в этом "файле", не связано. На самом деле это не файл, но вы можете open его и прочитать из него. Вы тоже можете к этому стремиться. Он позволяет читать диск на самом низком уровне, поэтому поиск необходим (вот почему он работает, и почему он не возвращает новую позицию, как любой "настоящий" файл?).

Согласно этому другому ответу UnixSE, вы можете получить размер устройства, прочитав этот файл /dev/sda/size.

Ответ 3

Длина "устройства", такого как /dev/sda, не указывается struct stat POSIX:

off_t st_size       For regular files, the file size in bytes. 

                    For symbolic links, the length in bytes of the 
                    pathname contained in the symbolic link. 

                    For a shared memory object, the length in bytes. 

                    For a typed memory object, the length in bytes. 

                    For other file types, the use of this field is 
                    unspecified. 

Поэтому в POSIX нет требований к размеру дискового устройства.

Linux также не указывает, что stat() должен возвращать размер дискового устройства:

st_size

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

Ответ 4

В Linux документированный способ получения размера устройства с необработанным диском, который вы можете открыть, - это BLKGETSIZE ioctl. Смотрите справочную страницу sd(4).

Обратите внимание, что это возвращает размер устройства в секторах. Вы можете подумать, что для размера в байтах вы должны умножиться на значение, возвращаемое ioctl BLKSSZGET, но если я правильно читаю исходный код, вам действительно нужно умножить на 512 независимо от того, что возвращает BLKSSZGET.

Ответ 5

lseek - это основа C fseek, поэтому он имеет схожую семантику, соответствующую fseek и совершенно не связан с другими областями Unix API. lseek касается провенанса, вы ожидаете, что lseek будет действовать как fseek, принимающий дескриптор файла, а fseek - это интерфейс C-библиотеки, созданный без привязки к Unix.

stat специфичен для Unix и делает свое дело. Разумная разница, если вы думаете о происхождении. Конечно, проблема в том, что API C имеют очень слабые модели типов, потому что C - один шаг, чтобы сделать возможной безопасность типов возможной.

Почему это важно? Потому что, по сути, seekable_size и file_object_size - это две принципиально разные концепции, которые требуют разных типов - даже стандартная библиотека C++ ошибается.

Но в то время как в C++ и с современными компиляторами это теперь совершенно бесполезный унаследованный недостаток, в C действительно нет способа эффективно обернуть целые числа в несовместимые типы без снижения производительности и читабельности кода. И, таким образом, вы получите что-то вроде offs_t или long используемое для совершенно несовместимых понятий. И это является источником путаницы: только то, что вы получаете число, связанное с размером, из функции, связанной с файлом, не означает, что число будет иметь то же значение. И значения обычно фиксируются в типах... Единственное значение, которое по сути имеет long, это "эй, я число, вы можете делать числовые вещи со мной"... :(