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