Чтение системных файлов с помощью Perl без выдачи дополнительных поисковых запросов при открытии

Я пытаюсь использовать Perl для анализа некоторых псевдо-файлов из /proc а также /sys псевдо файловые системы linux ( procfs и sysfs). Такие файлы отличаются от обычных файлов - они реализуются с помощью пользовательских обработчиков файловых операций. Большинство из них имеют нулевой размер для statнекоторые не могут быть открыты для чтения, другие не могут быть написаны. Иногда они реализованы неправильно (это ошибка, но она уже есть в ядре), и я все еще хочу читать их напрямую из Perl без запуска некоторых вспомогательных инструментов.

Есть быстрый пример чтения /proc/loadavg с помощью Perl этот файл реализован правильно:

perl -e 'open F,"</proc/loadavg"; $_=<F>; print '

С strace команды я могу проверить, как Perl реализует open функция:

$ strace perl -e 'open F,"</proc/loadavg"; $_=<F>; print ' 2>&1 | egrep -A5 ^open.*loadavg

open("/proc/loadavg", O_RDONLY)         = 3
ioctl(...something strange...)    = -1 ENOTTY
lseek(3, 0, SEEK_CUR)                   = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0

Есть lseek системный вызов, используемый open Perl-функция

С полосой cat /proc/loadavg не было никакого дополнительного seekсистемные вызовы:

$ strace cat /proc/loadavg 2>&1 | egrep -A2 ^open.*loadavg
open("/proc/loadavg", O_RDONLY)         = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0

Специальный файл, который я хочу прочитать (или написать), неправильно реализован seek файловые операции и не даст никаких полезных данных read (или для write) системный вызов после seek,

Есть ли способ открытия файлов для чтения в perl5 (без внешних модулей) без вызова extra lseek? (и без использования system("cat < /proc/loadavg"))

Есть ли способ открытия файлов для записи в perl5 без вызова extra lseek?

Существует sysopen, но он также выполняет дополнительные действия: perl -e 'use Fcntl;sysopen(F,"/proc/loadavg",O_RDONLY);sysread(F,$_,2048); print '

2 ответа

Решение

Как вы заметили, встроенный Perl open маскирует немного магии. Если эта магия встает на вашем пути, есть sysopen а также POSIX::open() которые предлагают уменьшающие степени магии. POSIX::open() достаточно немагичен, что возвращает файловые дескрипторы, а не файловые дескрипторы Perl, и вы должны использовать POSIX::read() вместо обычных операторов Perl, чтобы получить данные из него. Если этого недостаточно для ваших обстоятельств, вам может не повезти.

POSIX Модуль является частью основного дистрибутива Perl с самого первого выпуска Perl 5, поэтому, если у вас его нет, ваша установка Perl будет повреждена.

Если вы хотите пойти на самом низком уровне и избежать даже mmap от POSIX::open() (а также избегайте загрузки огромного POSIX модуль), выполните syscall()ты сам. Наверное, хочу require syscall.ph чтобы получить значения SYS_open а также SYS_read если вы их не знаете (для меня я знаю read, write, а также open являются 0,1,2соответственно - это важно знать для syscall функция ниже).

Следующий код:

strace perl -mPOSIX -e'$fd=POSIX::open("/proc/loadavg");POSIX::read($fd, 
$_, 9999);' 2>&1 | egrep -A2 '^open.*loadavg'

дает что-то вроде (для меня open() является openat())

openat(AT_FDCWD, "/proc/loadavg", O_RDONLY) = 3
mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =             
0x7f2389f0d000
read(3, "1.22 2.51 1.54 3/206 18145\n", 9999) = 27

Попробуйте что-то вроде этого:

strace perl -MFcntl -E'$p="/proc/loadavg"; $fd=syscall 2, $p, O_RDONLY; $bf = 
"\0"x50; syscall 0, $fd, $bf, 50' 2>&1 | egrep -A1 '^open.*loadavg'

и получить:

open("/proc/loadavg", O_RDONLY) = 3
read(3, "0.45 0.18 0.20 2/241 12349\n", 50) = 27

РЕДАКТИРОВАТЬ:
Что касается комментария от @osgx,

Есть \0 специальный символ в конце чтения. Как мне разобрать многострочный из POSIX::read, там нет while(<FILE>) сейчас.

Обратите внимание, что когда выwhile(<FILE>)"Вы действительно просто звоните read() один байт за раз и проверка на '\n' чар - или что там у тебя $/ (разделитель входной записи) установлен на (вы можете подтвердить это с помощью strace).

Таким образом, простой цикл для проверки $/ может быть достаточно. (Обратите внимание, что read() в случае успеха возвращает количество прочитанных байтов (0 означает EOF). Вот грубый пример синглаreadline":

require 'syscall.ph';
require Fcntl;
my($path, $fd, $buf, $res);
$path = '/proc/meminfo';
$fd = syscall SYS_open(), $path, O_RDONLY;
$buf = ' ';
$res = '';
$res .= $buf while syscall SYS_read(), $fd, $buf, 1 and $buf ne $/;
syscall SYS_close(), $fd; # optional in this case

Просто знайте, что если вы собираетесь на переносимость любого рода, syscallВозможно, это один из худших вариантов, но это цена специфичности. (POSIX::open/read/close() не намного лучше в этом смысле.). Чтобы сохранить мобильность, лучше использовать комментарий @ikegami (<: unix) и игнорировать дополнительные вызовы fstat а также fcntl;

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