home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / oper_sys / emerald / emrldsys.lha / Kernel / Em / node.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-08-17  |  17.5 KB  |  562 lines

  1.  
  2. /* Copyright 1986 Eric Jul.  May not be used for any purpose without    */
  3. /* written permission from the author.                            */
  4. /* The Emerald kernel for the Emerald programming language.             */
  5.  
  6.  
  7. /* This file contains the implementation of the Emerald kernel
  8.    Node operations.
  9. */
  10.  
  11. extern int              cEmRunnable;
  12. extern float            MachineLoadAvg();
  13.  
  14. #include <sys/time.h>
  15. #include "Kernel/h/system.h"
  16. #include "Kernel/h/assert.h"
  17. #include "Kernel/h/emTypes.h"
  18. #include "Kernel/h/hotsTypes.h"
  19. #include "Kernel/h/emeraldTypes.h"
  20. #include "Kernel/h/timeops.h"
  21. #include "Kernel/h/timerTypes.h"
  22. #include "Kernel/h/consts.h"
  23. #include "Kernel/h/kmdTypes.h"
  24. #include "Kernel/h/builtins.h"
  25. #include "Kernel/h/emkDefs.h"
  26. #include "Kernel/h/map.h"
  27. #include "Kernel/h/errMsgs.h"
  28. #include "Kernel/h/utils.h"
  29. #include "Kernel/h/macros.h"
  30. #ifdef xkernel
  31. #include "userprocess.h"
  32. #endif xkernel
  33.  
  34. #define NSStatus(x) x == Dead ? "Dead" : x == Alive ? "Alive" : \
  35.     x == Deaf ? "Deaf" : x == Dumb ? "Dumb" : x == Booting ? "Booting" : \
  36.     "Unknown status"
  37.  
  38. extern ODTag                stdLODataTag;
  39. extern GODP                 CreateGODEntry();
  40. extern OID                  thisNodeObjectOID;
  41. extern CodeODP              thisNodeCTODP;
  42. extern CodePtr              thisNodeCodePtr;
  43.  
  44. extern OID                  nodeListCTOID, nodeListElementCTOID,
  45.                 nodeListElementATOID;
  46. extern CodeODP              nodeListCTODP, nodeListElementCTODP,
  47.                 nodeListElementATODP;
  48. extern CodePtr              nodeListCodePtr, nodeListElementCodePtr;
  49. extern AbConPtr             nodeListElementAbCon, nodeAbCon,
  50.                             OIDOIDOIDToAbCon();
  51.  
  52.  
  53. extern CodeODP              timeODP;
  54. extern AbConPtr             timeAbCon;
  55.  
  56. extern void                 schedule(), fail(), StartProcessAtAddr();
  57. extern SSPtr                preemptRunning(), NewProcess();
  58.  
  59. extern CodePtr              timeCodePtr;
  60.  
  61. extern GODP                 AllocateWithOID();
  62.  
  63. extern ODP                  OTLookup();
  64. extern void                 OTInsert();
  65.  
  66. extern void                 GlobalCreateVector();
  67.  
  68. /**********************************************************************/
  69. /* Node listners are objects that want to be invoked when a node state*/
  70. /* change happens.                                                    */
  71. /**********************************************************************/
  72.  
  73. Map                         nodeListners;       /* ODP -> AbCon */
  74.  
  75. /************************************************************************/
  76. Boolean IsNode(fODP)
  77. ODP             fODP;
  78. /* Given an ODP, tell whether or not it is a reference to a node object */
  79. {
  80.     /* Node objects have OID: LNN << 24 */
  81.     if (fODP == (ODP) EMNIL) return FALSE;
  82.     if (fODP->G.tag.tag != GODTag || !fODP->G.tag.global) return FALSE;
  83.     if (fODP->G.ownOID == (OID) NULL) return FALSE;
  84.     return ((fODP->G.ownOID & 0xFFFFFF) == 0) &&
  85.     (((unsigned int) fODP->G.ownOID >> 24 & 0xFF) <= MAXNODENUMBER);
  86. }
  87.  
  88. /**********************************************************************/
  89. /*      GetNodeODPFromLNN                                             */
  90. /**********************************************************************/
  91.  
  92. GODP GetNodeODPFromLNN(fLNN)
  93. int                    fLNN;
  94. {
  95.     OID             nodeOID;
  96.     GODP            nodeGODP;
  97.     
  98.     nodeOID = mMakeNodeOIDFromLNN(fLNN);
  99.     nodeGODP = (GODP) OTLookup(nodeOID);
  100.     if (NonNULL(nodeGODP)) return nodeGODP;
  101.     /* It is not in the OT, so create one */
  102.     nodeGODP = (GODP) CreateGODEntry(nodeOID, thisNodeCodePtr->ownOID);
  103.     nodeGODP->tag.isFixed   = TRUE;
  104.     nodeGODP->tag.isResident= (fLNN == GetLNN());
  105.     nodeGODP->tag.setUpDone = TRUE;
  106.     nodeGODP->tag.inTransit = FALSE;
  107.     nodeGODP->tag.frozen    = !nodeGODP->tag.isResident;
  108.     nodeGODP->ownLoc        = mMakeLocation(fLNN, 1);
  109.     OTInsert((ODP) nodeGODP);
  110.     return nodeGODP;
  111. }
  112.  
  113. GODP GetNodeODPFromLocation(fLoc)
  114. EmLocation              fLoc;
  115. {
  116.     OID             nodeOID;
  117.     GODP            nodeGODP;
  118.     
  119.     nodeOID = mMakeNodeOIDFromLoc(fLoc);
  120.     nodeGODP = (GODP) OTLookup(nodeOID);
  121.     if (NonNULL(nodeGODP)) return nodeGODP;
  122.     /* It is not in the OT, so create one */
  123.     nodeGODP = (GODP) CreateGODEntry(nodeOID, thisNodeCodePtr->ownOID);
  124.     nodeGODP->tag.isResident= (mGetLocNodeNum(fLoc) == GetLNN());
  125.     nodeGODP->tag.setUpDone = TRUE;
  126.     nodeGODP->tag.inTransit = FALSE;
  127.     nodeGODP->tag.frozen    = !nodeGODP->tag.isResident;
  128.     nodeGODP->tag.isFixed   = TRUE;
  129.     nodeGODP->ownLoc        = mMakeLocation(mGetLocNodeNum(fLoc), 1);
  130.     OTInsert((ODP) nodeGODP);
  131.     return nodeGODP;
  132. }
  133.  
  134. /************************************************************************/
  135.  
  136. RODataPtr MakeNodeInfoFromHOTSRec(fEntry)
  137. HOTSRecord                 *fEntry;
  138. {
  139.     GODP                    entryGODP;
  140.     EmNodeListElementPtr    elem;
  141.     
  142.     entryGODP = AllocateWithOID(nodeListElementCodePtr,
  143.     (OID) NULL, nodeListElementCodePtr->instanceSize);
  144.     entryGODP->tag.frozen = FALSE;
  145.     entryGODP->tag.setUpDone = TRUE;
  146.  
  147.     elem = (EmNodeListElementPtr) entryGODP;
  148.     elem->up = (fEntry->NodeStat == Alive);
  149.     elem->LNN = fEntry->LNN;
  150.     elem->theNode = GetNodeODPFromLNN(elem->LNN);
  151.     elem->incarnationTime = (EmTimePtr) 
  152.     AllocateWithOID(timeCodePtr, (OID) NULL, timeCodePtr->instanceSize);
  153.     elem->incarnationTime->ownOID = (OID) NULL;
  154.     elem->incarnationTime->t.tv_sec = fEntry->NodeIncarnationId;
  155.     elem->incarnationTime->t.tv_usec = 0;
  156.     elem->tag.frozen = FALSE;
  157.     elem->tag.setUpDone = TRUE;
  158.     return (RODataPtr) entryGODP;
  159. }
  160.  
  161. /* call_k */
  162. void Node_getActiveNodes()
  163. {
  164.     register int            i;
  165.     int                     n;
  166.     register struct HOTStr *np;
  167.     time_t                  tmp;
  168.     char                    incarnationTimeString[20];
  169.     GODP                    theListGODP;
  170.     RODataPtr               entryRODP;
  171.     EmNodeListPtr           list;
  172.     Map                     activeMap;
  173.  
  174.     activeMap = Map_Create();
  175.     KMDTrace("Node", 3, "Node_getActiveNodes:\n");
  176.     /* Traverse HOTS table and create list of entries */
  177.     n = 0;
  178.     for (i=0; i < HOTSIZE; i++) {
  179.     for(np = HOTSTable[i]; np != NULL; np = np -> next) {
  180.         if (np->ThisEntry.NodeStat != Alive) continue;
  181.         tmp = np->ThisEntry.LastMsgTs;
  182.         sprintf(incarnationTimeString, "%.15s",
  183.             4+ctime(&np->ThisEntry.NodeIncarnationId));
  184.         KMDTrace("Node", 3, "%4d  %.15s  %-11.11s  %-8.8s   %-15.15s\n",
  185.         np->ThisEntry.LNN,
  186.         incarnationTimeString,
  187.         mGetHostName(&(np->ThisEntry.EtherAddr)),
  188.         NSStatus(np->ThisEntry.NodeStat),
  189.         4+ctime(&tmp));
  190.  
  191.         /* Create individual entry */
  192.         entryRODP = MakeNodeInfoFromHOTSRec(&np->ThisEntry);
  193.         Map_Insert(activeMap, n, (int) entryRODP);
  194.         n++;
  195.     }
  196.     }
  197.  
  198.     KMDTrace("Node", 3, "Found %d active nodes.\n", n);
  199.     GlobalCreateVector(nodeListCodePtr, (unsigned long) (n*sizeof(AVariable)),
  200.     (unsigned long) (n-1));
  201.     theListGODP = (GODP) currentSSP->regs.arg1;
  202.     list = (EmNodeListPtr) theListGODP->dataPtr;
  203.     /* Build from map */
  204.     Map_For(activeMap, n, entryRODP)
  205.     list->data[n].myAddr = (DataAddr) entryRODP; 
  206.     list->data[n].myAbConPtr = nodeListElementAbCon;        
  207.     Map_Next
  208.     Map_Destroy(activeMap);
  209.     
  210.     currentSSP->resultBrand = VariableBrand;
  211.     currentSSP->regs.arg1 = (int) theListGODP;
  212.     currentSSP->regs.arg2 = (int) OIDOIDOIDToAbCon(
  213.     OIDOfBuiltin(B_INSTAT, NODELISTINDEX),
  214.         (OID) EMNIL,
  215.     OIDOfBuiltin(B_INSTCT, NODELISTINDEX));
  216. }
  217.  
  218. /* call_k */
  219. void Node_getAllNodes()
  220. {
  221.     register int            i, n;
  222.     register struct HOTStr *np;
  223.     time_t                  tmp;
  224.     char                    incarnationTimeString[20];
  225.     GODP                    theListGODP;
  226.     RODataPtr               entryRODP;
  227.     EmNodeListPtr           list;
  228.  
  229.     GlobalCreateVector(nodeListCodePtr,
  230.     (unsigned long) (NoHOTSEntries*sizeof(AVariable)),
  231.     (unsigned long) (NoHOTSEntries-1));
  232.     theListGODP = (GODP) currentSSP->regs.arg1;
  233.     list = (EmNodeListPtr) theListGODP->dataPtr;
  234.     
  235.     KMDTrace("Node", 3, "Node_getAllNodes:\n");
  236.     /* Traverse HOTS table and create list of entries */
  237.     n = 0;
  238.     for (i=0; i < HOTSIZE; i++) {
  239.     for(np = HOTSTable[i]; np != NULL; np = np -> next) {
  240.         tmp = np->ThisEntry.LastMsgTs;
  241.         sprintf(incarnationTimeString, "%.15s",
  242.             4+ctime(&np->ThisEntry.NodeIncarnationId));
  243.         KMDTrace("Node", 3, "%4d  %.15s  %-11.11s  %-8.8s   %-15.15s\n",
  244.             np->ThisEntry.LNN,
  245.             incarnationTimeString,
  246.             mGetHostName(&(np->ThisEntry.EtherAddr)),
  247.             NSStatus(np->ThisEntry.NodeStat),
  248.             4+ctime(&tmp));
  249.  
  250.         /* Create individual entry */
  251.         entryRODP = MakeNodeInfoFromHOTSRec(&np->ThisEntry);
  252.         list->data[n].myAddr = (DataAddr) entryRODP; 
  253.         list->data[n].myAbConPtr = nodeListElementAbCon;
  254.         n++;
  255.     }
  256.     }
  257.     currentSSP->resultBrand = VariableBrand;
  258.     currentSSP->regs.arg1 = (int) theListGODP;
  259.     currentSSP->regs.arg2 = (int) OIDOIDOIDToAbCon(
  260.     OIDOfBuiltin(B_INSTAT, NODELISTINDEX),
  261.         (OID) EMNIL,
  262.     OIDOfBuiltin(B_INSTCT, NODELISTINDEX));
  263. }
  264.  
  265. /* call_k */
  266. void Node_getNodeInformation(fNode)
  267. GODP                fNode;
  268. {
  269.     int             nodeLNN;
  270.     HOTSRecord     *entryPtr;
  271.     
  272.     if (IsNULL(fNode) ||
  273.     (fNode->tag.tag != GODTag) || 
  274.     (fNode->ownOID == (OID) NULL) ||
  275.     ((fNode->ownOID & 0xFFFFFF) != 0) ||
  276.     ((nodeLNN = ((unsigned int) fNode->ownOID >> 24) & 0xFF) == 0) ||
  277.     (nodeLNN > MAXNODENUMBER))
  278.     {
  279.     fail(preemptRunning());
  280.         return;
  281.     }
  282.  
  283.     if (!mSUCCESS(HOTSSearchPtr(nodeLNN, &entryPtr))) {
  284.     /* Could not find it */
  285.     currentSSP->resultBrand = ODPBrand;
  286.     currentSSP->regs.arg1 = (int) EMNIL;
  287.     return;
  288.     }
  289.     currentSSP->resultBrand = ODPBrand;
  290.     currentSSP->regs.arg1 = (int) MakeNodeInfoFromHOTSRec(entryPtr);
  291. }
  292.  
  293. /************************************************************************/
  294.  
  295. /* call_c */
  296. float Node_getLoadAverage()
  297. /* return current load average; since we do not keep track of the
  298.    load average, return the UNIX load average plus the
  299.    instantaneous Emerald process load (minus the process itself).
  300. */
  301. {
  302.     float x = ((float) (cEmRunnable-1) + (float) MachineLoadAvg()) ;
  303.     return x;
  304. }
  305.  
  306. /* call_c */
  307. EmTimePtr Node_getTimeOfDay()
  308. {
  309. #ifndef xkernel
  310.     struct timezone         theZone;
  311. #endif xkernel
  312.     register EmTimePtr      theTime;
  313.     
  314.     theTime                 = (EmTimePtr) emalloc(sizeof(EmTime));
  315.     theTime->tag            = stdLODataTag;
  316.     theTime->myCodePtr      = timeCodePtr;
  317.     theTime->ownOID         = (OID) NULL;
  318. #ifndef xkernel
  319.     gettimeofday(&theTime->t, &theZone);
  320. #else xkernel
  321.     xgettime(&theTime->t);
  322. #endif xkernel
  323.     return (theTime);
  324. }
  325.  
  326. /**********************************************************************/
  327.  
  328. /* call_c */
  329. int Node_getLNN()
  330. /* Just return our node LNN */
  331. {
  332.     return(GetLNN());
  333. }
  334.  
  335. /**********************************************************************/
  336.  
  337. /* call_k */
  338. int Node_getName()
  339. /* return a string with our name in it */
  340. {
  341.     register StringPtr  x;
  342.     char thename[128], *p;
  343.     int sizeInBytes;
  344.     extern CodePtr stringCodePtr;
  345.     gethostname(thename, 128);
  346.     for (p = thename; *p && *p != '.'; p++) ;
  347.     *p = '\0';
  348.     sizeInBytes = p - thename;
  349.     x = (StringPtr) emalloc((int) sizeInBytes + String_data);
  350.     x->tag              = stdLODataTag; 
  351.     x->tag.replicated   = TRUE;
  352.     x->myCodePtr        = stringCodePtr;
  353.     x->ownOID           = (OID) NULL;
  354.     x->sizeInBytes      = sizeInBytes;
  355.     strncpy((char *)&x->data[0], thename, sizeInBytes);
  356.     KMDTrace("Create", 3, "Create String 0x%05x, %d chars, code %s\n",
  357.     x, sizeInBytes, PPCodePtr(stringCodePtr));
  358.     currentSSP->resultBrand = ODPBrand;
  359.     currentSSP->regs.arg1 = (int) x;
  360. }
  361.   
  362. /**********************************************************************/
  363.  
  364. /* call_k */
  365. /*ARGSUSED*/
  366. void Node_system(str, wantItsStdin, wantItsStdout)
  367. /*
  368.  * Do a system of the given string, returning its stdin (as an OutStream)
  369.  * and stdout (as an Instream)
  370.  */
  371. StringPtr str;
  372. int wantItsStdin, wantItsStdout;
  373. {
  374. #ifdef undef
  375.   if (IsNIL(str)) return;
  376.   if (str->sizeInBytes == 0) return;
  377.  
  378.     do some pipes 
  379.     if ((pid = vfork()) == 0) {
  380.         do some dup2s and closes
  381.         execl("/bin/sh", "sh", "-c", s, 0);
  382.         _exit(127);
  383.     } else {
  384.         do some dup2s and closes
  385.     }
  386.     create the streams  
  387.  
  388.     The streams will be placed on the stack in the result spots (2 of
  389.     them).
  390. #endif
  391. }
  392.   
  393. /**********************************************************************/
  394.  
  395. /* Handler routine passed to MMSetTimer */
  396. HResult TimePassed(fSSPtr)
  397. SSPtr      fSSPtr;
  398. /* A timer has expired and the process waiting for it should be restarted */
  399. {
  400.     schedule(fSSPtr);
  401. }
  402.  
  403. /**********************************************************************/
  404. /*      Delay                                                         */
  405. /**********************************************************************/
  406. /* Kernel call */
  407. void Delay(fDelayTime)
  408. register EmTimePtr      fDelayTime;
  409. /* delay the process for the given amount of time */
  410. {
  411.     SSPtr       theProcess;
  412.     if (IsNIL(fDelayTime)) {
  413.     fail(preemptRunning());
  414.     return;
  415.     }
  416.     
  417.     theProcess = preemptRunning();
  418.     theProcess->status.rs   = SSTimerWait;
  419.     KMDTrace("LineNumber", 4,
  420.     "%s blocking on Timer for %d.%06d seconds in %s\n",
  421.     PPPOID(theProcess->processOID), fDelayTime->t.tv_sec,
  422.     fDelayTime->t.tv_usec, PPSSPlace(theProcess));
  423.     MMSetMicroTimer((int)fDelayTime->t.tv_sec, (int)fDelayTime->t.tv_usec,
  424.     (HandlerPtr)TimePassed, (int)theProcess, (TimerId *)NULL);
  425. }
  426. /**********************************************************************/
  427. /*      WaitUntil                                                     */
  428. /**********************************************************************/
  429. /* Kernel call */
  430. void WaitUntil(fTime)
  431. register EmTimePtr      fTime;
  432. /* delay the process until the time given */
  433. {
  434.     struct timeval      now, diff;
  435. #ifndef xkernel
  436.     struct timezone     theZone;
  437. #endif xkernel
  438.     SSPtr               theProcess;
  439.  
  440.     if (IsNIL(fTime)) {
  441.     fail(preemptRunning());
  442.     return;
  443.     }
  444. #ifndef xkernel
  445.     gettimeofday(&now, &theZone);
  446. #else xkernel
  447.     xgettime(&now);
  448. #endif xkernel
  449.     if (timeGtrEq(now, fTime->t)) return;
  450.     timeSub(diff, fTime->t, now);
  451.     theProcess = preemptRunning();
  452.     theProcess->status.rs   = SSTimerWait;
  453.     KMDTrace("LineNumber", 4,
  454.     "%s blocking on Timer for %d.%06d seconds in %s\n",
  455.     PPPOID(theProcess->processOID), diff.tv_sec, diff.tv_usec,
  456.     PPSSPlace(theProcess));
  457.     MMSetMicroTimer((int)diff.tv_sec, (int)diff.tv_usec, 
  458.       (HandlerPtr)TimePassed, (int) theProcess, (TimerId *)NULL);
  459. }
  460.  
  461. /**********************************************************************/
  462. /*      SetNodeEventHandler                                           */
  463. /**********************************************************************/
  464.  
  465. /* call_c */
  466. void Node_setNodeEventHandler(fODP, fAbConPtr)
  467. ODP                     fODP;
  468. AbConPtr                fAbConPtr;
  469. {
  470.     AVariable           theVar;
  471.     theVar.myAddr       = (DataAddr) fODP;
  472.     theVar.myAbConPtr   = fAbConPtr;
  473.     
  474.     KMDTrace("Node", 3, "Setting event handler, var: %s\n", PPVar(&theVar));
  475.     if (IsNIL(fODP) || IsNIL(fAbConPtr)) {
  476.     KMDTrace("Node", 3, "NIL\n");
  477.     return;
  478.     }
  479.     Map_Insert(nodeListners, (int) fODP, (int) fAbConPtr);
  480. }
  481.  
  482. /**********************************************************************/
  483. /*      RemoveNodeEventHandler                                        */
  484. /**********************************************************************/
  485.  
  486. /* call_c */
  487. void Node_removeNodeEventHandler(fODP, fAbConPtr)
  488. ODP                     fODP;
  489. AbConPtr                fAbConPtr;
  490. {
  491.     AVariable           theVar;
  492.     theVar.myAddr       = (DataAddr) fODP;
  493.     theVar.myAbConPtr   = fAbConPtr;
  494.     
  495.     KMDTrace("Node", 3, "Removing event handler, var: %s\n", PPVar(&theVar));
  496.     if (IsNIL(fODP) || IsNIL(fAbConPtr)) {
  497.     KMDTrace("Node", 3, "NIL\n");
  498.     return;
  499.     }
  500.     Map_Delete(nodeListners, (int) fODP);
  501. }
  502.  
  503. /**********************************************************************/
  504. /*      NodeStateChanged                                              */
  505. /**********************************************************************/
  506. void NodeStateChanged(fHOTS)
  507. HOTSRecord             *fHOTS;
  508. /* There has been a state change for the given node */
  509. {
  510.     GODP                theODP;
  511.     AbConPtr            theAbConPtr;
  512.     
  513.     KMDTrace("Node", 3, "State changed for node #%d\n", fHOTS->LNN);
  514.     /*
  515.      * Traverse the list of objects that have requested notification of
  516.      * node state changes and schedule a notification call.
  517.      */
  518.  
  519.   /* If we are initialized then .... */
  520.   if (NonNULL(nodeListners)) {
  521.     Map_For(nodeListners, theODP, theAbConPtr)
  522.         KMDTrace("Node", 4, "Node state invoke for 0x%08x\n", theODP);
  523.         if (!theODP->tag.isResident) {
  524.         KMDTrace("Node", 3, "Node state not told to 0x%08x\n", theODP);
  525.         KMDTrace("Node", 3, "Remote invoke not implemented\n");
  526.     } else {
  527.             register SSPtr      p;
  528.             register EmTimePtr  theTime;
  529.             int                 opNum;
  530.  
  531.         p       = NewProcess();
  532.  
  533.             /* Push arguments onto stack */
  534.             PUSHIT(p->regs.sp, nodeAbCon);
  535.         PUSHIT(p->regs.sp, GetNodeODPFromLNN(fHOTS->LNN));
  536.  
  537.             /* Push second argument onto stack */
  538.             theTime = (EmTimePtr) AllocateWithOID(timeCodePtr,
  539.                 (OID) NULL, timeCodePtr->instanceSize);
  540.         theTime->ownOID = (OID) NULL;
  541.         theTime->t.tv_sec = fHOTS->NodeIncarnationId;
  542.         theTime->t.tv_usec = 0;
  543.         PUSHIT(p->regs.sp, timeAbCon);
  544.         PUSHIT(p->regs.sp, theTime);
  545.  
  546.             opNum = ((fHOTS->NodeStat == Alive) ? 0 : 1);
  547.             /* Note, the OpNum MUST agree with the AT handlerType */
  548.         StartProcessAtAddr(p, theODP, theODP->dataPtr,
  549.                 (CodeAddr) theAbConPtr->opVector[opNum].opAddress);
  550.     }
  551.     Map_Next;
  552.   }
  553. }
  554.  
  555. void NodeInit()
  556. {
  557.     KMDTrace("Node", 5, "Node initialization\n");
  558.     nodeListners    = Map_Create();
  559. }
  560.  
  561. /* Copyright 1986 Eric Jul */
  562.