#!/usr/bin/perl -w # # (c) Igor Chubin, imchubin@mail.ru, 2004-2005 # use strict; use POSIX; use Term::VT102; use Text::Iconv; use Data::Dumper; use Time::Local 'timelocal_nocheck'; use IO::Socket; use lib "."; use l3config; our @Command_Lines; our @Command_Lines_Index; our %Diffs; our %Sessions; our %Commands_Stat; # Statistics about commands usage our %Files_Stat; # Statistics about commands usage our %Script_Files; # Информация о позициях в скрипт-файлах, # до которых уже выполнен разбор # и информация о времени модификации файла # $Script_Files{$file}->{size} # $Script_Files{$file}->{tell} our $Killed =0; # В режиме демона -- процесс получил сигнал о завершении sub init_variables; sub main; sub load_diff_files; sub bind_diff; sub extract_from_cline; sub load_command_lines; sub sort_command_lines; sub process_command_lines; sub print_command_lines; sub printq; sub save_cache_stat; sub load_cache_stat; sub print_session; sub load_diff_files { my @pathes = @_; for my $path (@pathes) { my $template = "*.diff"; my @files = <$path/$template>; my $i=0; for my $file (@files) { next if defined($Diffs{$file}); my %diff; $diff{"path"}=$path; $diff{"uid"}="SET THIS"; # Сейчас UID определяется из названия каталога # откуда берутся diff-файлы # Это неправильно # # ВАРИАНТ: # К файлам жураналам должны прилагаться ситемны файлы, # мз которых и будет определяться соответствие # имён пользователей их uid'ам # $diff{"uid"} = 0 if $path =~ m@/root/@; $diff{"bind_to"}=""; $diff{"time_range"}=-1; next if not $file=~m@/(D?[0-9][0-9]?[0-9]?)[^/]*?([0-9]*):([0-9]*):?([0-9]*)@; $diff{"day"}=$1 || ""; $diff{"hour"}=$2; $diff{"min"}=$3; $diff{"sec"}=$4 || 0; $diff{"index"}=$i; print "diff loaded: $diff{day} $diff{hour}:$diff{min}:$diff{sec}\n"; local $/; open (F, "$file") or return "Can't open file $file ($_[0]) for reading"; my $text = ; if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i) { my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8"); $text = $converter->convert($text); } close(F); $diff{"text"}=$text; #print "$file loaded ($diff{day})\n"; #push @Diffs, \%diff; $Diffs{$file} = \%diff; $i++; } } } sub bind_diff { # my $path = shift; # my $pid = shift; # my $day = shift; # my $lab = shift; print "Trying to bind diff...\n"; my $cl = shift; my $hour = $cl->{"hour"}; my $min = $cl->{"min"}; my $sec = $cl->{"sec"}; my $min_dt = 10000; for my $diff_key (keys %Diffs) { my $diff = $Diffs{$diff_key}; # Check here date, time and user next if ($diff->{"day"} && $cl->{"day"} && ($cl->{"day"} ne $diff->{"day"})); #next if (!$diff->{"uid"} && $cl->{"euid"} != $diff->{"uid"}); my $dt=($diff->{"hour"}-$hour)*3600 +($diff->{"min"}-$min)*60 + ($diff->{"sec"}-$sec); if ($dt >0 && $dt < $min_dt && ($diff->{"time_range"} <0 || $dt < $diff->{"time_range"})) { print "Approppriate diff found: dt=$dt\n"; if ($diff->{"bind_to"}) { undef $diff->{"bind_to"}->{"diff"}; }; $diff->{"time_range"}=$dt; $diff->{"bind_to"}=$cl; #$cl->{"diff"} = $diff->{"index"}; $cl->{"diff"} = $diff_key; $min_dt = $dt; } } } sub extract_from_cline # Разобрать командную строку $_[1] и возвратить хэш, содержащий # номер первого появление команды в строке: # команда => первая позиция { my $what = $_[0]; my $cline = $_[1]; my @lists = split /\;/, $cline; my @commands = (); for my $list (@lists) { push @commands, split /\|/, $list; } my %commands; my %files; my $i=0; for my $command (@commands) { $command =~ /\s*(\S+)\s*(.*)/; if ($1 && $1 eq "sudo" ) { $commands{"$1"}=$i++; $command =~ s/\s*sudo\s+//; } $command =~ /\s*(\S+)\s*(.*)/; if ($1 && !defined $commands{"$1"}) { $commands{"$1"}=$i++; }; if ($2) { my $args = $2; my @args = split (/\s+/, $args); for my $a (@args) { $files{"$a"}=$i++ if !defined $files{"$a"}; }; } } if ($what eq "commands") { return %commands; } else { return %files; } } sub load_command_lines { my $lab_scripts_path = $_[0]; my $lab_scripts_mask = $_[1]; my $cline_re_base = qq' ( (?:\\^?([0-9]*C?)) # exitcode (?:_([0-9]+)_)? # uid (?:_([0-9]+)_) # pid (...?) # day (.?.?) # lab \\s # space separator ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) # time .\\[50D.\\[K # killing symbols (.*?([\$\#]\\s?)) # prompt (.*) # command line ) '; #my $cline_re = qr/$cline_re_base(?:$cline_re_base|$)/x; #my $cline_re = qr/(?:$cline_re_base)*$cline_re_base$/x; my $cline_re = qr/$cline_re_base/sx; my $cline_re1 = qr/$cline_re_base\x0D/sx; my $cline_re2 = qr/$cline_re_base$/sx; my $vt = Term::VT102->new ( 'cols' => $Config{"terminal_width"}, 'rows' => $Config{"terminal_height"}); my $cline_vt = Term::VT102->new ('cols' => $Config{"terminal_width"}, 'rows' => $Config{"terminal_height"}); my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8") if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i); print "Loading lm-scripts...\n" if $Config{"verbose"} =~ /y/; my $file; my $skip_info; my $commandlines_loaded =0; my $commandlines_processed =0; my @lab_scripts = <$lab_scripts_path/$lab_scripts_mask>; for $file (@lab_scripts){ # Пропускаем файл, если он не изменялся со времени нашего предудущего прохода my $size = (stat($file))[7]; next if ($Script_Files{$file} && $Script_Files{$file}->{size} && $Script_Files{$file}->{size} >= $size); my $local_session_id; # Начальное значение идентификатора текущего сеанса определяем из имени скрипта # Впоследствии оно может быть уточнено $file =~ m@.*/([^/]*)\.script$@; $local_session_id = $1; #Если файл только что появился, #пытаемся найти и загрузить информацию о соответствующей ему сессии if (!$Script_Files{$file}) { my $session_file = $file; $session_file =~ s/\.script/.info/; if (open(SESSION, $session_file)) { local $/; my $data = ; close(SESSION); for my $session_data ($data =~ m@(.*?)@sg) { my %session; while ($session_data =~ m@<([^>]*?)>(.*?)@sg) { $session{$1} = $2; } $local_session_id = $session{"local_session_id"} if $session{"local_session_id"}; $Sessions{$local_session_id}=\%session; } #Загруженную информацию сразу же отправляем в поток print_session($Config{cache}, $local_session_id); } } open (FILE, "$file"); binmode FILE; # Переходим к тому месту, где мы окончили разбор seek (FILE, $Script_Files{$file}->{tell}, 0) if $Script_Files{$file}->{tell}; $Script_Files{$file}->{size} = $size; $Script_Files{$file}->{tell} = 0 unless $Script_Files{$file}->{tell}; $file =~ m@.*/(.*?)-.*@; my $tty = $1; my $first_pass = 1; my %cl; my $last_output_length=0; while () { $commandlines_processed++; # time if (/[0-9][0-9]:[0-9][0-9]:[0-9][0-9].\[[0-9][0-9]D.\[K/ && m/$cline_re/) { s/.*\x0d(?!\x0a)//; # print "!!!",$_,"!!!\n"; # next; # while (m/$cline_re1/gs) { # } m/$cline_re2/gs; $commandlines_loaded++; $last_output_length=0; # Previous command my %last_cl = %cl; my $err = $2 || ""; =cut Атрибуты cline Список полей, характеризующих командную строку uid Идентификатор пользователя tty Идентификатор терминала, на котором была вызвана команда pid PID-процесса командного интерпретатора, в котором была вызвана команда lab лабораторная работа, к которой относится команда. Идентификатор текущей лабораторной работы хранится в файле ~/.labmaker/lab pwd (!) текущий каталог, из которого была вызвана команда day время вызова, день В действительности здесь хранится не время вызова команды, а с момента появления приглашения командного интерпретатора для ввода команды hour время вызова, час min время вызова, минута sec время вызова, секунда time (!) время вызова команды в Unix-формате. Предпочтительнее использовать этот формат чем hour:min:sec, использовавшийся в Labmaker fullprompt Приглашение командной строки prompt Сокращённое приглашение командной строки cline Командная строка output Результат выполнения команды diff Указатель на ассоциированный с командой diff note (!) Текстовый комментарий к команде. Может генерироваться из самого лога с помощью команд #^ Комментарий #= Комментарий #v Комментарий в том случае, если для комментирования достаточно одной строки, или с помощью команд cat > /dev/null #^ Заголовок Текст ^D в том случае, если комментарий развёрнутый. В последнем случае комментарий может содержать заголовок, абзацы и несложное форматирование. Символы ^, v или = после знака комментария # обозначает, к какой команде относится комментарий: к предыдущей (^), последующей (v) или это общий комментарий по тексту, не относящийся непосредственно ни к одной из них (=) err Код завершения командной строки histnum (!) Номер команды в истории командного интерпретатора status (!) Является ли данная команда вызванной (r), запомненной (s) или это подсказка completion (c). Команды, которые были вызваны и обработаны интерпретатором имеют состояние "r". К таким командам относится большинство команд вводимых в интерпретатор. Если команда набрана, но вызывать её по какой-либо причине не хочется (например, команда может быть не полной, вредоносной или просто бессмысленной в текущих условиях), её можно сбросить с помощью комбинации клавиш Ctrl-C (не путайте с прерыванием работающей команды! здесь она даже не запускается!). В таком случае она не выполняется, но попадает в журнал со статусом "s". Если команда появилась в журнале благодаря автопроолжению -- когда было показано несколько вариантов -- она имеет статус "c". euid Идентификатор пользователя от имени которого будет выполняться команда. Может отличаться от реального uid в том случае, если вызывается с помощью sudo version (!) Версия lilalo-prompt использовавшаяся при записи команды. 0 - версия использовавшая в labmaker. Отсутствует информация о текущем каталоге и номере в истории. Информация о версии также не указана в приглашении. 1 - версия использующаяся в lilalo raw_file Имя файла, в котором находится бинарное представление журнала. Может содержать ключевое слово HERE, обозначающее что бинарное представление хранится непосредственно в базе данных в атрибуте raw_data raw_start Начало блока командной строки в файле бинарного представления raw_output_start Начало блока вывода raw_end Конец блока командной строки в файле бинарного представления raw_cline Необработанная командная строка (без приглашения) в бинарном виде raw_data (*) Бинарное представление команды и результатов её выполнения ТАБЛИЦА SESSION Информация о сеансах (см. lm-install) =cut $cl{"local_session_id"} = $local_session_id; # Parse new command $cl{"uid"} = $3; $cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0 $cl{"pid"} = $4; $cl{"day"} = $5; $cl{"lab"} = $6; $cl{"hour"} = $7; $cl{"min"} = $8; $cl{"sec"} = $9; $cl{"fullprompt"} = $10; $cl{"prompt"} = $11; $cl{"raw_cline"} = $12; { use bytes; $cl{"raw_start"} = tell (FILE) - length($1); $cl{"raw_output_start"} = tell FILE; } $cl{"raw_file"} = $file; $cl{"err"} = 0; $cl{"output"} = ""; $cl{"tty"} = $tty; $cline_vt->process($cl{"raw_cline"}."\n"); $cl{"cline"} = $cline_vt->row_plaintext (1); $cl{"cline"} =~ s/\s*$//; $cline_vt->reset(); my %commands = extract_from_cline("commands", $cl{"cline"}); $cl{"euid"}=0 if defined $commands{"sudo"}; my @comms = sort { $commands{$a} cmp $commands{$b} } keys %commands; $cl{"last_command"} = $comms[$#comms] || ""; if ( $Config{"suppress_editors"} =~ /^y/i && grep ($_ eq $cl{"last_command"}, @{$Config{"editors"}}) || $Config{"suppress_pagers"} =~ /^y/i && grep ($_ eq $cl{"last_command"}, @{$Config{"pagers"}}) || $Config{"suppress_terminal"}=~ /^y/i && grep ($_ eq $cl{"last_command"}, @{$Config{"terminal"}}) ) { $cl{"suppress_output"} = "1"; } else { $cl{"suppress_output"} = "0"; } $skip_info = 0; print " ",$cl{"last_command"}; # Processing previous command line if ($first_pass) { $first_pass = 0; next; } # Error code $last_cl{"raw_end"} = $cl{"raw_start"}; $last_cl{"err"}=$err; $last_cl{"err"}=130 if $err eq "^C"; if (grep ($_ eq $last_cl{"last_command"}, @{$Config{"editors"}})) { bind_diff(\%last_cl); } # Output if (!$last_cl{"suppress_output"} || $last_cl{"err"}) { for (my $i=0; $i<$Config{"terminal_height"}; $i++) { my $line= $vt->row_plaintext($i); next if !defined ($line) || $line =~ /^\s*$/; $line =~ s/\s*$//; $last_cl{"output"} .= $line."\n"; } } else { $last_cl{"output"}= ""; } $vt->reset(); # Classifying the command line # Save if (!$Config{"lab"} || $cl{"lab"} eq $Config{"lab"}) { # Changing encoding for (keys %last_cl) { next if /raw/; $last_cl{$_} = $converter->convert($last_cl{$_}) if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i); } push @Command_Lines, \%last_cl; # Сохранение позиции в файле, до которой выполнен # успешный разбор $Script_Files{$file}->{tell} = $last_cl{raw_end}; } next; } $last_output_length+=length($_); #if (!$cl{"suppress_output"} || $last_output_length < 5000) { if ($last_output_length < 50000) { #print "(",length($_),")" if (length($_) > 2000) ; $vt->process("$_"."\n") } else { if (!$skip_info) { print "($cl{last_command})"; $skip_info = 1; } } } close(FILE); } if ($Config{"verbose"} =~ /y/) { print "...finished." ; print "Lines loaded: $commandlines_processed\n"; print "Command lines: $commandlines_loaded\n"; } } sub printq { my $TO = shift; my $text = join "", @_; $text =~ s/&/&/g; $text =~ s//>/g; print $TO $text; } sub sort_command_lines { print "Sorting command lines...\n" if $Config{"verbose"} =~ /y/; # Sort Command_Lines # Write Command_Lines to Command_Lines_Index my @index; for (my $i=0;$i<=$#Command_Lines;$i++) { $index[$i]=$i; } @Command_Lines_Index = sort { $Command_Lines[$index[$a]]->{"day"} cmp $Command_Lines[$index[$b]]->{"day"} || $Command_Lines[$index[$a]]->{"hour"} <=> $Command_Lines[$index[$b]]->{"hour"} || $Command_Lines[$index[$a]]->{"min"} <=> $Command_Lines[$index[$b]]->{"min"} || $Command_Lines[$index[$a]]->{"sec"} <=> $Command_Lines[$index[$b]]->{"sec"} } @index; print "...finished\n" if $Config{"verbose"} =~ /y/; } sub process_command_lines { for my $i (@Command_Lines_Index) { my $cl = \$Command_Lines[$i]; @{${$cl}->{"new_commands"}} =(); @{${$cl}->{"new_files"}} =(); $$cl->{"class"} = ""; if ($$cl->{"err"}) { $$cl->{"class"}="wrong"; $$cl->{"class"}="interrupted" if ($$cl->{"err"} eq 130); } if (!$$cl->{"euid"}) { $$cl->{"class"}.="_root"; } #tab# my @tab_words=split /\s+/, $$cl->{"output"}; #tab# my $last_word= $$cl->{"cline"} =~ /(\S*)$/; #tab# $last_word =~ s@.*/@@; #tab# my $this_is_tab=1; #tab# #tab# if ($last_word && @tab_words >2) { #tab# for my $tab_words (@tab_words) { #tab# if ($tab_words !~ /^$last_word/) { #tab# $this_is_tab=0; #tab# last; #tab# } #tab# } #tab# } #tab# $$cl->{"class"}="tab" if $this_is_tab; if ( !$$cl->{"err"}) { # Command does not contain mistakes my %commands = extract_from_cline("commands", ${$cl}->{"cline"}); my %files = extract_from_cline("files", ${$cl}->{"cline"}); # Searching for new commands only for my $command (keys %commands) { if (!defined $Commands_Stat{$command}) { push @{$$cl->{new_commands}}, $command; } $Commands_Stat{$command}++; } for my $file (keys %files) { if (!defined $Files_Stat{$file}) { push @{$$cl->{new_files}}, $file; } $Files_Stat{$file}++; } } } } =cut Вывести результат обработки журнала. =cut sub print_command_lines { my $output_filename=$_[0]; my $mode = ">"; $mode =">>" if $Config{mode} eq "daemon"; open(OUT, $mode, $output_filename) or die "Can't open $output_filename for writing\n"; #print OUT "\n"; my $cl; my $in_range=0; for my $i (@Command_Lines_Index) { $cl = $Command_Lines[$i]; if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) { $in_range=1; next; } if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) { $in_range=0; next; } next if ($Config{"from"} && $Config{"to"} && !$in_range) || ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ ) || ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0) || ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130); my @new_commands=@{$cl->{"new_commands"}}; my @new_files=@{$cl->{"new_files"}}; my $cl_class="cline"; my $out_class="output"; if ($cl->{"class"}) { $cl_class = $cl->{"class"}."_".$cl_class; $out_class = $cl->{"class"}."_".$out_class; } # Вырезаем из вывода только нужное количество строк my $output=""; if ($Config{"head_lines"} || $Config{"tail_lines"}) { # Partialy output my @lines = split '\n', $cl->{"output"}; # head my $mark=1; for (my $i=0; $i<= $#lines && $i < $Config{"cache_head_lines"}; $i++) { $output .= $lines[$i]."\n"; } # tail my $start=$#lines-$Config{"cache_tail_lines"}+1; if ($start < 0) { $start=0; $mark=0; } if ($start < $Config{"cache_head_lines"}) { $start=$Config{"cache_head_lines"}; $mark=0; } $output .= $Config{"skip_text"}."\n" if $mark; for (my $i=$start; $i<= $#lines; $i++) { $output .= $lines[$i]."\n"; } } else { # Full output $output .= $cl->{"output"}; } $output .= "^C\n" if ($cl->{"err"} eq "130"); # Совместимость с labmaker # Переводим в секунды Эпохи # В labmaker'е данные хранились в неудобной форме: hour, min, sec, day of year # Информация о годе отсутствовала # Её можно внести: # Декабрь 2004 год; остальные -- 2005 год. my $year = 2005; $year = 2004 if ( $cl->{day} > 330 ); # timelocal( $sec, $min, $hour, $mday,$mon,$year); $cl->{time} = timelocal_nocheck($cl->{sec},$cl->{min},$cl->{hour},$cl->{day},0,$year); # Начинаем вывод команды print OUT "\n"; print OUT "",$cl->{local_session_id},"\n"; print OUT "\n"; print OUT "",$cl->{raw_start},"\n"; print OUT "",$cl->{raw_output_start},"\n"; print OUT "",$cl->{raw_end},"\n"; print OUT "",$cl->{raw_file},"\n"; print OUT "",$cl->{tty},"\n"; print OUT "",$out_class,"\n"; print OUT ""; printq(\*OUT,,$cl->{"prompt"}); print OUT ""; print OUT ""; printq(\*OUT,$cl->{"cline"}); print OUT "\n"; print OUT "",$cl->{"last_command"},"\n"; if (@new_commands) { print OUT ""; printq(\*OUT, join (" ", @new_commands)); print OUT ""; } if (@new_files) { print OUT ""; printq(\*OUT, join (" ", @new_files)); print OUT ""; } print OUT ""; printq(\*OUT,$output); print OUT "\n"; if ($cl->{"diff"}) { print OUT ""; printq(\*OUT,${$Diffs{$cl->{"diff"}}}{"text"}); print OUT "\n"; } print OUT "\n"; } #print OUT "\n"; close(OUT); } sub print_session { my $output_filename = $_[0]; my $local_session_id = $_[1]; return if not defined($Sessions{$local_session_id}); open(OUT, ">>", $output_filename) or die "Can't open $output_filename for writing\n"; print OUT "\n"; my %session = %{$Sessions{$local_session_id}}; for my $key (keys %session) { print OUT "<$key>".$session{$key}."\n" } print OUT "\n"; close(OUT); } sub send_cache { # Если в кэше что-то накопилось, # попытаемся отправить это на сервер # my $cache_was_sent=0; if (open(CACHE, $Config{cache})) { local $/; my $cache = ; close(CACHE); my $socket = IO::Socket::INET->new( PeerAddr => $Config{backend_address}, PeerPort => $Config{backend_port}, proto => "tcp", Type => SOCK_STREAM ); if ($socket) { print $socket $cache; close($socket); $cache_was_sent = 1; } } return $cache_was_sent; } sub save_cache_stat { open (CACHE, ">$Config{cache_stat}"); for my $f (keys %Script_Files) { print CACHE "$f\t",$Script_Files{$f}->{size},"\t",$Script_Files{$f}->{tell},"\n"; } close(CACHE); } sub load_cache_stat { if (open (CACHE, "$Config{cache_stat}")) { while() { chomp; my ($f, $size, $tell) = split /\t/; $Script_Files{$f}->{size} = $size; $Script_Files{$f}->{tell} = $tell; } close(CACHE); }; } main(); sub process_was_killed { $Killed = 1; } sub main { $| = 1; init_variables(); init_config(); if ($Config{"mode"} ne "daemon") { =cut В нормальном режиме работы нужно считать скрипты, обработать их и записать результат выполнения в результриующий файл. После этого завершить работу. =cut for my $lab_log (split (/\s+/, $Config{"diffs"} || $Config{"input"})) { load_diff_files($lab_log); } load_command_lines($Config{"input"}, $Config{"input_mask"}); sort_command_lines; process_command_lines; print_command_lines($Config{"cache"}); } else { if (open(PIDFILE, $Config{agent_pidfile})) { my $pid = ; close(PIDFILE); if ( ! -e "/proc/$pid" || !`grep $Config{"l3-agent"} /proc/$pid/cmdline && grep "uid:.*\b$<\b" /proc/$pid/status`) { print "Removing stale pidfile\n"; unlink $Config{agent_pidfile} or die "Can't remove stale pidfile ". $Config{agent_pidfile}. " : $!"; } else { print "l3-agent is already running\n"; exit(0); } } if ($Config{detach} =~ /^y/i) { #$Config{verbose} = "no"; my $pid = fork; exit if $pid; die "Couldn't fork: $!" unless defined ($pid); open(PIDFILE, ">", $Config{agent_pidfile}) or die "Can't open pidfile ". $Config{agent_pidfile}. " for wrting: $!"; print PIDFILE $$; close(PIDFILE); for my $handle (*STDIN, *STDOUT, *STDERR) { open ($handle, "+<", "/dev/null") or die "can't reopen $handle to /dev/null: $!" } POSIX::setsid() or die "Can't start a new session: $!"; $0 = $Config{"l3-agent"}; $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&process_was_killed; } while (not $Killed) { @Command_Lines = (); @Command_Lines_Index = (); for my $lab_log (split (/\s+/, $Config{"diffs"} || $Config{"input"})) { load_diff_files($lab_log); } load_cache_stat(); load_command_lines($Config{"input"}, $Config{"input_mask"}); if (@Command_Lines) { sort_command_lines; process_command_lines; print_command_lines($Config{"cache"}); } save_cache_stat(); if (-e $Config{cache} && (stat($Config{cache}))[7]) { send_cache() && unlink($Config{cache}); } sleep($Config{"daemon_sleep_interval"} || 1); } unlink $Config{agent_pidfile}; } } sub init_variables { }