1#!/usr/local/bin/perl
2#  Copyright (c) 2004, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
3#  All rights reserved.
4#
5#  Redistribution and use in source and binary forms, with or without
6#  modification, are permitted provided that the following conditions are met:
7#
8#   * Redistributions of source code must retain the above copyright notice,
9#	  this list of conditions and the following disclaimer.
10#   * Redistributions in binary form must reproduce the above copyright notice,
11#	  this list of conditions and the following disclaimer in the documentation
12#	  and/or other materials provided with the distribution.
13#   * Neither the name of SWITCH nor the names of its contributors may be
14#	  used to endorse or promote products derived from this software without
15#	  specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29#  $Author: peter $
30#
31#  $Id: NfProfile.pm 69 2014-06-23 19:27:50Z peter $
32#
33#  $LastChangedRevision: 69 $
34
35package NfProfile;
36
37use strict;
38use Sys::Syslog;
39use POSIX 'setsid';
40use File::Find;
41use Fcntl qw(:DEFAULT :flock);
42
43use NfSen;
44use NfSenRRD;
45use Nfsync;
46use Log;
47
48our $PROFILE_VERSION = 130;	# version 1.3.0
49
50my @ProfileKeys = (
51	'description',	# Array of comment lines starting with '#'
52	'name', 		# name of profile
53	'group', 		# name of profile group
54	'tbegin', 		# Begin of profile
55	'tcreate', 		# Create time of profile
56	'tstart', 		# Start time of profile data
57	'tend', 		# End time of profile
58	'updated',		# Time of last update
59	'expire',		# Max lifetime of profile data in hours 0 = no expire time
60	'maxsize',		# Max size of profile in bytes	0 = no limit
61	'size',			# Current size of profile in bytes
62	'type',			# Profile type: 0: life, 1: history profile, 2: continuous profile
63	'locked',		# somebody is working on this profile
64	'status',		# status of profile
65	'version',		# version of profile.dat
66	'channel',		# array of ':' separated list of channel proprieties
67);
68
69my @ChannelKeys = (
70	'first',
71	'last',
72	'size',
73	'maxsize',
74	'numfiles',
75	'lifetime',
76	'watermark',
77	'status',
78);
79
80our @ChannelProperties = ( 'sign', 'colour', 'order', 'sourcelist' );
81
82# Default profile description
83my @ProfileTag = (
84	"# \n",
85);
86
87my %LegacyProfileKeys = (
88	'sourcelist' => 1,	# pre 1.3 parameter
89	'filter',	 => 1	# pre 1.3 Name of filter file
90);
91
92local $SIG{'__DIE__'} = sub {
93	my $message = shift;
94
95	syslog("err","PANIC!: I'm dying: '$message'");
96
97}; # End of __DIE__
98
99my $EODATA 	= ".\n";
100
101sub VerifyProfile {
102	my $profile  	 = shift;
103	my $profilegroup = shift;
104	my $must_exists	 = shift;
105
106	if ( !defined $profile ) {
107		return "Missing profile name";
108	}
109
110	if ( defined $profilegroup && $profilegroup ne '.') {
111		if ( $profilegroup =~ /[^A-Za-z0-9\-+_]+/ ) {
112			return "Illegal characters in profile group name '$profilegroup'!\n";
113		}
114	}
115
116	my $profilepath = ProfilePath($profile, $profilegroup);
117
118	if ( $profile =~ /[^A-Za-z0-9\-+_]+/ ) {
119		return "Illegal characters in profile name '$profile'!\n";
120	}
121	if ( !$must_exists ) {
122		return "ok";
123	}
124
125	if ( !-d "$NfConf::PROFILESTATDIR/$profilepath") {
126		my $err_msg;
127		if ( $profilegroup eq '.' ) {
128			$err_msg = "Profile '$profile' does not exists\n";
129		} else {
130			$err_msg = "Profile '$profile' does not exists profile group '$profilegroup'\n";
131		}
132		return $err_msg;
133	}
134	if ( !-f "$NfConf::PROFILESTATDIR/$profilepath/profile.dat") {
135		return "Missing profile descriptor file of profile '$profile'\n";
136	}
137
138	return "ok"
139
140} # End of VerifyProfile
141
142sub ProfilePath {
143	my $profile  = shift;
144	my $profilegroup = shift;
145
146	if ( !defined $profilegroup || $profilegroup eq '.' ) {
147		return "$profile";
148	} else {
149		return "$profilegroup/$profile";
150	}
151
152
153} # End of ProfilePath
154
155sub ChannelDecode {
156	my $opts 	= shift;
157	my $channel	= shift;
158
159	my $c = $$opts{'channel'};
160
161	my $profile = undef;
162	my $profilegroup = undef;
163
164	if ( $c =~ m#^([^/]+)/([^/]+)$# ) {
165		$profile  = $1;
166		$$channel = $2;
167		$profilegroup = '.';
168	} elsif ( $c =~ m#^([^/]+)/([^/]+)/([^/]+)$# ) {
169		$profilegroup = $1;
170		$profile  = $2;
171		$$channel = $3;
172	} else {
173		$$channel = $c;
174	}
175
176	if ( defined $profile && ( exists $$opts{'profile'} || exists $$opts{'profilegroup'} ) ) {
177		return "Ambiguous channel definition";
178	}
179
180	if ( defined $profile ) {
181		$$opts{'profile'} 	   = $profile;
182		$$opts{'profilegroup'} = $profilegroup;
183	}
184	return 'ok';
185
186} # End of ChannelDecode
187
188sub ProfileDecode {
189	my $opts 		 = shift;
190	my $profile 	 = shift;
191	my $profilegroup = shift;
192
193	if ( !exists $$opts{'profile'} ) {
194		$$profile = undef;
195		$$profilegroup = undef;
196		return "Missing profile name";
197	}
198
199	my $_profile = $$opts{'profile'};
200
201	if ( exists $$opts{'profilegroup'} ) {
202		if ( $_profile =~ m#/# ) {
203			$$profile = undef;
204			$$profilegroup = undef;
205			return "Ambiguous profile group";
206		}
207		$$profile 	   = $_profile;
208		$$profilegroup = $$opts{'profilegroup'};
209		return "ok";
210
211	} else {
212		if ( $_profile =~ m#^(.+)/([^/]+)$# ) {
213			$$profilegroup = $1;
214			$$profile 	   = $2;
215		} else {
216			$$profilegroup = ".";
217			$$profile 	   = $_profile;
218		}
219		return "ok";
220	}
221
222} # End of ProfileDecode
223
224sub ProfileExists {
225	my $profile  = shift;
226	my $profilegroup = shift;
227
228	my $profilepath = ProfilePath($profile, $profilegroup);
229
230	return -f "$NfConf::PROFILESTATDIR/$profilepath/profile.dat" ? 1 : 0;
231
232} # End of ProfileExists
233
234sub EmptyProfile {
235
236	my %empty;
237	# Make sure all fields are set
238	foreach my $key ( @ProfileKeys ) {
239		$empty{$key} = undef;
240	}
241
242	$empty{'description'}	= [];
243	$empty{'name'}		= undef;
244	$empty{'group'}		= '.';
245	$empty{'tbegin'}	= 0;
246	$empty{'tcreate'}	= 0;
247	$empty{'tstart'}	= 0;
248	$empty{'tend'}		= 0;
249	$empty{'channel'}	= {};
250	$empty{'updated'}	= 0;
251	$empty{'expire'}	= 0;
252	$empty{'maxsize'}	= 0;
253	$empty{'size'}		= 0;
254	$empty{'type'}		= 0;
255	$empty{'locked'}	= 0;
256	$empty{'status'}	= 'empty';
257	$empty{'version'}	= 0;
258
259	return %empty;
260
261} # End of EmptyProfile
262
263sub ProfileGroups {
264
265	my @AllProfilesGroups;
266	opendir(PROFILEDIR, "$NfConf::PROFILESTATDIR" ) or
267		$Log::ERROR = "Can't open profiles directory: $!",
268		return @AllProfilesGroups;
269
270	@AllProfilesGroups = grep { $_ !~ /^\.+/ &&  -d "$NfConf::PROFILESTATDIR/$_" &&
271									-f "$NfConf::PROFILESTATDIR/$_/.group" } readdir(PROFILEDIR);
272
273	closedir PROFILEDIR;
274
275	unshift @AllProfilesGroups, '.';
276
277	$Log::ERROR = undef;
278	return sort @AllProfilesGroups;
279
280} # End of ProfileGroups
281
282sub ProfileList {
283	my $profilegroup = shift;
284
285	if ( !defined $profilegroup ) {
286		$profilegroup = '.';
287	}
288
289	my @AllProfiles;
290	opendir(PROFILEDIR, "$NfConf::PROFILESTATDIR/$profilegroup" ) or
291		$Log::ERROR = "Can't open profile group directory: $!",
292		return @AllProfiles;
293
294	@AllProfiles = grep {  -f "$NfConf::PROFILESTATDIR/$profilegroup/$_/profile.dat" && $_ !~ /^\./ && $_ ne 'live' &&
295						  !-f "$NfConf::PROFILESTATDIR/$profilegroup/$_/.DELETED" }
296						readdir(PROFILEDIR);
297
298	closedir PROFILEDIR;
299
300	# make sure live is always listed first
301	if ( $profilegroup eq '.' ) {
302		unshift @AllProfiles, 'live';
303	}
304	$Log::ERROR = undef;
305	return sort @AllProfiles;
306
307} # End of ProfileList
308
309sub DeleteDelayed {
310
311	foreach my $profilegroup ( ProfileGroups() ) {
312		my @AllProfiles;
313		opendir(PROFILEDIR, "$NfConf::PROFILESTATDIR/$profilegroup" ) or
314			$Log::ERROR = "Can't open profile group directory: $!",
315			return @AllProfiles;
316
317		@AllProfiles = grep {  -f "$NfConf::PROFILESTATDIR/$profilegroup/$_/.DELETED" }
318							readdir(PROFILEDIR);
319
320		closedir PROFILEDIR;
321
322		# delete each profile
323		foreach my $profile ( @AllProfiles ) {
324			my $profilepath = ProfilePath($profile, $profilegroup);
325			syslog('err', "Delete delayed: profile '$profile' in group '$profilegroup' ");
326
327			my @dirs;
328			push @dirs, "$NfConf::PROFILESTATDIR";
329			if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
330				push @dirs, "$NfConf::PROFILEDATADIR";
331			}
332
333			foreach my $dir ( @dirs ) {
334				if ( !rename "$dir/$profilepath", "$dir/.$profile" ) {
335					syslog('err', "Failed to rename profile '$profile' in group '$profilegroup' in order to delete: $!");
336					next;
337				}
338
339				my $command = "/bin/rm -rf $dir/.$profile &";
340				system($command);
341				if ( defined $main::child_exit && $main::child_exit != 0 ) {
342					syslog('err', "Failed to execute command: $!\n");
343					syslog('err', "system command was: '$command'\n");
344				}
345			}
346		}
347	}
348
349} # End of ProfileList
350
351
352#
353# Return an array of names of all channels in this profile
354sub ProfileChannels {
355	my $profileref = shift;
356
357	return keys %{$$profileref{'channel'}}
358
359} # End of ProfileChannels
360
361sub ReadChannelStat {
362	my $profilepath  = shift;
363	my $channel		 = shift;
364
365	my %channelstat;
366
367	if ( ! -f "$NfConf::PROFILEDATADIR/$profilepath/$channel/.nfstat" ) {
368		$Log::ERROR = "Channel info file missing for channel '$channel' in '$profilepath'",
369		return ( 'empty' => 1 );
370	}
371
372	sysopen(CHANNELSTAT, "$NfConf::PROFILEDATADIR/$profilepath/$channel/.nfstat", O_RDONLY) or
373		$Log::ERROR = "Can't open channel stat file for channel '$channel' in '$profilepath': $!",
374		return ( 'empty' => 1 );
375
376	flock CHANNELSTAT, LOCK_SH;
377	while ( <CHANNELSTAT> ) {
378		chomp;
379		next if $_ =~ /^\s*$/;	# Skip empty lines
380
381		my ($key, $value) = split /\s*=\s*/;
382		if ( !defined $key ) {
383			warn "Error reading channel stat information. Unparsable line: '$_'";
384			$Log::ERROR = "Error reading channel stat information. Unparsable line: '$_'";
385		}
386
387		$channelstat{$key} = $value;
388	}
389	foreach my $key ( @ChannelKeys ) {
390		if ( !exists $channelstat{$key} ) {
391			$Log::ERROR = "Error reading channel stat information. Missing key '$key'";
392			return ( 'empty' => 1 );
393		}
394	}
395
396	flock CHANNELSTAT, LOCK_UN;
397	close CHANNELSTAT;
398
399	return %channelstat;
400
401} # End of ReadChannelStat
402
403sub EmptyStat {
404	my $statinfo = {};
405	foreach my $db ( @NfSenRRD::RRDdb ) {
406		$$statinfo{lc $db} = -1;
407	}
408	return $statinfo;
409
410} # End of EmptyStat
411
412sub ReadStatInfo {
413	my $profileref	= shift;
414	my $channel 	= shift;
415	my $subdirs		= shift;
416	my $tstart		= shift;
417	my $tend		= shift;
418
419	my $statinfo 	 = EmptyStat();
420	my $name  		 = $$profileref{'name'};
421	my $profilegroup = $$profileref{'group'};
422
423	my $profilepath = ProfilePath($name, $profilegroup);
424
425	my $err = undef;
426	my $args;
427	if ( defined $tend ) {
428		$args = "-I -R $NfConf::PROFILEDATADIR/$profilepath/$channel/$subdirs/nfcapd.$tstart:nfcapd.$tend";
429	} else {
430		$args = "-I -r $NfConf::PROFILEDATADIR/$profilepath/$channel/$subdirs/nfcapd.$tstart";
431	}
432	local $SIG{CHLD} = 'DEFAULT';
433	if ( !open(NFDUMP, "$NfConf::PREFIX/nfdump $args 2>&1 |") ) {
434		$err = $!;
435		return ( $statinfo, 255, $err);
436	}
437	my ( $label, $value );
438	while ( my $line = <NFDUMP> ) {
439		chomp $line;
440		( $label, $value ) = split ':\s', $line;
441		next unless defined $label;
442
443		# we use everywhere 'traffic' instead of bytes
444		$label =~ s/bytes/traffic/i;
445
446		$$statinfo{lc $label} = $value;
447	}
448
449	my $nfdump_exit = 0;
450	if ( !close NFDUMP ) {
451		$nfdump_exit = $?;
452		my $exit_value  = $nfdump_exit >> 8;
453		my $signal_num  = $nfdump_exit & 127;
454		my $dumped_core = $nfdump_exit & 128;
455		if ( $exit_value == 250 ) {
456			$err = "Failed get stat info for requested time slot";
457		} else {
458			$err = "Run nfdump failed: Exit: $exit_value, Signal: $signal_num, Coredump: $dumped_core";
459		}
460	};
461
462	return ($statinfo, $nfdump_exit, $err);
463
464} # End of ReadStatInfo
465
466sub ReadRRDStatInfo {
467	my $profileref	= shift;
468	my $channel 	= shift;
469	my $tstart		= shift;
470	my $tend		= shift;
471
472
473	$tstart = NfSen::ISO2UNIX($tstart);
474	if ( ! defined $tend ) {
475		$tend = $tstart;
476	} else {
477		$tend = NfSen::ISO2UNIX($tend);
478	}
479
480	my $statinfo 	 = EmptyStat();
481	my $name  		 = $$profileref{'name'};
482	my $profilegroup = $$profileref{'group'};
483
484	my $profilepath = ProfilePath($name, $profilegroup);
485
486	my $RRDdb = "$NfConf::PROFILESTATDIR/$profilepath/$channel.rrd";
487	my ($start,$step,$names,$data) = RRDs::fetch $RRDdb, "-s", $tstart-300, "-e", $tend-300, "MAX";
488	my $err=RRDs::error;
489	if ( defined $err ) {
490		return ($statinfo, $err);
491	}
492
493	foreach my $line (@$data) {
494		my $i = 0;
495		foreach my $val (@$line) {
496			if ( defined $val ) {
497				$$statinfo{$$names[$i++]} += int($NfConf::CYCLETIME * $val);
498			}
499		}
500	}
501
502	return ($statinfo, $err);
503
504} # End of ReadRRDStatInfo
505
506sub GetPeakValues {
507	my $profileref	= shift;
508	my $whichtype	= shift;
509	my $channellist	= shift;
510	my $tinit		= shift;	# UNIX time format
511
512	my $statinfo 	 = EmptyStat();
513	my $name  		 = $$profileref{'name'};
514	my $profilegroup = $$profileref{'group'};
515
516	my $profilepath = ProfilePath($name, $profilegroup);
517
518	my $tmin = $tinit - 3600;
519	if ( $tmin < $$profileref{'tstart'} ) {
520		$tmin = $$profileref{'tstart'};
521	}
522	if ( $tmin > $tinit ) {
523		return ( $tinit, "time outside profile time" );
524	}
525	my $tmax = $tmin + 2 * 3600;
526	if ( $tmax > $$profileref{'tend'} ) {
527		$tmax = $$profileref{'tend'};
528	}
529	if ( $tmax < $tinit ) {
530		return ( $tinit, "time outside profile time" );
531	}
532	if ( $tmax < $tmin ) {
533		return ( $tinit, "Can't select a time span");
534	}
535
536	my $max_sum_pos = 0;
537	my $max_sum_neg = 0;
538	my $sum_pos;
539	my $sum_neg;
540	my %sum_ref;
541	my $tpos = $tinit;
542	my $tneg = $tinit;
543	my @AllChannels = split /\!/, $channellist;
544	foreach my $ch ( @AllChannels ) {
545		if ( $$profileref{'channel'}{$ch}{'sign'} eq '+' ) {
546			$sum_ref{$ch} = \$sum_pos;
547		} else {
548			$sum_ref{$ch} = \$sum_neg;
549		}
550	}
551
552	my $err = undef;
553	for ( my $t=$tmin; $t<=$tmax; $t += $NfConf::CYCLETIME ) {
554		$sum_pos = 0;
555		$sum_neg = 0;
556		my $subdirs = NfSen::SubdirHierarchy($t);
557
558		my $tiso = NfSen::UNIX2ISO($t);
559		foreach my $ch ( @AllChannels ) {
560			my $args = "-I -r $NfConf::PROFILEDATADIR/$profilepath/$ch/$subdirs/nfcapd.$tiso";
561
562			local $SIG{CHLD} = 'DEFAULT';
563			if ( !open(NFDUMP, "$NfConf::PREFIX/nfdump $args 2>&1 |") ) {
564				$err = $!;
565				return ( $tinit, $err);
566			}
567			my ( $label, $value );
568			while ( my $line = <NFDUMP> ) {
569				chomp $line;
570				( $label, $value ) = split ':\s', $line;
571				next unless defined $label;
572
573				# we use everywhere 'traffic' instead of bytes
574				$label =~ s/bytes/traffic/i;
575				$label = lc $label;
576				if ( $label eq $whichtype ) {
577					${$sum_ref{$ch}} += $value;
578				}
579			}
580
581			my $nfdump_exit = 0;
582			if ( !close NFDUMP ) {
583				$nfdump_exit = $?;
584				my $exit_value  = $nfdump_exit >> 8;
585				my $signal_num  = $nfdump_exit & 127;
586				my $dumped_core = $nfdump_exit & 128;
587				if ( $exit_value == 250 ) {
588					$err = "Failed get stat info for requested time slot";
589				} else {
590					$err = "Run nfdump failed: Exit: $exit_value, Signal: $signal_num, Coredump: $dumped_core";
591				}
592				return ( $tinit, $err);
593			}
594		}
595$tiso = NfSen::UNIX2ISO($t);
596print ".t= $t $tiso, Sum+: $sum_pos, Sum-: $sum_neg\n";
597		if ( $sum_pos > $max_sum_pos ) {
598			$max_sum_pos = $sum_pos;
599			$tpos = $t;
600		}
601		if ( $sum_neg > $max_sum_neg ) {
602			$max_sum_neg = $sum_neg;
603			$tneg = $t;
604		}
605	}
606my $iso_tpos = NfSen::UNIX2ISO($tpos);
607my $iso_tneg = NfSen::UNIX2ISO($tneg);
608print ".Max+: $max_sum_pos at $iso_tpos, Max-: $max_sum_neg at $iso_tneg\n";
609	if ( $max_sum_pos > $max_sum_neg ) {
610		return ($tpos, undef );
611	} else {
612		return ($tneg, undef );
613	}
614
615} # End of GetPeakValues
616
617
618#
619# Returns the profile info hash, if successfull
620# else returns EmptyProfile and sets Log::ERROR
621sub ReadProfile {
622	my $name  		 = shift;
623	my $profilegroup = shift;
624
625	# compat option, if an old plugin does not specify a profilegroup
626	my $legacy = 0;
627	if ( !defined $profilegroup ) {
628		$profilegroup = '.';
629		$legacy = 1;
630	}
631
632	my %profileinfo = EmptyProfile();
633	my $description = [];
634
635	$Log::ERROR	 = undef;
636	my %empty	   = EmptyProfile();
637	$empty{'name'}  = $name;
638	$empty{'group'} = $profilegroup;
639
640	my $profilepath = ProfilePath($name, $profilegroup);
641
642	sysopen(ProFILE, "$NfConf::PROFILESTATDIR/$profilepath/profile.dat", O_RDONLY) or
643		$Log::ERROR = "Can't open profile data file for profile: '$name' in group '$profilegroup': $!",
644		return %empty;
645
646	flock ProFILE, LOCK_SH;
647
648	while ( <ProFILE> ) {
649		chomp;
650		next if $_ =~ /^\s*$/;	# Skip empty lines
651		if ( $_ =~ /^\s*#\s*(.*)$/ ) {
652			push @$description, "$1";
653			next;
654		}
655		my ($key, $value) = split /\s*=\s*/;
656		if ( !defined $key ) {
657			warn "Error reading profile information. Unparsable line: '$_'";
658			$Log::ERROR = "Error reading profile information. Unparsable line: '$_'";
659		}
660		if ( !defined $value ) {
661			warn "Error reading profile information. Empty value for line: '$_'";
662		}
663		if ( exists $empty{"$key"} ) {
664			if ( $key eq "channel" ) {
665				my @_tmp = split /:/, $value;
666				my $channelname = shift @_tmp;
667				foreach my $prop ( @ChannelProperties ) {
668					my $val = shift @_tmp;
669					$profileinfo{'channel'}{$channelname}{$prop} = $val;
670				}
671			} else {
672				$profileinfo{$key} = $value;
673			}
674		# this elsif needs the installer only
675		} elsif ( exists $LegacyProfileKeys{"$key"} ) {
676				$profileinfo{'legacy'}{$key} = $value;
677		} else {
678			warn "Error reading profile information. Unknown key: '$key'";
679			$Log::ERROR =  "Error reading profile information. Unknown key: '$key'";
680		}
681	}
682	$profileinfo{'description'} = $description;
683	flock ProFILE, LOCK_UN;
684	close ProFILE;
685
686	# Make sure all fields are set
687	foreach my $key ( @ProfileKeys ) {
688		next if defined $profileinfo{$key};
689		next if $key eq 'version';
690		$profileinfo{$key} = $empty{$key};
691		warn "Empty key '$key' in profile '$name' group '$profilegroup' - preset default value: $empty{$key}";
692	}
693
694	if ( $profileinfo{'name'} ne $name ) {
695		$Log::ERROR = "Corrupt stat file. Needs to be rebuilded";
696		return %empty;
697	}
698
699	if ( defined $Log::ERROR ) {
700		return %empty;
701	}
702
703	if ( $legacy ) {
704		$profileinfo{'sourcelist'} = join ':', keys %{$profileinfo{'channel'}};
705	}
706	return %profileinfo;
707
708} # End of ReadProfile
709
710#
711# Returns the profile info hash, if successfull
712# else if already locked, return EmptyProfile with 'locked' = 1
713# else returns EmptyProfile and sets Log::ERROR
714sub LockProfile {
715	my $name = shift;
716	my $profilegroup = shift;
717
718	my %profileinfo = EmptyProfile();
719	my $description = [];
720
721	$Log::ERROR	= undef;
722	my %empty	  = EmptyProfile();
723	$empty{'name'} = $name;
724	$empty{'group'} = $profilegroup;
725
726	my $profilepath = ProfilePath($name, $profilegroup);
727
728	sysopen(ProFILE, "$NfConf::PROFILESTATDIR/$profilepath/profile.dat", O_RDWR|O_BINARY) or
729		$Log::ERROR = "Can't open profile data file for profile: '$name' in group '$profilegroup': $!",
730		return %empty;
731
732	flock ProFILE, LOCK_EX;
733
734	while ( <ProFILE> ) {
735		chomp;
736		next if $_ =~ /^\s*$/;	# Skip empty lines
737		if ( $_ =~ /^\s*#\s*(.*)$/ ) {
738			push @$description, "$1";
739			next;
740		}
741		my ($key, $value) = split /\s*=\s*/;
742		if ( !defined $key ) {
743			warn "Error reading profile information. Unparsable line: '$_'";
744		}
745		if ( exists $empty{"$key"} ) {
746			if ( $key eq "channel" ) {
747				my @_tmp = split /:/, $value;
748				my $channelname = shift @_tmp;
749				foreach my $prop ( @ChannelProperties ) {
750					my $val = shift @_tmp;
751					$profileinfo{'channel'}{$channelname}{$prop} = $val;
752				}
753			} else {
754				$profileinfo{$key} = $value;
755			}
756		} else {
757			warn "Error reading profile information. Unknown key: '$key'";
758		}
759	}
760	$profileinfo{'description'} = $description;
761
762	# Make sure all fields are set
763	foreach my $key ( @ProfileKeys ) {
764		next if defined $profileinfo{$key};
765		next if $key eq 'version';
766		$profileinfo{$key} = $empty{$key};
767		warn "Empty key '$key' in profile '$name' group '$profilegroup' - preset default value: $empty{$key}";
768	}
769
770	if ( $profileinfo{'name'} ne $name ) {
771		flock ProFILE, LOCK_UN;
772		close ProFILE;
773		$Log::ERROR = "Corrupt stat file. Needs to be rebuilded";
774		return %empty;
775	}
776
777	# Is it already locked?
778	if ( $profileinfo{'locked'} ) {
779		flock ProFILE, LOCK_UN;
780		close ProFILE;
781		$empty{'locked'} = 1;
782		return %empty;
783	}
784
785	$profileinfo{'locked'} = 1;
786	seek ProFILE, 0,0;
787
788	foreach my $line ( @{$profileinfo{'description'}} ) {
789		print ProFILE "# $line\n";
790	}
791
792	foreach my $key ( @ProfileKeys ) {
793		next if $key eq 'description';
794		next if $key eq 'channel';
795		if ( !defined $profileinfo{$key} ) {
796			print ProFILE "$key = \n";
797		} else {
798			print ProFILE "$key = $profileinfo{$key}\n";
799		}
800	}
801	foreach my $channelname ( keys %{$profileinfo{'channel'}} ) {
802		my @_properties;
803		push @_properties, $channelname;
804		foreach my $prop ( @ChannelProperties ) {
805			push @_properties, $profileinfo{'channel'}{$channelname}{$prop};
806		}
807		print ProFILE "channel = ", join ':', @_properties, "\n";
808	}
809	my $fpos = tell ProFILE;
810	truncate ProFILE, $fpos;
811
812	flock ProFILE, LOCK_UN;
813	if ( !close ProFILE ) {
814		$Log::ERROR = "Failed to close profileinfo of profile '$name' in group '$profilegroup': $!.",
815		return %empty;
816	}
817
818	return %profileinfo;
819
820} # End of LockProfile
821
822sub WriteProfile {
823	my $profileref = shift;
824
825	my $name  		 = $$profileref{'name'};
826	my $profilegroup = $$profileref{'group'};
827
828	if ( length $name == 0 ) {
829		$Log::ERROR = "While writing profile stat file. Corrupt data ref",
830		return undef;
831	}
832
833	my $profilepath = ProfilePath($name, $profilegroup);
834
835	$Log::ERROR = undef;
836	sysopen(ProFILE, "$NfConf::PROFILESTATDIR/$profilepath/profile.dat", O_RDWR|O_CREAT) or
837		$Log::ERROR = "Can't open profile data file for profile '$name' in group '$profilegroup': $!\n",
838		return undef;
839
840	flock ProFILE, LOCK_EX;
841	seek ProFILE, 0,0;
842
843	foreach my $line ( @{$$profileref{'description'}} ) {
844		print ProFILE "# $line\n";
845	}
846	foreach my $key ( @ProfileKeys ) {
847		next if $key eq 'description';
848		next if $key eq 'channel';
849		next if $key eq 'legacy';
850		if ( !defined $$profileref{$key} ) {
851			print ProFILE "$key = \n";
852		} else {
853			print ProFILE "$key = $$profileref{$key}\n";
854		}
855	}
856	foreach my $channelname ( keys %{$$profileref{'channel'}} ) {
857		my @_properties;
858		push @_properties, $channelname;
859		foreach my $prop ( @ChannelProperties ) {
860			push @_properties, $$profileref{'channel'}{$channelname}{$prop};
861		}
862		print ProFILE "channel = ", join(':', @_properties), "\n";
863	}
864
865	my $fpos = tell ProFILE;
866	truncate ProFILE, $fpos;
867
868	flock ProFILE, LOCK_UN;
869	if ( !close ProFILE ) {
870		$Log::ERROR = "Failed to close profileinfo of profile '$name' in group '$profilegroup': $!.",
871		return undef;
872	}
873
874	return 1;
875
876} # End of WriteProfile
877
878
879sub ProfileHistory {
880	my $profileref = shift;
881
882	my $name   = $$profileref{'name'};
883	my $group  = $$profileref{'group'};
884	my $tstart = $$profileref{'tstart'};
885	my $tend   = $$profileref{'tend'};
886	my $continous_profile = ($$profileref{'type'} & 3) == 2;
887
888	my %liveprofile = ReadProfile('live', '.');
889	if ( $tstart < $liveprofile{'tstart'} ) {
890		syslog('warning', "live profile expired in requested start time.");
891		syslog('warning', "Adjust start time from %s to %s.",
892			scalar localtime($tstart), scalar localtime($liveprofile{'tstart'}));
893		$tstart = $liveprofile{'tstart'};
894		if ( $tend < $tstart ) {
895			syslog('err', "Error profiling history. tend now < tstart. Abort profiling");
896			return;
897		}
898	}
899
900	# we have to process that many time slices:
901	my $numslices = ((( $tend - $tstart ) / $NfConf::CYCLETIME ) + 1 );
902
903	$$profileref{'status'} = 'built 0';
904	$$profileref{'locked'} = 1;
905	if ( !WriteProfile($profileref) ) {
906		syslog('err', "Error writing profile '$name': $Log::ERROR");
907		return;
908	}
909
910	my $counter  = 0;
911	my $progress = 0;
912	my $percent  = $numslices / 100;
913
914	my $channellist = join ':', keys %{$liveprofile{'channel'}};
915
916	my $profilepath = ProfilePath($name, $group);
917	my $subdirlayout = $NfConf::SUBDIRLAYOUT ? "-S $NfConf::SUBDIRLAYOUT" : "";
918	my $arg = "-I -p $NfConf::PROFILEDATADIR -P $NfConf::PROFILESTATDIR $subdirlayout ";
919	$arg   .= "-z " if $NfConf::ZIPprofiles;
920
921	# create argument list specific for each channel
922	# at the moment this contains of all channels in a continues profile
923	my @ProfileOptList;
924	foreach my $channel ( keys %{$$profileref{'channel'}} ) {
925		push @ProfileOptList, "$group#$name#$$profileref{'type'}#$channel#$$profileref{'channel'}{$channel}{'sourcelist'}";
926	}
927
928	syslog('info', "ProfileHistory for profile '$name': $numslices time slices."),
929	# Update the profile status every .1% of all slices, at least every 2 slices
930	# more often does not makes senes
931	my $modulo	 = int ($percent / 10) < 2 ? 2 : int ($percent / 10);
932	my $profile_size = 0;
933	my $t = $tstart;
934	while ( $t <= $tend ) {
935		if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
936			last;
937		}
938
939		my $iso = NfSen::UNIX2ISO($t);
940		my $subdirs = NfSen::SubdirHierarchy($t);
941		my %statinfo	= ();
942		my $dsvector = join(':', @NfSenRRD::RRD_DS);
943
944		my $flist = "-M $NfConf::PROFILEDATADIR/live/$channellist -r nfcapd.$iso";
945
946		$counter++;
947		my $completed = sprintf "%.1f%%", $counter / $percent;
948		if ( $completed > 100 ) {
949			$completed = 100;
950		}
951		if ( ($counter % $modulo ) == 0 ) {
952			$$profileref{'status'} 	= "built $completed\n";
953			$$profileref{'updated'} = $t;
954			$$profileref{'size'} 	= $profile_size;
955			WriteProfile($profileref);
956			syslog('info', "Build Profile '$name':	 Completed: $completed\%"),
957		}
958
959		# run nfprofile but only for new profile $name
960		#syslog('debug', "Run profiler: '$arg' '$flist'");
961		if ( open NFPROFILE, "| $NfConf::PREFIX/nfprofile -t $t $arg $flist" ) {
962			local $SIG{PIPE} = sub { syslog('err', "Pipe broke for nfprofile"); };
963			foreach my $profileopts ( @ProfileOptList ) {
964				print NFPROFILE "$profileopts\n";
965				#syslog('debug', "profile opts: $profileopts");
966			}
967			close NFPROFILE;	# SIGCHLD sets $child_exit
968		}
969
970		if ( $main::child_exit != 0 ) {
971			syslog('err', "nfprofile failed: $!\n");
972			syslog('debug', "System was: $NfConf::PREFIX/nfprofile $arg $flist");
973			next;
974		}
975
976		if ( ($$profileref{'type'} & 4 ) == 0 ) { # no shadow profile
977			foreach my $channel ( keys %{$$profileref{'channel'}} ) {
978				my $outfile = "$NfConf::PROFILEDATADIR/$profilepath/$channel/$subdirs/nfcapd.$iso";
979
980				# array element 7 contains the size in bytes
981				$profile_size +=(stat($outfile))[7];
982			}
983		}
984		$t += $NfConf::CYCLETIME;
985		if ( $continous_profile && $t == $tend ) {
986			my %liveprofile = ReadProfile('live', '.');
987			$tend = $liveprofile{'tend'};
988			$$profileref{'tend'} = $tend;
989		}
990		if ( $$profileref{'maxsize'} && ( $profile_size > $$profileref{'maxsize'} )) {
991			syslog('err', "Reached profile max size while building. Cancel building");
992			open CANCELFLAG, ">$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
993			close CANCELFLAG;
994		}
995		if ( $$profileref{'expire'} && ( $t - $tstart ) > $$profileref{'expire'}*3600 ) {
996			syslog('err', "Reached profile max lifetime while building. Cancel building");
997			open CANCELFLAG, ">$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
998			close CANCELFLAG;
999		}
1000	}
1001	if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
1002		syslog('info', "ProfileHistory: canceled."),
1003		# Canceled profiles become a history profile
1004		$$profileref{'tend'}    = $t;
1005		if ( ($$profileref{'type'} & 4) > 0 ) { # is shadow
1006			$$profileref{'type'}  = 1;
1007			$$profileref{'type'} += 4;
1008		} else {
1009			$$profileref{'type'} = 1;
1010		}
1011		unlink "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
1012	} else {
1013		syslog('info', "ProfileHistory: Done."),
1014	}
1015
1016	$$profileref{'size'} 	= $profile_size;
1017	$$profileref{'updated'} = $tend;
1018
1019	return $profile_size;
1020
1021} # end of ProfileHistory
1022
1023sub AddChannel {
1024	my $profileref 	= shift;
1025	my $channel 	= shift;
1026	my $sign		= shift;
1027	my $order		= shift;
1028	my $colour		= shift;
1029	my $sourcelist 	= shift;
1030	my $filter		= shift;	# array ref to lines for filter
1031
1032	my $profile 	 = $$profileref{'name'};
1033	my $profilegroup = $$profileref{'group'};
1034	my $profilepath  = ProfilePath($profile, $profilegroup);
1035	my $tstart		 = $$profileref{'tstart'};
1036
1037	# name is already validated from calling routine
1038
1039	# setup channel directory
1040	my $dir = "$NfConf::PROFILEDATADIR/$profilepath/$channel";
1041	mkdir "$dir" or
1042		return "Can't create channel directory: '$dir' $!\n";
1043
1044	if ( !chmod 0775, $dir ) {
1045		rmdir "$dir";
1046		return "Can't chown '$dir': $! ";
1047	}
1048	if ( !chown $NfConf::UID, $NfConf::GID, $dir ) {
1049		rmdir "$dir";
1050		return "Can't chown '$dir': $! ";
1051	}
1052
1053	if ( $profile ne 'live' || $profilegroup ne '.') {
1054		# setup channel filter
1055		my $filterfile = "$NfConf::PROFILESTATDIR/$profilepath/$channel-filter.txt";
1056
1057		open(FILTER, ">$filterfile" ) or
1058			rmdir "$dir",
1059			return "Can't open filter file '$filter': $!";
1060
1061		print FILTER map "$_\n", @$filter;
1062		close FILTER;
1063	}
1064
1065	# Add channel to the top off the graph - search max_order
1066	my $max_order = 0;
1067	foreach my $ch ( keys %{$$profileref{'channel'}} ) {
1068		next unless $$profileref{'channel'}{$ch}{'sign'} eq $sign;
1069		$max_order++;
1070	}
1071	$max_order++;
1072	if ( ($order == 0) || ($order > $max_order) ) {
1073		$order = $max_order;
1074	}
1075
1076	# reorder channels
1077	foreach my $ch ( keys %{$$profileref{'channel'}} ) {
1078		next unless $$profileref{'channel'}{$ch}{'sign'} eq $sign;
1079		if ( $$profileref{'channel'}{$ch}{'order'} >= $order ) {
1080			$$profileref{'channel'}{$ch}{'order'}++;
1081		}
1082	}
1083
1084	$$profileref{'channel'}{$channel}{'sign'}   	= $sign;
1085	$$profileref{'channel'}{$channel}{'colour'} 	= $colour;
1086	$$profileref{'channel'}{$channel}{'order'}  	= $order;
1087	$$profileref{'channel'}{$channel}{'sourcelist'} = $sourcelist;
1088
1089	# $tstart is the first value we need in the RRD DB, therefore specify
1090	# $tstart - $NfConf::CYCLETIME ( 1 slot )
1091	NfSenRRD::SetupRRD("$NfConf::PROFILESTATDIR/$profilepath", $channel, $tstart - $NfConf::CYCLETIME, 1);
1092	if ( defined $Log::ERROR ) {
1093		rmdir "$NfConf::PROFILEDATADIR/$profilepath/$channel",
1094		unlink $filter;
1095		return "Creating RRD failed for channel '$channel': $Log::ERROR\n";
1096	}
1097
1098	chown $NfConf::UID, $NfConf::GID, "$NfConf::PROFILESTATDIR/$profilepath/$channel.rrd";
1099
1100	return "ok";
1101
1102} # End of AddChannel
1103
1104sub DeleteChannel {
1105	my $profileref = shift;
1106	my $channel = shift;
1107
1108	my $profile 	 = $$profileref{'name'};
1109	my $profilegroup = $$profileref{'group'};
1110	my $profilepath  = ProfilePath($profile, $profilegroup);
1111
1112	my $channelref   = $$profileref{'channel'}{$channel};
1113
1114	# remove logically the channel from the profile
1115	delete $$profileref{'channel'}{$channel};
1116
1117	if ( -d "$NfConf::PROFILEDATADIR/$profilepath/$channel" ) {
1118		# now remove physically the channel
1119		if ( !rename "$NfConf::PROFILEDATADIR/$profilepath/$channel", "$NfConf::PROFILEDATADIR/$profilepath/.$channel") {
1120			# restore channel to profile
1121			$$profileref{'channel'}{$channel} = $channelref;
1122			return "Failed to rename channel '$channel' in order to delete: $!\n";
1123		}
1124		system "/bin/rm -rf $NfConf::PROFILEDATADIR/$profilepath/.$channel";
1125	}
1126
1127	# Delete RRD DB
1128	NfSenRRD::DeleteRRD("$NfConf::PROFILESTATDIR/$profilepath", $channel);
1129
1130	# remove filter file
1131	unlink "$NfConf::PROFILESTATDIR/$profilepath/$channel-filter.txt";
1132
1133	# Reorder the remaining channels
1134	my $sign 	= $$channelref{'sign'};
1135	my $order 	= $$channelref{'order'};
1136	foreach my $ch ( keys %{$$profileref{'channel'}} ) {
1137		next unless $$profileref{'channel'}{$ch}{'sign'} eq $sign;
1138		if ( $$profileref{'channel'}{$ch}{'order'} > $order ) {
1139			$$profileref{'channel'}{$ch}{'order'}--;
1140		}
1141	}
1142
1143
1144	my $is_shadow = ($$profileref{'type'} & 4) > 0 ;
1145	if ( !$is_shadow ) {
1146		# re-calculate size of profile
1147		my $profilesize = 0;
1148		my $tfirst 		= 0;
1149		foreach my $ch ( keys %{$$profileref{'channel'}} ) {
1150			my %channelinfo = ReadChannelStat($profilepath, $ch);
1151			if ( exists $channelinfo{'empty'} ) {
1152				next;
1153			}
1154			if ( $tfirst ) {
1155				if ( $channelinfo{'first'} < $tfirst ) {
1156					$tfirst = $channelinfo{'first'};
1157				}
1158			} else {
1159				$tfirst = $channelinfo{'first'};
1160			}
1161			$profilesize += $channelinfo{'size'};
1162		}
1163		$$profileref{'size'}   = $profilesize;
1164		$$profileref{'tstart'} = $tfirst if $tfirst;
1165	}
1166
1167	return "ok";
1168
1169} # End of DeleteChannel
1170
1171sub GetProfilegroups {
1172	my $socket	= shift;
1173	my $opts 	= shift;
1174
1175	foreach my $profilegroup ( ProfileGroups() ) {
1176		print $socket "_profilegroups=$profilegroup\n";
1177	}
1178
1179	print $socket $EODATA;
1180	if ( defined $Log::ERROR ) {
1181		print $socket "ERR $Log::ERROR\n";
1182	} else {
1183		print $socket "OK Profile Listing\n";
1184	}
1185
1186
1187} # End of GetProfilegroups
1188
1189sub DoRebuild {
1190	my $socket		 = shift;
1191	my $profileinfo  = shift;
1192	my $profile 	 = shift;
1193	my $profilegroup = shift;
1194	my $profilepath  = shift;
1195	my $installer	 = shift;
1196	my $DoGraphs	 = shift;
1197
1198	# rebuilding live is a bit trickier to keep everything consistent
1199	# make sure, the periodic update process is done - then block cycles
1200	syslog('info', "Rebuilding profile '$profile', group '$profilegroup'");
1201	if ( $profile eq 'live' && $profilegroup eq '.' ) {
1202		syslog('debug', "Get lock");
1203		Nfsync::semwait();
1204	}
1205
1206	# rebuild each channel, using nfexpire
1207	my $profilesize = 0;
1208	my $tstart		= 0;
1209	my $tend		= 0;
1210	my $args = "-Y -p -r $NfConf::PROFILEDATADIR/$profilepath";
1211	if ( open NFEXPIRE, "$NfConf::PREFIX/nfexpire $args 2>&1 |" ) {
1212		local $SIG{PIPE} = sub { syslog('err', "Pipe broke for nfexpire"); };
1213		while ( <NFEXPIRE> ) {
1214			chomp;
1215			# Option -Y returns an extra status line: 'Stat|<profilesize>|<time>'
1216			if ( /^Stat\|(\d+)\|(\d+)\|(\d+)/ ) {
1217				$profilesize = $1;
1218				$tstart		 = $2;
1219				$tend		 = $3;
1220			} else {
1221				s/%/%%/;
1222				syslog('debug', "nfexpire: $_");
1223			}
1224			syslog('debug', "nfexpire: $_") unless $installer;
1225		}
1226		close NFEXPIRE;	# SIGCHLD sets $child_exit
1227	}
1228
1229	if ( $main::child_exit != 0 ) {
1230		if ( $installer ) {
1231			return "nfexpire failed: $!";
1232		} else {
1233			syslog('err', "nfexpire failed: $!\n");
1234			syslog('debug', "System was: $NfConf::PREFIX/nfexpire $args");
1235		}
1236	}
1237
1238	$$profileinfo{'size'}	= $profilesize;
1239	$$profileinfo{'tstart'} = $tstart;
1240	$$profileinfo{'tend'} 	= $tend;
1241	$$profileinfo{'updated'}= $tend;
1242
1243	if ( !$$profileinfo{'name'} ) {
1244		$$profileinfo{'description'} = \@ProfileTag;
1245		$$profileinfo{'name'}		 = $profile;
1246		$$profileinfo{'group'}		 = $profilegroup;
1247
1248	} else {
1249		if ( $$profileinfo{'name'} ne $profile ) {
1250			syslog("warning", "Profile name missmatch - Set '$$profileinfo{'name'}' to '$profile'");
1251			$$profileinfo{'name'} 	= $profile;
1252			$$profileinfo{'group'}	= $profilegroup;
1253		}
1254
1255	}
1256
1257	$$profileinfo{'maxsize'} 	= defined $$profileinfo{'maxsize'} ? $$profileinfo{'maxsize'} + 0 : 0;
1258	$$profileinfo{'expire'} 	= defined $$profileinfo{'expire'} ? $$profileinfo{'expire'} + 0 : 0;
1259
1260	# check order of profiles;
1261	my @CHAN = sort {
1262   		my $num1 = "$$profileinfo{'channel'}{$a}{'sign'}$$profileinfo{'channel'}{$a}{'order'}";
1263   		my $num2 = "$$profileinfo{'channel'}{$b}{'sign'}$$profileinfo{'channel'}{$b}{'order'}";
1264   		$num2 <=> $num1;
1265	} keys %{$$profileinfo{'channel'}};
1266
1267	my @CHANpos;
1268	my @CHANneg;
1269
1270	foreach my $channel ( @CHAN ) {
1271   		if ( $$profileinfo{'channel'}{$channel}{'sign'} eq "-" ) {
1272   			push @CHANneg, $channel;
1273   		} else {
1274   			unshift @CHANpos, $channel;
1275   		}
1276	}
1277
1278	my $order = 1;
1279	foreach my $channel ( @CHANpos ) {
1280		if ( $$profileinfo{'channel'}{$channel}{'order'} != $order ) {
1281			syslog('info', "Fixing channel order for channel '$channel'. Was $$profileinfo{'channel'}{$channel}{'order'}, set to $order") unless $installer;
1282			$$profileinfo{'channel'}{$channel}{'order'} = $order;
1283		}
1284		$order++;
1285	}
1286
1287	$order = 1;
1288	foreach my $channel ( @CHANneg ) {
1289		if ( $$profileinfo{'channel'}{$channel}{'order'} != $order ) {
1290			syslog('info', "Fixing channel order for channel '$channel'. Was $$profileinfo{'channel'}{$channel}{'order'}, set to $order") unless $installer;
1291			$$profileinfo{'channel'}{$channel}{'order'} = $order;
1292		}
1293		$order++;
1294	}
1295
1296	# in case of rebuilding the graphs
1297	if ( $DoGraphs ) {
1298		syslog('info', "Rebuilding graphs");
1299		$$profileinfo{'status'} = 'rebuilding';
1300		$$profileinfo{'locked'} = 1;
1301		if ( !WriteProfile($profileinfo) ) {
1302			syslog('err', "Error writing profile '$profile': $Log::ERROR");
1303			return "Failed writing profile";
1304		}
1305
1306		my $continous_profile = ($$profileinfo{'type'} & 3) == 2;
1307
1308		syslog('info', "Setting up RRD DBs");
1309		foreach my $channel ( @CHAN ) {
1310			NfSenRRD::SetupRRD("$NfConf::PROFILESTATDIR/$profilepath", $channel, $tstart - $NfConf::CYCLETIME, 1);
1311		}
1312
1313		my $numslices = ((( $tend - $tstart ) / $NfConf::CYCLETIME ) + 1 );
1314		my $percent  = $numslices / 100;
1315		my $counter  = 0;
1316		my $progress = 0;
1317		my $modulo	 = int ($percent * 10) < 2 ? 2 : int ($percent * 10);
1318
1319		my $t;
1320		for ( $t = $tstart; $t <= $tend; $t += $NfConf::CYCLETIME ) {
1321			my $t_iso 	= NfSen::UNIX2ISO($t);
1322			foreach my $channel ( @CHAN ) {
1323				my $subdirs = NfSen::SubdirHierarchy($t);
1324				my ($statinfo, $exit_code, $err ) = ReadStatInfo($profileinfo, $channel, $subdirs, $t_iso, undef);
1325				my @_values = ();
1326				foreach my $ds ( @NfSenRRD::RRD_DS ) {
1327           			if ( !defined $$statinfo{$ds} || $$statinfo{$ds} == - 1 ) {
1328              			push @_values, 0;
1329          			} else {
1330               			push @_values, $$statinfo{$ds};
1331           			}
1332				}
1333				$err = NfSenRRD::UpdateDB("$NfConf::PROFILESTATDIR/$profilepath", $channel, $t,
1334						join(':',@NfSenRRD::RRD_DS) , join(':', @_values));
1335				if ( $Log::ERROR ) {
1336					syslog('err', "ERROR Update RRD time: '$t_iso', db: '$channel', profile: '$profile' group '$profilegroup' : $Log::ERROR");
1337				}
1338			}
1339
1340			$counter++;
1341			my $completed = sprintf "%.1f%%", $counter / $percent;
1342			if ( $completed > 100 ) {
1343				$completed = 100;
1344			}
1345			if ( ($counter % $modulo ) == 0 ) {
1346				print $socket ".info Rebuilding Profile '$profile': Completed: $completed\%\n";
1347				syslog('info', "Rebuilding Profile '$profile': Completed: $completed\%");
1348			}
1349
1350
1351		}
1352
1353		# history data is updated and we are done with history profiles
1354		# A continous profile except 'live' must be updated up to the current slot
1355		# rebuilding the profile could have missed new slots -> profile missing slots
1356		if ( $continous_profile && ($profile ne 'live' || $profilegroup ne '.') ) {
1357			# get current current time slot
1358			my %liveprofile = ReadProfile('live', '.');
1359			$tend = $liveprofile{'tend'};
1360
1361			# this can happen, if a history profile is switch back to continous
1362			# profile and wants to get updated now. Be sure we have all data
1363			if ( $t < $liveprofile{'tstart'} ) {
1364				$t = $liveprofile{'tstart'};
1365			}
1366			my $profile_size = $$profileinfo{'size'};
1367
1368			# prepare profiler args
1369			my @ProfileOptList;
1370			foreach my $channel ( keys %{$$profileinfo{'channel'}} ) {
1371				push @ProfileOptList, "$profilegroup#$profile#$$profileinfo{'type'}#$channel#$$profileinfo{'channel'}{$channel}{'sourcelist'}";
1372			}
1373			my $channellist = join ':', keys %{$liveprofile{'channel'}};
1374			my $subdirlayout = $NfConf::SUBDIRLAYOUT ? "-S $NfConf::SUBDIRLAYOUT" : "";
1375			my $arg = "-I -p $NfConf::PROFILEDATADIR -P $NfConf::PROFILESTATDIR $subdirlayout ";
1376			$arg   .= "-z " if $NfConf::ZIPprofiles;
1377
1378			# profile missing slots
1379			if ( $t <= $tend ) {
1380				syslog('info', "Profiling missing slots");
1381			}
1382			while ( $t <= $tend ) {
1383				if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
1384					$$profileinfo{'status'} = 'canceled';
1385					syslog('info', "Rebuild canceled.");
1386					last;
1387				}
1388
1389				my $iso = NfSen::UNIX2ISO($t);
1390				my $subdirs = NfSen::SubdirHierarchy($t);
1391				my %statinfo	= ();
1392				my $dsvector = join(':', @NfSenRRD::RRD_DS);
1393
1394				my $flist = "-M $NfConf::PROFILEDATADIR/live/$channellist -r nfcapd.$iso";
1395
1396				# run nfprofile for remaining slots
1397				#syslog('debug', "Run profiler: '$arg' '$flist'");
1398				if ( open NFPROFILE, "| $NfConf::PREFIX/nfprofile -t $t $arg $flist" ) {
1399					local $SIG{PIPE} = sub { syslog('err', "Pipe broke for nfprofile"); };
1400					foreach my $profileopts ( @ProfileOptList ) {
1401						print NFPROFILE "$profileopts\n";
1402						#syslog('debug', "profile opts: $profileopts");
1403					}
1404					close NFPROFILE;	# SIGCHLD sets $child_exit
1405				}
1406
1407				if ( $main::child_exit != 0 ) {
1408					syslog('err', "nfprofile failed: $!\n");
1409					syslog('debug', "System was: $NfConf::PREFIX/nfprofile $arg $flist");
1410					next;
1411				}
1412
1413				foreach my $channel ( keys %{$$profileinfo{'channel'}} ) {
1414					my $outfile = "$NfConf::PROFILEDATADIR/$profilepath/$channel/$subdirs/nfcapd.$iso";
1415
1416					# array element 7 contains the size in bytes
1417					$profile_size +=(stat($outfile))[7];
1418				}
1419				$$profileinfo{'updated'} = $t;
1420				$$profileinfo{'tend'} 	 = $t;
1421				$$profileinfo{'size'} 	 = $profile_size;
1422
1423				$t += $NfConf::CYCLETIME;
1424				%liveprofile = ReadProfile('live', '.');
1425				$tend = $liveprofile{'tend'};
1426			}
1427		}
1428	} else {
1429		syslog('info', "Graphs for profile '$profile', group '$profilegroup' not rebuilded");
1430	}
1431
1432	if ( $profile eq 'live' && $profilegroup eq '.' ) {
1433		syslog('debug', "Release lock");
1434		Nfsync::semsignal();
1435	}
1436
1437	syslog('info', "Rebuilding done.");
1438
1439	# if we need to create the profile from
1440	# history data, lock the profile until down.
1441	$$profileinfo{'locked'} 		= 0;
1442
1443	# state ok
1444	$$profileinfo{'status'} 		= 'OK';
1445
1446	NfSenRRD::UpdateGraphs($profile, $profilegroup, $$profileinfo{'tend'}, 1);
1447
1448	return 'ok';
1449
1450} # End of DoRebuild
1451
1452#
1453# Entry points for nfsend. All subs have a socket and an opts field as input parameters
1454sub GetAllProfiles {
1455	my $socket	= shift;
1456	my $opts 	= shift;
1457
1458	foreach my $profilegroup ( ProfileGroups() ) {
1459		my @AllProfiles = ProfileList($profilegroup);
1460		if ( scalar @AllProfiles == 0 ) {
1461			# this groups no no profiles any longer - remove the directory
1462			# ignore errors as the last profile delete may still be in progress
1463			# however, the directories will be removed thereafter
1464			unlink "$NfConf::PROFILESTATDIR/$profilegroup/.group";
1465			unlink "$NfConf::PROFILEDATADIR/$profilegroup/.group";
1466			rmdir  "$NfConf::PROFILESTATDIR/$profilegroup";
1467			rmdir  "$NfConf::PROFILEDATADIR/$profilegroup";
1468		} else {
1469			foreach my $profile ( @AllProfiles ) {
1470				print $socket "_profiles=$profilegroup/$profile\n";
1471			}
1472		}
1473	}
1474
1475	print $socket $EODATA;
1476	if ( defined $Log::ERROR ) {
1477		print $socket "ERR $Log::ERROR\n";
1478	} else {
1479		print $socket "OK Profile Listing\n";
1480	}
1481
1482} # End of GetAllProfiles
1483
1484#
1485sub GetProfile {
1486	my $socket 	= shift;
1487	my $opts 	= shift;
1488
1489	my ($profile, $profilegroup);
1490	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
1491	if ( $ret ne 'ok' ) {
1492		print $socket $EODATA;
1493		print $socket "ERR $ret\n";
1494		return;
1495	}
1496
1497	$ret = VerifyProfile($profile, $profilegroup, 1);
1498	if ( $ret ne 'ok' ) {
1499		print $socket $EODATA;
1500		print $socket "ERR $ret\n";
1501		return;
1502	}
1503
1504	my $profilepath  = ProfilePath($profile, $profilegroup);
1505
1506	my %profileinfo = ReadProfile($profile, $profilegroup);
1507	if ( $profileinfo{'status'} eq 'empty' ) {
1508		print $socket $EODATA;
1509		print $socket "ERR Profile '$profile' in group '$profilegroup': $Log::ERROR\n";
1510		return;
1511	}
1512
1513	# raw output format
1514	foreach my $key ( keys %profileinfo ) {
1515		next if $key eq 'channel';
1516		if ( $key eq 'description' ) {
1517			foreach my $line ( @{$profileinfo{$key}} ) {
1518				print $socket "_$key=$line\n";
1519			}
1520		} else {
1521			if ( !defined $profileinfo{$key} ) {
1522				warn "Undef for key '$key' in '$profile', '$profilegroup'";
1523			}
1524			print $socket  "$key=$profileinfo{$key}\n";
1525		}
1526	}
1527	my @CHAN = sort {
1528   		my $num1 = "$profileinfo{'channel'}{$a}{'sign'}$profileinfo{'channel'}{$a}{'order'}";
1529   		my $num2 = "$profileinfo{'channel'}{$b}{'sign'}$profileinfo{'channel'}{$b}{'order'}";
1530   		$num2 <=> $num1;
1531	} keys %{$profileinfo{'channel'}};
1532
1533	foreach my $channel ( @CHAN ) {
1534		my @_properties;
1535		foreach my $prop ( @ChannelProperties ) {
1536			push @_properties, $profileinfo{'channel'}{$channel}{$prop};
1537		}
1538
1539		print $socket "_channel=$channel:" , join(':', @_properties), "\n";
1540	}
1541
1542	if ( -f "$NfConf::PROFILESTATDIR/$profilepath/flows-day.png" ) {
1543		print $socket "graphs=ok\n";
1544	} else {
1545		print $socket "graphs=no\n";
1546	}
1547
1548	print $socket $EODATA;
1549	print $socket "OK Command completed\n";
1550
1551} # End of GetProfile
1552
1553#
1554sub GetStatinfo {
1555	my $socket 	= shift;
1556	my $opts 	= shift;
1557
1558	my ($profile, $profilegroup);
1559	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
1560	if ( $ret ne 'ok' ) {
1561		print $socket $EODATA;
1562		print $socket "ERR $ret\n";
1563		return;
1564	}
1565
1566	$ret = VerifyProfile($profile, $profilegroup, 1);
1567	if ( $ret ne 'ok' ) {
1568		print $socket $EODATA;
1569		print $socket "ERR $ret\n";
1570		return;
1571	}
1572
1573	if ( !exists $$opts{'channel'} ) {
1574		print $socket $EODATA;
1575		print $socket "ERR Missing channel name!\n";
1576		return;
1577	}
1578	my $channel = $$opts{'channel'};
1579
1580	if ( !exists $$opts{'tstart'} ) {
1581		print $socket $EODATA;
1582		print $socket "ERR Missing time slot.\n";
1583		return;
1584	}
1585
1586	my $tstart = $$opts{'tstart'};
1587	if ( !NfSen::ValidISO($tstart) ) {
1588		print $socket $EODATA;
1589		print $socket "ERR Unparsable time format '$tstart'!\n";
1590		return;
1591	}
1592
1593	my $tend = undef;
1594	if ( exists $$opts{'tend'} ) {
1595		$tend = $$opts{'tend'};
1596		if ( !NfSen::ValidISO($tend) ) {
1597			print $socket $EODATA;
1598			print $socket "ERR Unparsable time format '$tend'!\n";
1599			return;
1600		}
1601	}
1602
1603	my %profileinfo = ReadProfile($profile, $profilegroup);
1604	if ( $profileinfo{'status'} eq 'empty' ) {
1605		print $socket $EODATA;
1606		print $socket "ERR Profile '$profile' in group '$profilegroup': $Log::ERROR\n";
1607		next;
1608	}
1609
1610	if ( !exists $profileinfo{'channel'}{$channel} ) {
1611		print $socket $EODATA;
1612		print $socket "ERR channel '$channel' does not exists.\n";
1613		return;
1614	}
1615	my $subdirs = NfSen::SubdirHierarchy(NfSen::ISO2UNIX($tstart));
1616
1617	# margin checks
1618	my $_tmp = NfSen::ISO2UNIX($tstart);
1619	if ( $_tmp < $profileinfo{'tstart'} ) {
1620		print $socket $EODATA;
1621		print $socket "ERR '$tstart' outside profile.\n";
1622		return;
1623	}
1624	if ( $_tmp > $profileinfo{'tend'} ) {
1625		print $socket $EODATA;
1626		print $socket "ERR '$tstart' beyond profile.\n";
1627		return;
1628	}
1629	if ( defined $tend ) {
1630		if ( $tend < $tstart ) {
1631			print $socket $EODATA;
1632			print $socket "ERR '$tend' before start time '$tstart'\n";
1633			return;
1634		}
1635		$_tmp = NfSen::ISO2UNIX($tend);
1636		if ( $_tmp > $profileinfo{'tend'} ) {
1637			print $socket $EODATA;
1638			print $socket "ERR '$tend' outside profile\n";
1639			return;
1640		}
1641	}
1642
1643	my @LogBook;
1644	my ($statinfo, $exit_code, $err );
1645	if ( ($profileinfo{'type'} & 4 ) > 0 ) {
1646		# shadow profile
1647		# get mean values from RRD
1648		($statinfo, $err ) = ReadRRDStatInfo(\%profileinfo, $channel, $tstart, $tend);
1649		$exit_code = defined $err ? 1 : 0;
1650	} else {
1651		# get real values from files
1652		($statinfo, $exit_code, $err ) = ReadStatInfo(\%profileinfo, $channel, $subdirs, $tstart, $tend);
1653	}
1654
1655	if ( $exit_code != 0 ) {
1656		print $socket $EODATA;
1657		print $socket "ERR $err\n";
1658		return;
1659	}
1660
1661	foreach my $ds ( @NfSenRRD::RRD_DS ) {
1662	 	print $socket "$ds=$$statinfo{$ds}\n";
1663	}
1664	print $socket $EODATA;
1665	print $socket "OK command completed\n";
1666
1667} # End of GetStatinfo
1668
1669sub AddProfile {
1670	my $socket = shift;
1671	my $opts   = shift;
1672
1673	# both options can be set at the same time
1674	my $history_profile	= 0;	# Profile start back in time; needs to be builded
1675	my $continuous_profile = 0;	# Profile continuous in time
1676
1677	my ($profile, $profilegroup);
1678	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
1679	if ( $ret ne 'ok' ) {
1680		print $socket $EODATA;
1681		print $socket "ERR $ret\n";
1682		return;
1683	}
1684
1685	$ret = VerifyProfile($profile, $profilegroup, 0);
1686	if ( $ret ne 'ok' ) {
1687		print $socket $EODATA;
1688		print $socket "ERR $ret\n";
1689		return;
1690	}
1691
1692	if ( ProfileExists($profile, $profilegroup) ) {
1693		print $socket $EODATA;
1694		print $socket "ERR profile '$profile' in group '$profilegroup' already exists\n";
1695		return;
1696	}
1697
1698	my $profilepath = ProfilePath($profile, $profilegroup);
1699
1700	# new profile must fit into live profile
1701	my %liveprofile = ReadProfile('live', '.');
1702	if ( $liveprofile{'status'} eq 'empty' ) {
1703		# Could not read live profile
1704		print $socket $EODATA;
1705		print $socket "ERR profile 'live': $Log::ERROR\n";
1706		return;
1707	}
1708
1709	my $now 	= time();
1710	my $tlast	= $liveprofile{'tend'};
1711	my $tstart	= $tlast;
1712	my $tend 	= $tstart;
1713
1714	if ( exists $$opts{'tstart'} ) {
1715		$tstart = $$opts{'tstart'};
1716		if ( !NfSen::ValidISO($tstart) ) {
1717			print $socket $EODATA;
1718			print $socket "ERR Time format wrong '$tstart'!\n";
1719			return;
1720		}
1721		$tstart = NfSen::ISO2UNIX($tstart);
1722		if ( $tstart > $now ) {
1723			print $socket $EODATA;
1724			print $socket "ERR Profile start time in future: '$tstart'!\n";
1725			return;
1726		}
1727		$history_profile = 1;
1728	} else {
1729		# otherwise we have a ontinuous profile
1730		$continuous_profile = 1;
1731	}
1732
1733	if ( exists $$opts{'tend'} ) {
1734		$tend = $$opts{'tend'};
1735		if ( !NfSen::ValidISO($tend) ) {
1736			print $socket $EODATA;
1737			print $socket "ERR Time format wrong '$tend'!\n";
1738			return;
1739		}
1740		$tend = NfSen::ISO2UNIX($tend);
1741		$history_profile = 1;
1742	} else {
1743		# otherwise we have a ontinuous profile
1744		$continuous_profile = 1;
1745	}
1746
1747	if ( $history_profile && ($tstart < $liveprofile{'tstart'}) ) {
1748		print $socket $EODATA;
1749		print $socket "ERR '$$opts{'tstart'}' not within 'live' profile time window\n";
1750		return;
1751	}
1752	if ( $history_profile && ($tend > $liveprofile{'tend'}) ) {
1753		print $socket $EODATA;
1754		print $socket "ERR '$$opts{'tend'}' not within 'live' profile time window\n";
1755		return;
1756	}
1757
1758	# prevent overflow to future timestamps
1759	$tend = $tlast if $tend > $tlast;
1760
1761	my $shadow_profile = exists $$opts{'shadow'} && $$opts{'shadow'} == 1;
1762	if ( $shadow_profile ) {
1763		$$opts{'expire'}  = 0;
1764		$$opts{'maxsize'} = 0;
1765	}
1766
1767	# expire time
1768	my $lifetime   = exists $$opts{'expire'}  ? NfSen::ParseExpire($$opts{'expire'}) : 0;
1769	if ( $lifetime < 0 ) {
1770		print $socket $EODATA;
1771		print $socket "ERR Unknown expire time '$$opts{'expire'}'\n";
1772		return;
1773	}
1774
1775	# max size
1776	my $maxsize	= exists $$opts{'maxsize'} ? NfSen::ParseMaxsize($$opts{'maxsize'}) : 0;
1777	if ( $maxsize < 0 ) {
1778		print $socket $EODATA;
1779		print $socket "ERR Unknown max size '$$opts{'maxsize'}'\n";
1780		return;
1781	}
1782
1783	# All channel issues are done in AddProfileChannel
1784
1785	# Do the work now:
1786	umask 0002;
1787	my @dirs;
1788	push @dirs, "$NfConf::PROFILESTATDIR";
1789	# if stat and data dirs differ
1790	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
1791		push @dirs, "$NfConf::PROFILEDATADIR";
1792	}
1793
1794	foreach my $dir ( @dirs ) {
1795		# make sure profile group exists
1796		if ( !-d "$dir/$profilegroup" ) {
1797			if ( !mkdir "$dir/$profilegroup" ) {
1798				my $err = $!;
1799				syslog("err", "Can't create profile group directory '$dir/$profilegroup': $err");
1800				print $socket $EODATA;
1801				print $socket "ERR Can't create profile group directory '$dir/$profilegroup': $err!\n";
1802				return;
1803			}
1804			if ( !open TAGFILE, ">$dir/$profilegroup/.group" ) {
1805				my $err = $!;
1806				syslog("err", "Can't create profile group tag file '$dir/$profilegroup/.group': $err");
1807				print $socket $EODATA;
1808				print $socket "ERR Can't create profile group tag file '$dir/$profilegroup/.group': $err!\n";
1809				return;
1810			}
1811			close TAGFILE;
1812		}
1813
1814		if ( !mkdir "$dir/$profilepath" ) {
1815			my $err = $!;
1816			syslog("err", "Can't create profile directory '$dir/$profilepath': $err");
1817			print $socket $EODATA;
1818			print $socket "ERR Can't create profile directory '$dir/$profilepath': $err!\n";
1819			return;
1820		}
1821
1822	}
1823
1824	# Convert a one line description
1825	if ( exists $$opts{'description'} && ref $$opts{'description'} ne "ARRAY" ) {
1826		$$opts{'description'} = [ "$$opts{'description'}" ];
1827	}
1828	my %profileinfo;
1829	$profileinfo{'channel'} = {};
1830
1831	$profileinfo{'description'}	= exists $$opts{'description'} ? $$opts{'description'} : \@ProfileTag;
1832	$profileinfo{'name'}		= $profile;
1833	$profileinfo{'group'}		= $profilegroup;
1834	$profileinfo{'tcreate'} 	= $now;
1835	$profileinfo{'tbegin'} 		= $tstart;
1836	$profileinfo{'tstart'} 		= $tstart;
1837	$profileinfo{'tend'} 		= $tend;
1838
1839	# the first slot to be updated is 'updated' + $NfConf::CYCLETIME
1840	$profileinfo{'updated'}		= $tstart - $NfConf::CYCLETIME;
1841
1842	# expiring profiles makes only sense for continous profiles
1843	$profileinfo{'expire'} 		= $continuous_profile ? $lifetime : 0;
1844	$profileinfo{'maxsize'} 	= $continuous_profile ? $maxsize : 0;
1845
1846	# the profile starts we 0 size
1847	$profileinfo{'size'} 		= 0;
1848
1849	# continuous profile overwrites history profile
1850	$profileinfo{'type'} 		= 1 if $history_profile;
1851	$profileinfo{'type'} 		= 2 if $continuous_profile;
1852	$profileinfo{'type'} 		+= 4 if $shadow_profile;	# set bit 2 to mark profile as shadow profile
1853
1854	# if we need to create the profile from
1855	# history data, lock the profile until done.
1856	$profileinfo{'locked'} 		= 0;
1857
1858	# status of profile
1859	$profileinfo{'status'}		= 'new';
1860
1861	# Version of profile
1862	$profileinfo{'version'}		= $PROFILE_VERSION;
1863
1864	if ( !WriteProfile(\%profileinfo) ) {
1865		syslog('err', "Error writing profile '$profile': $Log::ERROR");
1866		print $socket $EODATA;
1867		print $socket "ERR writing profile '$profile': $Log::ERROR\n";
1868		# Even if we could not write the profile, try to delete the remains anyway
1869		DeleteProfile($socket, $opts);
1870	}
1871
1872	print $socket $EODATA;
1873	print $socket "OK profile added\n";
1874
1875} # End of AddProfile
1876
1877sub AddProfileChannel {
1878	my $socket = shift;
1879	my $opts   = shift;
1880
1881	# Parameter checking
1882	if ( !exists $$opts{'channel'} ) {
1883		print $socket $EODATA;
1884		print $socket "ERR Missing channel name!\n";
1885		return;
1886	}
1887	my $channel;
1888	my $ret = ChannelDecode($opts, \$channel);
1889	if ( $ret ne 'ok' ) {
1890		print $socket $EODATA;
1891		print $socket "ERR $ret\n";
1892		return;
1893	}
1894
1895	my ($profile, $profilegroup);
1896	$ret = ProfileDecode($opts, \$profile, \$profilegroup);
1897	if ( $ret ne 'ok' ) {
1898		print $socket $EODATA;
1899		print $socket "ERR $ret\n";
1900		return;
1901	}
1902
1903	$ret = VerifyProfile($profile, $profilegroup, 1);
1904	if ( $ret ne 'ok' ) {
1905		print $socket $EODATA;
1906		print $socket "ERR $ret\n";
1907		return;
1908	}
1909
1910	# profile live has a diffenrent procedure anyway
1911	if ( $profile eq 'live' && $profilegroup eq '.' ) {
1912		print $socket $EODATA;
1913		print $socket "ERR For profile '$profile', add channels in nfsen.conf and run nfsen reconfig !\n";
1914		return;
1915	}
1916
1917	# validate name
1918	$ret = NfSen::ValidFilename($channel);
1919	if ( $ret ne "ok" ) {
1920		print $socket $EODATA;
1921		print $socket "ERR checking channel name: $ret!\n";
1922		return;
1923	}
1924
1925	my %profileinfo = ReadProfile($profile, $profilegroup);
1926	if ( exists $profileinfo{'channel'}{$channel} ) {
1927		print $socket $EODATA;
1928		print $socket "ERR channel '$channel' already exists.\n";
1929		return;
1930	}
1931
1932	# Profile filter:
1933	# A given 'filter' overwrites the filter in the file 'filterfile'
1934	my $filter = [];
1935	if ( exists $$opts{'filter'} ) {
1936		$filter = $$opts{'filter'};
1937		# convert a one line filter
1938		if ( ref $filter ne "ARRAY" ) {
1939			$filter = [ "$filter" ];
1940		}
1941	} elsif ( exists $$opts{'filterfile'} ) {
1942		open(FILTER, $$opts{'filterfile'} ) or
1943			syslog('err', "Can't open filter file '$filter': $!"),
1944			print $socket $EODATA;
1945			print $socket "ERR Can't open filter file '$filter': $!\n",
1946			return;
1947		@$filter = <FILTER>;
1948		close FILTER;
1949	}
1950	if ( scalar @$filter == 0 ) {
1951		push @$filter, "not any\n";
1952	}
1953	my %out = NfSen::VerifyFilter($filter);
1954	if ( $out{'exit'} > 0 ) {
1955		print $socket $EODATA;
1956		print $socket "ERR Filter syntax error: ", join(' ', $out{'nfdump'}), "\n";
1957		return;
1958	}
1959	my $sourcelist;
1960	my %liveprofile = ReadProfile('live', '.');
1961	if ( exists $$opts{'sourcelist'} ) {
1962		$sourcelist = $$opts{'sourcelist'};
1963		while ( $sourcelist =~ s/\|\|/|/g ) {;}
1964		$sourcelist =~ s/^\|//;
1965		$sourcelist =~ s/\|$//;
1966		my @_list = split /\|/, $sourcelist;
1967		foreach my $source ( @_list ) {
1968			if ( !exists $liveprofile{'channel'}{$source} ) {
1969				print $socket $EODATA;
1970				print $socket "ERR source '$source' does not exist in profile live\n";
1971				return;
1972			}
1973		}
1974		$profileinfo{'channel'}{$channel}{'sourcelist'} = $sourcelist;
1975	} else {
1976		$sourcelist = join '|', keys %{$liveprofile{'channel'}};
1977	}
1978
1979	%profileinfo = LockProfile($profile, $profilegroup);
1980	if ( $profileinfo{'status'} eq 'empty' ) {
1981		if ( $profileinfo{'locked'} == 1 ) {
1982			print $socket $EODATA;
1983			print $socket "ERR Profile is locked!\n";
1984			return;
1985		}
1986
1987		# it's an error reading this profile
1988		if ( defined $Log::ERROR ) {
1989			print $socket $EODATA;
1990			print $socket "ERR $Log::ERROR\n";
1991			syslog('err', "Error $profile: $Log::ERROR");
1992			return;
1993		}
1994	}
1995
1996	my $colour = '#abcdef';
1997	my $sign   = '+';
1998	my $order  = 0;
1999	if ( exists $liveprofile{'channel'}{$channel} ) {
2000		$colour = $liveprofile{'channel'}{$channel}{'colour'};
2001		$sign   = $liveprofile{'channel'}{$channel}{'sign'};
2002	 	$order  = $liveprofile{'channel'}{$channel}{'order'};
2003	}
2004
2005	if ( exists $$opts{'colour'} ) {
2006		if ( $$opts{'colour'} !~ /^#[0-9a-f]{6}$/i ) {
2007			print $socket $EODATA;
2008			print $socket "ERR colour format error. Use '#dddddd'\n";
2009			return;
2010		}
2011		$colour = $$opts{'colour'};
2012	}
2013
2014	if ( exists $$opts{'sign'} ) {
2015		if ( $$opts{'sign'} !~ /^[+\-]$/ ) {
2016			print $socket $EODATA;
2017			print $socket "ERR sign format error. Use '+' or '-'\n";
2018			return;
2019		}
2020		$sign = $$opts{'sign'};
2021	}
2022
2023	if ( exists $$opts{'order'} ) {
2024		if ( $$opts{'order'} !~ /^[0-9]+$/ ) {
2025			print $socket $EODATA;
2026			print $socket "ERR option format error: not a number\n";
2027			return;
2028		}
2029		$order = $$opts{'order'};
2030	}
2031
2032	# Everything should be clear by now - so do the work
2033	$ret = AddChannel(\%profileinfo, $channel, $sign, $order, $colour, $sourcelist, $filter);
2034
2035	$profileinfo{'locked'} 		= 0;
2036	if ( !WriteProfile(\%profileinfo) ) {
2037		DeleteProfileChannel($socket, $opts);
2038		$ret = "Can't update profile info: $Log::ERROR";
2039	}
2040
2041	if ( $ret ne 'ok' ) {
2042		print $socket $EODATA;
2043		print $socket "ERR Add channel '$channel' failed: $ret\n";
2044	} else {
2045		print $socket $EODATA;
2046		print $socket "OK Channel '$channel' added.\n";
2047	}
2048
2049} # End of AddProfileChannel
2050
2051sub DeleteProfileChannel {
2052	my $socket = shift;
2053	my $opts   = shift;
2054
2055	# Parameter checking
2056	my $channel;
2057	my $ret = ChannelDecode($opts, \$channel);
2058	if ( $ret ne 'ok' ) {
2059		print $socket $EODATA;
2060		print $socket "ERR $ret\n";
2061		return;
2062	}
2063
2064	my ($profile, $profilegroup);
2065	$ret = ProfileDecode($opts, \$profile, \$profilegroup);
2066	if ( $ret ne 'ok' ) {
2067		print $socket $EODATA;
2068		print $socket "ERR $ret\n";
2069		return;
2070	}
2071
2072	$ret = VerifyProfile($profile, $profilegroup, 1);
2073	if ( $ret ne 'ok' ) {
2074		print $socket $EODATA;
2075		print $socket "ERR $ret\n";
2076		return;
2077	}
2078
2079	# profile live has a diffenrent procedure anyway
2080	if ( $profile eq 'live' && $profilegroup eq '.') {
2081		print $socket $EODATA;
2082		print $socket "ERR Profile '$profile'. Delete channels in nfsen.conf and run nfsen reconfig !\n";
2083		return;
2084	}
2085
2086	# validate name
2087	$ret = NfSen::ValidFilename($channel);
2088	if ( $ret ne "ok" ) {
2089		print $socket $EODATA;
2090		print $socket "ERR checking channel name: $ret!\n";
2091		return;
2092	}
2093
2094	my %profileinfo = ReadProfile($profile, $profilegroup);
2095	if ( !exists $$opts{'force'} && !exists $profileinfo{'channel'}{$channel} ) {
2096		print $socket $EODATA;
2097		print $socket "ERR channel '$channel' does not exists.\n";
2098		return;
2099	}
2100
2101	%profileinfo = LockProfile($profile, $profilegroup);
2102	if ( $profileinfo{'status'} eq 'empty' ) {
2103		if ( $profileinfo{'locked'} == 1 ) {
2104			print $socket $EODATA;
2105			print $socket "ERR Profile is locked!\n";
2106			return;
2107		}
2108
2109		# it's an error reading this profile
2110		if ( defined $Log::ERROR ) {
2111			print $socket $EODATA;
2112			print $socket "ERR $Log::ERROR\n";
2113			syslog('err', "Error $profile: $Log::ERROR");
2114			return;
2115		}
2116	}
2117
2118	if ( Nfsync::semnowait() ) {
2119		$ret = NfProfile::DeleteChannel(\%profileinfo, $channel);
2120		Nfsync::semsignal();
2121	} else {
2122		print $socket $EODATA;
2123		print $socket "ERR Can not delete the channel while a periodic update is in progress. Try again later.\n";
2124	}
2125
2126
2127	if ( scalar(keys %{$profileinfo{'channel'}}) == 0 ) {
2128		$profileinfo{'status'}	= 'stalled';
2129		$profileinfo{'tstart'} 	= $profileinfo{'updated'};
2130		$profileinfo{'tend'} 	= $profileinfo{'tstart'};
2131	}
2132
2133	$profileinfo{'locked'} 		= 0;
2134	if ( !WriteProfile(\%profileinfo) ) {
2135		$ret = "Can't update profile info: $Log::ERROR";
2136	}
2137
2138	if ( $ret eq 'ok' ) {
2139		# for the continuous profile
2140		print $socket $EODATA;
2141		print $socket "OK Channel '$channel' deleted.\n";
2142	} else {
2143		print $socket $EODATA;
2144		print $socket "ERR Delete channel '$channel' failed: $ret\n";
2145	}
2146
2147} # End of DeleteProfileChannel
2148
2149sub CancelProfile {
2150	my $socket	= shift;
2151	my $opts 	= shift;
2152
2153	my ($profile, $profilegroup);
2154	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2155	if ( $ret ne 'ok' ) {
2156		print $socket $EODATA;
2157		print $socket "ERR $ret\n";
2158		return;
2159	}
2160
2161	$ret = VerifyProfile($profile, $profilegroup, 1);
2162	if ( $ret ne 'ok' ) {
2163		print $socket $EODATA;
2164		print $socket "ERR $ret\n";
2165		return;
2166	}
2167
2168	my $profilepath = ProfilePath($profile, $profilegroup);
2169
2170	if ( $profile eq 'live' && $profilegroup eq '.') {
2171		print $socket $EODATA;
2172		print $socket "ERR Don't want to delete profile 'live'!\n";
2173		return;
2174	}
2175
2176	my %profileinfo = ReadProfile($profile, $profilegroup);
2177	if ( ! -f "$NfConf::PROFILESTATDIR/$profilepath/.BUILDING" ) {
2178		print $socket $EODATA;
2179		print $socket "ERR No such build in progress\n";
2180		return;
2181	}
2182
2183	open CANCELFLAG, ">$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
2184	close CANCELFLAG;
2185
2186	print $socket $EODATA;
2187	print $socket "OK Building profile '$profile' canceled.\n";
2188
2189} # End of CancelProfile
2190
2191
2192sub DeleteProfile {
2193	my $socket	= shift;
2194	my $opts 	= shift;
2195
2196	my ($profile, $profilegroup);
2197	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2198	if ( $ret ne 'ok' ) {
2199		print $socket $EODATA;
2200		print $socket "ERR $ret\n";
2201		return;
2202	}
2203
2204	$ret = VerifyProfile($profile, $profilegroup, 1);
2205	if ( $ret ne 'ok' ) {
2206		print $socket $EODATA;
2207		print $socket "ERR $ret\n";
2208		return;
2209	}
2210
2211	my $profilepath = ProfilePath($profile, $profilegroup);
2212
2213	if ( $profile eq 'live' && $profilegroup eq '.') {
2214		print $socket $EODATA;
2215		print $socket "ERR Don't want to delete profile 'live'!\n";
2216		return;
2217	}
2218
2219	my %profileinfo = ReadProfile($profile, $profilegroup);
2220	if ( $profileinfo{'status'} eq 'empty' ) {
2221		# Could not read live profile
2222		print $socket $EODATA;
2223		print $socket "ERR Profile '$profile': $Log::ERROR\n";
2224		return;
2225	}
2226	if ( ! exists $$opts{'force'} && $profileinfo{'locked'} ) {
2227		print $socket $EODATA;
2228		print $socket "ERR Profile '$profile' is locked and can not be deleted now!\n";
2229		return;
2230	}
2231	if ( ! exists $$opts{'force'} && ( $profileinfo{'status'} ne 'OK' && $profileinfo{'status'} ne 'DELETED' )) {
2232		print $socket $EODATA;
2233		print $socket "ERR Profile '$profile' is not in status 'OK' and can not be deleted now!\n";
2234		return;
2235	}
2236	%profileinfo = LockProfile($profile, $profilegroup);
2237	$profileinfo{'status'} = 'DELETED';
2238	WriteProfile(\%profileinfo);
2239
2240	my @dirs;
2241	push @dirs, "$NfConf::PROFILESTATDIR";
2242	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
2243		push @dirs, "$NfConf::PROFILEDATADIR";
2244	}
2245	foreach my $dir ( @dirs ) {
2246		if ( !Nfsync::semnowait() ) {
2247			open DELFLAG, ">$NfConf::PROFILESTATDIR/$profilepath/.DELETED";
2248			print DELFLAG "profile deleted and to be removed later\n";
2249			close DELFLAG;
2250			print $socket $EODATA;
2251			print $socket "OK Profile '$profile' deleted.\n";
2252			return;
2253		}
2254
2255		if ( !rename "$dir/$profilepath", "$dir/.$profile" ) {
2256			Nfsync::semsignal();
2257			print $socket $EODATA;
2258			print $socket "ERR Failed to rename profile '$profile' in group '$profilegroup' in order to delete: $!\n";
2259			return;
2260		} else {
2261			Nfsync::semsignal();
2262		}
2263
2264		my $command = "/bin/rm -rf $dir/.$profile &";
2265		system($command);
2266		if ( defined $main::child_exit && $main::child_exit != 0 ) {
2267			syslog('err', "Failed to execute command: $!\n");
2268			syslog('err', "system command was: '$command'\n");
2269		}
2270	}
2271
2272	print $socket $EODATA;
2273	print $socket "OK Profile '$profile' deleted.\n";
2274
2275} # End of DeleteProfile
2276
2277sub ReGroupProfile {
2278	my $profileref = shift;
2279	my $newgroup   = shift;
2280
2281	my $profile = $$profileref{'name'};
2282	my $profilegroup = $$profileref{'group'};
2283
2284	if ( $profile eq 'live' && $profilegroup eq '.') {
2285		return "Don't want to move profile 'live'!\n";
2286	}
2287
2288	if ( $$profileref{'status'} ne 'OK' ) {
2289		return "Profile '$profile' is not in status 'OK' and can not be moved now!\n";
2290	}
2291
2292	my $profilepath	= ProfilePath($profile, $profilegroup);
2293	my $newprofilepath = ProfilePath($profile, $newgroup);
2294	if ( $profilepath eq $newprofilepath ) {
2295		return "ok";
2296	}
2297
2298	if ( -d "$NfConf::PROFILESTATDIR/$newprofilepath" ) {
2299		return "An other profile with name '$profile' already exists in new group\n";
2300	}
2301
2302	if ( !Nfsync::semnowait() ) {
2303		return "Can not rename the profile while a periodic update is in progress. Try again later.\n";
2304	}
2305
2306	my @dirs;
2307	push @dirs, "$NfConf::PROFILESTATDIR";
2308	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
2309		push @dirs, "$NfConf::PROFILEDATADIR";
2310	}
2311
2312	foreach my $dir ( @dirs ) {
2313		if ( ! -d "$dir/$newgroup" && !mkdir "$dir/$newgroup" ) {
2314			my $err = "Can't create new profile group directory '$NfConf::PROFILESTATDIR/$newgroup': $!";
2315			syslog("err", "$err");
2316			Nfsync::semsignal();
2317			return "$err\n";
2318		}
2319
2320		if ( !open TAGFILE, ">$dir/$newgroup/.group" ) {
2321			my $err = $!;
2322			syslog("err", "Can't create profile group tag file '$dir/$newgroup/.group': $err");
2323			Nfsync::semsignal();
2324			return "Can't create profile group tag file '$dir/$newgroup/.group': $err!\n";
2325		}
2326		close TAGFILE;
2327	}
2328
2329	if ( !rename "$NfConf::PROFILESTATDIR/$profilepath", "$NfConf::PROFILESTATDIR/$newprofilepath" ) {
2330		Nfsync::semsignal();
2331		return "Failed to rename profile '$profile': $!\n";
2332	}
2333	if ( "$NfConf::PROFILESTATDIR" ne "$NfConf::PROFILEDATADIR" ) {
2334		if ( !rename "$NfConf::PROFILEDATADIR/$profilepath", "$NfConf::PROFILEDATADIR/$newprofilepath" ) {
2335			# ohhh this is really bad! try to restore old profile stat dir
2336			rename "$NfConf::PROFILESTATDIR/$newprofilepath", "$NfConf::PROFILESTATDIR/$profilegroup";
2337
2338			Nfsync::semsignal();
2339			return "Failed to rename profile '$profile': $!\n";
2340		}
2341	}
2342
2343	$$profileref{'group'} = $newgroup;
2344	Nfsync::semsignal();
2345
2346	return 'ok';
2347
2348} # End of ReGroupProfile
2349
2350
2351sub CommitProfile {
2352	my $socket	= shift;
2353	my $opts 	= shift;
2354
2355	my ($profile, $profilegroup);
2356	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2357	if ( $ret ne 'ok' ) {
2358		print $socket $EODATA;
2359		print $socket "ERR $ret\n";
2360		return;
2361	}
2362
2363	$ret = VerifyProfile($profile, $profilegroup, 1);
2364	if ( $ret ne 'ok' ) {
2365		print $socket $EODATA;
2366		print $socket "ERR $ret\n";
2367		return;
2368	}
2369
2370	my $profilepath	= ProfilePath($profile, $profilegroup);
2371	# is this a new profile
2372	my %profileinfo = ReadProfile($profile, $profilegroup);
2373	if ( $profileinfo{'status'} ne 'new' && $profileinfo{'status'} ne 'stalled' ) {
2374		print $socket $EODATA;
2375		print $socket "ERR Profile '$profile' not a new profile. Nothing to confirm.\n";
2376		return;
2377	}
2378
2379	if ( $profileinfo{'status'} eq 'stalled' && ($profileinfo{'type'} & 3 ) == 1  ) {
2380		# a stalled history profile does not make any sens to commit
2381		print $socket $EODATA;
2382		print $socket "ERR Can not commit stalled history profile.\n";
2383		return;
2384	}
2385
2386	# at least one channel need to exist
2387	if ( scalar keys(%{$profileinfo{'channel'}}) == 0 ) {
2388		print $socket $EODATA;
2389		print $socket "ERR No channels in profile '$profile'. At least one channels must exists.\n";
2390		return;
2391	}
2392
2393	my $now	= time();
2394	$now -= $now % $NfConf::CYCLETIME;
2395
2396	if ( $profileinfo{'status'} eq 'stalled' ) {
2397		$profileinfo{'tstart'} 	= $now;
2398		$profileinfo{'tend'} 	= $profileinfo{'tstart'};
2399		$profileinfo{'updated'}	= $profileinfo{'tend'};
2400	}
2401
2402	# if history data required
2403	if ( $profileinfo{'tstart'} <  $profileinfo{'tend'} ) {
2404		chdir '/';
2405		my $pid = fork;
2406		if ( !defined $pid ) {
2407			$Log::ERROR = $!;
2408			print $socket $EODATA;
2409			print $socket "ERR Can't fork: $Log::ERROR\n";
2410			return;
2411		}
2412		if ( $pid ) {
2413			# we are the parent processs
2414			print $socket "pid=$pid\n";
2415			print $socket $EODATA;
2416			print $socket "OK Profiling netflow data\n";
2417			open PID, ">$NfConf::PROFILESTATDIR/$profilepath/.BUILDING" ||
2418				syslog('err', "Can't open pid file: $NfConf::PROFILESTATDIR/$profilepath/.BUILDING: $!");
2419			print PID "$pid\n";
2420			close PID;
2421			return;
2422		}
2423		setsid or die "Can't start a new session: $!";
2424		# the child starts to build the profile from history data
2425		close STDIN;
2426		close STDOUT;
2427		close $socket;
2428
2429		# STDERR is tied too syslog.
2430		untie *STDERR;
2431		close STDERR;
2432
2433		open STDIN, '/dev/null'   || die "Can't read /dev/null: $!";
2434		open STDOUT, '>/dev/null' || die "Can't write to /dev/null: $!";
2435		open STDERR, '>/dev/null' || die "Can't write to /dev/null: $!";
2436
2437		ProfileHistory(\%profileinfo);
2438
2439		unlink "$NfConf::PROFILESTATDIR/$profilepath/.BUILDING";
2440
2441		syslog('debug', "Graph update profile: $profile, Time: $profileinfo{'tend'}.");
2442		if ( NfSenRRD::UpdateGraphs($profile, $profilegroup, $profileinfo{'tend'}, 1) ) {
2443			syslog('err', "Error graph update: $Log::ERROR");
2444			$profileinfo{'status'}	= 'FAILED';
2445		} else {
2446			$profileinfo{'status'}	= 'OK';
2447		}
2448
2449		# unlock profile
2450		$profileinfo{'locked'} 		= 0;
2451
2452		WriteProfile(\%profileinfo);
2453		exit(0);
2454
2455	}
2456
2457	my $status;
2458	syslog('debug', "Graph update profile: $profile, Time: $profileinfo{'tend'}.");
2459	if ( NfSenRRD::UpdateGraphs($profile, $profilegroup, $profileinfo{'tend'}, 1) ) {
2460		syslog('err', "Error graph update: $Log::ERROR");
2461		$profileinfo{'status'}	= 'FAILED';
2462		$status = "ERR $Log::ERROR\n";
2463	} else {
2464		$profileinfo{'status'}	= 'OK';
2465		$status = "OK command completed\n";
2466	}
2467
2468	# unlock profile
2469	$profileinfo{'locked'} 		= 0;
2470
2471	WriteProfile(\%profileinfo);
2472
2473	print $socket $EODATA;
2474	print $socket $status;
2475	return;
2476
2477} # End of CommitProfile
2478
2479sub ModifyProfile {
2480	my $socket	= shift;
2481	my $opts 	= shift;
2482
2483	my ($profile, $profilegroup);
2484	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2485	if ( $ret ne 'ok' ) {
2486		print $socket $EODATA;
2487		print $socket "ERR $ret\n";
2488		return;
2489	}
2490
2491	$ret = VerifyProfile($profile, $profilegroup, 1);
2492	if ( $ret ne 'ok' ) {
2493		print $socket $EODATA;
2494		print $socket "ERR $ret\n";
2495		return;
2496	}
2497
2498	my $profilepath	= ProfilePath($profile, $profilegroup);
2499	my %profileinfo = ReadProfile($profile, $profilegroup);
2500	if ( $profileinfo{'status'} eq 'empty' ) {
2501		# Could not read profile
2502		print $socket $EODATA;
2503		print $socket "ERR Profile '$profile': $Log::ERROR\n";
2504		return;
2505	}
2506
2507	my $continuous_profile = ($profileinfo{'type'} & 3) == 2 || $profileinfo{'type'} == 0;
2508
2509	my $changed = 0;
2510
2511	if ( exists $$opts{'description'} ) {
2512		$profileinfo{'description'} = $$opts{'description'};
2513		$changed = 1;
2514	}
2515
2516	if ( exists $$opts{'locked'} ) {
2517		my $locked_opt = $$opts{'locked'};
2518		if ( $locked_opt !~ /^[01]$/ ) {
2519			print $socket $EODATA;
2520			print $socket "ERR Invalid value for option locked: '$locked_opt'. Use locked=0 or locked=1\n";
2521			return;
2522		}
2523		$profileinfo{'locked'} = 0 if $locked_opt == 0;
2524		$profileinfo{'locked'} = 1 if $locked_opt == 1;
2525		$changed = 1;
2526	}
2527
2528	if ( exists $$opts{'status'} ) {
2529		my $status = $$opts{'status'};
2530		if ( $status !~ /^new$|^OK$|^FAILED$/ ) {
2531			print $socket $EODATA;
2532			print $socket "ERR Invalid value for option status: '$status'. Use 'new', 'OK' or 'FAILED'\n";
2533			return;
2534		}
2535		$profileinfo{'status'} = $status;
2536		$changed = 1;
2537	}
2538
2539	if ( $continuous_profile ) {
2540		# these changes make only sense for continuous profiles
2541		# expire time
2542		if ( exists $$opts{'expire'} ) {
2543			my $lifetime   = NfSen::ParseExpire($$opts{'expire'});
2544			if ( $lifetime < 0 ) {
2545				print $socket $EODATA;
2546				print $socket "ERR Unknown expire time '$$opts{'expire'}'\n";
2547				return;
2548			}
2549			$profileinfo{'expire'} = $lifetime;
2550			$changed = 1;
2551		}
2552
2553		# max size
2554		if ( exists $$opts{'maxsize'} ) {
2555 			my $maxsize = NfSen::ParseMaxsize($$opts{'maxsize'});
2556			if ( $maxsize < 0 ) {
2557				print $socket $EODATA;
2558				print $socket "ERR Unknown max size '$$opts{'maxsize'}'\n";
2559				return;
2560			}
2561			$profileinfo{'maxsize'}	= $maxsize;
2562			$changed = 1;
2563		}
2564	}
2565
2566	if ( exists $$opts{'newgroup'} ) {
2567		my $newgroup = $$opts{'newgroup'};
2568		if ( $newgroup ne '.' && $newgroup =~ /[^A-Za-z0-9\-+_]+/ ) {
2569			print $socket $EODATA;
2570			print $socket "ERR Illegal characters in group name: '$newgroup'!\n";
2571			return;
2572		}
2573		my $ret = ReGroupProfile(\%profileinfo, $newgroup);
2574		if ( $ret ne 'ok' ) {
2575			print $socket $EODATA;
2576			print $socket "ERR $ret\n";
2577			return;
2578		}
2579		$changed = 1;
2580	}
2581
2582	if ( exists $$opts{'profile_type'} ) {
2583		my $new_type = $$opts{'profile_type'};
2584		if ( $new_type !~ /^\d$/ || $new_type == 0 || $new_type > 6 ) {
2585			print $socket $EODATA;
2586			print $socket "ERR Illegal profile type '$new_type'\n";
2587			return;
2588		}
2589		$new_type &= 7;
2590
2591		my $current_shadow 	= ($profileinfo{'type'} & 4 ) > 0;
2592		my $current_type 	= $profileinfo{'type'} & 3;
2593		my $new_shadow 		= ($new_type & 4 ) > 0;
2594		$new_type 		= $new_type & 3;
2595
2596print $socket ".current_shadow=$current_shadow\n";
2597print $socket ".current_type=$current_type\n";
2598print $socket ".new_shadow=$new_shadow\n";
2599print $socket ".new_type=$new_type\n";
2600
2601		if ( $new_type == 0 ) {
2602			print $socket $EODATA;
2603			print $socket "ERR Illegal profile type '$new_type'\n";
2604			return;
2605		}
2606
2607		my %liveprofile = ReadProfile('live', '.');
2608		if ( $liveprofile{'status'} eq 'empty' ) {
2609			# Could not read profile
2610			print $socket $EODATA;
2611			print $socket "ERR Profile 'live': $Log::ERROR\n";
2612			return;
2613		}
2614
2615		if ( $current_shadow == $new_shadow  ) {
2616			$profileinfo{'type'} = $new_type + ($new_shadow ? 4 : 0);
2617			$changed = 1;
2618print $socket ".Start/stop sequence only\n";
2619		} else {
2620			if ( $new_shadow ) {
2621				# new shadow profile
2622print $socket ".New type is shadow\n";
2623				foreach my $channel ( keys %{$profileinfo{'channel'}} ) {
2624					rename "$NfConf::PROFILEDATADIR/$profilepath/$channel", "$NfConf::PROFILEDATADIR/$profilepath/.$channel";
2625					system "/bin/rm -rf $NfConf::PROFILEDATADIR/$profilepath/.$channel &";
2626					mkdir "$NfConf::PROFILEDATADIR/$profilepath/$channel";
2627				}
2628				# use start of graph for new value of profile start time
2629				$profileinfo{'tstart'} = $profileinfo{'tbegin'};
2630print $socket ".Set profile first $profileinfo{'tstart'}\n";
2631				$profileinfo{'size'} 	= 0;
2632				$profileinfo{'expire'} 	= 0;
2633				if ( $new_type == 2 && $profileinfo{'tstart'} < $liveprofile{'tstart'} ) {
2634					$profileinfo{'tstart'} = $liveprofile{'tstart'};
2635print $socket ".Adjust cont profile tstart to live profile tstart $liveprofile{'tstart'}\n";
2636				}
2637				if ( $profileinfo{'tstart'} > $profileinfo{'tend'} ) {
2638					$profileinfo{'tstart'} = $profileinfo{'tend'};
2639				}
2640				$changed = 1;
2641			} else {
2642				# new real profile
2643print $socket ".New type is real\n";
2644				if ( $new_type == 2 ) {
2645					my $now = time();
2646					$profileinfo{'tstart'} = $now - ( $now % $NfConf::CYCLETIME );
2647				} else {
2648					print $socket $EODATA;
2649					print $socket "ERR Can not convert to history profile - no data available\n";
2650					return;
2651				}
2652			}
2653			$profileinfo{'type'} = $new_type + ($new_shadow ? 4 : 0);
2654			$changed = 1;
2655		}
2656	}
2657
2658	if ( $changed ) {
2659		if ( !WriteProfile(\%profileinfo) ) {
2660			syslog('err', "Error writing profile '$profile': $Log::ERROR");
2661			print $socket $EODATA;
2662			print $socket "ERR writing profile '$profile': $Log::ERROR\n";
2663		}
2664		print $socket $EODATA;
2665		print $socket "OK profile modified\n";
2666	} else {
2667		print $socket $EODATA;
2668		print $socket "OK Nothing modified\n";
2669	}
2670
2671} # End of ModifyProfile
2672
2673sub ModifyProfileChannel {
2674	my $socket = shift;
2675	my $opts   = shift;
2676
2677	# Parameter checking
2678
2679	my $channel;
2680	my $ret = ChannelDecode($opts, \$channel);
2681	if ( $ret ne 'ok' ) {
2682		print $socket $EODATA;
2683		print $socket "ERR $ret\n";
2684		return;
2685	}
2686
2687	my ($profile, $profilegroup);
2688	$ret = ProfileDecode($opts, \$profile, \$profilegroup);
2689	if ( $ret ne 'ok' ) {
2690		print $socket $EODATA;
2691		print $socket "ERR $ret\n";
2692		return;
2693	}
2694
2695	$ret = VerifyProfile($profile, $profilegroup, 1);
2696	if ( $ret ne 'ok' ) {
2697		print $socket $EODATA;
2698		print $socket "ERR $ret\n";
2699		return;
2700	}
2701
2702	my $profilepath = ProfilePath($profile, $profilegroup);
2703
2704	# validate name
2705	$ret = NfSen::ValidFilename($channel);
2706	if ( $ret ne "ok" ) {
2707		print $socket $EODATA;
2708		print $socket "ERR checking channel name: $ret!\n";
2709		return;
2710	}
2711
2712	my %profileinfo = ReadProfile($profile, $profilegroup);
2713
2714	# we can handle the option: sign, colour, ( color ), order
2715	if ( exists $$opts{'color'} ) {
2716		$$opts{'colour'} = $$opts{'color'};
2717	}
2718
2719	my $changed = 0;
2720	if ( exists $$opts{'colour'} ) {
2721		if ( $$opts{'colour'} !~ /^#[0-9a-f]{6}$/i ) {
2722			print $socket $EODATA;
2723			print $socket "ERR Invalid value for option colour: '$$opts{'colour'}'. Use colour=#aabbcc\n";
2724			return;
2725		}
2726		$profileinfo{'channel'}{$channel}{'colour'} = $$opts{'colour'};
2727		$changed = 1;
2728	}
2729
2730	my $max_pos  = 0;
2731	my $max_neg  = 0;
2732	my $maxorder = 0;;
2733	foreach my $ch ( keys %{$profileinfo{'channel'}} ) {
2734		if ( $profileinfo{'channel'}{$ch}{'sign'} eq '+' ) {
2735			$max_pos++;
2736		}
2737		if ( $profileinfo{'channel'}{$ch}{'sign'} eq '-' ) {
2738			$max_neg++;
2739		}
2740	}
2741
2742	if ( exists $$opts{'sign'} ) {
2743print $socket ".sign: +:$max_pos, -:$max_neg\n";
2744
2745		if ( $$opts{'sign'} !~ /^[+\-]$/ ) {
2746			print $socket $EODATA;
2747			print $socket "ERR Invalid value for option sign: '$$opts{'sign'}'. Use sign=+ or sign=-\n";
2748			return;
2749		}
2750		# if new sign is different from old sign, remove channel from old list, reorder list and
2751		# put the channel at the tail of the new list, increasing the number of elements in new list
2752		if ( $profileinfo{'channel'}{$channel}{'sign'} ne $$opts{'sign'} ) {
2753
2754print $socket ".Change sign from $profileinfo{'channel'}{$channel}{'sign'} to $$opts{'sign'}\n";
2755
2756			# re-order channels, closing the gap
2757			foreach my $ch ( keys %{$profileinfo{'channel'}} ) {
2758				if ( $profileinfo{'channel'}{$ch}{'sign'} eq $profileinfo{'channel'}{$channel}{'sign'} &&
2759					 $profileinfo{'channel'}{$ch}{'order'} > $profileinfo{'channel'}{$channel}{'order'} ) {
2760					$profileinfo{'channel'}{$ch}{'order'}--;
2761				}
2762			}
2763			$profileinfo{'channel'}{$channel}{'sign'}  = $$opts{'sign'};
2764			$profileinfo{'channel'}{$channel}{'order'} = $$opts{'sign'} eq '+' ? ++$max_pos : ++$max_neg;
2765
2766			$changed = 1;
2767		} # else nothing to do
2768else {
2769print $socket ".Nothing to do\n";
2770}
2771	}
2772
2773	if ( exists $$opts{'order'} ) {
2774		$maxorder = $profileinfo{'channel'}{$channel}{'sign'} eq '+'  ? $max_pos : $max_neg;;
2775		if ( $$opts{'order'} !~ /^[0-9]+$/ || $$opts{'order'} > $maxorder) {
2776			print $socket $EODATA;
2777			print $socket "ERR Invalid value for option order: '$$opts{'order'}'. Use order=1..$maxorder\n";
2778			return;
2779		}
2780
2781		# in case of '0' put channel at the end of the list
2782		if ( $$opts{'order'} == 0 ) {
2783			$$opts{'order'} = $maxorder;
2784		}
2785
2786		if ( $profileinfo{'channel'}{$channel}{'order'} != $$opts{'order'} ) {
2787			my $old_order = $profileinfo{'channel'}{$channel}{'order'};
2788			my $new_order = $$opts{'order'};
2789
2790			# re-order the channels
2791			foreach my $ch ( keys %{$profileinfo{'channel'}} ) {
2792				# only channels with same sign are affected
2793				next unless $profileinfo{'channel'}{$ch}{'sign'} eq $profileinfo{'channel'}{$channel}{'sign'};
2794
2795				if ( $new_order > $old_order ) {
2796					next if $profileinfo{'channel'}{$ch}{'order'} > $new_order;
2797					next if $profileinfo{'channel'}{$ch}{'order'} < $old_order;
2798					$profileinfo{'channel'}{$ch}{'order'}--;
2799				} else {
2800					next if $profileinfo{'channel'}{$ch}{'order'} < $new_order;
2801					next if $profileinfo{'channel'}{$ch}{'order'} > $old_order;
2802					$profileinfo{'channel'}{$ch}{'order'}++;
2803				}
2804
2805			}
2806
2807			# set new order of channel
2808			$profileinfo{'channel'}{$channel}{'order'} = $$opts{'order'};
2809
2810			$changed = 1;
2811		} # else nothing to do
2812	}
2813
2814	if ( exists $$opts{'sourcelist'} ) {
2815		if ( $profile eq "live" ) {
2816			print $socket $EODATA;
2817			print $socket "ERR Can't modify sourcelist in profile 'live'.\n";
2818			return;
2819		}
2820		my %liveprofile = ReadProfile('live', '.');
2821		my $sourcelist = $$opts{'sourcelist'};
2822		while ( $sourcelist =~ s/\|\|/|/g ) {;}
2823		$sourcelist =~ s/^\|//;
2824		$sourcelist =~ s/\|$//;
2825		my @_list = split /\|/, $sourcelist;
2826		foreach my $source ( @_list ) {
2827			if ( !exists $liveprofile{'channel'}{$source} ) {
2828				print $socket $EODATA;
2829				print $socket "ERR source '$source' does not exist in profile live\n";
2830				return;
2831			}
2832		}
2833		$profileinfo{'channel'}{$channel}{'sourcelist'} = $sourcelist;
2834		$changed = 1;
2835	}
2836	if ( exists $$opts{'filter'} ) {
2837		if ( $profile eq "live" ) {
2838			print $socket $EODATA;
2839			print $socket "ERR Can't modify filter in profile 'live'.\n";
2840			return;
2841		}
2842		my $filter = $$opts{'filter'};
2843		# convert single line filter
2844		if ( ref $filter ne "ARRAY" ) {
2845			$filter = [ "$filter" ];
2846		}
2847		my %out = NfSen::VerifyFilter($filter);
2848		if ( $out{'exit'} > 0 ) {
2849			print $socket $EODATA;
2850			print $socket "ERR Filter syntax error: ", join(' ', $out{'nfdump'}), "\n";
2851			return;
2852		}
2853		my $filterfile = "$NfConf::PROFILESTATDIR/$profilepath/$channel-filter.txt";
2854print $socket ".filterfile: $filterfile\n";
2855		if ( !open(FILTER, ">$filterfile" ) ) {
2856			print $socket $EODATA;
2857			print $socket "ERR Can't open filter file: $!\n";
2858			return;
2859
2860		}
2861		print FILTER join "\n", @$filter;
2862		print FILTER "\n";
2863		close FILTER;
2864
2865		$changed = 1;
2866	}
2867
2868	if ( $changed ) {
2869		if ( !WriteProfile(\%profileinfo) ) {
2870			syslog('err', "Error writing profile '$profile': $Log::ERROR");
2871			print $socket $EODATA;
2872			print $socket "ERR writing profile '$profile': $Log::ERROR\n";
2873		}
2874
2875		if ( NfSenRRD::UpdateGraphs($profile, $profilegroup, $profileinfo{'tend'}, 1) ) {
2876			syslog('err', "Error graph update: $Log::ERROR");
2877			$profileinfo{'status'}	= 'FAILED';
2878		} else {
2879			$profileinfo{'status'}	= 'OK';
2880		}
2881
2882		print $socket $EODATA;
2883		print $socket "OK profile modified\n";
2884	} else {
2885		print $socket $EODATA;
2886		print $socket "OK Nothing modified\n";
2887	}
2888
2889} # End of ModifyProfileChannel
2890
2891sub RebuildProfile {
2892	my $socket	= shift;
2893	my $opts 	= shift;
2894
2895	my ($profile, $profilegroup);
2896	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2897	if ( $ret ne 'ok' ) {
2898		print $socket $EODATA;
2899		print $socket "ERR $ret\n";
2900		return;
2901	}
2902
2903	$ret = VerifyProfile($profile, $profilegroup, 1);
2904	if ( $ret ne 'ok' ) {
2905		print $socket $EODATA;
2906		print $socket "ERR $ret\n";
2907		return;
2908	}
2909
2910	my $profilepath = ProfilePath($profile, $profilegroup);
2911
2912	my %profileinfo = ReadProfile($profile, $profilegroup);
2913	if ( $profileinfo{'status'} eq 'empty' ) {
2914		print $socket $EODATA;
2915		print $socket "ERR Profile '$profile': $Log::ERROR\n";
2916		return;
2917	}
2918
2919	if ( $profileinfo{'status'} eq 'new' ) {
2920		print $socket $EODATA;
2921		print $socket "ERR Profile '$profile' is a new profile.\n";
2922		return;
2923	}
2924
2925	if ( ($profileinfo{'type'} & 4) > 0  ) {
2926		print $socket $EODATA;
2927		print $socket "ERR Profile '$profile' is a shadow profile.\n";
2928		return;
2929	}
2930
2931	%profileinfo = LockProfile($profile, $profilegroup);
2932	if (  $profileinfo{'status'} eq 'empty' ) {
2933		if ( $profileinfo{'locked'} == 1 ) {
2934			print $socket $EODATA;
2935			print $socket "ERR Profile '$profile' is already locked. Can't rebuild now\n";
2936		} else {
2937			print $socket $EODATA;
2938			print $socket "ERR Profile '$profile': $Log::ERROR\n";
2939		}
2940		return;
2941	}
2942
2943	my $RebuildGraphs = exists $$opts{'all'} ? 1 : 0;
2944
2945	syslog('info', "Start to rebuild profile '$profile'");
2946
2947	my $status = DoRebuild($socket, \%profileinfo, $profile, $profilegroup, $profilepath, 0, $RebuildGraphs);
2948
2949	if ( !WriteProfile(\%profileinfo) ) {
2950		syslog('err', "Error writing profile '$profile': $Log::ERROR");
2951		print $socket $EODATA;
2952		print $socket "ERR writing profile '$profile': $Log::ERROR\n";
2953		return;
2954	}
2955
2956	print $socket $EODATA;
2957	if ( $status ne 'ok' ) {
2958		print $socket "ERR $status\n";
2959
2960	} else {
2961		print $socket "OK profile rebuilded\n";
2962	}
2963
2964} # End of RebuildProfile
2965
2966sub ExpireProfile {
2967	my $socket	= shift;
2968	my $opts 	= shift;
2969
2970	my ($profile, $profilegroup);
2971	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
2972	if ( $ret ne 'ok' ) {
2973		print $socket $EODATA;
2974		print $socket "ERR $ret\n";
2975		return;
2976	}
2977
2978	$ret = VerifyProfile($profile, $profilegroup, 1);
2979	if ( $ret ne 'ok' ) {
2980		print $socket $EODATA;
2981		print $socket "ERR $ret\n";
2982		return;
2983	}
2984
2985	my %profileinfo = LockProfile($profile, $profilegroup);
2986	# Make sure profile is not empty - means it exists and is not locked
2987	if ( $profileinfo{'status'} eq 'empty' ) {
2988		if ( $profileinfo{'locked'} == 1 ) {
2989			print $socket $EODATA;
2990			print $socket "ERR Profile is locked!\n";
2991			syslog('info', "Profile is locked. Can't expire");
2992			return;
2993		}
2994
2995		# it's an error reading this profile
2996		if ( defined $Log::ERROR ) {
2997			print $socket $EODATA;
2998			print $socket "ERR $Log::ERROR\n";
2999			syslog('err', "Error $profile: $Log::ERROR");
3000			return;
3001		}
3002	}
3003
3004	my $is_shadow = ($profileinfo{'type'} & 4) > 0 ;
3005	# history profiles do not want to be expired
3006	if ( ($profileinfo{'type'} & 3) == 1 || $is_shadow ) {
3007		$profileinfo{'locked'} = 0;
3008		if ( !WriteProfile(\%profileinfo) ) {
3009			syslog('err', "Error writing profile '$profile': $Log::ERROR");
3010		}
3011		syslog('info', "Can't expire history or shadow profile");
3012		print $socket $EODATA;
3013		print $socket "ERR Can't expire history profile\n";
3014		return;
3015	}
3016
3017	syslog('info', "Force expire for profile '$profile'");
3018
3019
3020	my $tstart			= $profileinfo{'tstart'};
3021	my $profilesize 	= $profileinfo{'size'};
3022
3023	my $args = "-Y -p -e $NfConf::PROFILEDATADIR/$profile -w $NfConf::low_water ";
3024	$args .= "-s $profileinfo{'maxsize'} " if $profileinfo{'maxsize'};
3025	my $_t = 3600*$profileinfo{'expire'};
3026	$args .= "-t $_t "  if defined $profileinfo{'expire'};
3027
3028	if ( open NFEXPIRE, "$NfConf::PREFIX/nfexpire $args 2>&1 |" ) {
3029		local $SIG{PIPE} = sub { syslog('err', "Pipe broke for nfexpire"); };
3030		while ( <NFEXPIRE> ) {
3031			chomp;
3032			if ( /^Stat|(\d+)|(\d+)/ ) {
3033				$profilesize = $1;
3034				$tstart		 = $2;
3035			}
3036			syslog('debug', "nfexpire: $_");
3037		}
3038		close NFEXPIRE;	# SIGCHLD sets $child_exit
3039	}
3040
3041	if ( $main::child_exit != 0 ) {
3042		syslog('err', "nfexpire failed: $!\n");
3043		syslog('debug', "System was: $NfConf::PREFIX/nfexpire $args");
3044		next;
3045	}
3046
3047	$profileinfo{'size'}	= $profilesize;
3048	$profileinfo{'tstart'} 	= $tstart;
3049
3050	$profileinfo{'locked'} = 0;
3051	if ( !WriteProfile(\%profileinfo) ) {
3052		syslog('err', "Error writing profile '$profile': $Log::ERROR");
3053	}
3054
3055	syslog('info', "End force expire");
3056
3057	print $socket $EODATA;
3058	print $socket "OK profile expired\n";
3059
3060} # End of ExpireProfile
3061
3062
3063sub GetChannelfilter {
3064	my $socket 	= shift;
3065	my $opts 	= shift;
3066
3067	my ($profile, $profilegroup);
3068	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3069	if ( $ret ne 'ok' ) {
3070		print $socket $EODATA;
3071		print $socket "ERR $ret\n";
3072		return;
3073	}
3074
3075	$ret = VerifyProfile($profile, $profilegroup, 1);
3076	if ( $ret ne 'ok' ) {
3077		print $socket $EODATA;
3078		print $socket "ERR $ret\n";
3079		return;
3080	}
3081
3082	my $profilepath = ProfilePath($profile, $profilegroup);
3083
3084	if ( !exists $$opts{'channel'} ) {
3085		print $socket $EODATA;
3086		print $socket "ERR profile and channel required.\n";
3087		return;
3088	}
3089	my $channel	= $$opts{'channel'};
3090
3091	my $channeldir = "$NfConf::PROFILEDATADIR/$profilepath/$channel";
3092	if ( ! -d $channeldir ) {
3093		print $socket $EODATA;
3094		print $socket "ERR no such channel\n";
3095		return;
3096	}
3097
3098	if ( $profile eq 'live' ) {
3099		print $socket "_filter=any\n";
3100		print $socket $EODATA;
3101		print $socket "OK Command completed\n";
3102	} else {
3103		my $filterfile = "$NfConf::PROFILESTATDIR/$profilepath/$channel-filter.txt";
3104
3105		if ( open(FILTER, "$filterfile" ) ) {
3106			while ( <FILTER> ) {
3107				chomp;
3108				print $socket "_filter=$_\n";
3109			}
3110			print $socket $EODATA;
3111			print $socket "OK Command completed\n";
3112		} else {
3113			print $socket $EODATA;
3114			print $socket "ERR Error reading filter - $!\n";
3115		}
3116		close FILTER;
3117	}
3118
3119} # End of GetChannelfilter
3120
3121sub GetChannelstat {
3122	my $socket 	= shift;
3123	my $opts 	= shift;
3124
3125	my ($profile, $profilegroup);
3126	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3127	if ( $ret ne 'ok' ) {
3128		print $socket $EODATA;
3129		print $socket "ERR $ret\n";
3130		return;
3131	}
3132
3133	$ret = VerifyProfile($profile, $profilegroup, 1);
3134	if ( $ret ne 'ok' ) {
3135		print $socket $EODATA;
3136		print $socket "ERR $ret\n";
3137		return;
3138	}
3139	my $profilepath = ProfilePath($profile, $profilegroup);
3140
3141	if ( !exists $$opts{'channel'} ) {
3142		print $socket $EODATA;
3143		print $socket "ERR profile and channel required.\n";
3144		return;
3145	}
3146
3147	my %profileinfo = ReadProfile($profile, $profilegroup);
3148	my $is_shadow = ($profileinfo{'type'} & 4) > 0 ;
3149	if ( $is_shadow ) {
3150		print $socket $EODATA;
3151		print $socket "ERR Profile is a shadow profile.\n";
3152		return;
3153	}
3154	my %channelinfo = ReadChannelStat($profilepath, $$opts{'channel'});
3155	if ( defined $Log::ERROR ) {
3156		print $socket $EODATA;
3157		print $socket "ERR $Log::ERROR\n";
3158		return;
3159	}
3160	foreach my $key ( keys %channelinfo ) {
3161		print "$key=$channelinfo{$key}\n";
3162	}
3163	print $socket $EODATA;
3164	print $socket "OK Command completed\n";
3165
3166} # End of GetChannelstat
3167
3168sub SendPicture {
3169	my $socket 	= shift;
3170	my $opts 	= shift;
3171
3172	my ($profile, $profilegroup);
3173	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3174	if ( $ret ne 'ok' ) {
3175		print $socket $EODATA;
3176		print $socket "ERR $ret\n";
3177		return;
3178	}
3179
3180	$ret = VerifyProfile($profile, $profilegroup, 1);
3181	if ( $ret ne 'ok' ) {
3182		print $socket $EODATA;
3183		print $socket "ERR $ret\n";
3184		return;
3185	}
3186	my $profilepath = ProfilePath($profile, $profilegroup);
3187
3188	if ( !exists $$opts{'picture'} ) {
3189		print $socket $EODATA;
3190		print $socket "ERR picture required.\n";
3191		return;
3192	}
3193	my $picture = $$opts{'picture'};
3194	sysopen(PIC, "$NfConf::PROFILESTATDIR/$profilepath/$picture", O_RDONLY) or
3195		print $socket $EODATA,
3196		print $socket "ERR Can't open picture file: $!",
3197		return;
3198
3199	my $buf;
3200	while ( sysread(PIC, $buf, 1024)) {
3201		syswrite($socket, $buf, length($buf));
3202	}
3203	close PIC;
3204
3205} # End of SendPicture
3206
3207sub GetDetailsGraph {
3208	my $socket 	= shift;
3209	my $opts 	= shift;
3210
3211	my ($profile, $profilegroup);
3212	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3213	if ( $ret ne 'ok' ) {
3214		print $socket $EODATA;
3215		print $socket "ERR $ret\n";
3216		return;
3217	}
3218
3219	$ret = VerifyProfile($profile, $profilegroup, 1);
3220	if ( $ret ne 'ok' ) {
3221		print $socket $EODATA;
3222		print $socket "ERR $ret\n";
3223		return;
3224	}
3225
3226	my %profileinfo = ReadProfile($profile, $profilegroup);
3227
3228	if ( !exists $$opts{'arg'} ) {
3229		print $socket $EODATA;
3230		print $socket "ERR details argument list required.\n";
3231		return;
3232	}
3233	my $detailargs = $$opts{'arg'};
3234	$ret = NfSenRRD::GenDetailsGraph(\%profileinfo, $detailargs);
3235	if ( $ret ne "ok" ) {
3236		syslog('err', "Error generating details graph: $ret");
3237	}
3238
3239} # End of GetDetailsGraph
3240
3241sub SearchPeak {
3242	my $socket 	= shift;
3243	my $opts 	= shift;
3244
3245	my ($profile, $profilegroup);
3246	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3247	if ( $ret ne 'ok' ) {
3248		print $socket $EODATA;
3249		print $socket "ERR $ret\n";
3250		return;
3251	}
3252
3253	if ( !exists $$opts{'channellist'} ) {
3254		print $socket $EODATA;
3255		print $socket "ERR channel list required.\n";
3256		return;
3257	}
3258	my $channellist = $$opts{'channellist'};
3259
3260	my %profileinfo = ReadProfile($profile, $profilegroup);
3261	my @AllChannels = split /\!/, $channellist;
3262	foreach my $channel ( @AllChannels ) {
3263		if ( !exists $profileinfo{'channel'}{$channel} ) {
3264			print $socket $EODATA;
3265			print $socket "ERR channel '$channel' does not exists in profile '$profilegroup/$profile'\n";
3266			return;
3267		}
3268	}
3269
3270	if ( !exists $$opts{'tinit'} ) {
3271		print $socket $EODATA;
3272		print $socket "ERR time slot required.\n";
3273		return;
3274	}
3275	my $tinit = $$opts{'tinit'};
3276	if ( !NfSen::ValidISO($tinit) ) {
3277		print $socket $EODATA;
3278		print $socket "ERR Unparsable time format '$tinit'!\n";
3279		return;
3280	}
3281
3282	if ( !exists $$opts{'type'} ) {
3283		print $socket $EODATA;
3284		print $socket "ERR type of graph required.\n";
3285		return;
3286	}
3287	my $type = $$opts{'type'};
3288	my ($t, $p ) = split /_/, $type;
3289	if ( not defined $t ) {
3290		print $socket $EODATA;
3291		print $socket "ERR type of graph required.\n";
3292		return;
3293	}
3294
3295	my %DisplayProto = ( 'any' => 1, 'TCP' => 1, 'UDP' => 1, 'ICMP' => 1, 'other' => 1 );
3296	my %DisplayType	 = ( 'flows' => 1, 'packets' => 1, 'traffic' => 1);
3297	if ( !exists $DisplayProto{$p} || !exists $DisplayType{$t} ) {
3298		print $socket $EODATA;
3299		print $socket "ERR type '$type' unknown.\n";
3300		return;
3301	}
3302	$type =~ s/_any//;
3303
3304	my ( $tmax, $err) = GetPeakValues(\%profileinfo, lc $type, $channellist, NfSen::ISO2UNIX($tinit));
3305
3306	if ( defined $err ) {
3307		print $socket $EODATA;
3308		print $socket "ERR $err\n";
3309	} else {
3310		$tmax=NfSen::UNIX2ISO($tmax);
3311		print $socket "tpeek=$tmax\n";
3312		print $socket $EODATA;
3313		print $socket "OK command completed\n";
3314	}
3315	return;
3316
3317} # End of SearchPeak
3318
3319sub CompileFileArg {
3320	my $opts 	  = shift;
3321	my $argref	  = shift;
3322	my $filterref = shift;
3323
3324	my ($profile, $profilegroup);
3325	my $ret = ProfileDecode($opts, \$profile, \$profilegroup);
3326	if ( $ret ne 'ok' ) {
3327		return "$ret";
3328	}
3329
3330	$ret = VerifyProfile($profile, $profilegroup, 1);
3331	if ( $ret ne 'ok' ) {
3332		return "$ret";
3333	}
3334
3335	my %profileinfo = ReadProfile($profile, $profilegroup);
3336
3337	if ( !exists $$opts{'srcselector'} ) {
3338		return "srcselector list required";
3339	}
3340	my $srcselector = $$opts{'srcselector'};
3341
3342	foreach my $channel ( split ':', $srcselector ) {
3343		if ( !exists $profileinfo{'channel'}{$channel} ) {
3344			return "Requested channel '$channel' does not exists in '$profilegroup/$profile'";
3345		}
3346	}
3347
3348	if ( !exists $$opts{'type'} ) {
3349		return "profile type required\n";
3350	}
3351	my $type = $$opts{'type'};
3352
3353	my $profilepath = ProfilePath($profile, $profilegroup);
3354	if ( $type eq 'real' ) {
3355		$$argref = "-M $NfConf::PROFILEDATADIR/$profilepath/$srcselector ";
3356		return "ok";
3357	}
3358
3359	# flow processing for shadow profiles is more complicated:
3360	# we need first to rebuild the channel filter for each channel selected and then apply the requested filter
3361	if ( $type eq 'shadow' ) {
3362		# compile directory list
3363		my %Mdir;
3364		foreach my $channel ( split ':', $srcselector ) {
3365			my %identlist = ();
3366			foreach my $channel_source ( split /\|/, $profileinfo{'channel'}{$channel}{'sourcelist'} ) {
3367				$Mdir{"$channel_source"} = 1;
3368				$identlist{"$channel_source"} = 1;
3369			}
3370			push @$filterref, "( ident " . join(' or ident ', keys %identlist) . ") and (";
3371			my $filterfile = "$NfConf::PROFILESTATDIR/$profilepath/$channel-filter.txt";
3372
3373			open(FILTER, "$filterfile" ) or
3374				return "Can't open filter file '$filterfile': $!";
3375			my @_tmp = <FILTER>;
3376			close FILTER;
3377			chomp(@_tmp);
3378			push @$filterref, @_tmp;
3379
3380			push @$filterref, ")";
3381			push @$filterref, "or";
3382		}
3383		# remove last 'or'
3384		pop @$filterref;
3385		# shadow profiles will access live data
3386		$$argref = "-M $NfConf::PROFILEDATADIR/live/" . join (':', keys %Mdir);
3387
3388		return "ok";
3389	}
3390
3391	return "unknown type $type.\n";
3392
3393} # End of CompileFileArg
3394
3395sub CancelBuilds {
3396
3397	foreach my $profilegroup ( ProfileGroups() ) {
3398		my @AllProfiles;
3399		opendir(PROFILEDIR, "$NfConf::PROFILESTATDIR/$profilegroup" ) or
3400			$Log::ERROR = "Can't open profile group directory: $!",
3401			return @AllProfiles;
3402
3403		@AllProfiles = grep {  -f "$NfConf::PROFILESTATDIR/$profilegroup/$_/.BUILDING" }
3404							readdir(PROFILEDIR);
3405
3406		closedir PROFILEDIR;
3407
3408		# delete each profile
3409		foreach my $profile ( @AllProfiles ) {
3410			my $profilepath = ProfilePath($profile, $profilegroup);
3411			syslog('err', "Cancel building profile '$profile' in group '$profilegroup' ");
3412			open CANCELFLAG, ">$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
3413			close CANCELFLAG;
3414			my $i = 0;
3415			while ( ($i < 60) && -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
3416				sleep(1);
3417				$i++;
3418			}
3419			if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
3420				syslog('err', "Cancel building profile '$profile' in group '$profilegroup' did not succeed! Abort waiting!");
3421			}
3422		}
3423	}
3424
3425} # End of CancelBuilds
3426
3427sub CheckProfiles {
3428
3429	foreach my $profilegroup ( ProfileGroups() ) {
3430		my @AllProfiles = ProfileList($profilegroup);
3431		foreach my $profile ( @AllProfiles ) {
3432			my $profilepath = ProfilePath($profile, $profilegroup);
3433			my %profileinfo = ReadProfile($profile, $profilegroup);
3434			if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.BUILDING" ) {
3435				syslog('err', "Clean-up debris profile '$profile' in group '$profilegroup' ");
3436				unlink "$NfConf::PROFILESTATDIR/$profilepath/.BUILDING";
3437				$profileinfo{'tend'} = $profileinfo{'updated'};
3438				if ( ($profileinfo{'type'} & 4) > 0 ) { # is shadow
3439					$profileinfo{'type'}  = 1;
3440					$profileinfo{'type'} += 4;
3441				} else {
3442					$profileinfo{'type'} = 1;
3443				}
3444				my $status = DoRebuild(\%profileinfo, $profile, $profilegroup, $profilepath, 0, 0);
3445				syslog('err', "Rebuilded profile '$profile' in group '$profilegroup': $status ");
3446			}
3447			if ( -f "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED" ) {
3448				syslog('err', "Clean-up debris profile '$profile' in group '$profilegroup' ");
3449				unlink "$NfConf::PROFILESTATDIR/$profilepath/.CANCELED";
3450				if ( ($profileinfo{'type'} & 4) > 0 ) { # is shadow
3451					$profileinfo{'type'}  = 1;
3452					$profileinfo{'type'} += 4;
3453				} else {
3454					$profileinfo{'type'} = 1;
3455				}
3456				my $status = DoRebuild(\%profileinfo, $profile, $profilegroup, $profilepath, 0, 0);
3457				syslog('err', "Rebuilded profile '$profile' in group '$profilegroup': $status ");
3458			}
3459			if ( $profileinfo{'locked'} ) {
3460				syslog('err', "Clean-up debris profile '$profile' in group '$profilegroup' ");
3461				$profileinfo{'locked'} = 0;
3462			}
3463			if ( !WriteProfile(\%profileinfo) ) {
3464				syslog('err', "Error writing profile '$profile' in group '$profilegroup' ");
3465			}
3466		}
3467	}
3468
3469
3470
3471} # End of CheckProfiles
3472
34731;
3474
3475
3476