init
This commit is contained in:
746
local/cgi-bin/Golem/dblib.pl
Normal file
746
local/cgi-bin/Golem/dblib.pl
Normal file
@@ -0,0 +1,746 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Generic database routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
# courtesy of LiveJournal.org
|
||||
sub disconnect_dbs {
|
||||
foreach my $h (($Golem::DB, $Golem::PlanerDB, $Golem::CalendarDB, $Golem::OtrsDB, $Golem::SwerrsDB)) {
|
||||
if ($h) {
|
||||
$h->disconnect();
|
||||
$h = undef;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR localtime() . " [$$]: closed db connections\n" if $ENV{'GOLEM_DEBUG'};
|
||||
}
|
||||
|
||||
# build DSN connection string based on database info hashref,
|
||||
# courtesy of livejournal.org
|
||||
#
|
||||
# $DBINFO = {
|
||||
# 'master' => {
|
||||
# 'dbname' => "golem_kohts",
|
||||
# 'host' => "localhost",
|
||||
# 'port' => 3306,
|
||||
# 'user' => "root",
|
||||
# 'pass' => "",
|
||||
# 'sock' => "",
|
||||
# 'encoding' => "utf8",
|
||||
# },
|
||||
# };
|
||||
#
|
||||
sub make_dbh_fdsn {
|
||||
my ($db) = @_;
|
||||
|
||||
my $fdsn = "DBI:mysql";
|
||||
$fdsn .= ":$db->{'dbname'}";
|
||||
$fdsn .= ";host=$db->{'host'}" if $db->{'host'};
|
||||
$fdsn .= ";port=$db->{'port'}" if $db->{'port'};
|
||||
$fdsn .= ";mysql_socket=$db->{'sock'}" if $db->{'sock'};
|
||||
$fdsn .= "|$db->{'user'}|$db->{'pass'}";
|
||||
|
||||
return $fdsn;
|
||||
}
|
||||
|
||||
# test if connection is still available
|
||||
# (should check for replication, etc. here)
|
||||
#
|
||||
sub connection_bad {
|
||||
my ($dbh, $try) = @_;
|
||||
|
||||
return 1 unless $dbh;
|
||||
|
||||
my $ss = eval {
|
||||
#
|
||||
# $dbh->selectrow_hashref("SHOW SLAVE STATUS");
|
||||
#
|
||||
# on a real slave
|
||||
#
|
||||
# $ss = {
|
||||
# 'Skip_counter' => '0',
|
||||
# 'Master_Log_File' => 'ararita-bin.882',
|
||||
# 'Connect_retry' => '60',
|
||||
# 'Master_Host' => 'ararita.lenin.ru',
|
||||
# 'Relay_Master_Log_File' => 'ararita-bin.882',
|
||||
# 'Relay_Log_File' => 'laylah-relay-bin.323',
|
||||
# 'Slave_IO_Running' => 'Yes',
|
||||
# 'Slave_SQL_Running' => 'Yes',
|
||||
# 'Master_Port' => '3306',
|
||||
# 'Exec_master_log_pos' => '17720151',
|
||||
# 'Relay_log_space' => '19098333',
|
||||
# 'Relay_Log_Pos' => '19098333',
|
||||
# 'Last_errno' => '0',
|
||||
# 'Last_error' => '',
|
||||
# 'Replicate_do_db' => 'prod_livejournal,prod_livejournal',
|
||||
# 'Read_Master_Log_Pos' => '17720151',
|
||||
# 'Master_User' => 'replication',
|
||||
# 'Replicate_ignore_db' => ''
|
||||
# };
|
||||
|
||||
$dbh->selectrow_hashref("select name from _dbi");
|
||||
};
|
||||
|
||||
if ($dbh->err && $dbh->err != 1227) {
|
||||
print STDERR localtime() . " [$$]: " . $dbh->errstr . "\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($ss && $ss->{'name'} ne '??') {
|
||||
return 0;
|
||||
}
|
||||
elsif ($ss && $ss->{'name'} eq '??') {
|
||||
print STDERR localtime() . " [$$]: DBI returned garbage: $ss->{'name'}\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
elsif (!$ss) {
|
||||
print STDERR localtime() . " [$$]: DBI returned nothing\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# LJR modification; redefined in cgi-bin/Golem.pmGolem.pm
|
||||
# so it works correctly with original LJ code
|
||||
#
|
||||
sub golem_get_db {
|
||||
my ($params, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
$params = {} unless $params;
|
||||
|
||||
if ($Golem::DB) {
|
||||
if (! connection_bad($Golem::DB)) {
|
||||
return $Golem::DB;
|
||||
}
|
||||
else {
|
||||
print STDERR localtime() . " [$$]: new connection: was bad\n" if $ENV{'GOLEM_DEBUG'};
|
||||
$Golem::DB->disconnect;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print STDERR localtime() . " [$$]: new connection: had none\n" if $ENV{'GOLEM_DEBUG'};
|
||||
}
|
||||
undef $Golem::DB;
|
||||
|
||||
# DB connection defaults (unless programmer specified them)
|
||||
#
|
||||
$params->{'RaiseError'} = 0 unless defined($params->{'RaiseError'});
|
||||
$params->{'PrintError'} = 1 unless defined($params->{'PrintError'});
|
||||
$params->{'AutoCommit'} = 1 unless defined($params->{'AutoCommit'});
|
||||
|
||||
Golem::die("No Golem::DBINFO master defined")
|
||||
unless $Golem::DBINFO->{'master'};
|
||||
|
||||
my $dbinfo = $Golem::DBINFO->{'master'};
|
||||
my $fdsn = make_dbh_fdsn($dbinfo);
|
||||
|
||||
$Golem::DB = DBI->connect($fdsn, $dbinfo->{'user'}, $dbinfo->{'pass'}, $params);
|
||||
while (!$Golem::DB && $opts->{'retry_forever'}) {
|
||||
Golem::do_log("database not available, retrying", {"stderr" => 1});
|
||||
sleep 1;
|
||||
$Golem::DB = DBI->connect($fdsn, $dbinfo->{'user'}, $dbinfo->{'pass'}, $params);
|
||||
}
|
||||
Golem::die("Unable to connect to database: " . DBI->errstr)
|
||||
unless $Golem::DB;
|
||||
|
||||
$Golem::DB->do("SET NAMES " . $dbinfo->{'encoding'})
|
||||
if $dbinfo->{'encoding'};
|
||||
|
||||
if (connection_bad($Golem::DB)) {
|
||||
print STDERR "got fresh new bad handle, retrying\n" if $ENV{'GOLEM_DEBUG'};
|
||||
$Golem::DB = undef;
|
||||
$Golem::DB = Golem::get_db();
|
||||
}
|
||||
|
||||
$Golem::default_dc_obj = Golem::get_dc($Golem::default_dc);
|
||||
return $Golem::DB;
|
||||
}
|
||||
|
||||
sub get_planer_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::PlanerDB if $Golem::PlanerDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError => 1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::PlanerDB = DBI->connect("DBI:Sybase:server=argo3.yandex.ru;database=planer;",
|
||||
"helpdesk", "gkfyshfcnfvfys123", $params);
|
||||
|
||||
return $Golem::PlanerDB;
|
||||
}
|
||||
|
||||
sub get_calendar_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::CalendarDB if $Golem::CalendarDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError =>1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::CalendarDB = DBI->connect("DBI:Sybase:server=argo3.yandex.ru;database=momdb;",
|
||||
"staffreader", "cegthgfhjkm678", $params);
|
||||
|
||||
return $Golem::CalendarDB;
|
||||
}
|
||||
|
||||
sub get_otrs_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::OtrsDB if $Golem::OtrsDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError =>1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::OtrsDB = DBI->connect("DBI:mysql:database=otrs_utf8:host=casa.yandex.ru:port=3306",
|
||||
"userorder", "xuo9Bahf", $params);
|
||||
|
||||
return $Golem::OtrsDB;
|
||||
}
|
||||
|
||||
sub get_swerrs_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::SwerrsDB if $Golem::SwerrsDB;
|
||||
|
||||
$params = { RaiseError => 0, PrintError => 1, AutoCommit => 1 }
|
||||
unless $params;
|
||||
|
||||
$Golem::SwerrsDB = DBI->connect("DBI:mysql:racktables:localhost:3306", "swerrs", "V7Hl}O]Usr", $params);
|
||||
|
||||
return $Golem::SwerrsDB;
|
||||
}
|
||||
|
||||
|
||||
sub sth_bind_array {
|
||||
my ($sth, $bound_values) = @_;
|
||||
|
||||
my $i = 0;
|
||||
foreach my $b (@{$bound_values}) {
|
||||
$i++;
|
||||
|
||||
Golem::die("error binding params")
|
||||
unless $sth->bind_param($i, $b) ;
|
||||
}
|
||||
}
|
||||
|
||||
# courtesy of LiveJournal.org
|
||||
# see also: http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id
|
||||
#
|
||||
sub alloc_global_counter {
|
||||
my ($tag, $recurse) = @_;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $newmax;
|
||||
|
||||
# in case name `counter` is already occupied
|
||||
# by some user table
|
||||
my $counter_prefix = "";
|
||||
$counter_prefix = $Golem::counter_prefix
|
||||
if defined($Golem::counter_prefix);
|
||||
|
||||
my $rs = $dbh->do("UPDATE ${counter_prefix}counter SET max=LAST_INSERT_ID(max+1) WHERE tag=?", undef, $tag);
|
||||
if ($rs > 0) {
|
||||
$newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
return $newmax;
|
||||
}
|
||||
|
||||
return undef if $recurse;
|
||||
|
||||
# no prior counter rows - initialize one.
|
||||
|
||||
# if this is a table then trying default id column
|
||||
if ($Golem::SCHEMA_CACHE->{'tables'}->{$tag}) {
|
||||
$newmax = $dbh->selectrow_array("SELECT MAX(id) FROM `$tag`");
|
||||
}
|
||||
else {
|
||||
Golem::die("alloc_global_counter: unknown tag [$tag], unable to get max value.");
|
||||
}
|
||||
|
||||
$newmax += 0;
|
||||
|
||||
$dbh->do("INSERT IGNORE INTO ${counter_prefix}counter (tag, max) VALUES (?,?)",
|
||||
undef, $tag, $newmax) || return undef;
|
||||
|
||||
return Golem::alloc_global_counter($tag, 1);
|
||||
}
|
||||
|
||||
# get schema table definition,
|
||||
# prepare in-memory table structure
|
||||
#
|
||||
sub get_schema_table {
|
||||
my ($table_name, $opts) = @_;
|
||||
|
||||
return $Golem::SCHEMA_CACHE->{'tables'}->{$table_name}
|
||||
if $Golem::SCHEMA_CACHE->{'tables'}->{$table_name} &&
|
||||
!$opts->{'force'};
|
||||
|
||||
delete($Golem::SCHEMA_CACHE->{'tables'}->{$table_name})
|
||||
if $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
|
||||
$Golem::SCHEMA_CACHE->{'tables'}->{$table_name}->{'fields'} = {};
|
||||
my $t = $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
Golem::debug_sql("describe `$table_name`");
|
||||
my $sth = $dbh->prepare("describe `$table_name`");
|
||||
$sth->execute();
|
||||
Golem::die("Error describing table [$table_name]: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
my $select_all_sql = "";
|
||||
while (my $r = $sth->fetchrow_hashref) {
|
||||
my $field_name = $r->{'Field'};
|
||||
|
||||
$t->{'fields'}->{$field_name} = $r;
|
||||
|
||||
if ($r->{'Type'} =~ /^enum\((.+)\)/o) {
|
||||
my $enums = $1;
|
||||
foreach my $etype (split(/,/o, $enums)) {
|
||||
$etype =~ s/'//go;
|
||||
$t->{'fields'}->{$field_name}->{'enum'}->{$etype} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($r->{'Type'} eq 'timestamp') {
|
||||
$select_all_sql .= "UNIX_TIMESTAMP(`$field_name`) as `$field_name`, ";
|
||||
}
|
||||
else {
|
||||
$select_all_sql .= "`$field_name`, ";
|
||||
}
|
||||
|
||||
if ($r->{'Key'} eq 'PRI') {
|
||||
$t->{'primary_key'}->{$field_name} = 1;
|
||||
}
|
||||
}
|
||||
chop($select_all_sql);
|
||||
chop($select_all_sql);
|
||||
|
||||
$Golem::SCHEMA_CACHE->{'tables'}->{$table_name}->{'select_all_sql'} = $select_all_sql;
|
||||
|
||||
return $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
}
|
||||
|
||||
# function tells whether field is data field or some special field
|
||||
# like host.id (incremented with alloc_global_counter) or
|
||||
# like host.last_updated (automatically updated when record is updated)
|
||||
# maybe we should filter them by name instead of using db structure hints?
|
||||
sub is_data_field {
|
||||
my ($table_name, $field_name, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
|
||||
if ($table_fields->{$field_name}) {
|
||||
if ($table_fields->{$field_name}->{'Default'} &&
|
||||
$table_fields->{$field_name}->{'Default'} eq 'CURRENT_TIMESTAMP' &&
|
||||
!$opts->{'ignore_default_current_timestamp'} ) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
# if ($table_fields->{$field_name}->{'Key'} &&
|
||||
# $table_fields->{$field_name}->{'Key'} eq 'PRI') {
|
||||
#
|
||||
# return 0;
|
||||
# }
|
||||
|
||||
# we have to distinguish between host.id and host_rackmap.host;
|
||||
# both are PRIMARY keys, but
|
||||
# 1) we shouldn't ever update host.id
|
||||
# 2) we have to update host_rackmap.host with host.id
|
||||
# when creating record corresponding to host record
|
||||
if ($field_name eq "id" && !$opts->{'manual_id_management'}) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub __insert {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __insert expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __insert expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
$dbh->{'PrintError'} = $opts->{'PrintError'}
|
||||
if defined($opts->{'PrintError'});
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
|
||||
# continue only if there's data for the table
|
||||
# or if there's a flag saying we should create
|
||||
# empty record with defaults
|
||||
my $have_data_for_the_table = 0;
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
if (Golem::is_data_field($table_name, $o, $opts)) {
|
||||
$have_data_for_the_table = 1;
|
||||
}
|
||||
}
|
||||
unless ($have_data_for_the_table || $opts->{'create_empty_record'}) {
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
my @record_fields;
|
||||
my @record_values;
|
||||
|
||||
foreach my $o (keys %{$record_hashref}) {
|
||||
# $record_hashref might contain more fields than present in database.
|
||||
# we only choose those which are in db
|
||||
|
||||
if ($table_fields->{$o} && Golem::is_data_field($table_name, $o, $opts)) {
|
||||
|
||||
# enum validation
|
||||
if ($table_fields->{$o}->{'enum'}) {
|
||||
Golem::die("Enum [$table_name.$o] value is not specified and doesn't have default value")
|
||||
if !defined($record_hashref->{$o}) && $table_fields->{$o}->{'Default'} eq '';
|
||||
|
||||
Golem::die("Enum [$table_name.$o] can't be [$record_hashref->{$o}]")
|
||||
if $record_hashref->{$o} && !$table_fields->{$o}->{'enum'}->{$record_hashref->{$o}};
|
||||
|
||||
# if they passed empty value for enum
|
||||
# and there's some default -- silently
|
||||
# decide to use it
|
||||
unless ($record_hashref->{$o}) {
|
||||
delete($record_hashref->{$o});
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
push @record_fields, $o;
|
||||
push @record_values, $record_hashref->{$o};
|
||||
}
|
||||
}
|
||||
|
||||
if ($table_fields->{"id"} && !$opts->{'manual_id_management'}) {
|
||||
if ($record_hashref->{"id"}) {
|
||||
Golem::die("Severe database structure or programmer error: __insert got id [$record_hashref->{'id'}]
|
||||
when creating record for table [$table_name]; won't overwrite.\n");
|
||||
}
|
||||
|
||||
$record_hashref->{"id"} = Golem::alloc_global_counter($table_name);
|
||||
|
||||
# check that id is not taken and
|
||||
# die with severe error otherwise
|
||||
#
|
||||
|
||||
my $t_id = $dbh->selectrow_array("select id from `$table_name` where id = ?",
|
||||
undef, $record_hashref->{"id"});
|
||||
if ($t_id && $t_id eq $record_hashref->{"id"}) {
|
||||
Golem::die("Severe database error: __insert got [$t_id] for table [$table_name] " .
|
||||
"from alloc_global_counter which already exists!\n" .
|
||||
"Probable somebody is populating [$table_name] without Golem::__insert()\n");
|
||||
}
|
||||
|
||||
push @record_fields, "id";
|
||||
push @record_values, $record_hashref->{"id"};
|
||||
}
|
||||
|
||||
my $sql;
|
||||
my @bound_values;
|
||||
|
||||
$sql = "INSERT INTO `$table_name` ( ";
|
||||
foreach my $o (@record_fields) {
|
||||
$sql = $sql . " `$o`,";
|
||||
}
|
||||
chop($sql);
|
||||
|
||||
$sql .= " ) VALUES ( ";
|
||||
|
||||
my $i = 0;
|
||||
foreach my $o (@record_values) {
|
||||
|
||||
# we represent timestamp datatype as unixtime (http://en.wikipedia.org/wiki/Unix_time)
|
||||
# doing all the conversions almost invisible to the end user
|
||||
#
|
||||
# if the value being written is 0 then we're not using FROM_UNIXTIME(value)
|
||||
# (which generates warnings) just value
|
||||
#
|
||||
if ($table_fields->{$record_fields[$i]}->{'Type'} eq 'timestamp' && $o && $o != 0) {
|
||||
Golem::die("Programmer error: __insert got hashref with invalid data for $table_name.$record_fields[$i] (should be unixtime)")
|
||||
unless $o =~ /^[0-9]+$/o;
|
||||
|
||||
$sql = $sql . "FROM_UNIXTIME(?),";
|
||||
}
|
||||
else {
|
||||
$sql = $sql . "?,";
|
||||
}
|
||||
|
||||
push @bound_values, $o;
|
||||
$i++;
|
||||
}
|
||||
chop($sql);
|
||||
$sql .= " )";
|
||||
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err && $dbh->{'PrintError'}) {
|
||||
Golem::do_log("got error [" . $dbh->err . "] [" . $dbh->errstr . "]" .
|
||||
" while executing [$sql] with values (" . join(",", @bound_values) . ")",
|
||||
{'stderr' => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
sub __update {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __update expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __update expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
my $unique_fields_arrayref = [keys %{$table->{'primary_key'}}];
|
||||
|
||||
if ($opts->{'unique_fields'}) {
|
||||
$unique_fields_arrayref = $opts->{'unique_fields'};
|
||||
}
|
||||
|
||||
# continue only if there's data for the table
|
||||
# in the in-memory hash or if there's a flag
|
||||
# saying we should create empty record with defaults
|
||||
#
|
||||
my $have_data_for_the_table = 0;
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
if (Golem::is_data_field($table_name, $o)) {
|
||||
my $is_unique = 0;
|
||||
|
||||
foreach my $u (@{$unique_fields_arrayref}) {
|
||||
if ($u eq $o) {
|
||||
$is_unique = 1;
|
||||
}
|
||||
}
|
||||
next if $is_unique;
|
||||
|
||||
$have_data_for_the_table = 1;
|
||||
}
|
||||
}
|
||||
unless ($have_data_for_the_table || $opts->{'create_empty_record'}) {
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
my $sql;
|
||||
my @bound_values;
|
||||
|
||||
$sql = "SELECT " . $table->{'select_all_sql'} . " from `$table_name` WHERE ";
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
if ($table_fields->{$f}->{'Type'} eq 'timestamp' && $record_hashref->{$f} != 0) {
|
||||
Golem::die("Programmer error: __update got hashref with invalid data for $table_name.$f (should be unixtime)")
|
||||
unless $record_hashref->{$f} =~ /^[0-9]+$/o;
|
||||
|
||||
$sql .= " `$f` = FROM_UNIXTIME(?) and ";
|
||||
}
|
||||
else {
|
||||
$sql .= " `$f` = ? and ";
|
||||
}
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
# create record if it doesn't exist: useful when updating
|
||||
# records in dependent tables (hosts_resps, hosts_netmap, host_rackmap)
|
||||
# when master table exists.
|
||||
unless ($sth->rows) {
|
||||
if ($opts->{"create_nonexistent"}) {
|
||||
$dbh = Golem::__insert($table_name, $record_hashref, $opts);
|
||||
return $dbh;
|
||||
}
|
||||
else {
|
||||
Golem::die("Programmer error: requested to update non-existent record with no create_nonexistent option");
|
||||
}
|
||||
}
|
||||
|
||||
my $existing_row;
|
||||
while(my $r = $sth->fetchrow_hashref()) {
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
Golem::die("more than 1 record fetched with should-be-unique lookup")
|
||||
if $existing_row;
|
||||
|
||||
$existing_row = $r;
|
||||
}
|
||||
|
||||
# check that existing record differs somehow from record to be written
|
||||
my $records_differ = 0;
|
||||
while (my ($k, $v) = each %{$existing_row}) {
|
||||
if (Golem::is_data_field($table_name, $k)) {
|
||||
|
||||
# what a mess!
|
||||
utf8::decode($record_hashref->{$k});
|
||||
utf8::decode($v);
|
||||
|
||||
if (
|
||||
($record_hashref->{$k} && $v && $v ne $record_hashref->{$k}) ||
|
||||
(! $record_hashref->{$k} && $v) ||
|
||||
($record_hashref->{$k} && ! $v)
|
||||
) {
|
||||
|
||||
Golem::debug_sql("in-memory [$table_name] object field [$k] differs: [" .
|
||||
($record_hashref->{$k} ? $record_hashref->{$k} : "") . "
|
||||
] -- [" .
|
||||
($v ? $v : "") .
|
||||
"]");
|
||||
|
||||
$records_differ = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# don't update database if that wouldn't actually
|
||||
# change any data; we should save A LOT of time here
|
||||
#
|
||||
return $dbh unless $records_differ;
|
||||
|
||||
@bound_values = ();
|
||||
$sql = "";
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
# $record_hashref might contain more fields than present in database.
|
||||
# we only choose those which are in db
|
||||
if ($table_fields->{$o} && Golem::is_data_field($table_name, $o)) {
|
||||
if ($table_fields->{$o}->{'Type'} eq 'timestamp' && $record_hashref->{$o} && $record_hashref->{$o} != 0) {
|
||||
Golem::die("Programmer error: __update got hashref with invalid data for $table_name.$o (should be unixtime)")
|
||||
unless $record_hashref->{$o} =~ /^[0-9]+$/o;
|
||||
|
||||
$sql = $sql . " `$o` = FROM_UNIXTIME(?),";
|
||||
}
|
||||
else {
|
||||
$sql = $sql . " `$o` = ?,";
|
||||
}
|
||||
push @bound_values, $record_hashref->{$o};
|
||||
}
|
||||
}
|
||||
chop($sql);
|
||||
|
||||
$sql = "UPDATE `$table_name` SET " . $sql . " WHERE ";
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
$sql .= " `$f` = ? and ";
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err) {
|
||||
Golem::do_log("error executing: $sql; bound values: " . join(",", @bound_values), {"stderr" => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
|
||||
sub __delete {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __delete expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __delete expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
my $unique_fields_arrayref = [keys %{$table->{'primary_key'}}];
|
||||
|
||||
if ($opts->{'unique_fields'}) {
|
||||
$unique_fields_arrayref = $opts->{'unique_fields'};
|
||||
}
|
||||
|
||||
my @bound_values = ();
|
||||
my $sql = "DELETE FROM `$table_name` WHERE ";
|
||||
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
$sql .= " `$f` = ? and ";
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err) {
|
||||
Golem::do_log("error executing: $sql; bound values: " . join(",", @bound_values), {"stderr" => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
197
local/cgi-bin/Golem/loglib.pl
Normal file
197
local/cgi-bin/Golem/loglib.pl
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Logging related routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
use Data::Dumper;
|
||||
|
||||
|
||||
sub dumper {
|
||||
my ($v) = @_;
|
||||
|
||||
return Dumper($v);
|
||||
}
|
||||
|
||||
sub debug_sql {
|
||||
my ($text, $bound_values, $opts) = @_;
|
||||
|
||||
if ($Golem::debug_sql) {
|
||||
$opts = {} unless $opts;
|
||||
|
||||
$text = "SQL: [" . ($text ? $text : "") . "]";
|
||||
|
||||
if ($bound_values && ref($bound_values) eq 'ARRAY') {
|
||||
$text .= " bound_values [" . Golem::safe_join(",", @{$bound_values}) . "]";
|
||||
}
|
||||
|
||||
if ($opts->{'stderr'}) {
|
||||
print STDERR localtime() . " " . $text . "\n";
|
||||
}
|
||||
else {
|
||||
print localtime() . " " . $text . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub debug2 {
|
||||
my ($text, $opts) = @_;
|
||||
|
||||
if ($Golem::debug2) {
|
||||
debug($text, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
sub debug {
|
||||
my ($text, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
if ($Golem::debug) {
|
||||
my $stamp = localtime() . ": ";
|
||||
|
||||
utf8::encode($text) if utf8::is_utf8($text);
|
||||
|
||||
if ($opts->{'stderr'}) {
|
||||
if (ref($text)) {
|
||||
print STDERR join("", map { "$stamp$_\n" } Dumper($text));
|
||||
}
|
||||
else {
|
||||
print STDERR $stamp . ($text ? $text : "") . "\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ref($text)) {
|
||||
print join("", map { "$stamp$_\n" } Dumper($text));
|
||||
}
|
||||
else {
|
||||
print $stamp . ($text ? $text : "") . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# safe_open and safe_close are copied
|
||||
# from ps_farm.pm (should be one library actually)
|
||||
sub safe_open {
|
||||
my ($filename, $mode, $timeout) = @_;
|
||||
|
||||
$timeout = 30 unless $timeout;
|
||||
$mode = "open" unless $mode;
|
||||
|
||||
if ($mode eq "overwrite" || $mode eq ">") {
|
||||
$mode = ">";
|
||||
}
|
||||
elsif ($mode eq "append" || $mode eq ">>") {
|
||||
$mode = ">>";
|
||||
}
|
||||
else {
|
||||
$mode = "";
|
||||
}
|
||||
|
||||
my $fh;
|
||||
my $i=0;
|
||||
while (! open($fh, "${mode}${filename}")) {
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to open $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "still trying to open $filename\n";
|
||||
$i = $i + 1;
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
while (! flock($fh, 2)) {
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to lock $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "still trying to lock $filename\n";
|
||||
$i = $i + 1;
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
my $fh1;
|
||||
if (!open($fh1, "${mode}${filename}")) {
|
||||
$i = $i + 1;
|
||||
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to open and lock $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "Locked $filename, but it's gone. Retrying...\n";
|
||||
return safe_open($filename, $mode, $timeout - 1);
|
||||
}
|
||||
else {
|
||||
close($fh1);
|
||||
return $fh;
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_close {
|
||||
my ($fh) = @_;
|
||||
return flock($fh, 8) && close($fh);
|
||||
}
|
||||
|
||||
sub do_log {
|
||||
my ($message, $opts) = @_;
|
||||
|
||||
my $module;
|
||||
my $stderr = 0;
|
||||
|
||||
if (ref($opts) eq 'HASH') {
|
||||
if ($opts->{'module'}) {
|
||||
$module = "[$opts->{'module'}] ";
|
||||
}
|
||||
if ($opts->{'stderr'}) {
|
||||
$stderr = $opts->{'stderr'};
|
||||
}
|
||||
}
|
||||
else {
|
||||
$module = $opts;
|
||||
$module = "[$module] " if $module;
|
||||
}
|
||||
|
||||
$message = "" unless $message;
|
||||
|
||||
utf8::encode($message) if utf8::is_utf8($message);
|
||||
|
||||
$module = "[" . $0 . "] " unless $module;
|
||||
|
||||
my $message_eol = chop($message);
|
||||
|
||||
my $message_formatted =
|
||||
localtime() . " " . $module .
|
||||
$message .
|
||||
$message_eol .
|
||||
($message_eol eq "\n" ? "" : "\n");
|
||||
|
||||
if ($stderr) {
|
||||
print STDERR $message_formatted;
|
||||
}
|
||||
elsif ($Golem::debug) {
|
||||
print $message_formatted;
|
||||
}
|
||||
|
||||
if (defined($Golem::LOG)) {
|
||||
my $fh = safe_open($Golem::LOG, ">>");
|
||||
Golem::die ("Unable to open $Golem::LOG\n") unless $fh;
|
||||
# binmode($fh, ":utf8");
|
||||
print $fh $message_formatted;
|
||||
safe_close($fh);
|
||||
}
|
||||
else {
|
||||
print STDERR "No Golem::LOG is configured. Logging to STDERR\n";
|
||||
print STDERR $message_formatted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
330
local/cgi-bin/Golem/netlib.pl
Normal file
330
local/cgi-bin/Golem/netlib.pl
Normal file
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# networking related routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
sub get_external_ipinfo {
|
||||
my ($ip) = @_;
|
||||
|
||||
return undef unless $ip && $Golem::noc_nets_cgi;
|
||||
|
||||
my $ipinfo;
|
||||
my @net_info;
|
||||
|
||||
my $wget_options = "";
|
||||
$wget_options = "--timeout=${Golem::noc_nets_cgi_timeout}"
|
||||
if $Golem::noc_nets_cgi_timeout;
|
||||
|
||||
open(NOCNET,"wget $wget_options -q -O - ${Golem::noc_nets_cgi}?name=$ip |");
|
||||
|
||||
Golem::debug("wget $wget_options -q -O - ${Golem::noc_nets_cgi}?name=$ip |");
|
||||
|
||||
my $line;
|
||||
while($line=<NOCNET>) {
|
||||
if ($line=~/^<tr align=center>/) {
|
||||
chomp($line);
|
||||
$line =~ s/<\/td><td>/\|/g;
|
||||
$line =~ s/(<td>|<tr align=center>|<\/tr>|<\/td>)//g;
|
||||
@net_info= split(/\|/,$line,5);
|
||||
}
|
||||
}
|
||||
close(NOCNET);
|
||||
|
||||
if (@net_info) {
|
||||
$ipinfo->{'ip'} = $net_info[0];
|
||||
$ipinfo->{'subnet'} = $net_info[1];
|
||||
$ipinfo->{'router'} = $net_info[2];
|
||||
$ipinfo->{'iface'} = $net_info[3];
|
||||
|
||||
$ipinfo->{'vlan'} = $ipinfo->{'iface'};
|
||||
$ipinfo->{'vlan'} =~ /(\d+)/;
|
||||
$ipinfo->{'vlan'} = $1;
|
||||
|
||||
$ipinfo->{'router_if_addr'} = $net_info[4];
|
||||
}
|
||||
|
||||
return $ipinfo;
|
||||
}
|
||||
|
||||
# --- ghetto code
|
||||
sub int2maskpart {
|
||||
my ($i) = @_;
|
||||
return "0" unless defined($i);
|
||||
|
||||
my $j = 0;
|
||||
my $bits = "";
|
||||
while ($j < 8) {
|
||||
if ($i <= $j) {
|
||||
$bits .= "0";
|
||||
}
|
||||
else {
|
||||
$bits .= "1";
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
return oct("0b" . $bits);
|
||||
}
|
||||
|
||||
|
||||
sub mask2netmask {
|
||||
my ($j) = @_;
|
||||
|
||||
my @ip;
|
||||
my $i;
|
||||
for ($i = 1; $i <= int($j / 8); $i++) {
|
||||
push @ip, int2maskpart(8);
|
||||
}
|
||||
while ($i < 5) {
|
||||
push @ip, int2maskpart($j % 8);
|
||||
$j = 0;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return join(".", @ip);
|
||||
}
|
||||
|
||||
|
||||
# convert string representation of ipv4 address into integer
|
||||
sub ipv4_str2int {
|
||||
my ($ip_string) = @_;
|
||||
|
||||
if ($ip_string =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) {
|
||||
return (16777216 * $1) + (65536 * $2) + (256 * $3) + $4;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# convert integer representation of ipv4 address into string
|
||||
sub ipv4_int2str {
|
||||
my ($ip_int) = @_;
|
||||
|
||||
if ($ip_int >= 0 && $ip_int <= 4294967295) {
|
||||
my $w = ($ip_int / 16777216) % 256;
|
||||
my $x = ($ip_int / 65536) % 256;
|
||||
my $y = ($ip_int / 256) % 256;
|
||||
my $z = $ip_int % 256;
|
||||
|
||||
return $w . "." . $x . "." . $y . "." . $z;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# /24 -> +255
|
||||
# /27 -> +31
|
||||
# /28 -> +15
|
||||
# /29 -> +7
|
||||
sub ipv4_mask2offset {
|
||||
my ($mask) = @_;
|
||||
$mask ||= 0;
|
||||
my $offset = 2 ** (32 - $mask);
|
||||
return $offset - 1;
|
||||
}
|
||||
|
||||
sub get_net {
|
||||
my ($ip, $mask, $opts) = @_;
|
||||
|
||||
Golem::trim(\$ip);
|
||||
|
||||
Golem::die("Programmer error: get_net expects net and mask")
|
||||
unless
|
||||
($ip && $mask && Golem::is_digital($mask)) ||
|
||||
($ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/);
|
||||
|
||||
if ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/o) {
|
||||
$ip = ipv4_str2int($1);
|
||||
$mask = $2;
|
||||
}
|
||||
elsif ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/o) {
|
||||
$ip = ipv4_str2int($1);
|
||||
}
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE ip = ? and mask = ?");
|
||||
$sth->execute($ip, $mask);
|
||||
my $net = $sth->fetchrow_hashref();
|
||||
$sth->finish();
|
||||
|
||||
if ($net->{'id'}) {
|
||||
return Golem::get_net_by_id($net->{'id'}, $opts);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_net_by_id {
|
||||
my ($id, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_net_by_id expects network id")
|
||||
unless $id;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE id = ?");
|
||||
|
||||
$sth->execute($id);
|
||||
|
||||
my $r = $sth->fetchrow_hashref();
|
||||
if ($r->{'id'}) {
|
||||
$r->{'ip_str'} = Golem::ipv4_int2str($r->{'ip'});
|
||||
$r->{'net_with_mask'} = $r->{'ip_str'} . "/" . $r->{'mask'};
|
||||
if ($r->{'name'} =~ /VLAN([\d]+)/o) {
|
||||
$r->{'vlan'} = $1;
|
||||
}
|
||||
else {
|
||||
$r->{'vlan'} = "";
|
||||
}
|
||||
|
||||
if ($opts->{'with_props'} || $opts->{'with_all'}) {
|
||||
$r = Golem::load_props("net_v4", $r);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub insert_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: insert_net expects net object")
|
||||
unless $net && ($net->{'ip_str'} || $net->{'ip'}) &&
|
||||
$net->{'mask'} && $net->{'name'};
|
||||
|
||||
if ($net->{'ip_str'}) {
|
||||
$net->{'ip'} = Golem::ipv4_str2int($net->{'ip_str'});
|
||||
}
|
||||
|
||||
my $enet = Golem::get_net($net->{'ip'}, $net->{'mask'});
|
||||
return Golem::err("net already exists [$enet->{'ip_str'}/$enet->{'$mask'} ($enet->{'id'})]")
|
||||
if $enet;
|
||||
|
||||
my $dbh = Golem::__insert("net_v4", $net);
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
$net->{'id'} = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
|
||||
return Golem::get_net_by_id($net->{'id'});
|
||||
}
|
||||
|
||||
sub save_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: save_net expects net object")
|
||||
unless $net && $net->{'id'} &&
|
||||
($net->{'ip_str'} || $net->{'ip'}) &&
|
||||
$net->{'mask'} && $net->{'name'};
|
||||
|
||||
if ($net->{'ip_str'}) {
|
||||
$net->{'ip'} = Golem::ipv4_str2int($net->{'ip_str'});
|
||||
}
|
||||
|
||||
my $enet = Golem::get_net($net->{'ip'}, $net->{'mask'}, {"with_all" => 1});
|
||||
|
||||
if ($enet) {
|
||||
my $dbh = Golem::__update("net_v4", $net);
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
$net = Golem::save_props("net_v4", $net);
|
||||
}
|
||||
else {
|
||||
return Golem::insert_net($net);
|
||||
}
|
||||
|
||||
return $net;
|
||||
}
|
||||
|
||||
sub delete_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_net expects net object")
|
||||
unless $net && $net->{'id'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
Golem::unset_row_tag("net_v4", $net->{'id'});
|
||||
$dbh->do("DELETE from net_v4prop where net_v4id = ?", undef, $net->{'id'});
|
||||
$dbh->do("DELETE from net_v4propblob where net_v4id = ?", undef, $net->{'id'});
|
||||
|
||||
$dbh->do("DELETE FROM net_v4 WHERE net_v4.id = ?", undef, $net->{'id'});
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
sub get_containing_net {
|
||||
my ($ip, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_containing_net expects ip address")
|
||||
unless $ip;
|
||||
|
||||
Golem::trim(\$ip);
|
||||
if ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/o) {
|
||||
$ip = Golem::ipv4_str2int($1);
|
||||
}
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
# choose nearest (by ip desc) and smallest (by mask desc) known network
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE ip < ? and mask <> 32
|
||||
order by ip desc, mask desc");
|
||||
|
||||
$sth->execute($ip);
|
||||
|
||||
my $net;
|
||||
|
||||
while(my $r = $sth->fetchrow_hashref()) {
|
||||
# choose first network that includes tested ip address if any
|
||||
if ($r->{'ip'} + ipv4_mask2offset($r->{'mask'}) ge $ip) {
|
||||
return Golem::get_net_by_id($r->{'id'}, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get_net_by_vlan {
|
||||
my ($vlan, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_net_by_vlan expects vlan name")
|
||||
unless $vlan;
|
||||
|
||||
$vlan =~ s/vlan//go;
|
||||
$vlan =~ s/\s//go;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE mask <> 32 and name like ? order by name");
|
||||
$sth->execute("VLAN${vlan}%");
|
||||
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
if ($r->{'name'} =~ /VLAN${vlan}\s/o) {
|
||||
return Golem::get_net($r->{'ip'}, $r->{'mask'}, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub is_ipv4 {
|
||||
my ($str) = @_;
|
||||
return $str =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/o;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
393
local/cgi-bin/Golem/proplib.pl
Normal file
393
local/cgi-bin/Golem/proplib.pl
Normal file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Properties manipulation routines
|
||||
#
|
||||
#
|
||||
# This is an object property library which
|
||||
# implements DB -> MEMORY (load_props)
|
||||
# and MEMORY -> DB (save_props) transition
|
||||
# of object properties
|
||||
#
|
||||
# Object is defined as a record in some TABLE
|
||||
# (called owner). To be able to use properties
|
||||
# for the given TABLE you should create
|
||||
# another two tables: TABLEprop and TABLEpropblob
|
||||
#
|
||||
# Example for host object:
|
||||
#
|
||||
# CREATE TABLE `hostprop` (
|
||||
# `hostid` int(11) NOT NULL,
|
||||
# `propid` smallint(6) NOT NULL default '0',
|
||||
# `propseq` int(11) NOT NULL default '0',
|
||||
# `value` varchar(1024) default NULL,
|
||||
# PRIMARY KEY (`hostid`,`propid`,`propseq`),
|
||||
# KEY `prop` (`propid`)
|
||||
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
#
|
||||
# CREATE TABLE `hostpropblob` (
|
||||
# `hostid` int(11) NOT NULL,
|
||||
# `propid` smallint(6) NOT NULL default '0',
|
||||
# `propseq` int(11) NOT NULL default '0',
|
||||
# `value` blob,
|
||||
# PRIMARY KEY (`hostid`,`propid`,`propseq`),
|
||||
# KEY `prop` (`propid`)
|
||||
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
#
|
||||
# After that you should create "allowed" properties
|
||||
# for the owner using `gconsole.pl --create-proplist`
|
||||
#
|
||||
# You could see all the defined properties for the owner
|
||||
# using gconsole.pl --list-proplist OWNER
|
||||
#
|
||||
#
|
||||
# For the owner (tables) which primary key is not simple
|
||||
# id auto_increment (as in the example above) the following
|
||||
# TABLEprop and TABLEpropblob structure should be used:
|
||||
#
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
use Storable;
|
||||
|
||||
|
||||
# check that given property owner is valid
|
||||
# currently there are two valid property owners: host, user
|
||||
#
|
||||
sub check_prop_owner {
|
||||
my ($owner) = @_;
|
||||
|
||||
Golem::die("Programmer error: check_prop_owner got empty prop owner")
|
||||
unless $owner;
|
||||
|
||||
my $props = {
|
||||
"eventhistory" => 1,
|
||||
"host" => 1,
|
||||
"user" => 1,
|
||||
"net_v4" => 1,
|
||||
};
|
||||
|
||||
Golem::die("Programmer error: not valid property owner [$owner]")
|
||||
unless defined($props->{$owner});
|
||||
}
|
||||
|
||||
# get property definition record(s) from database
|
||||
# for the specified owner
|
||||
#
|
||||
sub get_proplist {
|
||||
my ($owner, $hpname) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_proplist expects at least owner")
|
||||
unless $owner;
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth;
|
||||
my $ret;
|
||||
|
||||
if ($hpname) {
|
||||
$sth = $dbh->prepare("SELECT * FROM proplist WHERE owner = ? and name = ?");
|
||||
$sth->execute($owner, $hpname);
|
||||
$ret = $sth->fetchrow_hashref();
|
||||
$ret = 0 unless $ret->{'id'};
|
||||
}
|
||||
else {
|
||||
$sth = $dbh->prepare("SELECT * FROM proplist where owner = ?");
|
||||
$sth->execute($owner);
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
$ret->{$r->{'name'}} = $r;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub create_proplist {
|
||||
my ($owner, $op) = @_;
|
||||
|
||||
Golem::die("Programmer error: create_proplist expects at least owner and property name")
|
||||
unless $owner && $op && $op->{'name'};
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $eop = Golem::get_proplist($owner, $op->{'name'});
|
||||
Golem::die("proplist record already exists [$eop->{'name'} ($eop->{'id'})]")
|
||||
if $eop;
|
||||
|
||||
$op->{'owner'} = $owner;
|
||||
|
||||
my $dbh = Golem::__insert("proplist", $op);
|
||||
Golem::die("Error creating proplist: " . $dbh->errstr, $op)
|
||||
if $dbh->err;
|
||||
|
||||
$op->{'id'} = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
|
||||
Golem::do_log("new proplist record: [$op->{'owner'}/$op->{'name'}] ($op->{'id'})");
|
||||
|
||||
return $op;
|
||||
}
|
||||
|
||||
sub delete_proplist {
|
||||
my ($owner, $op) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_proplist expects proplist record")
|
||||
unless $op && $op->{'id'} && $op->{'name'};
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $eop = Golem::get_proplist($owner, $op->{'name'});
|
||||
|
||||
return Golem::err("delete_proplist: invalid proplist record")
|
||||
unless $eop;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $objs_with_prop =
|
||||
$dbh->selectrow_array("select count(*) from ${owner}prop where propid = ?", undef, $op->{'id'}) +
|
||||
$dbh->selectrow_array("select count(*) from ${owner}propblob where propid = ?", undef, $op->{'id'})
|
||||
;
|
||||
|
||||
return Golem::err("delete_proplist: unable to delete proplist record; $objs_with_prop records are using it")
|
||||
if $objs_with_prop;
|
||||
|
||||
$dbh->do("delete from proplist where id = ?", undef, $op->{'id'});
|
||||
|
||||
return Golem::err("error while deleting from proplist: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
# read object properties for the given owner
|
||||
# (exactly matches table name)
|
||||
#
|
||||
sub load_props {
|
||||
my ($owner, $o) = @_;
|
||||
|
||||
Golem::die("Programmer error: load_props expects owner name and object")
|
||||
unless $owner && ref($o);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth;
|
||||
|
||||
$o->{'props'}->{'data'} = {};
|
||||
|
||||
foreach my $t ("${owner}prop", "${owner}propblob") {
|
||||
my $sql = "select * from $t inner join proplist on proplist.id = $t.propid WHERE 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and `${owner}id` = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and `$pk_field` = ? ";
|
||||
}
|
||||
|
||||
Golem::die("Programmer error: load_props got empty value for primary key field [$pk_field] of table [$owner]")
|
||||
unless defined($o->{$pk_field});
|
||||
|
||||
push (@bound_values, $o->{$pk_field});
|
||||
}
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
my $v;
|
||||
|
||||
if ($t eq "${owner}prop") {
|
||||
$v = $r->{'value'};
|
||||
}
|
||||
if ($t eq "${owner}propblob") {
|
||||
# print STDERR Storable::thaw($r->{'value'});
|
||||
$v = Storable::thaw($r->{'value'});
|
||||
}
|
||||
|
||||
if ($r->{'datatype'} eq 'array' || $r->{'datatype'} eq 'arrayblob') {
|
||||
$o->{'props'}->{'data'}->{$r->{'name'}} = []
|
||||
unless defined($o->{'props'}->{'data'}->{$r->{'name'}});
|
||||
|
||||
push (@{$o->{'props'}->{'data'}->{$r->{'name'}}}, $v);
|
||||
}
|
||||
else {
|
||||
$o->{'props'}->{'data'}->{$r->{'name'}} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$o->{'props'}->{'loaded'} = 1;
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
# save properties from memory into database
|
||||
# checks that properties were loaded using load_props
|
||||
# (for advanced users: "loaded" is the key of check,
|
||||
# it is set by load_props which could be emulated)
|
||||
#
|
||||
sub save_props {
|
||||
my ($owner, $o) = @_;
|
||||
|
||||
Golem::die("Programmer error: save_props expects owner name and object")
|
||||
unless $owner && ref($o);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
return Golem::err("Programmer error: save_props should be called only after calling load_props")
|
||||
unless $o->{'props'}->{'loaded'};
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
while (my ($k, $v) = each %{$o->{'props'}->{'data'}}) {
|
||||
my $op = Golem::get_proplist($owner, $k);
|
||||
|
||||
unless ($op) {
|
||||
Golem::do_log("Non-existent $owner property name [$k], skipping.", {"stderr" => 1});
|
||||
next;
|
||||
}
|
||||
|
||||
my $do_save = sub {
|
||||
my ($value, $seq) = @_;
|
||||
|
||||
$seq = 0 unless defined($seq);
|
||||
|
||||
my $tpref = "";
|
||||
my $db_value;
|
||||
|
||||
if ($op->{'datatype'} eq 'blob' || $op->{'datatype'} eq 'arrayblob') {
|
||||
$db_value = Storable::nfreeze($value);
|
||||
$tpref = "blob";
|
||||
}
|
||||
else {
|
||||
$db_value = $value;
|
||||
|
||||
if ($op->{'datatype'} eq 'bool') {
|
||||
$db_value = $value ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
my $prop_ref = {
|
||||
"propid" => $op->{'id'},
|
||||
"propseq" => $seq,
|
||||
"value" => $db_value,
|
||||
};
|
||||
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$prop_ref->{"${owner}id"} = $o->{$pk_field};
|
||||
}
|
||||
else {
|
||||
$prop_ref->{$pk_field} = $o->{$pk_field};
|
||||
}
|
||||
}
|
||||
|
||||
$dbh = Golem::__update("${owner}prop${tpref}", $prop_ref, {"create_nonexistent" => 1});
|
||||
Golem::die("save_props: error while replacing ${owner} props: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
};
|
||||
|
||||
if ($op->{'datatype'} eq 'array' || $op->{'datatype'} eq 'arrayblob') {
|
||||
my $i = 0;
|
||||
foreach my $array_value (@{$v}) {
|
||||
$do_save->($array_value, $i);
|
||||
$i = $i + 1;
|
||||
}
|
||||
|
||||
my $tpref = "";
|
||||
if ($op->{'datatype'} eq 'arrayblob') {
|
||||
$tpref = "blob";
|
||||
}
|
||||
|
||||
my $sql = "delete from ${owner}prop${tpref} where 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and ${owner}id = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and $pk_field = ? ";
|
||||
}
|
||||
push (@bound_values, $o->{$pk_field});
|
||||
}
|
||||
$sql .= " and propid = ? and propseq >= ?";
|
||||
push (@bound_values, $op->{'id'});
|
||||
push (@bound_values, $i);
|
||||
|
||||
my $sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
}
|
||||
else {
|
||||
$do_save->($v, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
# deletes object property if no objects
|
||||
# are associated with the property
|
||||
#
|
||||
# input
|
||||
# owner type (for the listing see check_prop_owner)
|
||||
# object (with $obj->{'id'} defined)
|
||||
# property name to delete
|
||||
#
|
||||
sub delete_prop {
|
||||
my ($owner, $obj, $propname) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_prop expects owner and object")
|
||||
unless $owner && ref($obj);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $op = Golem::get_proplist($owner, $propname);
|
||||
return Golem::err("delete_prop: invalid propname [$propname]")
|
||||
unless $op;
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
my $tpref = "";
|
||||
if ($op->{'datatype'} eq 'blob' || $op->{'datatype'} eq 'blobarray') {
|
||||
$tpref = "blob";
|
||||
}
|
||||
|
||||
my $sql = "delete from ${owner}prop${tpref} where 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and ${owner}id = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and $pk_field = ? ";
|
||||
}
|
||||
push (@bound_values, $obj->{$pk_field});
|
||||
}
|
||||
$sql .= " and propid = ? ";
|
||||
push (@bound_values, $op->{'id'});
|
||||
|
||||
my $sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
Golem::die("delete_prop: error deleting $owner [$obj->{'id'}] property [$propname]: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
222
local/cgi-bin/Golem/textlib.pl
Normal file
222
local/cgi-bin/Golem/textlib.pl
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Text manipulation routines
|
||||
#
|
||||
# parts courtesy of LiveJournal.org
|
||||
#
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::decode_url_string
|
||||
# class: web
|
||||
# des: Parse URL-style arg/value pairs into a hash.
|
||||
# args: buffer, hashref
|
||||
# des-buffer: Scalar or scalarref of buffer to parse.
|
||||
# des-hashref: Hashref to populate.
|
||||
# returns: boolean; true.
|
||||
# </LJFUNC>
|
||||
sub decode_url_string
|
||||
{
|
||||
my $a = shift;
|
||||
my $buffer = ref $a ? $a : \$a;
|
||||
my $hashref = shift; # output hash
|
||||
my $keyref = shift; # array of keys as they were found
|
||||
|
||||
my $pair;
|
||||
my @pairs = split(/&/, $$buffer);
|
||||
@$keyref = @pairs;
|
||||
my ($name, $value);
|
||||
foreach $pair (@pairs)
|
||||
{
|
||||
($name, $value) = split(/=/, $pair);
|
||||
$value =~ tr/+/ /;
|
||||
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$name =~ tr/+/ /;
|
||||
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$hashref->{$name} .= $hashref->{$name} ? ",$value" : $value;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::ehtml
|
||||
# class: text
|
||||
# des: Escapes a value before it can be put in HTML.
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub ehtml
|
||||
{
|
||||
# fast path for the commmon case:
|
||||
return $_[0] unless $_[0] =~ /[&\"\'<>]/o;
|
||||
|
||||
# this is faster than doing one substitution with a map:
|
||||
my $a = $_[0];
|
||||
$a =~ s/\&/&/g;
|
||||
$a =~ s/\"/"/g;
|
||||
$a =~ s/\'/&\#39;/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
sub esql {
|
||||
return $_[0] unless $_[0] =~ /[\'\"\\]/o;
|
||||
|
||||
my $a = $_[0];
|
||||
$a =~ s/\'//go;
|
||||
$a =~ s/\"//go;
|
||||
$a =~ s/\\//go;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::is_ascii
|
||||
# des: checks if text is pure ASCII.
|
||||
# args: text
|
||||
# des-text: text to check for being pure 7-bit ASCII text.
|
||||
# returns: 1 if text is indeed pure 7-bit, 0 otherwise.
|
||||
# </LJFUNC>
|
||||
sub is_ascii {
|
||||
my $text = shift;
|
||||
return ($text !~ m/[^\x01-\x7f]/o);
|
||||
}
|
||||
|
||||
sub is_digital {
|
||||
my $text = shift;
|
||||
return ( $text =~ /\d+/o );
|
||||
}
|
||||
|
||||
# tests if there's data in configuration file string:
|
||||
# i.e. if it's not empty and is not commented
|
||||
#
|
||||
sub have_data {
|
||||
my ($line) = @_;
|
||||
|
||||
if ($line =~ /^\s*#/o || $line =~ /^[\s]*$/o) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# fixes user input strings (passed as array of references),
|
||||
# currently only trims
|
||||
#
|
||||
# used when importing:
|
||||
# - 1C data
|
||||
#
|
||||
sub fix_input {
|
||||
my (@i) = @_;
|
||||
|
||||
foreach my $v (@i) {
|
||||
Golem::die("Programmer error: check_input expects only scalar references")
|
||||
unless ref($v) eq 'SCALAR';
|
||||
|
||||
Golem::do_log("Inaccurate spacing trimmed [$$v]", {"stderr" => 1})
|
||||
if Golem::trim($v);
|
||||
}
|
||||
}
|
||||
|
||||
# given scalar string trims white space
|
||||
# at the beginning and in the end and
|
||||
# returns trimmed string
|
||||
#
|
||||
# given scalar reference trims white space
|
||||
# at the beginning and in the end and
|
||||
# returns true if trimming occured and false otherwise
|
||||
# NOTE: modifies the original string
|
||||
# reference to which was given as input parameter
|
||||
sub trim {
|
||||
my ($string) = @_;
|
||||
|
||||
if (ref($string) eq 'SCALAR') {
|
||||
my $tstr = $$string;
|
||||
|
||||
return 0 if $tstr eq ''; # nothing to trim, do not waste cpu cycles
|
||||
|
||||
$tstr =~ s/^\s+//so;
|
||||
$tstr =~ s/\s+$//so;
|
||||
|
||||
if ($tstr ne $$string) {
|
||||
$$string = $tstr;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "" if $string eq ''; # nothing to trim, do not waste cpu cycles
|
||||
|
||||
$string =~ s/^\s+//so;
|
||||
$string =~ s/\s+$//so;
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
# same as standard perl join except for it doesn't
|
||||
# output "uninitialized value in join" when joining
|
||||
# list with undef values; and we use those lists
|
||||
# when binding params to DBI query
|
||||
#
|
||||
sub safe_join {
|
||||
my ($delimiter, @arr) = @_;
|
||||
|
||||
my $joined_text = "";
|
||||
$delimiter = "" unless $delimiter;
|
||||
|
||||
foreach my $bv (@arr) {
|
||||
$joined_text .= ($bv ? $bv : "") . $delimiter;
|
||||
}
|
||||
|
||||
my $i;
|
||||
for ($i = 0; $i < length($delimiter); $i++) {
|
||||
chop($joined_text);
|
||||
}
|
||||
|
||||
return $joined_text;
|
||||
}
|
||||
|
||||
# should be used when you need to concatenate string
|
||||
# which might be undefined and you want empty string ("")
|
||||
# instead of perl warnings about uninitialized values
|
||||
#
|
||||
sub safe_string {
|
||||
my ($str) = @_;
|
||||
|
||||
if ($str) {
|
||||
return $str;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
# inserts $symbol every $range characters in a $line
|
||||
sub div_line {
|
||||
my ($line, $range, $symbol) = @_;
|
||||
|
||||
Golem::die("Programmer error: div_line expects at least string")
|
||||
unless $line;
|
||||
|
||||
$range = 70 unless $range;
|
||||
$symbol = ' ' unless $symbol;
|
||||
|
||||
my $result = '';
|
||||
for (my $i = 0 ; $i <= int(length($line)/$range) ; $i++) {
|
||||
$result .= substr($line,$i*$range,$range) . $symbol;
|
||||
}
|
||||
chop($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user