среда, 12 марта 2008 г.

PHP Daemon

А теперь немного извращений: демон на PHP.

ЧАСТЬ 1. ПИШЕМ ДЕМОНА.
Описание команд читать в мануалах. По-возможности, я даю на них ссылки.

declare(ticks=1);

$g_signals = array(
  'SIGTERM' => 0,
  'SIGHUP' => 0,
);

$g_ppid = null;
$g_pid = null;

dmn_sys_init(); // сердце демона!

$g_ppid = posix_getppid(); // pid родителя
$g_pid = posix_getpid(); // pid дочернего (текущего) процесса

// поехали!
while(1){
  // делаем что-то полезное.

  if($g_signals['SIGTERM'] == 1) // если получен сигнал завершения процесса, то не спеша, не паникуя тихонечко заканчиваем работу и выходим.
    break;

  if($sleep_time > 0)
    sleep(1); // спать одну секунду (на самом деле это идеологически не правильно, но для шаблона подойдет)
}

function dmn_sys_init(){
  global $g_pid;

  // 1. создаем дочерний процесс. при одном работающем это может быть не нужно, но если планируется запуск одного контрольного и нескольких рабочих процессов, то этот код необходим.
  if(-1 == ($g_pid = pcntl_fork()))
    exit("Could not fork.\n");

  // очень важный момент! Здесь определяем в каком мы процессе после разветвления.
  if($g_pid != 0){
    // 2. мы в родительском процессе
    // тут нам нужно отслеживать завершение дочерних процессов.
    // делается это банально просто: при создании процесса накручиваем счетчик дочерних процессов
    // при завершении дочернего процесса уменьшаем счетчик. Когда дочерних не осталось - выходим.
    // ждать завершения можно командой pcntl_wait.
    // написание этого кода я оставлю читателю в качестве домашного задания

    // для тех, кому лень думать есть приятные новости - дочерние процессы можно не ждать,
    // а делать в лучших традициях мира животных - выкидывать их из гнезда и тихонько помирать.
    exit("The end of parent process.\n");
  }

  // 3. мы в дочернем процессе!
  // отцепляем процесс от консоли
  if(-1 == posix_setsid())
    exit("Could not detach from terminal.\n");

  // устанавливаем хуки для обрабатываемых сигналов
  if(!pcntl_signal(SIGTERM, "dmn_sys_sig_handler"))
  exit("Could not setup SIGTERM handler.\n");

  if(!pcntl_signal(SIGHUP, "dmn_sys_sig_handler"))
  exit("Could not setup SIGHUP handler.\n");
}

// функция-хук обработки сигналов от операционной системы
function dmn_sys_sig_handler($signo)
{
  global $g_signals;

  switch ($signo){
  case SIGTERM:
    $g_signals['SIGTERM'] = 1;
    break;
  case SIGHUP:
    $g_signals['SIGHUP'] = 1;
    break;
  default:
    $g_signals['OTHER'] = $signo;
  }
}


ЧАСТЬ 2. ПИШЕМ RC СКРИПТЫ.
Здесь пример примитивнейшего RC скрипта для Gentoo.
RC скрипт и PHP файлы записать в /etc/init.d/my-php-daemon и /usr/lib/my-php-daemon соответственно.

#!/sbin/runscript

depend() {
  # выставляем зависимости
  need net
}

start() {
  # показываем юзеру что мы делаем
  ebegin "Starting my PHP daemon"

  # ведем лог
  echo --------------------- >> /var/log/my-php-daemon/rc.log
  date >> /var/log/my-php-daemon/rc.log

  # запускаем демоненка
  php-cgi -f /usr/lib/my-php-daemon/index.php >> /var/log/my-php-daemon/rc.log

  # конец
  eend $?
}

