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   100 use strict;
  15         25  
  15         558  
17 15     15   91 use warnings;
  15         27  
  15         362  
18 15     15   70 use warnings::register;
  15         23  
  15         1443  
19 15     15   81 use parent qw( Module::Generic );
  15         26  
  15         72  
20 15     15   774 use Config;
  15         25  
  15         550  
21 15     15   78 use JSON ();
  15         27  
  15         278  
22 15     15   68 use Nice::Try;
  15         26  
  15         118  
23 15     15   16795653 use Scalar::Util ();
  15         39  
  15         497  
24 15     15   88 use constant SHM_BUFSIZ => 65536;
  15         30  
  15         1471  
25 15     15   89 use constant SEM_LOCKER => 0;
  15         44  
  15         660  
26 15     15   84 use constant SEM_MARKER => 1;
  15         37  
  15         675  
27 15     15   87 use constant SHM_LOCK_WAIT => 0;
  15         28  
  15         643  
28 15     15   79 use constant SHM_LOCK_EX => 1;
  15         33  
  15         672  
29 15     15   94 use constant SHM_LOCK_UN => -1;
  15         28  
  15         615  
30 15     15   82 use constant SHM_EXISTS => 1;
  15         26  
  15         611  
31 15     15   77 use constant LOCK_SH => 1;
  15         26  
  15         565  
32 15     15   70 use constant LOCK_EX => 2;
  15         44  
  15         566  
33 15     15   127 use constant LOCK_NB => 4;
  15         32  
  15         586  
34 15     15   88 use constant LOCK_UN => 8;
  15         30  
  15         5703  
35             ## if( $^O =~ /^(?:Android|cygwin|dos|MSWin32|os2|VMS|riscos)/ )
36             ## Even better
37 15     15   107 our $SUPPORTED_RE = qr/\bIPC\/SysV\b/m;
38 15 50 33     1870 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         10499 require IPC::SysV;
44 15         20950 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         44 our $SYSV_SUPPORTED = 1;
49 15         2243 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       1248 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         85 our @EXPORT_OK = qw(LOCK_EX LOCK_SH LOCK_NB LOCK_UN);
94 15         98 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       32 our $N = do { my $foo = eval { pack "L!", 0 }; $@ ? '' : '!' };
  15         27  
  15         46  
  15         57  
