[code] Copyover - preliminary Circle port (WAS: Building ports revisited)

From: Erwin S. Andreasen (erwin@PIP.DKNET.DK)
Date: 07/16/97


On Tue, 15 Jul 1997, Eduo - Tempus Fugit Pseudo Admin wrote:

> In subject: Has anyone thought about making a warm boot? where the mud
> effectively reboots but without requiring logging out and that (only a
> pause of a minute or so in playing), I saw once code to do this in Merc (I
> may have the code lying aroudn somewhere), with this the above question

For fun I've ported my code to Circle. Attached is a preliminary patch.
Since I don't use circle, some of the things I did were mostly guesses :)

This was tested with a minimal configuration, stock circle - Linux
machine.

I used save_char(ch, ch->in_room); and Crash_rentsave(ch,0) to save
character; I am not sure how that works together with rent and such.

For loading, I duplicateed some of the code in interpret.c. Equipped EQ
doesn't seem to stay equipped after a character is loaded though.

Some code was moved into seperate functions so that it could be used by
both the copyover and the standard MUD routines.

There was one thing I couldn't figure out: it seems that Circle holds the
various index files open - index_boot opens an index file but never closes
it. The FILE* is a local and is so lost when exiting the function.  Is
there some devious way this descriptor is used elsewhere that I missed? In
the patched attached I simply close all those files.

NOTE AGAIN: I don't run Circle. I haven't tested this with patch X or Z.
This will not work under any other OS than UNIX. And don't ask me how to
apply this patch either :)

A short description of Copyover: The COPYOVER command will reexecute the
MUD: the executable files is restarted, reloading all library files - and
new code - but the descriptors to players are NOT closed. Rather, a list
of player names is saved and after the MUD reboots, it reloads those
players - causing them to see them to notice 5 seconds of lag.

NOTE: if you want this command to be used for reloading code, you should
probably add a rm -f ../bin/circle before actually doing the compiling of
circle in the makefile - otherwise your compiler will try to modify the
already running copy of circle (which is not allowed).

Removing the file will break the link between the bin/circle name and the
actual data on disk - the code used by the MUD stays on disk until the
game exits but the new copy will use other disk blocks.

Not doing this may cause grave problems with most UNIX systems that use
dynamic loading (most however are sane enough to warn you with a "text
file busy" if you do this :)

Oh, also: Circle seems to be using GET_XXX macros for everything; I am not
used to that so most of the code accesses the fields directly.

And finally: Circle uses \r\n, MERC uses \n\r. While as \r\n seems to be
correct, using \r\n breaks a client like tush.


--- act.wizard.c        1997/07/16 19:39:56     1.1
+++ act.wizard.c        1997/07/16 22:33:12
@@ -2956,3 +2956,74 @@ ACMD (do_syslog)
        sprintf (buf, "Your syslog is now %s.\r\n", logtypes[tp]);
        send_to_char (buf, ch);
 }
+
+extern int mother_desc, port;
+extern FILE *player_fl;
+void Crash_rentsave(struct char_data * ch, int cost);
+
+#define EXE_FILE "bin/circle" /* maybe use argv[0] but it's not reliable */
+
+/* (c) 1996-97 Erwin S. Andreasen <erwin@pip.dknet.dk> */
+ACMD(do_copyover)
+{
+       FILE *fp;
+       struct descriptor_data *d, *d_next;
+       char buf [100], buf2[100];
+
+       fp = fopen (COPYOVER_FILE, "w");
+
+       if (!fp)
+       {
+               send_to_char ("Copyover file not writeable, aborted.\n\r",ch);
+               return;
+       }
+
+       /* Consider changing all saved areas here, if you use OLC */
+       sprintf (buf, "\n\r *** COPYOVER by %s - please remain seated!\n\r", GET_NAME(ch));
+
+       /* For each playing descriptor, save its state */
+       for (d = descriptor_list; d ; d = d_next)
+       {
+               struct char_data * och = d->character;
+               d_next = d->next; /* We delete from the list , so need to save this */
+
+               if (!d->character || d->connected > CON_PLAYING) /* drop those logging on */
+               {
+            write_to_descriptor (d->descriptor, "\n\rSorry, we are rebooting. Come back in a few minutes.\n\r");
+                       close_socket (d); /* throw'em out */
+               }
+               else
+               {
+                       fprintf (fp, "%d %s %s\n", d->descriptor, GET_NAME(och), d->host);
+
+            /* save och */
+            Crash_rentsave(och,0);
+            save_char(och, och->in_room);
+                       write_to_descriptor (d->descriptor, buf);
+               }
+       }
+
+       fprintf (fp, "-1\n");
+       fclose (fp);
+
+       /* Close reserve and other always-open files and release other resources */
+
+    fclose(player_fl);
+
+       /* exec - descriptors are inherited */
+
+       sprintf (buf, "%d", port);
+    sprintf (buf2, "-C%d", mother_desc);
+
+    /* Ugh, seems it is expected we are 1 step above lib - this may be dangerous! */
+    chdir ("..");
+
+       execl (EXE_FILE, "circle", buf, buf2, (char *) NULL);
+
+       /* Failed - sucessful exec will not return */
+
+       perror ("do_copyover: execl");
+       send_to_char ("Copyover FAILED!\n\r",ch);
+
+    exit (1); /* too much trouble to try to recover! */
+}
--- comm.c      1997/07/16 19:39:56     1.1
+++ comm.c      1997/07/16 22:18:38
@@ -76,6 +76,10 @@ extern int auto_save;                        /* see config.c
 extern int autosave_time;              /* see config.c */
 struct timeval null_time;              /* zero-valued time structure */

