#!/usr/bin/perl
#
#   snaked: cool cron replacement.
#
#
#   petya@kohts.ru
#
#

BEGIN {
  use Cwd;
  use FindBin;

  # without this chroot abs_path (below)
  # returns empty string if daemon is called
  # from some shell script and current directory
  # is a home directory of a user (permissions?)
  chroot('/');

  $ENV{'MY_BIN'} = "$FindBin::Bin";
  $ENV{'MY_LIB'} = Cwd::abs_path("$ENV{'MY_BIN'}/../lib");
  if (! -d $ENV{'MY_LIB'}) {
    $ENV{'MY_LIB'} = Cwd::abs_path("$ENV{'MY_BIN'}/lib");
  }

  $ENV{'MY_ETC'} = Cwd::abs_path("$ENV{'MY_BIN'}/../etc");
  $ENV{'MY_ROOT'} = Cwd::abs_path("$ENV{'MY_BIN'}/../../../..");

  if (!$ENV{'PS_SNAKED_LIB'}) {
    $ENV{'PS_SNAKED_LIB'} = $ENV{'MY_LIB'};
  }
};

use strict;
use warnings;

use lib "$ENV{'MY_LIB'}";
use lib "$ENV{'PS_SNAKED_LIB'}";
use snaked;
use Yandex::Tools;
use Yandex::Tools::ProcessList;

package snaked::Daemon;
use Storable;
use Time::HiRes qw/usleep/;
use Schedule::Cron::Events;
use Time::Local;
use POSIX;
use IO::Handle; # autoflush
use Socket; # socketpair
use Fcntl;

my $version = '($Id: snaked 4283 2010-08-10 11:02:21Z kohts $)';

my ($daemon_regexp_configured, $daemon_match_cfg, $daemon_match_cfg1, $daemon_match_nocfg, $watchdog_match, $watchdog_match1);

my $my_path;
my $my_command_line;
my $watchdogs2maintain = 1;

$snaked::Daemon::runtime = {
  "type" => "master",

  "flags" => {
    "stop" => 0,
    "refresh_configuration" => 0,
    },

  "children" => {},
  "children_cache" => {},

  "usec_2check_watchdog" => 0,
  "usec_2refresh_configuration" => 0,
  
  "start_time" => time(),

  "current_tasks" => {},

  "tasks" => {},
  "task_groups" => {},

  "config" => {},

  };

sub sigTERM_handler {
#  Yandex::Tools::do_log("snaked $$ term");
  if ($snaked::Daemon::runtime->{'type'} eq 'master') {
    $snaked::Daemon::runtime->{'flags'}->{'stop'} = 1;
  }
  elsif ($snaked::Daemon::runtime->{'type'} eq 'watchdog') {
    exit;
  }
}
sub sigHUP_handler {
  if ($snaked::Daemon::runtime->{'type'} eq 'master') {
    $snaked::Daemon::runtime->{'flags'}->{'refresh_configuration'} = 1;
    $snaked::Daemon::runtime->{'flags'}->{'refresh_configuration_logged'} = 0;
  }
}
sub sigUSR1_handler {
}
sub sigUSR2_handler {
  if ($snaked::Daemon::runtime->{'type'} eq 'master') {
    # do not restart if alreadying being stopped
    if (!$snaked::Daemon::runtime->{'flags'}->{'stop'}) {
      $snaked::Daemon::runtime->{'flags'}->{'restart'} = 1;
    }
  }
}

$SIG{'TERM'} = \&sigTERM_handler;
$SIG{'HUP'} = \&sigHUP_handler;
$SIG{'USR1'} = \&sigUSR1_handler;
$SIG{'USR2'} = \&sigUSR2_handler;

sub help() {
  print '
    snaked -- cron as it should be
    ' . $version . '

    command-line options:
      
      start-up type:
        --daemon    -- run in background
        --debug     -- run in foreground with debug output

      runtime control:
        --restart   -- schedule restart for currently running daemon
                       (valid only for backgrounded daemon)
        --configure -- schedule reread of configuration
        --status    -- is there daemon running?
        --stop      -- schedule stop for currently running daemon

      configuration:
        --show-jobs   -- show configured daemon jobs
        --version     -- show daemon version

';
  exit 0;
}

sub config_value {
  my ($option_name) = @_;

  my $config = $snaked::Daemon::runtime->{'config'};
  if ($config->{$option_name}) {
    return $config->{$option_name}->{'value'};
  }
  
  return undef;
}

sub do_err_log {
  my ($msg) = @_;

  my $config = $snaked::Daemon::runtime->{'config'};

  if (config_value('log_errors')) {
    my $tmp_log = Yandex::Tools::get_log_filename();
    Yandex::Tools::set_log_filename(config_value('log_errors'));
    
    # just in case it fails
    my $res = eval {
      Yandex::Tools::do_log($msg);
    };

    Yandex::Tools::set_log_filename($tmp_log);
  }
}

sub run_task {
  my ($task_name) = @_;
  
  my $config = $snaked::Daemon::runtime->{'config'};
  my $task = $snaked::Daemon::runtime->{'tasks'}->{$task_name};

  my $old_job_name;
  $old_job_name = $ENV{'JOB_NAME'} if defined($ENV{'JOB_NAME'});
  $ENV{'JOB_NAME'} = $task_name;

  Yandex::Tools::debug("running task [$task_name] timeout [$task->{'execution_timeout'}] kill timeout [$task->{'kill_timeout'}]");
  my $o = Yandex::Tools::run_forked($task->{'cmd'}, {
    'timeout' => $task->{'execution_timeout'},
    'terminate_on_parent_sudden_death' => 1,
    'terminate_on_signal' => 'TERM',
    'terminate_wait_time' => $task->{'kill_timeout'},
    'clean_up_children' => 1,
    });
  Yandex::Tools::debug("finished [$task_name]: " . Yandex::Tools::safe_string($o->{'exit_code'}));

  $ENV{'JOB_NAME'} = $old_job_name if $old_job_name;

  if ($o->{'parent_died'}) {
    do_err_log("[$$] my parent died, exiting");
    Yandex::Tools::do_log("[$$] my parent died, exiting");
    exit 1;
  }
  elsif ($o->{'err_msg'}) {
    if (! defined($task->{'disable_notifications'})) {
      # save first failure time (this is only valid during
      # child life, parent will set this again in its
      # memory space after child returns $o->{'err_msg'})
      $task->{'runtime'}->{'first_failure_time'} = time()
        unless $task->{'runtime'}->{'first_failure_time'};

      # do not notify more often than once
      # each $task->{'notification_interval'} seconds
      # (notify after each failure if not defined)
      if ($task->{'notification_interval'}) {
        if ($task->{'runtime'}->{'first_failure_time'} + $task->{'notification_interval'} < time()) {
          Yandex::Tools::send_mail({
            'to' => ($task->{'admin_email'} ? $task->{'admin_email'} : $config->{'admin_email'}->{'value'}),
            'subject' => $0 . ": $task_name warning",
            'body' => $o->{'err_msg'},
            'no_cc_all' => 1,
            });
          
          # pretend that everything went fine
          # (this will make parent reset first_failure_time)
          return "";
        }
      }
      else {
        Yandex::Tools::send_mail({
          'to' => ($task->{'admin_email'} ? $task->{'admin_email'} : $config->{'admin_email'}->{'value'}),
          'subject' => $0 . ": $task_name warning",
          'body' => $o->{'err_msg'},
          'no_cc_all' => 1,
          });
      
        # if we have no notification interval
        # do not mask errors from parent --
        # so it can log them (!)
        #
      }
    }

    # if notification was not sent --
    # let parent know that we had problem
    # and it should set first_failure_time
    # (if not set)
    return $o->{'err_msg'};
  }

  return "";
}

