lilalo

view l3-agent @ 23:6d93c5f1d0e5

Выполнен шаг (2) в плане (N05) по построению распределённой системы lilalo.

Программа lm-report разрезана на две: l3-agent и l3-frontend.
Агент выполняет анализ script-файлов и записывает
результаты анализа в файл обмена (cache).
Фронтенд читает данные из файла обмена и представляет
их в требуемом формате (в настоящий момент только html).

Сейчас взаимодействие agent'а и frontend'а выполняется так:


. ^ . +-------+ . ^^ .
/ \ | | / \
( agent )-->| cache |--->( frontend )
\ / | | \ /
' . ' +-------+ ' .. '


Добавлены файлы:
l3-agent - агент
l3-frontend - фронтенд
l3-report - замена lm-report, использующая l3-agent и l3-frontend
l3-config.pm - модуль конфигурации системы

Новые конфигурационные параметры:
cache - Путь к временному XML-файлу, предназначенному
для обмена информацией между агентом и фронтендом

cache_head_lines - Количество строк вывода команды сверху, которые
должны быть сохранены в промежуточном XML-файле

cache_tail_lines - Количество строк вывода команды снизу, которые
должны быть сохранены в промежуточном XML-файле

Устаревшие параметры:
output_mask - Использование output_mask осуждается.
Параметр будет удалён из будущих версий