101 15         32 our $SHEM_REPO = {};
102 15         13075 our $VERSION = 'v0.1.0';
103             };
104              
105             sub init
106             {
107 128     128 1 4475 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         950 $self->{create} = 0;
110 128         236 $self->{destroy} = 0;
111 128         211 $self->{exclusive} = 0;
112 128         579 $self->{key} = IPC_PRIVATE;
113 128         1322 $self->{mode} = 0666;
114 128         378 $self->{serial} = '';
115             ## SHM_BUFSIZ
116 128         261 $self->{size} = SHM_BUFSIZ;
117 128         347 $self->{_init_strict_use_sub} = 1;
118 128 50       505 $self->SUPER::init( @_ ) || return;
119 128         4697 $self->{addr} = undef();
120 128         276 $self->{id} = undef();
121 128         253 $self->{locked} = 0;
122 128         456 $self->{owner} = $$;
123 128         220 $self->{removed} = 0;
124 128         215 $self->{semid} = undef();
125 128         595 return( $self );
126             }
127              
128 20     20 1 65 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 3092 sub create { return( shift->_set_get_boolean( 'create', @_ ) ); }
146              
147 21     21 1 130 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 348 sub exclusive { return( shift->_set_get_boolean( 'exclusive', @_ ) ); }
161              
162             sub exists
163             {
164 2     2 1 295 my $self = shift( @_ );
165 2         18 my $opts = {};
166 2 50       11 if( ref( $_[0] ) eq 'HASH' )
167             {
168 0         0 $opts = shift( @_ );
169             }
170             else
171             {
172 2         23 @$opts{ qw( key mode size ) } = @_;
173             }
174 2 50       23 $opts->{size} = $self->size unless( length( $opts->{size} ) );
175 2         51 $opts->{size} = int( $opts->{size} );
176 2         4 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         7 $serial = $self->serial;
185             ## $serial = $self->key;
186             }
187 2         48 my $flags = $self->flags({ mode => 0644 });
188             # Remove the create bit
189 2         15 $flags = ( $flags ^ IPC_CREAT );
190             ## $self->message( 3, "Checking if shared memory key \"", ( $opts->{key} || $self->key ), "\" exists with flags '$flags'." );
191 2         8 my $semid;
192 2         3 try
193 2     2   5 {
194 2         31 $semid = semget( $serial, 3, $flags );
195             ## $self->message( 3, "Found the shared memory? ", defined( $semid ) ? 'yes' : 'no' );
196 2 100       14 if( defined( $semid ) )
197             {
198 1         7 my $found = semctl( $semid, SEM_MARKER, GETVAL, 0 );
199 1         17 semctl( $semid, 0, IPC_RMID, 0 );
200 1 50       17 return( $found == SHM_EXISTS ? 1 : 0 );
201             }
202             else
203             {
204             ## $self->message( 3, "Error getting a semaphore: $!" );
205 1 50       20 return( 0 ) if( $! =~ /\bNo[[:blank:]]+such[[:blank:]]+file\b/ );
206 0         0 return;
207             }
208             }
209 2 50       20 catch( $e )
  0 50       0  
  2 50       8  
  2 0       2  
  2 50       4  
  2         4  
  2         3  
  2         3  
  2         10  
  0         0  
  2         4  
  0         0  
  2         8  
  2         6  
  2         5  
  2         10  
  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         26  
  2         31  
