File Coverage

blib/lib/Apache2/SSI/SharedMem.pm
Criterion Covered Total %
statement 381 519 73.4
branch 106 248 42.7
condition 28 80 35.0
subroutine 61 78 78.2
pod 29 29 100.0
total 605 954 63.4


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## Apache2 Server Side Include Parser - ~/lib/Apache2/SSI/SharedMem.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/01/18
7             ## Modified 2021/01/23
8             ## All rights reserved
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package Apache2::SSI::SharedMem;
14             BEGIN
15             {
16 15     15   163 use strict;
  15         29  
  15         475  
17 15     15   76 use warnings;
  15         21  
  15         397  
18 15     15   80 use warnings::register;
  15         34  
  15         1586  
19 15     15   101 use parent qw( Module::Generic );
  15         30  
  15         72  
20 15     15   838 use Config;
  15         25  
  15         616  
21 15     15   74 use JSON ();
  15         26  
  15         252  
22 15     15   71 use Nice::Try;
  15         27  
  15         134  
23 15     15   17538520 use Scalar::Util ();
  15         37  
  15         539  
24 15     15   92 use constant SHM_BUFSIZ => 65536;
  15         38  
  15         1610  
25 15     15   89 use constant SEM_LOCKER => 0;
  15         30  
  15         661  
26 15     15   85 use constant SEM_MARKER => 1;
  15         33  
  15         610  
27 15     15   80 use constant SHM_LOCK_WAIT => 0;
  15         34  
  15         633  
28 15     15   78 use constant SHM_LOCK_EX => 1;
  15         26  
  15         706  
29 15     15   80 use constant SHM_LOCK_UN => -1;
  15         36  
  15         598  
30 15     15   81 use constant SHM_EXISTS => 1;
  15         30  
  15         653  
31 15     15   78 use constant LOCK_SH => 1;
  15         31  
  15         609  
32 15     15   80 use constant LOCK_EX => 2;
  15         37  
  15         589  
33 15     15   81 use constant LOCK_NB => 4;
  15         27  
  15         581  
34 15     15   78 use constant LOCK_UN => 8;
  15         32  
  15         6153  
35             ## if( $^O =~ /^(?:Android|cygwin|dos|MSWin32|os2|VMS|riscos)/ )
36             ## Even better
37 15     15   120 our $SUPPORTED_RE = qr/\bIPC\/SysV\b/m;
38 15 50 33     1992 if( $Config{extensions} =~ m/$SUPPORTED_RE/ &&
      33        
39             ## No support for threads
40             !$Config{useithreads} &&
41             $^O !~ /^(?:Android|cygwin|dos|MSWin32|os2|VMS|riscos)/i )
42             {
43 15         10344 require IPC::SysV;
44 15         20446 IPC::SysV->import( qw( IPC_RMID IPC_PRIVATE IPC_SET IPC_STAT IPC_CREAT IPC_EXCL IPC_NOWAIT
45             SEM_UNDO S_IRWXU S_IRWXG S_IRWXO
46             GETNCNT GETZCNT GETVAL SETVAL GETPID GETALL SETALL
47             shmat shmdt memread memwrite ftok ) );
48 15         46 our $SYSV_SUPPORTED = 1;
49 15         2357 eval( <<'EOT' );
50             our $SEMOP_ARGS =
51             {
52             (LOCK_EX) =>
53             [
54             1, 0, 0, # wait for readers to finish
55             2, 0, 0, # wait for writers to finish
56             2, 1, SEM_UNDO, # assert write lock
57             ],
58             (LOCK_EX | LOCK_NB) =>
59             [
60             1, 0, IPC_NOWAIT, # wait for readers to finish
61             2, 0, IPC_NOWAIT, # wait for writers to finish
62             2, 1, (SEM_UNDO | IPC_NOWAIT), # assert write lock
63             ],
64             (LOCK_EX | LOCK_UN) =>
65             [
66             2, -1, (SEM_UNDO | IPC_NOWAIT),
67             ],
68             (LOCK_SH) =>
69             [
70             2, 0, 0, # wait for writers to finish
71             1, 1, SEM_UNDO, # assert shared read lock
72             ],
73             (LOCK_SH | LOCK_NB) =>
74             [
75             2, 0, IPC_NOWAIT, # wait for writers to finish
76             1, 1, (SEM_UNDO | IPC_NOWAIT), # assert shared read lock
77             ],
78             (LOCK_SH | LOCK_UN) =>
79             [
80             1, -1, (SEM_UNDO | IPC_NOWAIT), # remove shared read lock
81             ],
82             };
83             EOT
84 15 50       1276 if( $@ )
85             {
86 0         0 warn( "Error while trying to evel \$SEMOP_ARGS: $@\n" );
87             }
88             }
89             else
90             {
91 0         0 our $SYSV_SUPPORTED = 0;
92             }
93 15         86 our @EXPORT_OK = qw(LOCK_EX LOCK_SH LOCK_NB LOCK_UN);
94 15         104 our %EXPORT_TAGS = (
95             all => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
96             lock => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
97             'flock' => [qw( LOCK_EX LOCK_SH LOCK_NB LOCK_UN )],
98             );
99             # Credits IPC::Shareable
100 15 50       47 our $N = do { my $foo = eval { pack "L!", 0 }; $@ ? '' : '!' };
  15         29  
  15         34  
  15         60  
101 15         36 our $SHEM_REPO = {};
102 15         13654 our $VERSION = 'v0.1.0';
103             };
104              
105             sub init
106             {
107 128     128 1 4993 my $self = shift( @_ );
108             ## Default action when accessing a shared memory? If 1, it will create it if it does not exist already
109 128         1098 $self->{create} = 0;
110 128         280 $self->{destroy} = 0;
111 128         253 $self->{exclusive} = 0;
112 128         708 $self->{key} = IPC_PRIVATE;
113 128         1555 $self->{mode} = 0666;
114 128         309 $self->{serial} = '';
115             ## SHM_BUFSIZ
116 128         261 $self->{size} = SHM_BUFSIZ;
117 128         409 $self->{_init_strict_use_sub} = 1;
118 128 50       590 $self->SUPER::init( @_ ) || return;
119 128         5363 $self->{addr} = undef();
120 128         326 $self->{id} = undef();
121 128         291 $self->{locked} = 0;
122 128         471 $self->{owner} = $$;
123 128         279 $self->{removed} = 0;
124 128         266 $self->{semid} = undef();
125 128         651 return( $self );
126             }
127              
128 20     20 1 64 sub addr { return( shift->_set_get_scalar( 'addr', @_ ) ); }
129              
130             sub attach
131             {
132 0     0 1 0 my $self = shift( @_ );
133 0         0 my $flags = shift( @_ );
134 0 0       0 $flags = $self->flags if( !defined( $flags ) );
135 0         0 my $addr = $self->addr;
136 0 0       0 return( $addr ) if( defined( $addr ) );
137 0         0 my $id = $self->id;
138 0 0       0 return( $self->error( "No shared memory id! Have you opened it first?" ) ) if( !length( $id ) );
139 0         0 $addr = shmat( $id, undef(), $flags );
140 0 0       0 return( $self->error( "Unable to attach to shared memory: $!" ) ) if( !defined( $addr ) );
141 0         0 $self->addr( $addr );
142 0         0 return( $addr );
143             }
144              
145 196     196 1 3786 sub create { return( shift->_set_get_boolean( 'create', @_ ) ); }
146              
147 21     21 1 150 sub destroy { return( shift->_set_get_boolean( 'destroy', @_ ) ); }
148              
149             sub detach
150             {
151 0     0 1 0 my $self = shift( @_ );
152 0         0 my $addr = $self->addr;
153 0 0       0 return if( !defined( $addr ) );
154 0         0 my $rv = shmdt( $addr );
155 0 0       0 return( $self->error( "Unable to detach from shared memory: $!" ) ) if( !defined( $rv ) );
156 0         0 $self->addr( undef() );
157 0         0 return( $self );
158             }
159              
160 130     130 1 361 sub exclusive { return( shift->_set_get_boolean( 'exclusive', @_ ) ); }
161              
162             sub exists
163             {
164 2     2 1 358 my $self = shift( @_ );
165 2         25 my $opts = {};
166 2 50       10 if( ref( $_[0] ) eq 'HASH' )
167             {
168 0         0 $opts = shift( @_ );
169             }
170             else
171             {
172 2         31 @$opts{ qw( key mode size ) } = @_;
173             }
174 2 50       31 $opts->{size} = $self->size unless( length( $opts->{size} ) );
175 2         42 $opts->{size} = int( $opts->{size} );
176 2         11 my $serial;
177 2 50       7 if( length( $opts->{key} ) )
178             {
179 0         0 $serial = $self->_str2key( $opts->{key} );
180             ## $serial = $opts->{key};
181             }
182             else
183             {
184 2         8 $serial = $self->serial;
185             ## $serial = $self->key;
186             }
187 2         49 my $flags = $self->flags({ mode => 0644 });
188             # Remove the create bit
189 2         16 $flags = ( $flags ^ IPC_CREAT );
190             ## $self->message( 3, "Checking if shared memory key \"", ( $opts->{key} || $self->key ), "\" exists with flags '$flags'." );
191 2         23 my $semid;
192 2         9 try
193 2     2   4 {
194 2         39 $semid = semget( $serial, 3, $flags );
195             ## $self->message( 3, "Found the shared memory? ", defined( $semid ) ? 'yes' : 'no' );
196 2 100       13 if( defined( $semid ) )
197             {
198 1         29 my $found = semctl( $semid, SEM_MARKER, GETVAL, 0 );
199 1         18 semctl( $semid, 0, IPC_RMID, 0 );
200 1 50       22 return( $found == SHM_EXISTS ? 1 : 0 );
201             }
202             else
203             {
204             ## $self->message( 3, "Error getting a semaphore: $!" );
205 1 50       23 return( 0 ) if( $! =~ /\bNo[[:blank:]]+such[[:blank:]]+file\b/ );
206 0         0 return;
207             }
208             }
209 2 50       25 catch( $e )
  0 50       0  
  2 50       13  
  2 0       6  
  2 50       6  
  2         3  
  2         3  
  2         6  
  2         28  
  0         0  
  2         6  
  0         0  
  2         24  
  2         6  
  2         5  
  2         8  
  0         0  
  0         0  
  0         0  
  0         0  
210 0     0   0 {
211             ## $self->message( 3, "Trying to access shared memory triggered error: $e" );
212 0 0       0 semctl( $semid, 0, IPC_RMID, 0 ) if( $semid );
213 0         0 return( 0 );
214 0 0 33     0 }
  0 0 33     0  
  0 50       0  
  0 50       0  
  0         0  
  0         0  
  2         31  
  2         30  
215             }
216              
217             sub flags
218             {
219 130     130 1 306 my $self = shift( @_ );
220 130         280 my $opts = {};
221 15     15   143 no warnings 'uninitialized';
  15         31  
  15         2958  
222 130 100       874 $opts = Scalar::Util::reftype( $_[0] ) eq 'HASH'
    100          
223             ? shift( @_ )
224             : !( scalar( @_ ) % 2 )
225             ? { @_ }
226             : {};
227             ## $self->message( 3, "Option mode value is '$opts->{mode}'." );
228 130 100       526 $opts->{create} = $self->create unless( length( $opts->{create} ) );
229 130 50       2569 $opts->{exclusive} = $self->exclusive unless( length( $opts->{exclusive} ) );
230 130 100       3420 $opts->{mode} = $self->mode unless( length( $opts->{mode} ) );
231 130         1894 my $flags = 0;
232             ## $self->message( 3, "Adding create bit" ) if( $opts->{create} );
233 130 100       398 $flags |= IPC_CREAT if( $opts->{create} );
234             ## $self->message( 3, "Adding exclusive bit" ) if( $opts->{exclusive} );
235 130 50       2055 $flags |= IPC_EXCL if( $opts->{exclusive} );
236             ## $self->message( 3, "Adding mode '", ( $opts->{mode} || 0666 ), "'" );
237 130   50     1202 $flags |= ( $opts->{mode} || 0666 );
238             ## $self->message( 3, "Returning flags value '$flags'." );
239 130         370 return( $flags );
240             }
241              
242             ## sub id { return( shift->_set_get_scalar( 'id', @_ ) ); }
243             sub id
244             {
245 89     89 1 1014 my $self = shift( @_ );
246 89         444 my @callinfo = caller;
247 15     15   113 no warnings 'uninitialized';
  15         39  
  15         44513  
248             ## $self->message( 3, "Called from package $callinfo[0] in file $callinfo[1] at line $callinfo[2] with ", scalar( @_ ) ? ( "args: '" . join( "', '", @_ ) . "'." ) : 'no argument.' );
249 89 100       314 if( @_ )
250             {
251             ## $self->message( 3, "Setting id to value '", defined( $_[0] ) ? $_[0] : 'undef()', "'." );
252 66         176 $self->{id} = shift( @_ );
253             }
254             ## $self->message( 3, "Returning id '$self->{id}'" );
255 89         230 return( $self->{id} );
256             }
257              
258             sub key
259             {
260 192     192 1 15136 my $self = shift( @_ );
261 192 100       538 if( @_ )
262             {
263 128         316 $self->{key} = shift( @_ );
264 128         535 $self->{serial} = $self->_str2key( $self->{key} );
265             ## $self->message( 3, "Setting key to '$self->{key}' ($self->{serial})" );
266             }
267 192         985 return( $self->{key} );
268             }
269              
270             sub lock
271             {
272 6     6 1 1088 my $self = shift( @_ );
273 6         15 my $type = shift( @_ );
274 6         28 my $timeout = shift( @_ );
275             # $type = LOCK_EX if( !defined( $type ) );
276 6 100       58 $type = LOCK_SH if( !defined( $type ) );
277 6 50       80 return( $self->unlock ) if( ( $type & LOCK_UN ) );
278 6 50       32 return( 1 ) if( $self->locked & $type );
279 6 50 33     161 $timeout = 0 if( !defined( $timeout ) || $timeout !~ /^\d+$/ );
280             ## If the lock is different, release it first
281 6 50       36 $self->unlock if( $self->locked );
282 6   50     114 my $semid = $self->semid ||
283             return( $self->error( "No semaphore id set yet." ) );
284             ## $self->message( 3, "Setting a lock on semaphore id \"$semid\" with type \"$type\" and arguments: ", sub{ $self->dump( $SEMOP_ARGS->{ $type } ) } );
285 6         123 try
286 6     6   12 {
287 6         230 local $SIG{ALRM} = sub{ die( "timeout" ); };
  0         0  
288 6         56 alarm( $timeout );
289 6         21 my $rc = $self->op( @{$SEMOP_ARGS->{ $type }} );
  6         69  
290 6         50 alarm( 0 );
291 6 50       26 if( $rc )
292             {
293 6         43 $self->locked( $type );
294             }
295             else
296             {
297             ## $self->message( 3, "Unable to set a lock on semaphore id \"$semid\": $!" );
298 0         0 return( $self->error( "Failed to set a lock on semaphore id \"$semid\": $!" ) );
299             }
300             }
301 6 50       94 catch( $e )
  6 50       259  
  6 50       53  
  6 0       22  
  6 50       26  
  6         20  
  6         23  
  6         29  
  6         82  
  0         0  
  6         22  
  0         0  
  6         26  
  6         22  
  6         22  
  6         41  
  0         0  
  0         0  
  0         0  
  0         0  
302 0     0   0 {
303 0         0 return( $self->error( "Unable to set a lock: $e" ) );
304 0 0 33     0 }
  0 0 33     0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  6         245  
  0         0  
305 6         51 return( $self );
306             }
307              
308 49     49 1 215 sub locked { return( shift->_set_get_scalar( 'locked', @_ ) ); }
309              
310 257     257 1 3063 sub mode { return( shift->_set_get_scalar( 'mode', @_ ) ); }
311              
312             sub op
313             {
314 140     140 1 268 my $self = shift( @_ );
315 140 50       452 return( $self->error( "Invalid number of argument: '", join( ', ', @_ ), "'." ) ) if( @_ % 3 );
316 140         902 my $data = pack( "s$N*", @_ );
317 140         395 my $id = $self->semid;
318 140 50       2424 return( $self->error( "No semaphore set yet. You must open the shared memory first to set the semaphore." ) ) if( !length( $id ) );
319 140         1263 return( semop( $id, $data ) );
320             }
321              
322             sub open
323             {
324 64     64 1 547 my $self = shift( @_ );
325 64         164 my $opts = {};
326 64 50       260 if( ref( $_[0] ) eq 'HASH' )
327             {
328 0         0 $opts = shift( @_ );
329             }
330             else
331             {
332 64         328 @$opts{ qw( key mode size ) } = @_;
333             }
334 64 50       329 $opts->{size} = $self->size unless( length( $opts->{size} ) );
335 64         1158 $opts->{size} = int( $opts->{size} );
336 64   50     531 $opts->{mode} //= '';
337 64   50     372 $opts->{key} //= '';
338 64         118 my $serial;
339 64 50       540 if( length( $opts->{key} ) )
340             {
341 0         0 $serial = $self->_str2key( $opts->{key} );
342             ## $serial = $opts->{key};
343             }
344             else
345             {
346 64         286 $serial = $self->serial;
347             ## $serial = $self->key;
348             }
349 64         1002 my $create = 0;
350 64 50 33     1290 if( $opts->{mode} eq 'w' || $opts->{key} =~ s/^>// )
    50 33        
351             {
352 0         0 $create++;
353             }
354             elsif( $opts->{mode} eq 'r' || $opts->{key} =~ s/^<// )
355             {
356 0         0 $create = 0;
357             }
358             else
359             {
360 64         196 $create = $self->create;
361             }
362 64         1784 my $flags = $self->flags( create => $create );
363             ## $self->message( 3, "Trying to get the shared memory segment with key '", ( $opts->{key} || $self->key ), "' with serial '$serial', size '$opts->{size}' and mode '$opts->{mode}'." );
364 64         1057 my $id = shmget( $serial, $opts->{size}, $flags );
365 64 50       350 if( defined( $id ) )
366             {
367             ## $self->message( 3, "Got the shared memory first time around with id \"$id\"." );
368             }
369             else
370             {
371             ## $self->message( 3, "Shared memory does not exists yet ($!), trying to create it now start from $serial" );
372 0 0       0 my $newflags = ( $flags & IPC_CREAT ) ? $flags : ( $flags | IPC_CREAT );
373 0         0 my $limit = ( $serial + 10 );
374             ## IPC::SysV::ftok has likely made the serial unique, but as stated in the manual page, there is no guarantee
375 0         0 while( ++$serial <= $limit )
376             {
377 0         0 $id = shmget( $serial, $opts->{size}, $newflags | IPC_CREAT );
378             ## $self->message( 3, "Shared memory key '$serial' worked ? ", defined( $serial ) ? 'yes' : 'no' );
379 0 0       0 last if( defined( $id ) );
380             }
381             }
382            
383 64 50       222 if( !defined( $id ) )
384             {
385             ## $self->message( 3, "Could not open shared memory with flags '$flags': $!" );
386 0         0 return( $self->error( "Unable to create shared memory id with key \"$serial\" and flags \"$flags\": $!" ) );
387             }
388 64         271 $self->serial( $serial );
389            
390             ## $self->message( 3, "Shared memory created with id \"$id\"." );
391             ## The value 3 can be anything above 0 and below the limit set by SEMMSL. On Linux system, this is usually 32,000. Seem semget(2) man page
392 64         1967 my $semid = semget( $serial, 3, $flags );
393 64 50       283 if( !defined( $semid ) )
394             {
395             ## $self->message( 3, "Could not get a semaphore, trying again with creating it." );
396 0         0 my $newflags = ( $flags | IPC_CREAT );
397 0         0 $semid = semget( $serial, 3, $newflags );
398 0 0 0     0 return( $self->error( "Unable to get a semaphore for shared memory key \"", ( $opts->{key} || $self->key ), "\" wth id \"$id\": $!" ) ) if( !defined( $semid ) );
399             ## $self->message( 3, "Retrieved existing semaphore with semaphore id \"$semid\"." );
400             }
401             ## $self->message( 3, "Semaphore id is '$semid'" );
402             my $new = $self->new(
403 64   50     421 key => $opts->{key} || $self->key,
404             debug => $self->debug,
405             mode => $self->mode,
406             ) || return;
407 64         308 $new->id( $id );
408 64         279 $new->semid( $semid );
409 64 50       1213 if( !defined( $new->op( @{$SEMOP_ARGS->{LOCK_SH}} ) ) )
  64         426  
410             {
411 0         0 return( $self->error( "Unable to set lock on sempahore: $!" ) );
412             }
413            
414 64         329 my $there = $new->stat( SEM_MARKER );
415 64         315 $new->size( $opts->{size} );
416 64         1451 $new->flags( $flags );
417 64 100       216 if( $there == SHM_EXISTS )
418             {
419             ## $self->message( 3, "Binding to existing segment on ", $new->id );
420             }
421             else
422             {
423             ## $self->message( 3, "New segment on ", $new->id );
424             ## We initialise the semaphore with value of 1
425 51 50       202 $new->stat( SEM_MARKER, SHM_EXISTS ) ||
426             return( $self->error( "Unable to set semaphore during object creation: $!" ) );
427             ## $self->message( 3, "Semaphore created." );
428 51         1835 $SHEM_REPO->{ $id } = $new;
429             }
430 64         145 $new->op( @{$SEMOP_ARGS->{(LOCK_SH | LOCK_UN)}} );
  64         397  
431             ## $self->message( 3, "Returning new object persuant to open" );
432 64         585 return( $new );
433             }
434              
435 11     11 1 891 sub owner { return( shift->_set_get_scalar( 'owner', @_ ) ); }
436              
437             sub pid
438             {
439 0     0 1 0 my $self = shift( @_ );
440 0         0 my $sem = shift( @_ );
441 0 0 0     0 my $semid = $self->semid ||
442             return( $self->error( "No semaphore set yet. You must open the shared memory first to remove semaphore." ) ) if( !length( $id ) );
443 0         0 my $v = semctl( $semid, $sem, GETPID, 0 );
444 0 0       0 return( $v ? 0 + $v : undef() );
445             }
446              
447             sub rand
448             {
449 0     0 1 0 my $self = shift( @_ );
450 0   0     0 my $size = $self->size || 1024;
451 0   0     0 my $key = shmget( IPC_PRIVATE, $size, S_IRWXU|S_IRWXG|S_IRWXO ) ||
452             return( $self->error( "Unable to generate a share memory key: $!" ) );
453 0         0 return( $key );
454             }
455              
456             ## $self->read( $buffer, $size );
457             sub read
458             {
459 13     13 1 5614113 my $self = shift( @_ );
460 13         47 my $id = $self->id;
461             ## Optional length parameter for non-reference data only
462 13   50     93 my $size = int( $_[1] || $self->size || SHM_BUFSIZ );
463             ## $self->message( 3, "Reading $size bytes of data from memory for id '$id'." );
464 13 50       356 return( $self->error( "No shared memory id! Have you opened it first?" ) ) if( !length( $id ) );
465 13         31 my $buffer = '';
466 13         52 my $addr = $self->addr;
467 13 50       203 if( $addr )
468             {
469 0 0       0 memread( $addr, $buffer, 0, $size ) ||
470             return( $self->error( "Unable to read data from shared memory address \"$addr\" using memread: $!" ) );
471             }
472             else
473             {
474 13 50       6070 shmread( $id, $buffer, 0, $size ) ||
475             return( $self->error( "Unable to read data from shared memory id \"$id\": $!" ) );
476             }
477             ## Get rid of nulls end padded
478 13         5040 $buffer = unpack( "A*", $buffer );
479             ## $self->message( 3, "Data retrieved is '$buffer'." );
480 13         57 my $first_char = substr( $buffer, 0, 1 );
481 13         127 my $j = JSON->new->utf8->relaxed->allow_nonref;
482 13         30 my $data;
483 13         25 try
484 13     13   19 {
485             ## Does the value have any typical json format? " for a string, { for an hash and [ for an array
486 13 100 66     96 if( $first_char eq '"' || $first_char eq '{' || $first_char eq '[' )
      66        
487             {
488 11         115 $data = $j->decode( $buffer );
489             }
490             else
491             {
492 2         9 $data = $buffer;
493             }
494             }
495 13 50       149 catch( $e )
  13 100       44  
  13 50       61  
  13 0       33  
  13 50       27  
  13         16  
  13         31  
  13         43  
  13         54  
  0         0  
  12         23  
  1         31  
  13         51  
  13         27  
  13         40  
  13         50  
  0         0  
  0         0  
  0         0  
  0         0  
496 0     0   0 {
497 0 0       0 $self->error( "An error occured while json decoding data: $e", ( length( $buffer ) <= 1024 ? "\nData is: '$buffer'" : '' ) );
498             ## Maybe it's a string that starts with '{' or " or [ and triggered an error because it was not actually json data?
499             ## So we return the data stored as it is
500 0 0       0 if( @_ )
501             {
502 0         0 $_[0] = $buffer;
503 0   0     0 return( length( $buffer ) || "0E0" );
504             }
505             else
506             {
507 0         0 return( $buffer );
508             }
509 0 0 33     0 }
  0 0 66     0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  13         242  
  0         0  
510            
511 13 100       48 if( @_ )
512             {
513 11         21 my $len = length( $_[0] );
514             ## If the decoded data is not a reference of any sort, and the length parameter was provided
515 11 100       70 if( !ref( $data ) )
516             {
517 2 50       12 $_[0] = $size > 0 ? substr( $data, 0, $size ) : $data;
518 2   50     37 return( length( $_[0] ) || "0E0" );
519             }
520             else
521             {
522 9         20 $_[0] = $data;
523 9   50     99 return( $len || "0E0" );
524             }
525             }
526             else
527             {
528 2         15 return( $data );
529             }
530             }
531              
532             sub remove
533             {
534 2     2 1 6 my $self = shift( @_ );
535 2 50       18 return( 1 ) if( $self->removed );
536 2         151 my $id = $self->id();
537             ## $self->message( 3, "Called to remove shared memory id \"$id\"." );
538 2 50       12 return( $self->error( "No shared memory id! Have you opened it first?" ) ) if( !length( $id ) );
539 2         11 my $semid = $self->semid;
540 2 50       46 return( $self->error( "No semaphore set yet. You must open the shared memory first to remove semaphore." ) ) if( !length( $semid ) );
541 2         16 $self->unlock();
542             ## Remove share memory segment
543 2 50       77 if( !defined( shmctl( $id, IPC_RMID, 0 ) ) )
544             {
545 0         0 return( $self->error( "Unable to remove share memory segement id '$id' (IPC_RMID is '", IPC_RMID, "'): $!" ) );
546             }
547             ## Remove semaphore
548 2         252 my $rv;
549 2 50       25 if( !defined( $rv = semctl( $semid, 0, IPC_RMID, 0 ) ) )
550             {
551 0         0 $self->error( "Warning only: could not remove the semaphore id \"$semid\": $!" );
552             }
553 2 50       68 $self->removed( $rv ? 1 : 0 );
554 2 50       121 if( $rv )
555             {
556 2         25 delete( $SHEM_REPO->{ $id } );
557 2         26 $self->id( undef() );
558 2         5 $self->semid( undef() );
559             }
560 2 50       81 return( $rv ? 1 : 0 );
561             }
562              
563 4     4 1 34 sub removed { return( shift->_set_get_boolean( 'removed', @_ ) ); }
564              
565 336     336 1 1242 sub semid { return( shift->_set_get_scalar( 'semid', @_ ) ); }
566              
567 130     130 1 460 sub serial { return( shift->_set_get_scalar( 'serial', @_ ) ); }
568              
569 214     214 1 1933 sub size { return( shift->_set_get_scalar( 'size', @_ ) ); }
570              
571             sub stat
572             {
573 115     115 1 240 my $self = shift( @_ );
574 115         242 my $id = $self->semid;
575 115 50       1818 return( $self->error( "No semaphore set yet. You must open the shared memory first to set the semaphore." ) ) if( !length( $id ) );
576 115 50       303 if( @_ )
577             {
578 115 100       327 if( @_ == 1 )
579             {
580 64         185 my $sem = shift( @_ );
581             ## $self->message( 3, "Retrieving semaphore value for '$sem'" );
582 64         346 my $v = semctl( $id, $sem, GETVAL, 0 );
583 64 50       1665 return( $v ? 0 + $v : undef() );
584             }
585             else
586             {
587 51         181 my( $sem, $val ) = @_;
588             ## $self->message( 3, "Setting semaphore '$sem' with value '$val'." );
589 51         310 return( semctl( $id, $sem, SETVAL, $val ) );
590             }
591             }
592             else
593             {
594 0         0 my $data = '';
595 0 0       0 if( wantarray() )
596             {
597             ## $self->message( 3, "Returning all semaphore data." );
598 0 0       0 semctl( $id, 0, GETALL, $data ) || return( () );
599 0         0 return( ( unpack( "s*", $data ) ) );
600             }
601             else
602             {
603             ## $self->message( 3, "Returning all semaphore data as Apache2::SSI::SemStat object." );
604 0 0       0 semctl( $id, 0, IPC_STAT, $data ) ||
605             return( $self->error( "Unable to stat semaphore with id '$id': $!" ) );
606 0         0 return( Apache2::SSI::SemStat->new->unpack( $data ) );
607             }
608             }
609             }
610              
611 128     128 1 2399 sub supported { return( $SYSV_SUPPORTED ); }
612              
613             sub unlock
614             {
615 18     18 1 62 my $self = shift( @_ );
616 18 100       96 return( 1 ) if( !$self->locked );
617 6         128 my $semid = $self->semid;
618 6 50       98 return( $self->error( "No semaphore set yet. You must open the shared memory first to unlock semaphore." ) ) if( !length( $semid ) );
619             ## $self->message( 3, "Removing lock for semaphore id \"$semid\" and locked value '$self->{locked}'." );
620 6         37 my $type = $self->locked | LOCK_UN;
621 6 100       101 $type ^= LOCK_NB if( $type & LOCK_NB );
622 6 50       22 if( defined( $self->op( @{$SEMOP_ARGS->{ $type }} ) ) )
  6         33  
623             {
624 6         29 $self->locked( 0 );
625             }
626 6         142 return( $self );
627             }
628              
629             sub write
630             {
631 7     7 1 934 my $self = shift( @_ );
632 7 50       28 my $data = ( @_ == 1 ) ? shift( @_ ) : join( '', @_ );
633 7         19 my $id = $self->id();
634 7   50     17 my $size = int( $self->size() ) || SHM_BUFSIZ;
635 7         143 my @callinfo = caller;
636             ## $self->message( 3, "Called from file $callinfo[1] at line $callinfo[2]" );
637             ## $self->message( 3, "Size limit set to '$size'" );
638 7         100 my $j = JSON->new->utf8->relaxed->allow_nonref->convert_blessed;
639 7         22 my $encoded;
640 7         9 try
641 7     7   58 {
642 7         78 $encoded = $j->encode( $data );
643             }
644 7 50       40 catch( $e )
  7 50       21  
  7 50       18  
  7 0       19  
  7 50       16  
  7         8  
  7         9  
  7         13  
  7         28  
  0         0  
  7         11  
  0         0  
  7         26  
  7         14  
  7         24  
  7         14  
  0         0  
  0         0  
  0         0  
  0         0  
645 0     0   0 {
646 0         0 return( $self->error( "An error occured json encoding data provided: $e" ) );
647 0 0 33     0 }
  0 0 33     0  
  0 0       0  
  0 50       0  
  0         0  
  0         0  
  7         108  
  0         0  
648            
649 7 50       28 if( length( $encoded ) > $size )
650             {
651 0         0 return( $self->error( "Data to write are ", length( $encoded ), " bytes long and exceed the maximum you have set of '$size'." ) );
652             }
653             ## $self->message( 3, "Storing ", length( $encoded ), " bytes of data", ( length( $encoded ) <= 2048 ? ":\n'$encoded'" : '.' ) );
654             ## $size = length( $encoded );
655 7         23 my $addr = $self->addr;
656 7 50       120 if( $addr )
657             {
658 0 0       0 memwrite( $addr, $encoded, 0, $size ) ||
659             return( $self->error( "Unable to write to shared memory address '$addr' using memwrite: $!" ) );
660             }
661             else
662             {
663 7 50       2441 shmwrite( $id, $encoded, 0, $size ) ||
664             return( $self->error( "Unable to write to shared memory id '$id': $!" ) );
665             }
666             ## $self->message( 3, "Successfully wrote ", length( $encoded ), " bytes of data to memory." );
667 7         94 return( $self );
668             }
669              
670             sub _str2key
671             {
672 128     128   275 my $self = shift( @_ );
673 128         257 my $key = shift( @_ );
674 128 50 33     1366 if( !defined( $key ) || $key eq '' )
    50          
675             {
676 0         0 return( IPC_PRIVATE );
677             }
678             elsif( $key =~ /^\d+$/ )
679             {
680 0         0 return( IPC::SysV::ftok( __FILE__, $key ) );
681             }
682             else
683             {
684 128         283 my $id = 0;
685 128         1034 $id += $_ for( unpack( "C*", $key ) );
686             ## We use the root as a reliable and stable path.
687             ## I initially though about using __FILE__, but during testing this would be in ./blib/lib and beside one user might use a version of this module somewhere while the one used under Apache/mod_perl2 could be somewhere else and this would render the generation of the IPC key unreliable and unrepeatable
688 128         2526 my $val = IPC::SysV::ftok( '/', $id );
689             ## $self->message( 3, "Calling IPC::SysV::ftok for key '$key' with file '/' and numeric id '$id' returning '$val'." );
690 128         732 return( $val );
691             }
692             }
693              
694             END
695             {
696 15     15   39213 foreach my $id ( keys( %$SHEM_REPO ) )
697             {
698 10         47 my $s = $SHEM_REPO->{ $id };
699 10         119 $s->message( 3, "Cleaning up for shared memory id '$id' with destroy value '", $s->destroy, "' and owner '", $s->owner, "' vs pid '$$'." );
700 10         722 $s->unlock;
701 10 50       279 next unless( $s->destroy );
702 0 0       0 next unless( $s->owner == $$ );
703 0         0 $s->remove;
704             }
705             };
706              
707             # DESTROY
708             # {
709             # my $self = shift( @_ );
710             # my @callinfo = caller;
711             # ## $self->message( 3, "Got here from package $callinfo[0] in file $callinfo[1] at line $callinfo[2], destroying object for shared memory id \"", $self->id, "\" key \"", $self->key, "\" with destroy flags '", $self->destroy, "'." );
712             # ## $self->message( 3, "Object contains following keys: ", sub{ $self->dump( $self ) } );
713             # $self->unlock;
714             # $self->remove if( $self->destroy );
715             # };
716              
717              
718             {
719             package
720             Apache2::SSI::SemStat;
721             our $VERSION = 'v0.1.0';
722            
723 15     15   163 use constant UID => 0;
  15         32  
  15         1028  
724 15     15   99 use constant GID => 1;
  15         34  
  15         638  
725 15     15   85 use constant CUID => 2;
  15         28  
  15         618  
726 15     15   85 use constant CGID => 3;
  15         30  
  15         592  
727 15     15   79 use constant MODE => 4;
  15         31  
  15         621  
728 15     15   79 use constant CTIME => 5;
  15         34  
  15         621  
729 15     15   80 use constant OTIME => 6;
  15         30  
  15         582  
730 15     15   88 use constant NSEMS => 7;
  15         36  
  15         2929  
731            
732             sub new
733             {
734 0     0     my $this = shift( @_ );
735 0           my @vals = @_;
736 0   0       return( bless( [ @vals ] => ref( $this ) || $this ) );
737             }
738            
739 0     0     sub cgid { return( shift->[CGID] ); }
740            
741 0     0     sub ctime { return( shift->[CTIME] ); }
742            
743 0     0     sub cuid { return( shift->[CUID] ); }
744              
745 0     0     sub gid { return( shift->[GID] ); }
746            
747 0     0     sub mode { return( shift->[MODE] ); }
748              
749 0     0     sub nsems { return( shift->[NSEMS] ); }
750            
751 0     0     sub otime { return( shift->[OTIME] ); }
752              
753 0     0     sub uid { return( shift->[UID] ); }
754             }
755              
756             1;
757