Использование lm-report осуждается.
В будущих версиях программа lm-report будет удалена из дистрибутива.
Вместо неё нужно использовать l3-report.
author devi
date Wed Nov 02 19:16:11 2005 +0200 (2005-11-02)
parents
children ba4d6515b8fd
line source
1 #!/usr/bin/perl -w
3 #
4 # (c) Igor Chubin, imchubin@mail.ru, 2004-2005
5 #
7 use strict;
8 use Term::VT102;
9 use Text::Iconv;
10 use Data::Dumper;
11 use Time::Local 'timelocal_nocheck';
13 use lib ".";
14 use l3config;
17 our @Command_Lines;
18 our @Command_Lines_Index;
19 our @Diffs;
21 our %Commands_Stat; # Statistics about commands usage
22 our %Files_Stat; # Statistics about commands usage
25 sub init_variables;
26 sub main;
28 sub load_diff_files;
29 sub bind_diff;
30 sub extract_from_cline;
31 sub load_command_lines;
32 sub sort_command_lines;
33 sub process_command_lines;
34 sub print_command_lines;
35 sub printq;
37 sub load_diff_files
38 {
39 my @pathes = @_;
41 for my $path (@pathes) {
42 my $template = "*.diff";
43 my @files = <$path/$template>;
44 my $i=0;
45 for my $file (@files) {
46 my %diff;
48 $diff{"path"}=$path;
49 $diff{"uid"}="SET THIS";
51 # Сейчас UID определяется из названия каталога
52 # откуда берутся diff-файлы
53 # Это неправильно
54 #
55 # ВАРИАНТ:
56 # К файлам жураналам должны прилагаться ситемны файлы,
57 # мз которых и будет определяться соответствие
58 # имён пользователей их uid'ам
59 #
60 $diff{"uid"} = 0 if $path =~ m@/root/@;
62 $diff{"bind_to"}="";
63 $diff{"time_range"}=-1;
65 next if not $file=~m@/(D?[0-9][0-9]?[0-9]?)[^/]*?([0-9]*):([0-9]*):?([0-9]*)@;
66 $diff{"day"}=$1 || "";
67 $diff{"hour"}=$2;
68 $diff{"min"}=$3;
69 $diff{"sec"}=$4 || 0;
71 $diff{"index"}=$i;
73 print "diff loaded: $diff{day} $diff{hour}:$diff{min}:$diff{sec}\n";
75 local $/;
76 open (F, "$file")
77 or return "Can't open file $file ($_[0]) for reading";
78 my $text = <F>;
79 if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i) {
80 my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8");
81 $text = $converter->convert($text);
82 }
83 close(F);
84 $diff{"text"}=$text;
85 #print "$file loaded ($diff{day})\n";
87 push @Diffs, \%diff;
88 $i++;
89 }
90 }
91 }
94 sub bind_diff
95 {
96 # my $path = shift;
97 # my $pid = shift;
98 # my $day = shift;
99 # my $lab = shift;
101 print "Trying to bind diff...\n";
103 my $cl = shift;
104 my $hour = $cl->{"hour"};
105 my $min = $cl->{"min"};
106 my $sec = $cl->{"sec"};
108 my $min_dt = 10000;
110 for my $diff (@Diffs) {
111 # Check here date, time and user
112 next if ($diff->{"day"} && $cl->{"day"} && ($cl->{"day"} ne $diff->{"day"}));
113 #next if (!$diff->{"uid"} && $cl->{"euid"} != $diff->{"uid"});
115 my $dt=($diff->{"hour"}-$hour)*3600 +($diff->{"min"}-$min)*60 + ($diff->{"sec"}-$sec);
116 if ($dt >0 && $dt < $min_dt && ($diff->{"time_range"} <0 || $dt < $diff->{"time_range"})) {
117 print "Approppriate diff found: dt=$dt\n";
118 if ($diff->{"bind_to"}) {
119 undef $diff->{"bind_to"}->{"diff"};
120 };
121 $diff->{"time_range"}=$dt;
122 $diff->{"bind_to"}=$cl;
124 $cl->{"diff"} = $diff->{"index"};
125 $min_dt = $dt;
126 }
128 }
129 }
132 sub extract_from_cline
133 # Разобрать командную строку $_[1] и возвратить хэш, содержащий
134 # номер первого появление команды в строке:
135 # команда => первая позиция
136 {
137 my $what = $_[0];
138 my $cline = $_[1];
139 my @lists = split /\;/, $cline;
142 my @commands = ();
143 for my $list (@lists) {
144 push @commands, split /\|/, $list;
145 }
147 my %commands;
148 my %files;
149 my $i=0;
150 for my $command (@commands) {
151 $command =~ /\s*(\S+)\s*(.*)/;
152 if ($1 && $1 eq "sudo" ) {
153 $commands{"$1"}=$i++;
154 $command =~ s/\s*sudo\s+//;
155 }
156 $command =~ /\s*(\S+)\s*(.*)/;
157 if ($1 && !defined $commands{"$1"}) {
158 $commands{"$1"}=$i++;
159 };
160 if ($2) {
161 my $args = $2;
162 my @args = split (/\s+/, $args);
163 for my $a (@args) {
164 $files{"$a"}=$i++
165 if !defined $files{"$a"};
166 };
169 }
170 }
172 if ($what eq "commands") {
173 return %commands;
174 } else {
175 return %files;
176 }
178 }
180 sub load_command_lines
181 {
182 my $lab_scripts_path = $_[0];
183 my $lab_scripts_mask = $_[1];
185 my $cline_re_base = qq'
186 (?:\\^?([0-9]*C?)) # exitcode
187 (?:_([0-9]+)_)? # uid
188 (?:_([0-9]+)_) # pid
189 (...?) # day
190 (.?.?) # lab
191 \\s # space separator
192 ([0-9][0-9]):([0-9][0-9]):([0-9][0-9]) # time
193 .\\[50D.\\[K # killing symbols
194 (.*?([\$\#]\\s?)) # prompt
195 (.*) # command line
196 ';
197 #my $cline_re = qr/$cline_re_base(?:$cline_re_base|$)/x;
198 #my $cline_re = qr/(?:$cline_re_base)*$cline_re_base$/x;
199 my $cline_re = qr/$cline_re_base/sx;
200 my $cline_re1 = qr/$cline_re_base\x0D/sx;
201 my $cline_re2 = qr/$cline_re_base$/sx;
203 my $vt = Term::VT102->new ( 'cols' => $Config{"terminal_width"},
204 'rows' => $Config{"terminal_height"});
205 my $cline_vt = Term::VT102->new ('cols' => $Config{"terminal_width"},
206 'rows' => $Config{"terminal_height"});
208 my $converter = Text::Iconv->new($Config{"encoding"}, "utf-8")
209 if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i);
211 print "Loading lm-scripts...\n" if $Config{"verbose"} =~ /y/;
213 my @lab_scripts = <$lab_scripts_path/$lab_scripts_mask>;
214 my $file;
215 my $files_number = $#lab_scripts;
216 my $ii = 0;
217 my $skip_info;
219 my $commandlines_loaded =0;
220 my $commandlines_processed =0;
222 for $file (@lab_scripts){
223 #printf "\t%i %3.2f\n", $ii, (100*$ii++/$files_number) if $Config{"verbose"} =~ /y/;
225 open (FILE, "$file");
226 binmode FILE;
227 $file =~ m@.*/(.*?)-.*@;
229 my $tty = $1;
230 my $first_pass = 1;
231 my %cl;
232 my $last_output_length=0;
233 while (<FILE>) {
234 $commandlines_processed++;
235 # time
237 if (/[0-9][0-9]:[0-9][0-9]:[0-9][0-9].\[[0-9][0-9]D.\[K/ && m/$cline_re/) {
238 s/.*\x0d(?!\x0a)//;
239 # print "!!!",$_,"!!!\n";
240 # next;
241 # while (m/$cline_re1/gs) {
242 # }
243 m/$cline_re2/gs;
245 $commandlines_loaded++;
246 $last_output_length=0;
248 # Previous command
249 my %last_cl = %cl;
250 my $err = $1 || "";
253 =cut
255 ТАБЛИЦА КОМАНД
257 uid
258 Идентификатор пользователя
260 tty
261 Идентификатор терминала, на котором была вызвана команда
263 pid
264 PID-процесса командного интерпретатора,
265 в котором была вызвана команда
267 lab
268 лабораторная работа, к которой относится команда.
269 Идентификатор текущей лабораторной работы
270 хранится в файле ~/.labmaker/lab
272 pwd (!)
273 текущий каталог, из которого была вызвана команда
275 day
276 время вызова, день
277 В действительности здесь хранится не время вызова команды,
278 а с момента появления приглашения командного интерпретатора
279 для ввода команды
282 hour
283 время вызова, час
285 min
286 время вызова, минута
288 sec
289 время вызова, секунда
291 time (!)
292 время вызова команды в Unix-формате.
293 Предпочтительнее использовать этот формат чем hour:min:sec,
294 использовавшийся в Labmaker
296 fullprompt
297 Приглашение командной строки
299 prompt
300 Сокращённое приглашение командной строки
302 cline
303 Командная строка
305 output
306 Результат выполнения команды
308 diff
309 Указатель на ассоциированный с командой diff
311 note (!)
312 Текстовый комментарий к команде.
313 Может генерироваться из самого лога с помощью команд
314 #^ Комментарий
315 #v Комментарий
316 в том случае, если для комментирования достаточно одной строки,
317 или с помощью команд
318 cat > /dev/null #^ Заголовок
319 Текст
320 ^D
321 в том случае, если комментарий развёрнутый.
322 В последнем случае комментарий может содержать
323 заголовок, абзацы и несложное форматирование.
325 Символ ^ или v после знака комментария # обозначает,
326 к какой команде относится комментарий:
327 к предыдущей (^) или последующей (v)
329 err
330 Код завершения командной строки
332 histnum (!)
333 Номер команды в истории командного интерпретатора
335 status (!)
336 Является ли данная команда вызванной (r), запомненной (s)
337 или это подсказка completion (c).
339 Команды, которые были вызваны и обработаны интерпретатором
340 имеют состояние "r". К таким командам относится большинство
341 команд вводимых в интерпретатор.
343 Если команда набрана, но вызывать её по какой-либо причине
344 не хочется (например, команда может быть не полной, вредоносной
345 или просто бессмысленной в текущих условиях),
346 её можно сбросить с помощью комбинации клавиш Ctrl-C
347 (не путайте с прерыванием работающей команды! здесь она даже
348 не запускается!).
349 В таком случае она не выполняется, но попадает в журнал
350 со статусом "s".
352 Если команда появилась в журнале благодаря автопроолжению
353 -- когда было показано несколько вариантов --
354 она имеет статус "c".
356 euid
357 Идентификатор пользователя от имени которого будет
358 выполняться команда.
359 Может отличаться от реального uid в том случае,
360 если вызывается с помощью sudo
363 version (!)
364 Версия lilalo-prompt использовавшаяся при записи
365 команды.
367 0 - версия использовавшая в labmaker.
368 Отсутствует информация о текущем каталоге и номере в истории.
369 Информация о версии также не указана в приглашении.
372 1 - версия использующаяся в lilalo
374 raw_file (*)
375 Имя файла, в котором находится бинарное представление журнала.
376 Может содержать ключевое слово HERE,
377 обозначающее что бинарное представление хранится
378 непосредственно в базе данных в атрибуте raw_data
380 raw_start (*)
381 Начало блока командной строки в файле бинарного представления
383 raw_end (*)
384 Конец блока командной строки в файле бинарного представления
386 raw_cline (*)
387 Необработанная командная строка в бинарном виде
389 raw_data (*)
390 Бинарное представление команды и результатов её выполнения
395 ТАБЛИЦА SESSION
397 Информация о сеансах
402 =cut
404 # Parse new command
405 $cl{"uid"} = $2;
406 $cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0
407 $cl{"pid"} = $3;
408 $cl{"day"} = $4;
409 $cl{"lab"} = $5;
410 $cl{"hour"} = $6;
411 $cl{"min"} = $7;
412 $cl{"sec"} = $8;
413 $cl{"fullprompt"} = $9;
414 $cl{"prompt"} = $10;
415 $cl{"raw_cline"} = $11;
417 $cl{"err"} = 0;
418 $cl{"output"} = "";
419 $cl{"tty"} = $tty;
421 $cline_vt->process($cl{"raw_cline"}."\n");
422 $cl{"cline"} = $cline_vt->row_plaintext (1);
423 $cl{"cline"} =~ s/\s*$//;
424 $cline_vt->reset();
426 my %commands = extract_from_cline("commands", $cl{"cline"});
427 $cl{"euid"}=0 if defined $commands{"sudo"};
428 my @comms = sort { $commands{$a} cmp $commands{$b} } keys %commands;
429 $cl{"last_command"} = $comms[$#comms] || "";
431 if (
432 $Config{"suppress_editors"} =~ /^y/i
433 && grep ($_ eq $cl{"last_command"}, @{$Config{"editors"}}) ||
434 $Config{"suppress_pagers"} =~ /^y/i
435 && grep ($_ eq $cl{"last_command"}, @{$Config{"pagers"}}) ||
436 $Config{"suppress_terminal"}=~ /^y/i
437 && grep ($_ eq $cl{"last_command"}, @{$Config{"terminal"}})
438 ) {
439 $cl{"suppress_output"} = "1";
440 }
441 else {
442 $cl{"suppress_output"} = "0";
444 }
445 $skip_info = 0;
448 print " ",$cl{"last_command"};
450 # Processing previous command line
451 if ($first_pass) {
452 $first_pass = 0;
453 next;
454 }
456 # Error code
457 $last_cl{"err"}=$err;
458 $last_cl{"err"}=130 if $err eq "^C";
460 if (grep ($_ eq $last_cl{"last_command"}, @{$Config{"editors"}})) {
461 bind_diff(\%last_cl);
462 }
464 # Output
465 if (!$last_cl{"suppress_output"} || $last_cl{"err"}) {
466 for (my $i=0; $i<$Config{"terminal_height"}; $i++) {
467 my $line= $vt->row_plaintext($i);
468 next if !defined ($line) || $line =~ /^\s*$/;
469 $line =~ s/\s*$//;
470 $last_cl{"output"} .= $line."\n";
471 }
472 }
473 else {
474 $last_cl{"output"}= "";
475 }
477 $vt->reset();
480 # Classifying the command line
483 # Save
484 if (!$Config{"lab"} || $cl{"lab"} eq $Config{"lab"}) {
485 # Changing encoding
486 for (keys %last_cl) {
487 $last_cl{$_} = $converter->convert($last_cl{$_})
488 if ($Config{"encoding"} &&
489 $Config{"encoding"} !~ /^utf-8$/i);
490 }
491 push @Command_Lines, \%last_cl;
492 }
493 next;
494 }
495 $last_output_length+=length($_);
496 #if (!$cl{"suppress_output"} || $last_output_length < 5000) {
497 if ($last_output_length < 50000) {
498 #print "(",length($_),")" if (length($_) > 2000) ;
499 $vt->process("$_"."\n")
500 }
501 else
502 {
503 if (!$skip_info) {
504 print "($cl{last_command})";
505 $skip_info = 1;
506 }
507 }
508 }
509 close(FILE);
511 }
512 if ($Config{"verbose"} =~ /y/) {
513 print "...finished." ;
514 print "Lines loaded: $commandlines_processed\n";
515 print "Command lines: $commandlines_loaded\n";
516 }
517 }
521 sub printq
522 {
523 my $TO = shift;
524 my $text = join "", @_;
525 $text =~ s/&/&amp;/g;
526 $text =~ s/</&lt;/g;
527 $text =~ s/>/&gt;/g;
528 print $TO $text;
529 }
532 sub sort_command_lines
533 {
534 print "Sorting command lines...\n" if $Config{"verbose"} =~ /y/;
536 # Sort Command_Lines
537 # Write Command_Lines to Command_Lines_Index
539 my @index;
540 for (my $i=0;$i<=$#Command_Lines;$i++) {
541 $index[$i]=$i;
542 }
544 @Command_Lines_Index = sort {
545 $Command_Lines[$index[$a]]->{"day"} cmp $Command_Lines[$index[$b]]->{"day"} ||
546 $Command_Lines[$index[$a]]->{"hour"} <=> $Command_Lines[$index[$b]]->{"hour"} ||
547 $Command_Lines[$index[$a]]->{"min"} <=> $Command_Lines[$index[$b]]->{"min"} ||
548 $Command_Lines[$index[$a]]->{"sec"} <=> $Command_Lines[$index[$b]]->{"sec"}
549 } @index;
551 print "...finished\n" if $Config{"verbose"} =~ /y/;
553 }
555 sub process_command_lines
556 {
557 for my $i (@Command_Lines_Index) {
559 my $cl = \$Command_Lines[$i];
560 @{${$cl}->{"new_commands"}} =();
561 @{${$cl}->{"new_files"}} =();
562 $$cl->{"class"} = "";
564 if ($$cl->{"err"}) {
565 $$cl->{"class"}="wrong";
566 $$cl->{"class"}="interrupted"
567 if ($$cl->{"err"} eq 130);
568 }
569 if (!$$cl->{"euid"}) {
570 $$cl->{"class"}.="_root";
571 }
573 #tab# my @tab_words=split /\s+/, $$cl->{"output"};
574 #tab# my $last_word= $$cl->{"cline"} =~ /(\S*)$/;
575 #tab# $last_word =~ s@.*/@@;
576 #tab# my $this_is_tab=1;
577 #tab#
578 #tab# if ($last_word && @tab_words >2) {
579 #tab# for my $tab_words (@tab_words) {
580 #tab# if ($tab_words !~ /^$last_word/) {
581 #tab# $this_is_tab=0;
582 #tab# last;
583 #tab# }
584 #tab# }
585 #tab# }
586 #tab# $$cl->{"class"}="tab" if $this_is_tab;
589 if ( !$$cl->{"err"}) {
590 # Command does not contain mistakes
592 my %commands = extract_from_cline("commands", ${$cl}->{"cline"});
593 my %files = extract_from_cline("files", ${$cl}->{"cline"});
595 # Searching for new commands only
596 for my $command (keys %commands) {
597 if (!defined $Commands_Stat{$command}) {
598 push @{$$cl->{new_commands}}, $command;
599 }
600 $Commands_Stat{$command}++;
601 }
603 for my $file (keys %files) {
604 if (!defined $Files_Stat{$file}) {
605 push @{$$cl->{new_files}}, $file;
606 }
607 $Files_Stat{$file}++;
608 }
609 }
610 }
612 }
615 =cut
616 Вывести результат обработки журнала.
617 =cut
620 sub print_command_lines
621 {
622 my $output_filename=$_[0];
623 open(OUT, ">", $output_filename)
624 or die "Can't open $output_filename for writing\n";
628 print OUT "<livelablog>\n";
630 my $cl;
631 my $in_range=0;
632 for my $i (@Command_Lines_Index) {
633 $cl = $Command_Lines[$i];
635 if ($Config{"from"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"from"}/) {
636 $in_range=1;
637 next;
638 }
639 if ($Config{"to"} && $cl->{"cline"} =~ /$Config{"signature"}\s*$Config{"to"}/) {
640 $in_range=0;
641 next;
642 }
643 next if ($Config{"from"} && $Config{"to"} && !$in_range)
644 ||
645 ($Config{"skip_empty"} =~ /^y/i && $cl->{"cline"} =~ /^\s*$/ )
646 ||
647 ($Config{"skip_wrong"} =~ /^y/i && $cl->{"err"} != 0)
648 ||
649 ($Config{"skip_interrupted"} =~ /^y/i && $cl->{"err"} == 130);
651 my @new_commands=@{$cl->{"new_commands"}};
652 my @new_files=@{$cl->{"new_files"}};
654 my $cl_class="cline";
655 my $out_class="output";
656 if ($cl->{"class"}) {
657 $cl_class = $cl->{"class"}."_".$cl_class;
658 $out_class = $cl->{"class"}."_".$out_class;
659 }
661 # Вырезаем из вывода только нужное количество строк
663 my $output="";
664 if ($Config{"head_lines"} || $Config{"tail_lines"}) {
665 # Partialy output
666 my @lines = split '\n', $cl->{"output"};
667 # head
668 my $mark=1;
669 for (my $i=0; $i<= $#lines && $i < $Config{"cache_head_lines"}; $i++) {
670 $output .= $lines[$i]."\n";
671 }
672 # tail
673 my $start=$#lines-$Config{"cache_tail_lines"}+1;
674 if ($start < 0) {
675 $start=0;
676 $mark=0;
677 }
678 if ($start < $Config{"cache_head_lines"}) {
679 $start=$Config{"cache_head_lines"};
680 $mark=0;
681 }
682 $output .= $Config{"skip_text"}."\n" if $mark;
683 for (my $i=$start; $i<= $#lines; $i++) {
684 $output .= $lines[$i]."\n";
685 }
686 }
687 else {
688 # Full output
689 $output .= $cl->{"output"};
690 }
691 $output .= "^C\n" if ($cl->{"err"} eq "130");
694 # Совместимость с labmaker
696 # Переводим в секунды Эпохи
697 # В labmaker'е данные хранились в неудобной форме: hour, min, sec, day of year
698 # Информация о годе отсутствовала
699 # Её можно внести:
700 # Декабрь 2004 год; остальные -- 2005 год.
702 my $year = 2005;
703 $year = 2004 if ( $cl->{day} > 330 );
704 # timelocal( $sec, $min, $hour, $mday,$mon,$year);
705 $cl->{time} = timelocal_nocheck($cl->{sec},$cl->{min},$cl->{hour},$cl->{day},0,$year);
708 # Начинаем вывод команды
709 print OUT "<command>\n";
710 print OUT "<time>",$cl->{time},"</time>\n";
711 print OUT "<tty>",$cl->{tty},"</tty>\n";
712 print OUT "<out_class>",$out_class,"</out_class>\n";
713 print OUT "<prompt>";
714 printq(\*OUT,,$cl->{"prompt"});
715 print OUT "</prompt>";
716 print OUT "<cline>";
717 printq(\*OUT,$cl->{"cline"});
718 print OUT "</cline>\n";
719 print OUT "<last_command>",$cl->{"last_command"},"</last_command>\n";
720 if (@new_commands) {
721 print OUT "<new_commands>";
722 printq(\*OUT, join (" ", @new_commands));
723 print OUT "</new_commands>";
724 }
725 if (@new_files) {
726 print OUT "<new_files>";
727 printq(\*OUT, join (" ", @new_files));
728 print OUT "</new_files>";
729 }
730 print OUT "<output>";
731 printq(\*OUT,$output);
732 print OUT "</output>\n";
733 if ($cl->{"diff"}) {
734 print OUT "<diff>";
735 printq(\*OUT,${$Diffs[$cl->{"diff"}]}{"text"});
736 print OUT "</diff>\n";
737 }
738 print OUT "</command>\n";
740 }
742 print OUT "</livelablog>\n";
743 close(OUT);
744 }
747 =cut
748 sub print_command_lines2
749 {
750 my $output_filename=$_[0];
751 open(OUT, ">", $output_filename)
752 or die "Can't open $output_filename for writing\n";
755 print OUT <<OUT;
756 <log>
757 OUT
759 my $cl;
760 for my $i (@Command_Lines_Index) {
763 $cl = $Command_Lines[$i];
766 # Printing out
767 print OUT <<OUT;
768 <command>
769 <day>$cl->{day}</day>
770 <hour>$cl->{hour}</hour>
771 <min>$cl->{min}</min>
772 <sec>$cl->{sec}</sec>
773 <tty>$cl->{tty}</tty>
774 <uid>$cl->{uid}</uid>
775 <euid>$cl->{euid}</euid>
776 <prompt>$cl->{prompt}</prompt>
777 <cline>$cl->{cline}</cline>
778 <status>$cl->{err}</cline>
779 <output>
780 $cl->{output}</output>
781 </command>
782 OUT
783 }
785 for my $diff (@Diffs) {
787 print OUT <<OUT;
788 <diff>
789 <path>$diff->{path}</path>
790 <uid>$diff->{uid}</uid>
791 <day>$diff->{day}</day>
792 <hour>$diff->{hour}</hour>
793 <min>$diff->{min}</min>
794 <sec>$diff->{sec}</sec>
795 <text>
796 $diff->{text}</text>
797 </diff>
798 OUT
799 }
801 print OUT <<OUT;
802 </log>
803 OUT
804 }
805 =cut
807 main();
809 sub main
810 {
811 $| = 1;
813 init_variables();
814 init_config();
816 for my $lab_log (split (/\s+/, $Config{"diffs"} || $Config{"input"})) {
817 load_diff_files($lab_log);
818 }
820 load_command_lines($Config{"input"}, $Config{"input_mask"});
821 sort_command_lines;
822 process_command_lines;
823 print_command_lines($Config{"cache"});
825 }
827 sub init_variables
828 {
829 }