215             }
216              
217             sub flags
218             {
219 130     130 1 248 my $self = shift( @_ );
220 130         226 my $opts = {};
221 15     15   141 no warnings 'uninitialized';
  15         29  
  15         3009  
222 130 100       819 $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       563 $opts->{create} = $self->create unless( length( $opts->{create} ) );
229 130 50       2200 $opts->{exclusive} = $self->exclusive unless( length( $opts->{exclusive} ) );
230 130 100       3044 $opts->{mode} = $self->mode unless( length( $opts->{mode} ) );
231 130         1771 my $flags = 0;
232             ## $self->message( 3, "Adding create bit" ) if( $opts->{create} );
233 130 100       365 $flags |= IPC_CREAT if( $opts->{create} );
234             ## $self->message( 3, "Adding exclusive bit" ) if( $opts->{exclusive} );
235 130 50       1771 $flags |= IPC_EXCL if( $opts->{exclusive} );
236             ## $self->message( 3, "Adding mode '", ( $opts->{mode} || 0666 ), "'" );
237 130   50     1142 $flags |= ( $opts->{mode} || 0666 );
238             ## $self->message( 3, "Returning flags value '$flags'." );
239 130         313 return( $flags );
240             }
241              
242             ## sub id { return( shift->_set_get_scalar( 'id', @_ ) ); }
243             sub id
244             {
245 89     89 1 754 my $self = shift( @_ );
246 89         403 my @callinfo = caller;
247 15     15   112 no warnings 'uninitialized';
  15         31  
  15         47750  
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       278 if( @_ )
250             {
251             ## $self->message( 3, "Setting id to value '", defined( $_[0] ) ? $_[0] : 'undef()', "'." );
252 66         164 $self->{id} = shift( @_ );
253             }
254             ## $self->message( 3, "Returning id '$self->{id}'" );
255 89         238 return( $self->{id} );
256             }
257              
258             sub key
259             {
260 192     192 1 13192 my $self = shift( @_ );
261 192 100       479 if( @_ )
262             {
263 128         250 $self->{key} = shift( @_ );
264 128         417 $self->{serial} = $self->_str2key( $self->{key} );
265             ## $self->message( 3, "Setting key to '$self->{key}' ($self->{serial})" );
266             }
267 192         756 return( $self->{key} );
268             }
269              
270             sub lock
271             {
272 6     6 1 778 my $self = shift( @_ );
273 6         29 my $type = shift( @_ );
274 6         18 my $timeout = shift( @_ );
275             # $type = LOCK_EX if( !defined( $type ) );
276 6 100       32 $type = LOCK_SH if( !defined( $type ) );
277 6 50       72 return( $self->unlock ) if( ( $type & LOCK_UN ) );
278 6 50       35 return( 1 ) if( $self->locked & $type );
279 6 50 33     148 $timeout = 0 if( !defined( $timeout ) || $timeout !~ /^\d+$/ );
280             ## If the lock is different, release it first
281 6 50       23 $self->unlock if( $self->locked );
282 6   50     130 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         98 try
286 6     6   17 {
287 6         201 local $SIG{ALRM} = sub{ die( "timeout" ); };
  0         0  
288 6         59 alarm( $timeout );
289 6         23 my $rc = $self->op( @{$SEMOP_ARGS->{ $type }} );
  6         75  
290 6         36 alarm( 0 );
291 6 50       36 if( $rc )
292             {
293 6         34 $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       64 catch( $e )
  6 50       244  
  6 50       21  
  6 0       19  
  6 50       18  
  6         14  
  6         11  
  6         11  
  6         53  
  0         0  
  6         19  
  0         0  
  6         40  
  6         20  
  6         20  
  6         28  
  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         186  
  0         0  
305 6         50 return( $self );
306             }
307              
308 49     49 1 188 sub locked { return( shift->_set_get_scalar( 'locked', @_ ) ); }
309              
310 257     257 1 2749 sub mode { return( shift->_set_get_scalar( 'mode', @_ ) ); }
311              
312             sub op
313             {
314 140     140 1 254 my $self = shift( @_ );
315 140 50       409 return( $self->error( "Invalid number of argument: '", join( ', ', @_ ), "'." ) ) if( @_ % 3 );
316 140         696 my $data = pack( "s$N*", @_ );
317 140         297 my $id = $self->semid;
318 140 50       2149 return( $self->error( "No semaphore set yet. You must open the shared memory first to set the semaphore." ) ) if( !length( $id ) );
319 140         1167 return( semop( $id, $data ) );
320             }
321              
322             sub open
323             {
324 64     64 1 450 my $self = shift( @_ );
325 64         370 my $opts = {};
326 64 50       230 if( ref( $_[0] ) eq 'HASH' )
327             {
328 0         0 $opts = shift( @_ );
329             }
330             else
331             {
332 64         289 @$opts{ qw( key mode size ) } = @_;
333             }
334 64 50       301 $opts->{size} = $self->size unless( length( $opts->{size} ) );
335 64         1033 $opts->{size} = int( $opts->{size} );
336 64   50     545 $opts->{mode} //= '';
337 64   50     336 $opts->{key} //= '';
338 64         104 my $serial;
339 64 50       505 if( length( $opts->{key} ) )
340             {
341 0         0 $serial = $self->_str2key( $opts->{key} );
342             ## $serial = $opts->{key};
343             }
344             else
345             {
346 64         233 $serial = $self->serial;
347             ## $serial = $self->key;
348             }
349 64         923 my $create = 0;
350 64 50 33     1307 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         165 $create = $self->create;
361             }
362 64         1645 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         868 my $id = shmget( $serial, $opts->{size}, $flags );
365 64 50       274 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       191 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         220 $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         1660 my $semid = semget( $serial, 3, $flags );
393 64 50       255 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     412 key => $opts->{key} || $self->key,
404             debug => $self->debug,
405             mode => $self->mode,
406             ) || return;
407 64         250 $new->id( $id );
408 64         413 $new->semid( $semid );
409 64 50       1191 if( !defined( $new->op( @{$SEMOP_ARGS->{LOCK_SH}} ) ) )
  64         341  
410             {
411 0         0 return( $self->error( "Unable to set lock on sempahore: $!" ) );
412             }
413            
414 64         414 my $there = $new->stat( SEM_MARKER );
415 64         328 $new->size( $opts->{size} );
416 64         1265 $new->flags( $flags );
417 64 100       217 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       185 $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         1497 $SHEM_REPO->{ $id } = $new;
429             }
430 64         137 $new->op( @{$SEMOP_ARGS->{(LOCK_SH | LOCK_UN)}} );
  64         350  
