/* * Playground+ - reboot.c * Code to perform a seamless reboot (c) phypor 1998 * --------------------------------------------------------------------------- * * Changes to original: * - Change of #define's to point to correct path * - Replacement of ez_wall, ez_tellplayer and ez_log to PG+ formats * - All errors are sent to SU channel * - Code will wait till people out of editor before rebooting * - Slight changes to error messages * - Some uninformative and badly spelt comments removed * - Reset several "time" stats on reboot * - Change of include file locations * - Code tidely indented */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include "include/config.h" #include "include/player.h" #include "include/proto.h" /* Only add all this is we want seamless rebooting */ #ifdef SEAMLESS_REBOOT #define REBOOTING_DIR "junk/rebooting" #define PLAYER_LIST_FILE "junk/rebooting/_plist" #define TALKER_SYSINFO_FILE "junk/rebooting/_sysinfo" #define CHILDS_PID_FILE "junk/rebooting/_child_pid" extern int main_descriptor; extern int alive_descriptor; #ifdef IDENT extern void kill_ident_server(void); #endif /* IDENT */ char rebooter[MAX_NAME]; /* use a global for nicer looking code */ typedef struct talker_system_info { int main_fd, angel_fd, up_date, logins, sys_flags, session_reset; char session[MAX_SESSION], sess_name[MAX_NAME], rebooter[MAX_NAME]; } talker_system_info; int build_sysinfo(void) { talker_system_info tsi; FILE *f; memset(&tsi, 0, sizeof(talker_system_info)); tsi.main_fd = main_descriptor; tsi.angel_fd = alive_descriptor; tsi.up_date = up_date; tsi.logins = logins; tsi.sys_flags = sys_flags; tsi.session_reset = session_reset; strncpy(tsi.session, session, MAX_SESSION - 1); strncpy(tsi.sess_name, sess_name, MAX_NAME - 1); if (current_player) strncpy(tsi.rebooter, current_player->lower_name, MAX_NAME - 1); f = fopen(TALKER_SYSINFO_FILE, "w"); if (!f) { SUWALL(" -=*> Failed to open reboot system info file [%s]\n", strerror(errno)); LOGF("error", "Failed to open reboot system info file [%s]", strerror(errno)); return -1; } fwrite((void *) &tsi, sizeof(talker_system_info), 1, f); fclose(f); return 0; } int build_loggedin_players_list(void) { player *scan; char *oldstack = stack; int fd; /* build players list */ /* get to the end of the flatlist */ for (scan = flatlist_start; scan->flat_next; scan = scan->flat_next); /* work backwards through it, adding names to stack */ for (; scan; scan = scan->flat_previous) if (scan->location) stack += sprintf(stack, "%s\n", scan->lower_name); stack = end_string(stack); fd = open(PLAYER_LIST_FILE, (O_WRONLY | O_CREAT | O_NONBLOCK | O_TRUNC), (S_IRUSR | S_IWUSR)); if (fd < 0) { SUWALL(" -=*> Failed to open reboot player list file [%s]\n", strerror(errno)); LOGF("error", "Failed to open reboot player list file [%s]", strerror(errno)); stack = oldstack; return -1; } write(fd, oldstack, strlen(oldstack)); close(fd); stack = oldstack; return 0; } int build_loggedin_players_info(void) { player *scan, *oldcp; char *oldstack = stack; char location_string[MAX_NAME + MAX_ID]; FILE *f; for (scan = flatlist_start; scan; scan = scan->flat_next) { /* if the player doesnt have a location, no telling whut point they are in the login process, (or maybe they arent even logging in) we wont even attempt to keep up with them */ if (!scan->location) { oldcp = current_player; /* so the tell_player goes through */ current_player = scan; tell_player(scan, "\n\n Reboot in progress, losing connection.\n" " Please relogin.\n\n\n"); current_player = oldcp; shutdown(scan->fd, 2); close(scan->fd); continue; } /* do this roundabout so we cant possibly overwrite field length */ memset(location_string, 0, MAX_NAME + MAX_ID); sprintf(location_string, "%s.%s", scan->location->owner->lower_name, scan->location->id); strncpy(scan->location_string, location_string, MAX_NAME + MAX_ID - 1); sprintf(stack, "%s/%s", REBOOTING_DIR, scan->lower_name); stack = end_string(stack); f = fopen(oldstack, "w"); if (!f) { SUWALL(" -=*> Failed to open reboot player info file (%s) [%s]\n", scan->lower_name, strerror(errno)); LOGF("error", "Failed to open reboot player info (%s) [%s]", scan->lower_name, strerror(errno)); stack = oldstack; return -1; } fwrite((void *) scan, sizeof(player), 1, f); fclose(f); stack = oldstack; } return 0; } void close_fds(void) { int i, d = 0; player *scan; for (i = 4; i < (1 << 12); i++) /* start at 4, so we dont kill stdout, etc */ { if (i == main_descriptor || i == alive_descriptor) continue; for (scan = flatlist_start; scan; scan = scan->flat_next) if (scan->fd == i) break; if (scan) continue; if (close(i) == 0) d++; } } void do_reboot(void) { player *scan; int cpid; FILE *f; char server_name[256]; struct itimerval itimer; sprintf(server_name, "-=> %s <=- Talk Server on port %d", get_config_msg("talker_name"), atoi(get_config_msg("port"))); SUWALL(" -=*> Starting reboot ...\n"); if (build_sysinfo() < 0) { SUWALL(" -=*> Reboot failed (build_sysinfo) ...\n"); return; } if (build_loggedin_players_list() < 0) { SUWALL(" -=*> Reboot failed (build_loggedin_players_list) ...\n"); return; } if (build_loggedin_players_info() < 0) { SUWALL(" -=*> Reboot failed (build_loggedin_players_info) ...\n"); return; } /* kill the timer */ itimer.it_interval.tv_sec = itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = itimer.it_value.tv_usec = 0; setitimer(ITIMER_REAL, &itimer, 0); for (scan = flatlist_start; scan; scan = scan->flat_next) save_player(scan); sys_flags |= SHUTDOWN; sync_all(); sync_notes(0); sync_sitems(0); #ifdef IDENT kill_ident_server(); #endif /* IDENT */ #ifdef INTERCOM kill_intercom(); #endif sync_all_news_headers(); #ifdef ALLOW_MULTIS reboot_save_multis(); destroy_all_multis(); #endif /* here is where to add for any other syncing that needs to be done when your talker shutsdown... notes, etc */ close_fds(); cpid = getpid(); f = fopen(CHILDS_PID_FILE, "w"); if (f) { fprintf(f, "%d\n", cpid); fclose(f); } else LOGF("error", "Failed to fopen [%s] coz %s ... reboot fails.", CHILDS_PID_FILE, strerror(errno)); execlp("bin/" TALKER_EXEC, server_name, (char *) NULL); sys_flags &= ~SHUTDOWN; log("error", "Failed to execlp!"); SUWALL(" -=*> Failed to reboot ... !!!\n"); } /* Modified by Silver to take into account that people may be in the editor when the code reboots */ void reboot_command(player * p, char *str) { player *scan; char *oldstack = stack; CHECK_DUTY(p); #ifdef AUTOSHUTDOWN if (auto_sd) { tell_player(p, "\n Autoshutdown is active. Rebooting the talker may cause pfile corruption.\n" " You are advised to perform a proper shutdown to avoid problems.\n"); return; } #endif /* AUTOSHUTDOWN */ if (!strcasecmp(str, "-f")) { tell_player(p, " Forcing reboot ...\n"); LOGF("reboot", "%s forces a reboot ...", p->name); do_reboot(); } if (awaiting_reboot && strcasecmp(str, "-g")) { tell_player(p, " Already awaiting opportunity to reboot ...\n"); return; } for (scan = flatlist_start; scan; scan = scan->flat_next) if (scan->location && (scan->flags & IN_EDITOR || scan->mode & (MAILEDIT | ROOMEDIT | NEWSEDIT))) { awaiting_reboot = 1; stack += sprintf(stack, "%s ", scan->name); } if (!awaiting_reboot) { LOGF("reboot", "%s calls for a reboot ... and gets it.", p->name); do_reboot(); } stack = end_string(stack); TELLPLAYER(p, " Someone is currently in the editor (or in a mode).\n" " The code will reboot when they have finished ...\n" " (ppl holding up the show are: %s)\n", oldstack); stack = oldstack; SW_BUT(p, " -=*> %s requests a reboot of %s\n", p->name, get_config_msg("talker_name")); LOGF("reboot", "%s calls for a reboot ... but has to wait.", p->name); } /** ** these are for the other side of a reboot, that is the child awakening and loading in (if needed) the reboot info ** **/ /* a coupla specilized functions as its easier to spoon them and include here than try to including modification procs */ void trans_to_quiet(player * p, char *str) { room *r; player *previous, *scan; r = convert_room(p, str); if (!r) return; if (r == p->location) return; if (p->location) { previous = 0; scan = p->location->players_top; while (scan && scan != p) { previous = scan; scan = scan->room_next; } if (!scan) log("error", "Bad Location list 2"); else if (!previous) { p->location->players_top = p->room_next; if (!(p->location->players_top)) compress_room(p->location); } else previous->room_next = p->room_next; } p->room_next = r->players_top; r->players_top = p; p->location = r; } player *create_player_template(player * t) { player *p; p = (player *) MALLOC(sizeof(player)); memcpy(p, t, sizeof(player)); /* reset some parseing varibles */ memset(p->ibuffer, 0, IBUFFER_LENGTH); p->column = p->ibuff_pointer = 0; /* just in case, NULL all possible pointers */ p->hash_next = p->flat_next = p->flat_previous = p->room_next = 0; p->saved = 0; p->location = 0; p->edit_info = 0; p->input_to_fn = 0; p->timer_fn = 0; p->ttt_opponent = 0; p->gag_top = 0; p->command_used = 0; p->social = 0; if (flatlist_start) flatlist_start->flat_previous = p; p->flat_next = flatlist_start; flatlist_start = p; p->hash_next = hashlist[0]; hashlist[0] = p; p->hash_top = 0; p->timer_fn = 0; p->timer_count = -1; p->edit_info = 0; return p; } int retrieve_sysinfo(void) { talker_system_info tsi; FILE *f; memset(&tsi, 0, sizeof(talker_system_info)); f = fopen(TALKER_SYSINFO_FILE, "r"); if (!f) { LOGF("error", "Failed to open reboot system info file for read [%s]", strerror(errno)); return -1; } fread((void *) &tsi, sizeof(talker_system_info), 1, f); fclose(f); main_descriptor = tsi.main_fd; alive_descriptor = tsi.angel_fd; up_date = tsi.up_date; logins = tsi.logins; sys_flags = tsi.sys_flags; session_reset = tsi.session_reset; strncpy(session, tsi.session, MAX_SESSION - 1); strncpy(sess_name, tsi.sess_name, MAX_NAME - 1); strncpy(rebooter, tsi.rebooter, MAX_NAME - 1); return 0; } void reattach_player(player * info) { player *p, *previous, *scan; saved_player *sp; int hash; p = create_player_template(info); load_player(p); /* based on code from plists.c : link_to_program() */ previous = 0; scan = hashlist[0]; while (scan && scan != p) { previous = scan; scan = scan->hash_next; } if (!scan) log("error", "Bad non-name hash list"); else if (!previous) hashlist[0] = p->hash_next; else previous->hash_next = p->hash_next; hash = (int) (p->lower_name[0]) - (int) 'a' + 1; p->hash_next = hashlist[hash]; hashlist[hash] = p; p->hash_top = hash; p->flags |= PROMPT; p->timer_fn = 0; p->timer_count = -1; if (p->residency != NON_RESIDENT) { p->logged_in = 1; sp = p->saved; } current_players++; trans_to_quiet(p, p->location_string); if (!(p->location)) trans_to_quiet(p, sys_room_id(ENTRANCE_ROOM)); if (p->saved) { decompress_list(p->saved); decompress_alias(p->saved); decompress_item(p->saved); p->system_flags = p->saved->system_flags; p->tag_flags = p->saved->tag_flags; p->custom_flags = p->saved->custom_flags; p->misc_flags = p->saved->misc_flags; if (!(p->pennies)) p->pennies = p->saved->pennies; } } void retrieve_players(void) { FILE *f, *pf; char namein[160]; char *oldstack = stack; int ret; player spanky; f = fopen(PLAYER_LIST_FILE, "r"); if (!f) { log("error", "When rebooting, in retrieve_players, fopen failed."); return; } memset(namein, 0, 160); ret = fscanf(f, "%s", namein); while (ret && ret != EOF) { sprintf(stack, "%s/%s", REBOOTING_DIR, namein); stack = end_string(stack); pf = fopen(oldstack, "r"); stack = oldstack; if (!pf) LOGF("error", "Failed to open reboot player info read (%s) [%s]", namein, strerror(errno)); else { fread((void *) &spanky, sizeof(player), 1, pf); fclose(pf); reattach_player(&spanky); } memset(namein, 0, 160); ret = fscanf(f, "%s", namein); } } int possibly_reboot(void) { player *p; FILE *f; char r[16]; char reboot_msg[256] = ""; /* Set this to your reboot message (if any) */ f = fopen(CHILDS_PID_FILE, "r"); if (!f) return 0; /* no child pid file, fuggit */ memset(r, 0, 16); fgets(r, 15, f); if (getpid() != atoi(r)) return 0; SUWALL(" -=*> Starting reboot ...\n"); log("boot", "Rebooting ..."); if (reboot_msg[0]) raw_wall(reboot_msg); retrieve_sysinfo(); retrieve_players(); #ifdef ALLOW_MULTIS reboot_load_multis(); #endif system("rm -f " REBOOTING_DIR "/*"); if (sys_flags & PANIC) { sys_flags &= ~PANIC; /* no need to panic any more */ if (rebooter[0]) { p = find_player_absolute_quiet(rebooter); if (p) { SW_BUT(p, " -=*> %s managed to crash the talk server.\n", p->name); SW_BUT(p, " -=*> %s rebooted and back on track.\n\n", get_config_msg("talker_name")); tell_player(p, "\n\n" LINE "\nThis command has caused a system error to occur.\n" "Your connection has been terminated to prevent the code crashing.\n" "Please avoid usage of this command until we get it fixed. Sorry!\n\n" LINE "\n\007\n"); quit(p, 0); } else SUWALL(" -=*> Just crashed without a current_player.\n" " -=*> %s rebooted and back on track.\n\n", get_config_msg("talker_name")); } } SUWALL(" -=*> Reboot of %s successful.\n", get_config_msg("talker_name")); reboot_date = time(0); return 1; } void reboot_version(void) { sprintf(stack, " -=*> Seamless Reboot v0.5b (by phypor) enabled.\n"); stack = strchr(stack, 0); } #ifdef ALLOW_MULTIS int reboot_save_multis(void) { multi *mscan = all_multis; multiplayer *pscan; int multis_total; FILE *reboot_multi_file = fopen("junk/rebooting/multi", "w"); if (!reboot_multi_file) { log("reboot", "Couldn't open junk/rebooting/multi for writing"); return 1; } multis_total = multi_count(); /* amount of multis */ fprintf(reboot_multi_file, "%d\n", multis_total); while (mscan) { fprintf(reboot_multi_file, "%d,%d,%d,%d\n", mscan->number, mscan->multi_flags, mscan->multi_idle, players_on_multi(mscan)); pscan = mscan->players_list; while (pscan) { if (pscan->the_player) fprintf(reboot_multi_file, "%s\n", pscan->the_player->lower_name); else fprintf(reboot_multi_file, "%s\n", "<unknown>"); pscan = pscan->next_player; } mscan = mscan->next_multi; } /* actually save it */ fflush(reboot_multi_file); fclose(reboot_multi_file); return 0; } void reboot_load_multis(void) { multi *new = NULL, *old = NULL; multiplayer *newp = NULL, *oldp = NULL; char *currpos, *nextpos; player *p; int num_multis = 0, m_number = 0, m_flags = 0, m_players = 0; int m_idle = 0; char m_playername[MAX_NAME]; file reboot_multi_file = load_file("junk/rebooting/multi"); if (!(reboot_multi_file.where)) { log("reboot", "Couldn't open junk/rebooting/multi for reading"); return; } /* get first line - amount of multis */ currpos = reboot_multi_file.where; nextpos = strchr(currpos, '\n'); *nextpos += 0; num_multis = atoi(currpos); while (num_multis) { old = new; new = (multi *) MALLOC(sizeof(multi)); if (old) old->next_multi = new; else all_multis = new; /* get first line of multi definition */ currpos = nextpos; nextpos = strchr(currpos, ','); *nextpos++ = 0; m_number = atoi(currpos); currpos = nextpos; nextpos = strchr(currpos, ','); *nextpos++ = 0; m_flags = atoi(currpos); currpos = nextpos; nextpos = strchr(currpos, ','); *nextpos++ = 0; m_idle = atoi(currpos); currpos = nextpos; nextpos = strchr(currpos, '\n'); *nextpos++ = 0; m_players = atoi(currpos); new->number = m_number; new->multi_flags = m_flags; new->multi_idle = m_idle; new->players_list = NULL; new->next_multi = NULL; oldp = NULL; newp = NULL; while (m_players) { oldp = newp; newp = (multiplayer *) MALLOC(sizeof(multiplayer)); if (oldp) oldp->next_player = newp; else new->players_list = newp; /* get player name */ currpos = nextpos; nextpos = strchr(currpos, '\n'); *nextpos++ = 0; strcpy(m_playername, currpos); p = find_player_global_quiet(m_playername); newp->the_player = p; newp->next_player = NULL; m_players--; } num_multis--; } } #endif /* ALLOW_MULTIS */ #endif /* SEAMLESS_REBOOT */