594 lines
14 KiB
Perl
594 lines
14 KiB
Perl
|
#
|
||
|
# Copyright (c) 2006, 2007 Michael Schroeder, Novell Inc.
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License version 2 as
|
||
|
# published by the Free Software Foundation.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program (see the file COPYING); if not, write to the
|
||
|
# Free Software Foundation, Inc.,
|
||
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||
|
#
|
||
|
################################################################
|
||
|
#
|
||
|
# collection of useful functions
|
||
|
#
|
||
|
|
||
|
package BSUtil;
|
||
|
|
||
|
require Exporter;
|
||
|
@ISA = qw(Exporter);
|
||
|
@EXPORT = qw{writexml writestr readxml readstr ls mkdir_p xfork str2utf8 data2utf8 str2utf8xml data2utf8xml};
|
||
|
|
||
|
use XML::Structured;
|
||
|
use POSIX;
|
||
|
use Fcntl qw(:DEFAULT :flock);
|
||
|
use Encode;
|
||
|
use Storable ();
|
||
|
use IO::Handle;
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
our $fdatasync_before_rename;
|
||
|
|
||
|
sub set_fdatasync_before_rename {
|
||
|
$fdatasync_before_rename = 1;
|
||
|
if (!defined(&File::Sync::fdatasync_fd)) {
|
||
|
eval {
|
||
|
require File::Sync;
|
||
|
};
|
||
|
warn($@) if $@;
|
||
|
*File::Sync::fdatasync_fd = sub {} unless defined &File::Sync::fdatasync_fd;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub do_fdatasync {
|
||
|
my ($fd) = @_;
|
||
|
set_fdatasync_before_rename() unless defined &File::Sync::fdatasync_fd;
|
||
|
File::Sync::fdatasync_fd($fd);
|
||
|
}
|
||
|
|
||
|
sub writexml {
|
||
|
my ($fn, $fnf, $dd, $dtd) = @_;
|
||
|
my $d = XMLout($dtd, $dd);
|
||
|
local *F;
|
||
|
open(F, '>', $fn) || die("$fn: $!\n");
|
||
|
(syswrite(F, $d) || 0) == length($d) || die("$fn write: $!\n");
|
||
|
do_fdatasync(fileno(F)) if defined($fnf) && $fdatasync_before_rename;
|
||
|
close(F) || die("$fn close: $!\n");
|
||
|
return unless defined $fnf;
|
||
|
$! = 0;
|
||
|
rename($fn, $fnf) || die("rename $fn $fnf: $!\n");
|
||
|
}
|
||
|
|
||
|
sub writestr {
|
||
|
my ($fn, $fnf, $d) = @_;
|
||
|
local *F;
|
||
|
open(F, '>', $fn) || die("$fn: $!\n");
|
||
|
if (length($d)) {
|
||
|
(syswrite(F, $d) || 0) == length($d) || die("$fn write: $!\n");
|
||
|
}
|
||
|
do_fdatasync(fileno(F)) if defined($fnf) && $fdatasync_before_rename;
|
||
|
close(F) || die("$fn close: $!\n");
|
||
|
return unless defined $fnf;
|
||
|
rename($fn, $fnf) || die("rename $fn $fnf: $!\n");
|
||
|
}
|
||
|
|
||
|
sub appendstr {
|
||
|
my ($fn, $d) = @_;
|
||
|
local *F;
|
||
|
open(F, '>>', $fn) || die("$fn: $!\n");
|
||
|
if (length($d)) {
|
||
|
(syswrite(F, $d) || 0) == length($d) || die("$fn write: $!\n");
|
||
|
}
|
||
|
close(F) || die("$fn close: $!\n");
|
||
|
}
|
||
|
|
||
|
sub readstr {
|
||
|
my ($fn, $nonfatal) = @_;
|
||
|
local *F;
|
||
|
if (!open(F, '<', $fn)) {
|
||
|
die("$fn: $!\n") unless $nonfatal;
|
||
|
return undef;
|
||
|
}
|
||
|
my $d = '';
|
||
|
1 while sysread(F, $d, 8192, length($d));
|
||
|
close F;
|
||
|
return $d;
|
||
|
}
|
||
|
|
||
|
sub readxml {
|
||
|
my ($fn, $dtd, $nonfatal) = @_;
|
||
|
my $d = readstr($fn, $nonfatal);
|
||
|
return $d unless defined $d;
|
||
|
if ($d !~ /<.*?>/s) {
|
||
|
die("$fn: not xml\n") unless $nonfatal;
|
||
|
return undef;
|
||
|
}
|
||
|
return XMLin($dtd, $d) unless $nonfatal;
|
||
|
eval { $d = XMLin($dtd, $d); };
|
||
|
return $@ ? undef : $d;
|
||
|
}
|
||
|
|
||
|
sub fromxml {
|
||
|
my ($d, $dtd, $nonfatal) = @_;
|
||
|
return XMLin($dtd, $d) unless $nonfatal;
|
||
|
eval { $d = XMLin($dtd, $d); };
|
||
|
return $@ ? undef : $d;
|
||
|
}
|
||
|
|
||
|
sub toxml {
|
||
|
my ($d, $dtd) = @_;
|
||
|
return XMLout($dtd, $d);
|
||
|
}
|
||
|
|
||
|
sub touch($) {
|
||
|
my ($file) = @_;
|
||
|
if (-e $file) {
|
||
|
utime(time, time, $file);
|
||
|
} else {
|
||
|
# create new file, mtime is anyway current
|
||
|
local *F;
|
||
|
open(F, '>>', $file) || die("$file: $!\n");
|
||
|
close(F) || die("$file close: $!\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub ls {
|
||
|
local *D;
|
||
|
opendir(D, $_[0]) || return ();
|
||
|
my @r = grep {$_ ne '.' && $_ ne '..'} readdir(D);
|
||
|
closedir D;
|
||
|
return @r;
|
||
|
}
|
||
|
|
||
|
sub mkdir_p {
|
||
|
my ($dir) = @_;
|
||
|
|
||
|
return 1 if -d $dir;
|
||
|
my $pdir;
|
||
|
if ($dir =~ /^(.+)\//) {
|
||
|
$pdir = $1;
|
||
|
mkdir_p($pdir) || return undef;
|
||
|
}
|
||
|
while (!mkdir($dir, 0777)) {
|
||
|
my $e = $!;
|
||
|
return 1 if -d $dir;
|
||
|
if (defined($pdir) && ! -d $pdir) {
|
||
|
mkdir_p($pdir) || return undef;
|
||
|
next;
|
||
|
}
|
||
|
$! = $e;
|
||
|
warn("mkdir: $dir: $!\n");
|
||
|
return undef;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
# calls mkdir_p and changes ownership of the created directory to the
|
||
|
# supplied user and group if provided.
|
||
|
sub mkdir_p_chown {
|
||
|
my ($dir, $user, $group) = @_;
|
||
|
|
||
|
if (!(-d $dir)) {
|
||
|
mkdir_p($dir) || return undef;
|
||
|
}
|
||
|
return 1 unless defined($user) || defined($group);
|
||
|
|
||
|
$user = -1 unless defined $user;
|
||
|
$group = -1 unless defined $group;
|
||
|
|
||
|
if ($user !~ /^-?\d+$/ && !($user = getpwnam($user))) {
|
||
|
warn "user $user unknown\n"; return undef
|
||
|
}
|
||
|
if ($group !~ /^-?\d+$/ && !($group = getgrnam($group))) {
|
||
|
warn "group $group unknown\n"; return undef
|
||
|
}
|
||
|
|
||
|
my @s = stat($dir);
|
||
|
if ($s[4] != $user || $s[5] != $group) {
|
||
|
if (!chown $user, $group, $dir) {
|
||
|
warn "failed to chown $dir to $user:$group\n"; return undef;
|
||
|
}
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub drop_privs_to {
|
||
|
my ($user, $group) = @_;
|
||
|
|
||
|
if (defined($group)) {
|
||
|
$group = getgrnam($group) unless $group =~ /^\d+$/;
|
||
|
die("unknown group\n") unless defined $group;
|
||
|
if ($) != $group || $( != $group) {
|
||
|
($), $() = ($group, $group);
|
||
|
die("setgid: $!\n") if $) != $group;
|
||
|
}
|
||
|
}
|
||
|
if (defined($user)) {
|
||
|
$user = getpwnam($user) unless $user =~ /^\d+$/;
|
||
|
die("unknown user\n") unless defined $user;
|
||
|
if ($> != $user || $< != $user) {
|
||
|
($>, $<) = ($user, $user);
|
||
|
die("setuid: $!\n") if $> != $user;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub cleandir {
|
||
|
my ($dir) = @_;
|
||
|
|
||
|
my $ret = 1;
|
||
|
return 1 unless -d $dir;
|
||
|
for my $c (ls($dir)) {
|
||
|
if (! -l "$dir/$c" && -d _) {
|
||
|
cleandir("$dir/$c");
|
||
|
$ret = undef unless rmdir("$dir/$c");
|
||
|
} else {
|
||
|
$ret = undef unless unlink("$dir/$c");
|
||
|
}
|
||
|
}
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
sub linktree {
|
||
|
my ($from, $to) = @_;
|
||
|
return unless -d $from;
|
||
|
mkdir_p($to);
|
||
|
my @todo = sort(ls($from));
|
||
|
while (@todo) {
|
||
|
my $f = shift @todo;
|
||
|
if (! -l "$from/$f" && -d _) {
|
||
|
mkdir_p("$to/$f");
|
||
|
unshift @todo, map {"$f/$_"} ls("$from/$f");
|
||
|
} else {
|
||
|
link("$from/$f", "$to/$f") || die("link $from/$f $to/$f: $!\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub treeinfo {
|
||
|
my ($dir) = @_;
|
||
|
my @info;
|
||
|
my @todo = sort(ls($dir));
|
||
|
while (@todo) {
|
||
|
my $f = shift @todo;
|
||
|
my @s = lstat("$dir/$f");
|
||
|
next unless @s;
|
||
|
if (-d _) {
|
||
|
push @info, "$f";
|
||
|
unshift @todo, map {"$f/$_"} ls("$dir/$f");
|
||
|
} else {
|
||
|
push @info, "$f $s[9]/$s[7]/$s[1]";
|
||
|
}
|
||
|
}
|
||
|
return \@info;
|
||
|
}
|
||
|
|
||
|
sub xfork {
|
||
|
my $pid;
|
||
|
while (1) {
|
||
|
$pid = fork();
|
||
|
last if defined $pid;
|
||
|
die("fork: $!\n") if $! != POSIX::EAGAIN;
|
||
|
sleep(5);
|
||
|
}
|
||
|
return $pid;
|
||
|
}
|
||
|
|
||
|
sub cp {
|
||
|
my ($from, $to, $tof) = @_;
|
||
|
local *F;
|
||
|
local *T;
|
||
|
open(F, '<', $from) || die("$from: $!\n");
|
||
|
open(T, '>', $to) || die("$to: $!\n");
|
||
|
my $buf;
|
||
|
while (sysread(F, $buf, 8192)) {
|
||
|
(syswrite(T, $buf) || 0) == length($buf) || die("$to write: $!\n");
|
||
|
}
|
||
|
close(F);
|
||
|
close(T) || die("$to: $!\n");
|
||
|
if (defined($tof)) {
|
||
|
rename($to, $tof) || die("rename $to $tof: $!\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub checkutf8 {
|
||
|
my ($oct) = @_;
|
||
|
Encode::_utf8_off($oct);
|
||
|
return 1 unless defined $oct;
|
||
|
return 1 unless $oct =~ /[\200-\377]/;
|
||
|
eval {
|
||
|
Encode::_utf8_on($oct);
|
||
|
encode('UTF-8', $oct, Encode::FB_CROAK);
|
||
|
};
|
||
|
return $@ ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
sub str2utf8 {
|
||
|
my ($oct) = @_;
|
||
|
return $oct unless defined $oct;
|
||
|
return $oct unless $oct =~ /[^\011\012\015\040-\176]/s;
|
||
|
eval {
|
||
|
Encode::_utf8_on($oct);
|
||
|
$oct = encode('UTF-8', $oct, Encode::FB_CROAK);
|
||
|
};
|
||
|
if ($@) {
|
||
|
# assume iso-8859-1
|
||
|
eval {
|
||
|
Encode::_utf8_off($oct);
|
||
|
$oct = encode('UTF-8', $oct, Encode::FB_CROAK);
|
||
|
};
|
||
|
if ($@) {
|
||
|
Encode::_utf8_on($oct);
|
||
|
$oct = encode('UTF-8', $oct, Encode::FB_XMLCREF);
|
||
|
}
|
||
|
}
|
||
|
Encode::_utf8_off($oct); # just in case...
|
||
|
return $oct;
|
||
|
}
|
||
|
|
||
|
sub data2utf8 {
|
||
|
my ($d) = @_;
|
||
|
if (ref($d) eq 'ARRAY') {
|
||
|
for my $dd (@$d) {
|
||
|
if (ref($dd) eq '') {
|
||
|
$dd = str2utf8($dd);
|
||
|
} else {
|
||
|
data2utf8($dd);
|
||
|
}
|
||
|
}
|
||
|
} elsif (ref($d) eq 'HASH') {
|
||
|
for my $dd (keys %$d) {
|
||
|
if (ref($d->{$dd}) eq '') {
|
||
|
$d->{$dd} = str2utf8($d->{$dd});
|
||
|
} else {
|
||
|
data2utf8($d->{$dd});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub str2utf8xml {
|
||
|
my ($oct) = @_;
|
||
|
return $oct unless defined $oct;
|
||
|
return $oct unless $oct =~ /[^\011\012\015\040-\176]/s;
|
||
|
$oct = str2utf8($oct);
|
||
|
Encode::_utf8_on($oct);
|
||
|
# xml does not accept all utf8 chars, escape the illegal
|
||
|
$oct =~ s/([\000-\010\013\014\016-\037\177])/sprintf("&#x%x;",ord($1))/sge;
|
||
|
$oct =~ s/([\x{d800}-\x{dfff}\x{fffe}\x{ffff}])/sprintf("&#x%x;",ord($1))/sge;
|
||
|
Encode::_utf8_off($oct);
|
||
|
return $oct;
|
||
|
}
|
||
|
|
||
|
sub data2utf8xml {
|
||
|
my ($d) = @_;
|
||
|
if (ref($d) eq 'ARRAY') {
|
||
|
for my $dd (@$d) {
|
||
|
if (ref($dd) eq '') {
|
||
|
$dd = str2utf8xml($dd);
|
||
|
} else {
|
||
|
data2utf8xml($dd);
|
||
|
}
|
||
|
}
|
||
|
} elsif (ref($d) eq 'HASH') {
|
||
|
for my $dd (keys %$d) {
|
||
|
if (ref($d->{$dd}) eq '') {
|
||
|
$d->{$dd} = str2utf8xml($d->{$dd});
|
||
|
} else {
|
||
|
data2utf8xml($d->{$dd});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub waituntilgone {
|
||
|
my ($fn, $timeout) = @_;
|
||
|
while (1) {
|
||
|
return 1 unless -e $fn;
|
||
|
return 0 if defined($timeout) && $timeout <= 0;
|
||
|
select(undef, undef, undef, .1);
|
||
|
$timeout -= .1 if defined $timeout;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub lockopen {
|
||
|
my ($fg, $op, $fn, $nonfatal) = @_;
|
||
|
|
||
|
local *F = $fg;
|
||
|
while (1) {
|
||
|
if (!open(F, $op, $fn)) {
|
||
|
return undef if $nonfatal;
|
||
|
die("$fn: $!\n");
|
||
|
}
|
||
|
flock(F, LOCK_EX) || die("flock $fn: $!\n");
|
||
|
my @s = stat(F);
|
||
|
return 1 if @s && $s[3];
|
||
|
close F;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub lockcheck {
|
||
|
my ($op, $fn) = @_;
|
||
|
local *F;
|
||
|
while (1) {
|
||
|
if (!open(F, $op, $fn)) {
|
||
|
return -1;
|
||
|
}
|
||
|
if (!flock(F, LOCK_EX | LOCK_NB)) {
|
||
|
close(F);
|
||
|
return 0;
|
||
|
}
|
||
|
my @s = stat(F);
|
||
|
close F;
|
||
|
return 1 if @s && $s[3];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub lockopenxml {
|
||
|
my ($fg, $op, $fn, $dtd, $nonfatal) = @_;
|
||
|
if (!lockopen($fg, $op, $fn, $nonfatal)) {
|
||
|
die("$fn: $!\n") unless $nonfatal;
|
||
|
return undef;
|
||
|
}
|
||
|
my $d = readxml($fn, $dtd, $nonfatal);
|
||
|
if (!$d) {
|
||
|
local *F = $fg;
|
||
|
close F;
|
||
|
}
|
||
|
return $d;
|
||
|
}
|
||
|
|
||
|
sub lockcreatexml {
|
||
|
my ($fg, $fn, $fnf, $dd, $dtd) = @_;
|
||
|
|
||
|
local *F = $fg;
|
||
|
writexml($fn, undef, $dd, $dtd);
|
||
|
open(F, '<', $fn) || die("$fn: $!\n");
|
||
|
flock(F, LOCK_EX | LOCK_NB) || die("lock: $!\n");
|
||
|
if (!link($fn, $fnf)) {
|
||
|
unlink($fn);
|
||
|
close F;
|
||
|
return undef;
|
||
|
}
|
||
|
unlink($fn);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
sub isotime {
|
||
|
my ($t) = @_;
|
||
|
my @lt = localtime($t || time());
|
||
|
return sprintf "%04d-%02d-%02d %02d:%02d:%02d", $lt[5] + 1900, $lt[4] + 1, @lt[3,2,1,0];
|
||
|
}
|
||
|
|
||
|
# XXX: does that really belong here?
|
||
|
#
|
||
|
# Algorithm:
|
||
|
# each enable/disable has a score:
|
||
|
# +1 if it's a disable
|
||
|
# +2 if the arch matches
|
||
|
# +4 if the repo matches
|
||
|
#
|
||
|
sub enabled {
|
||
|
my ($repoid, $disen, $default, $arch) = @_;
|
||
|
|
||
|
# filter matching elements, check for shortcuts
|
||
|
return $default unless $disen;
|
||
|
my @dis = grep { (!defined($_->{'arch'}) || $_->{'arch'} eq $arch) &&
|
||
|
(!defined($_->{'repository'}) || $_->{'repository'} eq $repoid)
|
||
|
} @{$disen->{'disable'} || []};
|
||
|
return 1 if !@dis && $default;
|
||
|
my @ena = grep { (!defined($_->{'arch'}) || $_->{'arch'} eq $arch) &&
|
||
|
(!defined($_->{'repository'}) || $_->{'repository'} eq $repoid)
|
||
|
} @{$disen->{'enable'} || []};
|
||
|
return @dis ? 0 : $default unless @ena;
|
||
|
return @ena ? 1 : $default unless @dis;
|
||
|
|
||
|
# have @dis and @ena, need to do score thing...
|
||
|
my $disscore = 0;
|
||
|
for (@dis) {
|
||
|
my $score = 1;
|
||
|
$score += 2 if defined($_->{'arch'});
|
||
|
$score += 4 if defined($_->{'repository'});
|
||
|
if ($score > $disscore) {
|
||
|
return 0 if $score == 7; # can't max this!
|
||
|
$disscore = $score;
|
||
|
}
|
||
|
}
|
||
|
my $enascore = 0;
|
||
|
for (@ena) {
|
||
|
my $score = 0;
|
||
|
$score += 2 if defined($_->{'arch'});
|
||
|
$score += 4 if defined($_->{'repository'});
|
||
|
if ($score > $enascore) {
|
||
|
return 1 if $enascore == 6; # can't max this!
|
||
|
$enascore = $score;
|
||
|
}
|
||
|
}
|
||
|
return $enascore > $disscore ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
sub store {
|
||
|
my ($fn, $fnf, $dd) = @_;
|
||
|
if ($fdatasync_before_rename && defined($fnf)) {
|
||
|
local *F;
|
||
|
open(F, '>', $fn) || die("$fn: $!\n");
|
||
|
if (!Storable::nstore_fd($dd, \*F)) {
|
||
|
die("nstore_fd $fn: $!\n");
|
||
|
}
|
||
|
(\*F)->flush();
|
||
|
do_fdatasync(fileno(F));
|
||
|
close(F) || die("$fn close: $!\n");
|
||
|
} else {
|
||
|
if (!Storable::nstore($dd, $fn)) {
|
||
|
die("nstore $fn: $!\n");
|
||
|
}
|
||
|
}
|
||
|
return unless defined $fnf;
|
||
|
$! = 0;
|
||
|
rename($fn, $fnf) || die("rename $fn $fnf: $!\n");
|
||
|
}
|
||
|
|
||
|
sub retrieve {
|
||
|
my ($fn, $nonfatal) = @_;
|
||
|
my $dd;
|
||
|
if (!$nonfatal) {
|
||
|
$dd = ref($fn) ? Storable::fd_retrieve($fn) : Storable::retrieve($fn);
|
||
|
die("retrieve $fn: $!\n") unless $dd;
|
||
|
} else {
|
||
|
eval {
|
||
|
$dd = ref($fn) ? Storable::fd_retrieve($fn) : Storable::retrieve($fn);
|
||
|
};
|
||
|
if (!$dd && $nonfatal == 2) {
|
||
|
if ($@) {
|
||
|
warn($@);
|
||
|
} else {
|
||
|
warn("retrieve $fn: $!\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return $dd;
|
||
|
}
|
||
|
|
||
|
sub ping {
|
||
|
my ($pingfile) = @_;
|
||
|
local *F;
|
||
|
if (sysopen(F, $pingfile, POSIX::O_WRONLY|POSIX::O_NONBLOCK)) {
|
||
|
syswrite(F, 'x');
|
||
|
close(F);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub restartexit {
|
||
|
my ($arg, $name, $runfile, $pingfile) = @_;
|
||
|
return unless $arg;
|
||
|
if ($arg eq '--stop' || $arg eq '--exit') {
|
||
|
if (!(-e "$runfile.lock") || lockcheck('>>', "$runfile.lock")) {
|
||
|
print "$name not running.\n";
|
||
|
exit 0;
|
||
|
}
|
||
|
print "exiting $name...\n";
|
||
|
BSUtil::touch("$runfile.exit");
|
||
|
ping($pingfile) if $pingfile;
|
||
|
BSUtil::waituntilgone("$runfile.exit");
|
||
|
exit(0);
|
||
|
}
|
||
|
if ($ARGV[0] eq '--restart') {
|
||
|
die("$name not running.\n") if !(-e "$runfile.lock") || BSUtil::lockcheck('>>', "$runfile.lock");
|
||
|
print "restarting $name...\n";
|
||
|
BSUtil::touch("$runfile.restart");
|
||
|
ping($pingfile) if $pingfile;
|
||
|
BSUtil::waituntilgone("$runfile.restart");
|
||
|
exit(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
1;
|