№8(21) август 2004 подписной индекс 81655 www.samag.ru
Кроссплатформенная частная сеть: OpenVPN Linux и NTFS Особенности установки антивируса ClamAV во FreeBSD Обзор ОС Darwin 7.0 на платформе x86 Практика работы с NetBSD: профилирование ядра Создание и настройка IVR для голосовых шлюзов Cisco Systems Разгон и торможение Windows NT №8(21) август 2004
Поисковая система своими руками HTML-шаблоны для PHP и Perl Обработка HTML-шаблонов off-line
оглавление Знакомство с Usergate
АДМИНИСТРИРОВАНИЕ
Андрей Уваров
OpenVPN, или Кроссплатформенная частная сеть
dashin@ua.fm
Создание и настройка IVR 4 для голосовых шлюзов Cisco Systems
Андрей Бешков tigrisha@sysadmins.ru
Михаил Заграевский
Запуск в VMWare гостевой оси, установленной на физическом диске Николай Почабытов
mike@megalog.ru
13
nikel@tsr.ru
kk@sendmail.ru
Linux и NTFS Сергей Яремчук
18
grinder@ua.fm
duan@bk.ru
24
amsand@rambler.ru
CrossOver и лицензионный вопрос
HTML-шаблоны для PHP и Perl, или Не делайте инструмент самоцелью Дмитрий Горяинов
26
В яблочко! Краткий обзор ОС Darwin 7.0 на платформе x86 (Mac OS X 10.3 Jaguar)
dg@webclub.ru
a.borisov@tesv.tmb.ru
66
Обработка HTML-шаблонов off-line. Возможности и ограничения Алексей Мичурин alexey@office-a.mtu-net.ru
Антон Борисов
61
WEB
Сергей Супрунов
tigrisha@sysadmins.ru
48
Поисковая система своими руками Андрей Сапронов
Еще раз о ClamAV: особенности установки во FreeBSD
38
Разгон и торможение Windows NT Крис Касперски
Андрей Бешков
36
74
28 ОБРАЗОВАНИЕ
Практика работы с NetBSD: профилирование ядра Александр Байрак x01mer@pisem.net
Автоматизация процессов в сети 34
Иван Коробко ikorobko@prosv.ru
84
Уважаемые системные администраторы! Редакция журнала объявляет конкурс на лучшие статьи, описывающие успешный опыт в области системного администрирования (решение нетривиальных проблем, успешная организация работы отдела и любые другие полезные материалы, которые помогут вашим коллегам в работе). Самые интересные будут опубликованы в ноябрьском и декабрьском номерах журнала «Системный администратор». Условия участия смотрите на сайте журнала www.samag.ru в разделе «Конкурс».
№8(21), август 2004
1
администрирование
OpenVPN, ИЛИ КРОССПЛАТФОРМЕННАЯ ЧАСТНАЯ СЕТЬ АНДРЕЙ БЕШКОВ Рано или поздно большинство системных администраторов сталкивается с необходимостью соединить свои территориально удаленные сети с помощью надежного, достаточно быстрого и в то же время защищенного от прослушивания и вмешательства злоумышленников канала. Таким образом, получается, что нам нужно создать частную виртуальную сеть (Virtual Private Network), или VPN. В этот момент перед нами открывается развилка из нескольких путей для претворения задуманного в жизнь: 1. Использовать свободную реализацию IPSec (Internet Protocol Security). В качестве таковой могут выступать FreeSWAN или FreeBSD IPSec. 2. Купить и внедрить коммерческое решение. Например, Cisco VPN или Securepoint VPN Server, который также основан на IPSec, или взять решение от какого-либо другого производителя, например Windows IPSec. В данном разделе я сознательно не делю решения на аппаратные и программные, потому что это всего лишь обзор. 3. Взять на вооружение свободные разработки, использующие криптографические алгоритмы собственного изготовления. Список таких приложений довольно велик. Поэтому перечислим только те, что у всех на слуху – cipe, vpnd, tinc, – этот список можно было бы продолжать очень долго. 4. Самостоятельно написать программное обеспечение, реализующее все механизмы VPN. 5. Использовать PPTP (Point to Point Tunneling Protocol). Давайте разберемся с достоинствами и недостатками каждой из предлагаемых методик решения проблемы. Что же можно сказать по поводу первого решения. Последние несколько лет при создании VPN стандартом дефакто считается IPSec. Такая распространенность помогает нам не слишком беспокоиться о совместимости VPN-серверов и клиентов. Все-таки единый стандарт – штука очень удобная. Подобный способ хорош тем, что не потребует больших материальных затрат и в то же время предлагает стойкую криптографическую защиту передаваемых данных. Но на этом его преимущества заканчиваются, и на сцену выходят недостатки. Во-первых, IPSec не обязательно сможет мирно сосуществовать с вашим межсетевым экраном, особенно если тот выполняет над пакетами изуверские операции, называемые в народе NAT. Опять же экраны с контролем состояния соединения (statefull firewall), находящиеся между двумя точками виртуального тоннеля и управляемые провайдером, могут не пропускать те или иные IPSecпакеты. О кроссплатформенности разных реализаций IPSec пока что остается только мечтать в связи с тем, что для
4
реализации функций IPSec приходится вносить в ядро операционной системы и IP-стек довольно много изменений. Как гласит старинная пословица: «Надежность заканчивается там, где начинается сложная механика», это значит, что одна-единственная ошибка в коде, реализующем IPSec, приведет к огромной дыре в безопасности. Вдобавок, насколько я знаю, на данный момент не существует легко настраиваемого свободного клиента IPSec для Windows. Значит, либо придется разбираться в настройках тех клиентов, что распространяются под эгидой opensource, либо покупать коммерческий. Также стоит обратить внимание на несколько большую сложность установки и настройки такого комплекса по сравнению с остальными типами VPN. Еще одним из минусов является отсутствие технической поддержки. Если что-то пойдет не так, как планировалось, то на квалифицированную помощь от производителя рассчитывать трудно. Это ведь свободный продукт, поэтому придется просить совета либо у своих более удачливых коллег, либо в форумах и списках, посвященных данному программному обеспечению. Впрочем, не каждый производитель проприетарного софта может похвастаться квалифицированной службой поддержки. К тому же обычно у многих свободных разработок существует внушительный лагерь сторонников, которые будут рады обратить вас в свою веру и заодно помочь в настройке программы. Второй способ выглядит довольно привлекательным в случае, если у вас есть деньги. В обмен на довольно внушительную сумму вы, скорее всего, получите более или менее качественную техническую поддержку, стабильно работающее оборудование и специалистов, которые самостоятельно или по вашим указаниям решат, как именно соединить сети для достижения наилучшего результата. Да и с технической документацией вероятнее всего дело будет обстоять очень хорошо. Но, как всегда в жизни, плюсов без минусов не бывает. Выбрав решение от одного производителя, вы будете впредь очень сильно привязаны к нему. Соответственно, если выбранный вендор не реализует те или иные возможности в своей продуктовой линейке, значит и вы, скорее всего, не сможете ими воспользоваться. Третий вариант наиболее подходит для тех, кому нужно развернуть VPN без больших затрат времени, сил и денег. В то же время нужно отдавать себе отчет, что обычно подобные программы пишут для собственного использования те, кто не смог или не захотел разобраться с принципами работы и методикой настройки IPSec. Соответственно основной идеей разработки является удобство использования и нетребовательность к ресурсам. Со временем такие программы понемногу совершенствуются, но все же не стоит ожидать от них запредельной надежности и безопаснос-
администрирование ти. Происходит это потому, что каждый автор использует свои собственные реализации криптоалгоритмов. Конечно, они обеспечивают некоторую степень защищенности, но без проведения сторонней экспертизы точно сказать, насколько надежно все это работает, довольно затруднительно. Соответственно данный класс программ больше подходит для защиты каналов, по которым передается информация с малым временем жизни, предназначенная для ограниченного распространения. Внедрение такого решения позволит защитить наши данные от начинающих злоумышленников, но не от профессионалов в области подглядывания. Впрочем, для некоторых предприятий вполне достаточно и таких возможностей. Четвертый путь выглядит привлекательным только для специалистов в области криптографии, обладающих большим количеством свободного времени. Но, к сожалению, таких людей в мире немного, а тех, кто позволит себя нанять, еще меньше. Вариант решения, основанный на PPT, большей частью используется приверженцами Microsoft. Данный стандарт реализует довольно стойкое шифрование и аутентификацию соединений. Созданный в недрах большой Редмондской корпорации, он не получил особого распространения в мире Unix. Хотя свободное программное обеспечение для работы с ним все же существует и пользуется некоторым спросом, все же решения на основе IPSec практикуют гораздо чаще. Происходит это, видимо, потому, что IPSec имеет более надежные процедуры шифрования. Но политические мотивы сопротивления со стороны юниксистов новинкам, внедряемым Microsoft, тоже не стоит сбрасывать со счетов. Ну а большинству читателей, не воспользовавшихся вариантами, предлагаемыми выше, видимо, придется обратиться к программе, которой собственно и посвящена данная статья. Между делом неплохо было бы посетить сайт проекта http://openvpn.sourceforge.net. Ну а я продолжу свое повествование. История разработки OpenVPN выглядит довольно забавно и в то же время является хрестоматийной иллюстрацией принципов, типичных для движения открытых исходников. В конце 2001 года Джеймс Йонан (James Yonan) успешно выполнил работу над большим проектом для компании, сотрудником которой он является и по сей день. В качестве благодарности работодатель разрешил ему не ходить каждый день на работу и выполнять свои обязанности удаленно. С этого момента жизнь Джеймса кардинально изменилась. Словно в калейдоскопе мелькали страны и континенты: Египет, Кыргызстан, Монголия, Китай. Как обычно, без русских тут не обошлось. Уж очень автора OpenVPN беспокоил тот парадоксальный факт, что в России есть достаточно большое количество безработных, но очень талантливых хакеров. А судя по его наблюдениям, некоторая часть трафика шла именно через российские сети. Объездив всю центральную Азию, Джеймс попутно продолжал искать решение, которое позволит ему работать с центральным офисом удобно, безопасно, надежно и в то же время не будет стоить бешеных денег. Испробовав на себе все вышеперечисленные способы создания VPN, он понял, что на данный момент идеал, к сожалению, недостижим.
№8(21), август 2004
Решив разработать свой собственный пакет, реализующий функции VPN, он в отличие от других авторов не стал заниматься созданием своего собственного уникального криптоалгоритма с нуля. Вместо этого был использован проверенный временем и неоднократно доказавший свою надежность стандарт SSL, для работы с которым можно было опереться на доступную для многих систем библиотеку OpenSSL. Стоит отметить, что такая же идея используется и в программном обеспечении, называемом vtun. Итак, давайте посмотрим поближе на возможности, предоставляемые OpenVPN. ! Официально OpenVPN успешно работает под управлением следующих операционных систем: Linux, Solaris, OpenBSD, FreeBSD, NetBSD, Mac OS X, Windows 2000/ XP. Это позволяет создавать сложные кроссплатформенные туннели. Впрочем, не составит никакого труда портировать OpenVPN в любую другую систему, для которой существует драйвер tun/tap-устройств. К тому же данная разработка независима от размера и старшинства байтов в машинном слове, что облегчает перенос на новые операционные системы. ! Компрессия потока передаваемых данных и управление полосой пропускания производятся с помощью библиотеки LZO. Процесс сжатия является адаптивным, то есть попытки упаковать передаваемые данные будут предприняты, только если есть смысл их упаковывать. Эта возможность может быть легко отключена по желанию пользователя, как и любые другие компоненты в случае, если вам не удалось с ними поладить, или если вы хотите поступиться какой-то частью функционала взамен на быстродействие. ! Поддерживаются два типа туннелей: IP и Ethernet, соответственно называемые routed и bridged. Таким образом, появляется возможность туннелировать как IP-подсети, так и виртуальные Ethernet-адаптеры. ! Отлично работает в сетях, где адреса распределяются с помощью DHCP. Помогает подключаться к VPN-клиентам, попадающим в Интернет через dial-up. ! Позволяет создать туннели поверх NAT, несмотря на то что NAT изменяет содержимое заголовков передаваемых пакетов. ! Дает возможность работать с любыми механизмами шифрования, встроенными в OpenSSL для защиты передаваемого трафика. А это, в свою очередь, позволяет каждому клиенту выбрать тип, режим работы (CBC, CFB, OFB) и размер ключа шифра в соответствии с индивидуальными предпочтениями. ! Для аутентификации датаграмм, пересылаемых по туннелю, можно использовать HMAC-дайджест. ! В случае если в передаваемых данных есть повторяющиеся последовательности, для их сокрытия будет использован алгоритм explicit IV. ! Каждая датаграмма помечается с помощью специальных ID, создаваемых на основе времени отправки и номера последовательности. Таким образом предотвращается возможность повторного проигрывания злоумышленником последовательности записанных пакетов.
5
администрирование ! В качестве дополнительной меры безопасности может
!
!
!
!
!
быть использован протокол TLS, позволяющий аутентифицировать сессию с помощью динамического обмена сертификатами. Довольно большой оптимизации быстродействия при динамическом обмене SSL/TLS-ключами позволяет добиться использование мультипоточной библиотеки pthread. Таким образом, даже частый обмен между сервером и клиентом ключами размером более чем 2048 байт практически не влияет на скорость передачи туннелируемых данных. Для увеличения безопасности OpenVPN позволяет переместить себя в chroot-окружение и снижает свои привилегии после старта так, чтобы отлично работать от имени самого бесправного пользователя системы. Еще одним полезным с точки зрения безопасности свойством является наличие ключа --mlock. Он позволяет запретить OpenVPN записывать в процессе работы на жесткий диск какую-либо информацию, связанную с секретными ключами и данными, передаваемыми по туннелю. В связи с тем, что данная программа является всего лишь обычным пользовательским приложением, а не частью ядра, она может вполне мирно сосуществовать с другими приложениями, использующими tun/tap-туннели. OpenVPN создавался для тесной интеграции с пользовательскими скриптами и другими высокоуровневыми приложениями. Что в свою очередь дает возможность по первому требованию легко создавать и уничтожать туннели. Позволяет удобно работать через межсетевые экраны с контролем состояния соединения. В случае если по туннелю не передаются данные, OpenVPN позволяет через определенные промежутки времени посылать ping, для того чтобы не дать межсетевым экранам разорвать соединение из-за неактивности.
В данной статье мы будем работать только с маршрутизируемыми (routed) туннелями, а разговор о туннелях типа мост (bridge) отложим до следующего раза. Как я уже упомянул ранее, маршрутизируемый туннель умеет работать только с TCP/IP-трафиком. Соответственно широковещательный трафик через него не пройдет. А это значит, что некоторые протоколы вроде Microsoft Netbios работать поверх нашего туннеля не смогут. Задачей на сегодня будет соединение через VPN трех удаленных филиалов одной организации. Будем считать, что они называются магазин, склад и офис. В каждом из филиалов стоит маршрутизатор, имеющий на борту по два реальных и два виртуальных сетевых интерфейса. Первый из реальных интерфейсов направлен во внутреннюю локальную сеть с серым адресным пространством, а второй – в Интернет. Адреса для внешних интерфейсов выданы нам провайдером и принадлежат к сети 80.80.20.0/24. Для виртуальных tun-интерфейсов выбрана подсеть 10.3.0.0/24. Òàáëèöà 1
Более наглядно все вышеуказанное отображено на приведенной схеме.
Итак, начнем установку пакетов на ALT Linux. В системном репозитарии есть все, что нам может понадобиться. # apt-get update # apt-get install iptables openssl liblzo liblzo-devel openvpn
После удачного завершения установки создаем пользователя openvpn, входящего в группу openvpn. Процессы openvpn, стартуя от имени пользователя root, будут снижать свои права, чтобы с наименьшими привилегиями работать от имени вышеуказанного пользователя. Согласитесь, что дополнительная безопасность никому не помешает. # useradd openvpn
Теперь можно приступить к тестам общей работоспособности openvpn. Первым делом нужно посмотреть, какие из алгоритмов шифрования нам доступны: # openvpn --show-ciphers
Довольно неплохо. Стоит обратить внимание на надписи variable и fixed возле каждого наименования шифра. Они указывают, существует ли возможность самостоятельно выбирать размер ключа. Для аутентификации каждой получаемой UDP-датаграммы применяется так называемый механизм дайджеста. Смотрим доступные дайджесты: # openvpn --show-digests
6
администрирование Теперь нужно создать конфигурационные файлы, которые опишут наши туннели. Первый из них описывает туннель между машинами Linux и FreeBSD, а второй – между Linux и Windows. Файл linux-freebsd.conf: proto udp dev tun port 5000 comp-lzo ping 15 verb 3 user openvpn group openvpn remote 80.80.20.129 ifconfig 10.3.0.9 10.3.0.10 route 10.10.120.0 255.255.255.0 10.3.0.10 secret /etc/openvpn/static.key auth MD5 cipher DES-CBC tun-mtu 1500
Файл linux-windows.conf: Сначала нужно сгенерировать статический ключ, с помощью которого в дальнейшем будет производиться шифрование потока. #openvpn --genkey --secret /etc/openvpn/static.key
Внутри файла static.key должно появиться что-то похожее на следующие данные:
После этого можно провести тестирование криптосистемы с использованием только что созданного ключа. # openvpn --test-crypto --secret /etc/openvpn/static.key
Судя по надписям, все работает вполне приемлемо. Конечно, не стоит забывать, что статический ключ имеет один большой недостаток. Если злоумышленнику удастся его раздобыть, то он сможет расшифровать все данные, которые передавались по сети. С динамическим ключом такое развитие событий нам бы не угрожало. В самом худшем случае можно было бы расшифровать только малую часть трафика.
№8(21), август 2004
proto udp dev tun port 5002 comp-lzo ping 15 verb 3 user openvpn group openvpn remote 80.80.20.128 ifconfig 10.3.0.6 10.3.0.5 route 10.10.130.0 255.255.255.0 10.3.0.5 secret /etc/openvpn/static.key tun-mtu 1500 auth MD5 cipher DES-CBC
Начинаем разбираться по порядку, что означает каждая из этих опций. ! proto – протокол, используемый для передачи данных. Может принимать значения udp, tcp-client, tcp-server. Настоятельно рекомендуется использовать udp, потому что туннелирование поверх TCP создает слишком большие накладные расходы. Впрочем, если нет другого выхода, то и TCP сойдет. В таком случае нужно на одном конце туннеля установить настройки этой переменной как tcpserver, а на другом – как tcp-client. Соответственно отвечать за инициацию соединения будет машина со значением tcp-client. По умолчанию эта переменная имеет значение udp, поэтому если вы с этим согласны, то ее определение можно в дальнейшем не использовать. ! port – порт, на котором нужно ждать соединений. Должен быть одинаковым на обоих концах туннеля. Номера портов для разных туннелей не могут совпадать. ! dev – тип виртуального устройства туннеля. Может принимать значения tun, tap, null. В этом случае OpenVPN будет пытаться динамически выбрать и задействовать первое свободное устройство данного типа. Если же ему это не удается, то нужно будет четко указать имя устройства с помощью опции dev-node. Например, dev-node tun1. ! comp-lzo – включение упаковки потока данных. В случае если поток все же не стоило упаковывать, размер передаваемых данных увеличится всего на один байт. Впрочем, за все время эксплуатации данного программного комплекса я не встречал случая, когда система адаптивной компрессии ошиблась и стала пытаться применить сжатие там, где делать этого не стоило.
7
администрирование ! ping – в случае если по VPN-каналу не передается ни-
! cipher – алгоритм, используемый для шифрования паке-
каких данных, приказывается отправлять ping каждые n секунд, чтобы не позволить соединению разорваться из-за простоя. Полезно в случае, если между конечными точками vpn находятся межсетевые экраны с контролем состояния. verb – уровень подробности выводимых сообщений. Чем больше число, тем многословнее и педантичнее программа будет рассказывать о том, что происходит у нее внутри. Верхним пределом для этой переменной является число 11. Данная опция чаще всего используется при отладке и первоначальной настройке туннелей. user, group – имя пользователя и группы, от имени которой будет работать программа. Первоначально openvpn стартует от имени root и, прочитав все интересующие файлы, снижает привилегии до указанного уровня. remote – IP-адрес хоста, представляющего из себя дальнюю сторону туннеля. Если его не указать, то openvpn будет пассивно принимать все входящие соединения, не пытаясь самостоятельно соединяться с удаленной машиной. Затем все полученные соединения должны будут пройти авторизацию. Данный режим удобен для работы с dial-up системами, у которых постоянно меняется IP-адрес. ifconfig – назначает виртуальному tun/tap интерфейсу IPадрес. Заодно указывает адрес удаленного виртуального интерфейса. Это необходимо, потому что туннель работает как стандартное соединение «точка-точка». route – описывает маршрут, который должны пройти пакеты, чтобы попасть в удаленную сеть. Можно обозначить маршрут двумя способами. Либо с помощью этой настройки, либо переменной up, значением которой необходимо указать имя командного скрипта, отвечающего за выполнение правильной настройки маршрутизации. В случае если вы пользовались первым способом настройки маршрутизации, то openvpn самостоятельно удалит из таблицы маршрутов нужную запись, когда пользователь попросит его завершить работу. Ну а если прибегали к услугам опции up, то необходимо также описать опцию down, которая будет указывать на скрипт, выполняющий самостоятельные действия по удалению маршрута. secret – указывает имя файла, в котором хранится статический ключ, используемый для шифрования потока. tun-mtu – максимальный размер пакета, передаваемого по виртуальному интерфейсу. Пакеты большего размера будут разбиваться на несколько кусков и передаваться последовательно отдельными датаграммами. Желательно, чтобы на обоих концах туннеля значение этой переменной было одинаково, иначе можно провести несколько интересных часов в забавном поиске причины, почему авторизация и передача первых пакетов проходит нормально, а затем все начинает работать весьма медленно и нестабильно. auth – наименование алгоритма, используемого для аутентификации приходящих пакетов. В моем случае это MD5. Хотя никто не мешает взять любой другой.
тов. Blowfish выбран потому, что является весьма стойким и в то же время достаточно быстрым. В случае если хочется повысить надежность шифрования, нужно использовать переменную keysize и указывать большой размер ключа. По умолчанию Blowfish использует ключ длиной 128 бит, хотя максимально возможный размер – 448 бит.
!
!
!
!
!
! !
!
8
На этом установку и первоначальную настройку пакета на Linux-машине можно считать завершенной. Переходим на FreeBSD. Как человек рассудительный и здравомыслящий, предпочитаю устанавливать сложное программное обеспечение из портов. # cd /usr/ports/archivers/lzo # make install clean # cd ../../security/openssl # make install clean # cd ../openvpn # make install clean
К сожалению, несмотря на то что перед началом инсталляции я обновил список портов, система упрямо не хотела ставить openvpn-1.6.0, а вместо него подсовывала openvpn1.4.0. Дабы не наступать на грабли и впоследствии не ломать голову над совмещением разных версий, пришлось деинсталлировать ранее установленную версию 1.4.0. Хотя в документации описано, как «подружить» старые и новые версии, я решил рисковать. # make deinstall
Отправляемся по адресу: http://www.freebsd.org/cgi/ ports.cgi?query=openvpn&stype=all и с помощью поиска выясняем, что исходные тексты самой свежей версии порта openvpn-1.6.0 не желают скачиваться. Ну на нет и суда нет. Видимо, такова судьба, и нам придется снова пойти в обход. Берем отсюда бинарный пакет ftp://ftp.freebsd.org/pub/ FreeBSD/ports/i386/packages-4-stable/All/openvpn-1.6.0.tgz и устанавливаем его с помощью утилиты pkg_add. # pkg_add openvpn-1.6.0.tgz
Не забываем добавить в систему пользователя и группу openvpn. # adduser openvpn
Затем создаем директорию /etc/openvpn, где у нас будут храниться файлы с настройками и ключом. С помощью какого-либо безопасного транспорта переносим с Linuxмашины файл static.key. В качестве средства, пригодного для этой цели, можно использовать scp, sftp, архив с паролем или просто дискету, переданную доверенным лицом. Теперь давайте посмотрим на содержимое конфигурационных файлов. Файл freebsd-linux.conf: dev tun
администрирование port 5000 comp-lzo ping 15 verb 3 user openvpn group openvpn remote 80.80.20.131 ifconfig 10.3.0.10 10.3.0.9 route 10.10.140.0 255.255.255.0 10.3.0.9 secret /etc/openvpn/static.key auth MD5 cipher DES-CBC tun-mtu 1500 comp-lzo
Файл freebsd-windows.conf: dev tun remote 80.80.20.128 port 5001 ifconfig 10.3.0.1 10.3.0.2 route 10.10.130.0 255.255.255.0 10.3.0.2 secret /etc/openvpn/static.key ping 10 verb 3 tun-mtu 1500 user openvpn group openvpn auth MD5 cipher DES-CBC comp-lzo
Я думаю, что, пользуясь предыдущими объяснениями, будет довольно легко понять все используемые настройки. Права на директорию /etc/openvpn и файлы в ней должны позволять чтение только пользователю root и группе wheel. Если очень хочется, то можно выполнить проверку доступных шифров так же, как мы делали это для Linux. Наконец-то мы готовы к запуску первого из наших туннелей. На Linux-машине выполняем следующую команду: # openvpn --config /etc/openvpn/linux-freebsd.conf
И соответственно на FreeBSD делаем так: # openvpn --config /etc/openvpn/freebsd-linux.conf
На терминале Linux должны появляться следующие надписи:
Как только увидите надпись «Peer Connection Initiated with», считайте, что дело сделано. Теперь с помощью команды ping можно проверить, видны ли оконечные адреса из соединяемых частных сетей. В нашем примере это 10.10.120.1 и 10.10.140.1. Не забываем настроить компьютеры, находящиеся в наших локальных сетях так, чтобы они считали машины Linux и FreeBSD шлюзами по умолчанию. Кстати, стоит отметить, что openvpn может вполне успешно работать и без всяких конфигурационных файлов. Дело в том, что все параметры можно передать в программу и из командной строки. К примеру, для поднятия того же самого туннеля можно было скомандовать вот так: # openvpn --remote 80.80.20.128 --dev tun --port 5001 ↵ --ifconfig 10.3.0.1 10.3.0.2 --route 10.10.130.0 ↵ 255.255.255.0 10.3.0.2 --secret /etc/openvpn/static.key ↵ --ping 10 --verb 3 --tun-mtu 1500 --user openvpn ↵ --group openvpn --auth MD5 --cipher DES-CBC
Хотя, с моей точки зрения, данный способ выглядит несколько громоздко. Стоит отметить один интересный факт: дистрибутив openvpn для Linux устанавливает в систему скрипт /etc/rc.d/ init.d/openvpn, который помогает удобно управлять нашими туннелями с помощью команды service openvpn. Главной особенностью скрипта является способность поднять туннели для всех *.conf-файлов, находящихся в /etc/openvpn. Таким образом, нам не надо придумывать, как автоматически запустить требуемое количество экземпляров openvpn с нужными настройками после перезагрузки системы. Во FreeBSD тоже есть скрипт подобного предназначения. Сразу после инсталляции он обычно находится в /usr/ local/etc/rc.d/openvpn.sh.sample. Посмотрев внутрь него, понимаем, что он совершенно бесполезен, так как практически ничего не умеет делать. Поэтому нам придется смастерить свой собственный вариант такого скрипта. Для этого нужно создать файл openvpn.sh и внести в него следующие данные: #! /bin/sh case x$1 in xstart)
Ну а FreeBSD-машина должна говорить что-то вроде это-
;; xstop)
го: *) esac
№8(21), август 2004
/usr/local/sbin/openvpn --config ↵ /etc/openvpn/freebsd-linux.conf & /usr/local/sbin/openvpn --config ↵ /etc/openvpn/freebsd-windows.conf &
killall -SIGTERM openvpn route delete 10.10.130.0 route delete 10.10.140.0 ;; echo >&2 "Usage: $0 {start|stop}"
9
администрирование Обязательно кладем файл в /usr/local/etc/rc.d/ и даем ему право на выполнение. Стоит обратить внимание на наличие знака «&» после запуска каждого экземпляра openvpn. Сделано это потому, что данная программа жестко привязывается к терминалу, с которого была запущена. Соответственно при отсутствии этого знака второй процесс openvpn не запустится до тех пор, пока терминал не освободится, а этого при правильном развитии событий не должно произойти никогда. Вторым важным моментом является наличие команд route, удаляющих записи о маршрутах в той ветке скрипта, которая отвечает за выполнение действия stop. Удалять маршруты приходится вручную, потому что к моменту завершения работы openvpn выполняется от лица одноименного пользователя и группы, а изменять таблицу маршрутизации имеет право только root. Конечно, можно было бы портировать под FreeBSD версию скрипта, поставляющуюся в комплекте с Linux-версией программы, но сейчас заниматься этим как-то недосуг. Возможно, я сделаю это в следующей статье. Настало время перейти к настройке Windows-системы. На первый взгляд здесь все довольно просто. Берем дистрибутив для этой платформы на родном сайте программы http:// prdownloads.sourceforge.net/openvpn/openvpn-1.6.0-install.exe. Затем запускаем только что скачанный инсталлятор и методично жмем на кнопки «Далее» и «ОК». После установки в систему будет добавлен новый сетевой интерфейс со странным именем «Подключение по локальной сети 3».
Тут стоит сделать одну важную оговорку: под Windows не существует различия между tun- и tap-устройствами. А еще точнее было бы сказать, что такая реализация tap-драйвера позволяет устройству работать в любом из двух режимов. Для нас столь длинное название устройства неудобно, поэтому переименовываем его во что-нибудь более краткое и информативное. Например, в Linux. Затем с помощью меню Пуск → Программы → Open → VPNAdd a new TAP-win32 virtual ethernet adapter создаем еще один виртуальный интерфейс и переименовываем его во FreeBSD. Думаю, название каждого из этих интерфейсов достаточно красноречиво говорит об их предназначении. Надеюсь, что все еще помнят, зачем мы создавали файл static.key. Если это действительно так, то копируем его в C:\Program Files\OpenVPN\config\ под именем staic.txt. По непонятной причине файлы с расширением .key openvpn, работающие под Windows, не воспринимаются как секретные ключи. Затем создаем конфигурационные файлы наших туннелей. Стоит обратить внимание на тот факт, что для успешной работы у этих файлов должно быть расширение .ovpn. Файл windows-freebsd.ovpn: remote 80.80.20.129 dev-node FreeBSD dev tun port 5001 ifconfig 10.3.0.2 10.3.0.1 secret static.txt ping 10 verb 3 route 10.10.120.0 255.255.255.0 10.3.0.1 auth MD5 cipher DES-CBC comp-lzo
Файл windows-linux.ovpn:
Если присмотреться внимательно к свойствам данного подключения, то можно заметить что оно представляет собой не что иное, как интерфейс tap.
remote 80.80.20.131 dev tun dev-node Linux port 5002 ifconfig 10.3.0.5 10.3.0.6 secret static.txt ping 10 verb 3 route 10.10.140.0 255.255.255.0 10.3.0.6 tun-mtu 1500 comp-lzo
В сущности, все тесты, которые мы использовали до этого, отлично работают и под управлением Windows. Поэтому, если есть желание, можете выполнить openvpn с параметрами --show-digests и --show-ciphers. Единственное, на что стоит обратить внимание, – это обязательное наличие в конфигурационном файле записи dev-node с именем используемого устройства. К сожалению, Windows умеет автоматически находить и использовать нужное виртуальное устройство туннеля только в том случае, если оно в системе представлено в единственном числе. Кстати, стоит отметить тот факт, что в случае работы устройства tun под Windows использовать для туннеля какие попало адреса из подсети 10.3.0.0 не получится, придется выбирать из таблицы, выводимой командой openvpn --show-valid-subnets. Как и под UNIX-системами, можно поднять туннель вызовом openvpn с указанием имени используемого конфигурационного файла, а можно и без него, просто перечис-
10
администрирование лив все нужные ключи в командной строке. Есть еще два способа запуска туннеля. Первый из них состоит в том, что нужно с помощью проводника перейти в директорию C:\Program Files\OpenVPN\config\ и щелкнуть правой клавишей на файле конфигурации. В ниспадающем меню выбрать «Start OpenVPN on this config file». А можно поступить более умно и включить службу OpenVPN, которая самостоятельно будет заботиться о том, чтобы запустить по одному экземпляру программы для каждого конфигурационного файла.
Ну и на закуску осталось посмотреть на интерфейсы Windows: Настройка протокола IP для Windows 2000: На этом радостном этапе установку и настройку можно считать завершенными. На обоих UNIX-машинах сначала останавливаем, а затем снова запускаем сервисы openvpn. # service openvpn stop # service openvpn start
Адаптер Ethernet Linux:
# /usr/local/etc/rc.d/openvpn.sh stop # /usr/local/etc/rc.d/openvpn.sh start
То же самое проделываем и под управлением Windows. После этого все вышеописанные туннели должны правильно подняться и заработать без сбоев. Соответственно таблица интерфейсов, получаемая с помощью ifconfig, на Linux должна выглядеть так: Адаптер Ethernet FreeBSD:
Адаптер Ethernet Подключение по локальной сети 2:
Адаптер Ethernet Подключение по локальной сети:
А для FreeBSD будет характерна следующая картина:
№8(21), август 2004
11
администрирование
Для проверки того, как работает туннель, запускаем с разных сторон ping, направленный в удаленные сети, и смотрим на результаты. Теперь хотелось бы разобраться, как обстоит дело с шифрованием. Для этого начинаем прослушивание интерфейсов tun0 – 10.3.0.10 и lnc1 – 80.80.20.129 и снова выполняем ping 10.10.140.1. Таким образом, мы видим, что через tun0 пакеты icmp идут в открытом виде.
А проходя через интерфейс lnc1, в Интернет они попадают уже зашифрованными: # tcpdump –i lnc1 -lenx
# tcpdump –i tun0 -lenx
Таким образом, видно, что поставленную задачу мы успешно выполнили.
12
администрирование
ЗАПУСК В VMWare ГОСТЕВОЙ ОСИ, УСТАНОВЛЕННОЙ НА ФИЗИЧЕСКОМ ДИСКЕ В этой статье будет описана возможность запуска гостевой системы, установленной на компьютере при помощи VMWare. ОС Windows 2000 Professional, Linux (Fedora Core и SuSe) установлены самым обычным способом. Для более рационального использования времени и ресурсов необходимо одну из них (в данном случае Windows) запускать либо на виртуальной машине, либо на реальном компьютере, и чтобы при этом сохранялись все изменения, сделанные в системе. Это также необходимо на случай отсутствия администратора на работе. Про установку и запуск этой программы можно прочитать на http://www.onix.opennet.ru, где выложены статьи Андрея Бешкова, либо в прошлых номерах журнала [1, 2, 3]. Но в них не описан вопрос запуска уже установленной системы, находящейся на физическом диске. Данный материал должен помочь в этом.
НИКОЛАЙ ПОЧАБЫТОВ У системного администратора или программиста очень часто бывают ситуации, когда необходимо проверить работу программы или что-то сделать в другой ОС. Для этого на компьютер ставятся несколько систем. Но для того чтобы проверить работоспособность новой программы, надо перегружать компьютер, а это не всегда хорошо. Поэтому был выпущен такой продукт, как VMWare (http:// www.wmware.com). Его особенности можно не расписывать, он позволяет многое, главное – с его помощью можно запустить в качестве гостевой операционной системы уже установленную на жестком диске. Вся суть заключается в том,
№8(21), август 2004
что на виртуальном компьютере мы получаем возможность работать сразу, ничего не переустанавливая. В данной статье в качестве базовой ОС рассматривается Linux Fedore Core 2 test1, а в качестве гостевой – Windows 2000 Professional. Из-за того, что FC2 работает на ядре серии 2.6.*, нужно устанавливать VMWare 4.5.1, в которой учтена возможность работать с этой серией ядер. Переходим к главному – запуску установленной системы. Сформулируем требования: ! Должны быть установлены Windows и Linux.
13
администрирование ! Резервная копия системы (на случай, если что-то не получится с первого раза).
! Хороший boot-менеджер. В самом начале необходимо загрузиться в Windows и провести cледующие настройки. Выбираем: Свойства системы → Оборудование → Профили оборудования:
После чего получаем примерно такую картинку:
Далее щелкаем по кнопке, и на экран выводится окно, в котором перечислены существующие профили оборудования. Нам надо скопировать текущий профиль:
В появившемся окне вводим название профиля:
14
Обязательно надо отметить пункт «Дождаться явного указания от пользователя». Далее необходимо отправить компьютер на перезагрузку и выбрать в меню загрузки Windows. Теперь там появляется дополнительный пункт «Выбор профиля для загрузки», нам нужен только что созданный профиль для виртуальной машины. Эти действия надо проделать только для систем на основе NT (Windows NT4, Windows 2000, Windows XP, Windows 2003). В Windows 9* система plug and play, поэтому необходимые драйвера установятся сами.
администрирование После того как загрузилась система с выбранным профилем, заходим в «Диспетчер устройств» и начинаем там хозяйничать. Сразу же необходимо заметить, что надо отказываться от предложений перегрузить систему. В первую очередь удаляем видеоадаптер:
После этого находим звуковые устройства и отключаем их, так же поступаем и с сетевыми устройствами, USB: Теперь систему можно перегружать в Linux, первый этап закончен. Второй этап начинается с конфигурирования и подготовке VMWare для работы. Будем считать, что VMWare уже установлена и сконфигурирована, поэтому просто ее запускаем. И начинаем создавать новую виртуальную машину:
Выбираем пункт «Сustom». На следующем экране указываем нужную гостевую операционную систему. Далее выбираем имя виртуальной машины и каталог, где будут храниться основные конфигурационные файлы. На следующем экране выделяем количество оперативной памяти для гостевой ОС. После этого отмечаем тип сетевого подключения. Потом предстоит определиться с I/O adapter types. Правильнее было бы назвать этот пункт «Контроллеры для подключения НЖМД и CD-ROM». Контроллер ATAPI выбран по умолчанию, а вот со SCSI-контроллерами предоставлен выбор. Лучше всего оставить настройки по умолчанию и перейти к следующему экрану. Здесь необходимо отметить один из трех вариантов: ! Создание нового виртуального диска. ! Использование существующего виртуального диска. ! Использование физического диска. Для контроллера IDE нужно сменить драйвера на стандартные из поставки Windows:
№8(21), август 2004
Так как наша цель запустить на виртуальном компьютере установленную систему, то выделяем последний пункт:
15
администрирование На следующем экране выбираем имя конфигурационного файла и место его хранения. Возможно, будут еще какие-то вопросы (зависит от оборудования и дистрибутива Linux). Отвечаем на них утвердительно. VMWare практически готова к запуску:
При нажатии кнопки «Next» появится предупреждение, соглашаемся с ним и видим следующий экран, на котором предстоит выбрать устройство и использование какой-то части или всего диска. Лучше остановиться на тех разделах, на которых у вас установлена система Windows, и может быть, какие-то дополнительные диски (если у вас существуют дополнительные разделы для Windows. Если на компьютере существует два и более жестких диска, и системы установлены на разных физических дисках, то необходимо для нормальной работы подключать все жесткие диски, иначе система не будет загружаться):
Выбираем редактирование свойств виртуальной машины. Надо поправить всего лишь один параметр – подключение CD/DVD-ROM должно быть SCSI:
Запускаем виртуальную машину. И вот здесь вполне возможно столкнуться с трудностью, если boot-менеджер – GRUB, то Windows грузиться не будет. Решение пока не найдено. Такая же проблема может быть и с lilo. При попытке запустить Windows появляется ошибка, говорящая о том, что NTLoader не найден. Самым простым выходом из данной ситуации является установка другого boot-менеджера, лучше понимающего файловые системы FAT и NTFS. В данном случае был использован Acronis OS Selecter 8.0, так как он работает с файловыми системи ext3 и reiserfs, вполне возможно, что и другие boot-менеджеры справятся с этой задачей. И вот появляется долгожданное меню выбора профиля загрузки:
16
администрирование
Естественно, что надо выбирать профиль, созданный для виртуальной машины. При загрузке Windows будет искать новые устройства и драйвера для них, поэтому после того, как будут введены логин и пароль, нужно проинсталлировать VMWare Tools (VM → Install VMWare Tools).
После этого перезагружаемся, если возникнет необходимость. Теперь можно полноценно работать с системой:
Здесь необходимо сразу отметить несколько моментов по работе: ! USB Flash-накопители работают очень хорошо и без дополнительных настроек. ! Может возникнуть серьезная проблема у тех, кто пользуется USB-шнуром для связи мобильного телефона и компьютера. Драйвера и программное обеспечение устанавливаются без проблем и также нормально работают. Но при перезагрузке можно увидеть «синий экран смерти». Возможно, он появляется из-за отсутствия подключенного кабеля или телефона. Вариантов решения данной проблемы я пока не нашел, поэтому нужно загрузиться в Windows и там удалить драйвера и ПО от этого кабеля. ! Возникает странная «плавающая ошибка» с интегрированным SCSI-контроллером. Непонятно почему, но отрубаются даже мышь с клавиатурой, что может потребовать переустановки системы. Также в VMWare можно загрузить установленные на жестком диске и другие системы, в частности Linux. Здесь ситуация попроще и система спокойно может стартовать при помощи стандартных линуксовых загрузчиков lilo или GRUB. Для запуска Linux в качестве гостевой системы не требуется никаких дополнительных заморочек и профилей. Достаточно создать в VMWare новую гостевую систему и запустить ее. В качестве гостевой системы бралась Red Hat Linux AS 3.0. Kudzu не был отключен, поэтому установка и удаление новых устройств прошло без проблем, система запустилась и начала работать. Возможно следующее: если в системе есть SCSI-адаптер и SCSI-устройства, то они не примонтируются и не будут работать, соответственно в логах будут ошибки монтирования этих устройств. Так же будет конфликтовать Xfree86 и придется конфигурировать его заново. Таким образом, получаем решение, с которым очень удобно работать, и с которым можно вписаться в любую сеть. Вы можете также ознакомиться с книгой В. Костромина [4], с той ее частью, где описывается работа с VMWare (следует учесть, что в ней описана предыдущая версия (до 4.0) программы, и в качестве boot-менеджера выступает NTLoader).
Литература: 1. Бешков А. Виртуальный полигон для администратора и разработчика. – // Журнал «Системный администратор», №9(10), сентябрь 2003 г. – 08-23 с. 2. Бешков А. Виртуальный полигон для разработчика и администратора на основе Linux и VMWare. – // Журнал «Системный администратор», №11(12), ноябрь 2003 г. – 04-17 с. 3. Бешков А. VMWare со всеми удобствами. – // Журнал «Системный администратор», №6(19), июнь 2004 г. – 12-17 с. 4. Костромин В. Linux для пользователя. - «БХВ-Петербург», 2002 г.
№8(21), август 2004
17
администрирование
LINUX И NTFS «...ntfs not supported by kernel» Сообщение системы об ошибках.
СЕРГЕЙ ЯРЕМЧУК До недавнего времени острой необходимости в доступе к разделам с файловой системой NTFS, в общем-то, и не было. Ругать разработчиков ядра Linux не за что, необходимые работы ведутся уже давно и отнюдь не безуспешно. Уже в 1995 году для ядер серии 2.0 был доступен патч для работы с этой файловой системой, а с версии 2.2 (если быть точнее 2.1.74) подержка NTFS в ядро была включена стандартно. Но все равно разработки велись несколько вяло. Причин тому несколько. Семейство пользовательских операционных систем Windows до Me включительно поддерживают исключительно FAT, а NTFS использовалась на ОС корпоративносерверного уровня, где вряд ли кто-то додумается поставить одновременно две операционные системы на одном компьютере. Появление Windows 2000 практически ничего не изменило, на домашних компьютерах ее устанавливали довольно неохотно, и если посмотреть на форумах тех времен, то можно отметить относительное спокойствие. А вот Windows XP смог привлечь пользователя, этот момент и стоит считать началом действительного интереса разработчиков к проблеме. Многие (да почти все) производители включили поддержку NTFS в ядрах своих дистрибутивов. Проблем с доступом к разделам NTFS не обнаружат пользователи Mandrake, SUSE, ALTLinux, ASPLinux, Slackware, Debian и прочих популярных дистрибутивов. Только компания RedHat, очевидно, руководствуясь лицензионной чистотой своего дистрибутива, не включила поддержку данной ФС. Поэтому пользователи RedHat и Fedora увидят при попытке монтирования раздела сообщение, вынесенное в эпиграф. На данный момент имеются два свободных проекта, посвоему решающие вопрос работы с разделами NTFS. Первый, проект Linux-NTFS, предлагает традиционный подход, т.е. написание драйвера, который позволит нормально работать с этой файловой системой. Второй, проект Captive NTFS, пробует решить все эмуляцией системы NT.
Проект Linux-NTFS Сейчас для GNU/Linux фактически существуют два драйвера NTFS. Первый используется в ядрах серии 2.4.х и в 2.5.0-2.5.10, этот драйвер имеет ограниченные возможности по записи в раздел NTFS, и поэтому такой режим помечен как опасный, т.е. может привести к потере данных. Этот драйвер долго практически не развивался. Вторая версия драйвера была фактически переписана заново одним из разработчиков Антоном Алтапармаковым (Anton Altaparmakov), причем записи в раздел NTFS уделялось повышенное внимание, драйвер стал более легким, простым и быстрым, поддерживаются NTFS версий 1.2, 3.0 и 3.1, Unicod, сжатые файлы. Хотя в настоящее время име-
18
ются еще проблемы, например, драйвер не понимает зашифрованные файлы, квоты, игнорирует информацию безопасности и ограничен по записи. Проблемы эти, в общем-то, существуют не по вине разработчиков. Все дело в том, что Microsoft не выпустило никакой документации по внутреннему строению NTFS, и драйвера разрабатываются фактически с пустого листа, методом научного тыка и плясания с бубном вокруг компьютера. Строение же NTFS довольно сложное и внутри напоминает базу данных, когда изменение в одном месте требует замен и во многих других местах файловой системы, а иначе она попросту будет разрушена. Поэтому новый драйвер пока может только перезаписывать существующие файлы, но не может изменять их длину, добавлять новые и удалять старые файлы, не доступны операции с каталогами. Теперь немного практики. В ядрах, начиная с 2.5.11 и, конечно же, в серии 2.6, используется новый драйвер, поэтому если у вас с поддержкой не сложилось, то просто перекомпилируйте ядро, включив нужные пункты. Так как в народе еще популярны ядра серии 2.4, то для их владельцев доступен патч. Текущую версию драйвера узнать очень просто: # dmesg | grep -i ntfs
Или такой вариант: # grep -i ntfs /var/log/messages
Как видите, ядро Slaksware 9.1, которое использовалось во время написания статьи, поддерживает первую версию драйверов, но это довольно легко исправить. Если вывод этих команд ничего не дал, то дополнительно проверить поддерживаемые ядром файловые системы можно, введя: # cat /proc/filesystems
За драйверами, патчами, утилитами для работы с NTFS и документацией идем на сайт http://linux-ntfs.sourceforge.net. Владельцам RedHat и Fedora можно идти сразу на страницу http://linux-ntfs.sourceforge.net/rpm/index.html, где доступны прекомпилированные rpm-пакеты с необходимыми модулями. Выбираем нужный и устанавливаем. #rpm -ihv --noscripts kernel-ntfs-2.6.5-1.358.i586.rpm #/sbin/depmod -a
администрирование Например, мы хотим пересобрать ядро 2.4.25 с новым драйвером. Скачиваем по ссылке патч к используемому ядру, в моем случае это linux-2.4.25-ntfs-2.1.6a.patch.bz2, и распаковываем его. # bunzip2 linux-2.4.25-ntfs-2.1.6a.patch.bz2
Теперь распаковываем ядро, взятое с www.kernel.org, можно использовать и имеющееся в вашем дистрибутиве, только тогда возьмите и патч с соответствующим номером. #cd /usr/src
Накладываем патч: #cd linux #patch -p1 < ../linux-2.4.25-ntfs-2.1.6a.patch
В последнем случае не должно быть сообщений об ошибках, иначе проверьте, правильно ли заданы пути. И теперь приступаем к конфигурации ядра. Набираем make nenuconfig, в «File systems» необходимо включить пункты драйвера NTFS (рис. 1). Сохраняем конфигурацию и выходим. После чего идет стандарная компиляция ядра и настройка загрузчика.
Ðèñóíîê 1
Выполнив все, перезагружаемся с новым ядром и проверяем:
тема не сможет сама определить ее и выдаст примерно такое сообщение:
Для того чтобы просмотреть опции по умолчанию, с которыми монтируется раздел, вводим: # cat /proc/mounts | grep -i ntfs
Как видите, раздел смонтирован в режиме «чтение-запись», это можно изменить на «только чтение», явно задав в командной строке опцию -r (или -o ro). Владельцем является root (uid=0, gid=o), опция errors указывает, как будет себя вести система при возникновении ошибок, поддерживается два варианта: continue (продолжает работу) и recover (пытается восстановить), в настоящее время поддерживается только замена boot-сектора резервным. Опции fmask и dmask задают параметры доступа к файлам и каталогам соответственно, возможно использование общей опции umask, задающей доступ к файлам и каталогам одновременно. NTFS хранит имена в Unicode, но драйвер переводит их в ASCII; чтобы указать на используемый язык, применяются две конструкции: -o iocharset= или в новом варианте -o nls=. Для отображения русских имен используются koi8-r, возможно задание utf8 (если ядро не поддерживает Unicod, то дополнительно используйте utf8=true). В моем случае nls был выбран автоматически, потому что при конфигурировании ядра эта кодировка была прописана по умолчанию (смотрите в File Systems – Native Language Support – Default NLS Option). Параметр mft_zone_multiplier указывает на размер зарезервированной в master file table части, которая содержит информацию о файле. Примечательно, что маленькие файлы полностью помещаются в MFT, что позволяет быстро его находить и избежать потерь дискового пространства присущих FAT (в FAT файл размером 1 байт займет на диске минимум 4 Кб). Первоначально задается при форматировании, но в процессе эксплуатации может изменяться на лету. Цифра 1 является значением по умолчанию и соответствует 12.5% зарезервированного объема, 2 – 25%, 3 – 37.5% и 4 – 50.0%. Полная опция монтирования может быть указана в таком виде:
#grep -i ntfs /var/log/messages #mount /dev/hda7 /mnt/temp/ -t ntfs -r -o nls=koi8-r -o ↵ uid=500,gid=winuser,umask=0222
Как видите, версия драйвера обновилась и соответствует установленной; пробуем примонтировать раздел, для того чтобы узнать, какой именно, вводим: #
fdisk -l
|
grep -i ntfs
Монтируем раздел. # mount /dev/hda7 /mnt/temp/ -t ntfs
Обратите внимание, что ключ -t, указывающий на файловую систему, в данном случае обязательный, иначе сис-
№8(21), август 2004
часть после второй -o, вообще говоря, не нужна и дана для примера. После этого с чтением данных проблем быть не должно, все имена будут отображаться нормально. Дополнительно проект Linux-NTFS предоставляет и ряд утилит для работы с NTFS из-под Linux, библиотеку libntfs, обеспечивающую доступ к функциям NTFS программ, в том числе и других разработчиков, а также libntfs-gnomevfs – модуль для Gnome VFS (virtual filesystem), обеспечивающий универсальный доступ ко всем файловым системам. Большая часть утилит ориентирована скорее на разработчиков, но годом раньше вообще ничего подобного не было, прогресс налицо. Все они доступны в пакете ntfsprogs, который
19
администрирование распространяется как в исходных кодах, так и имеются прекомпилированные пакеты. Установка из исходников проблем не вызывает, все те же стандартные ./configure; make; make install. В результате в системе появится еще 10 утилит: ! ntfsfix – для установки измененных драйвером разделов NTFS, нечто вроде scandisk, который должен использоваться после каждой записи (особенно со старым драйвером) во избежание возможной потери данных, для того чтобы привести файловую систему в непротиворечивое состояние. ! mkntfs – для создания NTFS 1.2 (поддерживается всеми Windows NT/2000/XP). ! ntfscat – является аналогом стандартной UNIX-утилиты cat, предназначен для чтения файлов в разделах NTFS. ! ntfsclone – предназначена для клонирования (копирования, сохранения, создания резервного образа и восстановления) раздела с файлововй системой NTFS. Работает на уровне секторов диска и сохраняет только используемые данные, неиспользуемые заполняются нулями, что позволяет эффективно сжимать полученные образы. Полезна для создания точных копий раздела и восстановления системы. Примеры: ! Создание копии раздела: #ntfsclone --output system.img /dev/hda1
! Восстановили раздел: #ntfsclone --overwrite /dev/hda1 system.img
! Так можно заглянуть внутрь созданного образа: #mount -t ntfs -o loop system.img /mnt/ntfsclone
! ntfscluster – идентификация файлов в указанном разделе или области NTFS. Работает в трех режимах: ! info (режим по умолчанию) – покажет общую информацию об области NTFS; ! sector – покажет список файлов в заданном диапазоне секторов; ! cluster – то же, что и предыдущий, только выводит список файлов в группе. #./ntfscluster /dev/hda7
20
! ntfsinfo – выводит атрибуты по номеру inode или имени файла.
! ntfslabel – выведет или установит метку файловой системы NTFS (метка до 128 Unicode-знаков). # ./ntfslabel -v /dev/hda7
! ntfsls – аналог UNIX-утилиты ls (Windows – dir) – выводит список файлов в разделе NTFS (монтировать необязательно). # ./ntfsls -v -d /dev/hda7
! ntfsresize – а вот это действительно полезная утилита, предназначена для изменения размера файловой системы NTFS без потерь данных. Примечание: утилита не манипулирует размерами разделов, для этого необходимо воспользоваться утилитой fdisk. Как работать с ней, поговорим дальше. ! ntfsundelete – восстановление удаленных файлов на разделе NTFS. Имеет три режима работы: ! scan (режим по умолчанию) – просмотр файловой системы на предмет наличия удаленных файлов, при нахождении выводит список таких файлов; ! undelete – пытается, насколько это возможно, восстановить утраченные данные (кроме сжатых и зашифрованных файлов), файл сохраняется в другой раздел; ! copy – полезен большей частью при отладке, сохраняет данные MFT в файл. #./ntfsundelete /dev/hda7
Сообщение «Volume is dirty» может возникнуть после записи в раздел, изменения размера раздела и других возможных операций, связанных с изменением данных, после каждой такой операции во избежание несоответствия рекомендуется проверка раздела средствами Windows. #./ntfsundelete /dev/hda7
--force
администрирование Пробуем восстановить один из найденных файлов: #./ntfsundelete /dev/hda7 -s -m home.jpg --force
В настоящее время начата разработка еще нескольких утилит, некоторые из них находятся в стадии альфы. Это ntfswipe – позволит зачистить нулями свободные части диска, ntfsdefrag – дефрагментатор файлов, каталогов и MFT, для проверки диска будет использоваться ntfsck, nttools позволит просмотреть/создать/изменить/копировать/найти требуемые значения (эквивалентны командам ntfscp, ntfsgrep, ntfstouch, ntfsrm, ntfsrmdir, ntfsmkdir). И пока в планах еще одна утилита ntfsdiskedit, которая позволит работать с дисковыми структурами NTFS. Начиная с Windows 2000, файловая система получила новое понятие – динамический диск, который пришел на смену стилю разделов, принятых еще в MS-DOS (Basic Disks), и позволил снять все ограничения файловой системы, в том числе и создавать многодисковые тома. Информация хранится в журналируемой базе данных Logical Disk Manager (LDM) в последнем 1 Мб диска. Начиная с версии ядра 2.5.29, в него включаются драйвера для работы с LDM, для версий 2.4.19 и 2.4.20-pre1-ac1 доступен патч. Отдельно в пакете linux-ldm доступны две утилиты для работы с LDM. Первая ldminfo выводит детальную информацию о LDM-базе, ldmutil – предназначена для восстановления, резервирования и изменения LDM-баз. Планируется в ближайшее время начать работу над библиотекой ldmlib, в которую будет вынесена часть кода, и еще над несколькими утилитами, которые позволят работать с отдельными частями раздела LDM и записывать/читать информацию с базы. Как видите, хотя работа над новыми драйверами и утилитами идет полным ходом, но, увы, на данный момент они не могут обеспечить пользователя требуемой (полной) функциональностью, и, как говорится, нормальные герои всегда идут в обход, что и сделали разработчики в проекте, о котором пойдет речь дальше.
но работает не со всеми версиями драйверов Windows (полный список проверенных в работе доступен на сайте), во всяком случае со взятыми с русифицированной версии Windows XP с первым сервис-паком работа у меня не пошла. В этом случае придется тащить файлы с сайта Microsoft, где они находятся по адресу: http://www.microsoft.com/ WindowsXP/pro/downloads/servicepacks/sp1/checkedbuild.asp, с которыми captive работает отменно. Правда, ситуация с их использованием двоякая, с одной стороны, они лежат свободно, с другой – предназначены для пользователей Windows, во всяком случае во всех дистрибутивах, имеющих captive, их приходится добывать самостоятельно. Для работы их следует положить в каталог /var/lib/captive. Так как драйверы Windows требуют особых привилегий вроде прямого доступа к железу, то полную 100% эмуляцию реализовать не получится, т.к. UNIXсистемы, естественно, будут защищаться от процессов, которые лезут не в свое дело, поэтому эмулируемая среда отделена от остальной части UNIX и любой код, выполняющийся в этой среде, не должен привести к краху системы. Для защиты от возможного краха используются несколько технологий. Так, для работы потребуется модуль ядра LUFS (Linux Userland File System), который, если будете собирать из исходников, нужно взять с http://lufs.sourceforge.net/lufs. Во время установки создается новый пользователь и группа captive, от имени которых и будут работать процессы, а сам процесс по умолчанию запускается в изолированой CORBA sandbox среде и chroot-окружении. К сожалению, эти ограничения сказались на невозможности работы одновременно сразу с несколькими разделами. Драйверы же на сайте проекта captive доступны как в исходных кодах, так и в прекомпилированном виде со статической линковкой, работают они одинаково, проблема может быть только с модулем ядра lufs.o, который должен быть собран под определенную версию ядра. После установки для первоначального конфигурирования необходимо запустить утилиту captive-install-acquire (рис. 2), которая проверит наличие необходимых библиотек и при необходимости закачает все нужное.
Проект Captive NTFS Тема эмуляции работы операционных систем, и в частности Windows, довольно популярна в среде Linux/UNIX, достаточно вспомнить проекты вроде Wine или Crossover Office, поэтому не удивительно, что нашлись разработчики, попытавшиеся заставить работать Linux с разделами NTFS средствами самой Windows. Свободный проект, реализующий возможность записи/чтения данных с разделов NTFS путем эмуляции всех необходимых уровней Windows, называется Captive и находится по адресу: http://www.jankratochvil.net/project/ captive. Для работы captive потребуется драйвер ntfs.sys и системный модуль ядра NT – ntoskrnl.exe. Если под рукой имеются компьютеры с установленными Windows XP, то их можно взять в C:\WINDOWS\system32\ntoskrnl.exe и C:\WINDOWS\system32\drivers\ntfs.sys), но captive нормаль-
№8(21), август 2004
Ðèñóíîê 2
21
администрирование Если все есть, то можно монтировать раздел: # mount -t captive-ntfs /dev/hda7 /mnt/utils/
В случае ошибок вся информация доступна /var/log/ messages. #cat /var/log/messages | grep captive-lufs
Если требуется автоматическое монтирование при загрузке системы, используйте скрипт captive-install-fstab с параметром -add, который автоматически добавит в файл /etc/ fstab используемый раздел. Еще пару слов хочу сказать о коммерческом драйвере от Paragon Software Group, обеспечивающем прозрачный доступ к разделам с файловой системой NTFS. Доступен драйвер в двух версиях: персональной и профессиональной. Как и у предыдущих проектов, поддерживаются все версии файловой системы NTFS, сжатые файлы и каталоги, размеры дисков до 127 Гб, в персональной версии работа возможна только в режиме чтения, а в професиональной возможна запись (что, в общем, и не должно вызывать удивления; учитывая опыт работы этой компании на подобном поприще). Демо-версию драйвера, которую производитель разрешает использовать без регистрации в течение 30 дней и поддерживающую только чтение, можно скачать с http://www.ntfs-linux.com. Для установки потребуются исходники ядра. Установка проста до безобразия: после распаковки архива запускаем скрипт install.sh (возможен запуск в интерактивном режиме ./install.sh –interactive и при помощи –iocharset=koi8-r возможно установить кодировку по умолчанию для раздела NTFS). После этого, если не выбрано автоматическое монтирование при загрузке, можно примонтировать раздел вручную:
источником, т.к. в некоторых дистрибутивах вам позволят изменить раздел только с командной строки (например, забравшись во вторую консоль [Ctrl]-[Alt]-[F2]). Для работы ntfsresize драйвер поддержки NTFS в ядре не нужен, утилита обращается напрямую к диску. Также если не требуются все утилиты или хотите использовать ее в спасательной дискете, то можно взять статически слинкованную версию ntfsresize по адресу: http://mlf.linux.rulez.org/mlf/ezaz/ ntfsresize-static-1.9.1.tgz. Теперь посмотрим, что у нас есть: # ./ntfsresize -i /dev/hda7
Команда выдала все о разделе NTFS и сообщила, что можем уменьшить раздел вплоть до 5 Мб. Но перед реальным изменением желательно прогнать тест. # ./ntfsresize --no-action --size
500M /dev/hda7
Если в результате получим сообщение «The read-only test run ended successfully.», то можно смело приступать к изменению размера. Если же команда выдала «ERROR:», то лучше для начала исправить ошибки, спешка в данном случае ни к чему хорошему не приведет. # ./ntfsresize -s 500M /dev/hda7
#mount -t ufsd /dev/hda7 /mnt/test_ntfs # mount -t ufsd -o iocharset=koi8-r /dev/hda7 /mnt/test_ntfs
Изменение размеров NTFS из-под GNU/Linux Теперь давайте попробуем разобраться с вопросом, можно ли изменить размер раздела с файловой системой NTFS прямо из-под GNU/Linux, не прибегая к посторонним утилитам вроде Partition Magic. По заверению разработчиков проекта Linux-NTFS, утилита ntfsresize, доступная пользователям с июля 2002, позволит изменить размер раздела, не разрушив при этом данных, причем нормально поддерживаются все версии NTFS и работает нормально со всеми версиями системы (Windows XP/2000/NT4, Windows Server 2003 и даже беты Longhorn). Разработчиками сделано все, чтобы свести риск потери данных к минимуму, для подстраховки проводятся всевозможные проверки, включая проверку на непротиворечивость данных, и, найдя проблемы и при возникновении подозрений, утилита отказывается производить изменение размера. Давайте проверим. Хотя уже появились графические средства, использующие ntfsresize, но для начала разберемся с перво-
22
Если все данные сохранены, то соглашаемся:
Проверить работу можно, введя:
администрирование #./ntfsresize --info --force /dev/hda7
Как видите, утилита свою работу проделала, но изменила только размер самой файловой системы NTFS, размер же дискового раздела остался неизменным (что можно легко проверить, введя: fdisk -l | grep -i ntfs), и далее, следуя инструкции, необходимо проделать еще несколько шагов. Неплохо бы для начала на всякий случай сохранить MBR. # dd if=/dev/hda of=hda.mbr bs=512 count=1
Запускаем fdisk, конечно, было бы нагляднее воспользоваться cfdisk, но если указать ему новое значение раздела в мегабайтах, то он округлит до ближайшего значения, которое, вполне возможно, будет меньше требуемой величины, что приведет к невозможности работать с разделом, fdisk же корректно обрабатывает ситуацию. # fdisk /dev/hda
Если же вся работа производилась в работающей с жесткого диска системе, необходимо заново перечитать данные о новом разбиении, для чего придется перезагрузиться. Но для нормальной работы обязательно нужно проверить файловую систему средствами Windows. Уже появились и графические утилиты, в том числе и инсталляторы, позволяющие изменить раздел в наглядном и понятном любому пользователю виде, которые используют код ntfsresize. К таким приложениям относятся DiskDrake от MandrakeSoft, который может встретиться и в других производных от Mandrake дистрибутивах, YaST от SUSE доступный и после установки. Некоторые дистрибутивы используют коммерческие утилиты для разбиения разделов ASPLinux – PartitionExpert, Xandros использует PQDisk. Но самой известной, распространенной и, главное, свободной графической утилитой, предназначенной для разбиения диска, является QTParted (http://qtparted.sourceforge.net, рис. 3). Те, кто пользовался PartitionMagic, трудностей с освоением не встретят. Выбираем нужный раздел, далее правый клик – Resize (рис. 4 ), полозком или цифрами выставляем новый размер и подтверждаем изменения File → Commit.
Удаляем раздел, на котором размещается NTFS, в моем случае это 7.
Создаем на его месте раздел с новым размером 500 Мб, при этом начальный цилиндр обязательно должен совпадать, т.е. судя по выводу выше – 3522.
Ðèñóíîê 3
Устанавливаем тип раздела для NTFS – 7.
Записываем изменения и выходим. Ðèñóíîê 4
Если все выше проделывалось с LiveCD или дискетного дистрибутива, то сразу же можно просмотреть информацию о разделе. # ./ntfsresize -i -f /dev/hda7
№8(21), август 2004
К сожалению, можно сделать вывод, что работа GNU/ Linux с файловой системой NTFS еще далека от идеала, а учитывая трудности написания вслепую драйвера и проблемы с полной эмуляцией, когда будет окончательное решение, сказать пока трудно. Но как видите, по сравнению с предыдущими годами, сдвиги есть, и работа ведется усиленными темпами. Так, сайт Linux-NTFS уже давно не обновлялся, потому что разработчики сосредоточились на конечном результате. Будем надеяться, что он не заставит себя долго ждать.
23
администрирование
ЕЩЕ РАЗ О ClamAV: ОСОБЕННОСТИ УСТАНОВКИ ВО FreeBSD СЕРГЕЙ СУПРУНОВ Статья Сергея Яремчука «Свободный антивирус», опубликованная в мартовском номере журнала «Системный администратор» 2004 года, вдохновила меня на установку этого пакета на вверенном мне сервере. Однако выяснилось, что под FreeBSD этот процесс происходит несколько иначе, чем под Linux, что и стало поводом для написания данной статьи. Впрочем, ничего сложного нет, поэтому материал ориентирован скорее на новичков. Антивирус ClamAV имеется в коллекции портов FreeBSD, а потому вполне разумным мне показалось выполнить установку именно оттуда. Ряд проблем, с которыми пришлось столкнуться, показал, что ClamAV портирован на эту ОС не совсем корректно. Но обо всем по порядку… Первым делом я отправился в /usr/ports/security/clamav. Чтение Makefile позволило узнать, что предлагаемая версия – 0.73, что выглядит довольно хорошо (последняя на тот момент была 0.74, но, как известно, «последнее» – не значит «лучшее»). Там же узнаем опции, с которыми пакет будет сконфигурирован: CONFIGURE_ARGS= --with-dbdir=${DATADIR} ↵ --disable-clamuko --disable-clamav ↵ --enable-bigstack --disable-dependency-tracking
Опцию --disable-clamav, отменяющую работу от имени пользователя clamav, я решил убрать – зачем пренебрегать дополнительной безопасностью? Ну и поскольку системные администраторы – люди исключительно бескорыстные, и все их заботы и труды – о пользователях и ради пользователей, то была добавлена поддержка milter для последующей интеграции нашего антивируса с sendmail:
Сборка пошла, но через несколько минут появилось сообщение, что программа не будет нормально работать, пока в системе не появится пользователь clamav. Пришлось выполнить этот ультиматум вручную, хотя обычно порт создает нужных ему пользователей самостоятельно. Сборка завершилась успешно, инсталляция тоже особых проблем не породила, не считая того, что с первого раза вывалилась ошибка, а повторный make install, запущенный для детального изучения проблемы, завершился без проблем. Видимо, с первой попытки не удалось закачать какой-то вспомогательный пакет. На стадии инсталляции тоже частенько докачиваются и устанавливаются вспомогательные пакеты, которые для сборки не нужны, но используются в дальнейшей работе. В частности, для clamav именно на этом этапе происходит установка портов unarj, unrar и т. д., если их нет в системе, а проверка архивов включена. На первый взгляд все выглядело весьма респектабельно: конфигурационные файлы clamav.conf и freshclam.conf чинно лежали в /usr/local/etc. В первом я включил закомментированные по умолчанию опции LogTime, выводящую в лог время события (лог при этом растет вдвое быстрее, зато понятно, что и когда происходит), и ScanRAR, разрешающую проверку rar-архивов (проверка остальных по умолчанию включена). Директория /usr/local/etc/rc.d обзавелась еще двумя сценариями, призванными автоматически запускать при перезагрузке компьютера демон clamd и утилиту обновления баз freshclam. Запуск clamscan продемонстрировал его работоспособность и вселил надежды на светлое будущее:
CONFIGURE_ARGS+= --enable-milter
Попутно я обратил внимание на следующую строчку: OPTIONS=
MILTER “Compile the milter interface” off
Попытка собрать систему вышеприведенной опцией и без нее не обнаружила никаких различий. Более того, в bsd.ports.mk упоминания параметра OPTIONS не нашлось. Возможно, во FreeBSD он не используется. Полагая, что подготовительные работы закончены и все остальное инсталлятор сделает сам, была подана команда make. И тут случилась первая аномалия: утилита, как и положено, попыталась скачать дистрибутив пакета, но только с одного сайта – с ftp.freebsd.org, и заявила об отсутствии там нужного ей файла clamav-0.73.tar.gz. Раньше я такого не видел – на сервере операционной системы всегда в папке distfiles обнаруживался любой нужный портам архив. Пришлось забирать требуемый файл с сайта проекта clamav.sourceforge.net.
24
Однако сильно смущало время обработки – свыше 10 секунд для 20 Кб проверенных файлов (конфигурация сервера – Celeron-466/64Мб, load average ~ 0.1). Может, дело в каких-то «подготовительных работах» вроде подгрузки базы данных, и на больших объемах дело пойдет лучше? Действительно, проверка на папке /usr/ports/distfiles показала некоторое увеличение скорости:
администрирование
Почти 2 секунды на каждый мегабайт. Ну да ладно. Нагрузка на мой сервер пока минимальна, будем надеяться, что медлительность с лихвой окупится дотошностью. По крайней мере тестовые сигнатуры в собственных пакетах антивирусу обнаружить удалось. Следующая неприятность ждала меня при попытке запустить демон традиционным для FreeBSD способом: /usr/local/etc/rc.d/clamav-clamd.sh start
Оказалось, что стартовые сценарии разработаны в стиле Linux и для запуска из /usr/local/etc/rc.d не годятся. Переносить их в /etc/rc.d с добавлением соответствующих опций (приведенных в начале стартовых скриптов) в файл /etc/rc.conf я посчитал неправильным. Как бы то ни было, но место всех программ, устанавливаемых пользователем, – в /usr/local. Ну что ж, раз не хочешь по-хорошему, сделаем все посвоему. Созданные при инсталляции стартовые сценарии были безжалостно уничтожены, а их место занял следующий: #!/bin/sh # Manager for ClamAV: clamd & clamav-milter case $1 in start) if [ -e /var/run/clamav/clamd.pid ]; then echo 'Ingored - clamd already started.' exit 0 fi if [ -e /var/run/clamav/clmilter.sock ]; then rm /var/run/clamav/clmilter.sock fi /usr/local/sbin/clamd /usr/local/sbin/clamav-milter -blo ↵ /var/run/clamav/clmilter.sock echo 'clamd & clamav-milter started' ;;
сокетов соответствуют друг другу в строке запуска clamavmilter и в sendmail.mc.) С сокетом clamd.sock таких манипуляций не понадобилось, поскольку демон успешно убивает старый сокет сам, лишь сообщив в лог-файл об этом. Как видно из файла, помимо демона clamd запускается и clamav-milter. А вот в вопросе обновления баз я решил на демонизацию freshclam не полагаться (хотя такая возможность предусмотрена) – зачем запускать еще один демон, когда с этим отлично справится cron: 0
0,12
*
*
*
/usr/local/bin/freshclam
Настройка sendmail была выполнена в полном соответствии с документацией и проблем не вызвала (см. статью Сергея Яремчука). Нужно только не забыть перезапустить сервер (make restart в /etc/mail). Итак, затратив немного усилий, удалось заставить clamav работать так, как это принято во FreeBSD. Конечно, если бы порт был разработан более корректно, то и эти усилия не понадобились бы. В первые часы работы было «зарезано» около 50 зараженных писем (напомню, что лечить файлы ClamAV пока не умеет), извещения о данном прискорбном факте были добросовестно разосланы отправителям, получателям и администратору. Огорчало одно – язык сообщений был английским, а зная своих пользователей, нетрудно было спрогнозировать раскаленный телефон службы технической поддержки. Но и эта проблема оказалась более чем решаемой: все сообщения, отсылаемые пользователям, были сосредоточены в исходном файле clamav-milter.c и насчитывали не более пяти строк. Таким образом, «русификация» свелась к простому вбиванию новых слов взамен старых. Ну и в заголовок отсылаемого письма была добавлена (в том же clamav-milter.c) строчка «Content-Type: text/plain; charset= ”koi8-r”», поскольку без нее мой Outlook упорно пытался подсунуть мне письма под видом win-1251. После правки исходника – повторная сборка и установка. Результат представлен на рисунке.
stop) killall clamav-milter kill -15 `cat /var/run/clamav/clamd.pid` echo 'clamd & clav-milter stopped' ;; restart) kill -1 `cat /var/run/clamav/clamd.pid` echo 'clamd restarted' ;; fresh) /usr/local/bin/freshclam ;; *) echo "Usage: `basename $0` ↵ {start|stop|restart|fresh}" >&2 ;; esac exit 0
Удалять clmilter.sock я решил вручную, поскольку натолкнулся на то, что иногда он удаляется с некоторой паузой (или не удаляется вообще) в результате уничтожения демона clamav-milter, и при повторном запуске milter возникает ошибка. (Кстати, следует убедиться, что имена и пути
№8(21), август 2004
В заключение следует заметить, что проблемы, описанные в статье, вполне вероятны при попытке запустить на FreeBSD и иные приложения, первоначально разработанные для другой ОС. Методы решения большинства из них будут аналогичными. Главное – хорошо представлять себе, что должно получиться в итоге.
25
администрирование
CROSSOVER И ЛИЦЕНЗИОННЫЙ ВОПРОС
АНДРЕЙ БЕШКОВ 26
администрирование В ответ на публикацию трилогии о CrossOver Office в мой почтовый ящик посыпались читательские письма с разнообразными вопросами. Сегодня мы займемся самым часто встречающимся из них: «Объясните мне смысл покупать этот продукт, если есть wine». Wine – это, конечно, хорошо, но в некоторых областях CrossOver на данный момент впереди планеты всей. Например, продвинутая поддержка InstallShield, удобство интерфейса, простота установки и самое главное – очень дружелюбная служба технической поддержки. Впрочем, это уже индивидуальные вкусовые предпочтения. Большинство читателей в силу особенностей менталитета более всего интересует вариант, чтобы можно было и рыбку съесть, и косточки сдать. То есть получить все бесплатно. Моралисты всех мастей сейчас начнут качать головой и говорить: «Ой, как нехорошо». Ну а мы посмотрим, как добиться решения поставленной задачи. Предположим, что у нас установлена версия CrossOver Office c ограничением на 30 дней. Проявив немного любознательности, можно заметить, что после инсталляции у нас на жестком диске появился файл /var/run/cxoffice/.evaluation. Хотя в некоторых дистрибутивах Linux этот файл может находиться, к примеру, в домашней директории пользователя. Для того чтобы пробный период никогда не закончился, нужно всего лишь удалять его раз в несколько дней. Данный трюк работает на основе того, что начало тестового периода всегда отсчитывается от даты создания вышеупомянутого файла. А если его на диске нет – значит программа считает, что запущена в первый раз и пользователю полагается законные тридцать пробных дней. Насколько я понимаю, с юридической точки зрения тут нет никаких проблем. Владелец компьютера имеет полное право удалять любые файлы, хранящиеся на жестком диске личного компьютера. Ну а тот факт, что защита программы от этого будет неправильно работать, однозначно относится к личным проблемам разработчика. В то же время можно пойти совсем другим путем и стать официальным пользователем коммерческой версии Cross Over Office совершенно бесплатно. И самое главное, что мы не только соблюдаем все законы, что само по себе достойно всяческих похвал, но еще и помогаем развитию проекта. Для этого нужно, вооружившись именем пользователя и паролем, которые нам выдали при первичной регистрации, войти на сайт http://www.codeweavers.com/login. Затем ознакомиться с документацией по центру совместимости приложений http://www.codeweavers.com/site/ compatibility. Центр создавался с одной-единственной целью облегчить взаимодействие между разработчиками и пользователями продукта. Соответственно посещать его в любом случае полезно, но мы сейчас не об этом. В составе центра находится список приложений, которые так или иначе работают под CrossOver Office. Посмотреть на его наполнение можно здесь: http://www.codeweavers.com/ site/compatibility/browse/name. С выходом новых версий CrossOver Office из-за изменений, вносимых в код, одни приложения начинают работать лучше, а другие наоборот – хуже, соответственно, разработчики проекта заинтересованы в большом количестве бета-тестеров. Таким образом, можно будет очень быстро заметить, насколько
№8(21), август 2004
ухудшилось самочувствие той или иной программы и по горячим следам провести корректировку кода системы. Тут мы как раз и подходим к самому интересному. Бетатестеров проекта называют адвокатами приложений по той причине, что каждый из них следит за самочувствием дорогих ему сердцу программ. Подробнее почитать о процессе адвокатства и получаемых от него выгодах можно тут – http://www.codeweavers.com/site/compatibility/ advocate_overview. Если говорить коротко, то адвокаты получают в свое распоряжение новейшие версии коммерческих разработок CrossOver Office. Все отчеты от адвокатов по поводу тех или иных проблем, возникающих в процессе эксплуатации программного продукта, попадают на рассмотрение технической службы вне очереди. Плюс ко всему возможность напрямую общаться с нужными вам людьми из команды СodeWeavers. Также приветствуется голосование за интересные для вас приложения, таким образом можно стимулировать разработчиков уделять больше внимания именно этой программе. В обмен на такие услуги адвокат должен заниматься тестированием своего приложения на новых версиях CrossOver Office, помогать другим пользователям, у которых возникли проблемы установки или использования данного приложения, активно поддерживать форум приложения, участвовать в дискуссиях по поводу тех или иных проблем системы и, наконец, отправлять отчеты о найденных ошибках. Думаю, это небольшая плата за возможность пользоваться коммерческой версией CrossOver Office, к тому же все это происходит на добровольных началах, поэтому не стоит ожидать, что кто-то будет стоять над вами с палкой и требовать отработать n-ое количество человеко-часов на благо проекта. Все, кто захотел стать адвокатом и тем самым помочь проекту, могут сделать это прямо сейчас. В списке программ нужно выбрать то приложение, которое нравится вам: http://www.codeweavers.com/site/compatibility/browse/ name. В качестве примера давайте используем 1C Предприятие, у которого пока что нет адвокатов ht tp:// www.codeweavers.com/site/compatibility/browse/ name?app_id=298. На страничке приложения нажать на ссылки «Want to help?» затем «help CodeWeavers» и, наконец, «Make Me an Advocate». В течение нескольких ближайших дней ваша заявка будет рассмотрена и, скорее всего, удовлетворена. О назначении вас адвокатом можно узнать, периодически заходя на страницу приложения. В случае если интересующих вас приложений в базе найти не удалось, есть возможность добавить их самому: http://www.codeweavers.com/site/compatibility/submit/ и уже затем стать адвокатом. Кстати, стоит отметить, что количество адвокатов, работающих над одним приложением, теоретически не ограничено, но на практике больше пяти не бывает. Став адвокатом, можно будет получить доступ к внутренним форумам и разделу с новейшим программным обеспечением: http://www.codeweavers.com/site/compatibility/ advocate_center/. Также стоит подписаться на списки рассылки: http://www.codeweavers.com/site/support, довольно часто там обсуждают интересные проблемы.
27
администрирование
В ЯБЛОЧКО! КРАТКИЙ ОБЗОР ОС DARWIN 7.0 НА ПЛАТФОРМЕ X86 (MAC OS X 10.3 JAGUAR) Полгода прошло с момента выхода в свет последнего релиза упомянутого в заголовке продукта. Президент Apple Стив Джобс подтвердил, что MacOSX действительно можно запустить на x86-платформе [1]. Давайте разберемся, что же может привлечь потенциального пользователя/администратора, помимо легендарного названия фирмы Apple Computer? В первую очередь, ориентирование компании на постепенное расширение своей продукции. Как известно, на протяжении своей истории операционные системы фирмы Apple работали только на оборудовании самой Apple. Отчасти это правильно. Приходится меньше волноваться о вопросах совместимости аппаратного и программного обеспечения. В то же время это и откровенный тупик в развитии.
АНТОН БОРИСОВ Что же из себя представляет Darwin? Если коротко – это некое ядро операционной системы MacOSX, оно состоит из 5 главных компонентов: микроядро Mach, BSD-подсистема, файловая система, сетевая подсистема и система ввода-вывода (I/O Kit). ! Микроядро Mach занимается распределением вычислительных ресурсов, защитой памяти, обменом сообщений между процессами. ! Вокруг микроядра существует «обертка» из POSIX API, абстрактная файловая система и сетевая подсистема. Многое в микроядре заимствовано из 4.4 BSD-Lite2, соответственно модель процессов, система безопасности, потоковая поддержка покажутся знакомыми всем, кто работал с BSD-системами. ! Файловая система поддерживает как UNIX (UFS), так и «родную» маковскую файловую систему (HFS). ! TCP/IP-стек основан на проверенном временем BSDкоде. ! Объектно-ориентированная система ввода/вывода представляет развитую инфраструктуру для управления драйверами устройств. К сожалению, графическая подсистема MacOSX (Quick Time, OpenGL, Quartz), пользовательский интерфейс (Aqua) не присутствуют в системе. Хотя и без них Darwin является уже полноценной ОС. Более подробно об истории возникновения Darwin см. [2].
28
Дискутировать относительно открытости продуктов самой фирмы Apple можно долго, однако нас в первую очередь должно волновать несколько моментов: ! списки совместимости с оборудованием (так называемые hardware compatibility list [3]); ! знание структуры *BSD-систем; ! потребность в использовании данной ОС в обучающем процессе на производстве. Насчет первого пункта сильно огорчаться не следует, т.к. система стартует и работает на более-менее современной x86 машине. В общем-то, Intel Celeron 366, 128 Мб памяти должно хватить для ознакомления. Также следует обзавестись одной из следующих сетевых карт: 3Com 3c90x, Intel 8255x, Broadcom 570x, Realtek 8139, Dec 21x4 (она же Tulip). Встроенные сетевые карты, к сожалению, не поддерживаются. Видеокарта должна работать в VESA-режиме. Для начала возьмем дистрибутив с сайта OpenDarwin [4]. Он из себя представляет упакованный gzip-образ, который потом надо записать на компакт-диск. Размер упакованного образа составляет 430 Мб. wget -ct0 http://opendarwin.org/downloads/darwin-701.iso.gz gzip -d darwin-701.iso.gz cdrecord -v darwin-701.iso
Для дальнейшей работы потребуется пустой жесткий диск размером не более 2 Гб или такого же объема раздел.
администрирование Установленная система занимает примерно 1 Гб. Оставшееся место будем использовать для сборки необходимых пакетов. Настраиваем загрузку с компакт-диска, и после определения оборудования ядром ОС мы увидим приглашение выбрать тот диск, на который хотим поставить операционную систему.
Пункт 1 предназначен для установки на пустой диск. В случае, когда не надо иметь на диске несколько систем, выбирайте его. Пункт 3 предназначен для случая, когда место заранее выделено и размечено под операционную систему Darwin (ее идентификатор 0xA8, см. далее по тексту). Если вам не подошли пункты 1 или 3, тогда вспоминаем, что настоящие герои всегда идут в обход и выбирают самый легкий путь. В целом разметка вручную не так и страшна, как кажется на первый взгляд. Выбираем пункт 2. Для начала следует ознакомиться с командами fdisk данной системы. Правильно, нажимаем слово «help» и читаем, что есть в меню. Команд немного, поэтому можно набирать не полную версию, а, например, сокращать до 2 начальных букв. То есть вместо «help» писать «he». Команды прочитаны, пора выделить место под наше сегодняшнее яблочко. Распечатаем содержимое MBR диска, на который будем ставить ОС. Для этого подаем команду «print». Видим теперь, где находятся разделы и свободное место. Для редактирования свободного места подадим команду «edit ID», где ID – это номер раздела, предположим под номером 2 (то есть на текущем диске первичный раздел номер 2). Процедура выбора необходимого размера для нового раздела не составит, я надеюсь, особого труда. Отмечу, что размер считается в секторах, а не в килобайтах. Идентификатор файловой системы для Darwin имеет код 0xA8 (Darwin UFS). Затем следует обновить информацию в MBR (Master Boot Record). Если вы ставите на чистый диск, то в MBR никакого загрузчика нет. Поэтому следует записать в него штатный, идущий в комплекте с OpenDarwin. В случае когда вы устанавливаете на отдельный раздел, все равно придется перезаписать MBR. В этом нет ничего страшного, т.к. дальше в статье разъясняется на примере LILO, как сделать загрузку нескольких операционных систем. Для этих целей предусмотрена команда «update». После этого запишем в MBR непосредственно сведения о разделе, в котором будет жить наша «яблочная» операционная система. Сделаем это с помощью команды «write». Следует помнить, что в MBR записывается новый загрузчик, поэтому если у вас стоял LILO, GRUB или что-то аналогичное, то он будет просто-напросто переписан загрузчиком от Darwin. У меня используется LILO, поэтому я просто добавил в /etc/lilo.conf следующую строчку: # DarwinOS begins other = /dev/hdd2 label = DarwinOS # DarwinOS ends
№8(21), август 2004
И запустив /sbin/lilo, перезаписываю MBR. Теперь при старте можно будет выбрать загрузку с раздела hdd2, где обитает OpenDarwin. Всё. Выставим флаг активности раздела (команда «active ID», где ID – опять-таки нужный нам номер раздела). И завершим разметку подав команду «exit». После этого осталось дождаться, чтобы установились пакеты, входящие в состав компакт-диска. Как говорится, возьмите чай, кофе – сахар по вкусу. В ходе установки внимание обращает тот факт, что все пакеты находятся в упакованном виде. Сжаты они с помощью bzip2. После установки истинно «яблочных» пакетов, таких как AppleUSBAudio, следом идут пакеты с до боли знакомыми названиями. Согласитесь, что такие строчки, как apache-670tar.bz2, bind9-7tar.bz2, gcc-1495tar.bz2, вам о чем-то говорят. В этом нет ничего удивительного, т.к. система, как вы, наверное, слышали, базируется на BSD. Большинство утилит, присутствующих в системе, хорошо знакомы людям, постоянно работающим в Open/Net/ FreeBSD, да и тем, кто когда-либо занимался сборкой программ под UNIX. Вернемся к нашему яблоку. Перегружаемся, не забываем вытащить из привода компакт-диск. Далее загрузка будет происходить с нашего жесткого диска. Вот несколько интересных, на мой взгляд, строк, которые можно наблюдать при старте системы. Ваше аппаратное обеспечение отличается от моего, но по аналогии можете сами проследить отличия.
Итак, строка externalClock = 0x85 означает, что шина работает на частоте 133 МГц (переведите цифры в десятичное представление). Строка currentClock = 0x5ba разъясняет, что текущая тактовая частота равна 1466 МГц, а максимальная частота для этого типа процессора равна 2250 МГц (maximumClock = 0x8ca). Стоит разъяснить, откуда появляется надпись «hfs_ mount: invalid HFS+ sig 0x0000». Вы помните, когда мы ставили идентификатор раздела, то поставили 0xA8 (Darwin UFS). В связи с чем делаем вывод, что корневой раздел у нас отформатировался в UFS-формате. Ничего страшного, кроме досадного факта, что файловая система у нас получилась нежурналируемая. Для того чтобы корневая файловая система была в HFS+ формате, при начальной разметке диска следует ставить идентификатор 0xAF (вме-
29
администрирование сто 0xA8). Перевести систему из UFS в HFS+ пока не представляется возможным. Процесс загрузки ОС кардинально не отличается от процесса загрузки системы на FreeBSD. Знакомые конфигурационные файлы в /etc (rc, rc.boot, rc.common, fstab, syslog.conf и т. д.). Итак, перед нами приглашение на вход в систему. Заходим под пользователем root (пароль изначально не установлен). Что необходимо сделать на этом этапе? Попробуем поднять сеть? Давайте сделаем! Даем команду: # ifconfig
Ага, вот и наша сетевая карта (интерфейс en0). Есть два пути – правильный и не очень правильный. Чтобы прописать в Darwin IP-адрес для интерфейса, в /etc/ надо создать файл iftab. Структура его следующая: en0 inet 10.0.0.10 netmask 255.255.255.0 up
где 10.0.0.10 – IP-адрес, 255.255.255.0 – маска сети, «up» – интерфейс при загрузке поднять. Стоит отметить, что файла /etc/rc.conf в Darwin просто нет. Поэтому вписать информацию о сетевых адресах по аналогии с FreeBSD не получится. Так вот, правильный путь не работает. Поэтому либо пропишем в /etc/rc.common наш сетевой адрес (для этого найдите функцию CheckForNetwork() – в ее теле и надо прописать), либо идем в системный каталог автозагрузки. Его полный путь – /System/Library/StartupItems. Каталог, отвечающий за сеть, именуется Network. Файл, который необходимо отредактировать, совпадает с названием каталога (его имя тоже Network). Обратите внимание, что на нем установлены биты на исполнение. # cat Network #!/bin/sh ## # Configure network interfaces and host name ## . /etc/rc.common StartService () { ConsoleMessage "Initializing network" ipconfig waitall > /dev/null 2>&1 if [ "${IPV6:=-YES-}" = "-NO-" ] then sysctl -w net.inet6.ip6.auto_on=0 > /dev/null ip6 -x fi if [ "${IPFORWARDING:=-NO-}" = "-YES-" ] then sysctl -w net.inet.ip.forwarding=1 > /dev/null
30
else fi
sysctl -w net.inet.ip.forwarding=0 > /dev/null ifconfig en0 10.0.0.10 netmask 255.255.255.0
} StopService () { return 0 } RestartService () { return 0 } RunService "$1"
Вот так выглядит файл Network на моей машине. Добавленная строка выделена красным цветом. Небольшое уточнение, выясненное в ходе настройки. При переходе на новую систему не надо забывать, что структурно Darwin не отличается от FreeBSD или, скажем, Linux в плане загрузки модулей. Что я хочу этим сказать. Если у вас сетевая карта опознана, то Darwin подгрузит для нее модуль. Как только модуль подгружен, то можно выставлять IP-адрес и маску. В общем, если выставляете IP-адрес в один из rc-файлов, будьте внимательны. Вставляйте строку после загрузки модулей ядра (они загружаются демоном kextd). На этом этапе сеть у нас готова. Правда, развертка монитора может раздражать. Мы ведь работаем в VESA-режиме. Вертикальная развертка 60 Hz. Давайте зададим текстовый режим консоли. Для этого редактируем файл /Library/Preferences/SystemConfiguration/ com.apple.Boot.plist. В оригинале он выглядит так. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/P ropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Kernel</key> <string>mach_kernel</string> <key>Kernel Flags</key> <string> -v</string> <key>Boot Graphics</key> <string>Yes</string> <key>APM</key> <string>Yes</string> </dict> </plist>
Да, забыл сказать. Почти все конфигурационные файлы в Darwin хранятся в xml-формате. Что мы сейчас и видим. Для текстового режима необходимо поменять параметр Boot Graphics с Yes на No. Со следующей загрузки будет использоваться текстовый режим. Добавим теперь сервисы ftp, telnet. Редактируем файл /etc/inetd.conf, удаляем ненужные символы, ставим комментарии перед нужными нам службами. Посылаем сигнал, чтобы демон inetd перечитал свои настройки. killall -1 inetd
администрирование Отлично. Службы появились. Теперь задумаемся, а может, нам еще нужен и веб-сервер? А DNS-сервер? Что ж, пробовать, так все и сразу. Чтобы запускать указанные и некоторые другие службы, при старте Darwin существует файл /etc/hostconfig:
{
# cat /etc/hostconfig # /etc/hostconfig ## # This file is maintained by the system control panels ## # Network configuration HOSTNAME=DarwinOS # Services AUTOMOUNT=-NOCUPS=-NOIPFORWARDING=-NOIPV6=-YESNETINFOSERVER=-NONISDOMAIN=-NORPCSERVER=-YESQTSSERVER=-YESWEBSERVER=-YESDNSSERVER=-NOCOREDUMPS=-NOVPNSERVER=-NO-
Если параметр установлен в -YES-, тогда при старте указанные службы будут запущены. Запуском apache (строка WEBSERVER=-YES-) занимается файл /System/Library/StartupItems/Apache/Apache. Для DNS-сервера выделен файл /System/Library/Startup Items/BIND/BIND. В целом прослеживается аналогия со структурой системных файлов, например, как в Linux Slackware. В последней скрипты, запускающие определенный сервис, называются /etc/rc.d/rc.ServiceName. Строка MAILSERVER предназначена для почтовой службы postfix, TIMESYNC – для синхронизации времени, CUPS – для сервиса, отвечающего за печать, SMBSERVER – sambaсервис, добавим самостоятельно чуть позднее. Картина вырисовывается следующая. Сначала отрабатываются файлы /etc/rc*, а затем – соответствующие файлы в /System/Library/StartupItems. Чтобы запустить свой сервис, необходимо создать в /System/Library/StartupItems директорию с названием сервиса. В ней должны находиться: исполняемый файл, совпадающий с названием только что созданной директории и информационный файл StartupParameters.plist. Давайте посмотрим, как создать скрипт сервиса, отвечающего за старт samba-сервера.
}
Description = "smb file server"; Provides = ("Samba"); Requires = ("Resolver"); OrderPreference = "None"; Messages = { start = "Starting Samba"; stop = "Stopping Samba"; };
Вставляем в файл /etc/hostconfig строчку SMBSERVER=-YES- и в дальнейшем надо будет выделить sambaресурсы (файл /etc/smb.conf). Так, со службами все понятно. Установим локальное время в системе. cd /etc ln -s /usr/share/zoneinfo/Europe/Moscow localtime
Для необходимого нам hostname следует подправить файл /etc/rc.boot. Вместо строки по умолчанию «hostname localhost» вставим «hostname DarwinOS». Теперь мы рассмотрим концепцию NetInfo, реализованную в Darwin. Система может искать информацию как в файлах службы NetInfo, так и в «плоских» («plain text») файлах (/etc/fstab, /etc/passwd и др.). Для добавления пользователя, существует утилита niutil (NetInfo Util). С помощью этой же утилиты настраивается большая часть системы. niutil niutil niutil niutil niutil
-create -createprop -createprop -createprop -createprop
/ / / / /
/users/Bob /users/Bob /users/Bob /users/Bob /users/Bob
shell /bin/tcsh realname UncleBob home /Users/Bob _shadow_passwd
Комментарии, надеюсь, излишни? Сначала мы создали объект Bob в иерархии /users, а затем добавили этому объекту свойства. Добавить добавили, но надо и создать ему домашний каталог. cd /Users mkdir Bob chown -R Bob:wheel Bob
Теперь у Bob домашняя директория, да и сам он получился по умолчанию в группе wheel. Можно переделать, добавив через niutil пользователю uid и gid. Для получения списка объектов в иерархии NetInfo выполним команду: niutil -list . /
#!/bin/sh #
Include system wide configuration options
. /etc/rc.common #
Start SMB services
Получим список корневых иерархий. Для просмотра объекта resolver (аналог /etc/resolver): niutil -read . /locations/resolver
if [ "${SMBSERVER:=-NO-}" = "-YES-" ]; then ConsoleMessage "Starting SMB services" /usr/sbin/smbd -D /usr/sbin/nmbd -D
Создадим запись в объекте resolver: niutil -createprop . /locations/resolver ↵ nameserver 10.0.0.100
fi
Всю базу NetInfo можно просмотреть:
Файл StartupParameters.plist:
nidump -r / / > nidump.txt
№8(21), август 2004
31
администрирование Приступаем к заключительной части нашей статьи. Готовимся к прыжку в окна X-сервера. В Darwin он называется XDarwin (это портированный в Mac OS проект XFree86). В системе обнаруживается только один оконный менеджер. Это twm. Для запуска twm создадим в домашней директории файл .xinitrc: # cat ~/.xinitrc /usr/X11R6/bin/xterm & /usr/X11R6/bin/twm
Не забываем, что он должен иметь установленным исполняемый бит. # chmod +x .xinitrc
А теперь в окна! То есть в X-сервер.
./configure --prefix=/usr/local/blackbox make && make install
Меняем .xinitrc. echo “/usr/local/blackbox/bin/blackbox” > ~/.xinitrc
Теперь, как мне кажется, намного симпатичнее. Большую часть приложений придется собирать из исходников. Кому нравится GNOME, нет проблем. Дело вкуса. Для любителей клубнички даже существует проект darwine, запуск windows-приложений под Darwin (небезызвестный родоначальник – проект wine). Пару слов о модулях, упомянутых в начале статьи. Просмотр информации о загруженных модулях: # kextstat
# startx
Как-то непривычно, не правда ли? Давайте поменяем twm на другой менеджер. Например, blackbox. Сказано – сделано.
В списке я оставил упоминания модулей, названия которых отчасти говорят о платформе и используемом оборудовании. ! kextload kextName – загрузка модуля под именем kextName (они находятся в /System/Library/Extensions). ! kextunload kextName – соответственно выгрузка из памяти модуля kextName. ! kextxcache – подготовка базы модулей, хранящихся в /System/Library/Extensions. Apache-сервер, которым комплектуется система, идентифицируется следующим образом:
wget -ct0 http://prdownloads.sf.net/blackboxwm/ ↵ blackbox-0.65.0.tar.gz mkdir ~/sources mv blackbox-0.65.0.tar.gz sources tar xzvf blackbox-0.65.0.tar.gz cd blackbox-0.65.0
32
администрирование Настройка ничем оригинальным не отличается. Интересно, как же идентифицируется система со стороны. Сейчас поглядим. Заходим на удаленную машину. И оттуда запускаем наш сканер портов nmap. rootfuji:# /usr/local/nmap/bin/nmap -v -sS -O darwin
Со стороны не отличишь, действительно ли это MacOSX на платформе PPC или x86. Жаль, что ни Aqua, ни Quartz не предусмотрены. Посмотрим на samba-ресурсы удаленной машины. DarwinOS:~ root# smbclient -L fuji -I fuji 2> /dev/null
Посмотрим на геометрию диска из-под ОС Darwin. DarwinOS:~ root# fdisk /dev/rdisk1
Наглядно и просто. Файловых систем не так уж и много. # ls /System/Library/Filesystems
№8(21), август 2004
Файловая система NTFS доступна только на чтение. Для монтирования файловой системы из-под Linux: # mount /dev/hdd2 /mnt/hd –t ufs –o, ufstype=44bsd
На момент написания статьи поддержка из-под Linux только на чтение. Напоследок пара строк о средстве фильтрации трафика. Конечно, это ipfw. Полностью похож на своего *BSDсобрата. Впрочем, это не собрат, а скомпилированный под Darwin оригинальный ipfw. Правила задаются и убираются абсолютно так же, как и в FreeBSD. Более подробно см. в статьях: «Ipfw и управление трафиком в FreeBSD» (№6 журнала «Системный администратор» за 2003 год), «Сам себе антихакер. Защита от хакерских атак с помощью ipfw» (№1 журнала «Системный администратор» за 2004 год). В целом из машины на основе OpenDarwin вполне возможно сделать почтовый релей, систему доступа из Интернета по ppp-соединению, веб-сервер. Основные компоненты по созданию сетевой инфраструктуры уже присутствуют в системе: postfix, mysql, php, apache, bind, perl. Относительно сетевой безопасности – код стека TCP/IP, как говорилось в начале статьи, основан на оригинальном BSD-коде. Если каких-либо программ не хватает, то следует обратиться на сайт [7]. Вполне возможно, что эти программы уже портированы и доступны, как ports. Несколько слов о компиляции программ под Darwin. В системе используется GNU Compiler Collection. Поэтому особых проблем при сборке возникнуть не должно. В Darwin я встретил такое понятие, как «толстые» файлы («fat»-files). То есть при сборке файлы компилируются под несколько архитектур, например, под PPC и под x86. И собираются в один бинарный файл. При выполнении файла происходит определение архитектуры и передается управление на необходимый участок кода. В частности, ядро, идущее с системой, собрано с поддержкой как PowerPC (PPC) архитектуры, так и x86. Конечно же, статья не претендует на полное освещение всех нюансов «фруктовой» ОС. Она предназначена в первую очередь для тех, кто собирается расширить свой кругозор, и тех, кто неравнодушен к самой компании Apple Computer. Мечты придуманы для того, чтобы из них делать реальность. И фруктовая компания не забывает об этом.
Ссылки: 1. 2. 3. 4. 5. 6. 7. 8.
http://developer.apple.com/darwin/history.html http://news.com.com/2100-1045_3-5103279.html http://www.opendarwin.org/hardware/ http://blackboxwm.sf.net http://www.opendarwin.org/documentation/ http://www.mymac.ru http://darwinports.opendarwin.org Потемкин А. Mac OS X или то, что должен знать каждый про Macintosh, Apple и операционные системы. – // Журнал «Системный администратор», №7, июль, 2003 г. – 68-77 с.
33
администрирование
ПРАКТИКА РАБОТЫ С NetBSD: ПРОФИЛИРОВАНИЕ ЯДРА
АЛЕКСАНДР БАЙРАК Перед началом рассмотрения процесса профилирования я предполагаю, что вы установили исходные тексты системы и умеете перекомпилировать ядро. Как это сделать, было описано в статье «Первые шаги в NetBSD. Часть 1», опубликованной в июньском номере журнала. Целью и задачей профилирования ядра служит сравнение производительности старого и нового ядра. Например, вы скомпилировали новое ядро, и по вашим расчетам оно должно работать быстрее первого, но желаемого результата получено не было. В чем дело? Где вы ошиблись? Все это можно будет выяснить, используя профилирование ядра. Если вы собираете NetBSD для какой-либо встроенной системы, без профилирования вам точно не обойтись. Ведь во встроенных системах аппаратные характеристики железа, как правило, очень ограниченны. А без использования профилирования вряд ли удастся настроить систему на оптимальную производительность. Рассмотрим пример профилирования для NetBSD, работающей на обычном x86-компьютере. Для примера мы возьмем ядро GENERIC, поставляющееся с системой по умолчанию, и сравним его по производительности с собранным вами новым ядром. Соберем профилированное GENERIC-ядро. Переходим в каталог, где располагаются конфигурационные файлы ядра:
# cp /netbsd /netbsd.old
Копируем в корень новое: # cp netbsd /netbsd
Перезагружаемся: # reboot
Сразу после загрузки системы проверим, работает профилирование или нет. # kgmon – b
В ответ мы должны получить:
Теперь отключим профилирование: # kgmon –h
В ответ мы получим:
Теперь нам нужно поместить данные kgmon в файл. # cd /sys/arch/i386/conf/ # kgmon –p
Опция –p указывает, что собираемое ядро будет профилироваться: # config –p GENERIC
Запускаем сборку ядра: # cd ../compile/GENERIC.PROF/ # make depend && make
Сохраняем старое ядро:
34
После этой команды мы получим файл gmon.out размером около 3 Мб. Далее нам нужно получить вывод gprof: #gprof /netbsd > gprof.out
Должен заметить, имя файла для вывода можно выбрать самому. Примерно через 2-3 минуты в текущем каталоге появится заказанный нами файл gprof.out. После это-
администрирование го переходим непосредственно к процессу анализа полученных данных. Смотрим наш gprof.out (или как вы его назвали). Первым разделом идет Flat profile, это список всех вызванных функций, время и количество их вызовов. Вот пример части вывода:
Дальше в том же духе. Давайте рассмотрим содержание этих столбцов более подробно. 1. Сколько всего времени (в процентах) исполнялась та или иная функция. 2. Общая сумма времени (в секундах) выполнения всех функций до текущего момента. 3. Время (в секундах) исполнения какой-либо функции. Это основной показатель данной таблицы. 4. Общее количество вызовов некой функции. 5. Среднее время (в миллисекундах), истраченное на вызов функции. Если функция не профилируется, то столбец останется пустым. Например, функцию idle, как вы понимаете, «улучшить» никак нельзя, поэтому текущий столбец для этой функции оказался незаполненным. 6. Среднее время (в миллисекундах), истраченное этой функцией и ее потомками на вызов. Так же, как и в предыдущем столбце, если функция не профилируется, значение остается пустым. 7. Имя функции. Как видно из этого фрагмента, подавляющее большинство времени система бездействовала. Теперь следует раздел Call Graph Profile. Его задача – показать дальнейшие запросы («потомки») от перечисленных функций. Вот часть вывода:
И так далее. Всего 6 столбцов с данными. 1. Уникальное число, присвоенное каждой функции. 2. Cколько времени (в процентах) исполнялась некая функция и все ее «потомки». 3. Общий процент времени, истраченный на эту функцию. 4. Общее время, занятое «потомками» этой функции. 5. Сколько раз функция была вызвана. После «/» идет количество вызовов этой функции ее потомками. Рекурсивные вызовы не учитываются. 6. Имя функции.
№8(21), август 2004
Следующим разделом этого файла является список всех функций, которые указывались выше, отсортированные в алфавитном порядке, и с указанием уникального номера, присвоенного в разделе Call graph profile. После того как мы проанализировали полученные данные, давайте создадим еще одно ядро системы. Отличие от «оригинального» будет в заведомой «заторможенности» одной из функций. Для примера (как и в NetBSD handbook) возьмем функцию check_exec. Настало время немного поправить ядро системы. Берем свой любимый текстовый редактор и открываем файл /usr/src/sys/kern/kern_exec.c. Ищем там функцию check_exec и добавляем в конце вот такой код: for (x = 0; x < 100000000; x++) { y = x; }
Не забыв в начале функции check_exec, написать: int x; int y;
После внесения этих нехитрых изменений, снова перекомпилируем ядро. Естественно, с профилированием. Перезагрузившись, повторяем уже известные нам действия по созданию файлов gmon.out и gprof.out. И переходим к анализу полученных файлов. В данном случае результат сразу бросается в глаза, вот что у меня получилось в gprof.out, раздел Flat profile:
Сравним получившиеся результаты функции check_exec с теми, которые были получены до модификации последней. До:
После:
Разница, я думаю, всем понятна. А теперь представим, что изменения, аналогичные тем, что мы специально добавили в систему для уменьшения производительности, попали в код случайно, например, вследствие ошибки программиста. Без профилирования, как мне кажется, будет сложно узнать, что именно «притормаживает» систему. А какие перспективы открывает профилирование для оценки оптимизации кода ядра! Внесли некоторые изменения в процедуру, погоняли машину в тестовом режиме, сравнили результаты с тем, что было в старом варианте и что получилось в новом. И все видно сразу как на ладони. Для любителей оптимизации и разработчиков встроенных систем это просто находка! Вообще тема профилирования достаточно обширна, но думаю, что описанного выше простого примера вполне достаточно, чтобы понять принцип профилирования ядра.
35
администрирование
ЗНАКОМСТВО С USERGATE АНДРЕЙ УВАРОВ Одной из «вечных» проблем является организация учёта интернет-трафика пользователей. Многие из тех, кто сталкивался с этой задачей, знают, что решается она не всегда так просто, как хотелось бы. Традиционными платформами для прокси-серверов являются операционные системы семейства UNIX. Для создания прокси-сервера на базе любой UNIX-подобной ОС вам необходимо иметь достаточно долгий опыт работы с ней, возможно, даже могут понадобиться программистские навыки. И тогда по силам будет решить задачу практически любой сложности. Но если вы не владеете этими навыками или по каким-либо другим причинам возникает «огромная» проблема с подсчётом трафика, то эта статья для вас. Usergate является одним из самых простых решений учёта интернет-трафика. Но стоит сразу сказать, что не является бесплатным, да и существует только под Windows, что соответственно увеличивает стоимость системы и уменьшает ее надежность. Использование данной системы вполне приемлемо и оправдано в небольших офисах или «домашних» сетях, где число компьютеров и требования, предъявляемые к серверу, невелики. И для обслуживания сервера вам не понадобится специалист, так как опытный пользователь вполне может справиться с данной программой. Итак, Usergate представляет собой кэширующий прокси-сервер, позволяющий гибко управлять разграничением доступа пользователей к сети Интернет. Найти более подробную информацию об этой программе и узнать расценки можно по адресу: http://usergate.ru. Остановимся более подробно на возможностях, предоставляемых данной системой. Как уже было сказано выше, – это кэширующий прокси-сервер для HTTP, DNS и FTP (через HTTP) запросы. Имеется поддержка протокола Socks5 и туннелирование POP3- и SMTP-протоколов. В составе программы находится интегрированный вебсервер, правда, с очень ограниченными возможностями. Конечно, с полноценными веб-серверами он сравниться не может, да и, к сожалению, пока и со своей прямой задачей – предоставлением пользователям статистической информации, не всегда справляется (по загадочным причинам просто перестаёт работать). В настоящий момент речь ведётся о версии 2.7, как обещают разработчики, в последующих версиях все недостатки будут устранены, а также будут включены новые возможности. Гибкая система разграничения доступа пользователей к сети Интернет включает в себя ограничение доступа как отдельного пользователя, так и группы пользователей. Возможен запрет доступа в определённые часы, дни, а также ограничение пользователя по трафику (в системе можно определять, каким образом осуществлять подсчёт трафи-
36
ка, то есть для пользователя можно установить учёт как только входящего, так и входящего и исходящего трафика). Аналогично Squid Blocks возможно создание или использование уже готовых списков запрещенных ресурсов, так называемых «черных списков», правда, в отличие от Suid Blocks, все записи вносятся непосредственно в конфигурационный файл, что не очень удобно. Есть ещё некоторое количество дополнительных возможностей, таких как автодозвон, portmaping, авторизация на прокси-сервере каскада и другие.
Установка и настройка Вообще с установкой и настройкой больших сложностей возникнуть не должно, ввиду того что программа имеет достаточно удобный интерфейс. Если вы находитесь за прокси-сервером, соответственно указываете его IP-адрес и, если необходима авторизация, заполняете поля: имя пользователя и пароль.
Также неотъемлемой частью настройки системы будет создание и редактирование групп пользователей и отдель-
администрирование ных пользователей, так как это напрямую связано с основной задачей программы. В первую очередь необходимо создать группы, а затем в них добавлять пользователей. К сожалению, нет возможности перемещать пользователей из одной группы в другую. При добавлении каждой группы пользователей необходимо указать имя создаваемой группы, и если требуется, ограничения и тарифы. Существует возможность учёта входящего и исходящего трафика для группы пользователей по разным тарифам, которые вручную определяются администратором. При добавлении пользователя необходимо указать его логин и пароль, которые будут использоваться для авторизации пользователя при выходе в Интернет, и группу, к которой он будет принадлежать. Установленный у пользователя атрибут «Администратор» даёт возможность просматривать через веб-интерфейс статистику всех пользователей. Но, к сожалению, веб-интерфейс не предоставляет возможности администрирования.
Каждому пользователю можно установить следующие ограничения: по трафику, по времени, по скорости доступа, по упоминавшимся ранее «черным спискам», запрещённым часам работы и по длине документа (в данном случае речь идёт об HTTP-протоколе).
К сожалению, имеющаяся система ограничения доступа не очень эффективна, ввиду того что лимитирование по трафику осуществляется только за день или месяц. Это создаст вам определённые проблемы, если вы решите исполь-
№8(21), август 2004
зовать Usergate. Так как вы не сможете устанавливать абсолютных ограничений, то есть если вы хотите установить определённому пользователю лимит, к примеру, на 100 Мб, а к концу месяца он израсходует лишь часть своего трафика, то весь счёт пользователя обнулится, а ограничение будет прежним. Конечно, можно каждый месяц пересчитывать трафик всем пользователям вручную, но как вы, наверное, догадываетесь – это выходом не является. В этом состоит, на мой взгляд, один из серьёзнейших недостатков системы. Несомненно, удобной и необходимой является возможность видеть в реальном времени работу пользователей в Интернете (так называемая функция мониторинга работы пользователей).
Теперь стоит немного поговорить о недостатках. Как уже упоминалось, они имеются в ограничении доступа, в работе веб-сервера. Говоря об интегрированном веб-сервере, необходимо не только напомнить о плохом качестве работы и об отсутствии возможности администрирования, но и о том, что через веб-интерфейс предоставляются только статистические сведения. Ещё одним большим недостатком является некоторое неудобство удалённого администрирования, хотя наверняка многие и не сочтут это за недостаток. Пока возможность администрирования Usergate предоставляется через встроенные средства операционной системы, а именно через Remote Desktop, или же посредством таких программ, как RAdmin. В настоящий момент Usergate активно развивается и уже существует версия 2.8, которая мало отличается от 2.7, но как планируют разработчики, в недалёком будущем будут устранены все упомянутые недостатки и добавлены новые возможности. Использовать Usergate или нет – выбор ваш, но всё же хочется ещё раз отметить, что это хорошее решение для Windows (и крайне простое в реализации), но существует целый ряд подобных систем для UNIX, ничуть не уступающих, и даже превосходящих его по своим возможностям.
37
администрирование
СОЗДАНИЕ И НАСТРОЙКА IVR ДЛЯ ГОЛОСОВЫХ ШЛЮЗОВ CISCO SYSTEMS
Президента компании AT&T на пресс-конференции спросили: -Почему телефонная трубка не изменилась за последние 100 лет? -Потому что все изменения претерпела телефонная станция.
МИХАИЛ ЗАГРАЕВСКИЙ В последнее время трудно найти провайдера сетевых услуг, который бы не уделял должного внимания IP-телефонии. Услуги IP-телефонии или VoIP можно условно разделить на 3 группы – это «терминация» трафика удаленных операторов в сети PSTN, проксирование VoIP-звонков и предоставление услуг связи, используя IP-сети. Конечного пользователя (клиента некого оператора VoIP), как правило, всегда интересует 3-я группа – для него она фактически означает получение возможности сделать междугородний или международный звонок по несколько более выгодным тарифам, чем те, которые готов предоставить ему городской узел связи. Среди множества способов реализации такого сервиса особую популярность занимает так называемая IP-карта. Приобретая ее, клиент может позвонить по местному (городскому) телефонному номеру, указанному на карте, и, перейдя в тональный режим, ввести по запросу системы пин-код, указанный на карте, и непосредственно код страны, города и номер телефона, куда он желает сделать звонок. Когда клиент дозванивается по номеру, указанному на карточке, начинает работать IVR (Interactive Voice Response) – специальное приложение, подгружаемое или частично встроенное в голосовой шлюз, и обеспечивающее интерактивное «общение» с клиентом: запрос необходимых данных для авторизации, получение информации о том, куда клиент желает сделать звонок, выдача сведений об ошибках и балансе карты. Мы рассмотрим создание и настройку IVR для маршрутизаторов и голосовых шлюзов фирмы CISCO (http:// www.cisco.com) с аналоговыми FXO (Foreing Exchange Office) портами или цифровыми голосовыми портами. Все
38
примеры, рассмотренные ниже, были протестированы на маршрутизаторе CISCO 3725 IOS 12.3(7)T, но должны работать на любом голосовом шлюзе фирмы CISCO с IOS, поддерживающим TCL IVR API 2.0 и авторизацию по протоколу RADIUS. Нашей целью будет создание и отладка полноценного приложения для предоставления услуги междугородней связи с использованием предоплаченных (дебетных) телефонных карт. IVR для оборудования CISCO представляет собой скрипт, написанный на языке TCL (Tool Command Language) с использованием CISCO TCL IVR API (Application Program Interface). Мы будем работать с версией API 2.0. Итак, давайте прежде всего четко определим наши цели и основные стадии звонка. Предполагается, что информация от клиента к IVR будет поступать с использованием DTMF (Dual Tone Multifrequency) (тонального донабора). Клиенту предлагается: ! Выбрать язык общения с системой. ! Ввести номер (пин-код) его карточки. ! Ввести номер телефона, куда он желает сделать звонок. ! Клиента соединяют с требуемым номером. Естественно, при любых ошибках клиенту выдается соответствующее диагностическое сообщение. Например, при неверном вводе номера карты или неправильном номере назначения. Попутно мы запрограммируем некоторые приятные сервисные функции, такие как: сообщение о занятости или недоступности желаемого номера, возможность прервать звонок и сделать новый, не кладя трубки.
администрирование По завершении звонка маршрутизатор может предоставить информацию о клиенте (номере его карты), длительности звонка и назначении звонка серверу RADIUS для ведения статистики. Как уже говорилось ранее, IVR – это программа, написанная на языке TCL с использованием CISCO IVR API, которая начинает свое выполнение при поступлении звонка на маршрутизатор (голосовой порт). Наше TCL-приложение может находиться на различных носителях или удаленных серверах, а именно FTP, TFTP и FLASH-памяти маршрутизатора. При поступлении звонка шлюз запускает IVRскрипт, определенный в конфигурации соответcтвующего dial-peer, связанного с портом, на который поступил звонок. Например, в шлюзе определен голосовой аналоговый FXO-порт: voice-port 1/0/0 cptone RU timeouts initial 20 timeouts interdigit 20 timeouts wait-release 10
С ним связан соответствующий dial-peer: dial-peer voice 1 pots application debitcard port 1/0/0
То есть при поступлении звонка на голосовой порт 1/0/0 запустится IVR-приложение с именем debitcard, которое должно быть определено глобально в конфигурации маршрутизатора: call application voice debitcard call application voice debitcard call application voice debitcard call application voice debitcard call application voice debitcard call application voice debitcard call application voice debitcard slot0:/ivr/prompts/ru/ call application voice debitcard slot0:/ivr/prompts/en/
slot0:/ivr/debitcard.tcl uid-len 8 pin-len 3 warning-time 10 language 1 ru language 2 en set-location ru 0 ↵ set-location en 0 ↵
Первая строчка сообщает маршрутизатору о местонахождении IVR-скрипта с именем debitcard.tcl, в нашем случае он находится на slot0: – дополнительном модуле Flashпамяти. Вторая и третья определяют количество цифр в идентификаторе и пин-коде карты. Четвертая строчка определяет количество секунд до окончания звонка, за которое клиента следует предупредить. Пятая и шестая указывают маршрутизатору, где искать необходимые звуковые файлы, из которых будут формироваться голосовые сообщения для клиента. Вообще говоря, для каждого IVR-скрипта можно определить любой параметр или параметры, значение которых он потом сможет считать через соответствующие IVR API. Например, наличие следующей строчки в конфигурации шлюза: call application voice debitcard say_seconds yes
определяет для IVR-скрипта debitcard параметр say_
№8(21), август 2004
seconds, имеющий значение yes. Позже, в процессе выполнения скрипта, мы сможем изменить его поведение в зависимости от значения этого параметра, что, несомненно, увеличивает универсальность приложения. IVR-приложение базируется на трех составляющих: процедуры инициализации, функции обработки событий и конечное состояние (FSM – Finite State Machine). Процедуры или функции инициализации применяются для объявления и присвоения значений переменным, используемых скриптом. Эти функции бывают двух типов. К первому типу относятся те, которые будут выполнены всего один раз в начале работы скрипта. Они, как правило, используются для инициализации глобальных переменных, значение которых не должно меняться. Например, в этих процедурах мы можем считать параметры, определенные для скрипта в глобальной конфигурации маршрутизатора. Второй тип процедур инициализации – это те, которые вызываются при каждом поступлении звонка на маршрутизатор, а именно при получении события ev_setup_ indication или ev_handoff. В них, как правило, принято определять и инициализировать глобальные переменные, которые могут меняться при каждом звонке. При получении IVR-скриптом определенного события вызываются соответствующие функции обработки. Примером таких событий могут быть нажатие клавиши на клавиатуре телефонного аппарата, сигнал о завершении проигрывания клиенту голосовых файлов или окончании процесса авторизации на RADIUS-сервере. Для IVR-скрипта назначен четкий набор событий, который он может обрабатывать, и на каждое из них можно привязать функцию-обработчик этого события, которая будет автоматически выполнена при поступлении этого события в каждом конечном состоянии скрипта. FSM – (конечные состояния) выполнения IVR-скрипта определяют, какие именно обработчики будут вызываться при получении тех или иных событий, и в какое состояние должен перейти скрипт после вызова обработчика. Итак, приступим к написанию скрипта. Мы создадим текстовый файл с именем debitcard.tcl. Далее приводится полный листинг скрипта с подробным описанием процедур и команд. В тексте слова «процедура» и «функция» являются синонимами. proc init global global global global global global global global global set set set set
{ } { param; retryCnt; LangPattern; ParamForCard; ParamForDest; AccountLen; PinLen; CardLen; WarnTime;
param(abortKey) * param(interruptPrompt) true param(ignoreInitialTermKey) true LangPattern(1) {[1,2]}
set AccountLen [string trim [infotag get cfg_avpair ↵ uid-len]]; set PinLen [string trim [infotag get cfg_avpair ↵ pin-len]]; set retryCnt [string trim [infotag get cfg_avpair ↵ retry-count]];
39
администрирование set WarnTime [string trim [infotag get cfg_avpair ↵ warning-time]]; set CardLen [expr $AccountLen + $PinLen]; set set set set set
ParamForCard(abortKey) * ParamForCard(initialDigitTimeout) 10 ParamForCard(terminationKey) # ParamForCard(maxDigits) $CardLen; ParamForCard(interruptPrompt) true
set ParamForDest(abortKey) * set ParamForDest(initialDigitTimeout) 10 set ParamForDest(terminationKey) # set ParamForDest(interruptPrompt) true set ParamForDest(dialPlanTerm) true; set ParamForDest(ignoreInitialTermKey) true; return; }
Процедура init вызывается всего один раз при первом запуске скрипта, и в ней производится объявление и инициализация переменных, не меняющих свое значение для всех звонков, обслуживаемых IVR-скриптом. Она вызывается в глобальной области TCL-приложения командой init, и все переменные, объявленные и установленные таким образом, будут сохранять свои значения на всем протяжении работы шлюза для всех поступающих звонков. В этой процедуре объявляются следующие переменные: param, ParamForDest, ParamForCard – ассоциативные массивы, содержащие параметры для сбора информации о набранных клиентом цифрах для выбора языка сообщений системы, номере карты и номере телефона назначения. Массивы могут содержать следующие именованные индексы и значения: ! param(abortKey) – определяет клавишу, которая будет использоваться как клавиша отмены набора (например, если клиент по ошибке нажал не ту клавишу и вовремя это заметил, то он будет иметь возможность нажать клавишу, определенную параметром param(abortKey), и IVR-скрипт получит событие ev_collectdigits_done, определяющее завершение процесса сбора информации о полученных от клиента цифрах со статусом «cd_002», который означает, что клиент нажал клавишу отмены. В нашем случае это клавиша с символом «*» (звездочка). ! param(interDigitTimeout) – устанавливает тайм-аут в секундах между нажатиями клавиш. Если за этот промежуток времени не будет нажата ни одна клавиша, то скрипт получит событие ev_collectdigits_done со статусом завершения «cd_001». ! param(initialDigitTimeout) – определяет тайм-аут до момента нажатия первой клавиши. По истечении этого времени возникает событие ev_collectdigits_done со статусом завершения «cd_001». ! param(interruptPrompt) – может принимать значения true или false – определяет возможность прерывать клиентом голосовые сообщения нажатием любой клавиши. ! param(terminationKey) – по аналогии с abortKey позволяет задать клавишу, которая будет использоваться в качестве индикатора завершения клиентов ввода цифр. При нажатии этой клавиши возникает событие ev_ collectdigits_done со статусом «cd_005». В нашем случае это клавиша с символом «#» (решетка).
40
! param(maxDigits) – задает максимальное количество цифр, по истечении набора которых возникает событие ev_collectdigits_done со статусом «cd_005». Мы вычисляем этот параметр для количества цифр, ожидаемых от клиента при вводе номера карты, зная длину идентификатора (логина) и пароля. Сначала мы с помощью команды infotag get устанавливаем значения переменных AccountLen и PinLen в те значения, которые определены для этих параметров в глобальной конфигурации маршрутизатора. Затем вычисляем сумму этих значений, которую присваиваем переменной CardLen. И наконец, переменная LangPattern представляет собой массив, содержащий шаблоны, при совпадении с которыми процесс сбора цифр завершается со статусом «cd_005». В нашем случае мы хотим получить только цифры 1 или 2 для русского и английского языков соответственно. proc init_perCallVars { } { global NumLangPrompt; global NumCardPrompt; global NumDestPrompt; global PromptFlag; global DestPromptFlag; global NoPlayWarn; global NoTimeLimit; global SetupDone; set set set set set set set set
NumLangPrompt 0; NumCardPrompt 0; NumDestPrompt 0; PromptFlag 0; DestPromptFlag 0; NoPlayWarn 0; NoTimeLimit 0; SetupDone 0;
return; }
Процедура init_PerCallVars отвечает за объявление и инициализацию глобальных переменных, меняющих свое значение в процессе каждого звонка. В описании других процедур будет дано пояснение каждой из них. proc act_Setup { } { init_perCallVars; leg setupack leg_incoming; infotag set med_language prefix "ru"; SelectLanguageMenu; return; }
Процедура act_Setup вызывается IVR-скриптом при поступлении нового звонка. Именно с нее, по сути, начинается обработка звонка клиента. Почему именно с нее? Имя этой функции определено как обработчик события ev_setup_indication, которое генерируется при каждом новом звонке. За это отвечает строчка: set ivr_fsm(CALLCOMES,ev_setup_indication) "act_Setup ↵ same_state";
в конце нашего скрипта. Тут следует ненадолго перейти к теме FSM. Перед выполнением IVR-скрипта требуется определить имя массива, который будет содержать в себе все состояния звонка, функции-обработчики событий, которые могут возникнуть при этом состоянии, и имя следующего состояния, в кото-
администрирование рое перейдет звонок при выполнении этой функции, а также стартовое состояние звонка. Заметьте: не после выполнения функции, а непосредственно сразу (одновременно). Учтите также, что все вызовы функции и команд в TCL IVR API «не блокирующие», и для выполнения последующей команды скрипт не будет дожидаться завершения предыдущей. Это может смутить программистов, работавших ранее с такими языками, как Си, но к этому необходимо привыкнуть. Например, если в процедуре написаны следующие команды: leg collectdigits 1 callInfo leg collectdigits 2 callInfo leg setup 295786 setupInfo $callID5 puts "\nThis will be executed immediately i.e. before ↵ the collect digits or call setup is actually complete"
то все они будут выполнены одна за другой, не дожидаясь завершения предыдущей. В данном случае начнется процесс сбора цифр на виртуальных составляющих звонка leg 1 и leg 2, процесс установки соединения на $callID5 и выдано отладочное сообщение командой puts. Далее IVRприложение начнет ожидать получения событий от этих процессов и вызвать соответствующие функции-обработчики. Командой fsm define (в конце нашего скрипта) определяется имя массива, описанного выше ivr_fsm, и первоначальное состояние звонка CALLCOMES. Командой set ivr_fsm мы будем добавлять к этому массиву новые элементы, создавая так называемые «FSM-переходы». Общий синтаксис этой команды таков: set array(curr_state,curr_event) “act_proc NEXTSTATE”
где: ! array – это имя определенного командой fsm define массива. ! curr_state – имя текущего состояния скрипта, при котором получено событие curr_event. ! act_proc – имя функции-обработчика события, которую необходимо выполнить при поступлении события curr_event в состоянии curr_state. ! NEXTSTATE – имя состояния, в которое должен перейти звонок. При определении FSM можно использовать следующие мета-определения: ! any_state – определяет любое состояние звонка, для которого не установлен иной обработчик в другом FSMпереходе; может быть использовано в левой части (индексе массива) FSM-перехода. ! same_state – трактуется как «то же состояние»; может быть использовано в правой части (значении элемента массива) определения FSM-перехода. ! ev_any_event – определяет любое событие, которое может получить скрипт. Теперь, глядя на строчку: set ivr_fsm(CALLCOMES,ev_setup_indication) "act_Setup ↵ same_state";
№8(21), август 2004
мы легко можем понять, что при получении события ev_setup_ indication в состоянии CALLCOMES будет выполнена функция act_Setup и звонок останется в прежнем состоянии (same_state). Возвращаясь к нашей функции act_Setup, мы видим, что в ней вызывается процедура init_perCallVars для объявления и инициализации глобальных переменных, которым необходимо иметь возможность менять свое значение при каждом отдельно взятом звонке. Командой leg setupack посылается сообщение setup acknowledgement, чтобы перевести наш звонок в состояние, при котором возможно получение клиентом голосовых сообщений. Команда infotag set med_language prefix «ru» устанавливает текущий язык голосового интерфейса и фактически указывает скрипту, где ему следует искать звуковые файлы для этого языка. Помните строчку в глобальной конфигурации шлюза: call application voice debitcard set-location ru 0 ↵ slot0:/ivr/prompts/ru/
теперь скрипт «знает», что все файлы, из которых следует составлять голосовые сообщения, находятся на носителе slot0:/ivr/prompts/ru/. Перед именем файла при его поиске он автоматически будет добавлять префикс ru, например, команда media play _wecome.au «проиграет» клиенту файл, полный путь, к которому будет slot0:/ivr/prompts/ru/ru_ wecome.au. И в конце этой процедуры вызывается функция Select LanguageMenu: proc SelectLanguageMenu { } { global param; global retryCnt; global NumLangPrompt; global LangPattern; if {$NumLangPrompt < $retryCnt} { media play leg_incoming %s2000 _RUS_lang_sel1.au ↵ %s500 _ENG_lang_sel2.au leg collectdigits leg_incoming param LangPattern; } else { media play leg_incoming _final.au; fsm setstate CALLDISCONNECT; } return; }
В ее задачи входит следующее: пользователю проигрываются два приглашения на разных языках с предложением нажать соответствующую клавишу для выбора нужного языка. После проигрывания команда leg collectdigits переводит скрипт в состояние ожидания поступления события о завершении процесса сбора цифр. Это событие поступит после однократного нажатия клиентом любой клавиши – за такое поведение отвечает массив LangPattern, позволяющий клиенту нажать только одну клавишу. Но если эта клавиша не была цифрой 1 или 2, то статус завершения события ev_collectdigits_done будет отличен от «cd_005». При получении этого события определенный нами FSMпереход: set ivr_fsm(CALLCOMES,ev_collectdigits_done) ↵ "CheckLangSelection CHECKLANG";
обеспечит вызов функции CheckLangSelection, которая и
41
администрирование проверит статус завершения события и при неудовлетворительном результате должна увеличить значение глобальной переменной NumLangPrompt для того, чтобы функция SelectLanguageMenu не повторяла приглашения больше раз, чем записано в переменной retryCnt, тем самым не позволяя звонку зациклиться на проигрывании звуковых файлов, если нам попался туго соображающий пользователь. Именно здесь становится понятна необходимость использования функции init_perCallVars, в которой определяются подобные переменные, регулирующие максимальное количество проигрывания приглашений. Нам необходимо, чтобы при следующем звонке они обнулились, что невозможно реализовать через глобальную процедуру init. К тому же при нескольких одновременных звонках у каждого из них должен быть свой экземпляр переменной. proc CheckLangSelection { } { global NumLangPrompt; set collect_status [infotag get evt_status]; set collect_digits [infotag get evt_dcdigits]; switch $collect_status { "cd_001" { incr NumLangPrompt; media play leg_incoming _no_digits_entered.au; return; } "cd_002" { SelectLanguageMenu; fsm setstate CALLCOMES; return; } "cd_005" { infotag set med_language $collect_digits; fsm setstate CARDSELECTION; act_GetCard; return; } "cd_006" { incr NumLangPrompt; media play leg_incoming _wrong_lang_sel.au; return; } default { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; } } return; }
В процедуре CheckLangSelection мы получаем статус события ev_collectdigits_done командой infotag get evt_status; и набранные цифры(у) командой infotag get evt_dcdigits, затем для удобства присваиваем их временным переменным collect_status и collect_digits. При получении интересующего нас статуса «cd_005» мы командой fsm setstate «вручную» переводим звонок в состояние CARDSELECTION и вызываем процедуру act_GetCard для запроса информации о пин-коде карты клиента и последующей его авторизации. proc act_GetCard { } { global ParamForCard; global NumCardPrompt; global PromptFlag; global retryCnt; if {$NumCardPrompt < $retryCnt} { switch $PromptFlag { 0 {media play leg_incoming %s500 ↵ _enter_card_num.au; #first play} 1 {media play leg_incoming _invalid_digits.au; #Not enuf digits pressed} 3 {media play leg_incoming _no_card_entered.au;
42
↵ ↵
#Timeout - no digits entered} default { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; return; } } leg collectdigits leg_incoming ParamForCard; } else { media play leg_incoming _final.au; fsm setstate CALLDISCONNECT; } return; }
Процедура act_GetCard проигрывает клиенту соответствующие звуковые файлы не более $retryCnt раз в зависимости от значения переменной PromptFlag, которая изначально установлена в 0 в процедуре init_perCallVars. Соответственно при первом вызове этой процедуры клиент слышит приглашение ввести номер (пин-код) карточки. Значение 1 соответствует неверному количеству набранных цифр и значение 3 – статусу, при котором клиент не набрал ни одной цифры. Далее скрипт выполняет команду leg collectdigits и ожидает получения события ev_ collectdigits_done, которое проанализирует функция act_ GotCardNumber, что обеспечит FSM-переход: proc act_GotCardNumber { } { global NumCardPrompt global AccountLen; global PinLen; global CardLen; global PromptFlag; global retryCnt; global account; global pin; set status [infotag get evt_status]; switch $status { "cd_005" { set card_number [infotag get evt_dcdigits]; set card_len [string length $card_number]; if {$card_len == $CardLen} { set account [string range $card_number 0 ↵ [expr $AccountLen - 1]]; set pin [string range $card_number $AccountLen ↵ [expr $card_len - 1]] puts "account = $account pin = $pin"; aaa authorize $account $pin "" "" leg_incoming; } else { incr NumCardPrompt; set PromptFlag 1; act_GetCard; return; } } "cd_001" { incr NumCardPrompt; set PromptFlag 3; act_GetCard; return; } "cd_002" { set PromptFlag 0 act_GetCard; return; } } return; }
Процедура act_GotCardNumber выполняется при получении события ev_collectdigits_done. Глобальная переменная ParamForCard, инициализированная в функции init, обеспечивает нам получение определенного количества цифр – в нашем конкретном случае это число 11 ($Cardlen =
администрирование $AccountLen + $PinLen). При неудовлетворяющем нас статусе события функция act_GotCardNumber инкремирует переменную NumCardPrompt для счетчика количества приглашений ввода карты, устанавливает значение переменной PromptFlag и снова вызывает процедуру act_GetCard, которая проверяет, не превышен ли счетчик приглашений, и указывает клиенту на ошибку, основываясь на значении PromptFlag. При успешно завершившемся процессе сбора цифр (статус «cd_005») функция вычисляет логин и пароль, и с помощью команды: aaa authorize $account $pin "" ""
leg_incoming;
указывает маршрутизатору отправить запрос на авторизацию карты RADIUS-серверу. При получении ответа от него скрипту поступит событие ev_authorize_done. Далее скрипт выполнит функцию act_CardAuthorize и останется в том же состоянии, для этого используем следующий FSM-переход: set ivr_fsm(CARDSELECTION,ev_authorize_done) ↵ "act_CardAuthorize same_state";
определенный в конце нашего скрипта: proc act_CardAuthorize { } { global PromptFlag; global NumCardPrompt; global ParamForDest; global ParamForCard; global retryCnt; set status [infotag get evt_status]; if {$status == "ao_000"} { if {[infotag get aaa_avpair_exists ↵ h323-credit-amount]} { set amt [infotag get aaa_avpair ↵ h323-credit-amount] } else { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; return; } fsm setstate DESTSELECTION; if {$amt <= 999999.99} { media play leg_incoming _you_have.au %a$amt ↵ _enter_dest.au; } else { media play leg_incoming _enter_dest.au; } leg collectdigits leg_incoming ParamForDest; return; } if {[infotag get aaa_avpair_exists h323-return-code]} { set return_code [infotag get aaa_avpair ↵ h323-return-code]; } else { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; return; } incr NumCardPrompt; if {$NumCardPrompt < $retryCnt} { leg collectdigits leg_incoming ParamForCard; act_PlayCardReturnCode $return_code; } else { ct_GetCard; } return; }
Процедура act_CardAuthorize так же анализирует статус события ev_authorize_done, и при успешной авториза-
№8(21), август 2004
ции («ao_000») получает с помощью команды infotag get aaa_avpair h323-credit-amount сумму на счете клиента, сохраняет ее в переменной amt и проигрывает ее клиенту с помощью команды media play %a$amt. Тут необходимо сделать небольшое отступление. TCL IVR API 2.0 предоставляет функции TTS (Text To Speech), которые позволяют воспроизводить клиенту голосовые сообщения, основанные на параметрах команды media play. Например, параметр %a<num> считает значение num денежной суммой в центах (в нашем случае в копейках), и клиент услышит эквивалентную сумму в долларах и центах или в рублях и копейках. К великому сожалению, IVR API могут работать только с несколькими языками, как правило это английский, китайский и испанский, а для русского языка необходимо купить у фирмы Cisco Systems скрипт, обеспечивающий поддержку русского языка. Любителям экстремальных ощущений предлагается написать такой скрипт самим, который потом можно будет подгрузить в маршрутизатор командой call language voice в режиме глобальной конфигурации. Скрипт этот должен быть написан с применением «чистого» TCL без использования CISCO IVR API, и из него будет вызываться функция translate с передачей ей параметров из команды media play. Функция translate должна возвратить строку, состоящую из имен *.au-файлов, подлежащих воспроизведению, разделенных пробелами. Кроме этого, по невыяcненной пока причине, TCL IVR-скрипт, получив от TTS TCL-строку с перечисленными аудио-файлами, требующими проигрывания, игнорирует весь текст до первого пробела (лидирующие пробелы не учитываются). То есть, если ваш скрипт вернет следующую строку: «_200.au _40.au _7.au», то проиграны будут только файлы _40.au и _7.au. Несмотря на то, что такое поведение не зарегистрировано как официальная ошибка, оно все же присутствовало во всех тестированных автором версиях IOS и оборудовании. По этому поводу официальные лица Cisco ничего не говорят, кроме того, что они не несут никакой ответственности за написанный третьими лицами код, и подобное программное обеспечение надо приобретать у них. Далее скрипт, вызывая команду fsm state DESTSELECTION, устанавливает для звонка новое состояние и, используя команду leg collectdigits, переходит в режим ожидания завершения сбора цифр от клиента, которые определят номер телефона назначения. При отказе в авторизации (статус события ev_authorize_ done не равен «ao_000») функция act_CardAuthorize получает код ответа от RADIUS-сервера командой: infotag get aaa_avpair_exists h323-return-code
и присваивает его переменной return_code, затем в случае, если количество попыток ввода номера телефона еще не превышено, вызывается функция act_PlayCard ReturnCode c передачей ей переменной return_code в качестве параметра для проигрывания соответствующего звукового сообщения об ошибке, и скрипт вызывает команду leg collectdigits leg_incoming ParamForCard для повторного сбора цифр – номера карты. Вот тут, кстати, мы и используем
43
администрирование свойство «не блокирующего» вызова функций: leg collectdigits и act_PlayCardReturnCode идут одна за другой, но выполнены они будут условно одновременно – скрипт будет проигрывать голоcовые сообщения об ошибке и ожидать события ev_collectdigits_done, после которого опять отработает процедура act_GotCardNumber, согласно уже использованному нами ранее FSM-переходу: set ivr_fsm(CARDSELECTION,ev_collectdigits_done)
поскольку скрипт остался в прежнем состоянии: CARD SELECTION. proc act_PlayCardReturnCode { return_code } { switch $return_code { 2 {media play leg_incoming _auth_fail.au;} 7 {media_play leg_incoming _zero_bal.au;} default { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; return; } } return; } proc act_GetDestination { } { global retryCnt; global ParamForDest; global DestPromptFlag; global NumDestPrompt; if {$NumDestPrompt < $retryCnt} { switch $DestPromptFlag { 0 {media play leg_incoming _enter_dest.au; ↵ #Abortkey pressed} 1 {media play leg_incoming _no_dest_entered.au; ↵ #Timeout -no digits entered} 2 {media play leg_incoming _reenter_dest.au; ↵ #Not mutch to the dial plan} default { media play leg_incoming _no_aaa.au; fst setstate CALLDISCONNECT; return; } } leg collectdigits leg_incoming ParamForDest; } else { media play leg_incoming _dest_collect_fail.au; fsm setstate CALLDISCONNECT; } return; }
act_GetDestination; return; } "cd_002" { set DestPromptFlag 0; act_GetDestination; return; } "cd_004" { set destination [infotag get evt_dcdigits]; puts "\n************dest_number = $destination"; aaa authorize $account $pin "" $destination ↵ leg_incoming; return; } default { incr NumDestPrompt; set DestPromptFlag 2; act_GetDestination; return; } } return; }
Процедура act_GotDestination будет выполняться в соответствии с FSM-переходом: set ivr_fsm(DESTSELECTION,ev_collectdigits_done) ↵ "act_GotDestination same_state";
Она проверяет статус завершения события ev_collect digits_done. В случае неустраивающего нас статуса вызывает функцию act_GetDestination, установив предварительно переменную DestPromptFlag в значение, которое укажет функции act_GetDestination, какой именно звуковой файл следует проиграть, и, как всегда, следует проверка, не превышен ли счетчик максимально допустимых попыток ввести информацию. При устраивающем нас номере телефона, введенном пользователем (статус «cd_004» – совпадение с планом набора) с помощью команды: aaa authorize $account $pin "" $destination leg_incoming;
RADIUS-серверу будет отправлен запрос на авторизацию звонка. По завершении авторизации скрипт получит событие ev_authorize_done, которое согласно определенному нами FSM-переходу: set ivr_fsm(DESTSELECTION,ev_authorize_done) ↵ "act_CallAuthorize same_state";
обработает процедура act_CallAuthorize: Процедура act_GetDestination выполняет в некоторой степени вспомогательную функцию – она будет вызываться в случае какой-либо ошибки, например, при тайм-ауте ввода цифр, нажатии клиентом клавиши, определенной как aborKey, или при несоответствии с планом набора, определенным в маршрутизаторе. proc act_GotDestination { } { global NumDestPrompt; global DestPromptFlag; global account; global pin; global destination; set status [infotag get evt_status]; switch $status { "cd_001" { incr NumDestPrompt; set DestPromptFlag 1;
44
proc act_CallAuthorize { } { global NumDestPrompt; global DestPromptFlag; global WarnTime; global NoPlayWarn; global NoTimeLimit; global creditTime; global retryCnt; global ParamForDest; global param; set status [infotag get evt_status]; set param(enableReporting) true; set param(interruptPrompt) false; if {$status == "ao_000"} { if {[infotag get aaa_avpair_exists ↵ h323-credit-time]} { set creditTime [infotag get aaa_avpair ↵ h323-credit-time]; } else { media play leg_incoming _no_aaa.au;
администрирование fsm setstate CALLDISCONNECT; return; } if {$creditTime <= $WarnTime} {set NoPlayWarn 1;} if {$creditTime == "unlimited"} {set NoTimeLimit 1;}
{
media play leg_incoming _you_have.au %t$creditTime; leg collectdigits leg_incoming param; ↵ #For Fast leg setup fsm setstate PLACECALL; return; } incr NumDestPrompt; #Call authorize failed if {[infotag get aaa_avpair_exists h323-return-code]} set return_code [infotag get aaa_avpair ↵ h323-return-code]; } else { media play leg_incoming no_aaa.au; fsm setstate CALLDISCONNECT; return; } if {$NumDestPrompt < $retryCnt} { act_PlayDestReturnCode $return_code; leg collectdigits leg_incoming ParamForDest; } else { act_GetDestination; } return; }
Процедура act_CallAuthorize анализирует статус события ev_authorize_done. При успешной авторизации клиенту проговаривается, сколько времени у него на балансе и возвращенное скрипту RADIUS-сервером в переменной h323credit-time, далее скрипт запускает команду leg collectdigits leg_incoming param и командой fsm setstate PLACECALL переходит в новое состояние: PLACECAL, в котором по завершении проигрывания звуковых файлов обработает событие ev_media_done и через FSM-переход: set ivr_fsm(PLACECALL,ev_media_done) "act_CallSetup same_state;
будет выполнена функция act_CallSetup, которая наконецто соединит нашего клиента с требуемым номером. При ошибке в авторизации функция получит код ошибки из переменной h323-return-code и будет вызвана процедура act_PlayDestReturnCode, которая озвучит соответствующее сообщение об ошибке. Если счетчик попыток ввести номер не превышен, скрипт будет ожидать от клиента повторного ввода номера, по завершении которого с помощью того же FSM-перехода: set ivr_fsm(DESTSELECTION,ev_collectdigits_done) "act_GotDestination same_state";
управление будет передано функции act_GotDestination. Теперь объясним, зачем мы используем команду leg collectdigits при успешной авторизации, ведь казалось бы больше ждать ввода от клиента не стоит. Это позволит клиенту прервать сообщение о доступном времени звонка и перейти непосредственно к соединению. Данная возможность будет реализована с помощью команд: set param(enableReporting) true
и set param(interruptPrompt) false;
№8(21), август 2004
и FSM-перехода: set ivr_fsm(PLACECALL,ev_digit_end) "act_FastSetup same_state";
Если клиент в момент прослушивания информации об оставшемся времени звонка нажмет клавишу «#», скрипт получит событие ev_digit_end, сигнализирующее о том, что была нажата некая клавиша, и будет вызвана функция act_FastSetup, которая проверит эту клавишу на соответствие с символом «#». В случае совпадения прервет проигрывание звуковых файлов командой media stop и вызовет процедуру act_CallSetup. Переменная SetupDone будет установлена в 1, чтобы избежать повторного вызова функции act_CallSetup. proc act_FastSetup { } { global SetupDone; if {[infotag get evt_digit] != "#" || $SetupDone} { return; } media stop leg_incoming; act_CallSetup; set SetupDone 1; return; } proc act_CallSetup { } { global destination; global account; global SetupDone; set callinfo(accountNum) $account; set callinfo(alertTime) 60; set SetupDone 1; leg setup $destination callinfo leg_incoming; return; }
Процедура act_CallSetup инициализирует ассоциативный массив callinfo необходимыми значениями – опциями для будущего соединения, в частности устанавливает максимальное время ожидания поднятия трубки вызываемой стороной 60 секунд. И наконец, запускает команду, ради которой, собственно, и создавался этот скрипт: leg setup $destination callinfo leg_incoming
с передачей ей в качестве параметров номера назначения, этот массив и условное обозначение голосового канала, к которому подключен наш клиент. При завершении команды leg setup скрипт получит событие ev_setup_done, которое будет обработано FSM-переходом: set ivr_fsm(PLACECALL,ev_setup_done) "act_CallSetupDone ↵ same_state";
и вызвана функция act_CallSetupDone: proc act_CallSetupDone { } { global DestPromptFlag; global ParamForDest; global param; global NoTimeLimit; global creditTime; global WarnTime;
45
администрирование global NoPlayWarn; global SetupDone; set status [infotag get evt_status]; switch $status { "ls_000" { if {!$NoTimeLimit} { #Setting call timer if {$NoPlayWarn} { timer start leg_timer [expr $creditTime - 1] ↵ leg_incoming; fsm setstate CALLLASTACTIVE; } else { set delay [expr $creditTime - $WarnTime]; timer start leg_timer $delay leg_incoming; fsm setstate CALLACTIVE; } } set param(enableReporting) true; leg collectdigits leg_incoming param; #For long pound return; } "ls_007" { set DestPromptFlag 0; set SetupDone 0; media play leg_incoming _dest_busy.au; leg collectdigits leg_incoming ParamForDest; fsm setstate DESTSELECTION; return; } default { set DestPromptFlag 0; set SetupDone 0; media play leg_incoming _dest_unreachable.au ↵ %s200 _enter_dest.au; leg collectdigits leg_incoming ParamForDest; fsm setstate DESTSELECTION; return; } } return; }
Процедура act_CallSetupDone получает статус завершения процесса установки соединения и при удачном соединении (статус «ls_000»), если не установлена переменная NoTimeLimit, сигнализирующая о неограниченном времени звонка, переводит звонок в состояние CALLACTIVE или CALLLASTACTIVE, в зависимости от оставшегося времени, и с помощью команды timer start «заводит» таймер, при срабатывании которого скрипт получит сообщение ev_leg_ timer, который будет обработан FSM-переходами: set ivr_fsm(CALLACTIVE,ev_leg_timer) ↵ "act_ActiveTimer CALLWARN";
или set ivr_fsm(CALLLASTACTIVE,ev_leg_timer) ↵ "act_LastActiveTimer same_state";
Время таймера рассчитывается как разность значений переменной, полученной от RADIUS-сервера h323-credittime, и глобальной переменной WarnTime, проинициализированной в функции init. Также мы опять вводим приятную возможность для клиента – прервать разговор в любой момент путем длительного (более 300ms) нажатия и удерживания клавиши с изображением решетки. В этот момент скрипт получит событие ev_digit_end, которое обработает FSM-переход: set ivr_fsm(CALLACTIVE,ev_digit_end) ↵ "act_LongPound CONNDESTROY";
При неуспешном завершении процесса установки соединения процедура act_CallSetupDone проиграет клиенту соответствующее сообщение об ошибке, предложение вве-
46
сти новый номер телефона назначения и перейдет в состояние DESTSELECTION, а согласно FSM-переходу: set ivr_fsm(DESTSELECTION,ev_collectdigits_done) ↵ "act_GotDestination same_state";
после окончания проигрывания файлов при получении события ev_collectdigits_done будет вызвана функция act_Got Destination: proc act_PlayDestReturnCode {return_code} { switch $return_code { 9 {media play leg_icoming _dest_blocked.au %s500 ↵ _enter_dest.au;} 12 {media play leg_incoming _not_enuf.au %s500 ↵ _enter_dest.au;} default { media play leg_incoming _no_aaa.au; fsm setstate CALLDISCONNECT; return; } } return; } proc act_ActiveTimer { } { global WarnTime; global incoming; global outgoing; set incoming [infotag get leg_incoming]; set outgoing [infotag get leg_outgoing]; connection destroy con_all; timer start leg_timer [expr $WarnTime - 1] leg_incoming; return; }
Процедура act_ActiveTimer выполнится в момент получения скриптом события ev_leg_timer. В ее задачи входит временно отсоединить вызывающую и вызываемую стороны звонка (это необходимо, чтобы клиент мог получить информацию о том, что время его звонка заканчивается), т.к. нельзя проигрывать звуковые файлы при установленном звуковом канале между двумя сторонами разговора, и установить новый таймер на оставшиеся у клиента $WarnTime секунд. После разрыва соединения скрипт получит событие ev_destroy_done, обрабатываемое FSM-переходом: set ivr_fsm(CALLWARN,ev_destroy_done) ↵ "act_CallWarnDestroy same_state"; proc act_LastActiveTimer { } { connection destroy con_all; return; }
Процедура act_LastActiveTimer, так же как и act_Active Timer, разрывает голосовой канал между двумя сторонами звонка согласно FSM-переходу: set ivr_fsm(CALLLASTACTIVE,ev_leg_timer) ↵ "act_LastActiveTimer same_state";
После этого скрипт будет ожидать получения события ev_destroy_done, которое обработается FSM-переходом: set ivr_fsm(CALLLASTACTIVE,ev_destroy_done) ↵ "act_PlayDisconnect CALLDISCONNECT";
администрирование и выполнение будет передано процедуре act_PlayDisconnect, которая сообщит клиенту о завершение его звонка и переведет скрипт в состояние CALLDISCONNECT: proc act_PlayDisconnect { } { media play leg_incoming _disconnected.au fsm setstate CALLDISCONNECT; return; } proc act_CallWarnDestroy { } { global WarnTime; media play leg_incoming _you_have.au %t$WarnTime; return; }
Процедуре act_CallWarnDestroy управление передается согласно FSM: set ivr_fsm(CALLWARN,ev_destroy_done) ↵ "act_CallWarnDestroy same_state";
В ее задачи входит сообщить клиенту, что до конца его разговора осталось $WarnTime секунд. Когда это сообщение будет проиграно, скрипт получит событие ev_media_ done и FSM-переход: set ivr_fsm(CALLWARN,ev_media_done) ↵ "act_CallWarnMedia CALLLASTACTIVE";
позаботится о том, чтобы воcстановить канал между сторонами звонка, запустив функцию act_CallWarnMedia, и переведет звонок в состояние CALLLASTACTIVE: proc act_CallWarnMedia { } { global incoming; global outgoing; connection create $incoming $outgoing; return; } proc act_LongPound { } { if {[infotag get evt_digit] != "#"} { fsm setstate same_state; return; } set duration [infotag get evt_digit_duration]; if {$duration < 300} { fsm setstate same_state; return; } connection destroy con_all; return; }
Процедура act_LongPound интересна тем, что дает клиенту возможность во время разговора (состояния CALL ACTIVE, CALLLASTACTIVE) прервать звонок и сделать новый, не кладя трубки. За это отвечают следующие FSMпереходы: set ivr_fsm(CALLACTIVE,ev_digit_end) ↵ "act_LongPound CONNDESTROY"; set ivr_fsm(CALLLASTACTIVE,ev_digit_end) ↵ "act_LongPound CONNDESTROY";
Если клиент нажмет любую клавишу, скрипт в этих со-
№8(21), август 2004
стояниях получит событие ev_digit_end, и будет вызвана процедура act_LongPound, которая проверит клавишу на совпадение с символом «#». В случае соответствия она разрушит канал связи между участниками звонка. При успешном разрушении канала связи скрипт получит событие ev_destroy_done. Поскольку согласно предыдущему FSMпереходу звонок находился в состоянии CONNDESTROY, будет введен в работу FSM-переход: set ivr_fsm(CONNDESTROY,ev_destroy_done) ↵ "act_ConnDestroyed same_state";
который вызовет процедуру act_ConnDestroyed. Последняя окончательно и полностью отключит вызываемую сторону и переведет скрипт в состояние DESTSELECTION, попутно передав управление функции act_GetDestination и не забыв заново проинициализировать глобальные переменные, необходимые для осуществления нового звонка. proc act_ConnDestroyed { } { leg disconnect leg_outgoing; init_perCallVars; act_GetDestination; fsm setstate DESTSELECTION; return; } proc act_Cleanup { } { call close; } requiredversion 2.0 init set ivr_fsm(any_state,ev_disconnected) ↵ "act_Cleanup same_state"; set ivr_fsm(CALLCOMES,ev_setup_indication) ↵ "act_Setup same_state"; set ivr_fsm(CALLCOMES,ev_collectdigits_done) ↵ "CheckLangSelection CHECKLANG"; set ivr_fsm(CHECKLANG,ev_media_done) "SelectLanguageMenu CALLCOMES"; set ivr_fsm(CARDSELECTION,ev_collectdigits_done) "act_GotCardNumber same_state"; set ivr_fsm(CARDSELECTION,ev_authorize_done) ↵ "act_CardAuthorize same_state"; set ivr_fsm(DESTSELECTION,ev_collectdigits_done) "act_GotDestination same_state"; set ivr_fsm(DESTSELECTION,ev_authorize_done) ↵ "act_CallAuthorize same_state"; set ivr_fsm(PLACECALL,ev_media_done) ↵ "act_CallSetup same_state"; set ivr_fsm(PLACECALL,ev_setup_done) ↵ "act_CallSetupDone same_state"; set ivr_fsm(PLACECALL,ev_digit_end) ↵ "act_FastSetup same_state"; set ivr_fsm(CALLACTIVE,ev_digit_end) ↵ "act_LongPound CONNDESTROY"; set ivr_fsm(CALLACTIVE,ev_leg_timer) ↵ "act_ActiveTimer CALLWARN"; set ivr_fsm(CALLWARN,ev_destroy_done) ↵ "act_CallWarnDestroy same_state"; set ivr_fsm(CALLWARN,ev_media_done) ↵ "act_CallWarnMedia CALLLASTACTIVE"; set ivr_fsm(CALLLASTACTIVE,ev_leg_timer) ↵ "act_LastActiveTimer same_state"; set ivr_fsm(CALLLASTACTIVE,ev_destroy_done) ↵ "act_PlayDisconnect CALLDISCONNECT"; set ivr_fsm(CALLLASTACTIVE,ev_digit_end) ↵ "act_LongPound CONNDESTROY"; set ivr_fsm(CONNDESTROY,ev_destroy_done) ↵ "act_ConnDestroyed same_state"; set ivr_fsm(CALLDISCONNECT,ev_media_done) ↵ "act_Cleanup same_state"; set ivr_fsm(CALLDISCONNECT,ev_disconnect_done) ↵ "act_Cleanup same_state"; fsm define ivr_fsm CALLCOMES;
↵
↵
47
администрирование
РАЗГОН И ТОРМОЖЕНИЕ WINDOWS NT i486C-ядру посвящается…
КРИС КАСПЕРСКИ Разработчики ядра исполнительной системы говорят, что оно дает пищу всему остальному. И это отнюдь не преувеличение! На плохом фундаменте ничего хорошего не построишь, и качество ядра в значительной мере определяет производительность всей операционной системы в целом. В комплект поставки Windows NT входит большое количество разнообразных ядер (в том числе и нашумевшее ядро i486С, по слухам значительно увеличивающее быстродействие системы). Как оценить их производительность? Обычные тестирующие пакеты для этого не подходят, и адекватную методику измерений приходится разрабатывать самостоятельно с учетом архитектуры ядра Windows и специфической направленности возложенных на него задач. Большинство пользователей и системных администраторов живут с ядром, назначенным операционной системой при ее инсталляции, совершенно не задумываясь о том, что его можно заменить другим. В штатный комплект поставки Windows NT входит более десятка различных ядер, и еще большее их количество может быть найдено на просторах Интернета. Некоторые производители аппаратного обеспечения оптимизируют ядра под свои машины, и зачастую эта оптимальность сохраняется на большинстве остальных. Существует мнение, что древние ядра (все еще совместимые с новомодными версиями Windows) намного производительнее современных, хотя и уступают им по функциональности. Отдельного разговора заслуживает история с ядром i486С, оптимизированным под 80486-машины. Оно входило во все версии Windows вплоть до NT 4.0, но затем неожиданно исчезло, и Windows 2000 вышла уже без него. Однако с Windows XP все вернулось вновь. Говорят, что оно здорово увеличивает производительность системы. Автор, знакомый с ним еще со времен Windows NT 4.0, подтверждает – да, увеличивает, особенно на медленных машинах. Для теоретического обоснования данного факта и была написана эта статья. Разумеется, само по себе ядро i486С не настолько интересно, чтобы уделять ему 13 журнальных полос. Давайте мыслить более глобально. Ядер много, а инструментов для оценки их производительности не существует (во всяком случае в открытом доступе нет ни одного). Тем не менее такой инструмент при желании можно разработать и самостоятельно, о чем я, собственно, и собираюсь рассказать.
Структура ядра Ядро Windows NT состоит из двух ключевых компонентов: executive system – исполнительной системы (далее по тексту KERNEL), реализованной в файле ntoskrnl.exe, и биб-
48
лиотеки аппаратных абстракций – Hardware Abstraction Layer (сокращенно HAL), представленной файлом HAL.DLL. На самом деле имена файлов могут быть любыми, и в зависимости от типа ядра они варьируются в довольно широких пределах. Исходная концепция построения Windows NT предполагала сосредоточить весь системно-зависимый код в HAL, используя его как фундамент для воздвижения системнонезависимой исполнительной системы. Тогда для переноса ядра на новую платформу было бы достаточно переписать один HAL, не трогая ничего остального (по крайней мере теоретически). В действительности же это требование так и не было выполнено, и большое количество системно-зависимого кода просочилось в исполнительную систему, а HAL превратился в сплошное нагромождение неклассифицируемых функций, тесно переплетенных с исполнительной системой, так что двухуровневая схема организации ядра в настоящее время выглядит довольно условной. Исполнительная система Windows NT реализует высокоуровневые функции управления основными ресурсами (как то: памятью, файлами, процессами и потоками), в определенном смысле являясь операционной системой в миниатюре. Большинство этих функций слабо связаны с конструктивными особенностями конкретной аппаратуры. Они практически не меняются от одной исполнительной системе к другой и одинаково производительны (или непроизводительны) во всех ядрах. Обособленная часть исполнительной системы, реализующая наиболее низкоуровневые операции и тесно взаимодействующая с библиотекой аппаратных абстракций, называется ядром (KERNEL). Большинство ядерных процедур предназначены для сугубо внутреннего использования и не экспортируются (хотя присутствуют в отладочных символах), а те, что экспортируются, обычно начинаются с префикса Ke (подпрограммы ядра) или Ki (обработка прерываний в ядре). Это уже третье упоминание ядра, которое мы встречаем, что создает определенную путаницу. Давайте попробуем разложить образовавшийся терминологический кавардак по полочкам. На самом верхнем уровне абстракций ядром принято называть совокупность компонентов операционной системы, работающих на привилегированном кольце нулевого уровня. Спустившись пониже, мы увидим, что ядро отнюдь не монолитно и состоит как минимум из двух частей: собственно самого ядра и загружаемых драйверов. Ядро Windows NT реализовано в двух файлах: библиотеке аппаратных абстракций (по сути дела являющееся набором первичных драйверов) и исполнительной системе. Выбором исполнительной системы руководит ключ KERNEL
администрирование файла boot.ini, поэтому многие ассоциируют ее с ядром, хотя это и не совсем верно, но не будем докапываться до столба, ведь фундамент исполнительной системы – тоже ядро. И это еще далеко не все! Подсистемы окружения (win32, POSIX, OS/2) имеют свои собственные ядра, сосредоточенные в прикладных библиотеках непривилегированного режима третьего кольца, и общаются с ядром Windows NT через специальную «прослойку», реализованную в файле NTDLL.DLL. Ядра подсистем окружения представляют собой сквозные переходники к ядру Windows NT и практически полностью абстрагированы от оборудования. Практически, но не совсем! Некоторая порция системно-зависимого кода присутствует и здесь. Многопроцессорные версии файлов NTDLL.DLL и KERNEL32.DLL для синхронизации потоков используют машинную команду LOCK. В однопроцессорных версиях она теряет смысл и заменяется более быстродействующей командой NOP. Наверняка существуют и другие различия, но я такие специально не искал, поскольку их влияние на производительность системы незначительно. Из всего этого зоопарка нас в первую очередь будет интересовать ядро исполнительной системы и HAL (рис. 1).
Типы ядер Тип выбираемого ядра определяется как архитектурными особенностями конкретной аппаратной платформы, так и личными предпочтениями пользователя системы, обусловленными спецификой решаемых задач. Существует по меньшей мере пять основных критериев классификации ядер: ! тип платформы (Intel Pentium/Intel Itanium, Compaq SystemPro, AST Manhattan); ! количество процессоров (однопроцессорные и многопроцессорные ядра); ! количество установленной памяти (до 4 Гб, свыше 4 Гб); ! тип контроллера прерываний (APIC- и PIC-ядра); ! тип корневого перечислителя (ACPI- и не ACPI-ядра). Очевидно, что ядро должно быть совместимо с целевым процессором на уровне двоичного кода и работать в наиболее естественном для него режиме (64-разрядный процессор, поддерживающий архитектуру IA32, сможет работать и со стандартным 32-разрядным ядром, но разумным такое решение не назовешь). Данная статья обсуждает вопросы оценки сравнительной производительности ядер
Ðèñóíîê 1. Àðõèòåêòóðà ÿäðà Windows NT
№8(21), август 2004
49
администрирование в рамках одной аппаратной платформы, и тема выбора процессора здесь не затрагивается. Многопроцессорные ядра отличаются от монопроцессорных прежде всего тем, что они «видят» все установленные процессоры и умеют с ними взаимодействовать, возлагая эту задачу на специальный драйвер, встроенный в HAL. Также в них кардинально переработаны механизмы синхронизации. Если в монопроцессорных ядрах для предотвращения прерывания критичного участка кода достаточно всего лишь подтянуть IRQL к верхнему уровню или заблокировать прерывания командной CLI, то в многопроцессорных ядрах такая стратегия уже не срабатывает – ведь на всех остальных процессорах прерывания разрешены – и приходится прибегать к спинлокам (от английского spin lock – взаимоблокировка). Для защиты участка кода от вмешательства извне система взводит специальный флаг, складывая поступающие запросы в специальную очередь. Естественно, это требует некоторого количества процессорного времени, что негативно сказывается на производительности, но другого выхода у нас нет. Значительно усложняется и схема диспетчеризации прерываний, ведь теперь один набор IRQ приходится делить между несколькими процессорами, а таблицы обработчиков аппаратных/программных прерываний поддерживать в согласованном состоянии. Изменения коснулись и планировщика, а точнее самой стратегии планирования потоков, которая может быть реализована как по симметричной, так и по асимметричной схеме. Симметрич-
Ðèñóíîê 2. Ñèììåòðè÷íàÿ è àñèììåòðè÷íàÿ îáðàáîòêà
50
ные ядра (а их большинство) допускают выполнение каждого из потоков на любом свободном процессоре, асимметричные же жестко закрепляют системные потоки за одним из процессоров, выполняя пользовательские потоки на всех остальных. Асимметричные ядра не входят в стандартный комплект поставки Windows NT и обычно предоставляются поставщиками соответствующего оборудования. Асимметричные ядра несколько менее производительны, чем симметричные (один из процессоров большую часть своего времени простаивает), однако их намного сложнее «затормозить», и сколько бы прожорливых потоков ни запустил злобный хакер, администратор всегда может обезоружить их, ведь системные потоки выполняются на отдельном процессоре! Многопроцессорные ядра следует использовать только на многопроцессорных системах, в противном случае мы значительно проиграем в производительности, причем многопроцессорные материнские платы с одним процессором на борту требуют специального унипроцессорного ядра (uniprocessor kernel), а работоспособность однопроцессорных ядер в такой конфигурации никем не гарантирована (хотя обычно они все-таки работают) (рис. 2). Разрядность внешней адресной шины младших моделей процессоров Intel Pentium составляет 32 бита, и потому они не могут адресовать более 4 Гб физической памяти. Поскольку для серьезных серверов и мощных рабочих станций этого оказалось недостаточно, начиная с Pentium Pro, ширина шины была увеличена до 36 бит, в результате чего
администрирование мы получили возможность адресовать вплоть до 64 Гб физической памяти. При работе в обычном режиме страничной адресации четыре старших бита адресной шины обнуляются, и чтобы их задействовать, необходимо перевести процессор в режим PAE (Physical Address Extensions), отличающийся структурой таблиц страничной переадресации и поддерживающий 2 Мб страницы памяти. PAE-ядра несколько производительнее обычных ядер, поскольку засовывают старшие 2 Мб адресного пространства процесса в одну страницу, сокращая тем самым издержки на переключение контекста между процессами. Вы можете использовать PAE-ядра, даже если у вас установлено менее 4 Гб физической памяти, однако выигрыш в производительности при этом будет не очень существенен. В зависимости от типа контроллера прерываний, установленного на материнской плате, следует выбирать либо PIC-, либо APIC-ядро. PIC-контроллеры поддерживают 15 IRQ и встречаются только на многопроцессорных материнских платах. APIC-контроллеры поддерживают до 256 IRQ и многопроцессорную обработку. На программном уровне PIC- и APICконтроллеры взаимно совместимы, поэтому PIC-ядро должно работать и с APIC-контроллером, однако, во-первых, при этом оно увидит всего лишь 15 IRQ, а во-вторых, такая конфигурация не тестировалась Microsoft, и потому никаких гарантий, что система не зависнет при загрузке, у нас нет. Материнские платы с поддержкой технологии ACPI могут работать как с ACPI, так и с не ACPI-ядрами, при этом не ACPI-ядра самостоятельно распределяют системные ресурсы компьютера и взаимодействуют с устройствами напрямую, а ACPI-ядра во всем полагаются на ACPI-контроллер, фактически являющийся корневым перечислителем, т.е. самой главной шиной компьютера, к которой подключены все остальные шины и устройства. И хотя эта шина виртуальна, производительность системы значительно падает, поскольку ACPI-контроллер имеет тенденцию вешать все PCI-устройства на одно прерывание со всеми вытекающими отсюда последствиями. Подробнее обо всем этом и многом другом я расскажу по ходу углубления внутрь статьи, а пока же сосредоточим свое внимание на ядре как таковом, приоткрыв черный ящик.
Почему не пригодны тестовые пакеты Среди изобилия тестовых программ, представленных на рынке, можно найти множество утилит для оценки быстродействия центрального процессора, оперативной памяти, видеокарты или подсистемы ввода/вывода, но мне неизвестны инструменты, пригодные для определения производительности ядра операционной системы. А почему бы не воспользоваться WINSTONE или SPECweb96? Первый имитирует запуск реальных офисных приложений, второй – веб-сервера. Разве их показания не будут отражать объективное влияние конкретного ядра на производительность всей операционной системы в целом? Нет, не будут. И вот почему. WINSTONE (как и большинство его соплеменников) прогоняет тесты в идеализированных условиях при минимальном количестве потоков, поэтому накладные расходы на переключение контекста не учи-
№8(21), август 2004
тываются. К тому же степень «отзывчивости» системы (подсознательно ассоциируемая с ее производительностью) обусловливается отнюдь не скоростью выполнения кода, а интеллектуальностью планировщика, повышающего приоритет потока в тот момент, когда он больше всех остальных нуждается в процессорном времени (например, при захвате фокуса, завершении ожидаемой операции ввода/ вывода и т. д.). Но тесты этого не учитывают. Допустим, пересчет миллиона ячеек электронной таблицы, независимо от версии ядра, длится ровно один час. Означает ли это, что все ядра равноценны? Вовсе нет! За кадром остались такие жизненно важные аспекты, как «подвижность» фоновых потоков системы, «чувствительность» к прерываниям и т. д. Одно ядро достойно обрабатывает клавиатурный ввод одновременно с расчетом, а другое жутко тормозит, реагируя на нажатия с задержкой в несколько секунд. Ну и с каким из них вам будет приятнее работать? А ведь тестирующей программе все равно… Зайдем с другой стороны. Обычно тестовое задание состоит из серии повторяющихся замеров, время выполнения которых усредняется. Замеры со значительными отклонениями от средневзвешенного значения отбрасываются (ну мало ли, может, в этот момент началась отгрузка кэша на диск). Если продолжительность одного замера составляет не более 20 мс (целая вечность для процессора!), за это время может вообще не произойти ни одного переключения контекста, а если оно и произойдет, то будет безжалостно отбраковано при обработке результатов, в итоге мы получим «чистую» производительность машины за вычетом вклада операционной системы. Можно ли этого избежать? Увы! В противном случае результаты тестирования будут варьироваться от прогона к прогону, и пользователь растеряется – какое же значение ему выбрать? Ведь на коротком промежутке времени (порядка 10 – 20 мс) издержки от побочных эффектов крайне непостоянны и неоднородны, поэтому выдавать неочищенный результат нельзя. Если же продолжительность замера превышает 20 мс, планировщик Windows автоматически перераспределяет процессорное время так, чтобы переключение контекста основного потока (и этим потоком будет тестовый поток!) происходило как можно реже, а его накладные расходы стремились к нулю. Естественно, остальные потоки системы сажаются на голодный паек, работая рывками (и, как мы увидим далее, даже при умеренной загрузке системы на «плохих» ядрах потоки получают управление не чаще чем один раз… в десять секунд. Не «тиков», а именно секунд! И хотя интегральная производительность системы не только не уменьшается, но даже возрастает, работать с ней становится невозможно). То же самое относится и к имитатору веб-сервера. Допустим, одно ядро обрабатывает сто тысяч запросов за минуту, а другое – сто пятьдесят. Какое из них производительнее? А если первое обслуживает всех своих клиентов плавно, а второе отдает информацию «плевками» с паузами по десять-пятнадцать секунд? К сожалению, известные мне тесты этого обстоятельства не учитывают и потому их показания при всей своей объективности толкают на выбор неправильного ядра. Помните анекдот про машину, которая
51
администрирование ездит быстро, но тормозит медленно? Производительность – это не только отношение количества проделанной работы к затраченному времени, это еще и качество предоставляемого сервиса! Сформулируем главное требование, предъявляемое нами к системе: планировщик должен исхитриться перераспределить процессорное время между потоками так, чтобы обеспечить наилучший баланс между производительностью и плавностью работы даже при большом количестве одновременно выполняющихся потоков. Теперь для достижения полного счастья остается лишь найти ядро своей мечты.
Обсуждение методик тестирования Скажите, какую физическую величину измеряет обыкновенный ртутный или спиртовой термометр. Температуру? Нет, объем. Каждый тест по-своему объективен и что-то измеряет, но результат измерений в значительной мере определяется его интерпретацией. Объем ртутного шарика прямо пропорционален его температуре. Это хорошо. Но производительность не температура. Это комплексная величина, и в индексах производительности столько же смысла, сколько и в коэффициенте интеллектуальности отдельного индивидуума. Кто умнее – Гейзенберг или Галуа? Кто сильнее – кит или слон? Какое из всех ядер самое производительное? Можно спорить до хрипоты, но в такой формулировке вопрос не имеет ответа. Как минимум мы должны выявить факторы, в наибольшей степени влияющие на совокупное быстродействие системы, и разработать адекватные методики тестирования, разлагающие векторное понятие производительности на скалярные величины. Никакая методика тестирования не безупречна и отражает лишь часть реальной действительности, поэтому к полученным результатам следует относиться с большой осторожностью. Если Формула-1 ездит быстрее, чем КамАЗ, отсюда еще не следует, что она сохранит свое преимущество при перевозке двухсот тонн кирпичей. Методика тестирования тесно связана со спецификой возлагаемых на систему задач (у автогонщика свои требования к машине, у дальнобойщика – свои), поэтому никаких конкретных решений здесь не приводится, напротив, эта статья показывает, как разрабатывать тестовые методики самостоятельно и какие проблемы поджидают вас на этом пути. А проблем будет много.
Разность таймеров Современные материнские платы несут на борту несколько независимых таймеров, которые никто не калибровал и каждый из которых врет слегка по-своему. Различные ядра используют различные таймеры для подсчета времени и системного планирования, поэтому отталкиваться от APIфункций типа GetTickCount или QueryPerfomanceCounter для сравнения производительности ядер категорически недопустимо! Ну во всяком случае без их предварительной калибровки. Первым (а на IBM XT еще и единственным) возник программируемый таймер интервалов – Programmable Interval Timer, или сокращенно PIT, базирующийся на микросхеме
52
Intel 8254 (сейчас – 82C54) и тактируемый частотой 1.19318 МГц (сейчас – либо 1.19318, либо 14.31818 МГц, причем последняя встречается намного чаще), что обеспечивало ему превосходную по тем временам точность измерений – порядка 0.84 мс (~1 мс с учетом накладных расходов). В Windows продолжительность одного тика таймера составляет 10 мс. Каждые 10 мс таймер дергает прерыванием, и закрепленный за ним обработчик увеличивает системное время на эту же величину. Если обработчик по каким-либо причинам проморгает таймерное прерывание (аппаратные прерывания запрещены инструкцией CLI или перепрограммированием PIC-контроллера), системное время начнет отставать от реального, существенно снижая точность измерений. Хуже того, размеренность хода PIT далеко не идеальна и варьируется в довольно широких пределах. Windows использует PIC-таймер в основном для планирования потоков, а для измерения времени стремится использовать другие, более точные таймеры, и переходит на PIT только тогда, когда ни один из них не доступен. Таймер часов реального времени (Real Time Clock или сокращенно RTC), впервые появившийся в IBM AT, обычно тактируется частотой 32.768 кГц, автоматически обновляя счетчик времени в CMOS, что не требует наличия программного обработчика прерываний. Частота обновления по умолчанию составляет 100 Гц, но при желании RTC-таймер может быть перепрограммирован, и тогда часы будут идти или медленнее, или быстрее. Точность показаний зависит как от состояния питающей батарейки (которая вообще-то никакая не батарейка, а настоящий литиевый аккумулятор, но это уже не важно), так и от добротности реализации микросхемы RTC со всеми обслуживающими ее компонентами. По заверениям производителей среднее время ухода за день составляет порядка 1-2 сек, однако имеющиеся у меня материнские платы врут намного сильнее, к тому же некоторые из них обнаруживают значительные «биения» на временных интервалах порядка десятых долей секунды, что отнюдь не способствует точности измерений. К тому же некоторые версии Windows периодически синхронизируют системное время с часами реального времени, что еще больше подрывает доверие к системному времени. Используя его для измерения продолжительности тех или иных процессов, вы можете получить очень странный результат. Часы реального времени используют многие тестовые программы и перепрограммирование RTC-таймера позволяет фальсифицировать результат. Windows 2000 использует RTC только для периодической синхронизации с системными часами. Усовершенствованный контроллер прерываний (Advanced Programmable Interrupt Controller, или сокращенно APIC), в основном использующийся в многопроцессорных системах, помимо прочей оснастки включает в себя и некоторую пародию на таймер, предназначенный для планирования потоков и не пригодный ни для каких измерений ввиду своей невысокой точности. Однако по непонятным причинам APIC-ядра используют APIC-таймер в качестве основного таймера системы, при этом величина одного «тика» составляет уже не 10 мс, а 15 мс. Естественно, часы идут с прежней скоростью, но политика планирования существенно изменяется – с увеличением кванта со-
администрирование кращаются накладные расходы на переключение контекстов, но ухудшается плавность коммутации между ними. Использовать показания счетчика системного времени для сравнения производительности APIC-ядер с другими ядрами недопустимо, т.к. полученный результат будет заведомо ложным. Материнские платы, поддерживающие ACPI (Advanced Configuration and Power Interface), имеют специальный таймер, обычно управляемый менеджером электропитания и потому называющийся Power Management Timer, или сокращенно PM. Еще его называют ACPI-таймером. Штатно он тактируется частой 3.579545 МГц (тактовая частота PIT, разделенная на четыре), что обеспечивает точность измерений порядка ~0.3 мс. ACPI-ядра используют PM-таймер в качестве основного таймера системы, чему сами не рады. Чипсеты от VIA, SIS, ALI, RCC не вполне корректно реализуют PM-таймер, что приводит к обвальному падению производительности операционной системы и снижению надежности ее работы. Проблема лечится установкой соответствующего пакета обновления, подробнее о котором можно прочитать в технической заметке Q266344. Разумеется, исправить аппаратную проблему (а в данном случае мы имеем дело именно с ней) программными средствами невозможно, и ее можно лишь обойти. Но даже на правильном чипсете при высокой загрузке PCI-шины PM-таймер не успевает своевременно передавать свои тики, и хотя они при этом не пропадают, обновление счетчика времени происходит «рывками», для преодоления которых Microsoft рекомендует сверять показания PM с показаниями PIC/APIC или RTC. И если PM неожиданно прыгнет вперед (jump forward), обогнав своих соплеменников, этот замер должен аннулироваться как недействительный. Современные чипсеты (и, в частности, Intel 845) содержат специальный высокоточный таймер (High Precision Event Timers, или сокращенно HPET), тактируемый частотой от 10 МГц, при которой время одного тика составляет от 0.1 мс при точности порядка ±0.2% на интервалах от 1 мс до 100 мс. Это действительно рекордно высокая точность, по крайней мере на порядок прерывающая точность всех остальных таймеров, однако HPET все еще остается завидной экзотикой, и чипсеты с его поддержкой пока еще не очень широко распространены. Помимо этого на материнской плате можно найти множество таймеров, например, PCI Latency Timer или десятки таймеров, обслуживающих чипсет, шины, память и прочие системные устройства. Многие из них тактируются частотами PCI- или AGP-шины, что обеспечивает достаточно высокую точность измерений (ниже, чем у HPET, но существенно выше, чем у PM). К сожалению, они в своей массе не стандартизированы и на каждой материнской плате реализуются по-своему, если вообще реализуются. Некоторые используют в качестве таймера команду RDTSC, считывающую показания внутреннего счетчика процессора, каждый такт увеличивающегося на постоянную величину (как правило единицу). Для профилировки машинного кода она подходит на ура, но вот на роль беспристрастного метронома уже не тянет. Некоторые ACPIконтроллеры динамически изменяют частоту процессора
№8(21), август 2004
или усыпляют его в паузах между работой для лучшего охлаждения. Как следствие – непосредственное преобразование процессорных тактов в истинное время оказывается невозможным. Утилиту для оценки разности хода нескольких таймеров можно скачать, например, отсюда: http://www.overclockers.ru/ cgi-bin/files/download.cgi?file=320&filename=timertest.rar. И если выяснится, что ваши таймеры идут неодинаково, для сравнения производительности различных ядер будет необходимо ограничиться лишь одним из них. Надежнее всего использовать для снятия показаний свой собственный драйвер, поскольку стратегия выбора таймеров ядром системы в общем случае непредсказуема и может отличаться от вышеописанной.
Ðèñóíîê 3. testtimer çà ðàáîòîé
Рассмотрим некоторые API-функции, используемые тестовыми программами для измерения интервалов времени. ! GetTickCount. Самая популярная функция. Возвращает количество миллисекунд, прошедших со времени последнего старта системы. В зависимости от типа установленного ядра использует либо PIT-, либо APIC-таймеры, в соответствии с чем ее разрешение составляет либо 10 мс, либо 15 мс, причем некоторые тики таймера могут быть пропущены (т.е. за 30 мс не произойдет ни одного увеличения счетчика). Не рекомендуется к употреблению. ! GetSystemTime. Возвращает истинное время. То есть функция думает, что оно истинное, а в действительности – производное от системного счетчика, инкрементируемого каждые 10 мс или 15 мс за вычетом «съеденных» тиков и врожденной неравномерности хода PIT- и APIC-таймеров. Периодически синхронизирует себя с часами реального времени, а если запущена специальная интернет-служба, то еще и с атомными часами, т.е. системное время продвигается траекторией пьяного гонщика, едущего по пересеченной местности. Для измерений временных промежутков непригодна. ! timeGetTime. То же самое, что и GetTickCount. Документация утверждает, что разрешающая способность этой функции составляет 1 мс, в действительности же – 10 мс. Синхронные изменения timeGetTime и GetTickCount подтверждают, что они питаются от одного источника.
53
администрирование ! Sleep. Усыпляет поток на указанное количество миллисекунд, задерживая на время управление. Теоретически может использоваться для калибровки других таймеров и вычисления коэффициента перевода таковой процессора в секунды. Практически же… Время ожидания принудительно округляется до величины, кратной «тику» основного системного таймера, причем на момент выхода из сна данный поток должен находиться в самом начале очереди потоков, ожидающих выполнения, в противном случае ему придется подождать. Может быть, доли секунды, а может, несколько минут – все зависит от размеров очереди, а она непостоянна. На хорошо загруженной системе поток, планирующий вздремнуть 100 мс, рискует проснуться… через минуту! ! QueryPerformanceCounter. Возвращает количество тиков наиболее точного таймера, прошедших с момента старта системы или… его последнего переполнения. Является переходником к функции KeQueryPerformance Counter, реализованной в HAL. Windows XP поддерживает HPET, более ранние системы – нет. Если HPET аппаратно доступен и программно поддерживается, используется он, в противном случае ACPI-ядра будут использовать PM, а не ACPI – либо PIT, либо возвратят ноль, сигнализируя об ошибке. Для определения продолжительности одного тика можно использовать функцию QueryPerformanceFrequency, возвращающую его частоту в герцах. Это самое лучшее средство для профилировки и хронометража времени из всех имеющихся, однако, как уже говорилось, PM-таймеры могут идти неточно или совершать неожиданные прыжки вперед, поэтому, показания, возращенные QueryPerformance Counter, требуют некоторой обработки.
Синхронизация На машинном уровне межпроцессорная синхронизация обеспечивается префиксом LOCK, предоставляющим команде монопольный доступ к памяти. Все посягательства со стороны остальных процессоров (если они есть) блокируются. Обычно LOCK используется совместно с командами, устанавливающими или снимающими флаги, сигнализирующие о том, что указанная структура данных в настоящий момент модифицируется процессором, и потому лучше ее не трогать. Многопроцессорные ядра содержат множество LOCK, встречающихся в самых неожиданных местах, и съедающих вполне ощутимый процент производительности, поэтому многопроцессорные ядра всегда медленнее. В однопроцессорных ядрах часть LOCK убрана полностью, вместе с примыкающими к ним флагами, а часть заменена более быстродействующими NOP. Общая же структура ядра сохранена в более или менее неизменном виде (рис. 4).
ACPI и IRQ Прерывания – их мифическая разводка, неиссякаемый источник слухов, сплетен, легенд и суеверий. Попробуем с ними разобраться. Все x86-процессоры (за исключением старших моделей Intel Pentium-4) управляют прерываниями через специальный интерфейсный вывод, обычно обозначаемый как INTR,
54
высокий уровень сигнала на котором свидетельствует о поступлении запроса на прерывание. Процессор прекращает текущую работу, вырабатывает сигнал подтверждения прерывания (Interrupt Acknowledge) и считывает с шины данных 8-битный номер вектора перекрывания (INT n), переданный контроллером прерываний, обычно реализуемым на правнучатых племянниках микросхемы i8259 и территориально находящийся в южном мосту чипсета. За вычетом 32 прерываний, зарезервированных разработчиками процессора, мы имеем 224 прерывания, пригодных для обработки сигналов от периферийных устройств. Означает ли это, что верхнее ограничение на максимальное количество одновременно поддерживаемых устройств равно 224? Нет! Некоторые из древних процессоров имели всего лишь один вектор прерывания, но это ничуть не мешало им управлять десятком устройств одновременно. Как? Да очень просто. Что такое прерывание? Всего лишь способ устройства обратить на себя внимание. При наличии достаточного количества свободных векторов за каждым из устройств может быть закреплено свое персональное прерывание, однозначно указывающее на источник сигнала. Это существенно упрощает как проектирование самих устройств, так и разработку обслуживающих их драйверов, однако отнюдь не является необходимым. Получив сигнал прерывания, процессор может опросить все устройства по очереди, выясняя, кто из них затребовал внимания. Естественно, это достаточно медленная операция, выливающаяся в десятки дополнительных операций ввода/вывода и к тому же потенциально небезопасная, т.к. малейшая ошибка разработчика оборачивается серьезными конфликтами. Короче говоря, для достижения наивысшей стабильности и производительности системы каждое прерывание должно использоваться не более чем одним устройством. Контроллер прерываний, используемый в IBM XT, поддерживал восемь аппаратных прерываний, обозначенных IRQ (Interrupt Request) и пронумерованных от 0 до 7. Номер IRQ соответствует приоритету прерывания: чем больше номер – тем ниже приоритет. Во время обработки более приоритетных прерываний генерация менее приоритетных подавляется и соответственно, наоборот, менее приоритетные прерывания вытесняются более приоритетными. Считается, чем больше ресурсов требует устройство, тем выше должен быть приоритет его IRQ. Это неверно. Выбор предпочтительного IRQ, определяется отнюдь не «прожорливостью» устройства, а критичностью потери прерывания. Допустим, сетевая карта, видя, что входной буфер практически полон, а данные по витой паре продолжают поступать, сгенерировала прерывание, которое было вытеснено прерыванием звуковой карты, имеющей более высокий приоритет и сигнализирующей об опустошении выходного буфера. Если драйвер звуковой карты ненароком замешкается и удержит обработчик прерывания дольше положенного, входной буфер сетевой карты переполнится, и часть пакетов окажется безвозвратно утеряна, и данные придется передавать вновь, что несколько снизит быстродействие сети. Является ли эта ситуация нормальной? Для кого-то да, а для кого-то и нет! Потерянных пакетов, конечно, жаль, но если поменять приоритеты местами, звуковая карта отреагирует на опустошение входного буфера суровым иска-
администрирование жением воспроизводимого сигнала, чего в некоторых ситуациях ни в коем случае допускать нельзя. Никакого другого влияния на производительность выбор приоритетов не оказывает. Независимо от номера IRQ обработка прерывания занимает одно и то же время. От обработки остальных прерываний она также не освобождает. При условии, что аппаратные устройства и обслуживающие их драйверы реализованы правильно (т.е. более или менее безболезненно переживают потерю IRQ и «отпускают» прерывание практически сразу же после его возникновения), выбор приоритетов никакой роли не играет. Контроллер прерываний позволяет отображать аппаратные IRQ0-IRQ7 на 8 любых смежных векторов прерываний, например, на INT 30h – INT 37h. Тогда, при возбуждении IRQ0, процессор сгенерирует прерывание INT 30h, а при возбуждении IRQ3 – INT 33h. В IBM AT количество контроллеров было увеличено до двух, причем второй был подключен на вход первого, в результате чего количество аппаратных прерываний возросло до 15. Почему не 16? Так ведь одна из восьми линий прерываний была израсходована на каскадирование с другим контроллером! Некоторое количество прерываний разошлось по системным устройствам, некоторое было выделено шине ISA – тогдашнему индустриальному стандарту. Генерация прерываний осуществлялась изменением уровня сигнала на линии соответствующего IRQ. Могут ли два или более уст-
ройств висеть на одном IRQ? Ну вообще-то могут, но если они одновременно сгенерируют сигнал прерывания, то до контроллера дойдет лишь один из них, а остальные будут потеряны, но ни контроллер, ни устройства об этом не догадаются. Такая ситуация получила название конфликта, и ее последствия всем хорошо известны. Впрочем, если прерывания возникают не слишком часто, то оба устройства вполне уживаются друг с другом (в свое время автор держал на одном прерывании и мышь, и модем). Шина PCI, пришедшая на смену ISA, работает всего с четырьмя линиями равно приоритетных прерываний, условно обозначаемых как INTA, INTB, INTC и INTD. На каждый слот подведены все четыре прерывания, и устройство может использовать любое подмножество из них, хотя обычно ограничиваются только одним. Линии прерываний одного слота соединяются с линями остальных слотов (а в некоторых дешевых платах все INTA, INTB, INTC и INTD вешаются на одну линию прерывания). Для равномерного распределения прерывания по устройствам на каждом слоту происходит ротация прерываний (рис. 5). Допустим, у нас есть два слота: в первом слоте прерывание INTA (со стороны устройства) соответствует прерыванию INTA (со стороны шины), прерывание INTB → INTB и т. д. Во втором слоте прерыванию INTA (с стороны устройства) соответствует прерывание INTB (со стороны шины), INTB → INTC, INTC → INTD и INTD → INTA, в результате чего устройства, исполь-
Ðèñóíîê 4. ACPI êàê êîðíåâîé ïåðå÷èñëèòåëü
№8(21), август 2004
55
администрирование зующие прерывание INTA, оказываются развешаны по прерываниям INTA и INTB.
Ðèñóíîê 5. Ðîòàöèÿ àïïàðàòíûõ ïðåðûâàíèé PCI-øèíû
Линии прерываний INTA – INTB соединяются с выводами PIRQ0 – PIRQ3 контроллера PCI-шины, а оттуда через роутер (PCI Interrupt Router) попадают в контроллер прерываний, тем или иным образом отображаясь на четыре линии IRQ, не занятые никакими ISA-устройствами. Поскольку количество установленных PCI-устройств обычно много больше четырех (мы считаем также и внутренние устройства, такие, например, как интегрированный контроллер USB, чаще всего повешенный на INTD), несколько устройств вынуждены делить одно прерывание между собой. В отличие от ISA, в PCI-шине совместное использование прерываний является ее нормальным состояниям. Генерация прерываний осуществляется не по переходу, а по состоянию, и устройство может удерживать линию прерывания в соответствующем состоянии до тех пор, пока его запрос не будет обработан. Теоретически это легко. Практически же… Даже поверхностное тестирование обнаруживает большое количество устройств и драйверов, не вполне соответствующих спецификациям и не желающих делить свое PIRQ с другими (или делающих это настолько неумело, что производительность падает в разы). Следование спецификациям предотвращает конфликты, но оставляет проблему падения производительности в силе. При совместном использовании прерываний драйвера получают сигналы не только от своих, но и от чужих устройств, заставляя их обращаться к своему устройству за подтверждением, и если выяснится, что прерывание сгенерировало не оно, запрос передается следующему драйверу в цепочке. А теперь представьте, что произойдет, если на одном прерывании висит
Ðèñóíîê 6. Âñå PCI-óñòðîéñòâà íà îäíîì ïðåðûâàíèè
56
десяток устройств и драйвер наиболее «беспокойного» из них попадет в самый хвост очереди? Для достижения наивысшей производительности следует, во-первых, оптимально распределить PCI-карты по слотам (например, если у вас на шесть PCI-слотов приходятся две PCI-карты, то, втыкая устройства в первый и пятый слот, вы вешаете их на одно PIRQ), по возможности совмещая на одном PIRQ только наименее темпераментные устройства, т.е. такие, которые генерируют прерывания реже всего. Во-вторых, каждое PIRQ должно отображаться на свое IRQ. Какое – не суть важно (ведь приоритет PCI-прерываний одинаков), но только свое. Совместное использование одного IRQ несколькими PIRQ обычно не приводит к конфликтам, но негативно сказывается на производительности, ведь драйвера работают не с PIRQ, а с IRQ! ACPI-ядра, работающие с PCI-шиной через ACPI-контроллер, лишены возможности управлять отображением PIRQ на IRQ по своему усмотрению. Не может управлять этим и BIOS (во всяком случае легальными средствами). Сам же ACPI стремится повесить все PIRQ на одно IRQ (обычно IRQ9), и помешать ему очень трудно. Если количество установленных PCI-устройств намного больше четырех, то разница в производительности между ACPI- и не ACPI-ядрами несущественна, поскольку, даже отказавшись от ACPI, вы все равно будете вынуждены разделять одно PIRQ между несколькими устройствами. Другое дело, если количество PCI-устройств невелико и наиболее темпераментные из них висят на своих прерываниях, тогда при переходе с ACPI- на не ACPI-ядро разница в быстродействии системы может оказаться очень значительной (то же самое относится и к неудачно спроектированным устройствам, не умеющим делить прерывания с другими и не имеющих достойной замены, например, дорогой видеоускоритель, RAID-контроллер и т. д.). К сожалению, просто взять и отключить ACPI нельзя, поскольку он является не только менеджером питания, распределителем ресурсов, но еще и корневым перечислите-
администрирование лем. ACPI- и не ACPI-ядра используют различные деревья устройств и потому взаимно несовместимы. Смена ядра в обязательном порядке требует переустановки системы, в противном случае та откажется загружаться. Это существенно затрудняет сравнение быстродействия ACPI- и не ACPI-ядер, поскольку переустановка системы радикальным и непредсказуемым образом изменяет ее производительность (рис. 6). Продвинутые материнские платы используют усовершенствованный контроллер прерываний (Advanced PIC или сокращенно APIC), поддерживающий 256 IRQ и способный работать в многопроцессорных системах. Однако в монопроцессорных конфигурациях он не обеспечивает никаких дополнительных преимуществ, т.к. количество свободных прерываний ограничивается не контроллером, а PCI-шиной. К тому же APIC-ядра не вполне корректно работают с таймером, что сводит на нет все их преимущества.
Переключение контекста Под «многозадачностью» большинство пользователей подразумевает возможность параллельного выполнения нескольких приложений: чтобы в фоне играл WinAmp, скачивался mp3 с Интернета, принималась почта, редактировалась электронная таблица и т. д. Минимальной единицей исполнения в Windows является поток. Потоки объединяются в процессы, а процессы – в задания (jobs). Каждый поток обладает собственным стеком и набором регистров, но все потоки одного процесса выполняются в едином адресном пространстве и обладают идентичными квотами. В любой момент времени на данном процессоре может выполняться только один поток, и если количество потоков превышает количество установленных процессоров, потоки вынуждены сражаться за процессорное время. Распределением процессорного времени между потоками занимается ядро. Вытесняющая многозадачность, реализованная в Windows NT, устроена приблизительно так: каждому потоку выдается определенная порция машинного времени, называемая квантом (quantum), по истечении которой планировщик (dispatcher) принудительно переключает процессор на другой поток. Учет процессорного времени обеспечивается за счет таймера. Периодически (раз в 10 мс или 15 мс) таймер генерирует аппаратное прерывание, приказывающее процессору временно приостановить выполнение текущего потока и передать бразды правления диспетчеру. Диспетчер уменьшает квант потока на некоторую величину (обычно равную двум) и либо возобновляет выполнение потока, либо (если квант обратился в нуль) сохраняет регистры потока в специальной области памяти, называемой контекстом (context), находит поток, больше всего нуждающийся в процессорном времени, восстанавливает его контекст вместе с контекстом процесса (если этот поток принадлежит другому процессу) и передает ему управление. Потоки обрабатываются по очереди в соответствии с их приоритетом и принятой стратегией планирования. Планировщик сложным образом манипулирует с очередью, повышая приоритеты потоков, которые слишком долго ждут процессорного времени, только что получили фокус управления или дождались завершения операции ввода/вывода.
№8(21), август 2004
Алгоритм планирования непрерывно совершенствуется, однако, не все усовершенствования оказывают благоприятное влияние на производительность. В общем случае многопоточные приложения должны исполняться на тех ядрах, под стратегию планирования которых они оптимизировались, в противном случае можно нарваться на неожиданное падение производительности. При небольшом количестве потоков накладные расходы на их переключения довольно невелики, и ими можно пренебречь, но по мере насыщения системы они стремительно растут! На что же расходуется процессорное время? Прежде всего на служебные нужды самого планировщика (анализ очереди, ротацию приоритетов и т. д.), затем на сохранение/восстановление контекста потоков и процессов. Посмотрим, как все это устроено изнутри? Дизассемблирование показывает, что планировщик как бы размазан по всему ядру. Код, прямо или косвенно связанный с планированием, рассредоточен по десяткам функций, большинство из которых не документированы и не экспортируются. Это существенно затрудняет сравнение различных ядер друг с другом, но не делает его невозможным. Чуть позже мы покажем, как можно выделить подпрограммы профилировщика из ядра, пока же сосредоточимся на переключении и сохранении/восстановлении контекста. Процессоры семейства x86 поддерживают аппаратный механизм управления контекстами, автоматически сохраняя/восстанавливая все регистры при переключении на другую задачу, но Windows не использует его, предпочитая обрабатывать каждый из регистров вручную. Какое-то время автор думал, что i486С-ядро, ничего не знающее о MMX/ SSE-регистрах современных процессоров и не включающее их в контекст, будет выигрывать в скорости, однако параллельная работа двух и более мультимедийных приложений окажется невозможной. В действительности же оказалось, что за сохранение/восстановление регистров сопроцессора (если его можно так назвать) отвечают машинные команды FXSAVE/FXSTOR, обрабатывающие и MMX/SSE-регистры тоже, но чтобы выяснить это, пришлось перерыть все ядро – от HAL до исполнительной системы! Переключение контекста осуществляется служебной функцией SwapContext, реализованной в ntoskrnl.exe. Это чисто внутренняя функция, и ядро ее не экспортирует. Тем не менее она присутствует в символьных файлах (symbol file), бесплатно распространяемых фирмой Microsoft. Полный комплект занимает порядка 150 Мб и неподъемно тяжел для модемного скачивания. Ряд утилит, таких, например, как Symbol Retriever, от NuMega, позволяют выборочно скачивать необходимые символьные файлы вручную, значительно сокращая время перекачки, однако по непонятным причинам они то работают, то нет (Microsoft блокирует доступ?), поэтому необходимо уметь находить точку входа в SwapContext самостоятельно. Это легко. SwapContext – единственная, кто может приводить к синему экрану смерти с надгробной надписью «ATTEMPTED_SWICH_FROM_DPC», которой соответствует BugCheck код B8h. Загрузив ntoskrnl.exe в ИДУ (или любой другой дизассемблер), перечислим все перекрестные ссылки, ведущие к функциям KeBugCheck и KeBugCheckEx. В какой-то из них мы найдем PUSH B8h/CALL KeBugCheck или что-то в этом роде. Она-то
57
администрирование и будет функцией SwapContex. Прокручивая экран дизассемблера вверх, мы увидим вызов функции HalRequestSoft wareInterrupt, которая, собственно, и переключает контекст, а в многопроцессорной версии ядра еще и машинную команду FXSAVE, которая тут совсем ни к чему и которая отсутствует в монопроцессорной версии. К тому же многопроцессорные версии намного щепетильнее относятся к вопросам синхронизации и потому оказываются несколько менее производительными. Функция HalRequestSoftwareInterrupt, реализованная в HAL, через короткий патрубок соединяется с функциями _HalpDispatchInterrupt/_HalpDispatchInterrupt, cохраняющими/ восстанавливающими регистры в своих локальных переменных (не в контексте потока!) и на определенном этапе передающих управление на KiDispatchInterrupt, вновь возвращающую нас в ntoskrnl.exe и рекурсивно вызывающую SwapContext. Кто же тогда сохраняет/восстанавливает контексты? Оказывается – аппаратные обработчики. Список указателей на предустановленные обработчики находится в ntoskrnl.exe и содержится в переменной IDT (не путать с IDT-таблицей процессора!), которая, как и следовало ожидать, не экспортируется ядром, но присутствует в символьных файлах. При их отсутствии найти переменную IDT можно так: просматривая таблицу прерываний любых из ядерных отладчиков (Soft-Ice, Microsoft Kernel Debugger), определите адреса нескольких непереназначенных обработчиков прерываний (т.е. таких, которые указывают на ntoskrnl.exe, а не к драйверу) и, загрузив ntoskrnl.exe в дизассемблер, восстановите перекрестные ссылки, ведущие к ним. Это и будет структурой IDT. Другие функции также могут сохранять/восстанавливать текущий контекст (это, в частности, делает Kei386EoiHelper, расположенная в ntoskrnl.exe), поэтому накладные расходы на переключение между потоками оказываются достаточно велики и выливаются в тысячи и тысячи команд машинного кода, причем каждое ядро имеет свои особенности реализации. Как оценить, насколько одно из них производительнее другого? Логично, если мы уговорим ядро переключать контексты так быстро, как только это возможно, то количество переключений в единицу времени и определит долю накладных расходов в общем быстродействии ядра. Сказано – сделано. Создаем большое количество потоков (по меньшей мере сто или даже триста) и каждый из них заставляем циклически вызывать функцию Sleep(0), приводящую к отдаче квантов времени и как следствие – немедленному переключению на другой поток. Количество переключений контекста можно определить по содержимому специального счетчика производительности, отображаемого Системным Монитором, утилитой CPUMon Марка Руссиновича, отладчиком Microsoft Kernel Debugger и многими другими программами. Ëèñòèíã 1. Èçìåðèòåëü ñêîðîñòè ïåðåêëþ÷åíèÿ êîíòåêñòà thread() { // îòäàåì ïðîöåññîðíîå âðåìÿ â áåñêîíå÷íîì öèêëå while(1) Sleep(0); } #define defNthr
58
300
#define argNthr ((argc > 1)?atol(argv[1]):defNthr) main(int argc, char **argv) { int a, zzz; printf("creating %d threads...", argNthr);
}
// ñîçäàåì argNthr ïîòîêîâ for (a = 0; a < argNthr; a++) ↵ CreateThread(0, 0, (void*)thread, 0,0, &zzz); printf("OK\n"); thread(); return 0;
Сравнение ACPI-ядра с i486С-ядром на машине автора (Intel Pentium-III 733 МГц, 256 Мб SDR-RAM-133) обнаруживает значительное расхождение в их производительности. i486С-ядро переключает контекст намного быстрее, особенно при работе с большим количеством потоков. В общем случае – количество переключений контекста обратно пропорционально количеству потоков, т.к. контексты надо гдето хранить, а кэш-память не резиновая. Если ядро делает много лишних сохранений (о которых мы уже говорили), оно существенно проигрывает в скорости. Тем не менее, все ядра спроектированы достаточно грамотно и сохраняют отличную подвижность даже при работе с тысячами потоков. Переключение процессов требует дополнительных накладных расходов и потребляет намного больше памяти, попутно вызывая сброс буфера ассоциативной трансляции, поскольку каждый из процессов обладает своим адресным пространством. Выделить код, ответственный за переключение контекстов, несложно – он выдает себя обращением к регистру CR3, загружая в него указатель на каталог страниц (Page Directory Physical Address). Давайте немного модернизируем нашу тестовую программу, заменив потоки процессами. Один из вариантов реализации может выглядеть так: Ëèñòèíã 2. Èçìåðèòåëü ñêîðîñòè ïåðåêëþ÷åíèÿ ïðîöåññîâ thread() { while(1) Sleep(0); } #define defNthr 3 #define argNthr ((argc > 1)?atol(argv[1]):defNthr) #define argProc "-666" main(int argc, char **argv) { int a, zzz; char buf[1000]; STARTUPINFO st; PROCESS_INFORMATION pi; memset(&st, 0, sizeof(st)); st.cb = sizeof(st); if ((argc > 1) && !strcmp(argv[1], argProc)) thread();
}
sprintf(buf,"%s %s",argv[0], argProc); printf("creating %d proc...", argNthr); for (a = 0; a < argNthr; a++) CreateProcess(0, buf, 0,0,0, ↵ NORMAL_PRIORITY_CLASS,0, 0, &st, &pi); printf("OK\n"); thread(); return 0;
Даже при небольшом количестве процессов система значительно «проседает» под нагрузкой и начинает ощутимо притормаживать, а количество переключений контекстов сокращаются приблизительно вдвое. i486С-ядро по-прежне-
администрирование му держится впереди, что не может не радовать, к тому же с ним система намного более оживленно реагирует на внешние раздражители (в частности, клавиатурный ввод). Быстродействие подсистемы ввода/вывода специально не тестировалось, но по субъективным ощущениям i486С и с этим справляется намного быстрее. Желающие подкрепить экспериментальные данные доброй порцией дизассемблерных листингов могут самостоятельно проанализировать функции планировщика, если, конечно, ухитрятся выдрать их из ядра! Далеко не всем исследователям недр Windows удалось это сделать… Задумайтесь: если ядро львиную долю процессорного времени тратит на переключение контекстов, не означает ли это, что наиболее интенсивно вызываемыми функциями окажутся функции, принадлежащие планировщику? Запускаем нашу тестовую программу, подключаем Microsoft Kernel Profiler (или любой другой профилировщик ядра по вкусу) и дав ему на сбор статистики порядка десяти секунд, внимательно изучим полученный результат: Ëèñòèíã 3. Ôóíêöèè ÿäðà, ïðÿìî èëè êîñâåííî îòíîñÿùèåñÿ ê ïëàíèðîâàíèþ 4484 4362 4333 2908 2815 300 73 41
ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe ntoskrnl.exe
ExReleaseResourceForThread KiDispatchInterrupt SeTokenImpersonationLevel KeDelayExecutionThread KiIpiServiceRoutine RtlPrefetchMemoryNonTemporal KeDisconnectInterrupt ExFreePoolWithTag
Длительность квантов Частые переключения контекстов отрицательно сказываются на производительности системы, поэтому Windows NT подбирает продолжительность одного кванта с таким расчетом, чтобы они происходили как можно реже, теряя при этом подвижность и способность достаточно быстро реагировать. Допустим, у нас имеется 100 потоков, каждому из которых выделяется 100 мс процессорного времени, причем все потоки используют отведенное им время полностью. Тогда между двумя переключениями одного и того же потока пройдет 10 сек! Вот тебе, бабушка, и многозадачный день… Разве это нормально, когда нажатая клавиша отображается на экране только через 10 сек? Когда сетевые клиенты получают в час по чайной ложке? А ведь если сервер обрабатывает каждого из клиентов в отдельном потоке (что является типичной стратегией программирования в Windows NT), он должен умело распределять процессорное время между тысячами потоков! Разработчики UNIX, программирующие не ради денег, а в силу исторической неизбежности, стремятся выбирать величину кванта так, чтобы сервер не терял отзывчивости даже при пиковой нагрузке. Разработчики Windows NT, напротив, оптимизировали свою систему под максимальную производительность, меняя величину кванта от версии к версии так, чтобы совокупное количество обработанных запросов в единицу времени было максимальным. Ведь производительность – это сила, а комфортабельность и уют – понятия растяжимые. Поднимите компьютерные журналы, полазьте по Интернету – везде лежат только сравнитель-
№8(21), август 2004
ные тесты производительности, но нигде – отношение времени простоя клиента ко времени работы. Ладно, оставим лирику и перейдем к делу. Windows NT поддерживает два типа квантов – длинные и короткие. Независимо от своего типа кванты могут быть как фиксированной, так и переменной длины (кванты переменной длины еще называют динамическими). Величина кванта выражается в условных единицах, официально называемых quantum units. Три квантовых единицы обычно равны одному тику таймера. Управление типом и продолжительностью кванта осуществляется через следующий параметр реестра: HKLM\ SYSTEM\CurrentControlSet\Control\PriorityControl\Win32 PrioritySeparation. Если 4-й и 5-й биты, считая от нуля, равны 10, система использует короткие кванты. То же самое происходит при оптимизации параметров быстродействия под исполнение приложений (Панель Управления → Система → Дополнительно → Параметры быстродействия). 01 – указывает на длинные кванты (они же используются при оптимизации системы под выполнение служб в фоновом режиме). Любое другое значение выбирает продолжительность кванта по умолчанию (короткие – в Windows 2000 Professional, длинные – в Windows 2000 Server). Если 2-й и 3-й биты равны 10 – длина квантов фиксированна; 01 – позволяет планировщику динамически варьировать продолжительность кванта в заранее оговоренных пределах. Любое другое значение выбирает тип квантов по умолчанию (переменные – в Windows 2000 Professional, фиксированные – в Windows 2000 Server). При использовании динамических квантов планировщик пытается автоматически увеличивать продолжительность квантов некоторых потоков, тех, которым процессорное время нужнее всего. Во всяком случае, планировщик думает, что оно им нужнее, а думает он приблизительно так: если поток обслуживает GUI-окно и это окно находится в фокусе, продолжительность кванта увеличивается. Если поток полностью использует весь отведенный ему квант, его продолжительность увеличивается. Если… Конкретный алгоритм планирования зависит от выбранного ядра, и потому одни ядра могут оказаться намного предпочтительнее других. Два младших бита задают индекс в двухэлементном массиве PsPrioritySeparation, расположенном внутри ntoskrnl.exe и используемым планировщиком для расчета продолжительности квантов активного процесса. Эта переменная не экспортируется ядром, но упоминается в символьных файлах. Если же они отсутствуют, обратитесь к функции PsSet ProcessPriorityByClass, которая использует первый элемент этого массива как указатель на другой массив. Òàáëèöà 1. Ìîäåëüíûé ðÿä êâàíòîâ â Windows 2000 Professional
Для экспериментов с квантами слегка модернизируем нашу тестовую утилиту, заставляя потоки (процессы) использовать отведенный им квант времени целиком. А в первичный поток встроим счетчик времени, вычисляющий продолжительность интервала между двумя соседними переключениями:
59
администрирование Ëèñòèíã 4. Èçìåðèòåëü ïðîäîëæèòåëüíîñòè êâàíòîâ thread() { int a, b; while(!f) Sleep(0); while (f != 2); while(1) { for (a = 1; a< 100; a++) b = b + (b % a); } } #define defNthr 300 #define argNthr ((argc > 1)?atol(argv[1]):defNthr) main(int argc, char **argv) { int a, zzz; SYSTEMTIME st; printf("creating %d threads...", argNthr); for (a = 0; a < argNthr; a++) CreateThread(0, 0, (void*)thread, 0,0, &zzz); f = 1;
printf("OK\n");
while(1) { GetSystemTime(&st); printf("* %02d:%02d:%02d\n",st.wHour, ↵ st.wMinute, st.wSecond); Sleep(0); } return 0;
Под Windows 2000 Professional уже при 100 потоках время прогона очереди составляет 10 сек, в то же время под Windows 2000 Server и того больше. Выглядит это, скажу я вам, очень удручающе, и работать в таких условиях становится крайне дискомфортно. Причем приобретение более быстродействующего процессора не ускоряет обработку очереди, ведь потоки по-прежнему используют отведенные им кванты целиком, и хотя успевают переработать намного больше данных, каждый из них, как и раньше, получает управление раз в десять секунд. Переход на двухпроцессорную машину повышает отзывчивость системы приблизительно в 1.3 раза (два процессора уменьшают длину очереди в два раза, но за счет перехода на APIC продолжительность одного тика таймера увеличивается с 10 мс до 15 мс, итого 2/15/10 ~ 1.3). Однако есть и другой, менее дорогостоящий и намного более радикальный способ решения проблемы. Один щелчок мыши увеличит реакционность системы в пять, а то и более раз. Не верите? Ну так щелкните по рабочему столу, чтобы окно нашего тестового приложения потеряло фокус. Взгляните на таблицу временных замеров, которую мне удалось получить. Ëèñòèíã 5. Âðåìÿ îáðàáîòêè î÷åðåäè èç 100 ïîòîêîâ ïðè èñïîëíåíèè íà ïåðåäíåì ïëàíå (ñëåâà) è â ôîíîâîì ðåæèìå (ñïðàâà) íà Windows 2000 Professional 00:14:48 00:15:02 00:15:16 00:15:30 00:15:46 00:15:59 00:16:11 00:16:27 00:16:41 00:16:55
60
Обсуждение полученных результатов Результаты тестирования i486С-ядра, полученные на машине автора, приведены ниже.
Sleep(0); f = 2;
}
Что произошло? Потоки ушли в фон, их приоритет понизился, а величина кванта сократилась до минимума. И хотя накладные расходы на переключение контекста возросли, время обработки очереди сократилось до 2 сек, с которыми вполне можно жить! А теперь закройте все окна с несохраненными документами, которые вам жалко потерять, и увеличьте приоритет тестового приложения хотя бы на одну ступень. Висим? А то! Потоки тестового приложения отбирают процессорное время у всех остальных потоков (включая и некоторые системные), и они оказываются нефункциональны. То есть функциональны, но раз за 10 сек, чего для обработки клавиатурного и мышиного ввода более чем недостаточно. Забавно, но i486С-ядро при этом продолжает работать более или менее нормально.
00:23:10 00:23:12 00:23:14 00:23:16 00:23:18 00:23:20 00:23:22 00:23:24 00:23:27 00:23:29
Òàáëèöà 2. Ñêîðîñòü ïåðåêëþ÷åíèÿ êîíòåêñòà ïîòîêîâ íà Windows 2000 Professional ñ îòäà÷åé êâàíòîâ âðåìåíè (ïîòîêè èñïîëüçóþò íè÷òîæíóþ ÷àñòü îòâåäåííîãî èì ïðîöåññîðíîãî âðåìåíè)
Òàáëèöà 3. Ñêîðîñòü ïåðåêëþ÷åíèÿ êîíòåêñòà ïðîöåññîâ íà Windows 2000 Professional ñ îòäà÷åé êâàíòîâ âðåìåíè (ïîòîêè èñïîëüçóþò íè÷òîæíóþ ÷àñòü îòâåäåííîãî èì ïðîöåññîðíîãî âðåìåíè)
Òàáëèöà 4. Ñêîðîñòü ïåðåêëþ÷åíèÿ êîíòåêñòà ïîòîêîâ íà Windows 2000 Professional áåç îòäà÷è êâàíòîâ âðåìåíè (ïîòîêè èñïîëüçóþò îòâåäåííîå èì ïðîöåññîðíîå âðåìÿ öåëèêîì)
Òàáëèöà 5. Âðåìÿ îáðàáîòêè î÷åðåäè íà Windows 2000 Professional áåç îòäà÷è êâàíòîâ âðåìåíè (ïîòîêè èñïîëüçóþò îòâåäåííîå èì ïðîöåññîðíîå âðåìÿ öåëèêîì)
Как видно, это ядро имеет множество преимуществ перед стандартным ACPI-ядром. Какое из них использовать – каждый должен решать сам. i486С-ядро не поддерживает ACPI и поэтому не способно в полной мере управлять энергопитанием компьютера, однако отрицать его сильные стороны, право же, не стоит. Оно действительно увеличивает производительность системы, и ничего мифического в этом разгоне нет. Не верите? Испытайте его сами. Для этого в процессе установки (переустановки) операционной системы дождитесь, когда на экране появится сообщение «Press F6 if you need to install a third party SCSI or RAID driver» (Нажмите F6, если вам необходимо загрузить SCSI- или RAID-драйвер стороннего производителя), нажмите F5 и выберите из списка имеющихся ядер «Standart PC with C-Step i486» (Стандартный компьютер i486 степпинг-С). После чего продолжите установку в обычном режиме.
администрирование
ПОИСКОВАЯ СИСТЕМА СВОИМИ РУКАМИ
Данная статья о том, как очень просто и быстро настроить службу индексирования (MS Indexing Service) для работы с интернет-сервером IIS (MS Internet Information Services). Рассматриваемые в статье методы и приемы могут быть применены ко всем версиям Windows, начиная c Windows 2000. Эта информация может быть полезна тем, перед кем встала задача организовать поиск по веб-сайту или по файловым серверам в локальной сети.
АНДРЕЙ САПРОНОВ Очень многим системным администраторам задача организации поиска представляется достаточно сложной. Они считают, что для этого необходимо писать какие-то скрипты, поисковые движки и прочее. Ничего этого не нужно. Для понимания материала (а соответственно для организации поиска) вам понадобятся самые элементарные знания по HTML и функционированию веб-сервера IIS. Почему все так просто. Дело в том, что корпорация Microsoft в составе своих операционных систем имеет стандартное средство для реализации подобных задач – службу индексирования (далее СИ). Эта служба появилась в операционных Windows достаточно давно – начиная с Windows NT 4.0. В периодической печати и сети имеется большое коли-
№8(21), август 2004
чество материала, знакомящего пользователя с данной службой. Поэтому, чтобы не повторяться, я дам лишь ссылки на подобные статьи: ! http://www.osp.ru/win2000/worknt/2000/02/004.htm ! http://www.compulenta.ru/dk/offline/2003/90/31473 Еще одним источником информации об этой службе для вас может стать встроенная справка. Здесь же следует отметить, что подавляющее большинство встреченных мною источников процентов на 80 повторяют информацию, приведенную в справке. Далее я буду полагать, что читатель либо ознакомился с приведенными материалами, либо имеет общие понятия об архитектуре и принципах функционирования СИ.
61
администрирование Со своей стороны я сделаю небольшое резюме по архитектуре и функциональным возможностям СИ.
Краткое описание службы индексирования СИ является стандартным компонентом. Основными задачами этой службы являются индексирование и организация поиска в указанных пользователем каталогах. Параметры службы, включая расположение индексируемых данных, можно указать в консоли управления компьютером, оснастке «Служба индексирования». По умолчанию служба может индексировать (а следовательно, осуществлять быстрый поиск) следующие виды файлов: HTML, все документы MS Office, сообщения MIME. Этого вполне достаточно для веб-сайта. Однако если вам необходима большая функциональность вашего поисковика, то СИ может индексировать файлы, для которых имеются специальные фильтры (т.н. IFilter). Фильтр представляет собой dll-библиотеку, которая реализует интерфейс IFilter для определенного типа файлов. Вы можете сами разработать подобную программу или же взять готовую. Последних существует достаточно много. Вот некоторые: ! zip – http://www.4-share.com ! pdf – http://www.adobe.com/support/downloads/product.jsp? product=1&platform=Windows ! DjVu – http://www.lizardtech.com/download/dl_download.php? detail=doc_ifilter&platform=win ! Jpg – http://www.aimingtech.com/jpeg_ifilter ! wmv/wma, shtml, vmf, msg, pdf, zip, xmp, vCard – http:// www.ifiltershop.com СИ позволяет индексировать документы, находящиеся на локальной машине и на любом доступном удаленном узле. Запросы к СИ могут осуществляться на различном логическом уровне (OLE, ADO, ISAPI расширения) и при по-
Ðèñóíîê 1
62
мощи различных средств разработки (C++, VB, VBS, JS). В простейшем случае запрос на поиск можно выполнить из оснастки СИ в меню «Опрос каталога». При помощи оснастки «Производительность» можно оценить (как локально, так и удаленно) работу СИ по множеству параметров. Вот наиболее интересные из них: размер индекса, число активных пользователей, число запросов (рис. 1).
IIS и Служба индексирования Буду считать, что у вас установлен и запущен сервер IIS, а СИ настроен на индексацию и поиск нужных вам папок. По умолчанию CИ индексирует содержимое вашего вебузла. Для поиска пользователю, как минимум, необходима форма для ввода запросов (например, как стандартная, показанная выше). Форму для запроса, а вместе с ней и методы доступа к СИ можно реализовать двумя путями: ASP или HTML. В статье я буду рассматривать второй путь. Причин тому две. Во-первых, он на порядок проще как для понимания, так и для реализации. А во-вторых, очень подробные примеры на ASP имеются в Platform SDK и MSDN. Кроме того, пример формы и запросов на поиск присутствуют в стандартной поставке IIS: Windows\help\iisHelp\ iis\misc\search.asp и Windows\help\iisHelp\iis\misc\query.asp соответственно. Эти примеры имеют подробные комментарии на русском языке. В случае с HTML форма будет очень простой и до боли знакомой (рис. 2). В HTML это выглядит примерно так. Ëèñòèíã 1. search.html – ôîðìà äëÿ ââîäà çàïðîñà ïîëüçîâàòåëÿ <HTML> <HEAD>
администрирование <TITLE>Ïðîñòàÿ ôîðìà äëÿ ïîèñêà</TITLE> </HEAD> <BODY> <!--Óêàçûâàåì íà ññûëêó, íà âõîä êîòîðîé ïîéäóò ïàðàìåòðû ôîðìû… --> <FORM ACTION="search.idq" METHOD="GET" ID="Form1"> <!--…à ïàðàìåòð îäèí - ñòðîêà çàïðîñà --> ß èùó: <INPUT TYPE="TEXT" NAME="CiRestriction" ↵ SIZE="30" MAXLENGTH="100" VALUE="" ID="Text1"> <!--Êíîïêà --> <INPUT TYPE="SUBMIT" VALUE="Ïîèñê" ID="Submit1" ↵ NAME="Submit1"> </FORM> </BODY> </HTML>
Ðèñóíîê 2
Файл search.idq является скриптом, который содержит всю необходимую информацию для запроса к СИ посредством ISAPI (Internet Server Application Programming Interface) расширения idq.dll. Для того чтобы IIS распознавал этот текстовый файл как скрипт в свойствах вашего веб-узла, на вкладке «Домашний каталог» нужно нажать кнопку «Настройка…» и сопоставить файлам с расширением *.idq фильтр Windows\System32\idq.dll.
# âîçìîæíûå ïîëÿ äëÿ îòîáðàæåíèÿ â ðåçóëüòàòàõ ïîèñêà # èìÿ ôàéëà, ðàçìåð, ðåëåâàíòíîñòü, îïèñàíèå, ïîëíûé ïóòü, # çàãîëîâîê äîêóìåíòà, äàòà ïîñëåäíåé ìîäèôèêàöèè CiColumns=filename, size, rank, characterization, ↵ vpath, DocTitle, write # ñòðîêà çàïðîñà (ñóæåíèÿ) – òî, ÷òî õî÷åò íàéòè ïîëüçîâàòåëü CiRestriction=%CiRestriction% # ìàêñèìàëüíî âîçìîæíîå êîëè÷åñòâî äîêóìåíòîâ CiMaxRecordsInResultSet=100 # ÷èñëî íàéäåííûõ äîêóìåíòîâ íà ñòðàíèöó CiMaxRecordsPerPage=10 # êàòàëîã(è) äëÿ ïîèñêà (îòíîñèòåëüíî êîðíåâîé ïàïêè âåá-óçëà) CiScope=/DaT, / # DEEP óêàçûâàåò, ÷òî ïîèñê èäåò âî âñåõ ïîäêàòàëîãàõ # êàòàëîãîâ èç CiScope CiFlags=DEEP # øàáëîííàÿ ñòðàíèöà äëÿ îòîáðàæåíèÿ ðåçóëüòàòîâ ïîèñêà CiTemplate=/search.htx # ïîðÿäîê ñîðòèðîâêè íàéäåííûõ äîêóìåíòîâ (çäåñü ïî óìåíüøåíèþ # ðåëåâàíòíîñòè) # d - óìåíüøåíèå ïðèçíàêà, a - óâåëè÷åíèå CiSort=rank[d]
Как видно, формат файла довольно прост: Переменная = Значение. Небольшие комментарии относительно некоторых переменных: ! CiRestriction – строка запроса. В данном случае принимает значение переменной %CiRestriction% из файла search.html (см. Листинг 1). Параметры, передаваемые в idq-файл, обрамляются с двух сторон знаками процента. Если хотите, можете передать больше параметров. Например, количество записей на страницу поиска. ! CiSort – признак для сортировки результатов. В этой переменной можно указать любое поле из заданных в CiColumns (доступно около 60 различных полей). Данный параметр можно также сделать настраиваемым пользователем; ! CiTemplate – ссылка на шаблонный htx-файл (HTML extension) для отображения результатов запроса; ! CiFlags – указывает на необходимость обхода всех вложенных папок. Можно использовать для поиска только в указанных папках без обхода всех их подкаталогов. Тогда CiFlags должен быть равен SHALLOW. В приведенном выше примере использованы далеко не все параметры для запроса. За полным перечнем свойств рекомендую обратиться к MSDN.
HTX
Ðèñóíîê 3
Также необходимо, чтобы idq-файл находился в папке, имеющей право на исполнение скриптов (сценариев). После этого IIS будет обрабатывать IDQ (Internet Data Query) файлы так, как мы этого хотим. А хотеть мы можем примерно следующим образом: Ëèñòèíã 2. search.idq – ñêðèïò äëÿ çàïðîñà íà ïîèñê # [Query] – îáÿçàòåëüíàÿ ñåêöèÿ äëÿ idq-ôàéëà [Query]
№8(21), август 2004
Файл htx является не чем иным, как HTML-страницей. Однако помимо тегов HTML в этом файле доступны специфические идентификаторы, относящиеся к СИ и являющиеся специфичными для введенного пользователем запроса. Эти переменные (идентификаторы), как и в idq-файле, обрамляются c обеих сторон знаком процента + знаки больше и меньше – слева и справа, как у тегов. Например, имя файла текущей записи находится в переменной <%filename%>. Именно эти идентификаторы позволят вам определить количество найденных документов, их ранг, расположение, а также все остальные параметры. Вместо htx-расширения вы можете без ограничений использовать стандартное расширение html. Предлагаю рассмотреть пример шаблонной страницы, которая представит пользователю результаты поиска.
63
администрирование Ëèñòèíã 3. search.htx – øàáëîííàÿ ñòðàíèöà äëÿ ïðåäñòàâëåíèÿ ðåçóëüòàòîâ ïîèñêà <!--Íîâûé ïîèñê. Ïðèìåíÿåòñÿ âîçìîæíîñòü ïîäêëþ÷åíèÿ ôàéëîâ --> <%include /search.html%> <HR width="100%" SIZE="1"> <!--â ñëó÷àå åñëè íåò íèêàêèõ äîêóìåíòîâ, ò.å. CiMatchedRecordCount==0, --> <!--ãäå CiMatchedRecordCount – ÷èñëî íàéäåííûõ äîêóìåíòîâ --> <%if CiMatchedRecordCount eq 0%> <H4> Íå íàéäåíî äîêóìåíòîâ, ñîîòâåòñòâóþùèõ çàïðîñó ↵ "<%CiRestriction%>". </H4> <%else%> <!--Åñëè ÷òî-òî íàøëè, òî óêàçûâàåì íîìåð íà÷àëüíîé è êîíå÷íîé çàïèñè íà ñòðàíèöå --> <H4>Äîêóìåíòû ñ <%CiFirstRecordNumber%> ↵ ïî <%CiLastRecordNumber%> èç ↵ <%CiMatchedRecordCount%> ñîîòâåòñòâóþùèõ ↵ çàïðîñó "<%CiRestriction%>". </H4> <%endif%> <!--%begindetail% âûâîäèò çàïèñè î íàéäåííûõ äîêóìåíòàõ ñ òåêóùåé %CiCurrentRecordNumber% --> <!--äî %CiCurrentRecordNumber%+%CiMaxRecordsPerPage% çàïèñè --> <%begindetail%> <!--Íå çàáûâàéòå, ÷òî êëèåíò ïîëó÷èò êîììåíòàðèè èç ýòîé ñåêöèè ìíîãî-ìíîãî ðàç --> <br> <%CiCurrentRecordNumber%>. <b><a href="<%vpath%>"><%filename%></a></b> <b><i>Îïèñàíèå: </i></b><%characterization%><br> <!--âûâîäèì ðåëåâàíòíîñòü, òî÷íóþ ññûëêó, ðàçìåð, è äàòó ïîñëåäíåé ìîäèôèêàöèè -->
Ðèñóíîê 4
64
<font size=-1 color=DarkGreen> <%rank%> - <%vpath%> - ↵ ðàçìåð <%size%> áàéò - <%write%> GMT</font> <br> <%enddetail%> <!--Êîíñòðóèðóåì êíîïêè äëÿ ïåðåìåùåíèÿ ìåæäó ñòðàíèöàìè ïîèñêà --> <!--Åñëè â âûâåäåííûõ çàïèñÿõ íåò ïåðâîé, çíà÷èò, ìîæíî âåðíóòüñÿ íàçàä, --> <!--ò.å. îòîáðàæàåì êíîïêó "Ïðåäûäóùàÿ" --> <%if CiContainsFirstRecord eq 0%> <!--Ïåðåäàåì óæå çíàêîìîìó ñêðèïòó ïàðàìåòðû ñäâèíóòüñÿ íà %CiMaxRecordsPerPage% çàïèñåé íàçàä --> <!--îò ïåðâîé çàïèñè íà ñòðàíèöå %CiBookmark%> <FORM ACTION="search.idq" METHOD="GET"> <INPUT TYPE="HIDDEN" NAME="CiBookmark" ↵ VALUE="<%CiBookmark%>" > <INPUT TYPE="HIDDEN" NAME="CiBookmarkSkipCount" ↵ VALUE="-<%CiMaxRecordsPerPage%>" > <!--åñëè íà ïðåäûäóùåé ñòðàíèöå âàì íå íóæíî çíà÷åíèå %CiRestriction%, òî ìîæíî--> <!--îáîéòèñü è áåç ýòîé ñòðîêè--> <INPUT TYPE="HIDDEN" NAME="CiRestriction" ↵ VALUE="<%CiRestriction%>" > <INPUT TYPE="SUBMIT" VALUE="Ïðåäûäóùàÿ"> </FORM> <%endif%> <!--Àíàëîãè÷íî ïîñòóïàåì ñ êíîïêîé "Ñëåäóþùàÿ" --> <%if CiContainsLastRecord eq 0%> --> <FORM ACTION="search.idq" METHOD="GET"> <INPUT TYPE="HIDDEN" NAME="CiBookmark" ↵ VALUE="<%CiBookmark%>" > <INPUT TYPE="HIDDEN" NAME="CiBookmarkSkipCount" ↵ VALUE="<%CiMaxRecordsPerPage%>" > <INPUT TYPE="HIDDEN" NAME="CiRestriction" ↵
администрирование </FORM> <%endif%>
VALUE="<%CiRestriction%>" > <INPUT TYPE="SUBMIT" VALUE="Ñëåäóþùàÿ">
Тегами <%begindetail%>…<%enddetail%> результат запроса разделяется на страницы с числом записей на каждой странице, равным <%CiMaxRecordsPerPage%>», задаваемым в idq-файле. Думаю, назначение остальных тегов ясно из текста программы. Кроме тегов-переменных, к результатам поиска применимы условные переходы <% if %>…<% else %>…<% endif %> и логические операторы: EQ (равенство), NE (неравенство), LT/GT (меньше/больше), LE/GE (меньше/больше либо равно), CONTAINS (содержание одной строки в другой), ISTYPEEQ (принадлежность определенному типу). Файлы htx поддерживают возможность подключения внешних html/htx-файлов (см. Листинг 3) при помощи директивы <%include filename%>. Подключать можно не более 31 файла, включая вложенные. В этом примере для вывода информации о найденном документе использовано свойство документа %Characterization% – краткое описание (аннотация). Это описание автоматически генерируется СИ при индексировании документов. Если вы хотите, чтобы оно генерировалось, то это нужно указать в свойствах СИ на вкладке «Генерация» – «Генерировать аннотации». Там же можно указать размер аннотации. В исходном HTML-коде были использованы комментарии. Необходимо помнить, что они, в отличие от кода, отвечающего за форматирование, передаются клиенту. Это может быть плохо и с позиции увеличения трафика (здесь идет многократное повторение, что может быть весьма ощутимо), и с позиции конфиденциальности. Можно сказать, что в минимальном варианте мы имеем рабочую и достаточно функциональную поисковую систему. На следующем рисунке представлена третья страница с результатами поиска на запрос «Производительность & IIS» (рис. 4).
Hit-Highlighting Традиционно в поисковых системах возможно отображение тех частей текста, где встречаются искомые слова или фраза. В СИ эту возможность фирма Microsoft назвала HitHighlighting. Для этого используется еще один вид скриптов – htw. Как и в случае с idq-скриптами, нужно «научить» IIS распознавать файлы htw как файлы сценария. Это делается тем же способом, который мы применяли по отношению к idq-файлам (см. выше). Нужно сопоставить файлам с расширением htw ISAPIфильтр – библиотеку Windows\system32\webhits.dll. После этого IIS при запросе «somefile.htw?параметры» сгенерирует страницу с результатами «подсветки». Самый элементарный запрос может иметь вид: null.htw?CiWebhitsFile=pageforhighlight.html&CiRestriction=IIS
В этом запросе два обязательных параметра (все параметры указываются поочередно через знак амперсанда): ! CiWebhitsFile – указывает на файл, для которого мы хотим сделать выделение участков текста.
№8(21), август 2004
! CiRestriction – строка запроса. Это как раз та самая фраза, по которой и будет происходить отбор. В показанном запросе будут выделены все части текста из файла pageforhighlight.html, содержащие слово IIS. Кроме основных параметров, существуют и дополнительные. Практически все они направлены на изменение вида отображаемого текста. Вы можете сделать выделенные слова жирными (CiBold), наклонными (CiItalic), указать цвет подсветки (CiHiliteColor). Параметр CiHiliteType при передаче ему значения Full выводит не куски страницы, а всю ее целиком, подсвечивая искомые слова. Файл null.htw является абстрактным файлом и служит указанием серверу для форматирования документа по умолчанию. В документации сообщается, что этот файл может и не существовать, однако без него IIS выдает стандартную ошибку – 404. После создания ничего не содержащего файла с таким именем все становится на свои места. Этот файл должен располагаться в папке, имеющей права на исполнение сценариев. Показанный в листинге 3 пример можно дополнить ссылкой «подробней», которая и покажет найденные в тексте слова. Сделаем ее красным цветом. В секцию <%begindetail%>…<%enddetail%> необходимо вставить следующий код: Ëèñòèíã 4. Ññûëêà íà ïîäðîáíûé îò÷åò î ñîâïàäåíèÿõ â äîêóìåíòå <!--ññûëêà íà äîêóìåíò, ñîäåðæàùèé ÷àñòè òåêñòà, êîòîðûå îêðóæàþò èñêîìóþ ôðàçó --> <a href="Null.htw?CiWebhitsFile=<%escapeURL ↵ vpath%>&CiRestriction=<%escapeURL ↵ CiRestriction%> "> <font size=-1 color=Red> ïîäðîáíåé </font> </a>
Здесь следует обратить внимание на ключевое слово <%EscapeURL%>. Это необходимо для приведения запросов к стандартному виду. Например, запрос на поиск всех документов с расширением doc «@filename=*.doc» в качестве параметра должен быть передан в виде «%40filename=%2A.doc», а использованный несколько выше запрос «Производительность & IIS» будет заменен на: “%EF%F0%EE%E8%E7%E2%EE%E4%E8%F2%E5%EB%FC%ED%EE%F1%F2 ↵ %FC%20%26%20IIS”
Похожее назначение и у ключевого слова <%EscapeHTML%>: он заменяет значимые в HTML символы на их аналоги. Таким образом, символ «<» будет заменен на «&lt».
Несколько слов о формате запросов Правила, формат, примеры запросов для поиска исчерпывающе представлены во встроенной справке СИ. Руководство написано на удивление хорошо и понятно.
Заключение Надеюсь, что я убедил вас в простоте организации поиска. Ведь можно организовать очень функциональный поиск при помощи одной HTML-страницы и двух маленьких скриптов. За формальным описанием ключевых параметров и констант советую обратиться к MSDN (msdn.microsoft.com) и к примерам из Platform SDK.
65
web
HTML-ШАБЛОНЫ ДЛЯ PHP И PERL, ИЛИ НЕ ДЕЛАЙТЕ ИНСТРУМЕНТ САМОЦЕЛЬЮ! ДМИТРИЙ ГОРЯИНОВ О шаблонах в веб-программировании знают или слышали буквально все. Интерпретируемые языки веб-программирования, такие как PHP, Perl, ASP VBScript, всегда давали возможность совмещать код языка и HTML. Это быстро и просто. Вначале. Как только проект или отдельный файлсценарий начинают разрастаться, эта смесь так же быстро становится неудобоваримой. Взявшись переписывать какой-либо сценарий, сразу хочется выкинуть HTML-вставки просто потому, что они начинают мешать читать программный код. Ради этого, ради разделения HTML-форматирования вывода и логики программы и был придуман механизм шаблонов. Логика остается программе, верстка – шаблону. Программа обрабатывает запрос, формирует данные и заполняет шаблон. Шаблон всегда содержит HTML-код и некоторые дополнительные конструкции для вставки данных и управления выводом. Тут необходимо кое-что прояснить.
Небольшой взгляд в историю, или Каким должен быть шаблонный движок? Несомненно, идея шаблонов пришла в голову программистам. И суть идеи явно была в том, чтобы убрать HTML-код из программы. А вот дальше… Дальше все складывалось по-разному. Понятно, что первыми «родились» простейшие шаблоны. Это просто HTML-код и специальные указания для вставки данных. По сути, переменные. <html> <head> <title>::title::</title> </head> <body> ::body:: </body> </html>
Такой простой HTML-документ. Можно сохранить и открыть в браузере. Будет страничка с заголовком «::title::» и содержимым «::body::». Есть конструкция (::имя::), которую нужно выделить из HTML-кода, есть некоторое имя, вместо которого нужно подставить данные из программы. Можете называть это переменной, но лучше назовем это именованной вставкой. Дальше все стало гораздо более запутанно. Довольно быстро выяснилось, что есть блоки, которые могут повторяться на одной и той же странице. А иногда на этой странице в зависимости от некоторого условия нужно либо выводить что-то, либо нет. Вот тут началось жуткое многообразие. В шаблоны стали добавлять макроязыки. При этом, в зависимости от ква-
66
лификации авторов системы, в шаблоны внедряли условные операторы, операторы циклов, регулярные выражения, макрофункции и даже объекты (последним сильно грешат системы шаблонов для языка Java). К чему же это привело? Кроме того, что авторы систем поупражнялись в написании мини-трансляторов, это привело к тому, что человек, хорошо владеющий навыками HTML-верстки, не мог создать ни один более-менее полезный шаблон. Он должен был предварительно изучить новый для себя синтаксис. И при этом все, что нам действительно нужно от шаблона это: ! возможность указать место для вставки данных; ! выделить блок HTML-кода, который можно будет включить или не включить в конечный вывод или, допустим, повторить нужное количество раз. В идеале синтаксис дополнительных конструкций должен позволять работать с шаблоном в традиционном HTMLредакторе и просматривать готовый файл-шаблон в браузере. Систем шаблонов написано действительно много. Мы будем рассматривать две из них (одну для языка PHP и вторую – для Perl). Основная цель статьи – показать, как правильно работать с готовыми движками, чтобы минимизировать изменения в HTML-коде шаблонов и оставить логику за программой.
Добавление шаблонного движка в проект Итак, что вообще требуется от шаблонного движка? Как минимум – установить, подключить и начать использовать. Во-первых, основная цель проекта не в том, чтобы поработать с шаблонами. Наша система будет загружена собственной полезной работой, и тратить большие ресурсы на поддержание работы с шаблонами крайне нежелательно. Во-вторых, нам вряд ли подойдет движок, который потребует дополнительной компиляции каких-либо компонент или изменения конфигурации веб-сервера. Дело даже не в том, что у нас нет своего сервера, а злой дядька провайдер не желает что-то там собрать-подключить на своей площадке. Просто вариант «отдельный проект – отдельный сервер» почти нереален. Скорее всего, наш сервер уже обслуживает какие-то проекты и нет ни малейшего желания бросаться их переписывать на новый лад или менять рабочую конфигурацию. Да и останавливать боевой сервер ради добавления «новой забавы» мало кто (читай – системный администратор) согласится.
web Поэтому лучше всего нам подойдет движок, который можно скачать, разархивировать и начать использовать без всякой дополнительной инсталляции. В качестве движка для PHP мы рассмотрим библиотеку XTemplate (http://sourceforge.net/projects/xtpl), а для Perl – модуль HTML::Template (http://html-template.sourceforge.net). Оба движка достаточно именно скачать и разархивировать. Вы, конечно, можете установить модуль для Perl по всем правилам, но вполне достаточно развернуть из архива каталог HTML с файлом-модулем Template.pm в нем. Куда разархивировать? Для простоты предлагается создать отдельный каталог для дополнительных библиотек. Пусть каталог htdocs – это корневой каталог размещения документов на веб-сервере. Создадим на одном уровне с ним каталог lib, и разархивируем в него наши движки, каждый – в отдельный каталог. Получится вот такая структура директорий:
В каталоге XTemplate будут файлы PHP-библиотеки, демонстрационные примеры и т. п. В каталоге HTML может находиться один-единственный perl-модуль – Template.pm Собственно, на этом все необходимые манипуляции по установке будут закончены. Просто и эффективно, не правда ли?
Просто отдельный класс Итак, мы решили отказаться от движков, требующих специальной инсталляции. Установка наших библиотек сводится к разархивированию или простому копированию. Пойдем дальше. Движок должен быть инкапсулирован в отдельный класс. Он не должен иметь ветвистых зависимостей с другими модулями. Иначе устанешь их удовлетворять. Множество хороших веб-программистов создавали и будут создавать свои, уникальные решения в рамках отдельных проектов. В таких случаях что-то непременно «затачивается» под конкретные нужды, что-то «дергается напрямую» и т. д. На базе таких решений вырастают системы публикаций или управления сайтами. Все это плотно упаковано, скручено в тугой узел, плохо отделимо друг от друга и, возможно, представляет собой законченный продукт, который, скорее всего, больше никому, кроме заказчика, не пригодится. Если же речь идет именно о подсистеме для работы с шаблонами, то такая система должна представлять собой законченный интерфейс, отделимый от остальной системы. Фактически – уникальный класс либо модуль. Программист, использующий движок, не должен тратить время на попытку понять, как оно должно было работать в соответствии с замыслом автора модуля или выполнять с десяток настроек. Нам нужен инструмент для работы, а не инструкция по обработке паровоза напильником с целью получить истребитель. В нашем случае это требование выполняется полностью. Нам нужно просто подключить новую библиотеку, согласуясь с синтаксисом языка.
№8(21), август 2004
Библиотеку PHP XTemplate мы разместили в подкаталоге XTemplate каталога lib. Класс XTemplate содержится в файле xtpl.p. Для того чтобы создать новый экземпляр класса, нам нужно подключить файл и создать объект: <?php ... include( getenv("DOCUMENT_ROOT")."/../lib/XTemplate/xtpl.p" ); $xtpl=new XTemplate( ïîëíîå_èìÿ_ôàéëà_øàáëîíà ); ... ?>
Для подключения библиотеки HTML::Template в Perl следует указать путь к её директории (если, как в нашем случае, она размещена отдельно от стандартных библиотек), подключить модуль и так же создать новый объект: #!/usr/bin/perl ... use lib $ENV{DOCUMENT_ROOT}."/../lib"; # Äðóãîé ñïîñîá äîáàâèòü êàòàëîã ðàçìåùåíèÿ áèáëèîòåêè: # unshift( @INC, $ENV{DOCUMENT_ROOT}."/../lib" ); use HTML::Template; my $template = HTML::Template->new( filename => ↵ ïîëíîå_èìÿ_ôàéëà_øàáëîíà );
Что же такое шаблон? Вернемся к началу. Для чего затевалась вся эта история с шаблонами? В основном для того, чтобы отделить код HTML от кода программы. Идеальный шаблон – это файл, содержащий HTML-разметку, и программно заполняемый данными. Одно из направлений в системах шаблонов – предварительная компиляция шаблонов в некоторое внутреннее представление. Обычно это убыстряет вывод программы, но любое изменение в шаблоне требует как минимум перекомпилирования его самого и, возможно, несколько зависимых шаблонов. То есть никакое изменение в HTML-коде не может сразу отразиться на сайте. Пока такие системы не дают настолько ощутимого выигрыша в скорости, чтобы считать это действительно хорошим решением. Так что сформулируем еще один постулат – движок не должен требовать дополнительной обработки шаблонов (читай – запретить предварительную компиляцию и прочие действия). Шаблон должен редактироваться как HTML-файл, иметь возможность предварительного просмотра как HTML-файл и подключаться «как есть». Мы же не хотим заставить верстальщика установить у себя веб-сервер лишь для того, чтобы просматривать промежуточные результаты своей работы? Вот два простых примера шаблонов для XTemplate и HTML::Template. Пример шаблона для библиотеки Xtemplate: <!-- BEGIN: main --> <html> <head><title>Ïðèìåð 1</title></head> <body> <dl> <dt>Çíà÷åíèÿ ïåðåìåííûõ äîáàâëÿþòñÿ â XTemplate ÷åðåç ìåòîä <i><b>assign( PARAM, value )</I></b>:</dt>
67
web <dd>$VARIABLE = <i>{VARIABLE}</i></dd> </dl> </body> </html> <!-- END: main -->
Если мы сохраним этот код в файле ex1.html и откроем его для просмотра в браузере, мы увидим такую «картинку»:
<!-- END: item --> </table> <!-- END: main -->
Рассмотрим шаблон подробнее. Прежде всего отметим, что весь шаблон для XTemplate заключен внутри именованного блока: <!-- BEGIN: main --> ... <!-- END: main -->
Ðèñóíîê 1. Âèä øàáëîíà äëÿ XTemplate â áðàóçåðå
Пример шаблона для библиотеки HTML::Template: <html> <head><title>Ïðèìåð 1</title></head> <body> <dl> <dt>Çíà÷åíèÿ ïåðåìåííûõ äîáàâëÿþòñÿ â HTML::Template ÷åðåç ìåòîä <i><b>param( PARAM => value );</i></b>:</dt> <dd>$VARIABLE = <i><TMPL_VAR NAME=VARIABLE></i></dd> </dl> </body> </html>
Так как подстановки в шаблонах HTML::Template имеют синтаксис, схожий с HTML-тэгами, HTML-вид шаблона будет слегка отличаться:
Это необходимое условие синтаксиса шаблонов XTemplate. Любой блок (и весь шаблон целиком) начинается с конструкции: <!-- BEGIN: èìÿ_áëîêà -->
И заканчивается конструкцией: <!-- END: èìÿ_áëîêà -->
Простая подстановка в шаблоне описывается так: {èìÿ_ïåðåìåííîé_äëÿ_ïîäñòàíîâêè}
Там, где логика подсказывает нам сгруппировать данные для подстановки, можно использовать массивы подстановок, описывая их таким образом: {èìÿ_ìàññèâà.èìÿ_êëþ÷à}
Ðèñóíîê 2. Âèä øàáëîíà äëÿ HTML::Template â áðàóçåðå
Код подстановки (<TMPL_VAR NAME=VARIABLE>) не отобразится в браузере. Фактически браузер воспримет этот как HTML-тэг, правило отображения которого ему неизвестно.
Лучшее – враг хорошего. Минимизируем логику, макроязык шаблонов Теперь постараемся разобраться с тем, что нам действительно понадобится в шаблонах, кроме HTML-кода. Есть несколько типичных задач, с которыми мы сталкиваемся постоянно и для которых мы хотим иметь четкое и простое решение. Хорошим примером такой задачи может служить вывод таблицы с чередованием фона строк. При выводе таблицы мы, во-первых, имеем дело с повторяющимися элементами (строками), во-вторых, чередование цвета фона – это выбор оформления в зависимости от условия. Шаблон «ex2.html» для библиотеки Xtemplate: <!-- BEGIN: main --> <table bgcolor="gray" cellspacing=2 border=0 cellpadding=4> <tr><th colspan="2" bgcolor="silver">{TOPIC}</th></tr> <!-- BEGIN: item --> <!-- BEGIN: odd --> <tr bgcolor="white"> <td>{DATA.NAME}</td><td>{DATA.VAL}</td> </tr> <!-- END: odd --> <!-- BEGIN: even --> <tr bgcolor="lightyellow"> <td>{DATA.NAME}</td><td>{DATA.VAL}</td> </tr> <!-- END: even -->
68
К примеру, это логично сделать при оформлении подстановок значений ячеек табличной строки. Шаблон описывает HTML-код для вывода данных. Сами данные формируются в программе. На уровне шаблона мы не можем предусмотреть количество элементов нашей таблицы. Поэтому каждую строку таблицы мы оформим в шаблоне в виде отдельного блока. Это делается для того, чтобы иметь возможность управлять выводом отдельных строк из программы-обработчика: <!-- BEGIN: item --> ... <!-- END: item -->
Внутри блока item мы опишем два других блока – «odd» и «even» – для чередования оформления строк. Программа-обработчик, проверяя условие четности строки, будет выбирать нужный блок и управлять генерацией конечного HTML-вывода, формируя содержимое блока item. В XTemplate вся логика организации циклов и условий ложится на код PHP-программы. В шаблоне описываются только блоки и имена для подстановки значений. Опишем PHP-код, обрабатывающий этот шаблон: <?php // Ïîäêëþ÷åíèå áèáëèîòåêè include( getenv("DOCUMENT_ROOT") ↵ ."/../lib/XTemplate/xtpl.p" ); // Ñîçäàíèå íîâîãî îáúåêòà. ôàéë øàáëîíà ex2.html // ðàçìåùàåì â êàòàëîãå htdocs/../data/xtemp
web $xtpl=new XTemplate( getenv("DOCUMENT_ROOT") ↵ ."/../data/xtemp/ex2.html" ); // Ïðîñòàÿ ïîäñòàíîâêà çíà÷åíèÿ â {TOPIC} $xtpl->assign( TOPIC, "Âûâîä òàáëèöû ñ ÷åðåäîâàíèåì ↵ ôîíà ñòðîê" ); $even = 0; // Öèêë äëÿ ôîðìèðîâàíèÿ ñòðîê òàáëèöû for ($i=1; $i<=10; $i++) { $row = array( NAME => "Ñòðîêà", VAL => $i ); $xtpl->assign(DATA, $row);
}
//  çàâèñèìîñòè îò ÷åòíîñòè ñòðîêè óêàçûâàåì // áëîê-øàáëîí âûâîäà «odd» èëè «even» if ( ($even = ($even XOR 1)) ) { $xtpl->parse("main.item.odd"); } else { $xtpl->parse("main.item.even"); } $xtpl->parse("main.item");
// Ðàçáîð ãëàâíîãî áëîêà øàáëîíà $xtpl->parse("main"); // Âûâîä ðåçóëüòàòà ðàçáîðà $xtpl->out("main"); ?>
Для того чтобы включить в результат вывода нужный блок из шаблона, используется метод parse( BLOCK ). Имя блока задается в виде строки, с указанием всех родительских блоков через точку:
ны программированию. Здесь нет таких простых и понятных блоков, выделяемых как HTML-комментарии. Увы, здесь есть отдельные конструкции для циклов и условий. Для нас хорошо то, что синтаксис этих конструкций аналогичен тэговой разметке, и они не отображаются при просмотре файла-шаблона браузером. Шаблон «ex2.html» для библиотеки HTML::Template: <table bgcolor="gray" cellspacing=2 border=0 ↵ cellpadding=4> <tr><th colspan="2" bgcolor="silver"><TMPL_VAR ↵ NAME=TOPIC></th></tr> <TMPL_LOOP NAME=item> <TMPL_IF NAME=odd> <tr bgcolor="white"> <td><TMPL_VAR NAME></td><td><TMPL_VAR VAL></td> </tr> <TMPL_ELSE> <tr bgcolor="lightyellow"> <td><TMPL_VAR NAME></td><td><TMPL_VAR VAL></td> </tr> </TMPL_IF> </TMPL_LOOP> </table>
В отличие от XTemplate мы не заключаем весь шаблон в блок. Библиотека HTML::Template не требует заключать весь шаблон в блок. Она работает с целым файлом, не деля его на отдельные блоки. Простую подстановку (замену) значения в шаблоне описывают так:
BLOCK::= [<Èìÿ_áëîêà>.]<Èìÿ_áëîêà> <TMPL_VAR NAME=èìÿ_ïàðàìåòðà_ïîäñòàíîâêè>
Таким образом, блок-шаблон для вывода нечетных строк будет именоваться «main.item.odd». Разумеется, для того чтобы блок был включен в конечный вывод, необходимо вывести и все родительские блоки: ...
$xtpl->parse(“main.item.odd”); ... $xtpl->parse(“main.item”); ... $xtpl->parse(“main”); ...
Метод assign( PARAM, value ) служит для заполнения значения переменной шаблона с именем PARAM. $xtpl->assign( TOPIC, "Âûâîä òàáëèöû ñ ÷åðåäîâàíèåì ↵ ôîíà ñòðîê" );
И наконец, метод out(BLOCK) печатает результат разбора шаблона. Если нам нужно не выводить, а сохранить его в виде готового текста, нужно использовать другой метод – text(BLOCK)…». Обращение к методу out() равносильно такому коду: ... $out = $xtpl->text("main"); echo $out;
Перепишем наш пример с таблицей для библиотеки HTML::Template. Сразу заметим, что изначально синтаксис шаблонов библиотеки HTML::Template более адаптирован «под программистов». Создатели библиотеки остались вер-
№8(21), август 2004
Для того чтобы описать в шаблоне повторяющийся HTML-код, его придется обрамить «оператором цикла»: <TMPL_LOOP NAME=èìÿ_ïîâòîðÿþùåãîñÿ_áëîêà> ... </TMPL_LOOP>
В программе для вывода такого цикла нужно сформировать массив и сопоставить его с именем повторяющегося блока. Каждый элемент массива соответствует включению HTML-кода, описанному внутри <TMPL_LOOP> … </TMPL_LOOP>. Элемент массива представляет собой хэш (наборы пар вида «ключ – значение»). То есть если в шаблоне мы описываем подстановку вида <TMPL_VAR NAME=имя_параметра_подстановки> внутри повторяющегося блока, то в хэше, описывающем элемент массива для подстановки, мы должны создать пару (имя_параметра_ подстановки => значение_для_подстановки). Создатели библиотеки внедрили в шаблон условный оператор. Мы будем использовать его только там, где нужно указать отдельный, вложенный блок HTML-кода, который будет подключаться в зависимости от условия, сформированного в программе: <TMPL_IF NAME=èìÿ_áëîêà> ... </TMPL_IF>
Подключение вывода блока зависит от значения, присвоенного обработчиком шаблона имени_блока. Если мы проинициализируем переменную блока значением «истина» –
69
web HTML-код, описанный внутри <TMPL_IF> … </TMPL_IF>, будет включен в результат разбора шаблона. Если мы не определим никакого значения для блока, или определим его как «ложь», блок не будет включен в результаты разбора шаблона. Как и в традиционном синтаксисе языков программирования, у условного блока (оператора) может существовать «альтернативная часть»: <TMPL_IF NAME=èìÿ_áëîêà> ... <TMPL_ELSE> ... </TMPL_IF>
Если переменная блока получила из программы значение «ложь», то HTML-код между <TMPL_ELSE> и </TMPL_IF> будет включаться в результат разбора. Код для обработки этого шаблона будет выглядеть так:
Вложенные шаблоны Что еще может пригодиться в работе с шаблонами? Любой сайт содержит повторяющиеся элементы дизайна – шапка, заголовки, меню и так далее. Хороший движок шаблонов должен иметь возможность подключать более мелкие шаблоны (назовите их заготовками или библиотечными элементами) внутрь более крупных, желательно – поддерживать многоуровневое, вложенное подключение. Чуть выше мы написали два шаблона для вывода таблицы. Логично не копировать этот код везде, где нам понадобится отобразить такую таблицу, а иметь возможность сохранить этот шаблон отдельно и подключать его по мере необходимости в другие шаблоны. Обе наши библиотеки такую возможность предоставляют. В XTemplate существует конструкция: {FILE "èìÿ_âëîæåííîãî_ôàéëà_øàáëîíà"}
#!/usr/bin/perl use strict; # Ïóòü ê êàòàëîãó áèáëèîòåêè use lib $ENV{DOCUMENT_ROOT}."/../lib"; # Ïîäêëþ÷åíèå áèáëèîòåêè use HTML::Template; # Ñîçäàíèå íîâîãî îáúåêòà.  äàííîì ñëó÷àå ìû îòäåëüíî # óêàçûâàåì â êîíñòðóêòîðå êðàòêîå èìÿ ôàéëà øàáëîíà # ex2.html è ïóòü ê êàòàëîãó htdocs/../data/htmltemp, # ãäå ðàçìåùåí øàáëîí my $template = HTML::Template->new( filename => "ex2.html", path => $ENV{DOCUMENT_ROOT}."/../data/htmltemp/" ); # Ïðîñòàÿ ïîäñòàíîâêà çíà÷åíèÿ â <TMPL_VAR NAME=TOPIC>. # Äåëàåòñÿ âûçîâîì ìåòîäà param( PARAM => value) $template->param(TOPIC => "Âûâîä òàáëèöû ñ ÷åðåäîâàíèåì ↵ ôîíà ñòðîê" ); my $even = 0; # Ñîçäàíèå ìàññèâà äëÿ âûâîäà ñòðîê òàáëèöû my $items = (); # Öèêë çàïîëíåíèÿ ìàññèâà äàííûìè for (my $i=1; $i<=10; $i++) { # Êàæäûé ýëåìåíò ìàññèâà (ñòðîêà òàáëèöû) ïðåäñòàâëÿåò # ñîáîé õýø âèäà: [ { "NAME" => çíà÷åíèå1, "VAL" => # çíà÷åíèå2, "odd" => [0|1] } ] my $row = { "NAME" => "Ñòðîêà", "VAL" => $i };
}
# Äëÿ íå÷åòíûõ ýëåìåíòîâ óñòàíàâëèâàåì çíà÷åíèå "odd", # ðàâíûì "1", äëÿ ÷åòíûõ - "0" if ( ($even = ($even xor 1)) ) { $row->{"odd"} = 1; } else { $row->{"odd"} = 0; } # Äîáàâëÿåì íîâûé õýø-ýëåìåíò â ìàññèâ push(@{$items}, $row);
# Àññîöèèðóåì ìàññèâ ñ ïîâòîðÿþùèìñÿ áëîêîì ( <TMPL_LOOP> ) $template->param(item => $items); # Ðåçóëüòàò ðàçáîðà øàáëîíà âîçâðàùàåò íàì â âèäå ñòðîêè # âûçîâ ìåòîäà output(). # Ìû ìîæåì ñîõðàíèòü åãî â ïåðåìåííóþ, çàïèñàòü â ôàéë # èëè âûâåñòè, êàê ëþáîå äðóãîå çíà÷åíèå: print "Content-type: text/html\n\n"; print $template->output(); __END__
70
В HTML::Template аналогичные функции выполняет конструкция: <TMPL_INCLUDE NAME="èìÿ_âëîæåííîãî_ôàéëà_øàáëîíà">
А вот здесь остановимся. Если вы посмотрите внимательно, то заметите, что мы вынуждены писать имя файла непосредственно в шаблоне. Это значит, что мы стали жестко привязаны к расположению файлов. Даже если мы укажем относительный путь к файлу шаблона, мы вынуждены будем в любом другом проекте в точности повторять структуру директорий для размещения шаблонов! Это плохо. Более того, это как-то противоречит самой идее о легкости подключения (читай – переноса) и замыслу о повторном использовании более мелких (библиотечных) шаблонов. Как можно выйти из ситуации? Например, иметь возможность единожды проинициализировать каталог размещения шаблонов в конструкторе класса. При наличии такой возможности мы можем один раз указать каталог шаблонов и в самих шаблонах уже не писать полный путь расположения вложенного шаблона. Вся настройка будет произведена в одном месте при вызове конструктора. В случае HTML::Template эта возможность предусмотрена изначально. Обратите внимание, мы сразу использовали инициализацию каталога шаблонов в нашем примере: my $template = HTML::Template->new( filename => "ex2.html", path => $ENV{DOCUMENT_ROOT}."/../data/htmltemp/" );
В библиотеке XTemplate это не предусмотрено (во всяком случае в версии 0.2.4-3). Сформировать имя файла шаблона динамически, написав нечто вроде: {FILE "{TEMP_FILE}"}
и присвоить (проассоциировать) нужное значение подста-
web новкой в {TEMP_FILE} тоже не выйдет. Конструктор класса производит рекурсивную inline-подстановку всех вложенных шаблонов, и только потом производит разбор и обработку полного шаблона со всеми подстановками. Что делать? Отказаться от идеи повторного использования вложенных элементов? Ну не все так печально, как кажется. Библиотека XTemplate распространяется по лицензии GNU General Public License. Эта лицензия подразумевает возможность внесения изменений в исходный продукт при соблюдении определенных правил1. Нужные нам изменения ограничатся тремя строчками с добавлением комментариев. Суть изменения сводится к добавлению еще одного члена класса и опциональной инициализации его в конструкторе. Этот новый член класса – path – будет определять путь к директории шаблонов. Остальные модификации в коде исходного класса ограничиваются конструктором и методом getfile(...). class XTemplate { ... /* Íîâûé ÷ëåí êëàññà */ var $path = ""; /***[ constructor ]*** / function XTemplate ($file,$mainblock="main", $path="") { /* Èíèöèàëèçèðóåì çíà÷åíèå path, åñëè ñîîòâåòñòâóþùèé ïàðàìåòð íå ïóñò */ if ($path) $this->path= str_replace("\\", "/", $path)."/"; … } … /***[ getfile ]*** / /* returns the contents of a file */ function getfile($file) { /* Èñïîëüçóåì êîíêàòåíàöèþ çíà÷åíèÿ path è ïåðåäàííîãî èìåíè ôàéëà */ $file = $this->path.$file; if (!isset($file)) { $this->set_error("!isset file name!"); return ""; } ... } ... } /* end of XTemplate class. */
</p> {FILE "ex2.html"} </body> </html> <!-- END: main -->
Обработчик шаблона выглядит так: <?php include( getenv("DOCUMENT_ROOT")."/../lib/XTemplate/xtpl.p" ); /*  êîíñòðóêòîð äîáàâèëñÿ äîïîëíèòåëüíûé ïàðàìåòð $path. Äëÿ ñîâìåñòèìîñòè íàì ïðèäåòñÿ âûçûâàòü êîíñòðóêòîð ñ óêàçàíèåì íàøåãî íîâîãî ïàðàìåòðà è âòîðîãî ïàðàìåòðà âûçîâà – íàçâàíèåì ãëàâíîãî áëîêà øàáëîíà. Åñòåñòâåííî, ýòî ïîíàäîáèòñÿ òîëüêî òàì, ãäå íàì íåîáõîäèìî óêàçàòü òðåòèé ïàðàìåòð êîíñòðóêòîðó, ò.å. â øàáëîíàõ ñ âëîæåíèåì. */ $xtpl=new XTemplate( "ex_inc.html", "main", getenv("DOCUMENT_ROOT")."/../lib/ ↵ data/xtemp" ); $xtpl->assign( TEMPLATE, "ex2.html"); /* Èíèöèàëèçàöèÿ äàííûõ âëîæåííîãî øàáëîíà. Ïðè îáðàùåíèè ê ìåòîäó parse() íóæíî ó÷èòûâàòü, ÷òî ýëåìåíòû øàáëîíà ex2.html âëîæåíû â áëîê "main" áîëåå âåðõíåãî øàáëîíà – ex_inc.html. Ïîýòîìó óêàçûâàòü ïðèäåòñÿ êàê $xtpl->parse("main.main.item") è ò. ï. */ $xtpl->assign( TOPIC, "Âûâîä òàáëèöû ñ ÷åðåäîâàíèåì ↵ ôîíà ñòðîê" ); $even = 0; for ($i=1; $i<=10; $i++) { $row = array( NAME => "Ñòðîêà", VAL => $i ); $xtpl->assign(DATA, $row); if ( ($even = ($even XOR 1)) ) { $xtpl->parse("main.main.item.odd"); } else { $xtpl->parse("main.main.item.even"); } $xtpl->parse("main.main.item"); } /* Ðàçáîð âëîæåííîãî øàáëîíà */ $xtpl->parse("main.main");
Результат наших действий в браузере отразится примерно таким образом:
После внесения таких изменений в класс XTemplate мы:
! Сможем инициализировать единый каталог для хранения шаблонов и использовать в них простые имена для подключения файлов вложенных шаблонов. ! Сохраним совместимость с предыдущей версией. Наши изменения сделаны таким образом, чтобы не затронуть работу шаблонов и обработчиков, написанных для исходной версии класса. Теперь можно нормально работать. Шаблон «ex_inc.html» для библиотеки XTemplate: <!-- BEGIN: main --> <html> <head><title>Âëîæåííûå øàáëîíû</title></head> <body> <p> Ïîäêëþ÷åíèå øàáëîíà òàáëèöû <b><i>"{TEMPLATE}" </i></b> ÷åðåç { FILE "{TEMPLATE}" } 1
Ðèñóíîê 3. Ðåçóëüòàò ðàáîòû ñ âëîæåííûìè øàáëîíàìè. Âûâîä òàáëèöû ðåàëèçîâàí îòäåëüíûì øàáëîíîì
Для библиотеки HTML::Template подобных модификаций исходного кода производить не требуется. Параметризованное указание каталога размещения шаблонов там предусмотрено заранее. Шаблон «ex_inc.html» для библиотеки HTML::Template:
Ознакомиться с полным текстом лицензии вы можете на сайте GNU.org (http://www.gnu.org/copyleft/gpl.html), или воспользоваться неофициальным переводом на русский язык – http://www.linuxdoc.ru/gnugpl_ru.html.
№8(21), август 2004
71
web <html> <head><title>Âëîæåííûå øàáëîíû</title></head> <body> <p> Ïîäêëþ÷åíèå øàáëîíà òàáëèöû <b><i>"<TMPL_VAR ↵ NAME=TEMPLATE>" </i></b> ÷åðåç &lt;TMPL_INCLUDE NAME="ex2.html"> </p> <TMPL_INCLUDE NAME="ex2.html"> </body> </html>
Обработчик шаблона: #!/usr/bin/perl use strict; # Ïóòü ê êàòàëîãó áèáëèîòåêè use lib $ENV{DOCUMENT_ROOT}."/../lib"; # Ïîäêëþ÷åíèå áèáëèîòåêè use HTML::Template;
Шаблон «ex_inc2.html» для библиотеки XTemplate: <!— BEGIN: main —> <html> <head><title>Âëîæåííûå øàáëîíû</title></head> <body> <p>
Подключение одного шаблона в разных местах страницы и с разным заполнением. </p> <!-- BEGIN: first --> Ñïèñîê 1: {FILE "list.html"} <!-- END: first -->
# Ñîçäàíèå íîâîãî îáúåêòà.  äàííîì ñëó÷àå ìû îòäåëüíî # óêàçûâàåì â êîíñòðóêòîðå êðàòêîå èìÿ ôàéëà øàáëîíà # ex_inc.html è ïóòü ê êàòàëîãó htdocs/../data/htmltemp, # ãäå ðàçìåùåí øàáëîí my $template = HTML::Template->new( filename => "ex_inc.html", path => $ENV{DOCUMENT_ROOT}."/../data/htmltemp/" );
<!-- BEGIN: second --> Ñïèñîê 2: {FILE "list.html"} <!-- END: second --> </body> </html> <!-- END: main -->
$template->param(TEMPLATE => "ex2.html" );
Обрабатываем шаблон на PHP:
# Çàïîëíåíèå äàííûõ âëîæåííîãî øàáëîíà $template->param(TOPIC => "Âûâîä òàáëèöû ñ ÷åðåäîâàíèåì ↵ ôîíà ñòðîê"); my $even = 0; my $items = (); for (my $i=1; $i<=10; $i++) { my $row = { "NAME" => "Ñòðîêà", "VAL" => $i }; if ( ($even = ($even xor 1)) ) { $row->{"odd"} = 1; } else { $row->{"odd"} = 0; } push(@{$items}, $row); } $template->param(item => $items); print "Content-type: text/html\n\n"; print $template->output(); __END__
Результат работы аналогичен, не будем загромождать место еще одной похожей картинкой. Еще один «интересный случай». Вложенные шаблоны хочется использовать для стандартных элементов. Легко предположить, что один и тот же элемент может использоваться на странице многократно. Если включить его как вложенный шаблон, получится путаница с заполнением. Как поступить? Нам нужен управляемый способ многократного включения вложенных шаблонов (стандартных элементов дизайна). Звучит очень серьезно, реализуется – просто. Каждое включение нужно выделить в виде отдельного блока. Шаблон «list.html» для библиотеки XTemplate: <!-- BEGIN: list --> <ol> <!-- BEGIN: item --> <li>{ITEM}</li>
72
<!-- END: item --> </ol> <!-- END: list -->
<?php include( getenv("DOCUMENT_ROOT")."/../lib/XTemplate/xtpl.p" ); $xtpl=new XTemplate( "ex_inc2.html", "main", getenv("DOCUMENT_ROOT")."/../lib/ ↵ data/xtemp" ); // Äàííûå äëÿ ïåðâîãî ñïèñêà $list = array("one", "two", "three"); foreach($list as $item) { $xtpl->assign(ITEM, $item); $xtpl->parse("main.first.list.item"); } // Ðàçáèðàåì áëîê "first" $xtpl->parse("main.first.list"); $xtpl->parse("main.first"); // Äàííûå äëÿ âòîðîãî ñïèñêà $list = array("ðàç", "äâà", "òðè"); foreach($list as $item) { $xtpl->assign(ITEM, $item); $xtpl->parse("main.second.list.item"); } // Ðàçáèðàåì áëîê "second" $xtpl->parse("main.second.list"); $xtpl->parse("main.second"); $xtpl->parse("main"); $xtpl->out("main"); ?>
Для того чтобы реализовать то же самое с использованием библиотеки HTML::Template, придется кое-что уточнить. В этой библиотеке нет понятия именованного блока. Как же разделить пространство действия переменных шаблона? Для этого нужно воспользоваться конструкцией <TMPL_LOOP …>…</TMPL_LOOP>. Внутри этого «цикла» переменные-подстановки являются вложенными в цикл. Воспользуемся этим фактом. Шаблон «list.html» для библиотеки HTML::Template: <ol> <TMPL_LOOP NAME="item"> <li><TMPL_VAR NAME="item"></li>
web </TMPL_LOOP> </ol>
Шаблон «ex_inc2.html» для библиотеки HTML::Template: <html> <head><title>Âëîæåííûå øàáëîíû</title></head> <body> <p>
Подключение одного шаблона в разных местах страницы и с разным заполнением. </p> Ñïèñîê 1: <TMPL_LOOP NAME="first"> <TMPL_INCLUDE NAME="list.html"> </TMPL_LOOP> Ñïèñîê 2: <TMPL_LOOP NAME="second"> <TMPL_INCLUDE NAME="list.html"> </TMPL_LOOP> </body> </html>
Вся тонкость обработки сводится к корректному формированию массивов: #!/usr/bin/perl use strict; # Ïóòü ê êàòàëîãó áèáëèîòåêè use lib $ENV{DOCUMENT_ROOT}."/../lib"; # Ïîäêëþ÷åíèå áèáëèîòåêè use HTML::Template; my $template = HTML::Template->new( filename => "ex_inc2.html", path => $ENV{DOCUMENT_ROOT}."/../lib/data/htmltemp/" ); # Çàïîëíÿåì ìàññèâ äëÿ øàáëîíà list.html my $items = (); push( @{$items}, ({"item" => "one"}, {"item" => "two"}, {"item" => "three"}) ); # Îðãàíèçóåì îáðàìëÿþùèé ìàññèâ äëÿ <TMPL_LOOP NAME=”first”> my $list = (); push(@{$list}, {"item" => $items}); # Àññîöèèðóåì çíà÷åíèå $template->param("first" => $list); # Îáíóëÿåì è çàïîëíÿåì ìàññèâû çàíîâî $items = (); push( @{$items}, ({"item" => "ðàç"}, {"item" => "äâà"}, {"item" => "òðè"}) ); $list = (); push(@{$list}, {"item" => $items}); # Àññîöèèðóåì çíà÷åíèå $template->param("second" => $list); print "Content-type: text/html\n\n"; print $template->output(); __END__
Не так уж и страшно, не так ли? Но обратите внимание на следующий факт. При заполнении конструкции <TMPL_LOOP …> используется не массив, а ссылка на массив. Это важно!
№8(21), август 2004
Заключение Выбирая движок для шаблонов, старайтесь оставить за шаблонами верстку (оформление), а логику – за программой. Не надо перекладывать на шаблоны логику выполнения и ни в коем случае не надо внедрять в шаблоны какиелибо объекты! Все «расширенные» (в сравнении с обычным кодом HTML) возможности шаблонов должны заканчиваться на возможностях: ! пометить точку для вставки (переменная шаблона); ! пометить блок для выбора (именованный IF); ! пометить блок для повтора (именованный LOOP). И даже это избыточная модель! В PHP-ом XTemplate все еще более «доведено до ума»: <ul><!-- BEGIN: menulist --> <!-- BEGIN: link --> <li><a href="{ITEM.URL}">{ITEM.NAME}</a></li><!-- END: link --> <!-- BEGIN: active --> <li>{ITEM.NAME}</li><!-- END: active --> <!-- END: menulist --></ul>
И все! У вас есть блок вида: <— BEGIN: имя —> …<— END: имя —>. На стороне программы вы можете повторить его нужное количество раз или выбрать из двух смежных тот, который нужно вывести, исходя из логики. Для библиотеки Perl HTML::Template считайте именованным блоком конструкцию <TMPL_IF NAME=имя >… </TMPL_IF>. Все, что нужно повторить несколько раз, должно быть заключено в конструкцию вида <TMPL_LOOP NAME=имя> …</TMPL_LOOP>. У вас есть место вставки вида {имя} или явная поблажка {имя_структуры.имя}. И последняя конструкция – включение одного шаблона в другой. По принципу inline-подстановки: {FILE «имя»} или <TMPL_INCLUDE NAME=«имя»>. Класс (движок) шаблонов должен уметь работать с вложениями блоков друг в друга и воспринимать каждый блок как отдельное пространство имен. Для HTML::Template это будет верно, если вы начнете рассматривать хэш, заполняющий цикл как ограничитель такого пространства. Этого достаточно, чтобы сформировать любой вывод. Класс (движок) шаблонов должен позволять рекурсивное включение файлов-шаблонов друг в друга. Это позволит вам создавать отдельные мини-шаблоны для повторяющихся элементов, а не копировать один и тот же HTML-код из шаблона в шаблон. Движок шаблонов должен быть инкапсулирован в отдельный класс-библиотеку. Подключаться, как любой другой модуль-файл, в программу и не требовать сложных действий для инсталляции или добавления в любую систему. Это позволит вам легко переносить и добавлять поддержку шаблонов в любую систему, не занимаясь перестановкой и дополнительной настройкой окружения. Движок также не должен требовать предварительной компиляции шаблонов ни в какое промежуточное представление. Это позволит вносить изменения в оформление именно на уровне шаблонов и полностью отделить HTMLформатирование вывода от программной части.
73
web
ОБРАБОТКА HTML-ШАБЛОНОВ OFF-LINE. ВОЗМОЖНОСТИ И ОГРАНИЧЕНИЯ
АЛЕКСЕЙ МИЧУРИН Сборке HTML-документов по шаблонам посвящено великое множество публикаций самого разного масштаба и качества: от небольших статей до детальных руководств и книг, от дилетантских до весьма профессиональных. Но подавляющее большинство авторов сосредоточивается на online-сборке. Ими рассматривается ситуация, когда на сервере лежат не статические документы, а шаблоны: заготовки и части документов. При запросе клиентом соответствующей страницы на сервере запускается некий механизм, собирающий веб-страницу «на лету» (в реальном масштабе времени) и отправляющий её клиенту. Для решения подобных задач разработано множество инструментов и средств, начиная с несложных и интегрированных глубоко в сервер (например, SSI) и заканчивая многофункциональными самостоятельными модулями и библиотеками с очень богатыми возможностями. Тема on-line-обработки шаблонов действительно очень интересна и воистину неисчерпаема, поскольку в разных условиях оказываются уместны разные подходы. Неудивительно, что так много авторов обращается именно к этой теме. Но я хотел бы уделить немного внимания механизмам off-line-сборки. Возможно, не слишком распространённым термином «off-line-сборка» я буду называть процесс сборки шаблонов на локальной машине, в отсутствие серверного ПО и не для передачи клиенту. В результате такой сборки вы получаете набор статических документов (хотя никто не запрещает использовать, например, SSI-инструкции), готовых к размещению на сервере. Оказывается, концепция сборки документов по шаблону может быть весьма полезна не только при сборке документов «на лету», но и при сборке статических документов. Прежде чем обсудить конкретную реализацию off-lineпроцессора шаблонов, нам необходимо сформулировать критерии, которым он должен соответствовать. Но перед этим давайте определимся, в каких ситуациях off-line-сборка может быть полезна (и для кого написана эта статья).
Когда применима off-line-сборка? Ценность автоматизации сборки статических документов
74
может показаться неочевидной, но это только на первый взгляд. Да, off-line-сборка позволит создавать только статические документы. Приёмы, которые мне предстоит описать, ни в коей мере не связаны с «on-line-движками». Тем не менее, разработка статических документов – неотъемлемый и трудоёмкий процесс, неизбежно сопровождающий создание и поддержку любого веб-ресурса. Облегчению этой задачи и посвящена настоящая статья. А прежде чем привести два примера, замечу, что техника, изложенная здесь, может применяться и для создания частей документов, которые далее будут использоваться как исходные данные для «on-line-движков». Приведу два, как мне кажется, наиболее житейских примера. Практически у любого ресурса есть страницы, которые обновляются редко, но время от времени обязательно обновляются. Это могут быть адреса торговых точек и филиалов, телефоны, расписание работы, информация о сотрудниках, подборки статей и многое другое. Эти страницы, скорее всего, должны быть оформлены в едином стиле с содержимым всего сервера, то есть включать стандартную шапку, элементы навигации и прочее. Невольно появляется мысль использовать для их создания технику сборки по единому шаблону. Вместе с тем, на практике эти страницы несут статическую информацию. Собирать их вновь при каждом запросе не очень разумно. Гораздо рациональней – собрать их единожды и разместить на сервере, а при необходимости вносить малые изменения в шаблоны, легко пересобирать необходимые страницы и размещать их на сервере. Возможно, необходимость проведения таких работ будет возникать раз в несколько лет, но даже редкую работу лучше делать просто, чем сложно. Здесь возникает возражение: такой поход прожорлив до дискового пространства. Соглашусь с этим, но отвечу, что дисковое пространство стоит копейки и обходится гораздо дешевле процессорного времени, за одну только возможность использования коего обычно приходится дополнительно доплачивать. При отказе от on-line-сборки в пользу off-line-сборки мы проигрываем в дисковом пространстве,
web но объем трафика (гораздо более важная характеристика) не меняется, а процессорное время (тоже весьма важная характеристика) существенно экономится. Таким образом, суммарно мы, скорее всего, получим выигрыш. (Вопросы трудоёмкости загрузки большого объёма данных на сервер и другие вопросы я обязательно затрону, но ближе к концу статьи.) Как видите, в описанной ситуации off-line-сборка вполне оправданна, а такая ситуация возникает при поддержке практически любого ресурса в Web. Вторая немаловажная область для применения техники off-line-сборки – разработка. Веб-дизайнер1 должен иметь возможность оперативно глянуть на дело рук своих. Если его работа полностью сосредоточена на разработке шаблонов, сборка которых произойдёт только на сервере, то ему (на локальной машине) затруднительно увидеть результат своих трудов. Сперва он вынужден разместить всё на сервере, а это не только трудно, но и потребует от дизайнера квалификации, не свойственной для него. В этой ситуации было бы удобнее, если бы дизайнер имел под рукой процессор обработки шаблонов и мог быстро пересобрать весь набор документов, легко «играя» палитрами или компоновками материала. То же самое относится и к верстальщику2. Ему было бы нелишне иметь возможность легко оценить, как будет выглядеть его работа в рамках общего дизайна (не «съедут» ли иллюстрации и таблицы, уместны ли цвета и т. п.). Разработка тем более упрощается, если программист, дизайнер и верстальщик пользуются одним набором инструментов и одним форматом шаблонов. Итак, задачи намечены, наметим теперь пути их решения.
Требования к аппарату off-line-сборки Самые общие требования ! Первое требование прозвучит почти комично: наша система сборки должна обеспечивать сборку. То есть она должна давать возможность описать, в какое место обрабатываемого файла содержимое какого файла будет вставляться. Такие места вставки будем называть точками вставки. Получив возможность вставлять некую информацию во многие места многих файлов, мы получили возможность хранить одну информацию в одном месте. Это позволит и изменять (при необходимости) одну информацию в одном месте. То есть при последовательном проведении этой техники в жизнь вы сможете поменять название раздела в одном-единственном файле, одним касанием <Enter> пе1
2
3
ресобрать документы, и название раздела изменится во всех оглавлениях, заголовках, элементах <title>...</title> и во всех других местах, где встречалось это название. Так же можно будет менять цвета и другие элементы оформления. То есть ресурс становится предельно управляемым во всех отношениях. ! Второе требование: программный код (код, выполняющий сборку) должен быть полностью отделён от кода шаблонов. Это классическое требование, которое всегда предъявляется к обработчикам шаблонов и нарушается с таким же постоянством. Иначе и быть не может. С одной стороны, процессор шаблонов должен быть управляемым, то есть программируемым, иначе он сможет собирать только один документ по одному шаблону и утратит всяческий смысл. Но, с другой стороны, программный код в шаблонах должен, как мы только что сказали, отсутствовать. Этот парадокс каждый из разработчиков решает согласно своим задачам. Наши задачи таковы, что в шаблонах должен быть минимум управляющих конструкций. Мы намеренно не будем реализовывать ни переменных, ни условных переходов. Естественно, у нас не будет ни циклов (частный случай условных переходов), ни вычислений (направленных на обработку переменных)3. Такие строгие меры продиктованы жизненной необходимостью. Помните про первое применение – долгосрочную поддержку ресурса? Шаблоны должны быть таковы, чтобы можно было легко вспомнить, что тут к чему, даже после полного забвения, которое приходит обычно уже через пару месяцев после окончания активной фазы разработки. При этом мы не должны наивно рассчитывать, что кто-то станет документировать сбою работу. С шаблонами должен легко управляться и дизайнер, не имеющий никакого представления о программировании, переменных, управляющих конструкциях и прочем. ! Программа обработки шаблонов должна быть максимально переносима. Её работоспособность не должна зависеть от ОС, ПО, дополнительных модулей и расширений. У дизайнера и верстальщика должна быть возможность просто носить её на flash-носителе вместе с рабочими материалами. Установка на новую машину должна быть упрощена до полной незаметности. ! Аппарат сборки должен выдавать ясный и простой протокол работы, позволяющий легко понять (или восстановить в памяти) ход сборки и источники той или иной информации, а при необходимости легко локализовать ошибки. ! Конечно, шаблоны должны обрабатываться рекуррентно, допуская вставку шаблона в шаблон.
Во избежание разночтений поясню, что под «веб-дизайнером» я буду понимать субъекта, разрабатывающего все элементы, общие для всех (или для больших групп) страниц ресурса. Это может быть и не один человек, а команда, каждый представитель которой специализируется на одном из аспектов веб-дизайна: графика, HTML, CSS, JavaScript... В таком случае, внедрение механизма, предлагаемого в статье, напрямую затронет только разработчика HTML-кода. Уточню, что под «верстальщиком» я буду понимать человека (или команду), обеспечивающего информационное наполнение каждой конкретной страницы. Верстальщик получает материалы в естественном для заказчика формате и адаптирует их для размещения на веб-страницах. Это не совсем так, что-то сделать всё-таки придётся. Читайте дальше.
№8(21), август 2004
75
web Требования к шаблонам ! Синтаксис оформления точек вставки должен допускать достаточную гибкость. Разработчик должен иметь возможность сделать точку вставки и заметной, и компактной, в зависимости от конкретной ситуации. ! Точки вставки не должны быть похожи на HTML-теги. Это позволит беспрепятственно применять программы проверки HTML-кода к отдельным шаблонам и легко локализовать ошибки типа забытых закрывающих тегов или неверных атрибутов.
Возможности управления ходом сборки Пришло время компромиссов и разрешения парадокса, о котором я говорил. Я уже вижу, как взгрустнули программисты. Конечно, отказ от переменных, условных переходов, вычислений и циклов – тяжёлая утрата. Можно ли сочетать предельную простоту кода и достаточную управляемость? Думаю, что да. Я бы назвал предлагаемое решение «условной вставкой». Каждый шаблон обрабатывается с определённым параметром. Фактически параметр – это единственная переменная. Но от того, что она одна, ей не нужно имя и синтаксис её использования становится предельно прост и понятен даже человеку, далёкому от программирования (особенно, если не называть её словом «переменная»). В зависимости от этого параметра в шаблон будет встраиваться содержимое того или иного файла. Итак, ещё одно требование: ! Обработчик должен поддерживать сборку с параметром и условную вставку. Пока, пожалуй, хватит требований, мелкие замечания и уточнения разберём на конкретном примере.
Пример реализации off-line-процессора Давайте теперь рассмотрим программу, хоть и далёкую от совершенства и законченности, но компактно реализующую изложенные выше идеи и вполне работоспособную, в чём у нас будет возможность убедиться в следующем разделе (где будет создан небольшой сайт из шести страниц).
коллекции DJGPP4. У пользователей UNIX проблем с установкой Perl вообще не возникнет, поскольку Perl является неотъемлемой частью подобных систем. Можно было бы поступить ещё концептуальней: написать программу на чём-нибудь компилируемом, например, на C. Такая программа не требовала бы даже интерпретатора. Возможно, это хорошая идея. Я не пошёл по этому пути по двум причинам. Во-первых, разница не так принципиальна: избавились от интерпретатора – понадобился компилятор. Во-вторых, код получился бы не такой компактный, и приводить его в статье было бы не так удобно. Последнее обстоятельство, как вы понимаете, не должно останавливать вас.
Осмотр кода и формат сценария сборки Давайте пробежим глазами строки кода, который у меня получился: 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
#!/usr/bin/perl -w #use strict; my $INPUT_PATH ='<input/'; my $OUTPUT_PATH='>output/'; sub assemble_step { my ($level, $file, $key)=@_; print (('. 'x$level).$file.':'.$key."\n"); local $/; open FH, $INPUT_PATH.$file or die $file.' : '.$!; my $text=<FH>; close FH; $level++; $text=~s{\(##[#\s]*([^#\s]+)[#\s]*##\)}{ my ($fn, $k)=($1, $key); $fn=~s/\?/$key/g; $fn=~s/\(([^)]+)\)/($key eq $1)?'yes':'no'/ge; ($fn, $k)=($1, $2) if ($fn=~m/^([^:]+):(.+)$/); assemble_step($level, $fn, $k); }ge; return $text; } sub assemble { my ($output, $input_root, $init_key)=@_; my $text=assemble_step(0, $input_root, $init_key); open FH, $OUTPUT_PATH.$output or die $output.' : '.$!; print FH $text; close FH; } while (<>) { assemble(split) }
Выбор языка При выборе языка программирования я остановился на вездесущем Perl, не устояв перед его широкими возможностями и богатством платформ, на которые он перенесён. Кроме того, давайте не будем использовать в нашей программе внешних модулей. Такой код можно будет запустить под Windows, даже не устанавливая громоздкий ActivePerl, а воспользовавшись одним только perl.exe из 4
5
76
Я буду предполагать, что читатель знаком с Perl, и ограничусь только краткими пояснениями. Тело программы состоит из одной строки 34 (если не считать объявления двух глобальных переменных в строках 5 и 6)5. Здесь в цикле while одна за другой обрабатываются строки входного файла. Чтобы отличать его от шаблонов, давайте назовём его громким словом «сценарий сборки» или просто «сценарий».
Речь идёт о дистрибутиве, доступном, например, по адресу: ftp://ftp.cpan.org/pub/CPAN/ports/msdos/LMOLNAR/perl542b.zip. Там вы найдёте perl.exe версии 5.004_02 от DJGPP, размером всего 266 Кб. Там же, в архиве CPAN, можно найти множество различных сборок Perl для различных платформ (ftp://ftp.cpan.org/pub/CPAN/ports). Многие из них обладают существенными ограничениями, которые касаются сетевых возможностей, возможностей работы с базами данных, ограниченным набором модулей, но эти ограничения не повлияют на работоспособность кода, приводимого в настоящей статье. Неприятным сюрпризом может стать только то, что некоторые сборки для DOS не поддерживают длинных имён. Это сделано специально, чтобы вам было легче развивать программу, я уже сказал, что приводимая здесь реализация скорее учебная, нежели боевая.
web Каждая строка сценария сборки разбивается на поля (разделители – любые пробельные символы в любом количестве). Значения этих полей передаются функции assemble, которая инициализирует и запускает процесс сборки шаблона, а результат сборки записывает в назначенный файл. Первое поле каждой строки сценария задаёт имя собираемого файла (имя файла, в который будет помещён окончательный результат сборки). Второе поле – имя корневого шаблона, с которого начнётся сборка. Третье поле – параметр, с которым будет собираться корневой шаблон. Функция assemble_step, выполняющая саму сборку, вызывается рекуррентно (вы видите, что она вызывается из самой себя в строке 21). Её аргументы таковы: первый – уровень вложенности (глубина рекурсии); второй – имя файла шаблона, который необходимо обработать; третий – параметр сборки, с которым необходимо обработать шаблон. Первое, что делает функция assemble_step, – выдаёт строку протокола сборки (строка 10 листинга). Начальные символы строки задают отступ, показывая глубину рекурсии (чем больше уровень вложенности, тем больше отступ). Далее следует имя файла шаблона и параметр, с которым его предстоит обработать. Далее (строки 11-15) в переменную $text читается шаблон, глубина вложенности увеличивается на единицу (это значение будет передано «дочерним» assemble_step) и начинается самое интересное – обработка шаблона. Сердцем нашего процессора шаблонов, как вы уже успели догадаться, являются строки листинга с 16 по 22, представляющие собой просто одно выражение глобального поиска и замены. Здесь-то и происходит сборка. Что же мы ищем и на что заменяем?
Точки вставки Ищем мы, конечно, точки вставки. Как видите (строка 16), оформлены они у нас будут достаточно гибко: открывающая круглая скобка; два знака #; любое количество пробелов6 или знаков #; некоторое количество знаков, отличающихся от пробелов и #, они сохранятся в переменной $1 и будут использованы для определения, что именно необходимо вставить в данную точку; далее следует снова произвольное количество пробелов и символов #; и наконец завершающие два # и закрывающая скобка. Я не навязываю читателю именно такой стиль, просто мне он кажется удобным. Знак # выбран потому, что его легко заметить в тексте. А формат позволяет оформлять точки вставки и компактно (полезно, когда их много): (## NAME ##)
И громоздко (полезно, когда точек вставки мало и их надо выделить): (############### NAME ###############)
или 6
(## ## ##
NAME
## ## ##)
Точки вставки, таким образом, можно оформлять в духе комментариев C с той лишь разницей, что начальный и конечный символы у нас разные и написание регулярного выражения для поиска точек вставки упрощается.
Вставка Что мы будем заменять – теперь понятно, давайте разберёмся с тем, чем мы будем заменять. В качестве имени файла, который будет вставляться в данную точку, будет использоваться наша переменная $1, но... после небольшой «доработки»: ! Первым делом (строка 18) все знаки ? в потенциальном имени файла заменятся на текущее значение параметра сборки. То есть если код: <html>(## text-for-? ##)</html>
собирается с параметром index, то между тегами <html> и </html> будет вставлен шаблон из файла text-for-index. Если же этот же шаблон собирается с параметром paper, то вместо (## text-for-? ##) будет вставлен шаблон из файла text-for-paper. ! Далее (в строке 19) в потенциальном имени файла отыскиваются все конструкции в круглых скобках. С ними производится следующая замена: если строка в скобках совпадает с текущим параметром сборки, то скобка заменяется на строку «yes», в противном случае она заменяется на строку «no». Поясню на примере шаблона: <html>(## is-i-home-(index).txt ##)</html>
Если этот шаблон собирается с параметром index, то будет использован файл is-i-home-yes.txt. Во всех других случаях, напротив, будет использован is-i-home-no.txt. ! Затем (строка 20) проверяется, содержит ли потенциальное имя файла двоеточие. Если ответ утвердительный, то часть имени слева от двоеточия используется в качестве имени файла, правая часть имени будет использована как новый параметр сборки. Это даёт возможность «подменять» параметр сборки в процессе самой сборки и собирать разные части документа с разными параметрами. Обратите внимание: подмена носит локальный характер, шаблон-«родитель» о ней ничего не знает, его обработка продолжается с тем же параметром. Подмену замечает только шаблон-«потомок» (и его потомки, если не произойдёт ещё одной подмены). ! Наконец (строка 21) мы вызываем рекуррентно assemble_step, которая производит сборку того, что нам в результате оказалось нужно (и, возможно, с новыми параметрами), и помещаем результат в точку вставки.
Пробелами в данном случае считаются не только истинные пробелы и табуляции, но и символы новой строки CR и LF.
№8(21), август 2004
77
web Пример сборки ресурса Давайте теперь рассмотрим пример создания простенького веб-ресурса с помощью нашей «кухни», и пусть его простота нас не смущает, ведь любой сложный ресурс всегда можно разбить на простые части (речь, конечно, по-прежнему идёт только о статических ресурсах). На нашем сервере будет три страницы: index.html, contact.html и about.html. Будем называть их основными. У каждой из них будет ещё и версия для печати: indexprint.html, contact-print.html и about-print.html, соответственно. Итого шесть страниц, все они показаны на рисунке с указаниями имён файлов. Сценарий сборки будет таков7:
01: 02: 03: 04: 05:
<html> <head> <title>(## ?-head ##)</title> </head> <body>
В этом шаблоне есть тоже одна точка вставки. В неё вставляется информация одного из трёх файлов index-head, contact-head и about-head. При сборке index.html (с параметром index) используется первый – index-head, для contact.html и about.html – contact-head и about-head соответственно. В файлах *-head, как вы уже поняли, содержатся заголовки соответствующих страниц. Например, в index-head: 01: Î íàñ
01: 02: 03: 04: 05: 06:
index.html contact.html about.html index-print.html contact-print.html about-print.html
skeleton skeleton skeleton skeleton-print skeleton-print skeleton-print
index contact about index contact about
Каждому собираемому html-файлу, как вы видите, здесь отвечает своя строка, в которой, кроме имени файла-мишени, указываются шаблон и параметр. Как вы поняли, все основные станицы будут собраны по шаблону skeleton, а страницы для печати – по шаблону skeleton-print.
Сборка основных страниц Рассмотрим сперва сборку основных страниц: index.html, contact.html и about.html. Согласно первым трём строчкам сценария сборки, все они собираются по общему шаблону skeleton, варьируется только параметр (исключительно для простоты чтения он тождественен имени соответствующего собираемого файла). Этот раздел будет фактически посвящён рассмотрению шаблона skeleton: 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
(### body-open ###) (### sep #########) <table width="100%"> <tr> <td><img src="../img/title.gif"></td> <td align="right"><img src="../img/banner.gif"></td> </tr> </table> (####### sep #######) <table> <tr valign="top"> <td>(## toc ##)</td> <td> <big><b>(## ?-head ##)</b></big> <br> <a href="(## ?-url-print ##)"><b><font size="1"> âåðñèÿ äëÿ ïå÷àòè</font></b></a> <br>(## ?-text ##) </td> </tr> </table> (### sep ##########) (### body-close ###)
В его первой строке находится точка вставки шаблона body-open: 7
78
Во второй строке шаблона skeleton (мы продолжаем его рассмотрение) – точка вставки шаблона sep: 01: 02: 03: 04: 05: 06: 07:
<table width="100%"> <tr bgcolor="#cccccc"> <td align="right"> <font size="1" color="#999999">ÑÒÅËÜÊÈ Inc.</font> </td> </tr> </table>
Здесь нет ничего хитрого, это чисто декоративный элемент – горизонтальная серая полоса. На наших страницах их три на каждой (см. рисунок). В строках 3-8 шаблона skeleton – код, отвечающий за логотип и баннер. В строке 9 повторяется вставка декоративного разделителя sep. В строках 10-21 описана таблица, несущая собственно тело страницы. В таблице только две ячейки (одна строка, два столбца). Левая содержит навигационное меню (шаблон toc, подключаемый в строке 12), к нему мы вернёмся чуть позже. В правой ячейке содержится несколько точек вставки. В первой из них подключается один из уже знакомых нам шаблонов *-head, соответствующий параметру сборки (строка 14). Это заголовок. В строке 16 создаётся ссылка на страницу с версией для печати. Адрес этой страницы берётся из файлов index-url-print, contact-url-print и about-url-print. Например, index-url-print содержит: 01: index-print.html
Единственное, о чём здесь следует упомянуть: файлы *-url-print не должны содержать ничего, кроме адресов. То есть в них не должно быть пробелов, табуляций, символов LF и CR и других невидимых символов. И наконец в строке 18 шаблона skeleton мы подключаем текст страницы из файлов (которые могут оказаться, в свою очередь, шаблонами) index-text, contact-text и abouttext. Так, например, в файл index.html вставляется текст из файла index-text:
Все файлы, упомянутые в настоящей статье, можно скачать одним архивом на сайте журнала: http://www.samag.ru/ source. В архиве также содержится программа сборки на Perl и bonus track: дополнительные файлы, необходимые для сборки веб-страницы, содержащей все страницы ресурса; как раз эта страница и является иллюстрацией статьи (readme прилагается).
web 01: 02: 03: 04:
Êîðïîðàöèÿ &laquo;ÑÒÅËÜÊÈ Inc.&raquo; ïðîèçâîäèò íåäîðîãèå, íî âûñîêîêà÷åñòâåííûå êîðïîðàòèâíûå ñòåëüêè. Ìû âñåãäà äåëàåì óïîð íà èìèäæåâûå ýëåìåíòû, íî ïðåäëàãàåì è óíèâåðñàëüíûå ãîòîâûå ðåøåíèÿ...
Шаблон skeleton заканчивается вставкой ещё одного декоративного разделителя sep и закрывающими тегами, вынесенными в отдельный файл body-close.
Понятно, что каждый из них содержит одну строчку. Например, index-url содержит: 01: index.html
Перейдём теперь к самому интересному.
Сборка оглавления Сборка страниц для печати Ещё проще устроена сборка страниц для печати. Как вы помните, для них используется общий базовый шаблон skeleton-print: 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15:
(### body-open ###) <hr> <big>(## ?-head ##)</big> <hr> <small>&copy; ÑÒÅËÜÊÈ Inc.</small> <hr> (############## ### ?-text ### ##############) <hr> <small>àäðåñ ðåñóðñà <u>http://www.stelki.biz/(## ?-url ##)</u></small> <hr> <small><a href="(## ?-url ##)">íàçàä</a></small> (### body-close ###)
В начале и в конце этого шаблона мы видим уже знакомые body-open и body-close. Текст заголовка берётся всё из тех же файлов *-head, а текст – из файлов *-text (обратите внимание, как выделена точка вставки текста). Единственные новые шаблоны, с которыми мы тут столкнулись, содержат адреса возврата на исходные страницы (не предназначенные для печати). Я говорю о шаблонах index-url, contact-url и about-url (они фигурируют в строках 12 и 14).
№8(21), август 2004
Вернёмся к шаблону toc, отвечающему за навигационное меню. Как вы уже видели на рисунке, оглавление у нас сделано в лучших традициях гипертекста: ни одна страница нигде не ссылается на саму себя. Для этого нам понадобился довольно мудрёный шаблон toc: 01: 02: 03: 04: 05:
<table> (## toc-(index):index ##) (## toc-(about):about ##) (## toc-(contact):contact ##) </table>
Попробуйте ответить на вопрос, сколько шаблонов используется шаблоном toc? Правильный ответ: два. Да! Нам понадобится всего два шаблона. Первый toc-yes: 01: <tr> 02: <td nowrap bgcolor="#cccccc"><b>&bull; 03: (## ?-head ##)</b></td> 04: </tr>
Второй toc-no: 01: 02: 03: 04: 05:
<tr> <td nowrap><b>&bull; <a href="(## ?-url ##)">(## ?-head ##)</a> </b></td> </tr>
79
web Шаблон toc-yes описывает одну строку таблицы, в единственной ячейке которой находится название соответствующего раздела. Шаблон toc-no описывает такую же конструкцию, но ячейка уже не подкрашена, а текст является ссылкой. Я думаю, читатель уже начал догадываться, как всё это работает. Например, при сборке документа about.html шаблон toc вызывается с параметром about. Во второй его строке будет подключён файл toc-no, так как параметр about не совпал с круглой скобкой (index). Этот шаблон, в свою очередь, вызывается с подменой параметра на index, и в нём будут подставлены файлы index-rul и index-head. То есть строка с названием первой страницы станет ссылкой на первую страницу. Во второй строке шаблона toc будет производиться вставка шаблона toc-yes, который не формирует ссылки. То есть мы добились своего – страница не ссылается на саму себя. Третья вставка (в четвёртой строке шаблона toc) пройдёт аналогично первой. Окончательно разобраться в деталях происходящего нам поможет протокол сборки.
Протокол работы Мы ещё ничего не сказали о протоколе (необходимость коего была оговорена в начале статьи), который выдаётся нашей системой сборки. Я не буду приводить его весь. Вот часть, отвечающая за сборку документа index.html:
мы не использовали шаблоны и захотели бы изменить название раздела, то нам пришлось бы вносить исправления в семи местах. Наш механизм в семь раз облегчит задачу. Обработчик позволяет делать очень многое и не уступает большинству подобных инструментов. Фактически любое изменение стиля или содержания сайта выполняется редактированием всего одного файла. Эта управляемость идёт на пользу не только отдельным разработчикам, но и облегчает взаимодействие в команде. Так, дизайнер мог вести разработку так, как это делали мы. Но программист перед окончательным размещением документов на сервере может просто поменять в одном файле (skeleton) код, отвечающий за баннер, и одним движением подключить баннерную систему на всех страницах ресурса. ! Шаблоны допускают весьма гибкое форматирование точек вставки, которое может обеспечить и наглядную, и компактную запись. ! Обработчик выдаёт протокол сборки, позволяющий легко понять коллегу или себя. ! Обработчик компактен и элементарно переносится на любую машину, работающую под управлением любой ОС. Но не будем долго распространяться о мощи шаблонного подхода. Давайте перейдём к недостаткам.
Слабые стороны подхода 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
skeleton:index . body-open:index . . index-head:index . sep:index . sep:index . toc:index . . toc-yes:index . . . index-head:index . . toc-no:about . . . about-url:about . . . about-head:about . . toc-no:contact . . . contact-url:contact . . . contact-head:contact . index-head:index . index-url-print:index . index-text:index . sep:index . body-close:index
Если вы внимательно читали весь предыдущий текст, то он будет вам знаком и понятен. Если же у вас осталось некоторое недопонимание, то он поможет разобраться.
Результаты Достижения Итак, что нам удалось сделать? Перечислю кратко основное. ! Мы написали и успешно применили обработчик шаблонов, синтаксис которых настолько прост, что может быть освоен даже абсолютно не знакомым с программированием человеком, каковыми обычно являются веб-верстальщики и веб-дизайнеры. Вместе с тем, наш аппарат позволяет весьма гибко манипулировать шаблонами. Даже в нашем маленьком примере вставка шаблона index-head происходит семь раз. Это значит, что если бы
80
Когда вы принимаете решение использовать или не использовать некий подход (в частности, описываемый в этой статье), важно знать не только достоинства и потенциальные возможности, но и недостатки подхода. Итак, какие есть недостатки? Какие из них можно преодолеть, а какие – нельзя?
Относительно работы обработчика Приведённая здесь реализация практически не содержит механизмов обработки нештатных ситуаций. Естественно, было бы полезно добавить следующее: ! Конечно, наш код допускает множество очевидных косметических усовершенствований: ограничение глубины рекурсии и защиту от зацикливания, проверку правильности сценария сборки... Но сосредоточимся на более существенных моментах. ! Было бы полезно реализовать проверку правильности конструкций, описывающих имя файла в точках вставки. Реакция программы на нештатные ситуации типа вложенных скобок или множественных двоеточий должна быть разумна. ! Программа должна корректно себя вести, если требуемый файл не существует. Как? Решать вам. Может быть, вы предпочтёте аварийную остановку (сейчас сделано именно так) или захотите, чтобы эта ситуация была бы проигнорирована. Возможно, было бы полезно реализовать поиск входных файлов по нескольким директориям, организовать обработку переменной среды, аналогичной PATH, или вынести информацию о возможных путях к файлам в сценарий сборки.
web Плодотворной может оказаться идея «аварийного шаблона», который вставляется в те точки, которые не были корректно обработаны. ! Было бы очень уместно сделать кэширование считанных файлов. Тем более, что это совсем не трудно. ! После того как вы сделаете кэширование, можно очень легко сделать предопределённые шаблоны. Например, шаблон DATE может содержать дату сборки. ! Очень полезной была бы и возможность регламентировать дополнительные обработки (или не обработки) файлов. Например, можно сделать, чтобы файлы с расширениями .file не обрабатывались как шаблоны, а их содержимое вставлялось «как есть». И наоборот, файлы с расширениями .quot проходили бы дополнительную обработку механизмом HTML-квотирования, заменяющим «&» на «&amp;» и так далее. Здесь тоже возможны варианты: должен ли, например, подвергаться квотированию шаблон, вставляемый в квотируемый шаблон? ! Было бы заманчиво хранить информацию с разными именами в одном файле, чтобы не заводить множество маленьких (как это получилось у нас с файлами заголовков и адресов). Это, правда, потребует некоторого усложнения «адресации» данных. Обойтись просто именем файла, как сделали мы, будет уже нельзя. Все ли в вашей команде одобрят подобные нововведения?
Относительно загрузки на сервер Я обещал вернуться к вопросам загрузки. Действительно, идея собрать все документы одним махом весьма заманчива, пока вы не столкнётесь с необходимостью загрузить их на сервер. Что здесь можно сказать? Во-первых, для части администраторов эта проблема не будет так остра, потому что большинство изменений всё-таки не будут затрагивать все документы и, конечно, не будут производиться слишком часто (иначе вам действительно следует выбрать для сопровождения вашего ресурса другой инструментарий, реализующий on-lineсборку). Во-вторых, если вам придётся всё-таки решать эту задачу, то у вас будет по крайней мере два пути решения. Можно написать CGI-сценарий, принимающий файлы в сжатом виде и распаковывающий их на сервере. Так вы сократите трафик в несколько раз. С другой стороны, можно написать сценарий, производящий сборку и размещение на сервере, а передавать ему надо будет только шаблоны. Это может очень сильно помочь сократить трафик, но создание такого механизма потребует от вас нешуточных усилий по обеспечению безопасности.
Усовершенствования, которые сложно сделать
! Когда начинаешь заботиться о «самодокументируемости» подобного сборщика, может появиться мысль: пусть он дополняет документы HTML-комментариями. Сделать это корректно гораздо сложнее, чем кажется на первый взгляд. Вы видели сами, что мы вставляем шаблоны и внутрь тегов. Если включить аппарат автоматической вставки комментариев, то код:
после обработки может превратиться в: <a href="<!— /file 'url' —>index.html<!— file 'url'/ —>">
что, естественно, не будет работать, как: <a href="index.html">
! Процесс сборки, на первый взгляд, напоминает работу утилиты make. Возникает искушение реализовать нечто подобное, избежав ненужных действий при внесении в шаблоны небольших изменений. Это тоже трудно сделать, так как для определения того, какие файлы нуждаются в повторной обработке, надо знать всё «дерево сборки» – все зависимости файлов. В явном виде они нигде не содержатся. Так что, если не сборка, то хотя бы просмотр всех файлов-шаблонов мне представляется неизбежным. ! Неразумным кажется и то, что мы всегда собираем все файлы проекта. Но отказ от этой концепции потребует усложнения синтаксиса командной строки. Или нам придётся придумать какой-то другой способ указать, что именно нам надо. Мой опыт показывает, что веб-дизайнеры этим не пользуются, предпочитая подождать полсекунды. Иногда я думаю, что они в чём-то правы. Кроме того, появление подобной возможности резко сузит возможности создания предопределённых шаблонов, о которых говорилось выше.
Усовершенствования, которые не следует делать И наконец, я хотел бы обозначить ряд усовершенствований, которые, на мой взгляд, не следует вносить, несмотря на их кажущуюся необходимость и заманчивую простоту. ! Вам может показаться, что не хватает возможности подставлять не файл, а сам параметр сборки. Задумайтесь, перед тем как реализовывать и эксплуатировать эту идею! Поверьте, на этой дороге вы не найдёте счастья. У информации должно быть имя и должно быть значение, нельзя объединять эти две материи. ! Не следует усложнять функциональность – вы потеряете прозрачность и читаемость и усложните код, внедрённый в ваши шаблоны. Если же вам понадобились циклы или условные переходы, лучше воспользуйтесь готовым решением, трезво взвесив все «за» и «против». Вероятно, новый инструмент потребует от вас установки сервера на машину дизайнера (или на все машины дизайнеров), не исключено, что ваш дизайнер может воспринять в штыки всё это богатство возможностей. В любом случае это будет другой обработчик шаблонов и уже совсем другая история.
<a href="(## url ##)">
№8(21), август 2004
81
образование
АВТОМАТИЗАЦИЯ ПРОЦЕССОВ В СЕТИ ИВАН КОРОБКО В повседневной работе системному администратору необходимо многократно выполнять одни и те же рутинные действия. Для облегчения собственного бытия имеет смысл автоматизировать следующие процессы: управление сетевыми ресурсами (автоматическое отключение/подключение сетевых принтеров и дисков в зависимости от членства пользователей в соответствующей группе безопасности), конфигурирование рабочей станции, сбор информации об аппаратном и программном обеспечении рабочих станций (решение задачи инвентаризации). Так или иначе, решение задач этого круга сводится к чтению/записи данных реестра. Управление реестром осуществляется с помощью нескольких инструментов, которые будут рассмотрены в настоящей статье. Основными из них являются сценарии регистрации пользователей в сети (сценарии загрузки или скрипты) и групповые политики. Системный администратор не может реализовать весь желаемый функционал, используя только сценарии загрузки, поскольку с их помощью невозможно безопасно выполнить изменения в ветвях реестра HKLM и HKU, так как это только сценарии, работающие с правами администратора. Однако, примененяя групповые политики, стартующие каждый раз при регистрации пользователя в сети, используя административные шаблоны на основе реестра, можно безопасно внести изменения в ветви реестра HKLM и HKU. Политики распространяются на пользователей, являющихся членами группы. Системный администратор может самостоятельно создать файлы – административные шаблоны, содержащие групповые политики (файл с расширением ADM), и применять их к пользователям, входящим в группу/домен (domain), подразделение (Organization Unit, OU). Таким образом, групповые политики и сценарии регистрации пользователей в сети органично дополняют друг друга. Далее рассмотрим подробнее круг вопросов, касающихся создания и применения сценариев загрузки и групповых политик, но сначала немного о реестре.
Реестр Это иерархическая база данных, содержащая настройки аппаратного и программного обеспечения компьютера. Для получения высокой скорости доступа к записям реестра информация в нем хранится в двоичном формате, а сам реестр состоит из нескольких файлов. В Microsoft Windows 3.x все настройки программного обеспечения располагались в файлах инициализации, которые имели расширение INI. Вся конфигурационная информация располагалась в двух файлах: SYSTEM.INI и 1
84
WIN.INI. При установке любого приложения все его настройки сохранялись в одном из этих файлов. Приложения пользовались небольшим количеством параметров. Главная причина – размер INI-файла, размер которого не должны превышать 64 Кб. Чтобы обойти эти ограничения, для каждой программы создавался свой INI-файл. Со временем из-за большого количества конфигурационных файлов производительность операционной системы значительно понизилась. В 1993 году была создана операционная система Microsoft Windows NT, в которой множество INI-файлов было заменено единой базой данных – реестром. С точки зрения файловой системы реестр представляет собой файл с расширением DAT. Для Windows NT/200x реестр хранится в файле NTUSER.DAT, который находится в каталоге %Windir%\Profiles1. В Windows 9x реестр состоит из двух файлов: USER.DAT и SYSTEM.DAT, которые хранятся в каталоге %Windir%. USER.DAT содержит настройки индивидуального пользователя, а SYSTEM.DAT – настройки компьютера. Реестры Windows 9x, NT, 2000 несовместимы друг с другом, однако идея построения реестров едина.
Ветви реестра Реестр состоит из разделов верхнего уровня, называемых кустами (hives): ! HKEY_CLASSES_ROOT (HKCR); ! HKEY_CURRENT_USER (HKCU); ! HKEY_LOCAL_MACHINE (HKLM); ! HKEY_USER (HKU); ! HKEY_CURRENT_CONFIG (HKCC). Структура реестра такова: в каждом из кустов находятся ключи, в которых содержатся параметры, имеющие значения. В разделе HKLM находится информация об аппаратном и программном обеспечении, а также сведения о системе безопасности. Этот раздел является одним из самых больших. Раздел HKCR является виртуальной ссылкой на раздел HKLM\Software\Classes. В нем содержатся сведения обо всех расширениях файлов, определениях типов, ярлыках, привязке, классах идентификаторов и т. д. Раздел HKU включает в себя настройки пользователя по умолчанию, в которые входят описания переменных среды, цветовых схем, шрифтов, сетевых настроек и т. д. Во время регистрации нового пользователя на рабочей станции, на жестком диске для него создается новый профиль. Настройки, содержащиеся в профиле, копируются из куста HKU.
В Microsoft Windows принято использовать переменные среды. %WinDir% содержит полный путь к каталогу, в котором установлена ОС, например, C:\Windows.
образование Изменения в HKU и HKLM можно сделать только с помощью утилиты REGEDT32.EXE в том случае, если у вас ОС Windows 2000, и REGEDIT.EXE – если Windows XP. Пользователь, от имени которого запускаются эти утилиты, должен обладать правами системного администратора. Раздел HKCU содержит сведения о текущем пользователе и имеет название, соответствующее значению идентификатора безопасности (SID) данного пользователя. Каждый раз при перезагрузке компьютера HKCU создается заново. Раздел HKCC является ссылкой на текущий профиль оборудования, хранящийся в HKLM. С помощью профиля оборудования определяют список устройств, драйвера которых будут подгружены в данном сеансе работы пользователя. Профили изначально предназначены для переносных компьютеров. Раздел HKDD (HKEY_DYN_DATA) не хранится в реестре, а динамически создается при загрузке операционной системы. В нем содержатся сведения о самонастраивающихся устройствах (Plag-and-Play). Как и любая база данных, реестр поддерживает несколько типов данных: Òàáëèöà 1
Windows 9x поддерживает следующие типы параметров: REG_BINARY и REG_DWORD, REG_SZ и REG_NONE. Групповые политики, описанные в ADM-файлах, могут производить операции только со следующими типами данных : REG_SZ, REG_EXPAND_SZ, REG_DWORD.
шения затрат на администрирование и конфигурирование рабочих станций. Перечислим некоторые задачи, которые могут быть решены с помощью сценария загрузки: ! Инвентаризация. Включает в себя сбор информации о регистрирующемся в сети пользователе, рабочей станции и формировании файла отчета. ! Автоматическое подключение сетевых ресурсов: принтеров и дисков. ! Автоматическое конфигурирование рабочих станций. ! Обеспечение интерактивности работы скрипта. В ходе выполнения скрипта на экране отображается соответствующая информация. После его выполнения пользователь может ознакомиться с подключенными ему ресурсами, информацией о его рабочей станции и т. д. Существует несколько языков, предназначенных для создания сценариев загрузки. Среди них стандартными являются сценарии на базе командной строки (файлы с расширением BAT, PIF), Windows Script Host (WSH), Microsoft Java Script (JScript), Microsoft Visual Basic Script Edition (VBScript). Существуют также языки, специально разработанные для создания скриптов, такие как KIXtart, AutoIT, CLRScript, WinBatch. Из всех этих языков рекомендуется остановить свой выбор на языке KIXtart2, поскольку, обладая огромными возможностями, он распространяется бесплатно. KIXtart 4.21 поддерживает 48 команд, 56 макросов-функций, более 100 функций и обладает следующим функционалом: ! вывод информации в виде диалоговых сообщений; ! подключение сетевых ресурсов; ! чтение информации из входного потока; ! расширенная поддержка редактирования реестра; ! поддержка INI-файлов; ! расширенная поддержка операций со строками и массивами; ! сбор информации о пользователе и рабочей станции; ! поддержка OLE-объектов; ! операции с файлами и каталогами; ! создание ярлыков Windows. Необходимо отметить, что сценарии, созданные на VBScript, Jscript, могут быть легко переписаны на KIXtart. Рассмотрим подробнее каждую из задач, решаемую скриптом.
Инвентаризация
Сценарий регистрации пользователей в сети Основной задачей сценария регистрации пользователей в сети, далее скрипта, является автоматизация процессов, связанных с подключением рабочих станций к сети, умень2
Решение задачи инвентаризации состоит из нескольких частей – сбора, записи на носитель в определенном формате и обработки информации. Так или иначе, информация черпается либо из BIOS с помощью программы, либо, что происходит чаще всего, из
KIXtart (http://kixtart.org) – интерпретируемый язык программирования, разработанный в 1991 году для создания сценариев загрузки. Простота, скорость и отсутствие конкурентов быстро сделали его популярным среди администраторов. KIXtart является бесплатным и поставляется вместе с Microsoft Resoure Kit. В настоящее время используется KIXtart ver 4.2х, который поддерживается Microsoft Windows Server 2003, Microsoft Windows XP, Microsoft Windows 2000, Microsoft Windows NT 3.x/4.x, всеми версиями Microsoft Windows 95, Microsoft® Windows 98, Microsoft Windows Millennium.
№8(21), август 2004
85
образование реестра с помощью стандартных средств ОС Window. Решение задачи инвентаризации c помощью WMI было описано в [1].
Подключение сетевых ресурсов Сетевыми ресурсами являются сетевые диски и принтеры.
Подключение сетевых принтеров Вопрос установки, настройки и автоматического подключения сетевых принтеров был подробно освещен в статьях [2], [3].
Подключение сетевых дисков Сценарий загрузки осуществляет подключение сетевых дисков пользователям в зависимости от их членства в группах аналогично подключению сетевых принтеров. Главная особенность данного сценария заключается в том, что в нем реализован механизм подключения различных ресурсов на одну и ту же букву. Необходимо строго следить, чтобы членства пользователей в группах не пересекались. Если это произойдет, что часть необходимых ресурсов не будет подключена. Рассмотрим содержимое конфигурационного файла, который представляет собой текстовый файл с произвольным расширением, например INI. В файле в квадратных скобках перечислены названия разделов, которые включают в себя букву, на которую будут монтироваться ресурс и его порядковый номер, присваивающийся для обеспечения возможности подключать на одну и ту же букву разные ресурсы. Каждый раздел содержит пять параметров: название сервера (SERVER), путь к сетевой папке (SHARE), группы безопасности, членам которых будет подключаться ресурс (ACCESSGROUP1 и ACCESSGROUP2), описание ресурса (DESCRIPTION). Пример файла приведен ниже: Ïðèìåð 1 [L1] SERVER=Main SHARE=Consultant ACCESSGROUP1=everyone ACCESSGROUP2=Âñå DESCRIPTION="Êîíñóëüòàíò+" [W1] SERVER=Second SHARE=work\department1 ACCESSGROUP1=department1 ACCESSGROUP2=department3 DESCRIPTION="Ðåñóðñû îòäåëà 1" [W2] SERVER=Second SHARE=work\department2 ACCESSGROUP1=department2 ACCESSGROUP2= DESCRIPTION="Ðåñóðñû îòäåëà 2"
Таким образом, на основе данных, прочитанных сценарием из примера, всем пользователям будет подключен «Консультант+» на букву «L», находящийся по пути \\main\ consultant. Пользователям, являющимся членами групп departament1, departament2, departament3, будет подключен
86
диск W. Для членов групп departament1, departament3 подключается ресурс по адресу: \\second\work\ departament1, для departament2 – \\second\work\departament2. Сценарий, обеспечивающий автоматическое управление подключением сетевых дисков работает по следующей схеме: ! Чистка локального кэша на рабочей станции, содержащего список групп, в которые входит пользователь. Удаление ветви реестра HKEY_CURRENT_USER\Software\KiXtart. ! Чтение параметрического файла. Данные рекомендуется помещать в соответствующие массивы. Схема чтения конфигурационного файла приведена на рис. 1. ! Отключение всех доступных сетевых дисков. ! Подключение сетевых дисков, на которые данный пользователь имеет права. ! Вывод на экран статистики о подключенных сетевых дисках.
Ðèñóíîê 1
Автоматическое конфигурирование рабочей станции Поскольку скрипт выполняется от имени пользователя, который не обладает правами системного администратора, то изменения могут быть внесены только в ветвь HKCU. В этом разделе хранятся сведения о текущем зарегистрированном пользователе, и он имеет название, соответствующее значению идентификатора безопасности (SID) текущего пользователя. Каждый раз при перезагрузке компьютера раздел создается заново на основе данных, считанных из HKU. Автоматическое конфигурирование ветви HKU выполняется с помощью групповых политик и будет рассмотренно позже. Изменения в ветви HKCU осуществляется с помощью скрипта. Ветвь HKCU не является точной копией HKU. Приведем наглядный пример. После запуска Windows 2k пользователь видит сообщение, в котором предлагается одновременно нажать CTRL+ALT+DEL. Нажав эту комбинацию клавиш, ему предлагается ввести имя учетной записи, пароль. Информация о языковых настройках в этом диалоговом окне, а именно язык по умолчанию и комбинация клавиш, с помощью которой осуществляется переключение между раскладками клавиатуры, хранится в ветви HKEY_USERS\ .DEFAULT\Keyboard Layout.
образование Ïðèìåð 2. Ïî óìîë÷àíèþ óñòàíîâëåí àíãëèéñêèé ÿçûê, ïåðåêëþ÷åíèå ìåæäó ðàñêëàäêàìè êëàâèàòóð îñóùåñòâëÿåòñÿ íàæàòèåì CTRL+SHIFT. Windows Registry Editor Version 5.00 [HKEY_USERS\.DEFAULT\Keyboard Layout\Preload] "1"="00000409" "2"="00000419" [HKEY_USERS\.DEFAULT\Keyboard Layout\Toggle] "Hotkey"="2"
Однако аналогичная ветвь в ветви HKСU (см. пример 3) отвечает за управление языковыми настройками рабочего стола пользователя. В данном случае нет никакой взаимосвязи между настройками ветвей HKU и HKCU. Ïðèìåð 3. Ïî óìîë÷àíèþ óñòàíîâëåí àíãëèéñêèé ÿçûê, ïåðåêëþ÷åíèå ìåæäó ðàñêëàäêàìè êëàâèàòóð îñóùåñòâëÿåòñÿ íàæàòèåì CTRL+SHIFT. Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Keyboard Layout\Preload] "1"="00000409" "2"="00000419" [HKEY_CURRENT_USER\Keyboard Layout\Toggle] "Hotkey"="2"
В качестве примера приведем таблицу, в которой описаны некоторые ключи и соответствующие им параметры, которые можно изменять на всех рабочих станциях домена каждый раз во время регистрации пользователей в сети с помощью скрипта. Приведенный список краток. «В специализированной литературе» и в Интернете читатель сможет найти множество советов по настройке реестра. Òàáëèöà 2
боты скрипта. Основным недостатком этого метода визуализации является полное отсутствие интерактивности работы сценария.
Визуализация скрипта с помощью сторонней надстройки KIXTart в виде DLL-библиотеки Визуализация и интерактивность работы скрипта реализуются с помощью специально созданной для этих целей надстройкой – KIXForms 3.2 или KIXGui 1.1.Обе программы можно загрузить с сайта http://www.kixtart.org. У этих программ есть только один недостаток: для корректной работы визуализационной части необходимо на рабочей станции зарегистрировать соответствующую DLL-библиотеку. Для регистрации этой библиотеки необходимо обладать правами администратора. Конечно, можно создать MSI-архив, который будет централизованно распространяться по сети с помощью групповых политик, но это неудобно. Визуализировать работу сценария, таким образом, выгодно только в небольших сетях с маленьким количеством рабочих станций.
Визуализация работы скрипта c помощью сторонней утилиты, передающей параметры из KIXTart в HTML-файл Этот способ мне кажется наиболее оптимальным для реализации визуализации и интерактивности работы скрипта в крупных сетях, поскольку утилита самодостаточна и представляет собой файл с расширением EXE, который рекомендуется располагать в каталоге Netlogon, вместе со скриптом. В качестве такой утилиты лучше использовать KixWin 1.1 (http://www.kixtart.org). И вызывать ее из скрипта с набором параметров. Затем она передает эти параметры в DHTML-файл. Поскольку файл пишет данные в файл, прежде чем что-либо отобразить на экране, то необходимо обеспечить правами возможность записи данных на диск сервера. Раздробление скрипта на две части является основным недостатком данного варианта. DHTML-файл вместе с сопутствующими ему файлами (GIF, JPEG, CSS) рекомендуется располагать в скрытой сетевой папке. DHTML-файл содержит в себе сценарии, созданные с помощью VBScript или JScript. Приведем синтаксис утилиты и фрагмент DHTML-файла:
Обеспечение интерактивности работы скрипта Сценарии на языке KIXTart можно визуализировать по крайней мере тремя способами: ! с помощью стандартных диалоговых окон; ! с помощью сторонней надстройки KIXTart в виде DLLбиблиотеки; ! c помощью сторонней утилиты, передающей параметры из KIXTart в HTML-файл. Рассмотрим все три способа.
Визуализация работы скрипта с помощью стандартных диалоговых окон Пользователю выводится информация на экран в виде сообщения. Этот метод рекомендуется использовать в начале сценария, чтобы уведомить пользователя о начале ра-
№8(21), август 2004
kixwin "dialog" ["arguments"] ["options"]
Описание параметров:
! “dialog” – строка, содержащая URL, указывающий на HTML-документ.
! “arguments” – строка, содержащая параметры, передаваемые из KIX в HTML. Для разделения параметров в HTML-файле используют строку window.dialogArguments. split(«;»). В данном примере разделителем параметров является «;». ! “style” – строка, которая определяет оформление диалогового окна. Используются один или несколько из следующих параметров стиля: dialogHeight:sHeight
87
образование dialogLeft:sXPos dialogTop:sYPos dialogWidth:sWidth center:{ yes | no | 1 | 0 | on | off } dialogHide:{ yes | no | 1 | 0 | on | off } edge:{ sunken | raised } help:{ yes | no | 1 | 0 | on | off } resizable:{ yes | no | 1 | 0 | on | off } scroll:{ yes | no | 1 | 0 | on | off } status:{ yes | no | 1 | 0 | on | off } unadorned:{ yes | no | 1 | 0 | on | off }
Ïðèìåð 6 @ECHO OFF if c:\%os%==c:\ goto win9x if not c:\%os%==c:\ goto winnt :winnt start /wait Kix32.exe Script.kix goto kix :win9x copy %0\..\win9x\*.dll c:\windows\system /y %0\..\Kix32.exe %0\..\Script.kix goto kix
Логическое разделение передаваемых параметров осуществляется с помощью заранее оговоренного символа. DHTML возвращает в KIX код ошибки после обработки кода в виде целого числа макросу @ERROR в случае успешного завершения операции @ERROR=0. Передача данных с помощью утилиты KIXWIN осуществляется с помощью сценария загрузки следующим образом:
:kix @echo End Of Batch File
Ïðèìåð 4 shell '%0/../kixwin.exe $html "$system_info ^ $hardware_info ↵ ^ Óñòàíîâëåííûå ïðîãðàììû: $en $prog ↵ ^ Ïîäêëþ÷åííûå ñåòåâûå äèñêè:$en $n1 ↵ ^ Ïîäêëþ÷åííûå ñåòåâûå ïðèíòåðû:$en ↵ $n2" "scroll:off;resizable:on"'
Как отмечалось ранее, параметры передаются DHTMLстранице, содержащей вставки на VBScript. Наличие вставок позволяет сделать DHTML-страницу интерактивной для пользователя. Чтение параметров, передаваемых из сценария загрузки, осуществляется по следующей схеме: Ïðèìåð 5 … <Script Language="JavaScript"> var argv; argv = window.dialogArguments.split("^"); document.all.parametr0 = argv[0]; </Script> … Ðèñóíîê 2
Внедрение скрипта в эксплуатацию Скрипт выполняется каждый раз при регистрации пользователя в сети, если он указан в разделе Profile свойствах пользователя (см. рис. 2) службы Active Directory: Users and Computers. Для автоматизации установки KIXtart на рабочих станциях домена предлагается следующее: в папку Netlogon поместить файлы: ! KIX32.EXE; ! SCRIPT.KIX; ! START.BAT ! KIXWIN.EXE ! подкаталог Win9x, содержит файлы KX16.DLL и KX32.DLL В скрытую сетевую папку скопировать DHTML- файл и все сопутствующие ему файлы. В ходе выполнения файла START.BAT определяется тип операционной системы, установленной на рабочей станции, и запускается скрипт. В зависимости от версии ОС происходит копирование файлов, необходимых для поддержки KIX этой операционной системой.
88
! !
! !
Пояснения к синтаксису файла START.BAT: Принцип определения операционной системы основан на том, что в Win9x отсутствует переменная окружения %os%; В Windows 2k переменная %os%=WindowsNT. С помощью строки «start /wait Kix32.exe Script.kix» добиваются последовательной загрузки – сначала скрипт, затем рабочий стол и т. д., а не одновременной. То есть на время выполнения скрипта многозадачность «отключается». Это делается для того, чтобы до окончания действия скрипта дальнейшая загрузка операционной системы не производилась. Для корректной работы Win9x в качестве пути к файлу необходимо указывать «%0\..\filename.ext». Windows XP не воспринимает относительного пути «%0/./», поэтому для Windows семейства 2k необходимо указать только имя файла, который находится в папке Netlogon.
На время выполнения скрипта необходимо скрыть CMDпанель, в которой выполняется cкрипт, и приостановить загрузку рабочего стола до окончания всего скрипта. Этого результата добиваются с помощью групповой политики, распространяющейся на домен («Default Domain Controllers
образование Policy»). В разделе групповой политики «User Configuration» необходимо соответственно включить «Run legacy logon script synhronously» (Запускать сценарий загрузки синхронно) и «Run legasy script hidden» (Запускать сценарий скрыто). Для этого необходимо проделать следующее: ! Зарегистрироваться на сервере с помощью учетной записи, имеющей административные права. ! Загрузить в Active Directory Users and Computers (Start – Programs – Administrative Tools) и войти в свойства контроллера домена (см. рис. 3). Перейти во вкладку вкладку «Group Policy» и зарузить «Default Dоmain Policy» (см. рис. 4). Ðèñóíîê 5
Групповые политики
Ðèñóíîê 3
Групповые политики (Group Policy, GP) представляют собой средство, обеспечивающее централизованное управление настройками рабочих станций, профилями пользователей. С их помощью определяют поведение операционной системы, рабочего стола, безопасности ОС, выключения компьютера, приложений и т. д. Реализации всех возможностей групповых политик добиваются их совместным использованием с Active Directory (AD) при условии, что в качестве рабочих станций используется ОС Windows 2000. Политики могут быть применены к следующим объектам AD: сайт (site), домен (domain), подразделение (Organization Unit, OU). Дополнительно групповые политики могут быть применены к таким объектам, как группа безопасности, соответственно к пользователям, входящим в эту группу. Групповые политики включают в себя следующие компоненты: Òàáëèöà 3
Ðèñóíîê 4
! В загруженной групповой политике (Default Domain Policy) необходимо в «User Configuration» (настройках пользователя) войти в «Administrative Templates» (административные шаблоны). ! Там выбрать раздел «System» (система), вкладку «logon/ logoff» (вход/выход) и включить раннее оговоренные политики (см. рис. 5).
№8(21), август 2004
С помощью политик, как говорилось ранее, можно корректировать только ветви реестра HKLM и HKU. В консоли групповых политик (см. рис. 5), вызываемой с помощью команды GPEDIT.MSC, все политики логически делятся на две час ти: Computer Configuration и User Configuration. В разделе Computer Configuration сосредоточены политики, посредством которых изменяется ветвь реестра HKLM, в разделе User Configuration соответственно HKU. Информация о параметрах и конфигурации групповых политик сосредоточена в HKLM\Software\Policies (предпочтительное расположение) или HKLM\Software\ Microsoft\Windows\CurrentVersion\Policies для раздела Computer Configration; в HKU\Software\Policies (предпочтительное расположение) или HKU\Software\Microsoft\ Windows\CurrentVersion\Policies для раздела User Configration.
89
образование Административные шаблоны. Синтаксис Административные шаблоны представляют собой текстовые файлы с расширением ADM. По умолчанию ADM-файлы находятся в каталоге %WinDir%\INF. Кратко рассмотрим возможности языка, с помощью которого создаются политики безопасности. Синтаксис языка включает в себя следующие ключевые компоненты: ! Комментарии ! Строки ! CLASS ! CATEGORY ! POLICY
Комментарии Комментарии полезно использовать для документирования содержимого создаваемого шаблона. Комментарии предваряют точкой с запятой (;) или двумя прямыми слэшами (//). Комментарии также можно помещать в конце строки. Приведем пример комментария: Ïðèìåð 7 ; Ýòî êîììåíòàðèé // È ýòî êîììåíòàðèé CLASS USER CLASS MACHINE
// // // //
Îïðåäåëåíèå êëàññà USER, îòâå÷àþùåãî çà ïîëüçîâàòåëüñêèå íàñòðîéêè Îïðåäåëåíèå êëàññà MACHINE, îòâå÷àþùåãî çà îáùåêîìïüþòåðíûå íàñòðîéêè
Строки Все возможные словесные описания – описание политики, названия разделов, параметров и т. д. рекомендуется располагать в специально предназначенном разделе [strings]. В тексте политики перед переменной, ссылающейся на текст в разделе [strings], ставят два восклицательных знака (!!). Каждая строка в разделе [strings] имеет формат name=«строка». Приведем два равнозначных примера. В первом примере не будем использовать преимущества строк. Ïðèìåð 8à) CLASS Machine POLICY "Ïðèìåð ïîëèòèêè" ……………… END POLICY Ïðèìåð 8á) CLASS Machine POLICY !!Pname ……………… END POLICY [Strings] Pname="Ïðèìåð ïîëèòèêè"
Использование строк позволяет создавать шаблоны групповых политик, что снижает трудозатраты и ускоряет процесс создание политик.
CLASS Первым элементом в файле, содержащем шаблон групповой политики, является ключевое слово CLASS, которое определяет, к какому типу относится описываемая ниже политика: компьютерная (Computer Configuration) или
90
пользовательская (User Configuration). В одном файле допускается многократное использование ключевого слова CLASS. Приведем пример синтаксиса элемента CLASS: CLASS Name
где Name может иметь одно из двух значений: USER или MACHINE. Значение USER определяет, что политика пользовательская. Изменения будут вноситься в реестр в ветви HKU, настройку политики следует осуществлять в разделе «User Configuration\Administrative Templates\»; Значение MACHINE соответственно определяет, что политика компьютерная. Изменения будут вноситься в реестр в ветви HKLM, настройку политики следует осуществлять в разделе «Computer Configuration\Administrative Templates\».
CATEGORY После определения класса политики необходимо определить местоположение в подпапке «Administrative Templates». Все, что делает ключевое слово CATEGORY, – это создает подпапку. В категории может быть ноль и более политик. Те из них, которые не содержат политик, содержат, как правило, одну или несколько подкатегорий. После определения категории ее необходимо закрыть с помощью END CATEGORY. Приведем пример синтаксиса элемента CATEGORY: CATEGORY Name KEYMAME SubKey ……………………….. END CATEGORY
где: ! Name – это имя папки, которое будет отображаться в редакторе Group Policy. Используйте строковую перемену или строку, заключенную в кавычках. ! SubKey – является необязательным параметром. Информация о параметрах и конфигурации групповых политик сосредоточена в HKLM\Software\Policies (предпочтительное расположение) или HKLM\Software\Microsoft\ Windows\CurrentVersion\Policies для раздела Computer Configration; в HKU\Software\Policies (предпочтительное расположение) или HKU\Software\Microsoft\Windows\ CurrentVersion\Policies для раздела User Configration. Не используйте в пути корневой ключ (HKLM, HKU), поскольку он уже описан ключевым словом CLASS. Если путь содержит пробелы, заключайте его в кавычки. Ïðèìåð 9 CLASS USER CATEGORY "POLICIES" CATEGORY !!SubPol1 KEYNAME "Software\Policies\SubPol1" ………………………… END CATEGORY CATEGORY !!SubPol2 KEYNAME "Software\Policies\SubPol1" ………………………… END CATEGORY END CATEGORY [strings] SubPol1="Policy1" SubPol2="Policy2"
образование удаления политики, то необходимо использовать ключевые слова VALUEON и VALUEOFF, которые следуют непосредственно за словом VALUENAME: VALUEON [NUMERIC] VValue VALUEOFF [NUMERIC] VValue
По умолчанию тип данных значение VValue REG_SZ. Если после ключевого слово указано NUMERIC, то тип данных значение VValue – REG_DWORD.
Ðèñóíîê 6
В разделе CATEGORY могут быть использованы следующие ключевые слова: ! CATEGORY ! END ! KEYNAME ! POLICY
POLICY Используйте ключевое слово POLICY, чтобы определить политику. В одной категории может быть включено несколько разделов POLICY, каждый из которых должен заканчиваться инструкцией END POLICY. Приведем пример синтаксиса элемента POLICY: POLICY Name [KEYNAME SubKey] EXPLAIN Help VALUENAME Value [PARTS] ………………. END POLICY
где: ! Name – это название политики, отображаемое в редакторе Group Policy. Используйте строковую переменную или строку, заключенную в кавычках. ! SubKey является необязательным параметром. Информация о параметрах и конфигурации групповых политик сосредоточена в HKLM\Software\Policies (предпочтительное расположение) или HKLM\Software\Microsoft\ Windows\CurrentVersion\Policies для раздела Computer Configration; в HKU\Software\Policies (предпочтительное расположение) или HKU\Software\Microsoft\Windows\ Current Version\Policies для раздела User Configration. Не используйте в пути корневой ключ (HKLM, HKU), поскольку он уже описан ключевым словом CLASS. Если путь содержит пробелы, заключайте его в кавычки. Ко всем политикам применяется последнее ключевое слово KEYNAME. ! Help – эта строка, содержимое которой редактор Group Policy отображает в разделе EXTENDED политики. Для перевода каретки используйте «\n». ! Value – каждая политика содержит ключевое слово VALUENAME, которое связывает с ней значение реестра. По умолчанию редактор политик предполагает, что это значение переменной типа REG_DWORD равно 1 (0х01), когда политика включена. Редактор политики удаляет значение, если политика выключена. В том случае, если необходимо его сохранить в реестре после
№8(21), август 2004
Программирование интерфейса политик безопасности Программирование интерфейса групповых политик сводится к созданию раскрывающихся списков, флажков, текстовых полей и т. д. Рассмотрим по порядку процедуры создания всех элементов интерфейса. 1) В первом варианте, самом простом, нет никаких управляющих элементов, кроме опции «Вкл/Выкл политику». В файле политики записан ключ реестра и значение, имеющее тип REG_SZ или REG_DWORD, которое может меняться в зависимости от того, включена ли политика. Приведем пример, в котором при включенной политике устанавливает переключение раскладки клавиатуры в диалоговом окне регистрации пользователя в сети CTRL+SHIFT, иначе – ALT+SHIFT. За переключение раскладки языка отвечает параметр «Hotkey», размещающийся в разделе «HKEY_USER\Keyboard Layout\Toggle» и принимающий значение 2 (CTRL+SHIFT) или 1 (ALT+SHIFT). Параметр «Hotkey» имеет тип данных REG_SZ. Приступим к созданию политики. Поскольку необходимо редактировать ветвь реестра HKU, то политика является пользовательской и будет создана в разделе «User Configuration\Administrative Templates\». Исходя из этого, политика принадлежит к классу USER. Параметр KEYNAME раздела CATEGORY, исходя из поставленной задачи, принимает значение «Keyboard Layout\ Toggle». В разделе POLICY параметр VALUENAME – «Hotkey». VALUEON – 2, а VALUEOFF – 1. Таким образом, политика выглядит следующим образом: Ïðèìåð 10 CLASS USER CATEGORY "Admin Policies" CATEGORY "Keyboard Toggle " KEYNAME ".Default\Keyboard Layout\Toggle" POLICY "Toggle Policy" EXPLAIN !!help VALUENAME "Hotkey" VALUEON 2 VALUEOFF 1 END POLICY END CATEGORY END CATEGORY [strings] help="Óïðàâëåíèå ïåðåêëþ÷åíèåì ðàñêëàäêè êëàâèàòóðû ïðè ðåãèñòðàöèè ïîëüçîâàòåëÿ â ñåòè \n\n Ïðè âêëþ÷åííîé ïîëèòèêå ïåðåêëþ÷åíèå ðàñêëàäêè êëàâèàòóðû îñóùåñòâëÿåòñÿ ñ ïîìîùüþ êîìáèíàöèè êëàâèø CTRL+SHIFT, ïðè âûêëþ÷åííîé – ALT+SHIFT."
2) Все остальные элементы интерфейса – флажки, выпадающие меню и т. д. можно реализовать только в разделе PART, являющемся членом раздела POLICY. Рассмотрим подробнее синтаксис раздела PART:
91
образование PART Name Type Keywords [KEYNAME Subkey] [DEFAULT Default] VALUENAME Name END PART
где: ! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. ! Type – может быть одним из следующих типов: Òàáëèöà 4
! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. Если в названии раздела встречаются пробелы, заключите его в кавычки. ! Value – название параметра, которое необходимо изменить. ! Value1 – значение, задаваемое при установленном флажке и включенной политике. ! Value2 – значение, задаваемое при снятом флажке и включенной политике.
COMBOBOX Ключевое слово COMBOBOX добавляет в диалоговое окно политики список с текстовым полем. Внутри COMBOBOX включен обязательный блок SUGGESTIONS, заканчивающийся инструкцией SUGGESTIONS. Внутри этого блока перечислены элементы. Элементы разделяются пробелами, элементы, имеющие пробелы, помещаются в кавычки (см. синтаксис). PART Name COMBOBOX SUGGESTIONS “Suggestion1” “Suggestion2” … END SUGGESTIONS [DEFAULT Default] [EXPANDABLETEXT] [MAXLENGHT] Max [NOSORT] [REQUIRED] VALUENAME Value END PART
! Keywords – эта информация специфична для каждого из типов. Дополнительная информация приведена ниже.
! Subkey – это необязательный подключ HKLM или HKU. Не используйте в пути корневой ключ (HKLM, HKU), поскольку он уже описан ключевым словом CLASS. Если путь содержит пробелы, заключайте его в кавычки. ! Defaults – это значение параметра по умолчанию. Когда администратор включает политику, то осуществляется считывание параметров по умолчанию. ! Value – модифицируемое значение реестра. Тип и данные значения полностью зависят от типа параметра.
CHECKBOX Ключевое слово CHECKBOX отображает флажок. По умолчанию флажок сброшен. Если требуется, чтобы изначально флажок был установлен, необходимо в теле раздела PART указать ключевое слово DEFCHECKED. При установленном флажке в реестр записывается значение 1, если сброшен – 0. В реестре по умолчанию представлен значением типа REG_SZ. Если необходимо записать данные в реестр в формате REG_DWORD, то необходимо после значений ключевых слов VALUEON и VALUEOFF добавить NUMERIC. Приведем синтаксис элемента CHECKBOX: PART Name CHECKBOX [DEFCHEKED] VALUENAME Value VALUEON [NUMERIC] Value1 VALUEOFF [NUMERIC] Value2 END PART
где:
92
“Suggestionm”
где: ! DEFAULT – указывает значение списка по умолчанию. ! EXPANDABLETEXT – создает значение типа REG_ EXPAND_SZ. ! MAXLENGHT – указывает максимальную длину строки. ! NOSORT – отключает сортировку списка редактором политик. ! REQUIRED – указывает обязательное значение. ! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. ! Suggestions – список элементов, помещающихся в раскрывающийся список. Все элементы, содержащие пробелы, помещаются в кавычки. Элементы списка разделяются пробелами. ! Default – значение по умолчанию. Считывается при включении политики. ! Max – максимальная длина данных значения. ! Value – модифицируемое значение реестра. По умолчанию имеет тип данных REG_SZ.
DROPDOWNLIST Ключевое слово DROPDOWNLIST добавляет в диалоговое окно политики раскрывающийся список. Внутри DROP DOWNLIST включен обязательный блок ITEMLIST, заканчивающийся инструкцией END ITEMLIST. Внутри этого блока перечислены элементы раскрывающегося списка (см. синтаксис). Приведем синтаксис элемента DROPDOWNLIST: PART Name DROPDOWNLIST ITEMLIST NAME Item VALUE Data END ITEMLIST
образование
END PART
[DEFAULT Default] [EXPANDABLETEXT] [NOSORT] [REQUIRED] VALUENAME Value
где: ! DEFAULT – указывает значение раскрывающегося списка по умолчанию. ! EXPANDABLETEXT – создает значение типа REG_ EXPAND_SZ. ! NOSORT – отключает сортировку списка редактором политик. ! REQUIRED – указывает обязательное значение. ! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. ! Item – имя каждого из элементов раскрывающегося списка. ! Data – значение, соответствующее отображаемому элементу Item. ! Default – значение по умолчанию. Считывается при включении политики. ! Value – модифицируемое значение реестра. По умолчанию имеет тип данных REG_SZ.
EDITTEXT Ключевое слово EDITTEXT позволяет вводить в текстовом поле информацию, состоящую из букв и цифр. По умолчанию редактор политик сохраняет этот текст в значении типа REG_SZ. Приведем синтаксис элемента EDITTEXT:
PART Name LISTBOX [EXPANDABLETEXT] [NOSORT] [ADDITIVE] [EXPLICITVALUE| VALUEPREFIX Prefix] END PART
где: ! EXPANDABLETEXT – создает значение типа REG_ EXPAND_SZ. ! NOSORT – отключает сортировку списка редактором политик. ! ADDITIVE – считывает значения, уже хранящиеся в реестре. ! EXPLICITVALUE – ключевое слово позволяет указать имя и данные значения. Отображаемый список имеет два столбца: один для имени, другой – для данных. Невозможно использовать эту инструкцию совместно с VALUEPREFIX. ! VALUEPREFIX – указанный префикс определяет имена значений. Если указывается префикс, то редактор групповых политик добавляет к нему возрастающий номер. В том случае, если префикс пустой, генерируются только возрастающие номера. ! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. ! Prefix – используется для генерации последовательности имен. Если указан префикс, например Sample, то генерируются следующие имена значений: Sample1, Sample2,..Samplen. Если префикс не указан, то генерируются только порядковые номера: 1,2,3,…n.
NUMERIC PART Name EDITTEXT [DEFAULT Default] [EXPANDABLETEXT] [MAXLENGHT] Max [NOSORT] [REQUIRED] VALUENAME Value END PART
где: ! DEFAULT – указывает значение списка по умолчанию. ! EXPANDABLETEXT – создает значение типа REG_ EXPAND_SZ. ! MAXLENGHT – указывает максимальную длину строки. ! REQUIRED – указывает обязательное значение. ! Name – указывается название раздела, которое будет отображено в консоле во время редактирования политики. ! Default – значение по умолчанию. Считывается при включении политики. ! Max – максимальная длина данных значения. ! Value – модифицируемое значение реестра. По умолчанию имеет тип данных REG_SZ.
LISTBOX Ключевое слово LISTBOX добавляет в диалоговое окно политик список с кнопками Add и Remove. Это единственный тип, который может быть использован системным администратором для управления несколькими значениями, содержащимися в одном ключе. В разделе LISTBOX невозможно использовать опцию VALUENAME, поскольку он не связан с каким-либо конкретным значением. Приведем синтаксис элемента LISTBOX:
№8(21), август 2004
Ключевое слово NUMERIC позволяет вводить буквенноцифровой текст, используя элемент управления «счетчик», который увеличивает или уменьшает число. По умолчанию переменные имеют тип данных REG_DWORD, однако, используя инструкцию TXTCONVERT, данные могут быть сохранены в формате REG_SZ. Приведем синтаксис элемента LISTBOX: PART Name NUMERIC
END PART
[DEFAULT Default] [MAX Max] [MIN Min] [REQUIRED] [SPIN] [TXTCONVERT] VALUENAME Value
где: ! DEFAULT – указывает значение списка по умолчанию. ! REQUIRED – указывает обязательное значение. ! SPIN – указывается шаг арифметической прогрессии. По умолчанию равен 1. Указанное значение 0 делает счетчик невидимым. ! TXTCONVERT – по умолчанию значения имеют тип данных REG_DWORD. Используйте эту инструкцию, чтобы сохранить данные в формате REG_SZ. ! Name – указывается название раздела, которое будет отображено в консоли во время редактирования политики. ! Default – значение по умолчанию. Считывается при включении политики. ! Max – максимальное значение. По умолчанию равно 9999.
93
образование ! Min – минимальное значение. По умолчанию равно 0. ! Value – модифицируемое значение реестра. По умолчанию имеет тип данных REG_DWORD.
TEXT Ключевое слово TEXT добавляет в диалоговое окно статический текст. Приведем синтаксис элемента TEXT: PART Text TEXT END PART
где Text – статический текст, добавляемый в диалоговое окно. Если текст имеет пробелы, то его следует заключать в кавычки.
Практика использования административных шаблонов Приведем два примера административных шаблонов. В корпоративной сети, как правило, существуют один или несколько файловых серверов, в которых хранятся дистрибутивы программного обеспечения, в том числе Microsoft Office. В том случае, если пользователю по какой-либо причине необходимо перейти с одной рабочей станции на другую, то при первой загрузке для него создается новый профиль. При первом запуске одного из компонентов MS Office, программа обращается к дистрибутиву, который расположен на файловом сервере. При изменении местоположения дистрибутива на сервере пользователю выдается сообщение о невозможности завершить процесс конфигурации программы и просьба обратиться к системному администратору. Во избежание этой неприятной ситуации рекомендуется использовать групповую политику, которая меняет в реестре пути к дистрибутиву MS Office (см. рис. 7): Ïðèìåð 11 CLASS Machine CATEGORY "Office 200" KEYNAME "Software\Policies" POLICY "Office2000" EXPLAIN !!help PART "Parametr1" EDITTEXT KEYNAME "SOFTWARE\Classes\Installer\Products\ ↵ 914000001E872D116BF00006799C897E\SourceList" VALUENAME LastUsedSource MAXLEN 100 EXPANDABLETEXT END PART PART !!help1 TEXT END PART PART "Parametr2" EDITTEXT KEYNAME "SOFTWARE\Classes\Installer\Products\ ↵ 914000001E872D116BF00006799C897E\SourceList\Net" VALUENAME 1 MAXLEN 100 EXPANDABLETEXT END PART PART !!help2 TEXT END PART END POLICY END CATEGORY [strings] help1="Exapmle1: n;3;\\Server\software\Office2000\" help2="Exapmle2: \\Server\software\Office2000\" help="\n\nCorrecting path to Microsoft Office 2000"
! Загрузить консоль групповых политик, выполнив команду GPEDIT.MSC.
! В любом из разделов («Computer Configuration\Administrative Templates» или «User Configuration\ Administrative Templates») вызвать контекстное меню, используя однократное нажатие на правую кнопку мыши. ! В появившемся меню выбрать «Add/Remove Templates…». ! В загрузившемся диалоговом окне нажать кнопку «Add». В появившемся окне, указав путь к файлу с расширением ADM, нажать кнопку «Open». ! Нажать кнопку «Close». После нажатия на эту кнопку программа проверит синтаксис файла. В том случае, если будет допущена синтаксическая ошибка, интерпретатор укажет номер строки, в которой она присутствует, и возможный вариант исправления.
Удаление административного шаблона Для удаления политики, подгруженной с помощью ADM-файла, необходимо выполнить действия в следующем порядке: ! Загрузить консоль групповых политик, выполнив команду GPEDIT.MSC. ! В любом из разделов («Computer Configuration\Administrative Templates» или «User Configuration\ Administrative Templates») вызвать контекстное меню, используя однократное нажатие на правую кнопку мыши. ! В появившемся меню выбрать «Add/Remove Templates…». ! В загрузившемся диалоговом окне выбрать файл, в котором описана политика, которую необходимо удалить. Нажать кнопку «Remove», затем «Close».
Заключение Используя в сценарии регистрации пользователей в сети вместе с административными шаблонами политик безопасности, системный администратор автоматизирует большую часть рутинных операций. Эффективность его работы при грамотном использовании описанных инструментов значительно возрастает.
Внедрение административных шаблонов Для загрузки ADM-файла, содержащего политику, необходимо выполнить следующие действия:
94
Ðèñóíîê 7
подписка на II полугодие 2004 Российская Федерация ! Подписной индекс: 81655 !
!
!
Каталог агентства «Роспечать» Подписной индекс: 87836 Объединенный каталог «Пресса России» Адресный каталог «Подписка за рабочим столом» Адресный каталог «Библиотечный каталог» Альтернативные подписные агентства: Агентство «Интер-Почта» (095) 500-00-60, курьерская доставка по Москве Агентство «Вся Пресса» (095) 787-34-47 Агентство «Курьер-Прессервис» Подписка On-line http://www.arzy.ru http://www.gazety.ru http://www.presscafe.ru
! Узбекистан – по каталогу «Davriy nashrlar» российские
! !
! !
СНГ В странах СНГ подписка принимается в почтовых отделениях по национальным каталогам или по списку номенклатуры АРЗИ: ! Казахстан – по каталогу «Российская Пресса» через ОАО «Казпочта» и ЗАО «Евразия пресс» ! Беларусь – по каталогу изданий стран СНГ через РГО «Белпочта» (220050, г.Минск, пр-т Ф.Скорины, 10)
!
издания через агентство по распространению печати «Davriy nashrlar» (7000029, Ташкент, пл.Мустакиллик, 5/3, офис 33) Азербайджан – по объединенному каталогу российских изданий через предприятие по распространению печати «Гасид» (370102, г. Баку, ул. Джавадхана, 21) Армения – по списку номенклатуры «АРЗИ» через ГЗАО «Армпечать» (375005, г.Ереван, пл.Сасунци Давида, д.2) и ЗАО «Контакт-Мамул» (375002, г. Ереван, ул.Сарьяна, 22) Грузия – по списку номенклатуры «АРЗИ» через АО «Сакпресса» ( 380019, г.Тбилиси, ул.Хошараульская, 29) и АО «Мацне» (380060, г.Тбилиси, пр-т Гамсахурдия, 42) Молдавия – по каталогу через ГП «Пошта Молдавей» (МД-2012, г.Кишинев, бул.Штефан чел Маре, 134) по списку через ГУП «Почта Приднестровья» (МD-3300, г.Тирасполь, ул.Ленина, 17) по прайслисту через ООО Агентство «Editil Periodice» (2012, г.Кишинев, бул. Штефан чел Маре, 134) Подписка для Украины: Киевский главпочтамп Подписное агентство «KSS» Телефон/факс (044)464-0220
Подписные индексы:
81655 по каталогу агентства «Роспечать»
87836 по каталогу агентства «Пресса России»
№8(21), август 2004
95
СИСТЕМНЫЙ АДМИНИСТРАТОР №8(21), Август, 2004 год РЕДАКЦИЯ Исполнительный директор Владимир Положевец Ответственный секретарь Наталья Хвостова sekretar@samag.ru Технический редактор Владимир Лукин Редактор Андрей Бешков РЕКЛАМНАЯ СЛУЖБА тел./факс: (095) 928-8253 Константин Меделян reсlama@samag.ru Верстка и оформление imposer@samag.ru maker_up@samag.ru Дизайн обложки Николай Петрочук 103045, г. Москва, Ананьевский переулок, дом 4/2 стр. 1 тел./факс: (095) 928-8253 Е-mail: info@samag.ru Internet: www.samag.ru РУКОВОДИТЕЛЬ ПРОЕКТА Петр Положевец УЧРЕДИТЕЛИ Владимир Положевец Александр Михалев ИЗДАТЕЛЬ ЗАО «Издательский дом «Учительская газета» Отпечатано типографией ГП «Московская Типография №13» Тираж 7000 экз. Журнал зарегистрирован в Министерстве РФ по делам печати, телерадиовещания и средств массовых коммуникаций (свидетельство ПИ № 77-12542 от 24 апреля 2002г.) За содержание статьи ответственность несет автор. За содержание рекламного обьявления ответственность несет рекламодатель. Все права на опубликованные материалы защищены. Редакция оставляет за собой право изменять содержание следующих номеров.
96
ЧИТАЙТЕ В СЛЕДУЮЩЕМ НОМЕРЕ: PHP 5 – пришествие неизбежно Итак свершилось. 13 июля 2004 года вышла пятая версия самого популярного на сегодняшний день языка вебразработки – PHP. Его создатель, Расмус Лендорф, подчёркивает, что изменения, привнесённые в язык, не революционны, а скорее эволюционны, но беглое знакомство с новыми возможностями PHP заставляет усомниться в словах мэтра. За недолгое время своего существования PHP сумел из набора скриптов для придания интерактивности домашней странички автора вырасти в мощное средство разработки веб-приложений и теперь – новый шаг вперед. Насколько этот шаг важен и какие последствия перехода на новую версию грозят программисту и системному администратору, можно оценить, ознакомившись с основными нововведениями PHP 5 и возможными проблемами, связанными с его использованием.
Роутер без диска В работе подчас встречаются ситуации, когда осуществление тех или иных мероприятий или действий традиционным путем если не то, что совсем невозможно, а просто неудобно, а привычные инструменты оказываются практически бесполезны. Вот и у меня возникла ситуация, когда для показа возможностей необходимо было в короткие сроки собрать и настроить сеть. А так как это была всего лишь презентация и все задействованные компьютеры использовались в реальной работе, то одним из требований было поменьше трогать настройки и ПО, для того чтобы можно было быстро вернуть все в рабочее состояние. Если с клиентскими компьютерами более-менее все решалось просто: где доустановкой пары программ, где использованием GNU/Linux LiveCD-дистрибутивов, то с роутером вышла заминка. Имеющиеся у меня образцы либо не
удовлетворяли по возможностям, либо требовали установки на жесткий диск, либо серьезного изменения настроек системы, что меня не совсем устраивало. Поэтому пришлось искать инструмент, который сможет помочь в такой ситуации. И он, конечно же, нашелся.
Linux на страже Windows. Обзор и установка системы резервного копирования BackupPC Когда у меня возникло желание сделать единое хранилище для ежедневных архивов информации с более чем десятка серверов своей организации, работающих под управлением нескольких различных ОС, свой выбор я остановил на платформе Linux. До сих пор каждый сервер с помощью уникальных для него скриптов в назначенное время сбрасывал по сети на сервер резервного копирования или stand by-сервер какие-то свои данные, например, пользовательские файлы с сетевых дисков или дампы базы данных. Для этого использовались различные протоколы: ftp, SMB или штатные средства СУБД. При этом приходилось следить за уникальным для каждого сервера log-файлом, и в случае какихлибо изменений в стратегии резервного копирования править скрипты на каждой машине. Чтобы как-то упростить администрирование и сократить время, затрачиваемое на поддержку и мониторинг всего этого «зоопарка», я начал искать систему, которая бы поддерживала копирование информации по сети, удаленное администрирование, умела делать инкрементальные бэкапы и не требовала установки клиентского программного обеспечения. Кроме того, было важно, чтобы система умела работать по протоколу SMB, так как часть серверов, в частности основной файлсервер работали под управлением ОС Windows. Спустя непродолжительное время, такая система была найдена.