431             ## $self->message( 3, "Returning new object persuant to open" );
432 64         458 return( $new );
433             }
434              
435 11     11 1 684 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 3295259 my $self = shift( @_ );
460 13         63 my $id = $self->id;
461             ## Optional length parameter for non-reference data only
462 13   50     100 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       319 return( $self->error( "No shared memory id! Have you opened it first?" ) ) if( !length( $id ) );
465 13         28 my $buffer = '';
466 13         52 my $addr = $self->addr;
467 13 50       198 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       6231 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         5150 $buffer = unpack( "A*", $buffer );
479             ## $self->message( 3, "Data retrieved is '$buffer'." );
480 13         60 my $first_char = substr( $buffer, 0, 1 );
481 13         198 my $j = JSON->new->utf8->relaxed->allow_nonref;
482 13         33 my $data;
483 13         17 try
484 13     13   20 {
485             ## Does the value have any typical json format? " for a string, { for an hash and [ for an array
486 13 100 66     147 if( $first_char eq '"' || $first_char eq '{' || $first_char eq '[' )
      66        
487             {
488 11         149 $data = $j->decode( $buffer );
489             }
490             else
491             {
492 2         8 $data = $buffer;
493             }
494             }
495 13 50       182 catch( $e )
  13 100       49  
  13 50       49  
  13 0       23  
  13 50       36  
  13         18  
  13         20  
  13         26  
  13         54  
  0         0  
  12         29  
  1         23  
  13         66  
  13         33  
  13         48  
  13         37  
  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         287  
  0         0  
