lilalo
view l3-agent @ 24:4b86595adb4b
Написал что нового в 0.2.2
| author | devi | 
|---|---|
| date | Wed Nov 02 19:25:39 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/&/&/g;
   526 	$text =~ s/</</g;
   527 	$text =~ s/>/>/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 }
