ИМЯ¶
fcntl -
манипуляции
с файловым
дескриптором
ОБЗОР¶
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
ОПИСАНИЕ¶
fcntl выполняет
одну из
различных
дополнительных
операций над
файловым
дескриптором
fd. Эта
операция
определяется
содержимым
аргумента
cmd.
Управление
close-on-exec¶
- F_DUPFD
- Ищет
наименьший
доступный
номер
файлового
дескриптора,
который
больше arg и
делает его
копией
дескриптора
fd.
Фактически
это другая
форма
вызова dup2(2)
которая
используется
с явным
указанием
файлового
дескриптора.
Старый и
новый
дескрипторы
могут
использоваться
равнозначно.
Они
разделяют
одни и те же
блокировки,
указатели
на позиции в
файле и
флаги;
например,
если позция
в файле
изменяется
с помощью lseek
для одного
из
дескрипторов,
то эта же
позиция
также будет
изменена и
для другого.
Однако,
данные два
дескриптора
не
разделяют
флаг close-on-exec. Флаг
close-on-exec в копии
выключен.
Это
означает,
что
дескриптор
не будет
закрыт в
случае
вызова exec.
При
успешном
выполнении
этой
операции,
возвращается
новый
файловый
дескриптор.
- F_GETFD
- Читает
флаг close-on-exec. Если
бит FD_CLOEXEC
установлен
в 0, то файл
будет
оставлен
открытым
при вызове
exec, в
противном
случае он
будет
закрыт.
- F_SETFD
- Устанавливает
флаг close-on-exec в
значение,
заданное
битом FD_CLOEXEC
аргумента
arg.
Флаги
состояния
файла¶
Любой
файловый
дескриптор
имеет
несколько
связанных с
ним флагов,
которые
инициализируются
вызовом
open(2) и,
возможно,
изменяются
затем
вызовом
fcntl(2).
Эти флаги
разделяются
между
копиями
(сделанными
с помощью
dup(2),
fork(2), и других
вызовов)
этого же
файлового
дескриптора.
Эти флаги и их
смысл
описываются
на странице
руководства
open(2).
- F_GETFL
- Читает
флаги
файлового
дескриптора.
- F_SETFL
- Устанавливает
часть
флагов,
относящихся
к состоянию
файла,
согласно
значению,
указанному
в аргументе
arg.
Оставшиеся
биты (режим
доступа,
флаги
создания
файла) в
значении arg
игнорируются.
В Linux данная
команда
может
изменять
только
флаги O_APPEND, O_NONBLOCK, O_ASYNC и
O_DIRECT.
Совместная
(advisory)
блокировка¶
F_GETLK,
F_SETLK и
F_SETLKW
используются
для
установки,
снятия и
тестирования
существующих
блокировок
записи
(также
известных
как
блокировки
сегмента
файла или
области
файла).
Третий
аргумент
lock
является
указателем
на
структуру,
которая
имеет по
крайней мере
следующие
поля (в
произвольном
порядке).
struct flock {
...
short l_type; /* Тип блокировки: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* Как интерпретировать l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Начальное смещение для блокировки */
off_t l_len; /* Количество байт для блокировки */
pid_t l_pid; /* PID процесса блокирующего нашу блокировку
(F_GETLK only) */
...
};
Поля
l_whence,
l_start и
l_len этой
структуры
задают
диапазон
байт,
который мы
хотим
заблокировать.
l_start - это
начальное
смещение для
блокировки,
которое
интерпретируется
как: начало
файла (если
значение
l_whence
установлено
в
SEEK_SET); текущая
позиция в
файле (если
значение
l_whence
установлено
в
SEEK_CUR); или
конец файла
(если
значение
l_whence
установлено
в
SEEK_END). В
последних
двух
случаях,
l_start
может иметь
отрицательное
значение,
предоставляя
смещение,
которого не
может быть
перед
началом
файла.
l_len -это
неотрицательное
целое число
(но см.
ЗАМЕЧАНИЯ
ниже),
которое
задаёт
количество
байт,
которые
будут
заблокированы.
Байты
следующие
после конца
файла могут
быть
заблокированы,
но это
нельзя
сделать для
байтов,
которые
находятся
перед
началом
файла.
Значение 0
для
l_len имеет
специальное
назначение:
блокировка
всех байтов,
начиная от
позиции,
заданной
l_whence
и
l_start до конца
файла, не
зависимо от
того,
насколько
велик файл.
Поле
l_type может
быть
использовано
для указания
типа
блокировки:
чтение (
F_RDLCK)
или запись
(
F_WDLCK).
Блокировку
на чтение
(разделяемая
блокировка)
на область
файла может
удерживать
любое
количество
процессов,
но только
один процесс
может
удерживать
блокировку
на запись
(эксклюзивная
блокировка).
Любая
эксклюзивная
блокировка
исключает
все другие
блокировки,
как
разделяемые
так и
эксклюзивные.
Один процесс
может
удерживать
только один
тип
блокировки
области
файла; если
происходит
новая
блокировка
на уже
заблокированную
область, то
существующая
блокировка
преобразуется
в новый тип
блокировки.
(Такие
преобразования
могут
привести к
разбиению,
уменьшению
или
срастанию с
существующей
блокировкой,
если
диапазон
байт,
заданный для
новой
блокировки
неточно
совпадает с
диапазоном
существующей
блокировки.)
- F_SETLK
- Установить
блокировку
(когда l_type
установлено
в значение
F_RDLCK или F_WRLCK) или
снять
блокировку
(когда l_type
установлено
в значение
F_UNLCK) или байты,
заданны с
помощью
полей l_whence, l_start
и l_len
структуры
lock. Если
конфликтующая
блокировка
удерживается
другим
процессом,
то данный
вызов
вернёт -1 и
установит
значение errno
в EACCES или EAGAIN.
- F_SETLKW
- Если
устнаовлен
F_SETLK, но для
файла
удерживается
конфликтующая
блокировка,
то
происходит
ожидание
снятия
блокировки.
Если во
время
ожидания
поступил
сигнал, то
данный
вызов
прерывается
и (после
возврата из
обработчика
сигнала) из
него
происходит
немедленный
возрат (где
возвращаемое
значение
установлено
в -1, а errno
установлено
в значение
EINTR).
- F_GETLK
- При входе
в этот вызов,
lock
описывает
блокировку,
которую мы
должны бы
установить
на файл. Если
такая
блокировка
не может
быть
установлена,
fcntl() по факту
не
устанавливает
её, но
возвращает
F_UNLCK в поле l_type
структуры
lock и
оставляет
другие поля
структуры
неизменёнными.
Если одна
или более
несовместимых
блокировок
мешают
установке
нашей
блокировки,
то fcntl()
возвращает
подробности
об одной из
этих
блокировок
в полях l_type,
l_whence, l_start и l_len
структуры
lock и
устанавливает
l_pid в
значение PID
того
процесса,
который
удерживает
блокировку.
Для того,
чтобы
установить
блокировку
на чтение,
fd
должен быть
открыт на
чтение. Для
того, чтобы
установить
блокировку
на запись,
fd
должен быть
открыт на
запись.
Чтобы
установить
оба типа
блокировки,
дескриптор
должен быть
открыт на
запись и на
чтение.
Также, как и
при снятии
блокировки
через явное
указание
F_UNLCK,
блокировка
автоматически
снимается,
когда
процесс
завершается
или если он
закрывает
любой
файловый
дескриптор,
ссылающийся
на файл, на
котором
удерживается
блокировка.
Это плохо:
это
означает,
что процесс
может
потерять
блокировки
на файлах
типа
/etc/passwd или
/etc/mtab, когда по
какой-либо
причине
библиотечная
функция
производит
их открытие,
чтение и
закрытие.
Блокировки
не
наследуются
процессом-потомком,
созданным
через
fork(2), но
сохраняются
при вызове
execve(2).
Поскольку
буферизация,
выполняется
через
библиотеку
stdio(3),
использование
блокировок с
функциями в
этом пакете
нужно
избегать;
вместо этих
функций
используйте
read(2) и
write(2).
Обязательная
(mandatory)
блокировка¶
(Не-POSIX.)
Описанные
выше
блокировки
могут быть
или
совместные
или
обязательные
и по
умолчанию
используются
совместные.
Чтобы
использовать
обязательные
блокировки,
они должны
быть
разрешены (с
помощью
опции "-o mand" в
mount(8)) для
файловой
системы,
содержащей
файл,
который
должен быть
заблокирован,
а также
разрешены
для самого
файла (с
помощью
запрещения
прав на
выполнение
группой и
установки set-GID
бита в
правах
доступа к
файлу).
Совместные
блокировки
не являются
принудительными
и полезны
только между
сотрудничающими
процессами.
Обязательные
блокировки
являются
принудительными
для всех
процессов.
Управление
сигналами¶
F_GETOWN,
F_SETOWN,
F_GETSIG и
F_SETSIG
используются
для
управления
сигналами
доступности
ввода/вывода:
- F_GETOWN
- Получить
идентификатор
процесса
или группу
процесса,
которые в
текущий
момент
принимают
сигналы SIGIO и SIGURG
для событий
на файловом
дескрипторе
fd. Группы
процессов
возвращаются
в виде
отрицательных
значений.
- F_SETOWN
- Установить
идентификатор
процесса
или группу
процесса,
которые
будут
принимать
сигналы SIGIO и SIGURG
для событий
на файловом
дескрипторе
fd. Группы
процессов
задаются в
виде
отрицательных
значений. (F_SETSIG
может
использоваться,
чтобы
задать
другой
сигнал
вместо SIGIO).
Если вы
установили
на файловый
дескриптор
флаг
состояния
O_ASYNC (или через
предоставление
этого флага
в вызове open(2),
или
используя
команду F_SETFL
вызова fcntl), то
сигнал SIGIO
посылается
всякий раз,
когда для
данного
файлового
дескриптора
становится
возможным
ввод или
вывод.
Процесс или
группа
процесса,
для приёма
сигнала
могут быть
выбраны,
используя
команду F_SETOWN в
вызове fcntl.
Если
файловым
дескриптором
является
сокет, то для
него также
выбирается
получатель
сигналов SIGURG,
которые
доставляются,
когда на
сокет
поступает
данных
больше, чем
его
пропускная
способность.
(SIGURG
посылается
во всех
ситуациях,
когда вызов
select(2) говорит,
что сокет
находится в
состоянии
"исключительной
ситуации").
Если
файловый
дескриптор
соответствует
терминальному
устройству,
то группе
процессов,
которые
работают с
терминалом
не в фоновом
режиме
будут
посылаться
сигналы SIGIO.
- F_GETSIG
- Получить
сигнал,
который
будет
послан,
когда
станет
возможен
ввод или
вывод.
Значение 0
означает
сигнал SIGIO.
Любое
другое
значение
(включая SIGIO)
является
другим
сигналам,
посылаемым
вместо SIGIO и в
это случае,
для
обработчика
сигнала
доступна
дополнительная
информация,
если он был
установлен
с SA_SIGINFO.
- F_SETSIG
- Установить
сигнал,
который
будет
послан,
когда
станет
возможен
ввод или
вывол.
Значение 0
означает
сигнал SIGIO.
Любое
другое
значение
(включая SIGIO)
является
другим
сигналам,
посылаемым
вместо SIGIO и в
это случае,
для
обработчика
сигнала
доступна
дополнительная
информация,
если он был
установлен
с SA_SIGINFO.
В случае
использования
F_SETSIG с
ненулевым
значением и
установкой
SA_SIGINFO для
обработчика
сигнала (см.
sigaction(2)),
обработчику
передаётся
дополнительная
информация
о событиях
ввода/вывода
в структуре
siginfo_t. Если
поле si_code
показывает,
что
сигналом
является SI_SIGIO,
то поле si_fd
содержит
файловый
дескриптор,
ассоциированный
с собрытием
вывода/вывода.
В противном
случае, не
существует
никакого
механизма,
чтобы
сообщить с
каким
файловым
дескриптором
связан
полученный
сигнал и вы
должны
использовать
(select(2), poll(2), read(2) с
установленным
флагом O_NONBLOCK и
т.д.) чтобы
определить
какой
файловый
дескриптор
доступен
для
ввода/вывода.
При выборе
сигнала
реального
времени (value >= SIGRTMIN)
как описано
в POSIX.1b, в
очередь
будут
добавляться
несколько
событий
ввода
вывода, с
теми же
номерами
сигналов.
(Размер
очереди
зависит от
доступной
памяти).
Дополнительная
информация
будет
доступна
как описано
выше, если
для
обработчика
сигнала
будет
установлено
SA_SIGINFO.
Используя
эти
механизмы,
программа
может
реализовать
полностью
асинхронный
ввода/вывод
без
использования
в своей
работе
select(2)
или
poll(2).
Использование
O_ASYNC,
F_GETOWN,
F_SETOWN
является
специфичным
для BSD и Linux.
F_GETSIG и
F_SETSIG являются
специфичными
для Linux. POSIX
описывает
асинхронный
ввод/вывод и
структуру
aio_sigevent
используемую
для сходных
вещей;
которые
также
доступны в Linux
как часть
библиотеки GNU C
(Glibc).
Аренда¶
F_SETLEASE и
F_GETLEASE (в Linux 2.4 и
выше)
используются
(соответственно)
для
установки и
получения
текущих
настроек
вызывающего
процесса,
арендующего
файл, на
который
указывает
fd.
Аренда файла
предоставляет
такой
механизм,
когда
процесс,
удерживающий
аренду
("держатель
аренды")
уведомляется
(через
доставку
сигнала), о
том, что
другой
процесс
("конкурент")
пытается
выполнить
open(2)
или
truncate(2) для
этого файла.
- F_SETLEASE
- Устанавливает
или удаляет
аренду
файла, в
соответствии
со целым
значением,
установленным
в аргументе
arg:
- F_RDLCK
- Установить
аренду
чтения. Это
приведёт к
генерации
уведомления,
когда
другой
процесс
открывает
указанный
файл для
записи или
усекает
его.
- F_WRLCK
- Установить
аренду
записи. Это
приведёт к
генерации
уведомления,
когда
другой
процесс
открывает
указанный
файл (для
чтения или
записи) или
усекает его.
Аренда
записи
может быть
установлена
на файл,
только если
этот файл не
открыт в
этот момент
каким-либо
другим
процессом.
- F_UNLCK
- Удалить
аренду с
указанного
файла.
Процесс
может
удержитвать
для файла
только один
тип аренды.
Аренда может
быть
установлена
только для
обычных
файлов.
Непривелегированный
процесс
может
установить
аренту
только для
файла, у
которого UID
совпадает с UID
файловой
системы
этого
процесса.
- F_GETLEASE
- Показывает
какой тип
аренды
удерживается
для файла, на
который
указывает fd
и
возвращает
одно из
значений F_RDLCK,
F_WRLCK или F_UNLCK,
соответственно
показывающих,
что
вызывающий
процесс
удерживает
аренду
чтения,
записи или
что аренды
нет. (Третий
аргумент fcntl()
при этому
опускается.)
Когда
конкурент
выполняет
вызов
open() или
truncate(), который
конфликтует
с арендой,
установленной
через
F_SETLEASE,
системный
вызов
блокируется
ядром (за
исключением
случая,
когда при
вызове
open()
указывается
флаг
O_NONBLOCK; в
этом случае
немедленно
возращается
ошибка
EWOULDBLOCK).
Затем ядро
уведомляет
держателя
аренды,
отправляя
ему сигнал
(по
умолчанию SIGIO).
Держатель
аренты
должен при
получении
этого
сигнала
выполнить
все
необходимые
действия для
подготовки
этого файла
к
использованию
другим
процессом
(например,
сбросить
буферы кэша)
и затем
удалить
аренду,
выполнив
операцию
F_SETLEASE
и установив
значение
аргумента
arg
в
F_UNLCK.
Если
держатель
аренды не
освободит
аренду в
течении
количества
секунд,
которое
задаётся в
файле
/proc/sys/fs/lease-break-time,
а системный
вызов
конкурента
останется
блокирующим
(то, есть
конкурент не
установит
флаг
O_NONBLOCK для
системного
вызова
open() и
этот
системный
вызов не
будет
прерван
обработчиком
события), то
ядро
принудительно
удалит
аренду у
процесса
держателя
аренды.
После того
как аренда
снята
держателем
аренды или
принудительно
удалена
ядром, ядро
снимает
блокировку с
системного
вызова
процесса
конкуренту и
разрешает
продолжить
его работу.
По умолчанию,
для
уведомления
держателя
аренды
используется
сигнал SIGIO, но
его можно
изменить,
используя
команду
F_SETSIG
для
fcntl (). Если
выполняется
команда
F_SETSIG
(даже
назначая
сигнал SIGIO) и
при этом
обработчик
сигнала
устанавливается
с
использованием
SA_SIGINFO, то
обработчик
будет
получит в
качестве
второго
аргумента
структуру
siginfo_t, в которой
поле
si_fd будет
содержать
дескриптор
файла, для
которого
установлена
аренда и
которому
пытается
получить
доступ
другой
процесс. (Это
полезно,
если
вызывающий
процесс
удерживает
аренду на
несколько
файлов).
Уведомления
об изменении
файла и
каталога¶
- F_NOTIFY
- (Начиная с
Linux 2.4)
Предоставляет
уведомление,
когда
изменяется
каталог, на
который
указывает fd
или файлы,
которые в
нём
содержатся.
События, о
наступлении
которых
делается
уведомление,
задаются в
аргументе
arg, который
является
битовой
маской,
получаемой
битовым
сложением (OR)
одной или
более
следующих
масок:
Маска |
Описание
(событие в
каталоге) |
|
|
|
DN_ACCESS |
Был
осуществлён
доступ к
файлу (read, pread, readv) |
|
|
DN_MODIFY |
Файл был
изменён (write, pwrite, |
|
|
|
writev, truncate, ftruncate) |
|
|
DN_CREATE |
Файл был
создан (open, creat, mknod, |
|
|
|
mkdir, link, symlink, rename) |
|
|
DN_DELETE |
Файл был
удалён (unlink, rename
в |
|
|
|
другой
каталог, rmdir) |
|
|
DN_RENAME |
Файл был
переименован
внутри
каталога (rename) |
|
|
DN_ATTRIB |
У файла был
изменён
атрибут (chown, chmod,
utime[s]) |
|
|
(Чтобы
включить
определения
этих масок,
нужно
задать
макроопределение
_GNU_SOURCE до
подключения
<fcntl.h>.)
Уведомления
об
изменении
состояния
каталога
обычно
однократные
и
приложение
должно
перерегистрировать
установку
уведомлений,
чтобы и
дальше
получать их.
Однако, если
в аргумент
arg добавить
маску DN_MULTISHOT, то
уведомления
будут
происходить
до тех пор,
пока не
будут явно
отменены.
Серии
запросов F_NOTIFY
накапливаются,
таким
образом
запросы на
уведомления,
указываемые
в arg будут
добавляться
к тем, что уже
установлены.
Чтобы
запретить
уведомления
для всех
событий,
выполните
вызов F_NOTIFY, в
котором
значение arg
установлено
в 0.
Уведомления
выполняются
через
доставку
сигнала. По
умолчанию -
это SIGIO, но вы
можете
изменить
его,
используя
команду F_SETSIG
для вызова
fcntl(). В
последнем
случае,
обработчик
сигнала
получит в
качестве
второго
аргумента
структуру
siginfo_t (если
обработчик
был
установлен
с
использованием
SA_SIGINFO), а поле si_fd в
этой
структуре
будет
содержать
дескриптор
файла, для
которого
было
сегенерировано
уведомление
(полезно,
когда
устанавливается
уведомление
для
нескольких
каталогов).
Кроме того,
когда
используется
DN_MULTISHOT, для
уведомлений
должен бы
быть
использован
сигнал
реального
времени POSIX.1b,
так что
множественные
уведомления
могут быть
поставлены
в очередь.
ВОЗВРАЩАЕМОЕ
ЗНАЧЕНИЕ¶
При успешном
вызове,
возвращаемое
значение
зависит от
использованной
операции:
- F_DUPFD
- Новый
файловый
дескриптор.
- F_GETFD
- Значение
флага.
- F_GETFL
- Значение
флага.
- F_GETOWN
- Значение,
представляющее
собой
владельца
дескриптора.
- F_GETSIG
- Значение
сигнала,
посылаемого
когда
становится
возможным
чтение или
запись или
ноль для
традиционного
поведения
SIGIO.
- Все
другие
команды.
Ноль.
В случае
ошибки,
возвращается
-1 и значение
errno
устанавливается
соответствующим
образом.
ОШИБКИ¶
- EACCES или EAGAIN
- Операция
запрещена
блокировками,
которые
удерживаются
другими
процессами.
Или,
операция
запрещена,
потому что
для этого
файла
другой
процесс
использует
механизм
отображения
в память (memory-map).
- EBADF
- fd не
является
открытым
файловым
дескриптором
или команда
была F_SETLK или
F_SETLKW и режим
открытия
файлового
дескриптора
не
совпадает с
типом
запрошенной
блокировки.
- EDEADLK
- Было
обнаружено,
что
указанная
команда F_SETLKW
должна
вызывать
мёртвую
блокировку
(deadlock).
- EFAULT
- lock
находится
за
пределами
доступного
адресного
пространства.
- EINTR
- Выполнение
команды F_SETLKW,
было
прервана
сигналом.
Или
выполнение
команд F_GETLK и
F_SETLK, было
прервано
сигналом
перед тем
как была
блокировка
была
проверка
или
установлена.
Большинство
таких
ошибок
случается
при
блокировке
удалённого
файла
(например,
блокировка
через NFS), но
иногда
такое может
случаться и
на
локальных
файлах.
- EINVAL
- Для
F_DUPFD,значение
arg
отрицательное
или же
больше, чем
максимально
возможное
значение.
Для
F_SETSIG,значение
arg не
содержит
допустимый
номер
сигнала.
- EMFILE
- Для F_DUPFD,
процесс
достиг
максимального
количества
открытых
файловых
дескрипторов.
- ENOLCK
- Открыто
слишком
много
блокировок
сегментов,
таблица
блокировок
заполнена
или ошибка
протокола
удалённой
блокировки
(например,
при
блокировке
через NFS).
- EPERM
- Попытка
сбросить
флаг O_APPEND на
файле,
который
открыт с
атрибутом
только для
добавления.
ЗАМЕЧАНИЯ¶
Ошибки,
возвращаемые
dup2
отличаются
от тех, что
возвращаются
при
F_DUPFD.
Начиная с
ядра 2.0, не
существует
разницы
между типами
блокировки,
которые
осуществляет
flock(2) и
fcntl(2).
POSIX 1003.1-2001
разрешает
отрицательное
значение
l_len.
(И если оно
отрицательное,
интервал,
описывающий
блокировку
покрывает
l_start+
l_len байт,
включая
l_start-1.)
Это
поддерживатся,
начиная с Linux 2.4.21 и
2.5.49.
Некоторые
системы
имеют больше
полей в
структуре
struct
flock например
такие как
l_sysid.
Вообще-то,
один
l_pid не
очень-то
полезен,
если
процесс,
удерживающий
блокировку
может
работать на
другой
машине.
СООТВЕТСТВИЕ
СТАНДАРТАМ¶
SVr4, SVID, POSIX, X/OPEN, BSD 4.3. В
стандарте POSIX.1
описываются
только F_DUPFD, F_GETFD, F_SETFD, F_GETFL,
F_SETFL, F_GETLK, F_SETLK и F_SETLKW. F_GETOWN и F_SETOWN в
BSD, не
поддеживаются
в SVr4; F_GETSIG и F_SETSIG
являются
специфичными
для Linux.
F_NOTIFY,
F_GETLEASE, и
F_SETLEASE являются
специфичными
для Linux. (Для
подключения
этих флагов
задавайте
макрос _GNU_SOURCE
перед
подключением
заголовочного
файла <fcntl.h>.)
Флаги,
которые
являются
допустимыми
для F_GETFL/F_SETFL также
поддерживаются
вызовом
open(2) и
варьируются
в
зависимости
от
операционных
систем; O_APPEND, O_NONBLOCK, O_RDONLY
и O_RDWR
описываются
в POSIX.1. SVr4
поддерживает
несколько
других опций
и флагов,
которые
здесь не
описываются.
SVr4
документирует
дополнительные
условия
ошибок EIO, ENOLINK и EOVERFLOW.
СМОТРИ
ТАКЖЕ¶
dup2(2),
flock(2),
lockf(3),
open(2),
socket(2)
См. также locks.txt, mandatory.txt
и dnotify.txt in /usr/src/linux/Documentation.
ПЕРЕВОД¶
Перевёл с
английского
Виктор
Вислобоков
<corochoone@perm.ru> 2005