stop() {
  # показываем юзеру что мы делаем
  ebegin "Stopping my php daemon"
  # останавливаем демона так - ищем процесс, выполняющий файл /usr/lib/my-php-daemon/index.php и посылаем ему сигнал SIGTERM.
  # внимание! если таких процессов несколько, то все они получат сигналы SIGTERM.
  pkill -TERM -f /usr/lib/my-php-daemon/index.php

  # ведем лог
  date >> /var/log/my-php-daemon/rc.log
  echo --------------------- >> /var/log/my-php-daemon/rc.log

  # конец
  eend $?
}


Таким образом все варнинги и ошибки PHP будут сыпаться в /var/log/my-php-daemon/rc.log, но, к сожалению, без даты. Добавить дату - это будет читателю второе домашнее задание.

Конечно, мануал не претендует на полноту и безупречность, но это лучше, чем ничего. А еще лучше для простого изучения.

БОНУС-ТРЕК
Поскольку PHP бывает весьма не предсказуем, то для контроля работы демона можно написать небольшой cron скриптик:

$g_vs_gw = "http://gw1.viasms.ru?"
$g_vs_eid = "12345";
$g_vs_password = "secure_word";

$g_needed = array(
  'my-php-daemon' => array(
    'regexp'=>'/php-cgi.+my-php-daemon/',
    'reset_cmd'=>'/etc/init.d/my-php-daemon zap --nocolor',
    'start_cmd'=>'/etc/init.d/my-php-daemon start --nocolor'
  ),
);

// fill array with system fields
$needed = array();

foreach($g_needed as $key=>$val){
  $needed[$key] = $val;
  $needed[$key]['running'] = 0;
}

$exec = shell_exec("ps -C php-cgi -o pid= -o command=");
$pss = explode("\n", $exec);

foreach($needed as $key=>$val)
  if(!$val['running'])
    foreach($pss as $ps)
      if(preg_match($val['regexp'], $ps))
        $needed[$key]['running'] = 1;

foreach($needed as $key=>$val){
  if(!$val['running']){
    print date("Ymd-His") . "\n";

    print shell_exec($val['reset_cmd']);
    $cmdresult = shell_exec($val['start_cmd']);
    print $cmdresult;

    $msg = $key . ' service is down. ' . $cmdresult;
    $pass = md5(md5($g_vs_password) . $msg);

    $url = $g_vs_gw . '?' .
      'eid=' . urlencode($g_vs_eid) .
      '&pass=' . urlencode($pass) .
      '&msg=' . urlencode($msg);

    print $url . "\n";
    readfile($url);
    print "\n";
  }
}


Этот скрипт во время своего запуска проверяет все демоны, указанные в массиве $g_needed. Если один из них не работает, то скрипт сбрасывает его состояние (zap) и запускает снова. Ориентировано, опять таки, на дистрибутив Gentoo. Для подстройки для вашего дистрибутива подкрутите элементы массива reset_cmd и start_cmd.

О результате выполнения пишет лог и отправляет SMS сообщение с текстом о состоянии запуска.
В качестве SMS шлюза используется сайт viasms.ru. Для этого в переменных $g_vs_eid и $g_vs_password надо указать id и пароль события.

В результате, работы скрипта к вам придет SMS сообщение примерно следующего содержания:


my-php-daemon service is down. Starting my PHP daemon [ ok ].


И вы сможете дальше спокойно пить пиво :О)

понедельник, 3 марта 2008 г.

Синхронизация Google

Для ищущих способы синхронизации сервисов Google с телефоном, компьютером, конкретным клиентом:

GCALDaemon is an OS-independent Java program that offers two-way synchronization between Google Calendar and various iCalendar compatible calendar applications. GCALDaemon is primarily designed as a calendar synchronizer but it can also be used as a Gmail notifier, Address Book importer, Gmail terminal and RSS feed converter.

пятница, 29 февраля 2008 г.

Как сделать зашифрованный диск

Дано: Gentoo Linux
Рабочая болванка - 1 штука.

Для начала убедимся, что у нас в ядре поставлены нужные галочки (версия 2.6.23):
BLK_DEV_LOOP=y
Prompt: Loopback device support
Location: -> Device Drivers -> Block devices

