our
$VERSION
=
'0.99.1_2'
;
use
5.006;
no
warnings
"experimental::lexical_subs"
;
run
=>
sub
{ run(
undef
,
@_
) },
spawn
=>
sub
{ spawn(
undef
,
@_
) },
syscmd
=>
sub
{ syscmd(
undef
,
@_
) },
runsub
=>
sub
{ syscmd(
undef
,
@_
)->runsub },
spawnsub
=>
sub
{ syscmd(
undef
,
@_
)->spawnsub },
};
our
(
@_CLASS
,
$_FIELDS
,
%_NEW
);
sub
new {
my
$class
=
shift
;
my
$CLASS
=
ref
$class
||
$class
;
$_NEW
{
$CLASS
} //=
do
{
my
@possible
= (
$CLASS
);
if
(
defined
&{
"${CLASS}::DOES"
} ) {
push
@possible
,
grep
!/^${CLASS}$/,
$CLASS
->DOES(
'*'
);
}
my
(
@new
,
@build
);
while
(
@possible
) {
no
strict
'refs'
;
my
$c
=
shift
@possible
;
push
@new
,
$c
.
'::_NEW'
if
exists
&{
$c
.
'::_NEW'
};
push
@build
,
$c
.
'::BUILD'
if
exists
&{
$c
.
'::BUILD'
};
push
@possible
, @{
$c
.
'::ISA'
};
}
[ [
reverse
(
@new
) ], [
reverse
(
@build
) ] ];
};
my
$self
= {
@_
?
@_
> 1 ?
@_
: %{
$_
[0] } : () };
bless
$self
,
$CLASS
;
my
$attrs
= {
map
{ (
$_
=> 1 ) }
keys
%$self
};
map
{
$self
->
$_
(
$attrs
) } @{
$_NEW
{
$CLASS
}->[0] };
{
local
$Carp::CarpLevel
= 3;
Carp::carp(
"Sys::Cmd: unexpected argument '$_'"
)
for
keys
%$attrs
;
}
map
{
$self
->
$_
} @{
$_NEW
{
$CLASS
}->[1] };
$self
;
}
sub
_NEW {
CORE::state
$fix_FIELDS
=
do
{
$_FIELDS
= {
@_CLASS
> 1 ?
@_CLASS
: %{
$_CLASS
[0] } };
$_FIELDS
=
$_FIELDS
->{
'FIELDS'
}
if
exists
$_FIELDS
->{
'FIELDS'
};
};
if
(
my
@missing
=
grep
{ not
exists
$_
[0]->{
$_
} }
'cmd'
) {
Carp::croak(
'Sys::Cmd required initial argument(s): '
.
join
(
', '
,
@missing
) );
}
$_
[0]{
'cmd'
} =
eval
{
$_FIELDS
->{
'cmd'
}->{
'isa'
}->(
$_
[0]{
'cmd'
} ) };
Carp::confess(
'Sys::Cmd cmd: '
. $@ )
if
$@;
$_
[0]{
'dir'
} =
eval
{
$_FIELDS
->{
'dir'
}->{
'isa'
}->(
$_
[0]{
'dir'
} ) }
if
exists
$_
[0]{
'dir'
};
Carp::confess(
'Sys::Cmd dir: '
. $@ )
if
$@;
$_
[0]{
'env'
} =
eval
{
$_FIELDS
->{
'env'
}->{
'isa'
}->(
$_
[0]{
'env'
} ) }
if
exists
$_
[0]{
'env'
};
Carp::confess(
'Sys::Cmd env: '
. $@ )
if
$@;
$_
[0]{
'mock'
} =
eval
{
$_FIELDS
->{
'mock'
}->{
'isa'
}->(
$_
[0]{
'mock'
} ) }
if
exists
$_
[0]{
'mock'
};
Carp::confess(
'Sys::Cmd mock: '
. $@ )
if
$@;
map
{
delete
$_
[1]->{
$_
} }
'cmd'
,
'dir'
,
'encoding'
,
'env'
,
'err'
,
'input'
,
'mock'
,
'on_exit'
,
'out'
;
}
sub
__RO {
my
(
undef
,
undef
,
undef
,
$sub
) =
caller
(1);
Carp::confess(
"attribute $sub is read-only"
);
}
sub
cmd { __RO()
if
@_
> 1;
$_
[0]{
'cmd'
} //
undef
}
sub
dir { __RO()
if
@_
> 1;
$_
[0]{
'dir'
} //
undef
}
sub
encoding {
__RO()
if
@_
> 1;
$_
[0]{
'encoding'
} //=
$_FIELDS
->{
'encoding'
}->{
'default'
}->(
$_
[0] );
}
sub
env { __RO()
if
@_
> 1;
$_
[0]{
'env'
} //
undef
}
sub
err { __RO()
if
@_
> 1;
$_
[0]{
'err'
} //
undef
}
sub
input { __RO()
if
@_
> 1;
$_
[0]{
'input'
} //
undef
}
sub
mock {
if
(
@_
> 1 ) {
$_
[0]{
'mock'
} =
eval
{
$_FIELDS
->{
'mock'
}->{
'isa'
}->(
$_
[1] ) };
Carp::confess(
'invalid (Sys::Cmd::mock) value: '
. $@ )
if
$@;
}
$_
[0]{
'mock'
} //
undef
;
}
sub
on_exit {
if
(
@_
> 1 ) {
$_
[0]{
'on_exit'
} =
$_
[1]; }
$_
[0]{
'on_exit'
} //
undef
;
}
sub
out { __RO()
if
@_
> 1;
$_
[0]{
'out'
} //
undef
}
sub
_dump {
my
$self
=
shift
;
my
$x
=
do
{
no
warnings
'once'
;
local
$Data::Dumper::Indent
= 1;
local
$Data::Dumper::Maxdepth
= (
shift
// 2 );
local
$Data::Dumper::Sortkeys
= 1;
Data::Dumper::Dumper(
$self
);
};
$x
=~ s/.*?{/{/;
$x
=~ s/}.*?\n$/}/;
my
$i
= 0;
my
@list
;
do
{
@list
=
caller
(
$i
++ );
}
until
$list
[3] eq __PACKAGE__ .
'::_dump'
;
warn
"$self $x at $list[1]:$list[2]\n"
;
}
@_CLASS
=
{
cmd
=> {
isa
=>
sub
{
ref
$_
[0] eq
'ARRAY'
|| _croak(
"cmd must be ARRAYREF"
);
@{
$_
[0] } || _croak(
"Missing cmd elements"
);
if
(
grep
{ !
defined
$_
} @{
$_
[0] } ) {
_croak(
'cmd array cannot contain undef elements'
);
}
$_
[0];
},
required
=> 1,
},
encoding
=> {
default
=>
sub
{
':utf8'
}, },
env
=> {
isa
=>
sub
{
ref
$_
[0] eq
'HASH'
|| _croak(
"env must be HASHREF"
);
$_
[0];
},
},
dir
=> {
isa
=>
sub
{
-d
$_
[0] || _croak(
"directory not found: $_[0]"
);
$_
[0];
},
},
input
=> {},
out
=> {},
err
=> {},
mock
=> {
is
=>
'rw'
,
isa
=>
sub
{
( ( not
defined
$_
[0] ) ||
'CODE'
eq
ref
$_
[0] )
|| _croak(
'must be CODEref'
);
$_
[0];
},
},
on_exit
=> {
is
=>
'rw'
, },
};
sub
_croak {
local
$Carp::CarpInternal
{
'Sys::Cmd'
} = 1;
local
$Carp::CarpInternal
{
'Sys::Cmd::Process'
} = 1;
Carp::croak(
@_
);
}
my
sub
merge_args {
my
$template
=
shift
;
my
(
@cmd
,
$opts
);
foreach
my
$arg
(
@_
) {
if
(
ref
(
$arg
) eq
'HASH'
) {
_croak( __PACKAGE__ .
': only a single hashref allowed'
)
if
$opts
;
$opts
=
$arg
;
}
else
{
push
(
@cmd
,
$arg
);
}
}
$opts
//= {};
if
(
$template
) {
$opts
->{cmd} = [
$template
->cmdline,
@cmd
];
if
(
exists
$opts
->{env} ) {
my
%env
= (
each
%{
$template
->env },
each
%{
$opts
->{env} } );
$opts
->{env} = \
%env
;
}
return
{
%$template
,
%$opts
};
}
_croak(
'$cmd must be defined'
)
unless
@cmd
&&
defined
$cmd
[0];
if
(
'CODE'
ne
ref
(
$cmd
[0] ) and not
$opts
->{mock} ) {
delete
$opts
->{mock};
if
( File::Spec->splitdir(
$cmd
[0] ) == 1 ) {
$cmd
[0] = File::Which::which(
$cmd
[0] )
|| _croak(
'command not found: '
.
$cmd
[0] );
}
if
( !-x
$cmd
[0] ) {
_croak(
'command not executable: '
.
$cmd
[0] );
}
}
$opts
->{cmd} = \
@cmd
;
$opts
;
}
sub
cmdline {
my
$self
=
shift
;
if
(
wantarray
) {
return
@{
$self
->cmd };
}
else
{
return
join
(
' '
, @{
$self
->cmd } );
}
}
sub
run {
my
$self
=
shift
;
my
$opts
= merge_args(
$self
,
@_
);
my
$ref_out
=
delete
$opts
->{out};
my
$ref_err
=
delete
$opts
->{err};
my
$proc
= Sys::Cmd::Process->new(
$opts
);
my
@err
=
$proc
->stderr->getlines;
my
@out
=
$proc
->stdout->getlines;
$proc
->wait_child;
if
(
$proc
->signal != 0 ) {
_croak(
sprintf
(
'%s[%d] %s [signal: %d core: %d]'
,
join
(
''
,
@err
),
$proc
->pid,
scalar
$proc
->cmdline,
$proc
->signal,
$proc
->core
)
);
}
elsif
(
$proc
->
exit
!= 0 ) {
_croak(
sprintf
(
'%s[%d] %s [exit: %d]'
,
join
(
''
,
@err
),
$proc
->pid,
scalar
$proc
->cmdline,
$proc
->
exit
)
);
}
if
(
$ref_err
) {
$$ref_err
=
join
''
,
@err
;
}
elsif
(
@err
) {
local
@Carp::CARP_NOT
= (__PACKAGE__);
Carp::carp
@err
;
}
if
(
$ref_out
) {
$$ref_out
=
join
''
,
@out
;
}
elsif
(
defined
(
my
$wa
=
wantarray
) ) {
return
@out
if
$wa
;
return
join
(
''
,
@out
);
}
}
sub
spawn {
my
$self
=
shift
;
Sys::Cmd::Process->new( merge_args(
$self
,
@_
) );
}
sub
syscmd {
my
$self
=
shift
;
Sys::Cmd->new( merge_args(
$self
,
@_
) );
}
sub
runsub {
my
$self
=
shift
;
sub
{
$self
->run(
@_
) };
}
sub
spawnsub {
my
$self
=
shift
;
sub
{
$self
->spawn(
@_
) };
}
our
$VERSION
=
'0.99.1_2'
;
use
parent -norequire,
'Sys::Cmd'
;
our
(
@_CLASS
,
$_FIELDS
,
%_NEW
);
sub
new {
my
$class
=
shift
;
my
$CLASS
=
ref
$class
||
$class
;
$_NEW
{
$CLASS
} //=
do
{
my
@possible
= (
$CLASS
);
if
(
defined
&{
"${CLASS}::DOES"
} ) {
push
@possible
,
grep
!/^${CLASS}$/,
$CLASS
->DOES(
'*'
);
}
my
(
@new
,
@build
);
while
(
@possible
) {
no
strict
'refs'
;
my
$c
=
shift
@possible
;
push
@new
,
$c
.
'::_NEW'
if
exists
&{
$c
.
'::_NEW'
};
push
@build
,
$c
.
'::BUILD'
if
exists
&{
$c
.
'::BUILD'
};
push
@possible
, @{
$c
.
'::ISA'
};
}
[ [
reverse
(
@new
) ], [
reverse
(
@build
) ] ];
};
my
$self
= {
@_
?
@_
> 1 ?
@_
: %{
$_
[0] } : () };
bless
$self
,
$CLASS
;
my
$attrs
= {
map
{ (
$_
=> 1 ) }
keys
%$self
};
map
{
$self
->
$_
(
$attrs
) } @{
$_NEW
{
$CLASS
}->[0] };
{
local
$Carp::CarpLevel
= 3;
Carp::carp(
"Sys::Cmd::Process: unexpected argument '$_'"
)
for
keys
%$attrs
;
}
map
{
$self
->
$_
} @{
$_NEW
{
$CLASS
}->[1] };
$self
;
}
sub
_NEW {
CORE::state
$fix_FIELDS
=
do
{
$_FIELDS
= {
@_CLASS
> 1 ?
@_CLASS
: %{
$_CLASS
[0] } };
$_FIELDS
=
$_FIELDS
->{
'FIELDS'
}
if
exists
$_FIELDS
->{
'FIELDS'
};
};
map
{
delete
$_
[1]->{
$_
} }
'_coderef'
;
}
sub
__RO {
my
(
undef
,
undef
,
undef
,
$sub
) =
caller
(1);
Carp::confess(
"attribute $sub is read-only"
);
}
sub
_coderef {
__RO()
if
@_
> 1;
$_
[0]{
'_coderef'
} //=
$_FIELDS
->{
'_coderef'
}->{
'default'
}->(
$_
[0] );
}
sub
core {
if
(
@_
> 1 ) {
$_
[0]{
'core'
} =
$_
[1]; }
$_
[0]{
'core'
} //=
$_FIELDS
->{
'core'
}->{
'default'
}->(
$_
[0] );
}
sub
exit
{
if
(
@_
> 1 ) {
$_
[0]{
'exit'
} =
$_
[1]; }
$_
[0]{
'exit'
} //=
$_FIELDS
->{
'exit'
}->{
'default'
}->(
$_
[0] );
}
sub
has_exit {
exists
$_
[0]{
'exit'
} }
sub
pid {
if
(
@_
> 1 ) {
$_
[0]{
'pid'
} =
$_
[1]; }
$_
[0]{
'pid'
} //
undef
;
}
sub
signal {
if
(
@_
> 1 ) {
$_
[0]{
'signal'
} =
$_
[1]; }
$_
[0]{
'signal'
} //=
$_FIELDS
->{
'signal'
}->{
'default'
}->(
$_
[0] );
}
sub
stderr {
if
(
@_
> 1 ) {
$_
[0]{
'stderr'
} =
$_
[1]; }
$_
[0]{
'stderr'
} //=
$_FIELDS
->{
'stderr'
}->{
'default'
}->(
$_
[0] );
}
sub
stdin {
if
(
@_
> 1 ) {
$_
[0]{
'stdin'
} =
$_
[1]; }
$_
[0]{
'stdin'
} //=
$_FIELDS
->{
'stdin'
}->{
'default'
}->(
$_
[0] );
}
sub
stdout {
if
(
@_
> 1 ) {
$_
[0]{
'stdout'
} =
$_
[1]; }
$_
[0]{
'stdout'
} //=
$_FIELDS
->{
'stdout'
}->{
'default'
}->(
$_
[0] );
}
sub
_dump {
my
$self
=
shift
;
my
$x
=
do
{
no
warnings
'once'
;
local
$Data::Dumper::Indent
= 1;
local
$Data::Dumper::Maxdepth
= (
shift
// 2 );
local
$Data::Dumper::Sortkeys
= 1;
Data::Dumper::Dumper(
$self
);
};
$x
=~ s/.*?{/{/;
$x
=~ s/}.*?\n$/}/;
my
$i
= 0;
my
@list
;
do
{
@list
=
caller
(
$i
++ );
}
until
$list
[3] eq __PACKAGE__ .
'::_dump'
;
warn
"$self $x at $list[1]:$list[2]\n"
;
}
@_CLASS
=
{
_coderef
=> {
default
=>
sub
{
my
$c
=
$_
[0]->cmd->[0];
ref
(
$c
) eq
'CODE'
?
$c
:
undef
;
},
},
pid
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
},
stdin
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
default
=>
sub
{ IO::Handle->new },
},
stdout
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
default
=>
sub
{ IO::Handle->new },
},
stderr
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
default
=>
sub
{ IO::Handle->new },
},
exit
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
predicate
=> 1,
default
=>
sub
{
Sys::Cmd::_croak(
'Process status values invalid before wait_child()'
);
},
},
signal
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
default
=>
sub
{
Sys::Cmd::_croak(
'Process status values invalid before wait_child()'
);
},
},
core
=> {
is
=>
'rw'
,
init_arg
=>
undef
,
default
=>
sub
{
Sys::Cmd::_croak(
'Process status values invalid before wait_child()'
);
},
},
};
sub
_spawn {
my
$self
=
shift
;
my
$fd0
= IO::Handle->new_from_fd( 0,
'r'
);
my
$fd1
= IO::Handle->new_from_fd( 1,
'w'
);
my
$fd2
= IO::Handle->new_from_fd( 2,
'w'
);
open
my
$old_fd0
,
'<&'
, 0;
open
my
$old_fd1
,
'>&'
, 1;
open
my
$old_fd2
,
'>&'
, 2;
pipe
(
my
$child_in
,
$self
->stdin ) ||
die
"pipe: $!"
;
pipe
(
$self
->stdout,
my
$child_out
) ||
die
"pipe: $!"
;
pipe
(
$self
->stderr,
my
$child_err
) ||
die
"pipe: $!"
;
Proc::FastSpawn::fd_inherit(
$_
, 1 )
for
0, 1, 2;
Proc::FastSpawn::fd_inherit(
fileno
(
$_
), 0 )
for
$old_fd0
,
$old_fd1
,
$old_fd2
,
$child_in
,
$child_out
,
$child_err
,
$self
->stdin,
$self
->stdout,
$self
->stderr;
my
$cmd_as_octets
=
[
map
{
my
$s
=
$_
; utf8::is_utf8(
$s
) ? utf8::encode(
$s
) ||
$s
:
$s
}
@{
$self
->cmd } ];
eval
{
open
$fd0
,
'<&'
,
fileno
(
$child_in
);
open
$fd1
,
'>&'
,
fileno
(
$child_out
);
open
$fd2
,
'>&'
,
fileno
(
$child_err
);
$self
->pid(
Proc::FastSpawn::spawn(
$cmd_as_octets
->[0],
$cmd_as_octets
,
[
map
{
$_
.
'='
. (
defined
$ENV
{
$_
} ?
$ENV
{
$_
} :
''
) }
keys
%ENV
]
)
);
};
my
$err
= $@;
open
$fd0
,
'<&'
,
fileno
(
$old_fd0
);
open
$fd1
,
'>&'
,
fileno
(
$old_fd1
);
open
$fd2
,
'>&'
,
fileno
(
$old_fd2
);
Sys::Cmd::_croak(
$err
)
if
$err
;
Sys::Cmd::_croak(
'Unable to spawn child'
)
unless
defined
$self
->pid;
close
(
$_
)
for
$old_fd0
,
$old_fd1
,
$old_fd2
,
$child_in
,
$child_out
,
$child_err
;
return
;
}
sub
_fork {
my
$self
=
shift
;
pipe
(
my
$child_in
,
$self
->stdin ) ||
die
"pipe: $!"
;
pipe
(
$self
->stdout,
my
$child_out
) ||
die
"pipe: $!"
;
pipe
(
$self
->stderr,
my
$child_err
) ||
die
"pipe: $!"
;
$self
->pid(
fork
() );
if
( !
defined
$self
->pid ) {
my
$why
= $!;
die
"fork: $why"
;
}
if
(
$self
->pid > 0 ) {
close
$child_in
;
close
$child_out
;
close
$child_err
;
return
;
}
$self
->
exit
(0);
$child_err
->autoflush(1);
my
$enc
=
$self
->encoding;
foreach
my
$quad
(
[ \
*STDIN
,
'<&='
.
$enc
,
fileno
(
$child_in
), 0 ],
[ \
*STDOUT
,
'>&='
.
$enc
,
fileno
(
$child_out
), 1 ],
[ \
*STDERR
,
'>&='
.
$enc
,
fileno
(
$child_err
), 1 ]
)
{
my
(
$fh
,
$mode
,
$fileno
,
$autoflush
) =
@$quad
;
open
(
$fh
,
$mode
,
$fileno
)
or
print
$child_err
sprintf
"[%d] open %s, %s: %s\n"
,
$self
->pid,
$fh
,
$mode
, $!;
$fh
->autoflush(1)
if
$autoflush
;
}
close
$self
->stdin;
close
$self
->stdout;
close
$self
->stderr;
close
$child_in
;
close
$child_out
;
close
$child_err
;
if
(
my
$code
=
$self
->_coderef ) {
$code
->();
_exit(0);
}
exec
( @{
$self
->cmd } );
die
"exec: $!"
;
}
sub
BUILD {
my
$self
=
shift
;
Carp::carp
'"out" attribute ignored'
if
defined
$self
->out;
Carp::carp
'"err" attribute ignored'
if
defined
$self
->err;
if
(
my
$mock
=
$self
->mock ) {
my
$ref
=
$mock
->(
$self
);
my
$out
=
shift
@$ref
//
''
;
my
$err
=
shift
@$ref
//
''
;
open
my
$outfd
,
'<'
, \
$out
||
die
"open \$out: $!"
;
open
my
$errfd
,
'<'
, \
$err
||
die
"open \$err: $!"
;
$self
->pid( -$$ );
$self
->stdout(
$outfd
);
$self
->stderr(
$errfd
);
$self
->mock(
sub
{
$ref
} );
$log
->debugf(
'[%d] %s [%s]'
,
$self
->pid,
scalar
$self
->cmdline,
$self
->encoding
);
return
;
}
my
$dir
=
$self
->dir;
require
File::
chdir
if
$dir
;
no
warnings
'once'
;
local
$File::chdir::CWD
=
$dir
if
$dir
;
local
%ENV
=
%ENV
;
if
(
defined
(
my
$x
=
$self
->env ) ) {
while
(
my
(
$key
,
$val
) =
each
%$x
) {
if
(
defined
$val
) {
$ENV
{
$key
} =
$val
;
}
else
{
delete
$ENV
{
$key
};
}
}
}
$self
->_coderef ?
$self
->_fork :
$self
->_spawn;
$self
->stdin->autoflush(1);
my
$enc
=
$self
->encoding;
binmode
(
$self
->stdin,
$enc
) or
warn
"binmode stdin: $!"
;
binmode
(
$self
->stdout,
$enc
) or
warn
"binmode stdout: $!"
;
binmode
(
$self
->stderr,
$enc
) or
warn
"binmode stderr: $!"
;
$log
->debugf(
'[%d] %s [%s]'
,
$self
->pid,
scalar
$self
->cmdline,
$enc
);
if
(
defined
(
my
$input
=
$self
->input ) ) {
local
$SIG
{PIPE} =
sub
{
warn
"Broken pipe when writing to:"
.
$self
->cmdline };
if
(
'ARRAY'
eq
ref
$input
&&
@$input
) {
$self
->stdin->
print
(
@$input
);
}
elsif
(
length
$input
) {
$self
->stdin->
print
(
$input
);
}
$self
->stdin->
close
;
}
return
;
}
sub
close
{
my
$self
=
shift
;
foreach
my
$h
(
qw/stdin stdout stderr/
) {
my
$fh
=
$self
->
$h
or
next
;
$fh
->opened or
next
;
if
(
$h
eq
'stderr'
) {
warn
sprintf
(
'[%d] uncollected stderr: %s'
,
$self
->pid // -1,
$_
)
for
$self
->stderr->getlines;
}
$fh
->
close
|| Carp::carp
"error closing $h: $!"
;
}
return
;
}
sub
wait_child {
my
$self
=
shift
;
return
unless
defined
$self
->pid;
return
$self
->
exit
if
$self
->has_exit;
if
(
$self
->mock ) {
my
(
$exit
,
$signal
,
$core
) = @{
$self
->mock->() };
$self
->
exit
(
$exit
// 0 );
$self
->signal(
$signal
// 0 );
$self
->core(
$core
// 0 );
}
else
{
local
$?;
local
$!;
my
$pid
=
waitpid
$self
->pid, 0;
my
$ret
= $?;
if
(
$pid
!=
$self
->pid ) {
warn
sprintf
(
'Could not reap child process %d (waitpid returned: %d)'
,
$self
->pid,
$pid
);
$ret
= 0;
}
if
(
$ret
== -1 ) {
warn
__PACKAGE__
.
' received invalid child exit status for pid '
.
$self
->pid
.
' Setting to 0'
;
$ret
= 0;
}
$self
->
exit
(
$ret
>> 8 );
$self
->signal(
$ret
& 127 );
$self
->core(
$ret
& 128 );
}
if
(
$self
->signal != 0 ) {
$log
->infof(
'[%d] %s [signal: %d core: %d]'
,
$self
->pid,
scalar
$self
->cmdline,
$self
->signal,
$self
->core
);
}
else
{
$log
->infof(
'[%d] %s [exit: %d]'
,
$self
->pid,
scalar
$self
->cmdline,
$self
->
exit
,
);
}
if
(
my
$subref
=
$self
->on_exit ) {
$subref
->(
$self
);
}
$self
->
exit
;
}
sub
DESTROY {
my
$self
=
shift
;
$self
->
close
;
$self
->wait_child;
}
1;