Требуется прочитать или записать данные через файловый манипулятор так. чтобы система не приостанавливала процесс до наступления готовности программы, файла, сокета или устройства на другом конце. Такая задача чаще возни кает для специальных, нежели для о
Решение
Откройте файл функцией sysopen с параметром 0_NOCBLOCK:
use Fcnti;
sysopen(MODEM, "/dev/cuaO", 0_NONBLOCK|0_RDWR) or die "Can't open modem: $!\n"; Если у вас уже есть файловый манипулятор, измените флаги с помощью функции fcnti:
use Fcnti;
$flags = o o;
fcntl(HANDLE, F_GETFL, $flags)
or die "Couldn't get flags for HANDLE : $!\n";
$flags |= 0_NONBLOCK;
fcntl(HANDLE, F_SETFL, $flags)
or die "Couldn't set flags for HANDLE: $!\n"; После того как файловый манипулятор будет открыт для асинхронного ввода/вывода, измените флаги с помощью функции fcnti:
use POSIX qw(:errno_h);
$rv = syswrite(HANDLE, $buffer, length $buffer);
if (!defined($rv) && $! == EAGAIN) {
# Ожидание
} elsif ($rv != length $buffer) {
# Незавершенная запись
} else {
# Успешная запись
}
$rv = sysread(HANDLE, $buffer, SBUFSIZ);
or die "sysread: $!";
if (!defined($rv) && $! == EAGAIN) {
# Ожидание
} else {
# Успешно прочитано $rv байт из HANDLE
}
Комментарий
Константа 0_NONBLOCK входит в стандарт POSIX и потому поддерживается большинством компьютеров. Мы используем модуль POSIX для получения числового значения ошибки EAGAIN.
> Смотри также -------------------------------
Описание функций sysopen и fcnti в perlfunc{1); документация по стандартному модулю POSIX; страницы руководства ореп{2) fcntl(2) рецепты 7.13 и 7.15.
Требуется узнать, сколько байтов может быть прочитано через файловый манипулятор функцией read или sysread.
Решение
Воспользуйтесь функцией iocti в режиме FIONREAD:
$size = pack("L", 0);
ioctl(FH, $FIONREAD, $size) or die "Couldn't call iocti: $!\n";
$size = unpack("L", $size);
# Могут быть прочитаны $size байт
Комментарий
Функция Perl iocti предоставляет прямой интерфейс к системной функции ioctl(2). Если ваш компьютер не поддерживает запросы FIONREAD при вызове iocti (2), вам не удастся использовать этот рецепт. FIONREAD и другие запросы iocti (2) соответствуют числовым з
Вам может понадобиться утилита Perl h2ph, преобразующая заголовочные файлы С в код Perl. FIONREAD в конечном счете определяется как функция в файле sys/ioctl.ph:
require 'sys/ioctl.ph';
$size = pack("L", 0);
ioctl(FH, FIONREADO, $size) or die "Couldn't call iocti: $!\n";
$size = unpack("L", $size);
Если утилита h2ph не установлена или не подходит вам, найдите нужное место в заголовочном файле с помощью дгер:
%grер FIONREAD /usr/include/*/*
/usr/include/asm/ioctls.h:#define FIONREAD Ox541B Также можно написать небольшую программу на С в "редакторе настоящего программиста":
% cat > fionread.c
#include main() {
printf("%#08x\n", FIONREAD);
}
^D
% cc -o fionread fionread
% ./fionread
Ox4004667f Затем жестко закодируйте полученное значение в программе. С переносимостью пускай возится ваш преемник:
$FIONREAD = Ox4004667f; # XXX: зависит от операционной системы
$size = pack("L", 0);
ioctl(FH, $FIONREAD, $size) or die "Couldn't call iocti: $!\n";
$size = unpack("L", $size); FIONREAD требует, чтобы файловый манипулятор был подключен к потоку. Следовательно, сокеты, каналы и терминальные устройства будут работать, а файлы - нет.
Если вам это покажется чем-то вроде системного программирования, взгляните на проблему под другим углом. Выполните асинхронное чтение данных из манипулятора (см. рецепт 7.14). Если вам удастся что-нибудь прочитать, вы узнаете, столько байтов ожидало чтени
> Смотри также -------------------------------
Рецепт 7.14; страница руководства ioctl(1) вашей системы; описание функции iocti в perlfunc(1).
Вы собираетесь использовать файловый манипулятор как обычную переменную, чтобы его можно было передать или вернуть из функции, сохранить в структуре данных и т. д.
Решение
Если у вас уже имеется символьный файловый манипулятор (например, STDIN или LOGFILE), воспользуйтесь записью тип-глоба, *FH. Такой подход является самым эффективным.
$vaciable = *FILEHANDLE; # Сохранить в переменной
subroutine(*FILEHANDLE); # или передать функции
sub subroutine {
my $fh = shift;
print $fh "Hello, filehandle!\n";
} Если вы хотите работать с анонимным файловым манипулятором, воспользуйтесь функцией return_fh (см. ниже) или новыми методами модулей IO::File или IO::Handle, сохраните его в скалярной переменной и используйте так, словно это обычный файловый манипулят
use FileHandle; # Анонимные манипуляторы $fh = FileHandle->new();
use IO::File; # 5.004 и выше $fh = 10::File->new();
Комментарий
Существует немало способов передать файловый манипулятор функции или сохранить его в структуре данных. Самое простое и быстрое решение заключается в применении тип-глоба, *FH. Рассматривайте запись *FH как обозначение типа файлового манипулятора, подобно
Когда вы начнете понимать недостатки этой модели, она вам уже не понадобится.
Конечно, в простых ситуациях этого вполне достаточно, но что если вам потребовался массив файловых манипуляторов с неизвестными именами? Как показано в главе 11 "Ссылки и записи", построение анонимных массивов, хэшей и даже функций во время выполнения про
Метод new модуля IO::Handle или IO::File генерирует анонимный 4^йловый манипулятор. Его можно передать функции, сохранить в массиве и вообще применять везде, где используются именованные тип-глобы файловых манипуляторов - и не только. Эти модули также мог
Объекты могут косвенно использоваться в качестве файловых манипуляторов, что избавляет вас от необходимости придумывать для них имена.
Чтобы получить тип-глоб из именованного файлового манипулятора, снабдите его префиксом *:
$fh_a = 10::File->new("< /etc/motd") or die "open /etc/motd: $!";
$fh_b = *STDIN;
some_sub($fh_a, $fh_b); Существуют и другие способы, но этот проще и удобнее всех остальных. Един ственное ограничение - в том, что его нельзя превратить в объект вызовом bless. Bless вызывается для ссылки на тип-глоб - именно это и происходит в IO::Handle. Ссылки на тип-гло
Создание и возврат нового файлового манипулятора из функции происходит следующим образом:
sub return_fh { # Создание анонимных файловых манипуляторов
local *FH; # Должны быть local, не my Я now open it if you want to, #then...
return *FH:
}
$handle = return_fh(); Функция, получающая файловый манипулятор в качестве аргумента, может либо сохранить его в переменной (желательно лексической) и затем косвенно использовать его:
sub accept_fh {
my $fh = shift;
print $fh "Sending to indirect filehandle\n";
}
in6o локализовать тип-глоб и использовать файловый манипулятор напрямую:
sub accept_fh {
local *FH = shift;
print FH "Sending to localized filehandle\n";
}
Оба варианта работают как с объектами IO::Handle, так и с тип-глобами и настоящими файловыми манипуляторами:
accept_fh(*STDOUT);
accept_fh($handle); Perl позволяет использовать строки, тип-глобы и ссылки на тип-глобы в качестве косвенных файловых манипуляторов, но без передачи тип-глобов или объектов IO::Handle можно нарваться на неприятности. Применение строк ("LOGFILE" вместо * LOGFILE) между па
В предыдущих примерах файловый манипулятор перед использованием присваивался скалярной переменной. Дело в том, что во встроенных функциях (print или printf) или в операторе о могут использоваться только простые скалярные переменные, но не выражения или эл
Ofd = (.STDIN, *STDOUT, *STDERR);
print $fd[1] "Type it: "; # НЕВЕРНО
$got = <$fd[0]> # НЕВЕРНО
print $fd[2] "What was that: $got"; # НЕВЕРНО
В print и printf это ограничение удается обойти - воспользуйтесь блоком ii выражением, в котором находится файловый манипулятор:
print { $fd[1] } "funny stuff\n";
printf { $fd[1] } "Pity the poor %x.\n", 3_735_928_559;
Pity the poor deadbeef. Внутри блока может находиться и более сложный код. Следующий фрагмент отправляет сообщение в один из двух адресов:
$ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2] } "cat stat $ok\n";
print { $fd[ 1 + ($ok || 0) ] } "cat stat $ok\n"; Подход, при котором print и printf интерпретируются как вызовы методов объекта, не работает для оператора о, поскольку это настоящий оператор, а не вызов функции с аргументом без запятых. Если тип-глобы сохранены в структуре, как это было сделано выше
$got = readline($fd[0]);
> Смотри также -------------------------------
Рецепт 7.1; документация по стандартному модулю FileHandle; описание функции open в perlfunc(1).
Требуется одновременно открыть больше файлов, чем позволяет ваша система.
Решение
Воспользуйтесь стандартным модулем FileCache:
use FileCache;
cacheout ($path); # При каждом применении манипулятора
print $path "output";
Комментарий
Функция cacheout модуля FileCache позволяет одновременно открывать больше файлов, чем позволяет операционная система. Если воспользоваться ей для открытия существующего файла, который FileCache видит впервые, этот файл без лишних вопросов усекается до нул
Функция cacheout() проверяет значение константы NO FILE уровня С из стандартного заголовочного файла sys/params.h, чтобы определить, сколько файлов разрешается открывать одновременно. В некоторых системах это значение может быть неверным или вовсе отс
В примере 7.8 файл xferlog, создаваемый популярным ЕТР-сервером wuftpd, разбивается на файлы, имена которых соответствуют именам пользователей. Поля файла xferlog разделяются пробелами; имя пользователя хранится в четвертом поле с конца.
Пример 7.8. splitwulog
#!/usr/bin/perl
# splitwulog - разделение журнала wuftpd по именам пользователей
use FileCache;
$outdir = '/var/log/ftp/by-user';
while (<>) {
unless (defined ($user = (split)[-4])) { warn "Invalid line: $.\n";
next;
}
$path = "$outdir/$user";
cacheout $path;
print $path $_;
}
> Смотри также ------
Документация по стандартному модулю FileCache; описание функции open в perlfunc(1).
Одни и те же данные требуется вывести через несколько разных файловых манипуляторов.
Решение
Если вы предпочитаете обходиться без создания новых процессов, напишите цикл f о reach для перебора файловых манипуляторов:
foreach $filehandle (OFILEHANDLES) { print $filehandle $stuff_to_print;
} Если новые процессы вас не пугают, откройте файловый манипулятор, связав его с программой tee:
open(MANY, "| tee file"! file2 file3 > /dev/null") or die $!;
print MANY "data\n" or die $!;
close(MANY) or die $!;
Комментарий
Файловый манипулятор передает выходные данные лишь одному файлу или программе. Чтобы дублировать вывод, следует многократно вызвать print или связать манипулятор с программой распределения выходных данных (например, tee). В первом варианте проще всего зан
# 'use strict' пожалуется на эту команду:
for $fh ('FH1', 'FH2', 'FH3') { print $fh "whatever\n" }
# но не возразит против этой:
for $fh (*FH1, *FH2, *FH3) { print $fh "whatever\n" }
Но если ваша система включает программу tee или вы установили Perl-версию tee из рецепта 8.19, можно открыть канал к tee и поручить ей всю работу по копированию файла в несколько приемников. Не забывайте, что tee обычно ко-
пирует выходные данные в STDOUT; если лишняя копия данных вам не нужна, перенаправьте стандартный вывод tee в /(lev/null:
open (FH, "| tee file-l file2 file3 >/dev/null");
print FH "whatever\n";
Вы даже можете перенаправить процессу tee свой собственный STDOUT и использовать при выводе обычную функцию print:
# Продублировать STDOUT в трех файлах с сохранением исходного STDOUT
open (STDOUT, "| tee file1 file2 file3") or die "Teeing off: $!\n";
print "whatever\n" or die "Writing: $!\n";
close(STDOUT) or die "Closing: $!\n";
> Смотри также -------------------------------
Описание функции print в perlfunc(1). Аналогичная методика используется в рецептах 8.19 и 13.15.
Вам известны файловые дескрипторы, через которые должен выполняться ввод/вывод, но Perl вместо числовых дескрипторов требует манипуляторы.
Решение
Для открытия файлового дескриптора воспользуйтесь режимами "<&=" и "<&" или методом fdopen модуля io::handle:
open(FH, "<&=$fdnum"); # fh открывается для дескриптора
open(FH, "<&$fdnum"); # fh открывается для копии дескриптора
use 10::Handle;
$fh->fdopen($FDNUM, "r"); # Открыть дескриптор 3 для чтения Чтобы закрыть дескриптор, воспользуйтесь функцией POSIX: : close или открой-г его описанным выше способом.
Комментарий
Иногда вам известен файловой дескриптор, а не манипулятор. В системе ввода/ вывода Perl вместо дескрипторов используются манипуляторы, поэтому для уже открытого файлового дескриптора придется создать новый манипулятор. Режимы open "<&", ">&" и "+<&" решаю
что нужно. Дело в том, что они используют лишь функцию fdopen уровня С без системной функции dup2.
Если у вас установлена версия perl 5.004 и выше, воспользуйтесь методом объекта io::handle:
use 10::Handle;
$fh = 10::Handle->new();
$fh->fdopen(3, "г"); # Открыть fd 3 для чтения
Закрытие числовых файловых дескрипторов встречается еще реже. Задача напрямую решается функцией POSIX: : close. Если в вашей системе нет библиотеки POSIX, но зато имеется работающая функция syscall (и ваш системный администратор установил файл sys/syscall
Ниже показано, как открыть файловые дескрипторы, которые почтовая система МН передает своим дочерним процессам. Дескрипторы идентифицируются по переменной окружения MHCONTEXTFD:
$fd = $ENV{MHCONTEXTFD};
open(MHCONTEXT, "<&=$fd") or die "couldn't fdopen $fd: $!";
# after processing close(MHCONTEXT)
or die "couldn't close context file: $!"; Чтобы закрыть дескриптор по числовому значению, достаточно сначала вызвать для него open.
> Смотри также --------------------------------
Документация но стандартным модулям POSIX и IO::Handle; страница руководства fdopen(3) вашей системы; описание функции open в perlfunc{1).
Синоним файлового манипулятора создается следующей командой:
"СИНОНИМ = *ОРИГИНАЛ;
Чтобы создать независимую копию файлового дескриптора для существующего манипулятора, воспользуйтесь функцией open в режиме &:
open(OUTCOPY, ">&STDOUT") or die "Couldn't dup S.TDOUT: $!";
open(INCOPY, "<&stdin" ) or die "couldn't dup stdin : $!"; Чтобы создать синоним файлового дескриптора для существующего манипулятора, воспользуйтесь функцией open в режиме &=:
open(OUTALIAS, ">&=STDOUT") or die "Couldn't alias STDOUT: $!";
open(INALIAS, "<&=stdin") or die "couldn't alias stdin : $!oo;
open(BYNUMBER, ">&=5") or die "Couldn't alias file descriptor 5: $!";
Комментарий
Если синоним манипулятора создан с помощью тип-глоба, программа по-прежнему работает лишь с одним объектом ввода/вывода Perl. При закрытии манипулятора-синонима закрывается и объект ввода/вывода. Все последующие попытки использования копий этого манипулят
При копировании дескриптора командой ореп(КОПИЯ, ">&МАНИПУЛЯТОР") вызывается системная функция dup(2). Вы получаете два независимых дескриптора с общей текущей позицией, блокировкой и флагами, но разными буферами ввода/вывода. Закрытие одного дескриптора
# Получить копии дескрипторов
open(OLDOUT, ">&STDOUT");
open(OLDERR, ">&STDERR");
# Перенаправить stdout и stderr
open(STDOUT, "> /Imp/program.out") or die "Can't redirect stdout: $!";
open(STDERR, ">&STDOUT") or die "Can't dup stdout: $!";
# Запустить программу system($joe_random_program):
# Закрыть измененные манипуляторы
close(STDOUT) or die "Can't close STDOUT: $!";
close(STDERR) or die "Can't close STDERR: $!";
# Восстановить stdout и stderr
open(STDERR, ">&OLDERR") or die "Can't restore stderr: $!";
open(STDOUT, ">&OLDOUT") or die "Can't restore stdout: $!";
# Для надежности закрыть независимые копии
close(OLDOUT) or die "Can't close OLDOUT: $!";
close(OLDERR) or die "Can't close OLDERR: $!": Если синоним дескриптора создается командой ореп(СИНОНИМ, ">&=МАНИПУЛЯ-ОР"), в действительности вызывается системная функция ввода/вывода fdopen(3V
Вы получаете один файловый дескриптор с двумя буферами, доступ к которым осу ществляется через два манипулятора. Закрытие одного манипулятора закрыва ет дескрипторы синонимов, но не манипуляторы - если вы попытаетесь вызва'1 [ print для манипулятора с зак
> Смотри также -------------------------------
Страница руководства dup(T) вашей системы; описание функции open в perlfunc(1).
При блокировке файлов мы рекомендуем по возможности использовать функ цию flock. К сожалению, в некоторых системах блокировка через flock ненадеж на. Допустим, функция flock может быть настроена на вариант блокировки поддержки сети или вы работаете в одн
Приведенная ниже программа и модуль содержат базовую реализацию механизма блокировки файлов. В отличие от обычной функции flock, данный модуль блокирует файлы по именам, а не по дескрипторам.
Следовательно, он может применяться для блокировки каталогов, сокетов и других нестандартных файлов. Более того, вы даже сможете блокировать несуществующие файлы. При этом используется каталог, созданный в иерархии на одном уровне с блокируемым файлом, по
Функция n flock вызывается с одним или двумя аргументами. Первый определяет имя блокируемого файла; второй, необязательный - промежуток времени, в течение которого происходит ожидание. Функция возвращает true при успешном предоставлении блокировки и false
Присвойте true переменной $File: : LockDir: : Debug, чтобы модуль выдавал сообщения при неудачном ожидании. Если вы забудете снять блокировку, при выходе из программы модуль снимет ее за вас. Этого не произойдет, если ваша программа получит неперехваченны
Вспомогательная программа из примера 7.9 демонстрирует применение модуля File::LockDir.
Пример 7.9. drivelock
#!/usr/bin/perl -w
# drivelock - демонстрация модуля File::LockDir
use strict;
use File::LockDir;
$SIG{INT} = sub { die "outta here\n" };
$File::LockDir::Debug = 1;
my $path = shift or die "usage: $0 \n";
unless (nflock($path, 2)) {
die "couldn't lock $path in 2 seconds\n";
}
sleep 100;
nunflock($path);
Исходный текст модуля приведен в примере 7.10. За дополнительными сведениями о построении модулей обращайтесь к главе 12 "Пакеты, библиотеки и модули".
Пример 7.10. File: :LockDir
package File::LockDir;
# Модуль, обеспечивающий простейшую блокировку
# на уровне имен файлов без применения хитрых системных функций.
# Теоретически информация о каталогах синхронизируется в NFS.
# Стрессовое тестирование не проводилось.
use strict;
use Exporter;
use vars qw((o)ISA OEXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(nflock nunflock);
use vars qw($Debug $Check);
$Debug |= 0; # Может определяться заранее
$Check ||= 5; # Может определяться заранее
use Cwd;
use Fcnti;
use Sys::Hostname;
use File::Basename;
use File::stat;
use Carp;
my %Locked_Files = ();
# Применение:
nflock(OAI?lJ1; ТАЙМАУТ)
sub nflock($;$) {
my $pathname = shift;
my $naptime = shift || 0;
my $lockname = name21ock($pathname);
my $whosegot = "$lockname/owner";
my $start = time();
my $missed = 0;
local *OWNER;
# Если блокировка уже установлена, вернуться
if ($Locked_Files{$pathname}) {
carp "$pathname already locked";
return 1
}
if (!-w dirname($pathname)) {
croak "can't write to directory of $pathname'
}
while (1) {
last if mkdir($lockname, 0777);
confess "can't get $lockname: $!" if $missed++ > 10 && !-d $lockname;
if ($Debug) {{
open(OWNER, "< $whosegot") || last; # exit "if"! my $lockee = ;
chomp($lockee):
printf STDERR "%s $0\[$$]; lock on %s held by %s\n", scalar(localtime), $pathname, $lockee;
close OWNER;
}} sleep $Check;
return if $naptime && time > $start+$naptime;
} sysopen(OWNER, $whosegot, 0_WRONLY|0_CREAT|O.EXCL)
or croak "can't create $whosegot:
$! printf OWNER "$0\[$$] on
%s since %s\n", hostname(), scalar(localtime);
close(OWNER)
or croak "close $whosegot: $!";
$Locked_Files{$pathname}++;
return 1;
}
# Освободить заблокированный файл
sub nunflock($) {
my $pathname = shift;
my $lockname = name21ock($pathname);
my $whosegot = "$lockname/owner";
unlink($whosegot);
carp "releasing lock on $lockname" if $Debug;
delete $Locked_Files{$pathname};
return rmdir($lockname);
}
# Вспомогательная функция
sub name21ock($) {
my $pathname = shift;
my $dir = dirname($pathname);
my $file = basename($pathname);
$dir = getcwd() if $dir eq '.';
my $lockname = "$dir/$file.LOCKDIR' return $lockname;
}
}
# Ничего не забыли?
END {
for my $pathname (keys %Locked_Files)
{ my $lockname = name21ock($pathname);
my $whosegot = "$lockname/owner";
carp "releasing forgotten $lockname";
unlink($whosegot);
return rmdir($lockname);
}
} 1
Функция Perl flock блокирует только целые файлы, но не отдельные их области. Хотя fcnti поддерживает частичную блокировку файлов, из Perl с ней работать трудно - в основном из-за отсутствия модуля XS, который бы обеспечивал переносимую упаковку необходимо
Программа из примера 7.11 реализует fcnti, но лишь для трех конкретных архитектур: SunOS, BSD и Linux. Если вы работаете в другой системе, придется узнать формат структуры flock. Для этого мы просмотрели заголовочный файл С sys/ fcntl.h и запустили програ
Функция struct_flock в программе lockarea выполняет упаковку и распаковку структуры, руководствуясь переменной $"0 с именем операционной системы. Объявления функции struct_flock не существует, мы просто создаем синоним для версии, относящейся к конкретной
Программа lockarea открывает временный файл, уничтожая его текущее содержимое, и записывает в него полный экран (80х23) пробелов. Все строки имеют одинаковую длину.
Затем программа создает производные процессы и предоставляет им возможность одновременного обновления файла. Первый аргумент, N, определяет количество порождаемых процессов (2**М). Следовательно, lockarea 1 порождает два процесса, lockarea 2 - четыре, loc
и т. д. С увеличением числа потомков возрастает конкуренция за блокировку участков файла.
Каждый процесс выбирает из файла случайную строку, блокирует и обновляет ее. Он записывает в строку свой идентификатор процесса с префиксом - количеством обновлений данной строки:
4: 18584 was just here
Если в момент запроса блокировки строка уже была заблокирована, то после предоставления блокировки в сообщение включается идентификатор предыдущего процесса:
29: 24652 ZAPPED 24656
Попробуйте запустить программу lockarea в фоновом режиме и отображайте изменения файла с помощью программы rep из главы 15. Получается видеоигра для системных программистов.
%lockarea 5 & % rep -1 'cat /tmp/lkscreen' Если работа основной программы прерывается клавишами Ctrl+C или сигналом SIGINT из командной строки, она уничтожает всех своих потомков, посылая сигнал всей группе процессов.
Пример 7.11. lockarea
#!/usr/bin/perl -w
# lockarea - частичная блокировка с использованием fcnti
use strict;
my $FORKS = shift || 1;
my $SLEEP = shift | | 1;
use Fcnti;
use POSIX qw(:unistd_h ;errno_h):
my $COLS = 80;
my $ROWS = 23;
# Когда вы в последний раз видели *этот* режим правильно работающим?
open(FH, "+> /tmp/lkscreen") or die $!;
select(FH);
$| = 1;
select STDOUT;
# Очистить экран
for (1 .. $ROWS) {
print FH " " x $COLS, "\n";
}
my $progenitor = $$;
fork while $FORKS-- > 0;
print "hello from $$\n";
if ($progenitor == $$) {
$SIG{INT} = \&genocide;
} else {
$SIG{INT} = sub { die "goodbye from $$" }:
}
while (1) {
my $line_num = int rand($ROWS), my $line;
my $n;
# Перейти к строке
seek(FH, $n = $line_num * ($COLS+1), SEEK_SET) or next;
# Получить блокировку
my $place = tell(FH);
my $him;
next unless defined($him = lock(*FH, $place, $COLS));
# Прочитать строку
read(FH, $line, $COLS) == $COLS or next;
my $count = ($line =~ /(\d+)/) ? $1 : 0;
$count++;
# Обновить строку
seek(FH, $place, 0) or die $!
my $update = sprintf($him
? "%6d: %d ZAPPED %d" : "%6d: %d was just here", $count, $$, $him);
my $start = int(rand($COLS - length($update)));
die "XXX" if $start + length($update) > $COLS;
printf FH "%*.*s\n", -$COLS, $COLS, " " x $start . $update;
# Снять блокировку и сделать паузу
unlock(*FH, $place, $COLS);
sleep $SLEEP if $SLEEP;
} die "NOT REACHED"; # На всякий случай
lock($handle, $offset, $timeout) - get an fcnti lock sub lock {
my ($fh, $start, Still) = @_;
##print "$$: Locking $start, $till\n";
my $lock = struct_flock(F_WRLCK, SEEK_SET, $start, $till, 0);
my $blocker = 0;
unless (fcntl($fh, F_SETLK, $lock)) {
die "F_SETLK $$ @_: $!oo unless $! == EAGAIN || $! == EDEADLK;
fcntl($fh, F_GETLK, $lock)
or die "F_GETLK $$ @_: $! $blocker = (struct_flock($lock))[-1];
##print "lock $$ @_: waiting for $blocker\n";
$lock = struct_flock(F_WRLCK, SEEK_SET, $start, $till, 0);
unless (fcntl($fh, F_SETLKW, $lock)) { warn "F_SETLKW $$ @>_: $!\n";
return; # undef
}
}
return Sblocker;
}
# unlock($handle, $offset, $timeout) - снять блокировку fcnti
sub unlock {
my ($fh, $start, $till) = @_;
##print "$$: Unlocking $start, $till\n";
my $lock - struct_flock(F_UNLCK, SEEK_SET, $start, $till, O):
fcntl($fh, F_SETLK, $lock) or die "F_UNLCK $$ @_: $!";
}
# Структуры flock для разных ОС
# Структура flock для Linux
# short 1_type;
# short 1_whence;
# off_t 1_start;
# off_t 1_len;
# pid_t 1_pid;
BEGIN {
# По данным c2ph:
typedef='s2 12 i', sizeof=16
my $FLOCK_STRUCT = os s 1 1 i';
sub linux_flock { if (wantarray) {
my ($type, $whence, Sstart, $len, $pid) = unpack($FLOCK_STRUCT, $_[0]);
return ($type, $whence, $start, $len, $pid);
} else {
my ($type, $whence, $start, $len, $pid) = @_;
return pack($FLOCK_STRUCT,
$type, $whence, $start, $len, $pid);
}
}
}
# Структура flock для SunOS
# short 1_type; /* F_RDLCK, F_WRLCK или F_UNLCK */
# short 1_whence; /* Флаг выбора начального смещения */
# long 1_start; /* Относительное смещение в байтах */
# long 1_len; /* Длина в байтах;
О - блокировка до EOF */
# short 1_pid; /* Возвращается F_GETLK "/
# short 1_xxx; /* Зарезервировано на будущее */
BEGIN {
# По данным c2ph: typedef='s2 12 s2', sizeof=16
my $FLOCK_STRUCT = os s 1 1 s s':
sub sunos_flock { If (wantarray) {
my ($type, $whence, $start, $len, $pid, $xxx) = unpack($FLOCK_STRUCT, $_[0]);
return ($type, $whence, $start, $len, $pid);
} else {
my ($type, $whence, $start, $len, $pid) = @>_;
return pack($FLOCK_STRUCT,
$type, $whence, $start, $len, $pid, 0);
}
}
}
# Структура flock для (Free)BSD:
# off_t 1_start; /* Начальное смещение "/
# off_t 1_len; /* len = 0 означает блокировку до конца файла */
# pid_t 1_pid; /* Владелец блокировки */
# short 1_type; /* Тип блокировки: чтение/запись и т. д. */
# short 1_whence; /* Тип 1_start */
BEGIN {
# По данным c2ph: typedef="q2 i s2", size=24
my $FLOCK_STRUCT = oII 11 i s s-;
sub bsd_flock {
if (wantarray) {
my ($xxstart, $start, $xxlen, $len, $pid, $type, $whence) = unpack($FLOCK_STRUCT, $_[0]);
return ($type, $whence, $start, $len, $pid);
} else {
my ($type, $whence, $start, $len, $pid) = @>_;
my ($xxstart, $xxlen) = (0,0);
return pack($FLOCK_STRUCT,
$xxstart, $start, $xxlen, $len, $pid, $type, $whence);
}
}
}
# Синоним структуры fcnti на стадии компиляции
BEGIN {
for ($-0) {
*struct_flock = do { /bsd/ && \&bsd_flpck
/linux/ && \&linux_flock /
sunos/ && \&sunos_flock die "unknown operating system $"0, bailing out";
};
}
}
# Установить обработчик сигнала для потомков
BEGIN {
my $called = 0;
sub genocide {
exit if $called++;
print "$$: Time to die, kiddies.\n" if $$ == $progenitor;
my $job = getpgrpO;
$SIG{INT} = 'IGNORE';
kill -2, $]ob if $job; # killpg(SIGINT, job) 1 while wait > 0;
print "$$: My turn\n" if $$ == $progenitor;
exit:
}
}
END { &genocide }