1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdarg.h>
5 #include <sys/types.h>
6 #include <sys/ipc.h>
7 #include <sys/shm.h>
8 #include <sys/sem.h>
9 #include <errno.h>
10 #include "sharestuff.h"
11
12 #ifndef errno
13 extern int errno;
14 #endif
15
16 #include "EXTERN.h"
17 #include "perl.h"
18 #include "XSUB.h"
19
20 /* Use Perl's memory management */
21
22 #ifndef Newxz
23 #define Newxz(pointer, number, type) \
24 Newz(1, pointer, number, type)
25 #endif
26
27 #ifdef HAS_UNION_SEMUN
28 #define SEMUN union semun
29 #else
30 union my_semun {
31 int val;
32 struct semid_ds *buf;
33 unsigned short *array;
34 };
35 #define SEMUN union my_semun
36 #endif
37
38 /* --- DEFINE MACROS FOR SEMAPHORE OPERATIONS --- */
39
40 #define GET_EX_LOCK(A) semop((A), &ex_lock[0], 3)
41 #define GET_EX_LOCK_NB(A) semop((A), &ex_lock_nb[0], 3)
42 #define RM_EX_LOCK(A) semop((A), &ex_unlock[0], 1)
43 #define GET_SH_LOCK(A) semop((A), &sh_lock[0], 2)
44 #define GET_SH_LOCK_NB(A) semop((A), &sh_lock_nb[0], 2)
45 #define RM_SH_LOCK(A) semop((A), &sh_unlock[0], 1)
46
47 /* --- DEFINE STRUCTURES FOR MANIPULATING SEMAPHORES --- */
48
49 static struct sembuf ex_lock[3] = {
50 {1, 0, 0}, /* wait for readers to finish */
51 {2, 0, 0}, /* wait for writers to finish */
52 {2, 1, SEM_UNDO} /* assert write lock */
53 };
54
55 static struct sembuf ex_lock_nb[3] = {
56 {1, 0, IPC_NOWAIT}, /* wait for readers to finish */
57 {2, 0, IPC_NOWAIT}, /* wait for writers to finish */
58 {2, 1, ( SEM_UNDO | IPC_NOWAIT )} /* assert write lock */
59 };
60
61 static struct sembuf ex_unlock[1] = {
62 {2, -1, ( SEM_UNDO | IPC_NOWAIT )} /* remove write lock */
63 };
64
65 static struct sembuf sh_lock[2] = {
66 {2, 0, 0}, /* wait for writers to finish */
67 {1, 1, SEM_UNDO} /* assert shared read lock */
68 };
69
70 static struct sembuf sh_lock_nb[2] = {
71 {2, 0, IPC_NOWAIT}, /* wait for writers to finish */
72 {1, 1, ( SEM_UNDO | IPC_NOWAIT )} /* assert shared read lock */
73 };
74
75 static struct sembuf sh_unlock[1] = {
76 {1, -1, ( SEM_UNDO | IPC_NOWAIT )} /* remove shared read lock */
77 };
78
79 FILE *log_fh = NULL;
80 #define LOG_ARGS const char *file, int line, const char *fmt, ...
81 #define LOG0(fmt) sharelite_log(__FILE__, __LINE__, fmt)
82 #define LOG1(fmt, a1) sharelite_log(__FILE__, __LINE__, fmt, a1)
83 #define LOG2(fmt, a1, a2) sharelite_log(__FILE__, __LINE__, fmt, a1, a2)
84 #define LOG3(fmt, a1, a2, a3) sharelite_log(__FILE__, __LINE__, fmt, a1, a2, a3)
85
86 static void sharelite_log_active( LOG_ARGS );
87 static void sharelite_log_nop( LOG_ARGS );
88
89 static void ( *sharelite_log ) ( LOG_ARGS ) = sharelite_log_active;
90
91 static void
sharelite_log_nop(LOG_ARGS)92 sharelite_log_nop( LOG_ARGS ) {
93 }
94
95 static void
sharelite_log_active(LOG_ARGS)96 sharelite_log_active( LOG_ARGS ) {
97 if ( NULL == log_fh ) {
98 const char *log_file = getenv( "IPC_SHARELITE_LOG" );
99 if ( NULL == log_file
100 || ( log_fh = fopen( log_file, "a" ), NULL == log_fh ) ) {
101 sharelite_log = sharelite_log_nop;
102 return;
103 }
104 }
105 {
106 struct timeval now;
107 char timebuf[40];
108 va_list ap;
109
110 gettimeofday( &now, NULL );
111 strftime( timebuf, sizeof( timebuf ), "%Y/%m/%d %H:%M:%S",
112 gmtime( &now.tv_sec ) );
113 fprintf( log_fh, "%s.%06lu %s, %d : ", timebuf,
114 ( unsigned long ) now.tv_usec, file, line );
115 va_start( ap, fmt );
116 vfprintf( log_fh, fmt, ap );
117 va_end( ap );
118 fprintf( log_fh, "\n" );
119 fflush( log_fh );
120 }
121 }
122
123 /* USER INITIATED LOCK */
124
125 /* returns 0 on success -- requested operation performed *
126 * returns -1 on error *
127 * returns 1 if LOCK_NB specified and operation would block */
128 int
sharelite_lock(Share * share,int flags)129 sharelite_lock( Share * share, int flags ) {
130
131 /* try to obtain exclusive lock by default */
132 if ( !flags ) {
133 flags = LOCK_EX;
134 }
135
136 /* Check for invalid combination of flags. Invalid combinations *
137 * are attempts to obtain *both* an exclusive and shared lock or *
138 * to both obtain and release a lock at the same time */
139 if ( ( ( flags & LOCK_EX ) && ( flags & LOCK_SH ) ) ||
140 ( ( flags & LOCK_UN )
141 && ( ( flags & LOCK_EX ) || ( flags & LOCK_SH ) ) ) ) {
142 return -1;
143 }
144
145 if ( flags & LOCK_EX ) {
146 /*** WANTS EXCLUSIVE LOCK ***/
147 /* If they already have an exclusive lock, just return */
148 if ( share->lock & LOCK_EX ) {
149 return 0;
150 }
151 /* If they currently have a shared lock, remove it */
152 if ( share->lock & LOCK_SH ) {
153 if ( RM_SH_LOCK( share->semid ) < 0 ) {
154 return -1;
155 }
156 share->lock = 0;
157 }
158 if ( flags & LOCK_NB ) { /* non-blocking request */
159 if ( GET_EX_LOCK_NB( share->semid ) < 0 ) {
160 if ( errno == EAGAIN ) { /* would we have blocked? */
161 return 1;
162 }
163 return -1;
164 }
165 }
166 else { /* blocking request */
167 if ( GET_EX_LOCK( share->semid ) < 0 ) {
168 return -1;
169 }
170 }
171 share->lock = LOCK_EX;
172 return 0;
173 }
174 else if ( flags & LOCK_SH ) {
175 /*** WANTS SHARED LOCK ***/
176 /* If they already have a shared lock, just return */
177 if ( share->lock & LOCK_SH ) {
178 return 0;
179 }
180 /* If they currently have an exclusive lock, remove it */
181 if ( share->lock & LOCK_EX ) {
182 if ( RM_EX_LOCK( share->semid ) < 0 ) {
183 return -1;
184 }
185 share->lock = 0;
186 }
187 if ( flags & LOCK_NB ) { /* non-blocking request */
188 if ( GET_SH_LOCK_NB( share->semid ) < 0 ) {
189 if ( errno == EAGAIN ) { /* would we have blocked? */
190 return 1;
191 }
192 return -1;
193 }
194 }
195 else { /* blocking request */
196 if ( GET_SH_LOCK( share->semid ) < 0 ) {
197 return -1;
198 }
199 }
200 share->lock = LOCK_SH;
201 return 0;
202 }
203 else if ( flags & LOCK_UN ) {
204 /*** WANTS TO RELEASE LOCK ***/
205 if ( share->lock & LOCK_EX ) {
206 if ( RM_EX_LOCK( share->semid ) < 0 ) {
207 return -1;
208 }
209 }
210 else if ( share->lock & LOCK_SH ) {
211 if ( RM_SH_LOCK( share->semid ) < 0 ) {
212 return -1;
213 }
214 }
215 }
216
217 return 0;
218 }
219
220 int
sharelite_unlock(Share * share)221 sharelite_unlock( Share * share ) {
222 if ( share->lock & LOCK_EX ) {
223 if ( RM_EX_LOCK( share->semid ) < 0 ) {
224 return -1;
225 }
226 }
227 else if ( share->lock & LOCK_SH ) {
228 if ( RM_SH_LOCK( share->semid ) < 0 ) {
229 return -1;
230 }
231 }
232 share->lock = 0;
233 return 0;
234 }
235
236 Node *
_add_segment(Share * share)237 _add_segment( Share * share ) {
238 Node *node;
239 int flags;
240
241 Newxz( node, 1, Node );
242
243 node->next = NULL;
244
245 /* Does another shared memory segment already exist? */
246 if ( share->tail->shmaddr->next_shmid >= 0 ) {
247 node->shmid = share->tail->shmaddr->next_shmid;
248 if ( ( node->shmaddr =
249 ( Header * ) shmat( node->shmid, ( char * ) 0,
250 0 ) ) == ( Header * ) - 1 ) {
251 return NULL;
252 }
253 share->tail->next = node;
254 share->tail = node;
255 return node;
256 }
257
258 flags = share->flags | IPC_CREAT | IPC_EXCL;
259
260 /* We need to create a new segment */
261 while ( 1 ) {
262 node->shmid = shmget( share->next_key++, share->segment_size, flags );
263 if ( node->shmid >= 0 ) {
264 break;
265 }
266 #ifdef EIDRM
267 if ( errno == EEXIST || errno == EIDRM ) {
268 continue;
269 }
270 #else
271 if ( errno == EEXIST ) {
272 continue;
273 }
274 #endif
275 return NULL;
276 }
277
278 share->tail->shmaddr->next_shmid = node->shmid;
279 share->tail->next = node;
280 share->tail = node;
281 if ( ( node->shmaddr =
282 ( Header * ) shmat( node->shmid, ( char * ) 0,
283 0 ) ) == ( Header * ) - 1 ) {
284 return NULL;
285 }
286 node->shmaddr->next_shmid = -1;
287 node->shmaddr->length = 0;
288
289 return node;
290 }
291
292 int
_detach_segments(Node * node)293 _detach_segments( Node * node ) {
294 Node *next_node;
295
296 while ( node != NULL ) {
297 next_node = node->next;
298 if ( shmdt( ( char * ) node->shmaddr ) < 0 ) {
299 return -1;
300 }
301 Safefree( node );
302 node = next_node;
303 }
304 return 0;
305 }
306
307 int
_remove_segments(int shmid)308 _remove_segments( int shmid ) {
309 int next_shmid;
310 Header *shmaddr;
311
312 while ( shmid >= 0 ) {
313 if ( ( shmaddr =
314 ( Header * ) shmat( shmid, ( char * ) 0,
315 0 ) ) == ( Header * ) - 1 ) {
316 return -1;
317 }
318 next_shmid = shmaddr->next_shmid;
319 if ( shmdt( ( char * ) shmaddr ) < 0 ) {
320 return -1;
321 }
322 if ( shmctl( shmid, IPC_RMID, ( struct shmid_ds * ) 0 ) < 0 ) {
323 return -1;
324 }
325 shmid = next_shmid;
326 }
327
328 return 0;
329 }
330
331 int
_invalidate_segments(Share * share)332 _invalidate_segments( Share * share ) {
333
334 if ( _detach_segments( share->head->next ) < 0 ) {
335 return -1;
336 }
337 share->head->next = NULL;
338 share->tail = share->head;
339 share->shm_state = share->head->shmaddr->shm_state;
340
341 return 0;
342 }
343
344 int
write_share(Share * share,char * data,int length)345 write_share( Share * share, char *data, int length ) {
346 char *shmaddr;
347 int segments;
348 int left;
349 int chunk_size;
350 Node *node;
351 int shmid;
352
353 if ( data == NULL ) {
354 return -1;
355 }
356
357 if ( !( share->lock & LOCK_EX ) ) {
358 if ( share->lock & LOCK_SH ) {
359 if ( RM_SH_LOCK( share->semid ) < 0 ) {
360 return -1;
361 }
362 }
363 if ( GET_EX_LOCK( share->semid ) < 0 ) {
364 return -1;
365 }
366 }
367
368 if ( share->shm_state != share->head->shmaddr->shm_state ) {
369 if ( _invalidate_segments( share ) < 0 ) {
370 return -1;
371 }
372 }
373
374 /* set the data length to zero. if we are interrupted or encounter *
375 * an error during the write, this guarantees that we won't *
376 * receive corrupt data in future reads. */
377 share->head->shmaddr->length = 0;
378
379 /* compute number of segments necessary to hold data */
380 segments =
381 ( length / share->data_size ) +
382 ( length % share->data_size ? 1 : 0 );
383
384 node = share->head;
385 left = length;
386 while ( segments-- ) {
387 if ( node == NULL ) {
388 if ( ( node = _add_segment( share ) ) == NULL ) {
389 return -1;
390 }
391 }
392 chunk_size = ( left > share->data_size ? share->data_size : left );
393 shmaddr = ( char * ) node->shmaddr + sizeof( Header );
394 memcpy( shmaddr, data, chunk_size );
395 left -= chunk_size;
396 data += chunk_size;
397 if ( segments ) {
398 node = node->next;
399 }
400 }
401
402 /* set new length in header of first segment */
403 share->head->shmaddr->length = length;
404
405 /* garbage collection -- remove unused segments */
406 if ( node->shmaddr->next_shmid >= 0 ) {
407 shmid = node->shmaddr->next_shmid;
408 if ( _detach_segments( node->next ) < 0 ) {
409 return -1;
410 }
411 if ( _remove_segments( shmid ) < 0 ) {
412 return -1;
413 }
414 node->shmaddr->next_shmid = -1;
415 node->next = NULL;
416 share->tail = node;
417 share->head->shmaddr->shm_state++;
418 }
419
420 ++share->head->shmaddr->version;
421
422 if ( !( share->lock & LOCK_EX ) ) {
423 if ( RM_EX_LOCK( share->semid ) < 0 ) {
424 return -1;
425 }
426 if ( share->lock & LOCK_SH ) {
427 if ( GET_SH_LOCK( share->semid ) < 0 ) {
428 return -1;
429 }
430 }
431 }
432
433 return 0;
434 }
435
436 int
read_share(Share * share,char ** data)437 read_share( Share * share, char **data ) {
438 char *shmaddr;
439 char *pos;
440 Node *node;
441 int length;
442 int left;
443 int chunk_size;
444
445 if ( !share->lock ) {
446 if ( GET_SH_LOCK( share->semid ) < 0 ) {
447 return -1;
448 }
449 }
450
451 if ( share->shm_state != share->head->shmaddr->shm_state ) {
452 if ( _invalidate_segments( share ) < 0 ) {
453 return -1;
454 }
455 }
456
457 node = share->head;
458 left = length = node->shmaddr->length;
459
460 /* Allocate extra byte for a null at the end */
461 Newxz( *data, length + 1, char );
462 pos = *data;
463
464 pos[length] = '\0';
465
466 while ( left ) {
467 if ( node == NULL ) {
468 if ( ( node = _add_segment( share ) ) == NULL ) {
469 goto fail;
470 }
471 }
472 chunk_size = ( left > share->data_size ? share->data_size : left );
473 shmaddr = ( char * ) node->shmaddr + sizeof( Header );
474 memcpy( pos, shmaddr, chunk_size );
475 pos += chunk_size;
476 left -= chunk_size;
477 node = node->next;
478 }
479
480 if ( !share->lock ) {
481 if ( RM_SH_LOCK( share->semid ) < 0 ) {
482 goto fail;
483 }
484 }
485
486 return length;
487
488 fail:
489 Safefree( *data );
490 return -1;
491 }
492
493 Share *
new_share(key_t key,int segment_size,int flags)494 new_share( key_t key, int segment_size, int flags ) {
495 Share *share;
496 Node *node;
497 int semid;
498 struct shmid_ds shmctl_arg;
499 SEMUN semun_arg;
500
501 again:
502 if ( ( semid = semget( key, 3, flags ) ) < 0 ) {
503 LOG1( "semget failed (%d)", errno );
504 return NULL;
505 }
506
507 /* It's possible for another process to obtain the semaphore, lock it, *
508 * and remove it from the system before we have a chance to lock it. *
509 * In this case (EINVAL) we just try to create it again. */
510 if ( GET_EX_LOCK( semid ) < 0 ) {
511 if ( errno == EINVAL ) {
512 goto again;
513 }
514 LOG1( "GET_EX_LOCK failed (%d)", errno );
515 return NULL;
516 }
517
518 /* XXX IS THIS THE RIGHT THING TO DO? */
519 if ( segment_size <= sizeof( Header ) ) {
520 segment_size = SHM_SEGMENT_SIZE;
521 }
522
523 Newxz( node, 1, Node );
524
525 if ( ( node->shmid = shmget( key, segment_size, flags ) ) < 0 ) {
526 LOG1( "shmget failed (%d)", errno );
527 return NULL;
528 }
529
530 if ( ( node->shmaddr =
531 ( Header * ) shmat( node->shmid, ( char * ) 0,
532 0 ) ) == ( Header * ) - 1 ) {
533 LOG1( "shmat failed (%d)", errno );
534 return NULL;
535 }
536
537 node->next = NULL;
538
539 Newxz( share, 1, Share );
540
541 share->key = key;
542 share->next_key = key + 1;
543 share->flags = flags;
544 share->semid = semid;
545 share->lock = 0;
546 share->head = node;
547 share->tail = node;
548
549 /* is this a newly created segment? if so, initialize it */
550 if ( ( semun_arg.val =
551 semctl( share->semid, 0, GETVAL, semun_arg ) ) < 0 ) {
552 LOG1( "shmctl failed (%d)", errno );
553 return NULL;
554 }
555
556 if ( semun_arg.val == 0 ) {
557 semun_arg.val = 1;
558 if ( semctl( share->semid, 0, SETVAL, semun_arg ) < 0 ) {
559 LOG1( "shmctl failed (%d)", errno );
560 return NULL;
561 }
562 share->head->shmaddr->length = 0;
563 share->head->shmaddr->next_shmid = -1;
564 share->head->shmaddr->shm_state = 1;
565 share->head->shmaddr->version = 1;
566 }
567
568 share->shm_state = share->head->shmaddr->shm_state;
569 share->version = share->head->shmaddr->version;
570
571 /* determine the true length of the segment. this may disagree *
572 * with what the user requested, since shmget() calls will *
573 * succeed if the requested size <= the existing size */
574 if ( shmctl( share->head->shmid, IPC_STAT, &shmctl_arg ) < 0 ) {
575 LOG1( "shmctl failed (%d)", errno );
576 return NULL;
577 }
578
579 share->segment_size = shmctl_arg.shm_segsz;
580 share->data_size = share->segment_size - sizeof( Header );
581
582 if ( RM_EX_LOCK( semid ) < 0 ) {
583 LOG1( "RM_EX_LOCK failed (%d)", errno );
584 return NULL;
585 }
586
587 return share;
588 }
589
590 unsigned int
sharelite_version(Share * share)591 sharelite_version( Share * share ) {
592 return share->head->shmaddr->version;
593 }
594
595 int
destroy_share(Share * share,int rmid)596 destroy_share( Share * share, int rmid ) {
597 int semid;
598 SEMUN semctl_arg;
599
600 if ( !( share->lock & LOCK_EX ) ) {
601 if ( share->lock & LOCK_SH ) {
602 if ( RM_SH_LOCK( share->semid ) < 0 ) {
603 return -1;
604 }
605 }
606 if ( GET_EX_LOCK( share->semid ) < 0 ) {
607 return -1;
608 }
609 }
610
611 semid = share->head->shmid;
612 if ( _detach_segments( share->head ) < 0 ) {
613 return -1;
614 }
615
616 if ( rmid ) {
617 if ( _remove_segments( semid ) < 0 ) {
618 return -1;
619 }
620 semctl_arg.val = 0;
621 if ( semctl( share->semid, 0, IPC_RMID, semctl_arg ) < 0 ) {
622 return -1;
623 }
624 }
625 else {
626 if ( RM_EX_LOCK( share->semid ) < 0 ) {
627 return -1;
628 }
629 }
630
631 Safefree( share );
632
633 return 0;
634 }
635
636 int
sharelite_num_segments(Share * share)637 sharelite_num_segments( Share * share ) {
638 int count = 0;
639 int shmid;
640 Header *shmaddr;
641
642 shmid = share->head->shmid;
643 while ( shmid >= 0 ) {
644 count++;
645 if ( ( shmaddr =
646 ( Header * ) shmat( shmid, ( char * ) 0,
647 0 ) ) == ( Header * ) - 1 ) {
648 return -1;
649 }
650 shmid = shmaddr->next_shmid;
651 if ( shmdt( ( char * ) shmaddr ) < 0 ) {
652 return -1;
653 }
654 }
655
656 return count;
657 }
658
659 void
_dump_list(Share * share)660 _dump_list( Share * share ) {
661 Node *node;
662
663 node = share->head;
664 while ( node != NULL ) {
665 printf( "shmid: %i\n", node->shmid );
666 node = node->next;
667 }
668 }
669