BEGIN
{
use
vars
qw( $VERSION $SUPPORTED_RE $SYSV_SUPPORTED $SEMOP_ARGS @EXPORT_OK %EXPORT_TAGS $N $SHEM_REPO )
;
our
$SUPPORTED_RE
=
qr/\bIPC\/
SysV\b/m;
if
(
$Config
{extensions} =~ m/
$SUPPORTED_RE
/ &&
!
$Config
{useithreads} &&
$^O !~ /^(?:Android|cygwin|dos|MSWin32|os2|VMS|riscos)/i )
{
IPC::SysV->
import
(
qw( IPC_RMID IPC_PRIVATE IPC_SET IPC_STAT IPC_CREAT IPC_EXCL IPC_NOWAIT
SEM_UNDO S_IRWXU S_IRWXG S_IRWXO
GETNCNT GETZCNT GETVAL SETVAL GETPID GETALL SETALL
shmat shmdt memread memwrite ftok )
);
our
$SYSV_SUPPORTED
= 1;
eval
(
<<'EOT' );
our $SEMOP_ARGS =
{
(LOCK_EX) =>
[
1, 0, 0, # wait for readers to finish
2, 0, 0, # wait for writers to finish
2, 1, SEM_UNDO, # assert write lock
],
(LOCK_EX | LOCK_NB) =>
[
1, 0, IPC_NOWAIT, # wait for readers to finish
2, 0, IPC_NOWAIT, # wait for writers to finish
2, 1, (SEM_UNDO | IPC_NOWAIT), # assert write lock
],
(LOCK_EX | LOCK_UN) =>
[
2, -1, (SEM_UNDO | IPC_NOWAIT),
],
(LOCK_SH) =>
[
2, 0, 0, # wait for writers to finish
1, 1, SEM_UNDO, # assert shared read lock
],
(LOCK_SH | LOCK_NB) =>
[
2, 0, IPC_NOWAIT, # wait for writers to finish
1, 1, (SEM_UNDO | IPC_NOWAIT), # assert shared read lock
],
(LOCK_SH | LOCK_UN) =>
[
1, -1, (SEM_UNDO | IPC_NOWAIT), # remove shared read lock
],
};
EOT
if
( $@ )
{
warn
(
"Error while trying to evel \$SEMOP_ARGS: $@\n"
);
}
}
else
{
our
$SYSV_SUPPORTED
= 0;
}
our
@EXPORT_OK
=
qw(LOCK_EX LOCK_SH LOCK_NB LOCK_UN)
;
our
%EXPORT_TAGS
= (
all
=> [
qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )
],
lock
=> [
qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )
],
'flock'
=> [
qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )
],
);
our
$N
=
do
{
my
$foo
=
eval
{
pack
"L!"
, 0 }; $@ ?
''
:
'!'
};
our
$SHEM_REPO
= {};
our
$VERSION
=
'v0.1.2'
;
};
sub
init
{
my
$self
=
shift
(
@_
);
$self
->{create} = 0;
$self
->{destroy} = 0;
$self
->{exclusive} = 0;
$self
->{key} =
&IPC::SysV::IPC_PRIVATE
;
$self
->{mode} = 0666;
$self
->{serial} =
''
;
$self
->{size} = SHM_BUFSIZ;
$self
->{_init_strict_use_sub} = 1;
$self
->SUPER::init(
@_
) ||
return
;
$self
->{addr} =
undef
();
$self
->{id} =
undef
();
$self
->{locked} = 0;
$self
->{owner} = $$;
$self
->{removed} = 0;
$self
->{semid} =
undef
();
return
(
$self
);
}
sub
addr {
return
(
shift
->_set_get_scalar(
'addr'
,
@_
) ); }
sub
attach
{
my
$self
=
shift
(
@_
);
my
$flags
=
shift
(
@_
);
$flags
=
$self
->flags
if
( !
defined
(
$flags
) );
my
$addr
=
$self
->addr;
return
(
$addr
)
if
(
defined
(
$addr
) );
my
$id
=
$self
->id;
return
(
$self
->error(
"No shared memory id! Have you opened it first?"
) )
if
( !
length
(
$id
) );
$addr
= shmat(
$id
,
undef
(),
$flags
);
return
(
$self
->error(
"Unable to attach to shared memory: $!"
) )
if
( !
defined
(
$addr
) );
$self
->addr(
$addr
);
return
(
$addr
);
}
sub
create {
return
(
shift
->_set_get_boolean(
'create'
,
@_
) ); }
sub
destroy {
return
(
shift
->_set_get_boolean(
'destroy'
,
@_
) ); }
sub
detach
{
my
$self
=
shift
(
@_
);
my
$addr
=
$self
->addr;
return
if
( !
defined
(
$addr
) );
my
$rv
= shmdt(
$addr
);
return
(
$self
->error(
"Unable to detach from shared memory: $!"
) )
if
( !
defined
(
$rv
) );
$self
->addr(
undef
() );
return
(
$self
);
}
sub
exclusive {
return
(
shift
->_set_get_boolean(
'exclusive'
,
@_
) ); }
sub
exists
{
my
$self
=
shift
(
@_
);
my
$opts
= {};
if
(
ref
(
$_
[0] ) eq
'HASH'
)
{
$opts
=
shift
(
@_
);
}
else
{
@$opts
{
qw( key mode size )
} =
@_
;
}
$opts
->{size} =
$self
->size
unless
(
length
(
$opts
->{size} ) );
$opts
->{size} =
int
(
$opts
->{size} );
my
$serial
;
if
(
length
(
$opts
->{key} ) )
{
$serial
=
$self
->_str2key(
$opts
->{key} );
}
else
{
$serial
=
$self
->serial;
}
my
$flags
=
$self
->flags({
mode
=> 0644 });
$flags
= (
$flags
^
&IPC::SysV::IPC_CREAT
);
my
$semid
;
local
$@;
my
$rv
=
eval
{
$semid
=
semget
(
$serial
, 3,
$flags
);
if
(
defined
(
$semid
) )
{
my
$found
=
semctl
(
$semid
, SEM_MARKER,
&IPC::SysV::GETVAL
, 0 );
semctl
(
$semid
, 0,
&IPC::SysV::IPC_RMID
, 0 );
return
(
$found
== SHM_EXISTS ? 1 : 0 );
}
else
{
return
(0)
if
( $! =~ /\bNo[[:blank:]]+such[[:blank:]]+file\b/ );
return
;
}
};
if
( $@ )
{
semctl
(
$semid
, 0,
&IPC::SysV::IPC_RMID
, 0 )
if
(
$semid
);
return
(0);
}
return
(
$rv
);
}
sub
flags
{
my
$self
=
shift
(
@_
);
my
$opts
=
$self
->_get_args_as_hash(
@_
);
$opts
->{create} =
$self
->create
unless
(
length
(
$opts
->{create} ) );
$opts
->{exclusive} =
$self
->exclusive
unless
(
length
(
$opts
->{exclusive} ) );
$opts
->{mode} =
$self
->mode
unless
(
length
(
$opts
->{mode} ) );
my
$flags
= 0;
$flags
|=
&IPC::SysV::IPC_CREAT
if
(
$opts
->{create} );
$flags
|=
&IPC::SysV::IPC_EXCL
if
(
$opts
->{exclusive} );
$flags
|= (
$opts
->{mode} || 0666 );
return
(
$flags
);
}
sub
id
{
my
$self
=
shift
(
@_
);
my
@callinfo
=
caller
;
no
warnings
'uninitialized'
;
if
(
@_
)
{
$self
->{id} =
shift
(
@_
);
}
return
(
$self
->{id} );
}
sub
key
{
my
$self
=
shift
(
@_
);
if
(
@_
)
{
$self
->{key} =
shift
(
@_
);
$self
->{serial} =
$self
->_str2key(
$self
->{key} );
}
return
(
$self
->{key} );
}
sub
lock
{
my
$self
=
shift
(
@_
);
my
$type
=
shift
(
@_
);
my
$timeout
=
shift
(
@_
);
$type
= LOCK_SH
if
( !
defined
(
$type
) );
return
(
$self
->unlock )
if
( (
$type
& LOCK_UN ) );
return
( 1 )
if
(
$self
->locked &
$type
);
$timeout
= 0
if
( !
defined
(
$timeout
) ||
$timeout
!~ /^\d+$/ );
$self
->unlock
if
(
$self
->locked );
my
$semid
=
$self
->semid ||
return
(
$self
->error(
"No semaphore id set yet."
) );
local
$@;
my
$rv
=
eval
{
local
$SIG
{ALRM} =
sub
{
die
(
"timeout"
); };
alarm
(
$timeout
);
my
$rc
=
$self
->op( @{
$SEMOP_ARGS
->{
$type
}} );
alarm
(0);
return
(
$rc
);
};
if
( $@ )
{
return
(
$self
->error(
"Unable to set a lock: $@"
) );
}
if
(
$rv
)
{
$self
->locked(
$type
);
}
else
{
return
(
$self
->error(
"Failed to set a lock on semaphore id \"$semid\": $!"
) );
}
return
(
$self
);
}
sub
locked {
return
(
shift
->_set_get_scalar(
'locked'
,
@_
) ); }
sub
mode {
return
(
shift
->_set_get_scalar(
'mode'
,
@_
) ); }
sub
op
{
my
$self
=
shift
(
@_
);
return
(
$self
->error(
"Invalid number of argument: '"
,
join
( ',
', @_ ), "'
." ) )
if
(
@_
% 3 );
my
$data
=
pack
(
"s$N*"
,
@_
);
my
$id
=
$self
->semid;
return
(
$self
->error(
"No semaphore set yet. You must open the shared memory first to set the semaphore."
) )
if
( !
length
(
$id
) );
return
(
semop
(
$id
,
$data
) );
}
sub
open
{
my
$self
=
shift
(
@_
);
my
$opts
= {};
if
(
ref
(
$_
[0] ) eq
'HASH'
)
{
$opts
=
shift
(
@_
);
}
else
{
@$opts
{
qw( key mode size )
} =
@_
;
}
$opts
->{size} =
$self
->size
unless
(
length
(
$opts
->{size} ) );
$opts
->{size} =
int
(
$opts
->{size} );
$opts
->{mode} //=
''
;
$opts
->{key} //=
''
;
my
$serial
;
if
(
length
(
$opts
->{key} ) )
{
$serial
=
$self
->_str2key(
$opts
->{key} );
}
else
{
$serial
=
$self
->serial;
}
my
$create
= 0;
if
(
$opts
->{mode} eq
'w'
||
$opts
->{key} =~ s/^>// )
{
$create
++;
}
elsif
(
$opts
->{mode} eq
'r'
||
$opts
->{key} =~ s/^<// )
{
$create
= 0;
}
else
{
$create
=
$self
->create;
}
my
$flags
=
$self
->flags(
create
=>
$create
);
my
$id
=
shmget
(
$serial
,
$opts
->{size},
$flags
);
if
(
defined
(
$id
) )
{
}
else
{
my
$newflags
= (
$flags
&
&IPC::SysV::IPC_CREAT
) ?
$flags
: (
$flags
|
&IPC::SysV::IPC_CREAT
);
my
$limit
= (
$serial
+ 10 );
while
(
$serial
<=
$limit
)
{
$id
=
shmget
(
$serial
,
$opts
->{size},
$newflags
|
&IPC::SysV::IPC_CREAT
);
$serial
++;
last
if
(
defined
(
$id
) );
}
}
if
( !
defined
(
$id
) )
{
return
(
$self
->error(
"Unable to create shared memory id with key \"$serial\" and flags \"$flags\": $!"
) );
}
$self
->serial(
$serial
);
my
$semid
=
semget
(
$serial
, 3,
$flags
);
if
( !
defined
(
$semid
) )
{
my
$newflags
= (
$flags
|
&IPC::SysV::IPC_CREAT
);
$semid
=
semget
(
$serial
, 3,
$newflags
);
return
(
$self
->error(
"Unable to get a semaphore for shared memory key \""
, (
$opts
->{key} ||
$self
->key ),
"\" wth id \"$id\": $!"
) )
if
( !
defined
(
$semid
) );
}
my
$new
=
$self
->new(
key
=>
$opts
->{key} ||
$self
->key,
debug
=>
$self
->debug,
mode
=>
$self
->mode,
destroy
=>
$self
->destroy,
) ||
return
;
$new
->id(
$id
);
$new
->semid(
$semid
);
if
( !
defined
(
$new
->op( @{
$SEMOP_ARGS
->{LOCK_SH}} ) ) )
{
return
(
$self
->error(
"Unable to set lock on sempahore: $!"
) );
}
my
$there
=
$new
->
stat
( SEM_MARKER );
$new
->size(
$opts
->{size} );
$new
->flags(
$flags
);
if
(
$there
== SHM_EXISTS )
{
}
else
{
$new
->
stat
( SEM_MARKER, SHM_EXISTS ) ||
return
(
$self
->error(
"Unable to set semaphore during object creation: $!"
) );
$SHEM_REPO
->{
$id
} =
$new
;
}
$new
->op( @{
$SEMOP_ARGS
->{(LOCK_SH | LOCK_UN)}} );
return
(
$new
);
}
sub
owner {
return
(
shift
->_set_get_scalar(
'owner'
,
@_
) ); }
sub
pid
{
my
$self
=
shift
(
@_
);
my
$sem
=
shift
(
@_
);
my
$semid
=
$self
->semid ||
return
(
$self
->error(
"No semaphore set yet. You must open the shared memory first to remove semaphore."
) );
my
$v
=
semctl
(
$semid
,
$sem
,
&IPC::SysV::GETPID
, 0 );
return
(
$v
? 0 +
$v
:
undef
() );
}
sub
rand
{
my
$self
=
shift
(
@_
);
my
$size
=
$self
->size || 1024;
my
$key
=
shmget
(
&IPC::SysV::IPC_PRIVATE
,
$size
,
&IPC::SysV::S_IRWXU
|
&IPC::SysV::S_IRWXG
|
&IPC::SysV::S_IRWXO
) ||
return
(
$self
->error(
"Unable to generate a share memory key: $!"
) );
return
(
$key
);
}
sub
read
{
my
$self
=
shift
(
@_
);
my
$id
=
$self
->id;
my
$size
=
int
(
$_
[1] ||
$self
->size || SHM_BUFSIZ );
return
(
$self
->error(
"No shared memory id! Have you opened it first?"
) )
if
( !
length
(
$id
) );
my
$buffer
=
''
;
my
$addr
=
$self
->addr;
if
(
$addr
)
{
memread(
$addr
,
$buffer
, 0,
$size
) ||
return
(
$self
->error(
"Unable to read data from shared memory address \"$addr\" using memread: $!"
) );
}
else
{
shmread
(
$id
,
$buffer
, 0,
$size
) ||
return
(
$self
->error(
"Unable to read data from shared memory id \"$id\": $!"
) );
}
$buffer
=
unpack
(
"A*"
,
$buffer
);
my
$first_char
=
substr
(
$buffer
, 0, 1 );
my
$j
= JSON->new->utf8->relaxed->allow_nonref;
my
$data
;
local
$@;
eval
{
if
(
$first_char
eq
'"'
||
$first_char
eq
'{'
||
$first_char
eq
'['
)
{
$data
=
$j
->decode(
$buffer
);
}
else
{
$data
=
$buffer
;
}
};
if
( $@ )
{
$self
->error(
"An error occured while json decoding data: $@"
, (
length
(
$buffer
) <= 1024 ?
"\nData is: '$buffer'"
:
''
) );
if
(
@_
)
{
$_
[0] =
$buffer
;
return
(
length
(
$buffer
) ||
"0E0"
);
}
else
{
return
(
$buffer
);
}
}
if
(
@_
)
{
my
$len
=
length
(
$_
[0] );
if
( !
ref
(
$data
) )
{
$_
[0] =
$size
> 0 ?
substr
(
$data
, 0,
$size
) :
$data
;
return
(
length
(
$_
[0] ) ||
"0E0"
);
}
else
{
$_
[0] =
$data
;
return
(
$len
||
"0E0"
);
}
}
else
{
return
(
$data
);
}
}
sub
remove
{
my
$self
=
shift
(
@_
);
return
( 1 )
if
(
$self
->removed );
my
$id
=
$self
->id();
return
(
$self
->error(
"No shared memory id! Have you opened it first?"
) )
if
( !
length
(
$id
) );
my
$semid
=
$self
->semid;
return
(
$self
->error(
"No semaphore set yet. You must open the shared memory first to remove semaphore."
) )
if
( !
length
(
$semid
) );
$self
->unlock();
if
( !
defined
(
shmctl
(
$id
,
&IPC::SysV::IPC_RMID
, 0 ) ) )
{
return
(
$self
->error(
"Unable to remove share memory segement id '$id' (IPC_RMID is '"
,
&IPC::SysV::IPC_RMID
,
"'): $!"
) );
}
my
$rv
;
if
( !
defined
(
$rv
=
semctl
(
$semid
, 0,
&IPC::SysV::IPC_RMID
, 0 ) ) )
{
$self
->error(
"Warning only: could not remove the semaphore id \"$semid\": $!"
);
}
$self
->removed(
$rv
? 1 : 0 );
if
(
$rv
)
{
delete
(
$SHEM_REPO
->{
$id
} );
$self
->id(
undef
() );
$self
->semid(
undef
() );
}
return
(
$rv
? 1 : 0 );
}
sub
removed {
return
(
shift
->_set_get_boolean(
'removed'
,
@_
) ); }
sub
semid {
return
(
shift
->_set_get_scalar(
'semid'
,
@_
) ); }
sub
serial {
return
(
shift
->_set_get_scalar(
'serial'
,
@_
) ); }
sub
size {
return
(
shift
->_set_get_scalar(
'size'
,
@_
) ); }
sub
stat
{
my
$self
=
shift
(
@_
);
my
$id
=
$self
->semid;
return
(
$self
->error(
"No semaphore set yet. You must open the shared memory first to set the semaphore."
) )
if
( !
length
(
$id
) );
if
(
@_
)
{
if
(
@_
== 1 )
{
my
$sem
=
shift
(
@_
);
my
$v
=
semctl
(
$id
,
$sem
,
&IPC::SysV::GETVAL
, 0 );
return
(
$v
? 0 +
$v
:
undef
() );
}
else
{
my
(
$sem
,
$val
) =
@_
;
return
(
semctl
(
$id
,
$sem
,
&IPC::SysV::SETVAL
,
$val
) );
}
}
else
{
my
$data
=
''
;
if
(
wantarray
() )
{
semctl
(
$id
, 0,
&IPC::SysV::GETALL
,
$data
) ||
return
( () );
return
( (
unpack
(
"s*"
,
$data
) ) );
}
else
{
semctl
(
$id
, 0,
&IPC::SysV::IPC_STAT
,
$data
) ||
return
(
$self
->error(
"Unable to stat semaphore with id '$id': $!"
) );
return
( Apache2::SSI::SemStat->new->
unpack
(
$data
) );
}
}
}
sub
supported {
return
(
$SYSV_SUPPORTED
); }
sub
unlock
{
my
$self
=
shift
(
@_
);
return
( 1 )
if
( !
$self
->locked );
my
$semid
=
$self
->semid;
return
(
$self
->error(
"No semaphore set yet. You must open the shared memory first to unlock semaphore."
) )
if
( !
length
(
$semid
) );
my
$type
=
$self
->locked | LOCK_UN;
$type
^= LOCK_NB
if
(
$type
& LOCK_NB );
if
(
defined
(
$self
->op( @{
$SEMOP_ARGS
->{
$type
}} ) ) )
{
$self
->locked( 0 );
}
return
(
$self
);
}
sub
write
{
my
$self
=
shift
(
@_
);
my
$data
= (
@_
== 1 ) ?
shift
(
@_
) :
join
(
''
,
@_
);
my
$id
=
$self
->id();
my
$size
=
int
(
$self
->size() ) || SHM_BUFSIZ;
my
@callinfo
=
caller
;
my
$j
= JSON->new->utf8->relaxed->allow_nonref->convert_blessed;
my
$encoded
;
local
$@;
eval
{
$encoded
=
$j
->encode(
$data
);
};
if
( $@ )
{
return
(
$self
->error(
"An error occured json encoding data provided: $@"
) );
}
if
(
length
(
$encoded
) >
$size
)
{
return
(
$self
->error(
"Data to write are "
,
length
(
$encoded
),
" bytes long and exceed the maximum you have set of '$size'."
) );
}
my
$addr
=
$self
->addr;
if
(
$addr
)
{
memwrite(
$addr
,
$encoded
, 0,
$size
) ||
return
(
$self
->error(
"Unable to write to shared memory address '$addr' using memwrite: $!"
) );
}
else
{
shmwrite
(
$id
,
$encoded
, 0,
$size
) ||
return
(
$self
->error(
"Unable to write to shared memory id '$id': $!"
) );
}
return
(
$self
);
}
sub
_str2key
{
my
$self
=
shift
(
@_
);
my
$key
=
shift
(
@_
);
if
( !
defined
(
$key
) ||
$key
eq
''
)
{
return
(
&IPC::SysV::IPC_PRIVATE
);
}
elsif
(
$key
=~ /^\d+$/ )
{
return
( IPC::SysV::ftok( __FILE__,
$key
) );
}
else
{
my
$id
= 0;
$id
+=
$_
for
(
unpack
(
"C*"
,
$key
) );
my
$val
= IPC::SysV::ftok(
'/'
,
$id
);
return
(
$val
);
}
}
END
{
foreach
my
$id
(
keys
(
%$SHEM_REPO
) )
{
my
$s
=
$SHEM_REPO
->{
$id
};
$s
->unlock;
next
unless
(
$s
->destroy );
next
unless
(
$s
->owner == $$ );
$s
->remove;
}
};
{
package
Apache2::SSI::SemStat;
our
$VERSION
=
'v0.1.0'
;
sub
new
{
my
$this
=
shift
(
@_
);
my
@vals
=
@_
;
return
(
bless
( [
@vals
] =>
ref
(
$this
) ||
$this
) );
}
sub
cgid {
return
(
shift
->[CGID] ); }
sub
ctime {
return
(
shift
->[CTIME] ); }
sub
cuid {
return
(
shift
->[CUID] ); }
sub
gid {
return
(
shift
->[GID] ); }
sub
mode {
return
(
shift
->[MODE] ); }
sub
nsems {
return
(
shift
->[NSEMS] ); }
sub
otime {
return
(
shift
->[OTIME] ); }
sub
uid {
return
(
shift
->[UID] ); }
}
1;