510            
511 13 100       50 if( @_ )
512             {
513 11         34 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       51 if( !ref( $data ) )
516             {
517 2 50       18 $_[0] = $size > 0 ? substr( $data, 0, $size ) : $data;
518 2   50     38 return( length( $_[0] ) || "0E0" );
519             }
520             else
521             {
522 9         32 $_[0] = $data;
523 9   50     116 return( $len || "0E0" );
524             }
525             }
526             else
527             {
528 2         17 return( $data );
529             }
530             }
531              
532             sub remove
533             {
534 2     2 1 13 my $self = shift( @_ );
535 2 50       15 return( 1 ) if( $self->removed );
536 2         189 my $id = $self->id();
537             ## $self->message( 3, "Called to remove shared memory id \"$id\"." );
538 2 50       18 return( $self->error( "No shared memory id! Have you opened it first?" ) ) if( !length( $id ) );
539 2         9 my $semid = $self->semid;
540 2 50       48 return( $self->error( "No semaphore set yet. You must open the shared memory first to remove semaphore." ) ) if( !length( $semid ) );
541 2         14 $self->unlock();
542             ## Remove share memory segment
543 2 50       61 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         239 my $rv;
549 2 50       9 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       43 $self->removed( $rv ? 1 : 0 );
554 2 50       125 if( $rv )
555             {
556 2         18 delete( $SHEM_REPO->{ $id } );
557 2         19 $self->id( undef() );
558 2         12 $self->semid( undef() );
559             }
560 2 50       59 return( $rv ? 1 : 0 );
561             }
562              
563 4     4 1 41 sub removed { return( shift->_set_get_boolean( 'removed', @_ ) ); }
564              
565 336     336 1 1046 sub semid { return( shift->_set_get_scalar( 'semid', @_ ) ); }
566              
567 130     130 1 532 sub serial { return( shift->_set_get_scalar( 'serial', @_ ) ); }
568              
569 214     214 1 1664 sub size { return( shift->_set_get_scalar( 'size', @_ ) ); }
570              
571             sub stat
572             {
573 115     115 1 218 my $self = shift( @_ );
574 115         234 my $id = $self->semid;
575 115 50       1645 return( $self->error( "No semaphore set yet. You must open the shared memory first to set the semaphore." ) ) if( !length( $id ) );
576 115 50       262 if( @_ )
577             {
578 115 100       277 if( @_ == 1 )
579             {
580 64         135 my $sem = shift( @_ );
581             ## $self->message( 3, "Retrieving semaphore value for '$sem'" );
582 64         279 my $v = semctl( $id, $sem, GETVAL, 0 );
583 64 50       1484 return( $v ? 0 + $v : undef() );
584             }
585             else
586             {
587 51         136 my( $sem, $val ) = @_;
588             ## $self->message( 3, "Setting semaphore '$sem' with value '$val'." );
589 51         253 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 2044 sub supported { return( $SYSV_SUPPORTED ); }
612              
613             sub unlock
614             {
615 18     18 1 64 my $self = shift( @_ );
616 18 100       92 return( 1 ) if( !$self->locked );
617 6         131 my $semid = $self->semid;
618 6 50       111 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         35 my $type = $self->locked | LOCK_UN;
621 6 100       103 $type ^= LOCK_NB if( $type & LOCK_NB );
622 6 50       21 if( defined( $self->op( @{$SEMOP_ARGS->{ $type }} ) ) )
  6         35  
623             {
624 6         28 $self->locked( 0 );
625             }
626 6         133 return( $self );
627             }
628              
629             sub write
630             {
631 7     7 1 673 my $self = shift( @_ );
632 7 50       66 my $data = ( @_ == 1 ) ? shift( @_ ) : join( '', @_ );
633 7         30 my $id = $self->id();
634 7   50     23 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         96 my $j = JSON->new->utf8->relaxed->allow_nonref->convert_blessed;
639 7         13 my $encoded;
640 7         12 try
641 7     7   12 {
642 7         73 $encoded = $j->encode( $data );
643             }
644 7 50       51 catch( $e )
  7 50       28  
  7 50       21  
  7 0       14  
  7 50       21  
  7         8  
  7         12  
  7         20  
  7         25  
  0         0  
  7         20  
  0         0  
  7         32  
  7         15  
  7         16  
  7         27  
  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         145  
  0         0  
648            
649 7 50       32 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         24 my $addr = $self->addr;
656 7 50       118 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       2677 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         126 return( $self );
668             }
669              
670             sub _str2key
671             {
672 128     128   237 my $self = shift( @_ );
673 128         259 my $key = shift( @_ );
674 128 50 33     1230 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         253 my $id = 0;
685 128         894 $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         2174 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         613 return( $val );
691             }
692             }
693              
694             END
695             {
696 15     15   37085 foreach my $id ( keys( %$SHEM_REPO ) )
697             {
698 10         36 my $s = $SHEM_REPO->{ $id };
699 10         94 $s->message( 3, "Cleaning up for shared memory id '$id' with destroy value '", $s->destroy, "' and owner '", $s->owner, "' vs pid '$$'." );
700 10         489 $s->unlock;
701 10 50       198 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   194 use constant UID => 0;
  15         38  
  15         1208  
724 15     15   112 use constant GID => 1;
  15         31  
  15         659  
725 15     15   89 use constant CUID => 2;
  15         33  
  15         607  
726 15     15   80 use constant CGID => 3;
  15         31  
  15         934  
727 15     15   81 use constant MODE => 4;
  15         29  
  15         598  
728 15     15   83 use constant CTIME => 5;
  15         30  
  15         591  
729 15     15   78 use constant OTIME => 6;
  15         31  
  15         557  
730 15     15   77 use constant NSEMS => 7;
  15         29  
  15         2956  
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