Почему 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
, это "эй, я число, вы можете делать числовые вещи со мной"... :(