lilalo
view l3-agent @ 27:098664cf339c
Выполнены шаги 4,5 в плане N05 по построению распределённой системы lilalo.
Шаг <6> в настоящее время не является необходимым.
Введено понятие сеанса.
Сеансом считается процедура работы с системой, начинающаяся с регистрации
в ней и зазаканчивающаяся разрегистрацией, и сопровождающаяся ведением одного
файла скрипта.
Одновременно с созданием скрипта (.script) создаётся соответствующий ему
файл с информацией о сеансе (.info).
Каждый сеанс имеет уникальный в пределах хоста идентификатор,
~local_session_id~, который впоследствии позволяет определить,
какие команды относятся к какому сеансу.
Добавлен backend-сервер, который получает данные от агентов и записывает
из в backend (в настойщий момент - в XML-файл).
Данные передаются по tcp-соединениям.
(Одновременно может работать несколько серверов.
Блокировка файла при записи пока что не выполняется ОСТОРОЖНО!!!!!!)
Агент периодически пытается отправить backend-серверу содержимое своего кэш-файла,
и если ему это удаётся, кэш файл очищается -- данные теперь хранятся в backend'е.
Взаимодействие агентов, backend-сервера и frontend'а
сейчас выполнеятся так:
+-------+
| |
| cache |
| |
+-^---+-+
| |
. ^ v . ^^ . +---------+ . ^^ .
/ \ tcp / \ | | / \ CGI
( agent )----->( backend- )-->| backend |-->( frontend )----->
\ / \ сервер / | | \ /
' . ' ' .. ' +---------+ ' .. '
^
|
+----+----+
| |
|*.script |
| *.info |
| |
+---------+
l3-frontend:
Теперь может выдавать результат работы на стандартный поток вывода.
Вместо имени файла нужно указать символ -
Добавлены файлы:
l3-backend - backend-сервер
l3-cgi - CGI-обвязка для l3-frontend'а
Новые конфигурационные параметры:
frontend_css Путь к файлу CSS, используемому в HTML-странице, которую генерирует frontend
frontend_google_ico Путь к иконке google
frontend_linux_ico Путь к иконке linux
frontend_freebsd_ico Путь к иконке freebsd
frontend_opennet_ico Путь к иконке opennet
frontend_local_ico Путь к иконке локальной документации
backend_address IP-адрес интерфейса, на котором работает backend-сервер
backend_port Порт, который слушает backend-сервер
backend_pidfile Путь к файлу, который хранит идентификатор процесса backend-сервера
backend_datafile Путь к файлу хранилищу (файлу backend)
Шаг <6> в настоящее время не является необходимым.
Введено понятие сеанса.
Сеансом считается процедура работы с системой, начинающаяся с регистрации
в ней и зазаканчивающаяся разрегистрацией, и сопровождающаяся ведением одного
файла скрипта.
Одновременно с созданием скрипта (.script) создаётся соответствующий ему
файл с информацией о сеансе (.info).
Каждый сеанс имеет уникальный в пределах хоста идентификатор,
~local_session_id~, который впоследствии позволяет определить,
какие команды относятся к какому сеансу.
Добавлен backend-сервер, который получает данные от агентов и записывает
из в backend (в настойщий момент - в XML-файл).
Данные передаются по tcp-соединениям.
(Одновременно может работать несколько серверов.
Блокировка файла при записи пока что не выполняется ОСТОРОЖНО!!!!!!)
Агент периодически пытается отправить backend-серверу содержимое своего кэш-файла,
и если ему это удаётся, кэш файл очищается -- данные теперь хранятся в backend'е.
Взаимодействие агентов, backend-сервера и frontend'а
сейчас выполнеятся так:
+-------+
| |
| cache |
| |
+-^---+-+
| |
. ^ v . ^^ . +---------+ . ^^ .
/ \ tcp / \ | | / \ CGI
( agent )----->( backend- )-->| backend |-->( frontend )----->
\ / \ сервер / | | \ /
' . ' ' .. ' +---------+ ' .. '
^
|
+----+----+
| |
|*.script |
| *.info |
| |
+---------+
l3-frontend:
Теперь может выдавать результат работы на стандартный поток вывода.
Вместо имени файла нужно указать символ -
Добавлены файлы:
l3-backend - backend-сервер
l3-cgi - CGI-обвязка для l3-frontend'а
Новые конфигурационные параметры:
frontend_css Путь к файлу CSS, используемому в HTML-странице, которую генерирует frontend
frontend_google_ico Путь к иконке google
frontend_linux_ico Путь к иконке linux
frontend_freebsd_ico Путь к иконке freebsd
frontend_opennet_ico Путь к иконке opennet
frontend_local_ico Путь к иконке локальной документации
backend_address IP-адрес интерфейса, на котором работает backend-сервер
backend_port Порт, который слушает backend-сервер
backend_pidfile Путь к файлу, который хранит идентификатор процесса backend-сервера
backend_datafile Путь к файлу хранилищу (файлу backend)
author | devi |
---|---|
date | Mon Nov 07 11:24:49 2005 +0200 (2005-11-07) |
parents | ba4d6515b8fd |
children | 450b6ac9b657 |
line source
1 #!/usr/bin/perl -w
3 #
4 # (c) Igor Chubin, imchubin@mail.ru, 2004-2005
5 #
7 use strict;
8 use POSIX;
9 use Term::VT102;
10 use Text::Iconv;
11 use Data::Dumper;
12 use Time::Local 'timelocal_nocheck';
13 use IO::Socket;
15 use lib ".";
16 use l3config;
19 our @Command_Lines;
20 our @Command_Lines_Index;
21 our @Diffs;
22 our %Sessions;
24 our %Commands_Stat; # Statistics about commands usage
25 our %Files_Stat; # Statistics about commands usage
27 our %Script_Files; # Информация о позициях в скрипт-файлах,
28 # до которых уже выполнен разбор
29 # и информация о времени модификации файла
30 # $Script_Files{$file}->{size}
31 # $Script_Files{$file}->{tell}
33 our $Killed =0; # В режиме демона -- процесс получил сигнал о завершении
35 sub init_variables;
36 sub main;
38 sub load_diff_files;
39 sub bind_diff;
40 sub extract_from_cline;
41 sub load_command_lines;
42 sub sort_command_lines;
43 sub process_command_lines;
44 sub print_command_lines;
45 sub printq;
47 sub save_cache_stat;
48 sub load_cache_stat;
49 sub print_session;
51 sub load_diff_files
52 {
53 my @pathes = @_;
55 for my $path (@pathes) {
56 my $template = "*.diff";
57 my @files = <$path/$template>;
58 my $i=0;
59 for my $file (@files) {
60 my %diff;
62 $diff{"path"}=$path;
63 $diff{"uid"}="SET THIS";
65 # Сейчас UID определяется из названия каталога
66 # откуда берутся diff-файлы
67 # Это неправильно
68 #
69 # ВАРИАНТ:
70 # К файлам жураналам должны прилагаться ситемны файлы,
71 # мз которых и будет определяться соответствие
72 # имён пользователей их uid'ам
73 #
74 $diff{"uid"} = 0 if $path =~ m@/root/@;
76 $diff{"bind_to"}="";
77 $diff{"time_range"}=-1;
79 next if not $file=~m@/(D?[0-9][0-9]?[0-9]?)[^/]*?([0-9]*):([0-9]*):?([0-9]*)@;
80 $diff{"day"}=$1 || "";
81 $diff{"hour"}=$2;
82 $diff{"min"}=$3;
83 $diff{"sec"}=$4 || 0;
85 $diff{"index"}=$i;
87 print "diff loaded: $diff{day} $diff{hour}:$diff{min}:$diff{sec}\n";
89 local $/;
90 open (F, "$file")
91 or return "Can't open file $file ($_[0]) for reading";
92 my $text = <F>;
93 if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i) {
94 my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8");
95 $text = $converter->convert($text);
96 }
97 close(F);
98 $diff{"text"}=$text;
99 #print "$file loaded ($diff{day})\n";
101 push @Diffs, \%diff;
102 $i++;
103 }
104 }
105 }
108 sub bind_diff
109 {
110 # my $path = shift;
111 # my $pid = shift;
112 # my $day = shift;
113 # my $lab = shift;
115 print "Trying to bind diff...\n";
117 my $cl = shift;
118 my $hour = $cl->{"hour"};
119 my $min = $cl->{"min"};
120 my $sec = $cl->{"sec"};
122 my $min_dt = 10000;
124 for my $diff (@Diffs) {
125 # Check here date, time and user
126 next if ($diff->{"day"} && $cl->{"day"} && ($cl->{"day"} ne $diff->{"day"}));
127 #next if (!$diff->{"uid"} && $cl->{"euid"} != $diff->{"uid"});
129 my $dt=($diff->{"hour"}-$hour)*3600 +($diff->{"min"}-$min)*60 + ($diff->{"sec"}-$sec);
130 if ($dt >0 && $dt < $min_dt && ($diff->{"time_range"} <0 || $dt < $diff->{"time_range"})) {
131 print "Approppriate diff found: dt=$dt\n";
132 if ($diff->{"bind_to"}) {
133 undef $diff->{"bind_to"}->{"diff"};
134 };
135 $diff->{"time_range"}=$dt;
136 $diff->{"bind_to"}=$cl;
138 $cl->{"diff"} = $diff->{"index"};
139 $min_dt = $dt;
140 }
142 }
143 }
146 sub extract_from_cline
147 # Разобрать командную строку $_[1] и возвратить хэш, содержащий
148 # номер первого появление команды в строке:
149 # команда => первая позиция
150 {
151 my $what = $_[0];
152 my $cline = $_[1];
153 my @lists = split /\;/, $cline;
156 my @commands = ();
157 for my $list (@lists) {
158 push @commands, split /\|/, $list;
159 }
161 my %commands;
162 my %files;
163 my $i=0;
164 for my $command (@commands) {
165 $command =~ /\s*(\S+)\s*(.*)/;
166 if ($1 && $1 eq "sudo" ) {
167 $commands{"$1"}=$i++;
168 $command =~ s/\s*sudo\s+//;
169 }
170 $command =~ /\s*(\S+)\s*(.*)/;
171 if ($1 && !defined $commands{"$1"}) {
172 $commands{"$1"}=$i++;
173 };
174 if ($2) {
175 my $args = $2;
176 my @args = split (/\s+/, $args);
177 for my $a (@args) {
178 $files{"$a"}=$i++
179 if !defined $files{"$a"};
180 };
183 }
184 }
186 if ($what eq "commands") {
187 return %commands;
188 } else {
189 return %files;
190 }
192 }
194 sub load_command_lines
195 {
196 my $lab_scripts_path = $_[0];
197 my $lab_scripts_mask = $_[1];
199 my $cline_re_base = qq'
200 (
201 (?:\\^?([0-9]*C?)) # exitcode
202 (?:_([0-9]+)_)? # uid
203 (?:_([0-9]+)_) # pid
204 (...?) # day
205 (.?.?) # lab
206 \\s # space separator
207 ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) # time
208 .\\[50D.\\[K # killing symbols
209 (.*?([\$\#]\\s?)) # prompt
210 (.*) # command line
211 )
212 ';
213 #my $cline_re = qr/$cline_re_base(?:$cline_re_base|$)/x;
214 #my $cline_re = qr/(?:$cline_re_base)*$cline_re_base$/x;
215 my $cline_re = qr/$cline_re_base/sx;
216 my $cline_re1 = qr/$cline_re_base\x0D/sx;
217 my $cline_re2 = qr/$cline_re_base$/sx;
219 my $vt = Term::VT102->new ( 'cols' => $Config{"terminal_width"},
220 'rows' => $Config{"terminal_height"});
221 my $cline_vt = Term::VT102->new ('cols' => $Config{"terminal_width"},
222 'rows' => $Config{"terminal_height"});
224 my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8")
225 if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i);
227 print "Loading lm-scripts...\n" if $Config{"verbose"} =~ /y/;
229 my $file;
230 my $skip_info;
232 my $commandlines_loaded =0;
233 my $commandlines_processed =0;
235 my @lab_scripts = <$lab_scripts_path/$lab_scripts_mask>;
236 for $file (@lab_scripts){
238 # Пропускаем файл, если он не изменялся со времени нашего предудущего прохода
239 my $size = (stat($file))[7];
240 next if ($Script_Files{$file} && $Script_Files{$file}->{size} && $Script_Files{$file}->{size} >= $size);
243 my $local_session_id;
244 # Начальное значение идентификатора текущего сеанса определяем из имени скрипта
245 # Впоследствии оно может быть уточнено
246 $file =~ /.*\/(.*)\.script$/;
247 $local_session_id = $1;
249 #Если файл только что появился,
250 #пытаемся найти и загрузить информацию о соответствующей ему сессии
251 if (!$Script_Files{$file}) {
252 my $session_file = $file;
253 $session_file =~ s/\.script/.info/;
254 if (open(SESSION, $session_file)) {
255 local $/;
256 my $data = <SESSION>;
257 close(SESSION);
259 for my $session_data ($data =~ m@<session>(.*?)</session>@sg) {
260 my %session;
261 while ($session_data =~ m@<([^>]*?)>(.*?)</\1>@sg) {
262 $session{$1} = $2;
263 }
264 $local_session_id = $session{"local_session_id"} if $session{"local_session_id"};
265 $Sessions{$session_id}=\%session;
266 }
268 #Загруженную информацию сразу же отправляем в поток
269 print_session($Config{cache}, $local_session_id);
270 }
271 }
273 open (FILE, "$file");
274 binmode FILE;
276 # Переходим к тому месту, где мы окончили разбор
277 seek (FILE, $Script_Files{$file}->{tell}, 0) if $Script_Files{$file}->{tell};
278 $Script_Files{$file}->{size} = $size;
279 $Script_Files{$file}->{tell} = 0 unless $Script_Files{$file}->{tell};
282 $file =~ m@.*/(.*?)-.*@;
284 my $tty = $1;
285 my $first_pass = 1;
286 my %cl;
287 my $last_output_length=0;
288 while (<FILE>) {
290 $commandlines_processed++;
291 # time
293 if (/[0-9][0-9]:[0-9][0-9]:[0-9][0-9].\[[0-9][0-9]D.\[K/ && m/$cline_re/) {
294 s/.*\x0d(?!\x0a)//;
295 # print "!!!",$_,"!!!\n";
296 # next;
297 # while (m/$cline_re1/gs) {
298 # }
299 m/$cline_re2/gs;
301 $commandlines_loaded++;
302 $last_output_length=0;
304 # Previous command
305 my %last_cl = %cl;
306 my $err = $2 || "";
309 =cut
311 Атрибуты cline
312 Список полей, характеризующих командную строку
314 uid
315 Идентификатор пользователя
317 tty
318 Идентификатор терминала, на котором была вызвана команда
320 pid
321 PID-процесса командного интерпретатора,
322 в котором была вызвана команда
324 lab
325 лабораторная работа, к которой относится команда.
326 Идентификатор текущей лабораторной работы
327 хранится в файле ~/.labmaker/lab
329 pwd (!)
330 текущий каталог, из которого была вызвана команда
332 day
333 время вызова, день
334 В действительности здесь хранится не время вызова команды,
335 а с момента появления приглашения командного интерпретатора
336 для ввода команды
339 hour
340 время вызова, час
342 min
343 время вызова, минута
345 sec
346 время вызова, секунда
348 time (!)
349 время вызова команды в Unix-формате.
350 Предпочтительнее использовать этот формат чем hour:min:sec,
351 использовавшийся в Labmaker
353 fullprompt
354 Приглашение командной строки
356 prompt
357 Сокращённое приглашение командной строки
359 cline
360 Командная строка
362 output
363 Результат выполнения команды
365 diff
366 Указатель на ассоциированный с командой diff
368 note (!)
369 Текстовый комментарий к команде.
370 Может генерироваться из самого лога с помощью команд
371 #^ Комментарий
372 #= Комментарий
373 #v Комментарий
374 в том случае, если для комментирования достаточно одной строки,
375 или с помощью команд
376 cat > /dev/null #^ Заголовок
377 Текст
378 ^D
379 в том случае, если комментарий развёрнутый.
380 В последнем случае комментарий может содержать
381 заголовок, абзацы и несложное форматирование.
383 Символы ^, v или = после знака комментария # обозначает,
384 к какой команде относится комментарий:
385 к предыдущей (^), последующей (v)
386 или это общий комментарий по тексту, не относящийся непосредственно
387 ни к одной из них (=)
389 err
390 Код завершения командной строки
392 histnum (!)
393 Номер команды в истории командного интерпретатора
395 status (!)
396 Является ли данная команда вызванной (r), запомненной (s)
397 или это подсказка completion (c).
399 Команды, которые были вызваны и обработаны интерпретатором
400 имеют состояние "r". К таким командам относится большинство
401 команд вводимых в интерпретатор.
403 Если команда набрана, но вызывать её по какой-либо причине
404 не хочется (например, команда может быть не полной, вредоносной
405 или просто бессмысленной в текущих условиях),
406 её можно сбросить с помощью комбинации клавиш Ctrl-C
407 (не путайте с прерыванием работающей команды! здесь она даже
408 не запускается!).
409 В таком случае она не выполняется, но попадает в журнал
410 со статусом "s".
412 Если команда появилась в журнале благодаря автопроолжению
413 -- когда было показано несколько вариантов --
414 она имеет статус "c".
416 euid
417 Идентификатор пользователя от имени которого будет
418 выполняться команда.
419 Может отличаться от реального uid в том случае,
420 если вызывается с помощью sudo
423 version (!)
424 Версия lilalo-prompt использовавшаяся при записи
425 команды.
427 0 - версия использовавшая в labmaker.
428 Отсутствует информация о текущем каталоге и номере в истории.
429 Информация о версии также не указана в приглашении.
432 1 - версия использующаяся в lilalo
434 raw_file
435 Имя файла, в котором находится бинарное представление журнала.
436 Может содержать ключевое слово HERE,
437 обозначающее что бинарное представление хранится
438 непосредственно в базе данных в атрибуте raw_data
440 raw_start
441 Начало блока командной строки в файле бинарного представления
443 raw_output_start
444 Начало блока вывода
446 raw_end
447 Конец блока командной строки в файле бинарного представления
449 raw_cline
450 Необработанная командная строка (без приглашения) в бинарном виде
452 raw_data (*)
453 Бинарное представление команды и результатов её выполнения
458 ТАБЛИЦА SESSION
460 Информация о сеансах
462 (см. lm-install)
465 =cut
467 $cl{"local_session_id"} = $local_session_id;
468 # Parse new command
469 $cl{"uid"} = $3;
470 $cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0
471 $cl{"pid"} = $4;
472 $cl{"day"} = $5;
473 $cl{"lab"} = $6;
474 $cl{"hour"} = $7;
475 $cl{"min"} = $8;
476 $cl{"sec"} = $9;
477 $cl{"fullprompt"} = $10;
478 $cl{"prompt"} = $11;
479 $cl{"raw_cline"} = $12;
481 {
482 use bytes;
483 $cl{"raw_start"} = tell (FILE) - length($1);
484 $cl{"raw_output_start"} = tell FILE;
485 }
486 $cl{"raw_file"} = $file;
488 $cl{"err"} = 0;
489 $cl{"output"} = "";
490 $cl{"tty"} = $tty;
492 $cline_vt->process($cl{"raw_cline"}."\n");
493 $cl{"cline"} = $cline_vt->row_plaintext (1);
494 $cl{"cline"} =~ s/\s*$//;
495 $cline_vt->reset();
497 my %commands = extract_from_cline("commands", $cl{"cline"});
498 $cl{"euid"}=0 if defined $commands{"sudo"};
499 my @comms = sort { $commands{$a} cmp $commands{$b} } keys %commands;
500 $cl{"last_command"} = $comms[$#comms] || "";
502 if (
503 $Config{"suppress_editors"} =~ /^y/i
504 && grep ($_ eq $cl{"last_command"}, @{$Config{"editors"}}) ||
505 $Config{"suppress_pagers"} =~ /^y/i
506 && grep ($_ eq $cl{"last_command"}, @{$Config{"pagers"}}) ||
507 $Config{"suppress_terminal"}=~ /^y/i
508 && grep ($_ eq $cl{"last_command"}, @{$Config{"terminal"}})
509 ) {
510 $cl{"suppress_output"} = "1";
511 }
512 else {
513 $cl{"suppress_output"} = "0";
515 }
516 $skip_info = 0;
519 print " ",$cl{"last_command"};
521 # Processing previous command line
522 if ($first_pass) {
523 $first_pass = 0;
524 next;
525 }
527 # Error code
528 $last_cl{"raw_end"} = $cl{"raw_start"};
529 $last_cl{"err"}=$err;
530 $last_cl{"err"}=130 if $err eq "^C";
532 if (grep ($_ eq $last_cl{"last_command"}, @{$Config{"editors"}})) {
533 bind_diff(\%last_cl);
534 }
536 # Output
537 if (!$last_cl{"suppress_output"} || $last_cl{"err"}) {
538 for (my $i=0; $i<$Config{"terminal_height"}; $i++) {
539 my $line= $vt->row_plaintext($i);
540 next if !defined ($line) || $line =~ /^\s*$/;
541 $line =~ s/\s*$//;
542 $last_cl{"output"} .= $line."\n";
543 }
544 }
545 else {
546 $last_cl{"output"}= "";
547 }
549 $vt->reset();
552 # Classifying the command line
555 # Save
556 if (!$Config{"lab"} || $cl{"lab"} eq $Config{"lab"}) {
557 # Changing encoding
558 for (keys %last_cl) {
559 next if /raw/;
560 $last_cl{$_} = $converter->convert($last_cl{$_})
561 if ($Config{"encoding"} &&
562 $Config{"encoding"} !~ /^utf-8$/i);
563 }
564 push @Command_Lines, \%last_cl;
566 # Сохранение позиции в файле, до которой выполнен
567 # успешный разбор
568 $Script_Files{$file}->{tell} = $last_cl{raw_end};
569 }
570 next;
571 }
572 $last_output_length+=length($_);
573 #if (!$cl{"suppress_output"} || $last_output_length < 5000) {
574 if ($last_output_length < 50000) {
575 #print "(",length($_),")" if (length($_) > 2000) ;
576 $vt->process("$_"."\n")
577 }
578 else
579 {
580 if (!$skip_info) {
581 print "($cl{last_command})";
582 $skip_info = 1;
583 }
584 }
585 }
586 close(FILE);
588 }
589 if ($Config{"verbose"} =~ /y/) {
590 print "...finished." ;
591 print "Lines loaded: $commandlines_processed\n";
592 print "Command lines: $commandlines_loaded\n";
593 }
594 }
598 sub printq
599 {
600 my $TO = shift;
601 my $text = join "", @_;
602 $text =~ s/&/&/g;
603 $text =~ s/</</g;
604 $text =~ s/>/>/g;
605 print $TO $text;
606 }
609 sub sort_command_lines
610 {
611 print "Sorting command lines...\n" if $Config{"verbose"} =~ /y/;
613 # Sort Command_Lines
614 # Write Command_Lines to Command_Lines_Index
616 my @index;
617 for (my $i=0;$i<=$#Command_Lines;$i++) {
618 $index[$i]=$i;
619 }
621 @Command_Lines_Index = sort {
622 $Command_Lines[$index[$a]]->{"day"} cmp $Command_Lines[$index[$b]]->{"day"} ||
623 $Command_Lines[$index[$a]]->{"hour"} <=> $Command_Lines[$index[$b]]->{"hour"} ||
624 $Command_Lines[$index[$a]]->{"min"} <=> $Command_Lines[$index[$b]]->{"min"} ||
625 $Command_Lines[$index[$a]]->{"sec"} <=> $Command_Lines[$index[$b]]->{"sec"}
626 } @index;
628 print "...finished\n" if $Config{"verbose"} =~ /y/;
630 }
632 sub process_command_lines
633 {
634 for my $i (@Command_Lines_Index) {
636 my $cl = \$Command_Lines[$i];
637 @{${$cl}->{"new_commands"}} =();
638 @{${$cl}->{"new_files"}} =();
639 $$cl->{"class"} = "";
641 if ($$cl->{"err"}) {
642 $$cl->{"class"}="wrong";
643 $$cl->{"class"}="interrupted"
644 if ($$cl->{"err"} eq 130);
645 }
646 if (!$$cl->{"euid"}) {
647 $$cl->{"class"}.="_root";
648 }
650 #tab# my @tab_words=split /\s+/, $$cl->{"output"};
651 #tab# my $last_word= $$cl->{"cline"} =~ /(\S*)$/;
652 #tab# $last_word =~ s@.*/@@;
653 #tab# my $this_is_tab=1;
654 #tab#
655 #tab# if ($last_word && @tab_words >2) {
656 #tab# for my $tab_words (@tab_words) {
657 #tab# if ($tab_words !~ /^$last_word/) {
658 #tab# $this_is_tab=0;
659 #tab# last;
660 #tab# }
661 #tab# }
662 #tab# }
663 #tab# $$cl->{"class"}="tab" if $this_is_tab;
666 if ( !$$cl->{"err"}) {
667 # Command does not contain mistakes
669 my %commands = extract_from_cline("commands", ${$cl}->{"cline"});
670 my %files = extract_from_cline("files", ${$cl}->{"cline"});
672 # Searching for new commands only
673 for my $command (keys %commands) {
674 if (!defined $Commands_Stat{$command}) {
675 push @{$$cl->{new_commands}}, $command;
676 }
677 $Commands_Stat{$command}++;
678 }
680 for my $file (keys %files) {
681 if (!defined $Files_Stat{$file}) {
682 push @{$$cl->{new_files}}, $file;
683 }
684 $Files_Stat{$file}++;
685 }
686 }
687 }
689 }
692 =cut
693 Вывести результат обработки журнала.
694 =cut
697 sub print_command_lines
698 {
699 my $output_filename=$_[0];
700 my $mode = ">";
701 $mode =">>" if $Config{mode} eq "daemon";
702 open(OUT, $mode, $output_filename)
703 or die "Can't open $output_filename for writing\n";
707 #print OUT "<livelablog>\n";
709 my $cl;
710 my $in_range=0;
711 for my $i (@Command_Lines_Index) {
712 $cl = $Command_Lines[$i];
714 if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
715 $in_range=1;
716 next;
717 }
718 if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) {
719 $in_range=0;
720 next;
721 }
722 next if ($Config{"from"} && $Config{"to"} && !$in_range)
723 ||
724 ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ )
725 ||
726 ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0)
727 ||
728 ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);
730 my @new_commands=@{$cl->{"new_commands"}};
731 my @new_files=@{$cl->{"new_files"}};
733 my $cl_class="cline";
734 my $out_class="output";
735 if ($cl->{"class"}) {
736 $cl_class = $cl->{"class"}."_".$cl_class;
737 $out_class = $cl->{"class"}."_".$out_class;
738 }
740 # Вырезаем из вывода только нужное количество строк
742 my $output="";
743 if ($Config{"head_lines"} || $Config{"tail_lines"}) {
744 # Partialy output
745 my @lines = split '\n', $cl->{"output"};
746 # head
747 my $mark=1;
748 for (my $i=0; $i<= $#lines && $i < $Config{"cache_head_lines"}; $i++) {
749 $output .= $lines[$i]."\n";
750 }
751 # tail
752 my $start=$#lines-$Config{"cache_tail_lines"}+1;
753 if ($start < 0) {
754 $start=0;
755 $mark=0;
756 }
757 if ($start < $Config{"cache_head_lines"}) {
758 $start=$Config{"cache_head_lines"};
759 $mark=0;
760 }
761 $output .= $Config{"skip_text"}."\n" if $mark;
762 for (my $i=$start; $i<= $#lines; $i++) {
763 $output .= $lines[$i]."\n";
764 }
765 }
766 else {
767 # Full output
768 $output .= $cl->{"output"};
769 }
770 $output .= "^C\n" if ($cl->{"err"} eq "130");
773 # Совместимость с labmaker
775 # Переводим в секунды Эпохи
776 # В labmaker'е данные хранились в неудобной форме: hour, min, sec, day of year
777 # Информация о годе отсутствовала
778 # Её можно внести:
779 # Декабрь 2004 год; остальные -- 2005 год.
781 my $year = 2005;
782 $year = 2004 if ( $cl->{day} > 330 );
783 # timelocal( $sec, $min, $hour, $mday,$mon,$year);
784 $cl->{time} = timelocal_nocheck($cl->{sec},$cl->{min},$cl->{hour},$cl->{day},0,$year);
787 # Начинаем вывод команды
788 print OUT "<command>\n";
789 print OUT "<local_session_id>",$cl->{session_id},"</local_session_id>\n";
790 print OUT "<time>",$cl->{time},"</time>\n";
791 print OUT "<raw_start>",$cl->{raw_start},"</raw_start>\n";
792 print OUT "<raw_output_start>",$cl->{raw_output_start},"</raw_output_start>\n";
793 print OUT "<raw_end>",$cl->{raw_end},"</raw_end>\n";
794 print OUT "<raw_file>",$cl->{raw_file},"</raw_file>\n";
795 print OUT "<tty>",$cl->{tty},"</tty>\n";
796 print OUT "<out_class>",$out_class,"</out_class>\n";
797 print OUT "<prompt>";
798 printq(\*OUT,,$cl->{"prompt"});
799 print OUT "</prompt>";
800 print OUT "<cline>";
801 printq(\*OUT,$cl->{"cline"});
802 print OUT "</cline>\n";
803 print OUT "<last_command>",$cl->{"last_command"},"</last_command>\n";
804 if (@new_commands) {
805 print OUT "<new_commands>";
806 printq(\*OUT, join (" ", @new_commands));
807 print OUT "</new_commands>";
808 }
809 if (@new_files) {
810 print OUT "<new_files>";
811 printq(\*OUT, join (" ", @new_files));
812 print OUT "</new_files>";
813 }
814 print OUT "<output>";
815 printq(\*OUT,$output);
816 print OUT "</output>\n";
817 if ($cl->{"diff"}) {
818 print OUT "<diff>";
819 printq(\*OUT,${$Diffs[$cl->{"diff"}]}{"text"});
820 print OUT "</diff>\n";
821 }
822 print OUT "</command>\n";
824 }
826 #print OUT "</livelablog>\n";
827 close(OUT);
828 }
830 sub print_session
831 {
832 my $output_filename = $_[0];
833 my $local_session_id = $_[1];
834 return if not defined($Sessions{$local_session_id});
836 open(OUT, ">>", $output_filename)
837 or die "Can't open $output_filename for writing\n";
838 print OUT "<session>\n";
839 my %session = %{$Sessions{$local_session_id}};
840 for my $key (keys %session) {
841 print OUT "<$key>".$session{$key}."</$key>\n"
842 }
843 print OUT "</session>\n";
844 close(OUT);
845 }
847 sub send_cache
848 {
849 `logger "step 0"`;
850 `logger "step 1"`;
852 # Если в кэше что-то накопилось,
853 # попытаемся отправить это на сервер
854 #
855 my $cache_was_sent=0;
857 if (open(CACHE, $Config{cache})) {
858 local $/;
859 my $cache = <CACHE>;
860 close(CACHE);
862 my $socket = IO::Socket::INET->new(
863 PeerAddr => $Config{backend_address},
864 PeerPort => $Config{backend_port},
865 proto => "tcp",
866 Type => SOCK_STREAM
867 );
869 if ($socket) {
870 print $socket $cache;
871 close($socket);
872 $cache_was_sent = 1;
873 }
874 }
875 return $cache_was_sent;
876 }
878 sub save_cache_stat
879 {
880 open (CACHE, ">$Config{cache_stat}");
881 for my $f (keys %Script_Files) {
882 print CACHE "$f\t",$Script_Files{$f}->{size},"\t",$Script_Files{$f}->{tell},"\n";
883 }
884 close(CACHE);
885 }
887 sub load_cache_stat
888 {
889 if (open (CACHE, "$Config{cache_stat}")) {
890 while(<CACHE>) {
891 chomp;
892 my ($f, $size, $tell) = split /\t/;
893 $Script_Files{$f}->{size} = $size;
894 $Script_Files{$f}->{tell} = $tell;
895 }
896 close(CACHE);
897 };
898 }
901 main();
903 sub process_was_killed
904 {
905 $Killed = 1;
906 }
908 sub main
909 {
911 $| = 1;
913 init_variables();
914 init_config();
916 for my $lab_log (split (/\s+/, $Config{"diffs"} || $Config{"input"})) {
917 load_diff_files($lab_log);
918 }
920 if ($Config{"mode"} ne "daemon") {
922 =cut
923 В нормальном режиме работы нужно
924 считать скрипты, обработать их и записать
925 результат выполнения в результриующий файл.
926 После этого завершить работу.
927 =cut
928 load_command_lines($Config{"input"}, $Config{"input_mask"});
929 sort_command_lines;
930 process_command_lines;
931 print_command_lines($Config{"cache"});
932 }
933 else {
934 if (open(PIDFILE, $Config{agent_pidfile})) {
935 my $pid = <PIDFILE>;
936 close(PIDFILE);
937 if ( ! -e "/proc/$pid" || !`grep $Config{"l3-agent"} /proc/$pid/cmdline && grep "uid:.*\b$<\b" /proc/$pid/status`) {
938 print "Removing stale pidfile\n";
939 unlink $Config{agent_pidfile}
940 or die "Can't remove stale pidfile ". $Config{agent_pidfile}. " : $!";
941 }
942 else {
943 print "l3-agent is already running\n";
944 exit(0);
945 }
946 }
947 if ($Config{detach} =~ /^y/i) {
948 #$Config{verbose} = "no";
949 my $pid = fork;
950 exit if $pid;
951 die "Couldn't fork: $!" unless defined ($pid);
953 open(PIDFILE, ">", $Config{agent_pidfile})
954 or die "Can't open pidfile ". $Config{agent_pidfile}. " for wrting: $!";
955 print PIDFILE $$;
956 close(PIDFILE);
958 for my $handle (*STDIN, *STDOUT, *STDERR) {
959 open ($handle, "+<", "/dev/null")
960 or die "can't reopen $handle to /dev/null: $!"
961 }
963 POSIX::setsid()
964 or die "Can't start a new session: $!";
966 $0 = $Config{"l3-agent"};
968 $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&process_was_killed;
969 }
970 while (not $Killed) {
971 @Command_Lines = ();
972 @Command_Lines_Index = ();
973 load_cache_stat();
974 load_command_lines($Config{"input"}, $Config{"input_mask"});
975 if (@Command_Lines) {
976 sort_command_lines;
977 process_command_lines;
978 print_command_lines($Config{"cache"});
979 }
980 save_cache_stat();
981 if (-e $Config{cache} && (stat($Config{cache}))[7]) {
982 send_cache() && unlink($Config{cache});
983 }
984 sleep($Config{"daemon_sleep_interval"} || 1);
985 }
987 unlink $Config{agent_pidfile};
988 }
990 }
992 sub init_variables
993 {
994 }