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