devi@52: #!/usr/bin/perl -w devi@23: devi@23: # igor@119: # (c) Igor Chubin, igor@chub.in, 2004-2008 devi@23: # devi@23: devi@23: use strict; devi@25: use POSIX; devi@23: use Term::VT102; devi@23: use Text::Iconv; devi@23: use Time::Local 'timelocal_nocheck'; devi@27: use IO::Socket; devi@23: igor@115: use lib "/etc/lilalo"; devi@23: use l3config; devi@23: devi@23: our @Command_Lines; devi@23: our @Command_Lines_Index; devi@28: our %Diffs; devi@27: our %Sessions; devi@23: devi@62: our %Script_Files; # Информация о позициях в скрипт-файлах, devi@62: # до которых уже выполнен разбор devi@62: # и информация о времени модификации файла devi@62: # $Script_Files{$file}->{size} devi@62: # $Script_Files{$file}->{tell} devi@23: devi@62: our $Killed =0; # В режиме демона -- процесс получил сигнал о завершении devi@23: devi@23: sub init_variables; devi@23: sub main; devi@23: devi@23: sub load_diff_files; devi@23: sub bind_diff; devi@62: sub extract_commands_from_cline; devi@23: sub load_command_lines; devi@23: sub sort_command_lines; devi@23: sub print_command_lines; devi@23: sub printq; devi@23: devi@25: sub save_cache_stat; devi@25: sub load_cache_stat; devi@27: sub print_session; devi@25: devi@23: sub load_diff_files devi@23: { devi@62: my @pathes = @_; devi@62: devi@62: for my $path (@pathes) { devi@62: my $template = "*.diff"; devi@62: my @files = <$path/$template>; devi@62: my $i=0; devi@62: for my $file (@files) { devi@28: devi@62: next if defined($Diffs{$file}); devi@62: my %diff; devi@23: devi@80: # Старый формат имени diff-файла devi@80: # DEPRECATED devi@80: if ($file=~m@/(D?[0-9][0-9]?[0-9]?)[^/]*?([0-9]*):([0-9]*):?([0-9]*)@) { devi@80: $diff{"day"}=$1 || ""; devi@80: $diff{"hour"}=$2; devi@80: $diff{"min"}=$3; devi@80: $diff{"sec"}=$4 || 0; devi@80: devi@80: $diff{"uid"} = 0 if $path =~ m@/root/@; devi@23: devi@62: print "diff loaded: $diff{day} $diff{hour}:$diff{min}:$diff{sec}\n"; devi@80: devi@80: } devi@80: # Новый формат имени diff-файла devi@80: elsif ($file =~ m@.*/([^_]*)_([0-9]+)(.*)@) { devi@80: $diff{"local_session_id"} = $1; devi@80: $diff{"time"} = $2; devi@80: $diff{"filename"} = $3; devi@80: $diff{"filename"} =~ s@_@/@g; devi@80: $diff{"filename"} =~ s@//@_@g; devi@80: devi@80: print "diff loaded: $diff{filename} (time=$diff{time},session=$diff{local_session_id})\n"; devi@80: } devi@80: else { devi@80: next; devi@80: } devi@80: devi@80: # Чтение и изменение кодировки содержимого diff-файла devi@62: local $/; devi@62: open (F, "$file") devi@62: or return "Can't open file $file ($_[0]) for reading"; devi@62: my $text = <F>; devi@62: if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i) { devi@62: my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8"); devi@62: $text = $converter->convert($text); devi@62: } devi@62: close(F); devi@62: $diff{"text"}=$text; devi@23: devi@80: $diff{"path"}=$path; devi@80: $diff{"bind_to"}=""; devi@80: $diff{"time_range"}=-1; devi@80: $diff{"index"}=$i; devi@80: devi@62: $Diffs{$file} = \%diff; devi@62: $i++; devi@62: } devi@62: } devi@23: } devi@23: devi@23: devi@23: sub bind_diff devi@23: { devi@62: print "Trying to bind diff...\n"; devi@23: devi@62: my $cl = shift; devi@62: my $hour = $cl->{"hour"}; devi@62: my $min = $cl->{"min"}; devi@62: my $sec = $cl->{"sec"}; devi@23: devi@62: my $min_dt = 10000; devi@23: igor@119: if (defined($cl->{"diff"})) { igor@119: print STDERR "Command ".$cl->{time}." is already bound"; igor@119: return; igor@119: } igor@119: igor@119: # Загружаем новые diff-файлы igor@119: # Это нужно делать непосредственно перед привязкой, поскольку diff'ы могли образоваться только что igor@119: for my $lab_log (split (/\s+/, $Config{"diffs"} || $Config{"input"})) { igor@119: load_diff_files($lab_log); igor@119: } igor@119: igor@119: my $diff_to_bind; devi@62: for my $diff_key (keys %Diffs) { igor@119: my $diff = $Diffs{$diff_key}; igor@119: next if ($diff->{"local_session_id"} igor@119: && $cl->{"local_session_id"} igor@119: && ($cl->{"local_session_id"} ne $diff->{"local_session_id"})); devi@80: igor@119: next if ($diff->{"day"} && $cl->{"day"} && ($cl->{"day"} ne $diff->{"day"})); devi@80: igor@119: my $dt; igor@119: if (not $diff->{"time"}) { igor@119: print STDERR "diff time is 0"; igor@119: print STDERR join(" ", keys(%$diff)); igor@119: print STDERR $diff->{text}; igor@119: } igor@119: if (not $cl->{"time"}) { igor@119: print STDERR "cl time is 0"; igor@119: } igor@119: if ($diff->{"time"} && $cl->{"time"}) { igor@119: $dt = $diff->{"time"} - $cl->{"time"} igor@119: } igor@119: else { igor@119: $dt=($diff->{"hour"}-$hour)*3600 +($diff->{"min"}-$min)*60 + ($diff->{"sec"}-$sec); igor@119: } igor@119: if ($dt >=0 && $dt < $min_dt && !$diff->{"bind_to"}) { igor@119: $min_dt = $dt; igor@119: $diff_to_bind = $diff_key; igor@119: } igor@119: } igor@119: if ($diff_to_bind) { igor@119: print "Approppriate diff found: dt=$min_dt\n"; igor@119: $Diffs{$diff_to_bind}->{"bind_to"}=$cl; igor@119: $cl->{"diff"} = $diff_to_bind; igor@119: } igor@119: else { igor@119: print STDERR "Diff not found\n"; igor@119: print STDERR "cl{time}",$cl->{time},"\n"; devi@62: } devi@23: } devi@23: devi@23: devi@62: sub extract_commands_from_cline devi@23: # Разобрать командную строку $_[1] и возвратить хэш, содержащий devi@23: # номер первого появление команды в строке: devi@62: # команда => первая позиция devi@23: { devi@62: my $cline = $_[0]; devi@62: my @lists = split /\;/, $cline; devi@62: devi@62: devi@62: my @commands = (); devi@62: for my $list (@lists) { devi@62: push @commands, split /\|/, $list; devi@62: } devi@23: devi@62: my %commands; devi@62: my %files; devi@62: my $i=0; devi@62: for my $command (@commands) { devi@62: $command =~ /\s*(\S+)\s*(.*)/; devi@62: if ($1 && $1 eq "sudo" ) { devi@62: $commands{"$1"}=$i++; devi@62: $command =~ s/\s*sudo\s+//; devi@62: } devi@62: $command =~ /\s*(\S+)\s*(.*)/; devi@62: if ($1 && !defined $commands{"$1"}) { devi@62: $commands{"$1"}=$i++; devi@62: }; devi@62: } devi@62: return %commands; devi@23: } devi@23: devi@23: sub load_command_lines devi@23: { devi@62: my $lab_scripts_path = $_[0]; devi@62: my $lab_scripts_mask = $_[1]; devi@23: devi@62: my $cline_re_base = qq' devi@62: ( devi@62: (?:\\^?([0-9]*C?)) # exitcode devi@62: (?:_([0-9]+)_)? # uid devi@62: (?:_([0-9]+)_) # pid devi@62: (...?) # day devi@62: (.?.?) # lab devi@62: \\s # space separator devi@62: ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) # time devi@62: .\\[50D.\\[K # killing symbols devi@62: (.*?([\$\#]\\s?)) # prompt devi@62: (.*) # command line devi@62: ) devi@62: '; devi@62: my $cline_re = qr/$cline_re_base/sx; devi@62: my $cline_re2 = qr/$cline_re_base$/sx; devi@23: devi@74: my $cline_re_v2_base = qq' devi@74: ( devi@74: v2[\#] # version devi@74: ([0-9]+)[\#] # history line number devi@74: ([0-9]+)[\#] # exitcode devi@74: ([0-9]+)[\#] # uid devi@74: ([0-9]+)[\#] # pid devi@74: ([0-9]+)[\#] # time igor@114: (.*?)[\#] # pwd devi@74: .\\[1024D.\\[K # killing symbols devi@74: (.*?([\$\#]\\s?)) # prompt devi@74: (.*) # command line devi@74: ) devi@74: '; devi@74: devi@74: my $cline_re_v2 = qr/$cline_re_v2_base/sx; devi@74: my $cline_re2_v2 = qr/$cline_re_v2_base$/sx; devi@74: igor@114: my $cline_re_v3_base = qq' igor@114: ( igor@114: v3[\#] # version igor@114: .* igor@114: ) igor@114: '; igor@114: my $cline_re_v3 = qr/$cline_re_v3_base/sx; igor@114: igor@114: my $cline_re2_v3_base = qq' igor@114: ( igor@114: v3[\#] # version igor@114: ([0-9]+)[\#] # history line number igor@114: ([0-9]+)[\#] # exitcode igor@114: ([0-9]+)[\#] # uid igor@114: ([0-9]+)[\#] # pid igor@114: ([0-9]+)[\#] # time igor@114: (.*?)[\#] # pwd igor@119: (.*?)[\#] # nonce igor@114: (.*?([\$\#]\\s?)) # prompt igor@114: (.*) # command line igor@114: ) igor@114: '; igor@114: my $cline_re2_v3 = qr/$cline_re2_v3_base$/sx; igor@114: igor@114: igor@115: my %vt; # Хэш виртуальных терминалов. По одному на каждый сеанс devi@81: my $cline_vt = Term::VT102->new ( devi@81: 'cols' => $Config{"terminal_width"}, devi@81: 'rows' => $Config{"terminal_height"}); devi@23: devi@62: my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8") devi@62: if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i); devi@62: devi@74: print "Parsing lab scripts...\n" if $Config{"verbose"} =~ /y/; devi@23: devi@62: my $file; devi@62: my $skip_info; devi@23: devi@62: my $commandlines_loaded =0; devi@62: my $commandlines_processed =0; devi@23: devi@62: my @lab_scripts = <$lab_scripts_path/$lab_scripts_mask>; devi@62: for $file (@lab_scripts){ devi@23: devi@62: # Пропускаем файл, если он не изменялся со времени нашего предудущего прохода devi@62: my $size = (stat($file))[7]; devi@62: next if ($Script_Files{$file} && $Script_Files{$file}->{size} && $Script_Files{$file}->{size} >= $size); devi@27: devi@27: devi@62: my $local_session_id; devi@62: # Начальное значение идентификатора текущего сеанса определяем из имени скрипта devi@62: # Впоследствии оно может быть уточнено devi@62: $file =~ m@.*/([^/]*)\.script$@; devi@62: $local_session_id = $1; devi@27: igor@115: if (not defined($vt{$local_session_id})) { igor@115: $vt{$local_session_id} = Term::VT102->new ( igor@115: 'cols' => $Config{"terminal_width"}, igor@115: 'rows' => $Config{"terminal_height"}); igor@115: } igor@115: devi@62: #Если файл только что появился, devi@62: #пытаемся найти и загрузить информацию о соответствующей ему сессии devi@62: if (!$Script_Files{$file}) { devi@62: my $session_file = $file; devi@62: $session_file =~ s/\.script/.info/; devi@62: if (open(SESSION, $session_file)) { devi@62: local $/; devi@62: my $data = <SESSION>; devi@62: close(SESSION); devi@27: devi@62: for my $session_data ($data =~ m@<session>(.*?)</session>@sg) { devi@62: my %session; devi@62: while ($session_data =~ m@<([^>]*?)>(.*?)</\1>@sg) { devi@62: $session{$1} = $2; devi@62: } devi@62: $local_session_id = $session{"local_session_id"} if $session{"local_session_id"}; devi@62: $Sessions{$local_session_id}=\%session; devi@62: } devi@25: devi@62: #Загруженную информацию сразу же отправляем в поток devi@62: print_session($Config{cache}, $local_session_id); devi@62: } devi@84: else { devi@84: die "can't open session file"; devi@84: } devi@62: } devi@25: devi@62: open (FILE, "$file"); devi@62: binmode FILE; devi@23: devi@62: # Переходим к тому месту, где мы окончили разбор devi@62: seek (FILE, $Script_Files{$file}->{tell}, 0) if $Script_Files{$file}->{tell}; devi@62: $Script_Files{$file}->{size} = $size; devi@62: $Script_Files{$file}->{tell} = 0 unless $Script_Files{$file}->{tell}; devi@32: devi@62: $file =~ m@.*/(.*?)-.*@; devi@23: devi@83: print "\n+- processing file $file\n| " devi@83: if $Config{"verbose"} =~/y/; devi@74: devi@62: my $tty = $1; devi@62: my %cl; devi@62: my $last_output_length=0; devi@62: while (<FILE>) { devi@62: $commandlines_processed++; devi@23: devi@62: next if s/^Script started on.*?\n//s; devi@23: devi@62: if (/[0-9][0-9]:[0-9][0-9]:[0-9][0-9].\[[0-9][0-9]D.\[K/ && m/$cline_re/) { devi@62: s/.*\x0d(?!\x0a)//; devi@62: m/$cline_re2/gs; devi@23: devi@62: $commandlines_loaded++; devi@62: $last_output_length=0; devi@23: devi@62: # Previous command devi@62: my %last_cl = %cl; igor@119: my $this_line = $1; devi@62: my $err = $2 || ""; devi@23: devi@62: $cl{"local_session_id"} = $local_session_id; devi@62: # Parse new command devi@62: $cl{"uid"} = $3; devi@74: #$cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0 devi@62: $cl{"pid"} = $4; devi@62: $cl{"day"} = $5; devi@62: $cl{"lab"} = $6; devi@62: $cl{"hour"} = $7; devi@62: $cl{"min"} = $8; devi@62: $cl{"sec"} = $9; devi@72: #$cl{"fullprompt"} = $10; devi@62: $cl{"prompt"} = $11; devi@62: $cl{"raw_cline"} = $12; devi@23: devi@62: { devi@62: use bytes; igor@119: $cl{"raw_start"} = tell (FILE) - length($this_line); devi@62: $cl{"raw_output_start"} = tell FILE; devi@62: } devi@62: $cl{"raw_file"} = $file; devi@23: devi@62: $cl{"err"} = 0; devi@62: $cl{"output"} = ""; devi@62: $cl{"tty"} = $tty; devi@23: devi@62: $cline_vt->process($cl{"raw_cline"}."\n"); devi@62: $cl{"cline"} = $cline_vt->row_plaintext (1); devi@62: $cl{"cline"} =~ s/\s*$//; igor@119: $cl{"cline"} =~ s/.*?[\#\$]\s*//; devi@62: $cline_vt->reset(); devi@23: devi@62: my %commands = extract_commands_from_cline($cl{"cline"}); devi@74: #$cl{"euid"}=0 if defined $commands{"sudo"}; devi@62: my @comms = sort { $commands{$a} cmp $commands{$b} } keys %commands; devi@62: $cl{"last_command"} = $comms[$#comms] || ""; devi@23: devi@62: if ( devi@62: $Config{"suppress_editors"} =~ /^y/i devi@62: && grep ($_ eq $cl{"last_command"}, @{$Config{"editors"}}) devi@62: || $Config{"suppress_pagers"} =~ /^y/i devi@62: && grep ($_ eq $cl{"last_command"}, @{$Config{"pagers"}}) devi@62: || $Config{"suppress_terminal"}=~ /^y/i devi@62: && grep ($_ eq $cl{"last_command"}, @{$Config{"terminal"}}) devi@62: ) { devi@62: $cl{"suppress_output"} = "1"; devi@62: } devi@62: else { devi@62: $cl{"suppress_output"} = "0"; devi@62: } devi@62: $skip_info = 0; devi@23: devi@23: devi@62: print " ",$cl{"last_command"}; devi@23: igor@119: if (grep ($_ eq $last_cl{"last_command"}, @{$Config{"editors"}})) { igor@119: bind_diff(\%last_cl); igor@119: } igor@119: devi@62: # Error code devi@62: $last_cl{"raw_end"} = $cl{"raw_start"}; devi@62: $last_cl{"err"}=$err; devi@62: $last_cl{"err"}=130 if $err eq "^C"; devi@23: devi@23: devi@62: # Output devi@62: if (!$last_cl{"suppress_output"} || $last_cl{"err"}) { devi@62: for (my $i=0; $i<$Config{"terminal_height"}; $i++) { igor@115: my $line= $vt{$local_session_id}->row_plaintext($i); devi@62: next if !defined ($line) ; #|| $line =~ /^\s*$/; devi@62: $line =~ s/\s*$//; devi@62: $line .= "\n" unless $line =~ /^\s*$/; devi@62: $last_cl{"output"} .= $line; devi@62: } devi@62: } devi@62: else { devi@62: $last_cl{"output"}= ""; devi@62: } devi@23: igor@115: $vt{$local_session_id}->reset(); devi@23: devi@23: devi@62: # Save devi@62: if (!$Config{"lab"} || $cl{"lab"} eq $Config{"lab"}) { devi@62: # Changing encoding devi@62: for (keys %last_cl) { devi@62: next if /raw/; devi@62: $last_cl{$_} = $converter->convert($last_cl{$_}) devi@62: if ($Config{"encoding"} && devi@62: $Config{"encoding"} !~ /^utf-8$/i); devi@62: } devi@62: push @Command_Lines, \%last_cl; devi@23: devi@62: # Сохранение позиции в файле, до которой выполнен devi@62: # успешный разбор devi@62: $Script_Files{$file}->{tell} = $last_cl{raw_end}; devi@62: } devi@62: next; devi@62: } devi@74: igor@114: elsif (m/$cline_re_v2/ || m/$cline_re_v3/) { devi@74: # Разбираем командную строку версии 2 igor@119: my $before=$_; igor@119: s/.*\x0d(?!\x0a)//; devi@74: igor@114: my $re; igor@114: if (m/$cline_re_v2/) { igor@114: $re=$cline_re2_v2; igor@114: } igor@114: else { igor@114: s/.\[1K.\[10D//gs; igor@114: $re=$cline_re2_v3; igor@119: print STDERR "... $_ ...\n"; igor@114: } igor@121: m/$re/gs; devi@74: devi@74: $commandlines_loaded++; devi@74: $last_output_length=0; devi@74: devi@74: # Previous command devi@74: my %last_cl = %cl; devi@74: devi@74: $cl{"local_session_id"} = $local_session_id; devi@74: # Parse new command igor@119: my $this_line = $1; devi@74: $cl{"history"} = $2; devi@74: my $err = $3; devi@74: $cl{"uid"} = $4; devi@74: #$cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0 devi@74: $cl{"pid"} = $5; devi@74: $cl{"time"} = $6; devi@74: $cl{"pwd"} = $7; igor@119: $cl{"nonce"} = $8; devi@74: #$cl{"fullprompt"} = $8; igor@119: $cl{"prompt"} = $10; igor@119: #$cl{"raw_cline"}= $10; igor@119: $cl{"raw_cline"}= $before; devi@74: devi@74: { devi@74: use bytes; igor@119: $cl{"raw_start"} = tell (FILE) - length($before); devi@74: $cl{"raw_output_start"} = tell FILE; devi@74: } devi@74: $cl{"raw_file"} = $file; devi@74: devi@74: $cl{"err"} = 0; devi@74: $cl{"output"} = ""; devi@74: #$cl{"tty"} = $tty; devi@74: devi@74: $cline_vt->process($cl{"raw_cline"}."\n"); devi@74: $cl{"cline"} = $cline_vt->row_plaintext (1); devi@74: $cl{"cline"} =~ s/\s*$//; igor@119: $cl{"cline"} =~ s/.*?[\#\$]\s*//; devi@74: $cline_vt->reset(); igor@119: print STDERR "cline=".$cl{"cline"}."<<\n"; devi@74: devi@74: my %commands = extract_commands_from_cline($cl{"cline"}); devi@74: #$cl{"euid"} = 0 if defined $commands{"sudo"}; devi@74: my @comms = sort { $commands{$a} cmp $commands{$b} } keys %commands; devi@74: $cl{"last_command"} devi@74: = $comms[$#comms] || ""; devi@74: igor@119: print STDERR "last_command=".$cl{"last_command"}."<<\n"; igor@119: devi@74: if ( devi@74: $Config{"suppress_editors"} =~ /^y/i devi@74: && grep ($_ eq $cl{"last_command"}, @{$Config{"editors"}}) devi@74: || $Config{"suppress_pagers"} =~ /^y/i devi@74: && grep ($_ eq $cl{"last_command"}, @{$Config{"pagers"}}) devi@74: || $Config{"suppress_terminal"}=~ /^y/i devi@74: && grep ($_ eq $cl{"last_command"}, @{$Config{"terminal"}}) devi@74: ) { devi@74: $cl{"suppress_output"} = "1"; devi@74: } devi@74: else { devi@74: $cl{"suppress_output"} = "0"; devi@74: } devi@74: $skip_info = 0; devi@74: devi@74: if ($Config{verbose} =~ /y/i) { devi@83: print "\n| " if $commandlines_loaded % 5 == 1; devi@74: print " ",$cl{"last_command"}; devi@74: } devi@74: igor@119: if (defined($last_cl{time}) igor@119: && grep ($_ eq $last_cl{"last_command"}, @{$Config{"editors"}})) { igor@119: bind_diff(\%last_cl); devi@74: } devi@74: devi@74: # Error code devi@74: $last_cl{"err"}=$err; devi@74: $last_cl{"raw_end"} = $cl{"raw_start"}; devi@74: devi@74: # Output devi@74: if (!$last_cl{"suppress_output"} || $last_cl{"err"}) { devi@74: for (my $i=0; $i<$Config{"terminal_height"}; $i++) { igor@115: my $line= $vt{$local_session_id}->row_plaintext($i); devi@74: next if !defined ($line) ; #|| $line =~ /^\s*$/; devi@74: $line =~ s/\s*$//; devi@74: $line .= "\n" unless $line =~ /^\s*$/; devi@74: $last_cl{"output"} .= $line; devi@74: } devi@74: } devi@74: else { devi@74: $last_cl{"output"}= ""; devi@74: } devi@74: igor@115: $vt{$local_session_id}->reset(); devi@74: devi@74: devi@74: # Changing encoding devi@74: for (keys %last_cl) { devi@74: next if /raw/; devi@74: if ($Config{"encoding"} && devi@74: $Config{"encoding"} !~ /^utf-8$/i) { devi@74: $last_cl{$_} = $converter->convert($last_cl{$_}) devi@74: } devi@74: } igor@119: if (defined($last_cl{time})) { igor@119: print STDERR "push id=".$last_cl{time}."\n"; igor@119: push @Command_Lines, \%last_cl; igor@119: # Сохранение позиции в файле, до которой выполнен igor@119: # успешный разбор igor@119: $Script_Files{$file}->{tell} = $last_cl{raw_end}; igor@119: } devi@74: next; devi@74: } devi@74: devi@74: # Иначе, это строка вывода devi@74: devi@62: $last_output_length+=length($_); devi@62: #if (!$cl{"suppress_output"} || $last_output_length < 5000) { devi@62: if ($last_output_length < 50000) { igor@115: $vt{$local_session_id}->process("$_"."\n") devi@62: } devi@62: else devi@62: { igor@119: if (!$skip_info && defined($cl{last_command})) { devi@62: print "($cl{last_command})"; devi@62: $skip_info = 1; devi@62: } devi@62: } devi@62: } devi@62: close(FILE); devi@23: devi@62: } devi@62: if ($Config{"verbose"} =~ /y/) { devi@74: print "\n`- finished.\n" ; devi@62: print "Lines loaded: $commandlines_processed\n"; devi@62: print "Command lines: $commandlines_loaded\n"; devi@62: } devi@23: } devi@23: devi@23: devi@23: devi@62: devi@62: sub sort_command_lines devi@62: { devi@74: print "Sorting command lines..." if $Config{"verbose"} =~ /y/; devi@62: devi@62: # Sort Command_Lines devi@62: # Write Command_Lines to Command_Lines_Index devi@62: devi@62: my @index; devi@62: for (my $i=0;$i<=$#Command_Lines;$i++) { devi@62: $index[$i]=$i; devi@62: } devi@62: devi@62: @Command_Lines_Index = sort { devi@86: defined($Command_Lines[$index[$a]]->{"time"}) devi@86: && defined($Command_Lines[$index[$b]]->{"time"}) devi@81: ? $Command_Lines[$index[$a]]->{"time"} <=> $Command_Lines[$index[$b]]->{"time"} devi@84: : defined($Command_Lines[$index[$a]]->{"day"}) devi@86: && defined($Command_Lines[$index[$b]]->{"day"}) devi@86: && defined($Command_Lines[$index[$a]]->{"hour"}) devi@86: && defined($Command_Lines[$index[$b]]->{"hour"}) devi@86: && defined($Command_Lines[$index[$a]]->{"min"}) devi@86: && defined($Command_Lines[$index[$b]]->{"min"}) devi@86: && defined($Command_Lines[$index[$a]]->{"sec"}) devi@86: && defined($Command_Lines[$index[$b]]->{"sec"}) devi@86: ? $Command_Lines[$index[$a]]->{"day"} cmp $Command_Lines[$index[$b]]->{"day"} devi@86: || $Command_Lines[$index[$a]]->{"hour"} <=> $Command_Lines[$index[$b]]->{"hour"} devi@86: || $Command_Lines[$index[$a]]->{"min"} <=> $Command_Lines[$index[$b]]->{"min"} devi@86: || $Command_Lines[$index[$a]]->{"sec"} <=> $Command_Lines[$index[$b]]->{"sec"} devi@86: : 0 devi@62: } @index; devi@62: devi@74: print "finished\n" if $Config{"verbose"} =~ /y/; devi@62: devi@62: } devi@62: devi@23: sub printq devi@23: { devi@62: my $TO = shift; devi@62: my $text = join "", @_; devi@62: $text =~ s/&/&/g; devi@62: $text =~ s/</</g; devi@62: $text =~ s/>/>/g; devi@62: print $TO $text; devi@23: } devi@23: devi@23: devi@23: =cut devi@23: Вывести результат обработки журнала. devi@23: =cut devi@23: devi@23: sub print_command_lines devi@23: { devi@62: my $output_filename=$_[0]; devi@85: open(OUT, ">>", $output_filename) devi@62: or die "Can't open $output_filename for writing\n"; devi@23: devi@23: devi@62: my $cl; devi@62: my $in_range=0; devi@62: for my $i (@Command_Lines_Index) { devi@62: $cl = $Command_Lines[$i]; devi@23: devi@62: if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) { devi@62: $in_range=1; devi@62: next; devi@62: } devi@62: if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) { devi@62: $in_range=0; devi@62: next; devi@62: } devi@62: next if ($Config{"from"} && $Config{"to"} && !$in_range) devi@62: || devi@62: ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ ) devi@62: || devi@62: ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0) devi@62: || devi@62: ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130); devi@62: devi@62: # Вырезаем из вывода только нужное количество строк devi@23: devi@62: my $output=""; devi@69: devi@73: if (!grep ($_ eq $cl->{"last_command"}, @{$Config{"full_output_commands"}}) devi@69: && ($Config{"head_lines"} devi@73: || $Config{"tail_lines"})) { devi@62: # Partialy output devi@62: my @lines = split '\n', $cl->{"output"}; devi@62: # head devi@62: my $mark=1; devi@62: for (my $i=0; $i<= $#lines && $i < $Config{"cache_head_lines"}; $i++) { devi@62: $output .= $lines[$i]."\n"; devi@62: } devi@62: # tail devi@62: my $start=$#lines-$Config{"cache_tail_lines"}+1; devi@62: if ($start < 0) { devi@62: $start=0; devi@62: $mark=0; devi@62: } devi@62: if ($start < $Config{"cache_head_lines"}) { devi@62: $start=$Config{"cache_head_lines"}; devi@62: $mark=0; devi@62: } devi@62: $output .= $Config{"skip_text"}."\n" if $mark; devi@73: for ($i=$start; $i<= $#lines; $i++) { devi@62: $output .= $lines[$i]."\n"; devi@62: } devi@62: } devi@62: else { devi@62: # Full output devi@62: $output .= $cl->{"output"}; devi@62: } devi@23: devi@62: # Совместимость с labmaker devi@23: devi@62: # Переводим в секунды Эпохи devi@62: # В labmaker'е данные хранились в неудобной форме: hour, min, sec, day of year devi@62: # Информация о годе отсутствовала devi@62: # Её можно внести: devi@62: # Декабрь 2004 год; остальные -- 2005 год. devi@23: devi@62: my $year = 2005; devi@62: #$year = 2004 if ( $cl->{day} > 330 ); devi@62: $year = $Config{year} if $Config{year}; devi@62: # timelocal( $sec, $min, $hour, $mday,$mon,$year); devi@74: $cl->{time} ||= timelocal_nocheck($cl->{sec},$cl->{min},$cl->{hour},$cl->{day},0,$year); devi@23: devi@23: devi@62: # Начинаем вывод команды devi@62: print OUT "<command>\n"; devi@98: print OUT "<l3cd>$Config{l3cd}</l3cd>\n" if $Config{"l3cd"}; devi@62: for my $element (qw( devi@62: local_session_id devi@74: history devi@74: uid devi@74: pid devi@62: time devi@74: pwd devi@62: raw_start devi@62: raw_output_start devi@62: raw_end devi@62: raw_file devi@62: tty devi@62: err devi@62: last_command devi@74: history igor@119: nonce devi@62: )) { devi@65: next unless defined($cl->{"$element"}); devi@62: print OUT "<$element>".$cl->{$element}."</$element>\n"; devi@62: } devi@62: for my $element (qw( devi@62: prompt devi@62: cline devi@62: )) { devi@65: next unless defined($cl->{"$element"}); devi@62: print OUT "<$element>"; devi@62: printq(\*OUT,$cl->{"$element"}); devi@62: print OUT "</$element>\n"; devi@62: } devi@72: #note devi@72: #note_title devi@62: print OUT "<output>"; devi@62: printq(\*OUT,$output); devi@62: print OUT "</output>\n"; devi@62: if ($cl->{"diff"}) { devi@62: print OUT "<diff>"; devi@62: printq(\*OUT,${$Diffs{$cl->{"diff"}}}{"text"}); devi@62: print OUT "</diff>\n"; devi@62: } devi@62: print OUT "</command>\n"; devi@23: devi@62: } devi@23: devi@62: close(OUT); devi@27: } devi@27: devi@27: sub print_session devi@27: { devi@62: my $output_filename = $_[0]; devi@62: my $local_session_id = $_[1]; devi@62: return if not defined($Sessions{$local_session_id}); devi@27: devi@84: print "printing session info. session id = ".$local_session_id."\n" devi@84: if $Config{verbose} =~ /y/; devi@84: devi@62: open(OUT, ">>", $output_filename) devi@62: or die "Can't open $output_filename for writing\n"; devi@62: print OUT "<session>\n"; devi@98: print OUT "<l3cd>$Config{l3cd}</l3cd>\n" if $Config{"l3cd"}; devi@62: my %session = %{$Sessions{$local_session_id}}; devi@62: for my $key (keys %session) { devi@84: print OUT "<$key>".$session{$key}."</$key>\n"; devi@84: print " ".$key,"\n"; devi@62: } devi@62: print OUT "</session>\n"; devi@62: close(OUT); devi@27: } devi@27: devi@27: sub send_cache devi@27: { devi@62: # Если в кэше что-то накопилось, devi@62: # попытаемся отправить это на сервер devi@62: # devi@62: my $cache_was_sent=0; devi@62: devi@62: if (open(CACHE, $Config{cache})) { devi@62: local $/; devi@62: my $cache = <CACHE>; devi@62: close(CACHE); devi@27: devi@62: my $socket = IO::Socket::INET->new( devi@62: PeerAddr => $Config{backend_address}, devi@62: PeerPort => $Config{backend_port}, devi@62: proto => "tcp", devi@62: Type => SOCK_STREAM devi@62: ); devi@27: devi@62: if ($socket) { devi@62: print $socket $cache; devi@62: close($socket); devi@62: $cache_was_sent = 1; devi@62: } devi@62: } devi@62: return $cache_was_sent; devi@23: } devi@23: devi@25: sub save_cache_stat devi@25: { devi@62: open (CACHE, ">$Config{cache_stat}"); devi@62: for my $f (keys %Script_Files) { devi@62: print CACHE "$f\t",$Script_Files{$f}->{size},"\t",$Script_Files{$f}->{tell},"\n"; devi@62: } devi@62: close(CACHE); devi@25: } devi@25: devi@25: sub load_cache_stat devi@25: { devi@62: if (open (CACHE, "$Config{cache_stat}")) { devi@62: while(<CACHE>) { devi@62: chomp; devi@62: my ($f, $size, $tell) = split /\t/; devi@62: $Script_Files{$f}->{size} = $size; devi@62: $Script_Files{$f}->{tell} = $tell; devi@62: } devi@62: close(CACHE); devi@62: }; devi@25: } devi@23: devi@23: devi@23: main(); devi@23: devi@25: sub process_was_killed devi@25: { devi@62: $Killed = 1; devi@25: } devi@25: devi@98: sub reload devi@98: { devi@98: init_config; devi@98: } devi@98: devi@23: sub main devi@23: { devi@23: devi@62: $| = 1; devi@23: devi@62: init_variables(); devi@62: init_config(); devi@23: devi@27: devi@62: if ($Config{"mode"} ne "daemon") { devi@27: devi@74: # В нормальном режиме работы нужно devi@74: # считать скрипты, обработать их и записать devi@74: # результат выполнения в результирующий файл. devi@74: # После этого завершить работу. devi@74: devi@85: # Очистим кэш-файл, если он существовал devi@85: if (open (CACHE, ">", $Config{"cache"})) { devi@85: close(CACHE); devi@85: }; devi@62: load_command_lines($Config{"input"}, $Config{"input_mask"}); devi@67: sort_command_lines; devi@62: #process_command_lines; devi@62: print_command_lines($Config{"cache"}); devi@62: } devi@62: else { devi@62: if (open(PIDFILE, $Config{agent_pidfile})) { devi@62: my $pid = <PIDFILE>; devi@62: close(PIDFILE); devi@62: if ($^O eq 'linux' && $pid &&(! -e "/proc/$pid" || !`grep $Config{"l3-agent"} /proc/$pid/cmdline && grep "uid:.*\b$<\b" /proc/$pid/status`)) { devi@62: print "Removing stale pidfile\n"; devi@62: unlink $Config{agent_pidfile} devi@62: or die "Can't remove stale pidfile ". $Config{agent_pidfile}. " : $!"; devi@62: } devi@108: elsif ($^O eq 'freebsd' && defined($pid) && $pid ne "" && not `ps axo uid,pid,command | grep '$< $pid $Config{"l3-agent"}' | grep -v grep 2> /dev/null`) { devi@62: print "Removing stale pidfile\n"; devi@62: unlink $Config{agent_pidfile} devi@62: or die "Can't remove stale pidfile ". $Config{agent_pidfile}. " : $!"; devi@62: } devi@62: elsif ($^O eq 'linux' || $^O eq 'freebsd' ) { devi@62: print "l3-agent is already running: pid=$pid; pidfile=$Config{agent_pidfile}\n"; devi@62: exit(0); devi@62: } devi@62: else { devi@62: print "Unknown operating system"; devi@62: exit(0); devi@62: } devi@62: } devi@62: if ($Config{detach} =~ /^y/i) { devi@62: #$Config{verbose} = "no"; devi@62: my $pid = fork; devi@62: exit if $pid; devi@62: die "Couldn't fork: $!" unless defined ($pid); devi@27: devi@62: open(PIDFILE, ">", $Config{agent_pidfile}) devi@62: or die "Can't open pidfile ". $Config{agent_pidfile}. " for wrting: $!"; devi@62: print PIDFILE $$; devi@62: close(PIDFILE); devi@27: devi@62: for my $handle (*STDIN, *STDOUT, *STDERR) { devi@62: open ($handle, "+<", "/dev/null") devi@62: or die "can't reopen $handle to /dev/null: $!" devi@62: } devi@27: devi@62: POSIX::setsid() devi@62: or die "Can't start a new session: $!"; devi@27: devi@62: $0 = $Config{"l3-agent"}; devi@62: devi@98: $SIG{INT} = $SIG{TERM} = \&process_was_killed; devi@98: $SIG{HUP} = \&reload; devi@98: devi@62: } devi@62: while (not $Killed) { devi@62: @Command_Lines = (); devi@62: @Command_Lines_Index = (); devi@62: load_cache_stat(); devi@62: load_command_lines($Config{"input"}, $Config{"input_mask"}); devi@62: if (@Command_Lines) { devi@74: sort_command_lines; devi@65: #process_command_lines; devi@62: print_command_lines($Config{"cache"}); devi@62: } devi@62: save_cache_stat(); devi@62: if (-e $Config{cache} && (stat($Config{cache}))[7]) { devi@62: send_cache() && unlink($Config{cache}); devi@62: } devi@62: sleep($Config{"daemon_sleep_interval"} || 1); devi@62: } devi@62: devi@62: unlink $Config{agent_pidfile}; devi@62: } devi@23: devi@23: } devi@23: devi@23: sub init_variables devi@23: { devi@23: }