Friday, November 23, 2007

Использование libmilter во FreeBSD

При сборке libmilter по умолчанию во FreeBSD она использует select(3) по умолчанию. Однако, в нагруженных системах это приводит к ошибке EBADF при попытке сделать select, так как номер дескриптора превышает FD_SETSIZE. В таком случае можно применить патч, который собирает libmilter с использованием poll(3).
libmilter.patch

Thursday, November 8, 2007

Извлечение данных из sysctl (3) в FreeBSD

С системным вызовом sysctl есть одна проблема: нет очевидного способа узнать тип возвращаемого значения. Это неважно, когда тип мы точно знаем, но сильно затрудняет жизнь, когда не знаем. Получается тип следующим образом:
к mib-у приписываются с начала два дополнительных целых - 0,4, то есть, целевой миб будет выглядеть так:
0,4,[mib]
где mib - искомый mib
Далее делается sysctl для этого mib-а, размер лучше задать равным BUFSIZ или же получить размер, указав в качестве буфера NULL.
Чтобы узнать, что хранится в mib'е необходимо разобрать полученный буфер следующим образом:
(unsigned int) (char) [(char) [(char)...]]
Для извлечения значения первое поле можно не использовать, но в нем также хранится тип, который извлекается бинарным "И" с константой CTLTYPE (number & CTLTYPE). Полученное значение сравнивается с CTL константами в /usr/include/sys/sysctl.h. Два символа после этого значения также можно использовать для опеределения типа - 1-й символ определяет тип, второй символ (которого может не быть) определяет наличие знака для численных типов. Соответствие типов и символов типа:
int - 'I'
long - 'L'
quad_t - 'Q'
pointer - 'P'
typedef - 'T'
struct - 'S'
Для 'S' и 'T' типов тип описывается после запятой, то есть, строчка будет 'S,some_struct' или 'T,some_type_t'. Для численных типов второй символ может быть 'U', тогда это число - беззнаковое число.

Wednesday, November 7, 2007

Autotools

При написании приложений, которые необходимо портировать хотя бы между разными версиями unix, рано или поздно приходится прибегать к auto-кошмару. Однако, если бы из всех разрозненных руководств и самих утилит собрать нечто похожее на мануал, то все было бы намного проще. Итак, вкратце:

Исходные файлы:
configure.ac (можно генерить autoscan'ом)
Makefile.am
Остальное генерируется само.

Утилиты:
autoscan - создает базовый вид configure.ac
autoheader - создает config.h.in на основе configure.ac
aclocal - объединяет макросы, существующие в системе, в файл aclocal.m4
automake - создает файл Makefile.in на базе Makefile.am и configure.ac. Полезные опции: -a (создавать все недостаточные файлы), --foreign (не ругаться на отсутствие гнутой документации и прочей байды)
autoconf - создает configure скрипт, которым далее можно пользоваться

Форматы файлов:
Makefile.am - похож немного на формат обычного make. Для plain конфигураций достаточно простого синтаксиса NAME [?|+]= VALUE

bin_PROGRAMS = some-cli
some_cli_SOURCES = some.c server.c

if GNOME_GUI
bin_PROGRAMS += some-gnome
endif

some_gnome_SOURCES = some.c server.c gnome-some.c
some_gnome_CFLAGS = @DEPS_CFLAGS@
some_gnome_LDADD = @DEPS_LIBS@

Символы @@ подменяются скриптом configure на значения соответствующих configure переменных - происходит это на этапе конфигурации Makefile.in в конечный Makefile.
GNOME_GUI специально передается из autoconf в automake - об этом далее. Переменные, в принципе, достаточно очевидные.

configure.ac - набор специализированных m4 макросов, которые после обработки препроцессором превращаются в исполняемый shell скрипт. Вкратце об m4 - синтаксис примерно такой: MACRO (param, param...) при этом есть специальный синтаксис - подстановки. Если param окружить квадратными скобками, то выполняются следующие подстановки:
[value value value] - передает список значений, как один параметр
[shell code] - выполняет соответствующи shell код, при этом все переменные, определенные внутри квадратных скобок могут использоваться и далее
[M4_MACRO (param...) M4_MACRO(param...)] - выполняет один или несколько макросов для вычисления параметра

Полезные макросы и конструкции autoconf:
- проверка --enable параметра и установка его по умолчанию
AC_ARG_ENABLE([gnome-gui],
[  --enable-gnome-gui    Turn on gnome gui],
[case "${enableval}" in
yes) gnomegui=true ;;
no)  gnomegui=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-gnome-gui]) ;;
esac],[gnomegui=true])
Общий вид: AC_ARG_ENABLE(name, help, action_if_set, action_if_unset)
из-за эскейпа этот макрос делается не очень читаемым, так как используется шелл-код внутри макроса.
- для передачи переменной для automake (см. выше) используется такая конструкция, которая дефайнит переменную GNOME_GUI, если была указана опция --enable-gnome-gui:
AM_CONDITIONAL([GNOME_GUI], [test x$gnomegui = xtrue])
- для записи некоторых параметров в файл config.h при указании опции можно пользоваться таким синтаксисом:
AC_ARG_WITH([icn-ip],
            [AS_HELP_STRING([--with-ip],
              [specify ip to use])],
            [with_icn_ip=${withval}],
          [with_icn_ip=127.0.0.1])
