File Coverage

blib/lib/Apache2/SSI/SharedMem.pm
Criterion Covered Total %
statement 381 519 73.4
branch 106 248 42.7
condition 27 77 35.0
subroutine 61 78 78.2
pod 29 29 100.0
total 604 951 63.5


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