sub add_child {
  my ($type, $id, $opts) = @_;

  $opts = {} unless $opts;

  Yandex::Tools::die ("Programmer error: add_child expects at least child type")
    unless $type;

  my $child_socket;
  my $parent_socket;

  socketpair($child_socket, $parent_socket, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ||
    Yandex::Tools::die ("socketpair: $!");

  $child_socket->autoflush(1);
  $parent_socket->autoflush(1);

  my $pid;

  if ($pid = fork) {
    # we are a parent
    close $parent_socket;

    my $flags = 0;
    fcntl($child_socket, F_GETFL, $flags) || die "can't fnctl F_GETFL: $!";
    $flags |= O_NONBLOCK;
    fcntl($child_socket, F_SETFL, $flags) || die "can't fnctl F_SETFL: $!";

    my $child = {
      'pid' => $pid,
      'type' => $type,
      'borntime' => time(),
      'killtime' => 0,
      'id' => $id,
      'child_socket' => $child_socket,
      'output' => '',
      };

    $snaked::Daemon::runtime->{'children'}->{'by_pid'}->{$pid} = $child;
    $snaked::Daemon::runtime->{'children'}->{'by_type'}->{$type}->{$pid} = $child;

    child_started($type);
  }
  else {
    Yandex::Tools::die("cannot fork: $!") unless defined $pid;

    $snaked::Daemon::runtime->{'type'} = "child";
    close $child_socket;

    my $r = run_task($type);

    while ($r =~ /([^\r\n]+?)([\r\n]|$)/sg) {
      my $s = $1;
      my $e = $2;

      print $parent_socket "$s\n";
    }

    close($parent_socket);
    exit 0;
  }
}

sub get_child_cache {
  my ($name) = @_;

  if (!defined($snaked::Daemon::runtime->{'children_cache'}->{$name})) {
    $snaked::Daemon::runtime->{'children_cache'}->{$name} = {
      'laststart' => 0
      };
  }
  return $snaked::Daemon::runtime->{'children_cache'}->{$name};
}

sub find_child {
  my ($id, $type) = @_;

  foreach my $v (values %{$snaked::Daemon::runtime->{'children'}->{'by_pid'}}) {
    
    if ($id) {
      # search for child by its id (and type if specified)

      if ($v->{'id'} && $v->{'id'} eq $id &&
        ($type && $v->{'type'} eq $type || !$type)
        ) {

        return $v;
      }
    }
    elsif ($type) {
      # returns first child of specified type
      if ($v->{'type'} eq $type) {
        return $v;
      }
    }
  }
  
  return undef;
}

sub child_started {
  my ($name) = @_;
  $snaked::Daemon::runtime->{'children_cache'}->{$name}->{'laststart'} = time();
}

sub child_finished {
  my ($name, $output) = @_;
  $snaked::Daemon::runtime->{'children_cache'}->{$name}->{'lastfinish'} = time();

  if ($output) {
    do_err_log("[$name]: $output");
    Yandex::Tools::do_log("[$name]: $output");
  }
}

# reads output from child if any
# (so it can't overflow IPC buffer)
#
sub manage_child {
  my ($pid) = @_;

  my $child = $snaked::Daemon::runtime->{'children'}->{'by_pid'}->{$pid};

  my $child_socket = $child->{'child_socket'};
  my $child_output = "";
  while (my $l = <$child_socket>) {
    $child_output .= $l;
  }
  
  $child->{'output'} .= $child_output;
}

sub remove_child {
  my ($pid) = @_;

  Yandex::Tools::die("Programmer error: remove_child called on child which hasn't finished yet")
    if waitpid($pid,WNOHANG) ne -1;

  my $child = $snaked::Daemon::runtime->{'children'}->{'by_pid'}->{$pid};
  my $task = $snaked::Daemon::runtime->{'tasks'}->{$child->{'type'}};

  close($child->{'child_socket'});

  # if child has output -- then it had some situation
  # which requires user invervention; save failure time
  #
  # (unset when child returns nothing --
  # meaning intervention is no longer needed)
  #
  if ($child->{'output'}) {
    $task->{'runtime'}->{'first_failure_time'} = time()
      unless $task->{'runtime'}->{'first_failure_time'};

    Yandex::Tools::debug("child output: " . $child->{'output'});

    if (! defined($task->{'disable_notifications'})) {
      # reset failure interval counter,
      # so we do not send notifications
      # more ofthen than notification_interval
      if ($task->{'runtime'}->{'first_failure_time'} + $task->{'notification_interval'} < time()) {
        $task->{'runtime'}->{'first_failure_time'} = time();
      }
    }
  }
  else {
    # delete first error time so next failure time will be saved
    delete($task->{'runtime'}->{'first_failure_time'});
  }

  child_finished($child->{'type'}, $child->{'output'});

  my $child_group = find_group_by_task(undef, $child->{'type'});
  delete $snaked::Daemon::runtime->{'current_tasks'}->{$child_group}->{$child->{'type'}};

  delete $snaked::Daemon::runtime->{'children'}->{'by_type'}->{$child->{'type'}}->{$pid};
  delete $snaked::Daemon::runtime->{'children'}->{'by_pid'}->{$pid};
}

sub have_children {
  my $have_children = 0;
  foreach my $k (keys %{$snaked::Daemon::runtime->{'children'}->{'by_pid'}}) {
    $have_children = 1;
    last;
  }
  return $have_children;
}

sub for_each_child {
  my ($opts) = @_;

  $opts = {} unless $opts;
  foreach my $k (keys %{$snaked::Daemon::runtime->{'children'}->{'by_pid'}}) {
    if ($opts->{'stop_now'}) {
#      Yandex::Tools::do_log("killing $k");
      kill(15, $k); # TERM (default for run_forked)
    }
  }
}

sub find_group_by_task {
  my ($task_groups, $task_name) = @_;
  $task_groups = $snaked::Daemon::runtime->{'task_groups'} unless $task_groups;

  foreach my $tg (keys %{$task_groups}) {
    if ($task_groups->{$tg}->{$task_name}) {
      return $tg;
    }
  }

  return undef;
}

# break $snaked::Daemon::Runtime->{'tasks'}
# (configured in /etc/ps-farm/options/ps-snaked/jobs)
# into task groups by dependency
#
sub prepare_task_groups {
  my $task_groups = $snaked::Daemon::runtime->{'task_groups'};
  my $tasks = $snaked::Daemon::runtime->{'tasks'};

  foreach my $task_name (keys %{$tasks}) {
    my $task = $tasks->{$task_name};
    
    my $attach_task_to_group = sub {
      my ($group_name, $task_name) = @_;

      foreach my $tg (keys %{$task_groups}) {
        if ($tg eq $group_name) {
          $task_groups->{$tg}->{$task_name} = 1;
        }
        elsif ($task_groups->{$tg}->{$task_name}) {
          delete ($task_groups->{$tg}->{$task_name});
        }
      }
    };

    # prepare task groups by dependencies

    # attach task to the group where
    # its conflicting tasks are
    #
    if ($task->{'conflicts'}) {
      my $conflicting_groups = {};

      foreach my $c_task (@{$task->{'conflicts'}}) {
        my $conflicting_tg = find_group_by_task($task_groups, $c_task);

        if ($conflicting_tg) {
          $conflicting_groups->{$conflicting_tg} = 1;
        }
      }

      if (scalar(keys %{$conflicting_groups}) > 1) {
        # found conflicting tasks in different task groups,
        # merging those groups into one

        # choose any
        my $dest_group;
        foreach (keys %{$conflicting_groups}) {
          $dest_group = $_;
          last;
        }

        # and attach all tasks from all the conflicting groups
        # to the destination group
        foreach my $tg (keys %{$conflicting_groups}) {
          foreach my $tt (keys %{$task_groups->{$tg}}) {
            $task_groups->{$dest_group}->{$tt} = 1;
          }

          if ($tg && $tg ne $dest_group) {
            delete($task_groups->{$tg});
          }
        }
      }
      elsif (scalar(keys %{$conflicting_groups}) eq 1) {
        # found conflicting tasks in one group,
        # attaching the task to this group

        my $dest_group;
        foreach (keys %{$conflicting_groups}) {
          $dest_group = $_;
          last;
        }

        $attach_task_to_group->($dest_group, $task_name);
      }
      else {
        # conflicting tasks are not in any group,
        # should attach them to the group where
        # this task will be
        
        my $tg = find_group_by_task($task_groups, $task_name);
        
        # current task is not in any group --
        # creating new group with its name
        if (!$tg) {
          $tg = $task_name;
          $task_groups->{$tg} = {};
          
          $attach_task_to_group->($tg, $task_name);
        }

        foreach my $ctg (@{$task->{'conflicts'}}) {
          $attach_task_to_group->($tg, $ctg);
        }
      }
    }

    if (! find_group_by_task($task_groups, $task_name)) {
      $task_groups->{$task_name} = {};
      $attach_task_to_group->($task_name, $task_name);
    }
  }
}

sub refreshOptions {
  my ($dir, $opts) = @_;
  
  $opts = {} unless $opts;

  my $config = $snaked::Daemon::runtime->{'config'};
  my $tasks = Storable::dclone($snaked::Daemon::runtime->{'tasks'});
  my $tmp;

  # read daemon options
  my $new_options = {};
  my $old_log_options = Yandex::Tools::get_log_options();

  $tmp = Yandex::Tools::read_dir($dir, {'output_type' => 'arrayref', 'only-files' => 1});
  foreach my $o (@{$tmp}) {
    next if $o =~ /^\./o;

    my $fileinfo = Yandex::Tools::fileinfo_struct({'absolute_name' => $dir . "/" . $o});

    $new_options->{$o} = 1;

    # option was not modified since we've read it
    if ($config->{$o} && $config->{$o}->{'mtime'} eq $fileinfo->{'mtime'}) {
      next;
    }

    my $option_updated = ($config->{$o} ? 1 : 0);

    $config->{$o}->{'mtime'} = $fileinfo->{'mtime'};
    $config->{$o}->{'value'} = Yandex::Tools::read_file_option($dir . "/" . $o);

    if ($option_updated) {
      Yandex::Tools::do_log("new value for option $o: " . $config->{$o}->{'value'});
    }
  }

  # remove old options
  foreach my $opt_name (keys %{$config}) {
    next if $new_options->{$opt_name};
    next if $opt_name eq 'admin_email'; # should list all options which have defaults here
    
    delete ($config->{$opt_name});
    Yandex::Tools::do_log("option $opt_name removed");
  }

  if (!$config->{'admin_email'}) {
    $config->{'admin_email'} = {
      'value' => 'root',
      'mtime' => 0,
      };
  }

  # configure logging (defaults to /tmp/ps-snaked.log, three 10MB files, rotated)
  #
  my $log_options = {};
  if ($config->{'log'}) {
    $log_options->{'filename'} = $config->{'log'}->{'value'};
  }
  else {
    $log_options->{'filename'} = ($ENV{'MY_ROOT'} eq "/" ? "" : $ENV{'MY_ROOT'}) . "/tmp/snaked.log";
  }
  if ($config->{'log_rotate_size'}) {
    $log_options->{'rotate_size'} = $config->{'log_rotate_size'}->{'value'};
  }
  else {
    $log_options->{'rotate_size'} = 1024 * 1024 * 10;
  }
  if ($config->{'log_rotate_keep_copies'}) {
    $log_options->{'rotate_keep_copies'} = $config->{'log_rotate_keep_copies'}->{'value'};
  }
  else {
    $log_options->{'rotate_keep_copies'} = 2;
  }
  Yandex::Tools::set_log_options($log_options);

  if (!Yandex::Tools::get_log_options() || !Yandex::Tools::can_log()) {
    if ($old_log_options) {
      Yandex::Tools::set_log_options($old_log_options);
    }
    else {
      Yandex::Tools::warn("Can not write to log file [$log_options->{'filename'}], check permissions; logging to STDERR");
    }
  }

  # in watchdog mode we don't need
  # to read job definitions
  return if $opts->{'no-jobs'};


  my $defined_jobs = {};

  # read daemon jobs
  $tmp = Yandex::Tools::read_dir($dir . "/jobs", {'output_type' => 'arrayref', 'only-directories' => 1});
  foreach my $o (@{$tmp}) {
    next if $o =~ /^\./o;

    $defined_jobs->{$o} = 1;

    my $dirinfo = Yandex::Tools::fileinfo_struct({'absolute_name' => $dir . "/jobs/" . $o});

    # job was not modified since we've read it
    if ($tasks->{$o} && $tasks->{$o}->{'mtime'} eq $dirinfo->{'mtime'}) {
      next;
    }

    if ($tasks->{$o}) {
      Yandex::Tools::do_log("reread job [$o] from disk");
    }

    $tasks->{$o}->{'mtime'} = $dirinfo->{'mtime'};

    my $joptions = Yandex::Tools::read_dir($dir . "/jobs/" . $o, {'output_type' => 'arrayref', 'only-files' => 1});
    foreach my $jo (@{$joptions}) {
      if ($jo eq 'conflicts') {
        $tasks->{$o}->{$jo} = Yandex::Tools::read_file_array($dir . "/jobs/" . $o . "/" . $jo);
      }
      elsif ($jo eq 'cmd') {
        $tasks->{$o}->{$jo} = $dir . "/jobs/" . $o . "/" . $jo;
      }
      else {
        $tasks->{$o}->{$jo} = Yandex::Tools::read_file_option($dir . "/jobs/" . $o . "/" . $jo);
      }
    }
    if (defined($tasks->{$o}->{'disabled'}) && !$opts->{'keep_disabled'}) {
      delete($tasks->{$o});
    }
  }
  
  # cleanup removed jobs, validate tasks
  TASKS: foreach my $task_name (keys %{$tasks}) {
    if (!$defined_jobs->{$task_name}) {
      Yandex::Tools::do_log("job [$task_name] removed");
      delete($tasks->{$task_name});
      next TASKS;
    }

    my $task = $tasks->{$task_name};

    if (!defined($task->{'execution_timeout'}) || !int($task->{'execution_timeout'})) {
      $task->{'execution_timeout'} = 0;
    }
    if (!defined($task->{'kill_timeout'}) ||
      !int($task->{'kill_timeout'}) && $task->{'kill_timeout'} ne 0) {
      $task->{'kill_timeout'} = 60;
    }

    foreach my $mp ("cmd") {
      if (!$task->{$mp}) {
        Yandex::Tools::do_log("skipping job [$task_name]: mandatory parameter [$mp] not specified");
        delete($tasks->{$task_name});
        next TASKS;
      }
    }
    if (! -x $task->{'cmd'}) {
      Yandex::Tools::do_log("skipping job [$task_name]: [$task->{'cmd'}] is not executable");
      delete($tasks->{$task_name});
      next TASKS;
    }

    if ((!$task->{'execution_interval'} && !$task->{'execution_schedule'}) ||
      ($task->{'execution_interval'} && $task->{'execution_schedule'})) {
      
      Yandex::Tools::do_log("skipping job [$task_name]: one and only one of (execution_interval, execution_schedule) must be defined");
      delete($tasks->{$task_name});
      next TASKS;
    }

    if ($task->{'execution_schedule'}) {
      my $cron;
      eval {
        $cron = new Schedule::Cron::Events($task->{'execution_schedule'}, Seconds => time());
      };

      if (!$cron) {
        my $msg = $@;
        # leave only first line
        $msg =~ s/[\r\n].+$//sgo;
        # remove filename in which the error was raised
        $msg =~ s/at\ \/.+$//sgo;
        $msg = ": $msg" if $msg;

        Yandex::Tools::do_log("skipping job [$task_name]: invalid execution_schedule $msg");
        delete($tasks->{$task_name});
        next TASKS;
      }
      $task->{'cron'} = $cron;
      $task->{'next_run'} = timelocal($task->{'cron'}->nextEvent);
    }

    foreach my $dp ("execution_interval", "execution_timeout", "notification_interval", "start_random_sleep") {
      if ($task->{$dp} && !Yandex::Tools::is_digital($task->{$dp})) {
        Yandex::Tools::do_log("skipping job [$task_name]: [$dp] must be numeric");
        delete($tasks->{$task_name});
        next TASKS;
      }
    }

    if ($task->{'conflicts'} && ref($task->{'conflicts'}) ne 'ARRAY') {
      Yandex::Tools::do_log("skipping job [$task_name]: [conflicts] must be an array reference");
      delete($tasks->{$task_name});
      next TASKS;
    }
    if ($task->{'conflicts'}) {
      foreach my $c_task (@{$task->{'conflicts'}}) {
        if ($c_task eq $task_name) {
          Yandex::Tools::do_log("skipping job [$task_name]: task conflicts with itself.");
          delete($tasks->{$task_name});
          next TASKS;
        }
      }
    }
  }

  # apply new tasks, recalculate task groups
  $snaked::Daemon::runtime->{'tasks'} = $tasks;
  $snaked::Daemon::runtime->{'task_groups'} = {};
  prepare_task_groups();
}

sub canonical_command_line {
  my ($cmdline, $path) = @_;

  return "" unless $cmdline && $path;

  # suppress space in the end of command on freebsd
  $cmdline =~ s/\ +$//go;

  # replace path to the executable with full path
  #
  # notes:
  #   - regexp is not global so it replaces only 1st occurrence
  #
  #   - .+? is not greedy so it will find the 1st occurrence of
  #   "(ps-)snaked" string which should be the name of executable
  # 
  $cmdline =~ s/.+?(ps-)?snaked(\s+|$)/${path}\/snaked /;
  $cmdline =~ s/\s+$//goi;

  return $cmdline;
}

sub exec_ps_snaked {
  my ($my_command_line, $my_path) = @_;

  # on ws1-569 in snaked.log got:
  #
  # Mon Oct 19 17:59:17 2009 [/place/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked] unable to exec  --cfg /place/home/monitor/ps-snake/etc/ps-farm/options/ps-snaked
  #
  # which effectively means that $my_command_line was empty
  # after calling canonical_command_line() below
  # (" --cfg ..." was appended to it in the next step)
  #
  # so trying to determine my command line if it's empty
  # (also added check on startup that we've got it)
  #

  # as a workaround for empty command line or path (why?)
  # trying to determine them during exit process
  if (!$my_command_line || !$my_path) {
    ($my_path, $my_command_line) = Yandex::Tools::ProcessList::get_my_path_commandline({'processes' =>
      Yandex::Tools::ProcessList::get_process_table()});
  }

  $my_command_line = canonical_command_line($my_command_line, $my_path);

  # append --cfg parameter if it's not specified
  # (codepath is used only during first run
  # when path to configuration was specified
  # by environment variable)
  if ($my_command_line !~ /--cfg $ENV{'PS_SNAKED_CFG'}/) {
    $my_command_line .= " --cfg $ENV{'PS_SNAKED_CFG'}";
  }

  # set environment variable to specify that we want to cleanup
  # already running snaked processes (this might be workaround
  # for some FreeBSD or Proc::ProcessTable on FreeBSD bug,
  # which caused the following:
  #
  # Thu Jun 24 10:29:31 2010 [/opt/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked] clock moved back from Thu Jun 24 10:29:25 2010 to Thu Jun 24 10:29:24 2010, restarting
  # Thu Jun 24 10:29:38 2010 [/opt/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked] [24836] requested to restart
  # Thu Jun 24 10:29:38 2010 [/opt/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked] [24836] stopped
  # Thu Jun 24 10:29:54 2010 [/opt/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked] [WARN] [29246] snaked is already running: /usr/bin/perl /opt/home/monitor/ps-snake/usr/local/ps-snake/bin/snaked --daemon --cfg /opt/home/monitor/ps-snake/etc/ps-farm/options/ps-snaked  [24836]
  #
  # [monitor@orange64 ~]$ uname -a
  # FreeBSD orange64.yandex.ru 7.2-STABLE FreeBSD 7.2-STABLE #0 r199991M: Mon Feb  8 12:50:25 MSK 2010     root@distillatory.yandex.ru:/place/tmp/mk_pkg.wG1LSf1f/obj/place/GIT-repos/FreeBSD-7-r199991/sys/PRODUCTION  amd64
  #
  # Proc::ProcessTable 0.54
  #
  $ENV{'snaked_cleanup_already_running'} = 1;

  Yandex::Tools::exec($my_command_line);
}

# spawn additional watchdogs slowly
sub manage_watchdogs {
  my $ptable = Yandex::Tools::ProcessList::get_process_table();

  my $number_of_watchdogs = 0;
  # get the ps-snaked daemon process for which the watchdog is running
  my $my_process = undef;
  foreach my $p (@$ptable) {
    next unless $p->cmndline;
    next if !Yandex::Tools::matches_with_one_of_regexps($p->cmndline, [$watchdog_match, $watchdog_match1]);

    $number_of_watchdogs = $number_of_watchdogs + 1;
  }

  if ($number_of_watchdogs < $watchdogs2maintain) {
    my $t_cmdline = $my_command_line;
    $t_cmdline = canonical_command_line($t_cmdline, $my_path);
    $t_cmdline =~ s/\-\-daemon/\-\-watchdog/;
    Yandex::Tools::run_forked($t_cmdline);
  }
}

sub stop_watchdogs {
  my $ptable = Yandex::Tools::ProcessList::get_process_table();

  # get the ps-snaked daemon process for which the watchdog is running
  my $my_process = undef;
  foreach my $p (@$ptable) {
    next unless $p->cmndline;
    next if !Yandex::Tools::matches_with_one_of_regexps($p->cmndline, [$watchdog_match, $watchdog_match1]);
    
    kill (15, $p->pid);
  }
}

# watchdog mode, starts ps-snaked daemon
# if finds that it's not running
sub run_watchdog {

  # set daemon type to change signal handling slightly
  $snaked::Daemon::runtime->{'type'} = 'watchdog';

  my $unsuccessful_tries = 0;
  my $life_time = 3600 * (rand($watchdogs2maintain) + 1);

  while(1) {
    # stop watchdogs from time to time to toss
    # their pid numbers (which might affect oom killers),
    # but not in case they detect that main process
    # is not running (and waiting a bit to start it)
    # 
    # watchdogs are restarted by main daemon.
    # 
    if ((time() - $snaked::Daemon::runtime->{'start_time'}) > $life_time && !$unsuccessful_tries) {
      exit(0);
    }

    if ($snaked::Daemon::runtime->{'usec_2check_watchdog'} < 1) {
      my $ptable = Yandex::Tools::ProcessList::get_process_table();

      my $currently_running_watchdogs = 0;

      # get the ps-snaked daemon process for which the watchdog is running
      my $my_process = undef;

      foreach my $p (@$ptable) {
        my $p_cmndline;
        my $r = Yandex::Tools::ProcessList::code_may_fail(sub {$p_cmndline = $p->cmndline});

        next unless $p_cmndline;

        if (Yandex::Tools::matches_with_one_of_regexps($p_cmndline, [$watchdog_match, $watchdog_match1])) {
          $currently_running_watchdogs = $currently_running_watchdogs + 1;
        }
        elsif (Yandex::Tools::matches_with_one_of_regexps($p_cmndline, [$daemon_match_cfg, $daemon_match_cfg1])) {
          # at this point any snaked is selected
          # (even that which is starting
          # or running external command)

          my $p_pid;
          my $p_ppid;
          my $p_pgrp;
          $r = Yandex::Tools::ProcessList::code_may_fail(sub {$p_pid = $p->pid});
          $r = Yandex::Tools::ProcessList::code_may_fail(sub {$p_ppid = $p->ppid});
          $r = Yandex::Tools::ProcessList::code_may_fail(sub {$p_pgrp = $p->pgrp});

          next unless $p_pid && $p_ppid && $p_pgrp;

          # real daemon is parented by init and is the process group leader,
          # if its not found -- start it, and it will clean up any
          # stuck child from previous daemon (shouldn't happen because
          # children are strongly attached to the main daemon
          # with use of terminate_on_sudden_parent_death flag of run_forked)
          if ($p_ppid eq 1 && $p_pid eq $p_pgrp) {
            $my_process = $p;
          }
        }
      }

      if ($my_process) {
        $unsuccessful_tries = 0;
      }
      else {
        $unsuccessful_tries = $unsuccessful_tries + 1;
      }

      if ($unsuccessful_tries > 0) {
        if ($unsuccessful_tries < 2) {
          # 4 seconds should be enough to start daemon
          # (if it's not found and began to start -- is restarting),
          # randomize each watchdog so they do not try to start
          # all at the same time
          # 
          sleep(4 + 4 * int(rand($currently_running_watchdogs)));
        }
        else {
          Yandex::Tools::do_log("watchdog [$$]: snaked not found (killed?), respawning");
          # replace --watchdog with --daemon
          my $t_cmdline = $my_command_line;
          $t_cmdline =~ s/\-\-watchdog/\-\-daemon/;

          # try to execute daemon instead of watchdog
          # if fork fails (wouldn't succeed probably,
          # but could we try at least?)
          #
          if (defined(my $pid = fork)) {
            if ($pid) {
              my $waitpid;
              
              # exec_ps_snaked forks before actually execing snaked
              # and parent exits immediately (which makes it
              # totally detached from watchdog)
              #
              while ($waitpid ne -1) {
                $waitpid = waitpid($pid, WNOHANG);
                sleep 1;
              }

              # watchdog to continue
              $unsuccessful_tries = 0;
            }
            else {
              # watchdog to become snaked
              # (detached from parent totally)
              exec_ps_snaked($t_cmdline, $my_path);
            }
          }
          else {
            exec_ps_snaked($t_cmdline, $my_path);
          }
        }
      }

      $snaked::Daemon::runtime->{'usec_2check_watchdog'} = ($watchdogs2maintain + 1) * 2 * 2000000;
    }

    usleep(50000);
    $snaked::Daemon::runtime->{'usec_2check_watchdog'} = $snaked::Daemon::runtime->{'usec_2check_watchdog'} - 50000;
  }
  exit (255);
}

sub get_cfg_path {
  if (!$ENV{'PS_SNAKED_CFG'}) {
    if (Yandex::Tools::defined_cmdline_param('cfg')) {
      $ENV{'PS_SNAKED_CFG'} = Yandex::Tools::get_cmdline_param('cfg');

      if (! -d "$ENV{'PS_SNAKED_CFG'}") {
        die "Configuration does not exist: $ENV{'PS_SNAKED_CFG'}\n";
      }
    }
    else {
      $ENV{'PS_SNAKED_CFG'} = $ENV{'MY_ETC'};

      if (! -d $ENV{'PS_SNAKED_CFG'}) {
        $ENV{'PS_SNAKED_CFG'} = ($ENV{'MY_ROOT'} eq "/" ? "" : $ENV{'MY_ROOT'}) .
          "/etc/ps-farm/options/ps-snaked";
      }
      if (! -d $ENV{'PS_SNAKED_CFG'} && -d "/etc/ps-farm/options/ps-snaked") {
        $ENV{'PS_SNAKED_CFG'} = "/etc/ps-farm/options/ps-snaked";
      }
      if (! -d $ENV{'PS_SNAKED_CFG'} && -d "/etc/snaked") {
        $ENV{'PS_SNAKED_CFG'} = "/etc/snaked";
      }
    }
  }

  if (! -d "$ENV{'PS_SNAKED_CFG'}") {
    $ENV{'PS_SNAKED_CFG'} = undef;
  }
  else {
    if (!$daemon_regexp_configured) {
      $daemon_match_cfg = qr/^([^\s]+perl[^\s]*[\s]+|)[^\s]+(ps-)?snaked.+(daemon|debug).+cfg.+$ENV{'PS_SNAKED_CFG'}/;
      $daemon_match_cfg1 = qr/^([^\s]+perl[^\s]*[\s]+|)[^\s]+(ps-)?snaked.+cfg.+$ENV{'PS_SNAKED_CFG'}.+(daemon|debug)/;
      $daemon_match_nocfg = qr/^([^\s]+perl[^\s]*[\s]+|)[^\s]+(ps-)?snaked.+(daemon|debug)/;
      $watchdog_match = qr/^([^\s]+perl[^\s]*[\s]+|)[^\s]+(ps-)?snaked.+(watchdog).+cfg.+$ENV{'PS_SNAKED_CFG'}/;
      $watchdog_match1 = qr/^([^\s]+perl[^\s]*[\s]+|)[^\s]+(ps-)?snaked.+cfg.+$ENV{'PS_SNAKED_CFG'}.+(watchdog)/;
      $daemon_regexp_configured = 1;
    }
  }

  return $ENV{'PS_SNAKED_CFG'};
}

Yandex::Tools::read_cmdline();
get_cfg_path();
Yandex::Tools::ProcessList::set_options({
  'daemon_match' => [$daemon_match_cfg, $daemon_match_cfg1],
  'daemon_match_startup' => [$daemon_match_nocfg],
  });

if (Yandex::Tools::defined_cmdline_param('stop')) {
  my $d = Yandex::Tools::ProcessList::get_other_daemon_process();
  if ($d) {
    print "requesting " . $d->pid() . " [" . $d->cmndline . "] to stop\n";
    kill (15, $d->pid);
  }
  else {
    print "no snaked daemon found for $ENV{'PS_SNAKED_CFG'}\n";
  }
  exit 0;
}
elsif (Yandex::Tools::defined_cmdline_param('configure')) {
  my $d = Yandex::Tools::ProcessList::get_other_daemon_process();
  if ($d) {
    print "requesting " . $d->pid() . " [" . $d->cmndline . "] to refresh configuration\n";
    kill ("HUP", $d->pid)
  }
  else {
    print "no snaked daemon found for $ENV{'PS_SNAKED_CFG'}\n";
  }
  exit 0;
}
elsif (Yandex::Tools::defined_cmdline_param('restart')) {
  my $d = Yandex::Tools::ProcessList::get_other_daemon_process();
  if ($d) {
    if (!Yandex::Tools::defined_cmdline_param('only-errors')) {
      print "requesting " . $d->pid() . " [" . $d->cmndline . "] to restart\n";
    }
    kill ("USR2", $d->pid)
  }
  else {
    print "no snaked daemon found for $ENV{'PS_SNAKED_CFG'}\n";
  }
  exit 0;
}
elsif (Yandex::Tools::defined_cmdline_param('status')) {
  my $d = Yandex::Tools::ProcessList::get_other_daemon_process();
  if ($d) {
    print "snaked is running as pid " . $d->pid . ". command line [" . $d->cmndline . "]\n";
  }
  else {
    print "no daemon running\n";
  }
  exit 0;
}
elsif (Yandex::Tools::defined_cmdline_param('show-jobs')) {
  refreshOptions($ENV{'PS_SNAKED_CFG'}, {'keep_disabled' => 1});
  print "    global options\n";
  foreach my $k (sort keys %{$snaked::Daemon::runtime->{'config'}}) {
    print "      $k: " . $snaked::Daemon::runtime->{'config'}->{$k}->{'value'} . "\n";
  }

  print "    configured jobs:\n";
  foreach my $job_name (sort keys %{$snaked::Daemon::runtime->{'tasks'}}) {
    print "      " . $job_name . "\n";
    my $job = $snaked::Daemon::runtime->{'tasks'}->{$job_name};
    foreach my $o (sort keys %{$job}) {
      print "        $o: ";
      if (ref($job->{$o}) eq 'ARRAY') {
        print join(",", @{$job->{$o}});
      }
      else {
        print $job->{$o};
      }
      print "\n";
    }
  }
  exit 0;
}
elsif (Yandex::Tools::defined_cmdline_param('version')) {
  print "$version\n";
  exit 0;
}

my $i_am_watchdog = Yandex::Tools::defined_cmdline_param('watchdog');

if (!Yandex::Tools::defined_cmdline_param('daemon') && !Yandex::Tools::defined_cmdline_param('debug') && !$i_am_watchdog) {
  help();
  exit 0;
}

if (!get_cfg_path()) {
  die "no configuration found (default /etc/snaked)";
}
refreshOptions($ENV{'PS_SNAKED_CFG'}, {'no-jobs' => $i_am_watchdog});
if (config_value('log_errors')) {
  if (!Yandex::Tools::can_write(config_value('log_errors'))) {
    Yandex::Tools::warn("Can not write to log_errors file [" . config_value('log_errors') .
      "], check permissions.");
    delete($snaked::Daemon::runtime->{'config'}->{'log_errors'});
  }
}

if (!$i_am_watchdog) {
  my $d = Yandex::Tools::ProcessList::get_other_daemon_process();
  if ($d) {
    if (!$ENV{'snaked_cleanup_already_running'}) {
      Yandex::Tools::warn("[$$] snaked is already running: " . $d->cmndline . " ["  . $d->pid . "]");
      exit 1;
    }
    else {
      my $previous_snaked = $d;
      $ENV{'snaked_cleanup_already_running'} = undef;
      kill(-9, $d->pid);
      sleep 3;
      $d = Yandex::Tools::ProcessList::get_other_daemon_process({'refresh_startup_processes' => 1});
      if ($d) {
        Yandex::Tools::warn("[$$] snaked is already running: " . $d->cmndline . " ["  . $d->pid . "] and doesn't stop on KILL signal");
        exit 1;
      }
      else {
        Yandex::Tools::warn("[$$] killed previously running snaked: " . $previous_snaked->cmndline . " ["  . $previous_snaked->pid . "], continuing to start");
      }
    }
  }
}

($my_path, $my_command_line) = Yandex::Tools::ProcessList::get_my_path_commandline();

Yandex::Tools::debug("my_path: $my_path");
Yandex::Tools::debug("my_command_line: $my_command_line");

print "starting snaked daemon for $ENV{'PS_SNAKED_CFG'}\n"
  unless $i_am_watchdog;

if (Yandex::Tools::defined_cmdline_param('daemon') || $i_am_watchdog) {
  # restart daemon using its full pathname and config path
  # if it was not started like this (so we could distinguish
  # between daemons by their locations)
  if ($my_command_line !~ /$my_path/ || $my_command_line !~ /--cfg $ENV{'PS_SNAKED_CFG'}/) {
    sigUSR2_handler();
  }

  Yandex::Tools::daemonize();

  # run watchdog (except for when snaked
  # would be restarted right after start)
  if ($i_am_watchdog && !$snaked::Daemon::runtime->{'flags'}->{'restart'}) {
    run_watchdog();
    exit;
  }
}
elsif ($Yandex::Tools::debug) {
  # stay in foreground
}

Yandex::Tools::do_log("[$$] started");

if ($snaked::Daemon::runtime->{'config'}->{'pidfile'} &&
  !$snaked::Daemon::runtime->{'flags'}->{'restart'} &&
  !$i_am_watchdog) {

  if (Yandex::Tools::can_write($snaked::Daemon::runtime->{'config'}->{'pidfile'}->{'value'})) {
    Yandex::Tools::write_file_option($snaked::Daemon::runtime->{'config'}->{'pidfile'}->{'value'}, $$);
  }
}

my $previous_now;
my $current_now;

my $max_job_time = config_value('max_job_time');
$max_job_time = 3600 * 2 unless $max_job_time;

while (1) {
  $previous_now = $current_now;
  $current_now = time();

  # clock moved back -- restarting
  if ($previous_now && $current_now && ($previous_now > $current_now)) {
    Yandex::Tools::do_log("clock moved back from " . localtime($previous_now) . " to " . localtime($current_now) . ", restarting");
    sigUSR2_handler();
  }

  if (!$snaked::Daemon::runtime->{'flags'}->{'restart'}) {
    if ($snaked::Daemon::runtime->{'usec_2check_watchdog'} < 1) {
      manage_watchdogs() if $watchdogs2maintain;
      $snaked::Daemon::runtime->{'usec_2check_watchdog'} = ($watchdogs2maintain + 1) * 2 * 2000000;
    }
  }

  my $have_active_children = values %{$snaked::Daemon::runtime->{'children'}->{'by_pid'}};
  Yandex::Tools::debug("active children:") if $have_active_children;

  # check status of all children removing those which finished
  foreach my $v (values %{$snaked::Daemon::runtime->{'children'}->{'by_pid'}}) {
    
    # minimize time() call a bit
    my $now = time();

    # check for really long running processes
    # and kill them brutally (not very fast
    # if killing doesn't work; blocking io?)
    #
    if (($now - $v->{'borntime'}) > $max_job_time && ($now - $v->{'killtime'}) > 5) {
      # kill first then log, because logging might fail
      # which leads to "die"

      # killing exactly child pid, which is only a "manager"
      # for the task; open3_run which is executed inside the child
      # checks whether manager is alive and terminates if not,
      # so killing manager notifies child that it should stop.
      kill(9, $v->{'pid'});
      $v->{'killtime'} = time();

      do_err_log("killed long running (". ($now - $v->{'borntime'}) .
        " seconds) process [$v->{'pid'}] [$v->{'type'}]", {"stderr" => 1});
      Yandex::Tools::do_log("killed long running (". ($now - $v->{'borntime'}) .
        " seconds) process [$v->{'pid'}] [$v->{'type'}]", {"stderr" => 1});
    }

    my $waitpid = waitpid($v->{'pid'}, WNOHANG);
    
    Yandex::Tools::debug("\tchild [$v->{'pid'}] [$v->{'type'}] [" . ($v->{'id'} ? $v->{'id'} : "") . "]: $waitpid;".
      " running " . (time() - $v->{'borntime'}) . " seconds");

    manage_child($v->{'pid'});

    if ($waitpid eq -1) {
      remove_child($v->{'pid'});
    }
  }

  if ($snaked::Daemon::runtime->{'flags'}->{'refresh_configuration'} ||
    $snaked::Daemon::runtime->{'usec_2refresh_configuration'} < 1) {
    if (!have_children()) {
      if ($snaked::Daemon::runtime->{'flags'}->{'refresh_configuration'}) {
        Yandex::Tools::do_log("requested to reread configuration, rereading");
      }
      refreshOptions($ENV{'PS_SNAKED_CFG'});
      $snaked::Daemon::runtime->{'flags'}->{'refresh_configuration'} = 0;
      $snaked::Daemon::runtime->{'usec_2refresh_configuration'} = 1000000 * 60;
    }
    else {
      if ($snaked::Daemon::runtime->{'flags'}->{'refresh_configuration'} &&
        !$snaked::Daemon::runtime->{'flags'}->{'refresh_configuration_logged'}) {
        Yandex::Tools::do_log("requested to reread configuration, waiting for children to stop");
        $snaked::Daemon::runtime->{'flags'}->{'refresh_configuration_logged'} = 1;
      }
    }
  }
  if ($snaked::Daemon::runtime->{'flags'}->{'restart'}) {
    if ($Yandex::Tools::debug) {
      Yandex::Tools::warn("unable to restart attached daemon");
      $snaked::Daemon::runtime->{'flags'}->{'restart'} = 0;
    }
    else {
      if (!$snaked::Daemon::runtime->{'flags'}->{'stop'}) {
        Yandex::Tools::do_log("[$$] requested to restart");
        $snaked::Daemon::runtime->{'flags'}->{'stop'} = 1;
      }
    }
  }

  # do processing if we were not requested to stop
  unless ($snaked::Daemon::runtime->{'flags'}->{'stop'}) {

    my $task_groups = $snaked::Daemon::runtime->{'task_groups'};

    # run all the tasks one by one (task groups are concurrent)
    foreach my $tg (keys %{$task_groups}) {
      $snaked::Daemon::runtime->{'current_tasks'}->{$tg} = {}
        unless $snaked::Daemon::runtime->{'current_tasks'}->{$tg};

      my $current_tasks = $snaked::Daemon::runtime->{'current_tasks'}->{$tg};
      my $configured_tasks = $snaked::Daemon::runtime->{'tasks'};

      if (scalar(keys %{$current_tasks})) {
        # process tasks in this task group
        foreach my $task_name (keys %{$current_tasks}) {
          my $task = $configured_tasks->{$task_name};
          
          # do not start task if it's already running
          next if find_child(undef, $task_name);

          # check if there're tasks runing
          # which block this task
          my $do_not_run = 0;
          foreach my $ctask (keys %{$task_groups->{$tg}}) {
            next if $ctask eq $task_name;

            if (find_child(undef, $ctask)) {
              $do_not_run = 1;
              last;
            }
          }

          next if $do_not_run;

          Yandex::Tools::debug("starting [$task_name]");
          add_child($task_name);
        }
      }
      else {
        # we've completed all tasks, reschedule tasks which need to be executed
        foreach my $task_name (keys %{$task_groups->{$tg}}) {
          my $task = $configured_tasks->{$task_name};
          my $child_e = get_child_cache($task_name);
          my $now = time();

          if ($task->{'start_random_sleep'}) {
            if (!$child_e->{'startup_sleep'}) {
              $child_e->{'startup_sleep'} = int(rand($task->{'start_random_sleep'}));
              $child_e->{'startup_sleep_started'} = $now;
              Yandex::Tools::debug("task [$task_name] random sleep [$child_e->{'startup_sleep'}]");
            }

            if ($now - $child_e->{'startup_sleep_started'} > $child_e->{'startup_sleep'}) {
              $child_e->{'startup_sleep_finished'} = $now;
              Yandex::Tools::debug("task [$task_name] random sleep finished");
            }
          }
          else {
            # random startup sleep not configured for the task
            $child_e->{'startup_sleep_finished'} = $now;
          }

          # schedule only those tasks which:
          #   - finished random startup sleep time (if configured) AND
          #     - were not run for $task->{'execution_interval'} time or
          #     - have there next_run time passed
          #
          if ($child_e->{'startup_sleep_finished'} && (
                $task->{'next_run'} && $task->{'next_run'} <= $now
                  ||
                $task->{'execution_interval'} &&
                  $now - $child_e->{'laststart'} > $task->{'execution_interval'}
              )
            ) {

            if ($task->{'next_run'}) {
              $task->{'next_run'} = timelocal($task->{'cron'}->nextEvent);
            }

            $current_tasks->{$task_name} = $task;
          }
        }
      }
    }
  }
  else {
    # wait for children to exit and exit then
    if (have_children()) {
      for_each_child ({'stop_now' => 1});
      Yandex::Tools::debug("waiting for children to exit");
      sleep 1;
    }
    else {
      unlink($snaked::Daemon::runtime->{'config'}->{'pidfile'}->{'value'})
        if $snaked::Daemon::runtime->{'config'}->{'pidfile'};
      
      Yandex::Tools::do_log("[$$] stopped");

      # do not restart watchdogs on restart as they will try
      # to start snaked if restart fails (which should not happen
      # but happens in 0,02-0,03 % of cases)
      #
      # we may want to send some signal to watchdogs here
      # to notify them about restart so they could extend
      # their waiting cycle a bit
      #
      if ($snaked::Daemon::runtime->{'flags'}->{'restart'}) {
        exec_ps_snaked($my_command_line, $my_path);
      }
      else {
        stop_watchdogs() if !$i_am_watchdog;
      }
      
      exit 0;
    }
  }

  if ($Yandex::Tools::debug) {
    Yandex::Tools::debug("-");
    sleep (1);
  }
  else {
    my $usec_to_sleep;
    if ($have_active_children) {
      $usec_to_sleep = 50000;
    }
    else {
      $usec_to_sleep = 500000;
    }

   usleep($usec_to_sleep);
   $snaked::Daemon::runtime->{'usec_2check_watchdog'} = $snaked::Daemon::runtime->{'usec_2check_watchdog'} - $usec_to_sleep;
   $snaked::Daemon::runtime->{'usec_2refresh_configuration'} = $snaked::Daemon::runtime->{'usec_2refresh_configuration'} - $usec_to_sleep;
  }
}

# yes i know this is the way
# to the world of endless may
exit(255);