AC_DEFINE_UNQUOTED([ICN_IP], ["$with_icn_ip"], ["Ip address of server to connect"]
Первый макрос похож на макрос для проверки enable, второй опеределяет соответствующий #define в config.h. Если в квадратных скобках указывать значение без кавычек, то и в config.h оно будет без кавычек (для числовых значений, например).

- шапка файла
m4_include([version.m4])
AC_INIT([ICNet], VERSION_NUMBER)
AM_INIT_AUTOMAKE([no-installinfo no-installman])
AC_CONFIG_HEADER([config.h])

version.m4 содержит одну строку - m4_define([VERSION_NUMBER], [1.0])

Остальное можно взять из того, что нагенерил autoscan

- использование pkg-config
AS_IF([test x$gnomegui = xtrue],
        [PKG_CHECK_MODULES(DEPS, gtk+-2.0 libbonoboui-2.0)
        AC_SUBST(DEPS_CFLAGS)
        AC_SUBST(DEPS_LIBS)
        ]
)
AS_IF проверяет установленную ранее переменную и если условие выполняется, выполняет проверку библиотек gnome - gtk+-2.0 и libbonoboui-2.0 используя pkg-config. Результаты записываются в переменные DEPS_CFLAGS и DEPS_LIBS, которые мы будем заменять в Makefile.in (там они экранируются символами @DEPS_CFLAGS@).

- проверка библиотеки .so
AC_CHECK_LIB([event], [event_loop])
1-й параметр - имя библиотеки, 2-й - проверка наличия определенного символа в этой библиотеке

- проверка хедеров
AC_CHECK_HEADERS([endian.h arpa/inet.h netinet/in.h stdlib.h string.h sys/socket.h syslog.h unistd.h])

- проверка функций
AC_CHECK_FUNCS([bzero memset socket strerror strtol strtoul])

Порядок запуска autotools

Makefile.am ->
autoscan ->
cp configure.scan configure.ac ->
(edit configure.ac) ->
aclocal ->
autoheader ->
automake ->
autoconf

./configure
make...

Thursday, November 1, 2007

Настройка cgi репозитария для mercurial

Преамбула:
для выполнения коммиттов нескольих пользователей в несколько репозитариев лучше всего использовать cgi расширение mercurial. Для этого необходимо настроить соответствующим образом apache, и hgwebdir.

Настройка web-серверва

создаем какую-то директорию, например /usr/local/hgstore, назначаем ее владельца www
записываем в httpd.conf:
    Alias /hg/ "/usr/local/hgstore/"
    <Directory "/usr/local/hgstore/">
        Order allow,deny
        Allow from all
        AllowOverride All
        Options ExecCGI FollowSymLinks
        AddHandler cgi-script .cgi
        RewriteEngine On
        #write base depending on where the base url lives
        RewriteBase /hg
        RewriteRule ^$ hgwebdir.cgi  [L] 
        # Send requests for files that exist to those files.
        RewriteCond %{REQUEST_FILENAME} !-f
        # Send requests for directories that exist to those directories.
        RewriteCond %{REQUEST_FILENAME} !-d
        # Send requests to hgwebdir.cgi, appending the rest of url.
        RewriteRule (.*) hgwebdir.cgi/$1  [QSA,L]
    </Directory>

Создаем в этой директории такой .htaccess:

AuthUserFile /usr/local/www/htpasswd_hgstore
AuthName "Mercurial Repository"
AuthType Basic
<LimitExcept GET>
  Require valid-user
</LimitExcept>

При таких настройках смотреть и чекаутить репозитарий смогут все пользователи, а коммиты и пуши могут делаться только с авторизацией через соответствующий файл паролей.

Настройка mercurial

Далее копируем hgwebdir в заданную директорию:
# install -b /usr/local/share/mercurial/www/hgwebdir.cgi /usr/local/hgstore
# chmod +x /usr/local/hgstore/hgwebdir.cgi

Для каждого репозитария прописываем соответствующую строчку в hgweb.config:
[paths]
VIRTUAL_PATH = FILESYSTEM_PATH
 
VIRTUAL_PATH - путь, который будет показан в url: https://host/hg/path
FILESYSTEM_PATH - путь, относительно расположения hgwebdir.cgi

Далее создаем hgrc по умолчанию:

[extensions]
hgext.notify =
hgext.gpg =

[email]
from = mercury@example.com

[smtp]
host = localhost

[web]
allow_push = *
push_ssl = false
contact = Vsevolod Stakhov
description = Get our elephants
baseurl = https://hostname/hg/rmilter/
allow_archive = bz2 gz zip
style = gitweb

[hooks]
# send one email per group of changes
changegroup.notify = python:hgext.notify.hook
#incoming.notify = python:hgext.notify.hook

[notify]
# multiple sources can be specified as a whitespace separated list
sources = serve push pull bundle
# really send email
test = false
# subscriber data lives in the notify repo
config = /usr/local/hgstore/notify.conf
#repos live in /usr/local/hgstore/repos on server, so strip 4 "/" chars
strip = 4
template = X-Hg-Repo: {webroot}\nSubject: {webroot}: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {root}\ndetails: {baseurl}{webroot}?cmd=changeset;node={node|short}\n\t{desc|tabindent|strip}\n
maxdiff = -1

Этот файл нужно копировать в каталог repo/REPONAME/,hg/hgrc
для каждого репозитория:
# mkdir repo
# hg init repo/REPONAME
# cp hgrc repo/REPONAME/.hg/hgrc
Поправив соответствующим образом.

Далее нужно не забыть прописать этот путь в hgweb.config.
После этого можно делать push и смотреть репозиторий через web.

Для добавления нового репозитария надо:

* сделать начальный каталог при помощи hg init repos/REPONAME
* прописать путь к нему в файле hgweb.config
* скопировать hgrc по умолчанию в каталог repos/REPONAME/.hg/hgrc
* отредактировать файл repos/REPONAME/.hg/hgrc

После всего нужно сменить фладельца всех файлов на www:
# chown -R www /usr/local/hgstore