1 /*
2  * speedtables shared memory support
3  */
4 
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10 #include <ctype.h>
11 #include <fcntl.h>
12 #include <sys/mman.h>
13 #include <sys/stat.h>
14 #include <sys/ipc.h>
15 #include <time.h>
16 #include <signal.h>
17 #ifdef WITH_TCL
18 #include <tcl.h>
19 #endif
20 
21 #include "shared.h"
22 
23 // Data that must be shared between multiple speedtables C extensions
24 //
25 // NB we used Tcl's interpreter-associated property lists to create, find and
26 // share the data.  Ideally we would have that C code in a shared library that
27 // the shared libraries we make invoke.
28 //
29 // NB this should be global to the program.  right now we support only one
30 // Tcl interpreter because of our approach of using assoc data.
31 //
32 struct speedtablesAssocData {
33     int      autoshare;
34     char    *share_base;
35     shm_t   *share_list;
36 };
37 
38 static struct speedtablesAssocData *assocData = NULL;
39 #define ASSOC_DATA_KEY "speedtables"
40 
41 
42 #ifndef WITH_TCL
ckalloc(size_t size)43 char *ckalloc(size_t size)
44 {
45     char *p = (char*) malloc(size);
46     if(!p)
47         shmpanic("Out of memory!");
48     return p;
49 }
50 # define ckfree(p) free(p)
51 #endif
52 
53 static char last_shmem_error[256] = { '\0' };
set_last_shmem_error(const char * message)54 void set_last_shmem_error(const char *message) {
55     strncpy(last_shmem_error, message, sizeof(last_shmem_error) - 1);
56 }
57 
get_last_shmem_error()58 const char *get_last_shmem_error() {
59     return last_shmem_error;
60 }
61 
62 // Callable by master or clients.
shared_perror(const char * text)63 void shared_perror(const char *text) {
64     if(last_shmem_error[0] != '\0') {
65         fprintf(stderr, "%s: %s\n", text, last_shmem_error);
66     } else {
67         perror(text);
68     }
69 }
70 
71 
72 // set/linkup_assoc_data - attach the bits of data that multiple speedtables
73 // C shared libraries need to share.
74 // Callable by master or clients.
75 // If not using Tcl, just allocate it if it doesn't exist already
76 #ifdef WITH_TCL
77 static void
linkup_assoc_data(Tcl_Interp * interp)78 linkup_assoc_data (Tcl_Interp *interp)
79 #else
80 void
81 set_assoc_data ()
82 #endif
83 {
84     if (assocData != NULL) {
85       //IFDEBUG(fprintf(SHM_DEBUG_FP, "previously found assocData at %lX\n", (long unsigned int)assocData);)
86         return;
87     }
88 
89 #ifdef WITH_TCL
90     // locate the associated data
91     assocData = (struct speedtablesAssocData *)Tcl_GetAssocData (interp, ASSOC_DATA_KEY, NULL);
92     if (assocData != NULL) {
93         //IFDEBUG(fprintf(SHM_DEBUG_FP, "found assocData at %lX\n", (long unsigned int)assocData);)
94         return;
95     }
96 #endif
97 
98     assocData = (struct speedtablesAssocData *)ckalloc (sizeof (struct speedtablesAssocData));
99     assocData->autoshare = 0;
100     assocData->share_base = NULL;
101     assocData->share_list = NULL;
102 
103 #ifdef WITH_TCL
104     //IFDEBUG(fprintf(SHM_DEBUG_FP, "on interp %lX, constructed assocData at %lX\n", (long unsigned int) interp, (long unsigned int)assocData);)
105     Tcl_SetAssocData (interp, ASSOC_DATA_KEY, NULL, (ClientData)assocData);
106 #endif
107 }
108 
109 
110 // map_file - map a file at addr. If the file doesn't exist, create it first
111 // with size default_size. Return share or NULL on failure.
112 //
113 // If the file has already been mapped, return the share associated with the
114 // file. The file check is purely by name, if multiple different names are
115 // used by the same process for the same file the result is undefined.
116 //
117 // If the file is already mapped, but at a different address, this is an error
118 //
119 // Callable by master or clients.
120 //
map_file(const char * file,char * addr,size_t default_size,int flags,int create)121 shm_t *map_file(const char *file, char *addr, size_t default_size, int flags, int create)
122 {
123     shm_t *p;
124 
125 #ifndef WITH_TCL
126     set_assoc_data();
127 #endif
128 
129     p = assocData->share_list;
130 
131 
132     // Look for an already mapped share
133     while(p) {
134         //IFDEBUG(fprintf (SHM_DEBUG_FP, "map_file: checking '%s' against '%s'\n", file, p->filename);)
135         if(file && p->filename && strcmp(p->filename, file) == 0) {
136             if((addr != NULL && addr != (char *)p->map)) {
137 		//IFDEBUG(fprintf (SHM_DEBUG_FP, "map_file: map address mismatch between %lX and %lX, mapping to the latter\n", (long unsigned int)addr, (long unsigned int)p->map);)
138             }
139             if(default_size && default_size > p->size) {
140 		//IFDEBUG(fprintf (SHM_DEBUG_FP, "map_file: requested size %ld bigger than segment size %ld\n", (long)default_size, (long)p->size);)
141                 return NULL;
142             }
143 	    //IFDEBUG(fprintf (SHM_DEBUG_FP, "map_file: resolved '%s' to same map at %lX\n", file, (long unsigned int)p);)
144 	    p->attach_count++;
145             return p;
146         }
147         p = p->next;
148     }
149 
150     managed_mapped_file *mmf;
151     mapheader_t *mh;
152     try {
153         //fprintf(stderr, "want to %s to %s at %p\n", (create != 0 ? "create" : "attach"), file, addr);
154 
155         if (create != 0) {
156 	    mmf = new managed_mapped_file(open_or_create, file, default_size, (void*)addr);
157 	} else {
158             mmf = new managed_mapped_file(open_only, file, (void*)addr);
159 	}
160 
161 	//fprintf(stderr, "created managed_mapped_file\n");
162 
163 	mh = mmf->find_or_construct<mapheader_t>("mapheader")();
164     } catch (interprocess_exception &Ex) {
165         snprintf(last_shmem_error, sizeof(last_shmem_error), "caught error while initialized managed_mapped_file: %s\n", Ex.what());
166         return NULL;
167     }
168 
169     p = (shm_t*)ckalloc(sizeof(shm_t));
170     p->filename = (char *) ckalloc(strlen(file)+1);
171     strcpy(p->filename, file);
172 
173     // Completely initialise all fields!
174     p->map = mh;
175     p->managed_shm = mmf;
176     p->share_base = addr;
177     p->size = default_size;
178     p->flags = flags;
179     p->fd = -1;
180     p->name = NULL;
181     p->creator = 0;
182     p->garbage = NULL;
183     p->horizon = LOST_HORIZON;
184     p->self = NULL;
185     p->objects = NULL;
186     p->attach_count = 1;
187 
188     // Hook in this new structure.
189     p->next = assocData->share_list;
190     assocData->share_list = p;
191 
192     return p;
193 }
194 
195 // unmap_file - Unmap the open and mapped associated with the memory mapped
196 // for share. Return 0 on error, -1 if the map is still busy, 1 if it's
197 // been umapped.
198 // Callable by master or clients.
unmap_file(shm_t * share)199 int unmap_file(shm_t   *share)
200 {
201     volatile reader_t   *r;
202 
203     // If there's anyone still using the share, it's a no-op
204     if(share->objects) {
205         return -1;
206     }
207 
208     // if we have multiple attachments and this isn't the last one, we're done
209     if (--share->attach_count > 0) {
210         return 1;
211     }
212 
213 #ifndef WITH_TCL
214     set_assoc_data();
215 #endif
216 
217     // remove from list
218     if(!assocData->share_list) {
219         return 0;
220     } else if(assocData->share_list == share) {
221         assocData->share_list = share->next;
222     } else {
223         shm_t   *p = assocData->share_list;
224 
225         while(p && p->next != share) {
226             p = p->next;
227 	}
228 
229         if(!p) {
230             return 0;
231         }
232 
233         p->next = share->next;
234     }
235 
236     // If we're a reader, zero out our reader entry for re-use
237     r = pid2reader(share->map, getpid());
238     if(r) {
239         r->pid = 0;
240         r->cycle = LOST_HORIZON;
241     }
242 
243 
244     if (share->garbage != NULL) {
245       delete share->garbage;
246     }
247     ckfree(share->filename);
248     delete share->managed_shm;
249 
250     ckfree((char*)share);
251 
252     return 1;
253 }
254 
255 // unmap_all - Unmap all mapped files.
256 // Callable by master or clients.
unmap_all(void)257 void unmap_all(void)
258 {
259 #ifndef WITH_TCL
260     set_assoc_data();
261 #endif
262 
263     while(assocData->share_list) {
264         shm_t *p    = assocData->share_list;
265         shm_t *next = p->next;
266 
267 	unmap_file(p);
268 
269 	assocData->share_list = next;
270     }
271 }
272 
273 
274 // Initialize a map file for use.
275 // Should only be called by the master.
shminitmap(shm_t * shm)276 void shminitmap(shm_t   *shm)
277 {
278     volatile mapheader_t  *map = shm->map;
279 
280     // COMPLETELY initialise map.
281     map->magic = MAP_MAGIC;
282     map->headersize = sizeof(mapheader_t);
283     map->mapsize = shm->size;
284     map->addr = shm->share_base;
285     map->namelist = NULL;
286     map->cycle = LOST_HORIZON;
287     memset((void*)map->readers, 0, sizeof(reader_t) * MAX_SHMEM_READERS);
288 
289     // freshly mapped, so this stuff is void
290     shm->garbage = new deque<garbage_t>();
291     shm->horizon = LOST_HORIZON;
292 
293     // Remember that we own this.
294     shm->creator = 1;
295 
296 }
297 
298 
299 
300 // Return estimate of free memory available.  Does not include memory waiting to be garbage collected.
301 // Callable only by master.
shmfreemem(shm_t * shm,int)302 size_t shmfreemem(shm_t *shm, int /*check*/)
303 {
304     return shm->managed_shm->get_free_memory();
305 }
306 
307 // Allocate some memory from the shared-memory heap.
308 // May return NULL if the allocation failed.
309 // Callable only by master.
_shmalloc(shm_t * shm,size_t nbytes)310 void *_shmalloc(shm_t   *shm, size_t nbytes)
311 {
312     return shm->managed_shm->allocate(nbytes, std::nothrow);
313 }
314 
315 // Allocate some memory from the shared-memory heap.
316 // May return NULL if the allocation failed.
317 // Callable only by master.
shmalloc_raw(shm_t * shm,size_t nbytes)318 void *shmalloc_raw(shm_t   *shm, size_t nbytes)
319 {
320     return shm->managed_shm->allocate(nbytes, std::nothrow);
321 }
322 
323 // Add a block of memory into the garbage pool to be deleted later.
324 // Callable only by master.
shmfree_raw(shm_t * shm,void * memory)325 void shmfree_raw(shm_t *shm, void *memory)
326 {
327     garbage_t entry;
328 
329     entry.cycle = shm->map->cycle;
330     entry.memory = (char*)memory;
331 
332     assert(shm->garbage != NULL && "master is missing garbage queue");
333 
334     // newer deletions are always added to the end, so that the oldest are at the front.
335     shm->garbage->push_back(entry);
336 }
337 
338 
339 // Free a block immediately.
340 // Callable only by master.
shmdealloc_raw(shm_t * shm,void * memory)341 int shmdealloc_raw(shm_t *shm, void *memory)
342 {
343     shm->managed_shm->deallocate(memory);
344     return 1;
345 }
346 
347 // Called by the master before making an update to shared-memory.
348 // Increments the cycle number and returns the new cycle number.
write_lock(shm_t * shm)349 int write_lock(shm_t   *shm)
350 {
351     volatile mapheader_t *map = shm->map;
352 
353     while(++map->cycle == LOST_HORIZON) {
354         continue;
355     }
356 
357     return map->cycle;
358 }
359 
360 // Called by the master at the end of an update to shared-memory.
361 // Performs garbage collection of deleted memory blocks that are
362 // no longer being accessed by readers.
363 #ifdef LAZY_GC
364 static int garbage_strike = 0;
365 #endif
write_unlock(shm_t * shm)366 void write_unlock(shm_t   *shm)
367 {
368     cell_t new_horizon;
369     int    age;
370 #ifdef LAZY_GC
371     if(++garbage_strike < LAZY_GC) return;
372     garbage_strike = 0;
373 #endif
374 
375     new_horizon = oldest_reader_cycle(shm);
376 
377     // If no active readers, then work back from current time
378     if(new_horizon == LOST_HORIZON) {
379         new_horizon = shm->map->cycle;
380     }
381 
382     age = new_horizon - shm->horizon;
383 
384     if(age > TWILIGHT_ZONE) {
385         shm->horizon = new_horizon;
386         garbage_collect(shm);
387     }
388 }
389 
390 
391 // Find the reader structure associated with a reader's pid.
392 // Callable by clients.
393 // Returns NULL if no match found.
pid2reader(volatile mapheader_t * map,int pid)394 volatile reader_t *pid2reader(volatile mapheader_t *map, int pid)
395 {
396     for (unsigned i = 0; i < MAX_SHMEM_READERS; i++) {
397         if(map->readers[i].pid == (cell_t)pid) {
398 	    return &map->readers[i];
399         }
400     }
401     return NULL;
402 }
403 
404 
405 // Add client (pid) to the list of readers.
406 // Callable by master.
407 // Returns 1 on success, 0 on failure.
shmattachpid(shm_t * share,int pid)408 int shmattachpid(shm_t   *share, int pid)
409 {
410     volatile mapheader_t *map = share->map;
411 
412     if(!pid) {
413 	return 0;         // invalid pid
414     }
415     if(pid2reader(map, pid)) return 1;    // success, already added.
416 
417     for (unsigned i = 0; i < MAX_SHMEM_READERS; i++) {
418         if (map->readers[i].pid == 0) {
419 	    map->readers[i].pid = (cell_t)pid;
420 	    map->readers[i].cycle = LOST_HORIZON;
421 	    return 1;     // successfully added.
422 	}
423     }
424 
425     return 0;     // no space for any more readers.
426 }
427 
428 // Called by a reader to start a read transaction on the current state of memory.
429 // Callable by clients.
430 // Returns the cycle number that is locked, or LOST_HORIZON (0) on error.
read_lock(shm_t * shm)431 int read_lock(shm_t   *shm)
432 {
433     volatile mapheader_t *map = shm->map;
434     volatile reader_t *self = shm->self;
435 
436     if(!self) {
437         shm->self = self = pid2reader(map, getpid());
438     }
439     if(!self) {
440         fprintf(stderr, "%d: Can't find reader slot!\n", getpid());
441         return LOST_HORIZON;
442     }
443     return self->cycle = map->cycle;
444 }
445 
446 // Called by a reader to end a read transaction on the current state of memory.
447 // Callable by clients.
read_unlock(shm_t * shm)448 void read_unlock(shm_t   *shm)
449 {
450     volatile reader_t *self = shm->self;
451 
452     if(!self)
453         return;
454 
455     self->cycle = LOST_HORIZON;
456 }
457 
458 // Go through each garbage block and, if it's not in use by any readers, return it to the free list.
459 // Callable only by master.
garbage_collect(shm_t * shm)460 void garbage_collect(shm_t   *shm)
461 {
462     cell_t       horizon = shm->horizon;
463     int          collected = 0;
464 
465     if(horizon != LOST_HORIZON) {
466         horizon -= TWILIGHT_ZONE;
467         if(horizon == LOST_HORIZON)
468             horizon--;
469     }
470 
471     assert(shm->garbage != NULL && "master is missing garbage queue");
472 
473     while(!shm->garbage->empty()) {
474 	garbage_t &garbp = shm->garbage->front();
475 
476         int delta = horizon - garbp.cycle;
477         if(horizon == LOST_HORIZON || garbp.cycle == LOST_HORIZON || delta > 0) {
478             shmdealloc_raw(shm, garbp.memory);
479 	    shm->garbage->pop_front();
480             collected++;
481         } else {
482             // stop when we find one that is still pending, since it is ordered by cycle.
483 	    break;
484         }
485     }
486 
487 //IFDEBUG(fprintf(SHM_DEBUG_FP, "garbage_collect(shm): cycle 0x%08lx, horizon 0x%08lx, collected %d, skipped %d\n", (long)shm->map->cycle, (long)shm->horizon, collected, shm->garbage->size());)
488 }
489 
490 // Find the cycle number for the oldest reader.
491 // Callable only by master.
oldest_reader_cycle(shm_t * shm)492 cell_t oldest_reader_cycle(shm_t   *shm)
493 {
494     volatile mapheader_t *map  = shm->map;
495     cell_t new_cycle = LOST_HORIZON;
496     cell_t map_cycle = map->cycle;
497     cell_t rdr_cycle = LOST_HORIZON;
498     int oldest_age = 0;
499     int age;
500 
501     for(unsigned i = 0; i < MAX_SHMEM_READERS; i++) {
502         if(map->readers[i].pid) {
503 	    if (kill(map->readers[i].pid, 0) == -1) {
504 	        // Found a pid belonging to a dead process.  Remove it.
505 	        //IFDEBUG(fprintf(SHM_DEBUG_FP, "oldest_reader_cycle: found dead reader pid %d, removing\n", (int) map->readers[i].pid);)
506 	        map->readers[i].pid = 0;
507 	        map->readers[i].cycle = LOST_HORIZON;
508 	        continue;
509 	    }
510 
511 	    rdr_cycle = map->readers[i].cycle;
512 
513 	    if(rdr_cycle == LOST_HORIZON)
514 	        continue;
515 
516 	    age = map_cycle - rdr_cycle;
517 
518 	    if(new_cycle == LOST_HORIZON || age >= oldest_age) {
519 	        oldest_age = age;
520 		new_cycle = rdr_cycle;
521 	    }
522 	}
523     }
524     return new_cycle;
525 }
526 
527 
528 #ifdef WITH_SHMEM_SYMBOL_LIST
529 // Add a symbol to the internal namelist. This will allow the master to
530 // pass things like the address of a ctable to the reader without having
531 // more addresses than necessary involved.
532 //
533 // Constraint - these entries are never deallocated (though they may be
534 // removed from the list) and the list is never updated with an incomplete
535 // entry, so no locking is necessary.
536 
add_symbol(shm_t * shm,CONST char * name,char * value,int type)537 int add_symbol(shm_t   *shm, CONST char *name, char *value, int type)
538 {
539     int namelen = strlen(name);
540     volatile mapheader_t *map = shm->map;
541     volatile symbol_t *s;
542     int len = sizeof(symbol_t) + namelen + 1;
543     if(type == SYM_TYPE_STRING)
544         len += strlen(value) + 1;
545 
546     s = (symbol_t *)shmalloc_raw(shm, len);
547     if(!s) return 0;
548 
549     memcpy((void*)(s->name), name, namelen + 1);
550 
551     if(type == SYM_TYPE_STRING) {
552         s->addr = &s->name[namelen+1];
553         len = strlen(value);
554         memcpy((void*)(s->addr), value, len + 1);
555     } else {
556         // take ownership of the pointer.
557         s->addr = value;
558     }
559 
560     s->type = type;
561 
562     s->next = map->namelist;
563     //fprintf(stderr, "add_symbol(%d) is saving %s at %p\n", (int)getpid(), name, s);
564     map->namelist = s;
565 
566     return 1;
567 }
568 
569 // Change the value of a symbol. Note that old values of symbols are never
570 // freed, because we don't know what they're used for and we don't want to
571 // lock the garbage collector for long-term symbol use. It's up to the
572 // caller to determine if the value can be freed and to do it.
set_symbol(shm_t * shm,CONST char * name,char * value,int type)573 int set_symbol(shm_t *shm, CONST char *name, char *value, int type)
574 {
575     volatile mapheader_t *map = shm->map;
576     volatile symbol_t *s = map->namelist;
577     while(s) {
578         if(strcmp(name, (char *)s->name) == 0) {
579             if(type != SYM_TYPE_ANY && type != s->type) {
580                 return 0;
581             }
582             if(type == SYM_TYPE_STRING) {
583 	        char *copy = (char*) shmalloc_raw(shm, strlen(value));
584                 if(!copy) return 0;
585 
586                 strcpy(copy, value);
587                 s->addr = copy;
588             } else {
589                 s->addr = value;
590             }
591             return 1;
592         }
593         s = s->next;
594     }
595     return 0;
596 }
597 
598 // Get a symbol back.
get_symbol(shm_t * shm,CONST char * name,int wanted)599 char *get_symbol(shm_t *shm, CONST char *name, int wanted)
600 {
601     volatile mapheader_t *map = shm->map;
602     volatile symbol_t *s = map->namelist;
603     while(s) {
604         if(strcmp(name, (const char*) s->name) == 0) {
605             if(wanted != SYM_TYPE_ANY && wanted != s->type) {
606                 return NULL;
607             }
608 	    //fprintf(stderr, "get_symbol(%d) found %s at %p\n", (int)getpid(), name, s);
609             return (char *) s->addr;
610         }
611         s = s->next;
612     }
613     return (char*) NULL;
614 }
615 #endif // WITH_SHMEM_SYMBOL_LIST
616 
617 
618 // Attach to an object (represented by an arbitrary string) in the shared
619 // memory file.  Always returns 1.
use_name(shm_t * share,const char * name)620 int use_name(shm_t *share, const char *name)
621 {
622     object_t *ob = share->objects;
623     while(ob) {
624         if(strcmp(ob->name, name) == 0) return 1;
625         ob = ob->next;
626     }
627     ob = (object_t *)ckalloc(sizeof *ob + strlen(name) + 1);
628 
629     ob->next = share->objects;
630     strcpy(ob->name, name);
631     share->objects = ob;
632     return 1;
633 }
634 
635 // Detach from an object (represented by a string) in the shared memory file
release_name(shm_t * share,const char * name)636 void release_name(shm_t *share, const char *name)
637 {
638     object_t *ob = share->objects;
639     object_t *prev = NULL;
640     while(ob) {
641         if(strcmp(ob->name, name) == 0) {
642             if(prev) prev->next = ob->next;
643             else share->objects = ob->next;
644             ckfree((char*)ob);
645             return;
646         }
647         prev = ob;
648         ob = ob->next;
649     }
650 }
651 
652 // Fatal error
shmpanic(const char * s)653 void shmpanic(const char *s)
654 {
655     fprintf(stderr, "PANIC: %s\n", s);
656     abort();
657 }
658 
659 // parse a string of type "nnnnK" or "mmmmG" to bytes;
parse_size(const char * s,size_t * ptr)660 int parse_size(const char *s, size_t *ptr)
661 {
662     size_t size = 0;
663 
664     while(isdigit((unsigned char)*s)) {
665         size = size * 10 + *s - '0';
666         s++;
667     }
668     switch(toupper((unsigned char)*s)) {
669         case 'G': size *= 1024;
670         case 'M': size *= 1024;
671         case 'K': size *= 1024;
672             s++;
673     }
674     if(*s)
675         return 0;
676     *ptr = size;
677     return 1;
678 }
679 
680 // NOTE: modifies the supplied string while parsing it.
parse_flags(char *)681 int parse_flags(char * /*s*/)
682 {
683     int   flags = 0;     // we don't actually support any flags anymore, so we ignore the input.
684 
685     return flags;
686 }
687 
688 // NOTE: returns a pointer to a static buffer.
flags2string(int flags)689 const char *flags2string(int flags)
690 {
691     static char buffer[32]; // only has to hold "nocore nosync shared"
692 
693     buffer[0] = 0;
694 
695 #ifdef MAP_NOCORE
696     if(flags & MAP_NOCORE)
697         strcat(buffer, "nocore ");
698     else
699 #endif
700         strcat(buffer, "core ");
701 
702 #ifdef MAP_NOSYNC
703     if(flags & MAP_NOSYNC)
704         strcat(buffer, "nosync ");
705     else
706 #endif
707         strcat(buffer, "sync ");
708 
709     strcat(buffer, "shared");
710 
711     return buffer;
712 }
713 
714 #ifdef WITH_TCL
TclGetSizeFromObj(Tcl_Interp * interp,Tcl_Obj * obj,size_t * ptr)715 int TclGetSizeFromObj(Tcl_Interp *interp, Tcl_Obj *obj, size_t *ptr)
716 {
717     if(parse_size(Tcl_GetString(obj), ptr))
718         return TCL_OK;
719 
720     Tcl_ResetResult(interp);
721     Tcl_AppendResult(interp, "Bad size, must be an integer optionally followed by 'k', 'm', or 'g': ", Tcl_GetString(obj), NULL);
722     return TCL_ERROR;
723 }
724 
TclShmError(Tcl_Interp * interp,const char * name)725 void TclShmError(Tcl_Interp *interp, const char *name)
726 {
727   Tcl_AppendResult(interp, get_last_shmem_error(), NULL);
728 }
729 
setShareBase(Tcl_Interp * interp,char * new_base)730 void setShareBase(Tcl_Interp *interp, char *new_base)
731 {
732     if (assocData == NULL) {
733         linkup_assoc_data(interp);
734     }
735 
736     if(!assocData->share_base) {
737         assocData->share_base = new_base;
738     }
739 }
740 
doCreateOrAttach(Tcl_Interp * interp,const char * sharename,const char * filename,size_t size,int flags,shm_t ** sharePtr)741 int doCreateOrAttach(Tcl_Interp *interp, const char *sharename, const char *filename, size_t size, int flags, shm_t **sharePtr)
742 {
743     shm_t     *share;
744     int        creator = 1;
745     int        new_share = 1;
746 
747     if (assocData == NULL) {
748         linkup_assoc_data(interp);
749     }
750 
751     if (size == ATTACH_ONLY) {
752         creator = 0;
753         size = 0;
754     }
755 
756     if (strcmp(sharename, "#auto") == 0) {
757         static char namebuf[32];
758         sprintf(namebuf, "share%d", ++assocData->autoshare);
759         sharename = namebuf;
760     }
761 
762     share = map_file(filename, assocData->share_base, size, flags, creator);
763     if (!share) {
764         TclShmError(interp, filename);
765         return TCL_ERROR;
766     }
767 
768     if (share->name) { // pre-existing share
769         creator = 0;
770         new_share = 0;
771     }
772 
773     if (creator) {
774         shminitmap(share);
775 	//fprintf(stderr, "successfully created new shared-memory\n");
776     } else {
777         //fprintf(stderr, "validating the provisionally attached shared-memory\n");
778         if (share->map->magic != MAP_MAGIC) {
779 	    Tcl_AppendResult(interp, "Not a valid share (bad magic): ", filename, NULL);
780 	    unmap_file(share);
781 	    return TCL_ERROR;
782 	}
783 	if (share->map->addr != share->share_base) {
784             Tcl_AppendResult(interp, "Did not attach to expected memory base (%p): ", share->map->addr, filename, NULL);
785 	    unmap_file(share);
786 	    return TCL_ERROR;
787 	}
788 
789 	// TODO: this is ugly, but we didn't find out the actual size until after we attached.  This needs to be consolidated with the share_base discovery when attaching.
790 	share->size = share->map->mapsize;
791 
792 	//fprintf(stderr, "successfully attached to shared-memory\n");
793     }
794 
795     // assocData->share_base = (char *)share + size;
796 #if 0
797     if (assocData->share_base == (char *)-1)
798         assocData->share_base = (char *)share + size;
799     else if ((char *)share == assocData->share_base)
800         assocData->share_base = assocData->share_base + size;
801 #endif
802 
803     if (new_share) {
804         share->name = (char *) ckalloc(strlen(sharename)+1);
805         strcpy(share->name, sharename);
806     }
807 
808     if (sharePtr)
809         *sharePtr = share;
810     else
811         Tcl_AppendResult(interp, share->name, NULL);
812 
813     return TCL_OK;
814 }
815 
doDetach(Tcl_Interp * interp,shm_t * share)816 int doDetach(Tcl_Interp *interp, shm_t *share)
817 {
818     if(share->objects) {
819         return TCL_OK;
820     }
821 
822     if(share->name) {
823         ckfree(share->name);
824         share->name = NULL;
825     }
826 
827     if(!unmap_file(share)) {
828         TclShmError(interp, share->name);
829         return TCL_ERROR;
830     }
831 
832     return TCL_OK;
833 }
834 
shareCmd(ClientData cData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])835 int shareCmd (ClientData cData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
836 {
837     int          cmdIndex  = -1;
838     char        *sharename = NULL;
839     shm_t       *share     = NULL;
840 
841     static CONST char *commands[] = {"create", "attach", "list", "detach", "names", "get", "multiget", "set", "info", "free", (char *)NULL};
842     enum commands {CMD_CREATE, CMD_ATTACH, CMD_LIST, CMD_DETACH, CMD_NAMES, CMD_GET, CMD_MULTIGET, CMD_SET, CMD_INFO, CMD_FREE };
843 
844     static CONST struct {
845         int need_share;         // if a missing share is an error
846         int nargs;              // >0 number args, <0 -minimum number
847         const char *args;             // String for Tcl_WrongNumArgs
848     } cmdtemplate[] = {
849         {0, -5, "filename size ?flags?"},  // CMD_CREATE
850         {0,  4, "filename"}, // CMD_ATTACH
851         {0, -2, "?share?"}, // CMD_LIST
852         {1,  3, ""}, // CMD_DETACH
853         {1, -3, "names"},  // CMD_NAMES
854         {1, 4, "name"}, // CMD_GET
855         {1, -4, "name ?name?..."}, // CMD_MULTIGET
856         {1, -5, "name value ?name value?..."}, // CMD_SET
857         {1,  3, ""}, // CMD_INFO
858         {1,  -3, "?quick?"} // CMD_FREE
859     };
860 
861     if (Tcl_GetIndexFromObj (interp, objv[1], commands, "command", TCL_EXACT, &cmdIndex) != TCL_OK) {
862         return TCL_ERROR;
863     }
864 
865     if(
866         (cmdtemplate[cmdIndex].nargs > 0 && objc != cmdtemplate[cmdIndex].nargs) ||
867         (cmdtemplate[cmdIndex].nargs < 0 && objc < -cmdtemplate[cmdIndex].nargs)
868     ) {
869         int nargs = abs(cmdtemplate[cmdIndex].nargs);
870         Tcl_WrongNumArgs (interp, (nargs > 3 ? nargs : 3), objv, cmdtemplate[cmdIndex].args);
871         return TCL_ERROR;
872     }
873 
874     // Find the share option now. It's not necessarily an error (yet) if it doesn't exist (in fact for
875     // the create/attach option it's an error if it DOES exist).
876     if(objc > 2) {
877         sharename = Tcl_GetString(objv[2]);
878 
879         share = assocData->share_list;
880         while(share) {
881             if(sharename[0]) {
882                 if (strcmp(share->name, sharename) == 0)
883                     break;
884             } else {
885                 if(share == (shm_t *)cData)
886                     break;
887             }
888             share = share->next;
889         }
890         if(share && !sharename[0])
891             sharename = share->name;
892     }
893 
894     if(cmdtemplate[cmdIndex].need_share) {
895         if(!share) {
896             Tcl_AppendResult(interp, "No such share: ", sharename, NULL);
897             return TCL_ERROR;
898         }
899     }
900 
901     switch (cmdIndex) {
902 
903         case CMD_CREATE: {
904             char      *filename;
905             size_t     size;
906             int        flags = 0;
907 
908             if(share) {
909                  Tcl_AppendResult(interp, "Share already exists: ", sharename, NULL);
910                  return TCL_ERROR;
911             }
912 
913             filename = Tcl_GetString(objv[3]);
914 
915             if (TclGetSizeFromObj (interp, objv[4], &size) == TCL_ERROR) {
916                 Tcl_AppendResult(interp, " in ... create ", sharename, NULL);
917                 return TCL_ERROR;
918             }
919 
920             if(objc > 6) {
921                 Tcl_WrongNumArgs (interp, 3, objv, cmdtemplate[cmdIndex].args);
922                 return TCL_ERROR;
923             } else if(objc == 6) {
924                 flags = parse_flags(Tcl_GetString(objv[5]));
925             }
926 
927             return doCreateOrAttach(interp, sharename, filename, size, flags, NULL);
928         }
929 
930         case CMD_ATTACH: {
931             if(share) {
932                  Tcl_AppendResult(interp, "Share already exists: ", sharename);
933                  return TCL_ERROR;
934             }
935 
936             return doCreateOrAttach(
937                 interp, sharename, Tcl_GetString(objv[3]), ATTACH_ONLY, 0, NULL);
938         }
939 
940         case CMD_DETACH: {
941             return doDetach(interp, share);
942         }
943 
944         // list shares, or list objects in share
945         case CMD_LIST: {
946             if(share) {
947                 object_t *object = share->objects;
948                 while(object) {
949                     Tcl_AppendElement(interp, object->name);
950                     object = object->next;
951                 }
952             } else {
953                 share = assocData->share_list;
954                 while(share) {
955                     Tcl_AppendElement(interp, share->name);
956                     share = share->next;
957                 }
958             }
959             return TCL_OK;
960         }
961 
962 
963         // Return miscellaneous info about the share as a name-value list
964         case CMD_INFO: {
965             Tcl_Obj *list = Tcl_NewObj();
966 
967 // APPend STRING, INTeger, or BOOLean.
968 #define APPSTRING(i,l,s) Tcl_ListObjAppendElement(i,l,Tcl_NewStringObj(s,-1))
969 #define APPINT(i,l,n) Tcl_ListObjAppendElement(i,l,Tcl_NewIntObj(n))
970 #define APPWIDEINT(i,l,n) Tcl_ListObjAppendElement(i,l,Tcl_NewWideIntObj(n))
971 #define APPBOOL(i,l,n) Tcl_ListObjAppendElement(i,l,Tcl_NewBooleanObj(n))
972 
973             if( TCL_OK != APPSTRING(interp, list, "size")
974              || TCL_OK != APPWIDEINT(interp, list, share->size)
975              || TCL_OK != APPSTRING(interp, list, "flags")
976              || TCL_OK != APPSTRING(interp, list, flags2string(share->flags))
977              || TCL_OK != APPSTRING(interp, list, "name")
978              || TCL_OK != APPSTRING(interp, list, share->name)
979              || TCL_OK != APPSTRING(interp, list, "creator")
980              || TCL_OK != APPBOOL(interp, list, share->creator)
981              || TCL_OK != APPSTRING(interp, list, "filename")
982              || TCL_OK != APPSTRING(interp, list, share->filename)
983              || TCL_OK != APPSTRING(interp, list, "base")
984              || TCL_OK != APPINT(interp, list, (long)share->map)
985             ) {
986                 return TCL_ERROR;
987             }
988 	    Tcl_IncrRefCount(list);
989             Tcl_SetObjResult(interp, list);
990             return TCL_OK;
991         }
992 
993 #ifdef WITH_SHMEM_SYMBOL_LIST
994         // Return a list of names
995         case CMD_NAMES: {
996             if (objc == 3) { // No args, all names
997                 volatile symbol_t *sym = share->map->namelist;
998                 while(sym) {
999 		  Tcl_AppendElement(interp, (char *)(sym->name));
1000                     sym = sym->next;
1001                 }
1002             } else { // Otherwise, just the names defined here
1003                 for (int i = 3; i < objc; i++) {
1004                     char *name = Tcl_GetString(objv[i]);
1005                     if(get_symbol(share, name, SYM_TYPE_ANY))
1006                         Tcl_AppendElement(interp, name);
1007                 }
1008             }
1009             return TCL_OK;
1010         }
1011 
1012         case CMD_GET: {
1013             char *name = Tcl_GetString(objv[3]);
1014             char *s = get_symbol(share, name, SYM_TYPE_STRING);
1015             if(!s) {
1016                 Tcl_ResetResult(interp);
1017                 Tcl_AppendResult(interp, "Unknown name ",name," in ",sharename, NULL);
1018                 return TCL_ERROR;
1019             }
1020             Tcl_SetResult(interp, s, TCL_VOLATILE);
1021             return TCL_OK;
1022         }
1023 
1024         case CMD_MULTIGET: {
1025             for (int i = 3; i < objc; i++) {
1026                 char *name = Tcl_GetString(objv[i]);
1027                 char *s = get_symbol(share, name, SYM_TYPE_STRING);
1028                 if(!s) {
1029                     Tcl_ResetResult(interp);
1030                     Tcl_AppendResult(interp, "Unknown name ",name," in ",sharename, NULL);
1031                     return TCL_ERROR;
1032                 }
1033                 Tcl_AppendElement(interp, s);
1034             }
1035             return TCL_OK;
1036         }
1037 
1038         case CMD_SET: {
1039 
1040             if (!share->creator) {
1041                 Tcl_AppendResult(interp, "Can not write to ",sharename,": Permission denied", NULL);
1042                 return TCL_ERROR;
1043             }
1044             if (!(objc & 1)) {
1045                 Tcl_AppendResult(interp, "Odd number of elements in name-value list.",NULL);
1046                 return TCL_ERROR;
1047             }
1048 
1049             for (int i = 3; i < objc; i += 2) {
1050                 char *name = Tcl_GetString(objv[i]);
1051                 if(get_symbol(share, name, SYM_TYPE_ANY)) {
1052                     set_symbol(share, name, Tcl_GetString(objv[i+1]), SYM_TYPE_STRING);
1053 		} else {
1054                     add_symbol(share, name, Tcl_GetString(objv[i+1]), SYM_TYPE_STRING);
1055 		}
1056             }
1057             return TCL_OK;
1058         }
1059 #endif // WITH_SHMEM_SYMBOL_LIST
1060 
1061         case CMD_FREE: {
1062             // TODO: this could be a compile-time selection.
1063 	    // TODO: actually test if objv[3] == 'quick'
1064             size_t memfree = shmfreemem(share, objc<=3);
1065 
1066             if (sizeof(size_t) > sizeof(int)) {
1067                 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(memfree));
1068             } else {
1069                 Tcl_SetObjResult(interp, Tcl_NewIntObj(memfree));
1070             }
1071             return TCL_OK;
1072         }
1073 
1074     }
1075     Tcl_AppendResult(interp, "Should not happen, internal error: no defined subcommand or missing break in switch", NULL);
1076     return TCL_ERROR;
1077 }
1078 
1079 int
Shared_Init(Tcl_Interp * interp)1080 Shared_Init(Tcl_Interp *interp)
1081 {
1082 #ifdef SHARED_TCL_EXTENSION
1083     if (NULL == Tcl_InitStubs (interp, TCL_VERSION, 0))
1084         return TCL_ERROR;
1085 #endif
1086 
1087 #ifdef WITH_SHARE_COMMAND
1088     Tcl_CreateObjCommand(interp, "share", (Tcl_ObjCmdProc *) shareCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1089 #endif
1090 
1091 #ifdef SHARED_TCL_EXTENSION
1092     return Tcl_PkgProvide(interp, "Shared", "1.0");
1093 #else
1094     return TCL_OK;
1095 #endif
1096 }
1097 #endif
1098 
1099 // vim: set ts=8 sw=4 sts=4 noet :
1100