+static bool fCopyOver;          /* Are we booting in copyover mode? */
+int  mother_desc;        /* Now a global */
+int     port;
+
 /* functions in this file */
 int     get_from_q (struct txt_q *queue, char *dest, int *aliased);
 void    init_game (int port);
@@ -97,7 +101,7 @@ void    record_usage (void);
 void    make_prompt (struct descriptor_data *point);
 void    check_idle_passwords (void);
 void    heartbeat (int pulse);
-
+void    init_descriptor (struct descriptor_data *newd, int desc);

 /* extern fcnts */
 void    boot_db (void);
@@ -134,7 +138,6 @@ void    gettimeofday (struct timeval *t,

 int     main (int argc, char **argv)
 {
-       int     port;
        char    buf[512];
        int     pos = 1;
        char   *dir;
@@ -146,6 +149,11 @@ int     main (int argc, char **argv)
        {
                switch (*(argv[pos] + 1))
                {
+                       case 'C': /* -C<socket number> - recover from copyover, this is the control socket */
+                               fCopyOver = TRUE;
+                               mother_desc = atoi(argv[pos]+2);
+                               break;
+
                        case 'd':
                                if (*(argv[pos] + 2))
                                        dir = argv[pos] + 2;
@@ -228,20 +236,110 @@ int     main (int argc, char **argv)
        return 0;
 }

+int enter_player_game(struct descriptor_data *d);
+
+/* Reload players after a copyover */
+void copyover_recover()
+{
+       struct descriptor_data *d;
+       FILE *fp;
+       char host[1024];
+    struct char_file_u tmp_store;
+       int desc, player_i;
+    bool fOld;
+    char name[MAX_INPUT_LENGTH];
+
+       log ("Copyover recovery initiated");
+
+       fp = fopen (COPYOVER_FILE, "r");
+
+       if (!fp) /* there are some descriptors open which will hang forever then ? */
+       {
+               perror ("copyover_recover:fopen");
+               log ("Copyover file not found. Exitting.\n\r");
+               exit (1);
+       }
+
+       unlink (COPYOVER_FILE); /* In case something crashes - doesn't prevent reading  */
+
+       for (;;)
+    {
+        fOld = TRUE;
+               fscanf (fp, "%d %s %s\n", &desc, name, host);
+               if (desc == -1)
+                       break;
+
+               /* Write something, and check if it goes error-free */
+               if (write_to_descriptor (desc, "\n\rRestoring from copyover...\n\r") < 0)
+               {
+                       close (desc); /* nope */
+                       continue;
+               }
+
+        /* create a new descriptor */
+        CREATE (d, struct descriptor_data, 1);
+        memset ((char *) d, 0, sizeof (struct descriptor_data));
+               init_descriptor (d,desc); /* set up various stuff */
+
+               strcpy(d->host, host);
+               d->next = descriptor_list;
+               descriptor_list = d;
+
+        d->connected = CON_CLOSE;
+
+               /* Now, find the pfile */
+
+        CREATE(d->character, struct char_data, 1);
+        clear_char(d->character);
+        CREATE(d->character->player_specials, struct player_special_data, 1);
+        d->character->desc = d;
+
+        if ((player_i = load_char(name, &tmp_store)) >= 0)
+        {
+            store_to_char(&tmp_store, d->character);
+            GET_PFILEPOS(d->character) = player_i;
+            if (!PLR_FLAGGED(d->character, PLR_DELETED))
+                REMOVE_BIT(PLR_FLAGS(d->character),PLR_WRITING | PLR_MAILING | PLR_CRYO);
+            else
+                fOld = FALSE;
+        }
+        else
+            fOld = FALSE;
+
+               if (!fOld) /* Player file not found?! */
+               {
+                       write_to_descriptor (desc, "\n\rSomehow, your character was lost in the copyover. Sorry.\n\r");
+                       close_socket (d);
+               }
+               else /* ok! */
+               {
+            write_to_descriptor (desc, "\n\rCopyover recovery complete.\n\r");
+            enter_player_game(d);
+            d->connected = CON_PLAYING;
+            look_at_room(d->character, 0);
+               }
+
+       }
+
+       fclose (fp);
+}
+


 /* Init sockets, run game, and cleanup sockets */
 void    init_game (int port)
 {
-       int     mother_desc;

        srandom (time (0));

        log ("Finding player limit.");
        max_players = get_max_players ();

-       log ("Opening mother connection.");
-       mother_desc = init_socket (port);
+       if (!fCopyOver) /* If copyover mother_desc is already set up */
+       {
+               log ("Opening mother connection.");
+               mother_desc = init_socket (port);
+       }

        boot_db ();

@@ -250,6 +348,9 @@ void    init_game (int port)
        signal_setup ();
 #endif

+       if (fCopyOver) /* reload players */
+               copyover_recover();
+
        log ("Entering game loop.");

        game_loop (mother_desc);
@@ -1045,6 +1146,24 @@ void    write_to_output (const char *txt
    *  socket handling                                                  *
    ****************************************************************** */

+/* Initialize a descriptor */
+void init_descriptor (struct descriptor_data *newd, int desc)
+{
+    static int last_desc = 0;  /* last descriptor number */
+
+       newd->descriptor = desc;
+       newd->connected = CON_GET_NAME;
+       newd->idle_tics = 0;
+       newd->wait = 1;
+       newd->output = newd->small_outbuf;
+       newd->bufspace = SMALL_BUFSIZE - 1;
+       newd->next = descriptor_list;
+       newd->login_time = time (0);
+
+       if (++last_desc == 1000)
+               last_desc = 1;
+       newd->desc_num = last_desc;
+}

 int     new_descriptor (int s)
 {
@@ -1052,7 +1171,6 @@ int     new_descriptor (int s)
        int     sockets_connected = 0;
        unsigned long addr;
        int     i;
-       static int last_desc = 0;       /* last descriptor number */
        struct descriptor_data *newd;
        struct sockaddr_in peer;
        struct hostent *from;
@@ -1118,19 +1236,7 @@ int     new_descriptor (int s)
        mudlog (buf2, CMP, LVL_GOD, FALSE);
 #endif

-       /* initialize descriptor data */
-       newd->descriptor = desc;
-       newd->connected = CON_GET_NAME;
-       newd->idle_tics = 0;
-       newd->wait = 1;
-       newd->output = newd->small_outbuf;
-       newd->bufspace = SMALL_BUFSIZE - 1;
-       newd->next = descriptor_list;
-       newd->login_time = time (0);
-
-       if (++last_desc == 1000)
-               last_desc = 1;
-       newd->desc_num = last_desc;
+       init_descriptor(newd, desc);

        /* prepend to list */
        descriptor_list = newd;
--- comm.h      1997/07/16 19:39:56     1.1
+++ comm.h      1997/07/16 21:21:18
@@ -10,6 +10,8 @@

 #define NUM_RESERVED_DESCS     8

+#define COPYOVER_FILE "copyover.dat"
+
 /* comm.c */
 void    send_to_all (char *messg);
 void    send_to_char (char *messg, struct char_data *ch);
--- db.c        1997/07/16 19:39:56     1.1
+++ db.c        1997/07/16 22:34:45
@@ -609,7 +609,9 @@ void    index_boot (int mode)
                qsort (help_table, top_of_helpt, sizeof (struct help_index_element), hsort);

                top_of_helpt--;
-       }
+    }
+
+    fclose (index); /* The index file was not closed - deliberate? */

 }

--- interpreter.c       1997/07/16 19:39:56     1.1
+++ interpreter.c       1997/07/16 22:03:45
@@ -63,6 +63,7 @@ ACMD(do_ban);
 ACMD(do_bash);
 ACMD(do_cast);
 ACMD(do_color);
+ACMD(do_copyover);
 ACMD(do_commands);
 ACMD(do_consider);
 ACMD(do_credits);
@@ -244,6 +245,7 @@ const struct command_info cmd_info[] = {
   { "comb"     , POS_RESTING , do_action   , 0, 0 },
   { "commands" , POS_DEAD    , do_commands , 0, SCMD_COMMANDS },
   { "compact"  , POS_DEAD    , do_gen_tog  , 0, SCMD_COMPACT },
+  { "copyover" , POS_DEAD    , do_copyover , LVL_GRGOD, 0 },
   { "cough"    , POS_RESTING , do_action   , 0, 0 },
   { "credits"  , POS_DEAD    , do_gen_ps   , 0, SCMD_CREDITS },
   { "cringe"   , POS_RESTING , do_action   , 0, 0 },
@@ -1234,6 +1236,46 @@ int perform_dupe_check(struct descriptor
 }


+/* load the player, put them in the right room - used by copyover_recover too */
+int enter_player_game (struct descriptor_data *d)
+{
+    extern sh_int r_mortal_start_room;
+    extern sh_int r_immort_start_room;
+    extern sh_int r_frozen_start_room;
+
+    sh_int load_room;
+    int load_result;
+
+    reset_char(d->character);
+    if (PLR_FLAGGED(d->character, PLR_INVSTART))
+        GET_INVIS_LEV(d->character) = GET_LEVEL(d->character);
+    if ((load_result = Crash_load(d->character)))
+        d->character->in_room = NOWHERE;
+    save_char(d->character, NOWHERE);
+
+    d->character->next = character_list;
+    character_list = d->character;
+
+    if ((load_room = GET_LOADROOM(d->character)) != NOWHERE)
+        load_room = real_room(load_room);
+
+    /* If char was saved with NOWHERE, or real_room above failed... */
+    if (load_room == NOWHERE) {
+        if (GET_LEVEL(d->character) >= LVL_IMMORT) {
+            load_room = r_immort_start_room;
+        } else {
+            load_room = r_mortal_start_room;
+        }
+    }
+
+    if (PLR_FLAGGED(d->character, PLR_FROZEN))
+        load_room = r_frozen_start_room;
+
+    char_to_room(d->character, load_room);
+
+    return load_result;
+}
+

 /* deal with newcomers and other non-playing sockets */
 void nanny(struct descriptor_data *d, char *arg)
@@ -1242,12 +1284,8 @@ void nanny(struct descriptor_data *d, ch
   int player_i, load_result;
   char tmp_name[MAX_INPUT_LENGTH];
   struct char_file_u tmp_store;
-  extern sh_int r_mortal_start_room;
-  extern sh_int r_immort_start_room;
-  extern sh_int r_frozen_start_room;
   extern const char *class_menu;
   extern int max_bad_pws;
-  sh_int load_room;

   int load_char(char *name, struct char_file_u *char_element);
   int parse_class(char arg);
@@ -1522,32 +1560,9 @@ void nanny(struct descriptor_data *d, ch
       break;

     case '1':
-      reset_char(d->character);
-      if (PLR_FLAGGED(d->character, PLR_INVSTART))
-       GET_INVIS_LEV(d->character) = GET_LEVEL(d->character);
-      if ((load_result = Crash_load(d->character)))
-       d->character->in_room = NOWHERE;
-      save_char(d->character, NOWHERE);
-      send_to_char(WELC_MESSG, d->character);
-      d->character->next = character_list;
-      character_list = d->character;
-
-      if ((load_room = GET_LOADROOM(d->character)) != NOWHERE)
-       load_room = real_room(load_room);
-
-      /* If char was saved with NOWHERE, or real_room above failed... */
-      if (load_room == NOWHERE) {
-       if (GET_LEVEL(d->character) >= LVL_IMMORT) {
-         load_room = r_immort_start_room;
-       } else {
-         load_room = r_mortal_start_room;
-       }
-      }

-      if (PLR_FLAGGED(d->character, PLR_FROZEN))
-       load_room = r_frozen_start_room;
-
-      char_to_room(d->character, load_room);
+      load_result = enter_player_game(d);
+      send_to_char(WELC_MESSG, d->character);
       act("$n has entered the game.", TRUE, d->character, 0, 0, TO_ROOM);

       STATE(d) = CON_PLAYING;




 =============================================================================
Erwin Andreasen   Herlev, Denmark <erwin@pip.dknet.dk>  UNIX System Programmer
<URL:http://pip.dknet.dk/~erwin/>                       (not speaking for) DDE
 =============================================================================


     +------------------------------------------------------------+
     | Ensure that you have read the CircleMUD Mailing List FAQ:  |
     | http://democracy.queensu.ca/~fletcher/Circle/list-faq.html |
     +------------------------------------------------------------+



This archive was generated by hypermail 2b30 : 12/08/00 PST