BLK_DEV_CRYPTOLOOP=y
Prompt: Cryptoloop Support
Location -> Device Drivers -> Block devices -> Loopback device support

В разделе Cryptographic API указать используемые алгоритмы шифрования, в нашем примере:
CRYPTO_TWOFISH=y
Prompt: Twofish cipher algorithm
Location:
-> Cryptographic API

Можно это все собрать и модулями, главное проследить чтобы они были загружены. Однако, если часто этим пользоваться, то собрать все необходимое прямо в ядре намного удобнее.

(добавлено 20080423): Проверить софт: sys-apps/util-linux должна быть с USE флагом loop-aes.

Затем:
1. создаем чистый файл под размер болванки. В примере ниже будет создан файл 1024*4200k байт.


# dd if=/dev/zero of=./disk.dat bs=1024 count=4200k

2. создаем шифрованный loop device и вводим пароль для шифрования:

# losetup -e twofish256 /dev/loop0 ./disk.dat
Password:

3. создаем файловую систему на вновь созданном устройстве

# mkfs.ext2 /dev/loop0

4. монтируем устройство

# mount /dev/loop0 /mnt/secure-disk

5. После этого в папку /mnt/secure-disk копируем все необходимые данные.
Оставшееся место можно, как обычно, контролировать командой df.

6. Затем размонтируем и удаляем устройство

# umount /mnt/test && losetup -d /dev/loop0

Полученный таким образом файл disk.dat можно записать на болванку. Важно помнить, что файлы больше 2Гб требуют на диске файловую систему UDF, которая, если не ошибаюсь, в Windows не поддерживается. Проблему можно обойти сделав несколько фалов размером до 2 гб и записать диск в обычном формате ISO.

Также лучше не забыть положить на диск файл readme.txt с короткой инструкцией. Она вам самим пригодится, чтобы не забыть какой алгоритм шифрования использовался:

(http://knopkodav.blogspot.com/2008/02/blog-post.html)

# losetup -e twofish256 /dev/loop0 ./disk.dat
Password:
# mount /dev/loop0 /mnt/secure-disk -t ext2
...
# umount /dev/loop0
# losetup -d /dev/loop0


Пока не изученные альтернативы:
loop-aes

пятница, 8 февраля 2008 г.

Drupal тип поля password_confirm

Никогда не ставьте
'#maxlength' => ххх,

для необязательных типов полей
'#type' => 'password_confirm',

получите ошибку
"warning: mb_strlen() expects parameter 1 to be string, array given in /var/www/localhost/htdocs/includes/unicode.inc on line 370."

Это связно с тем, что Друпал проверят длинну поля, а поскольку password_confirm - массив из двух полей, то имеем некорректный параметр передаваемый в mbstrlen.

Немного бэктрейса (лишнее вырезано):
warning: mb_strlen() expects parameter 1 to be string, array given in /var/www/html/localhost/htdocs/includes/unicode.inc on line 370.


Array
(
[#type] => password_confirm
[#description] => To change the current password, enter the new password in both fields.
[#maxlength] => 30
[#size] => 40
[#post] => Array
(
[sz_personal_pass] => Array
(
[pass1] =>
[pass2] =>
)
)
[#name] => sz_personal_pass
[#id] => edit-sz-personal-pass
[#value] => Array
(
[pass1] =>
[pass2] =>
)
[#needs_validation] => 1
)

среда, 9 января 2008 г.

Static /dev on Gentoo OpenVZ VPS

(в модификации от 28 июля 2008)

В OpenVZ VPS по-умолчанию почему-то не содержит девайсов /dev/random, /dev/urandom.
Недостаток легко устраняется несколькими действиями:

----- СПОСОБ 1:

1) редактируем файл /vz/private/123/etc/conf.d/rc
в нем меняем RC_DEVICES="static"

После этого подразумевается, что все устройства этой VPS в /dev вы создадите руками.
Этим и займемся:
2) mknod --mode 666 /vz/private/123/dev/ptmx c 5 2
3) mkdir /vz/private/123/dev/pts
4) rm -f /vz/private/123/dev/null
mknod --mode 666 /vz/private/123/dev/null c 1 3
5) mknod --mode 444 /vz/private/123/dev/urandom c 1 9
6) mknod --mode 444 /vz/private/123/dev/random c 1 8

Последние два нужно обязательно, но по-отдельности опционально.
Реально можно отказаться от использования /dev/random, если поставить флаг USE="urandom" (справедливо для arp). Но, иной софт может и не понять.

Все это можно делать не останавливая VPS, поскольку делается в папке private.

После изменений перезапускаем:
# vzctl stop 123
# vzctl start 123

----- СПОСОБ 2 (спасибо анонимному читателю за подсказку, этот способ проще и корректнее):

1) редактируем файл /vz/private/123/etc/conf.d/rc
в нем меняем RC_DEVICE_TARBALL="yes"

2) После изменений перезапускаем VPS:
# vzctl stop 123
# vzctl start 123

Внутри самой VPS создаем устройства:
2) mknod --mode 444 /dev/urandom c 1 9
3) mknod --mode 444 /dev/random c 1 8

