This commit is contained in:
2019-02-06 00:49:12 +03:00
commit 8dbb1bb605
4796 changed files with 506072 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;

View 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/\&/&amp;/g;
$a =~ s/\"/&quot;/g;
$a =~ s/\'/&\#39;/g;
$a =~ s/</&lt;/g;
$a =~ s/>/&gt;/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;