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