Теперь при ребуте они будут создаваться автоматически.

-----

Дополнительные ссылки:
http://forum.openvz.org/index.php?t=msg&goto=5140
http://wiki.openvz.org/Physical_to_VE#.2Fdev.2Furandom
http://mlblog.osdir.com/openvz.user/2006-01/index.shtml

пятница, 21 декабря 2007 г.

USB Linux Scanner "Segmentation fault" error

Замучался со сканером Canon Canoscan LiDe25 на Mandriva 2008. Из за попытки сканирования падает OpenOffice, Kooka, XSane ни слова при этом не произнося. Причем находит его нормально.

Еще под рутом сканер работает замечательно, под юзером - нет. Собственно, что имеем:


$ sane-find-scanner
found USB scanner (vendor=0x04a9 [Canon], product=0x2220 [CanoScan], chip=LM9832/3) at libusb:004:002
# Your USB scanner was (probably) detected. It may or may not be supported by
# SANE. Try scanimage -L and read the backend's manpage.
$ scanimage -L
Segmentation fault

Интернет на это оказался на удивление беден, замучался искать на тему глюком сканера, политики безопасности, подключения устройств и т.п. В итоге все свелось только к совету читать дебаг-информацию:

$ export SANE_DEBUG_DLL=255
$ scanimage -L
...
[dll] add_backend: adding backend `v4l'
[dll] add_backend: adding backend `hp_rts88xx'
[dll] add_backend: adding backend `epkowa'
[dll] add_backend: adding backend `smfp'
[dll] sane_get_devices
[dll] load: searching backend `smfp' in `/usr/lib/sane'
[dll] load: trying to load `/usr/lib/sane/libsane-smfp.so.1'
[dll] load: dlopen()ing `/usr/lib/sane/libsane-smfp.so.1'
[dll] init: initializing backend `smfp'
Segmentation fault
$ sudo scanimage -L
...
device `plustek:libusb:004:002' is a Canon LiDE25 USB flatbed scanner


Ага, предпологаем, что вся беда в smfp, тем более, что сканер использует бэкенд plustek (бекенды можно подсмотреть тут).
Идем в /etc/sane.d/dll.conf и комментируем последнюю строчку, попутно читая комментарий:

#
# The following backends are not included in the sane-backends distribution
# If you want to use them, download them from their webpages and read their
# documentation
#
# HPLIP backend homepage: http://hplip.sf.net/
# Uncomment the following line if hpaio is installed:
#hpaio
hp_rts88xx
#primascan
epkowa
#smfp

Готово.
Честно говоря, я так и понял почему под рутом оно работало, но разбираться времени нет, поэтому пусть будет так.

пятница, 14 декабря 2007 г.