소스 검색

* Merges from charmerge:
- Added DBMap::exists. (r14090)
- Added sv_parse_next, a stepped version of sv_parse (delim-separated parser). (r14100 r14104)
- Added missing fd check to do_close. (r14145)
- Normalized, refactored and restructured some code (in preparation for shutdown/reconnect code). (r14145 r14150)
- Changed the char select request code to allow the char-server to reject it. (player in map-server trying to go to char select) (r14150)
- Added shutdown support to the servers. (incomplete) (r14152)

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@14851 54d463be-8e91-2dee-dedb-b68131a5f0ec

flaviojs 14 년 전
부모
커밋
b02133b422
21개의 변경된 파일1001개의 추가작업 그리고 336개의 파일을 삭제
  1. 8 0
      Changelog-Trunk.txt
  2. 270 101
      src/char/char.c
  3. 8 0
      src/char/char.h
  4. 273 103
      src/char_sql/char.c
  5. 9 0
      src/char_sql/char.h
  6. 11 3
      src/common/core.c
  7. 13 0
      src/common/core.h
  8. 57 3
      src/common/db.c
  9. 14 0
      src/common/db.h
  10. 3 0
      src/common/socket.c
  11. 89 47
      src/common/strlib.c
  12. 21 0
      src/common/strlib.h
  13. 98 24
      src/login/login.c
  14. 8 0
      src/login/login.h
  15. 1 15
      src/map/atcommand.c
  16. 67 32
      src/map/chrif.c
  17. 1 1
      src/map/chrif.h
  18. 11 5
      src/map/clif.c
  19. 1 1
      src/map/clif.h
  20. 28 1
      src/map/map.c
  21. 10 0
      src/map/map.h

+ 8 - 0
Changelog-Trunk.txt

@@ -1,5 +1,13 @@
 Date	Added
 
+2011/06/16
+	* Merges from charmerge:
+	- Added DBMap::exists. (r14090)
+	- Added sv_parse_next, a stepped version of sv_parse (delim-separated parser). (r14100 r14104)
+	- Added missing fd check to do_close. (r14145)
+	- Normalized, refactored and restructured some code (in preparation for shutdown/reconnect code). (r14145 r14150)
+	- Changed the char select request code to allow the char-server to reject it. (player in map-server trying to go to char select) (r14150)
+	- Added shutdown support to the servers. (incomplete) (r14152)
 2011/06/13
 	* Fixed pet's equip would visually disappear when it's walk speed was changed while it was standing (related r14838). [Ai4rei]
 2011/06/11

+ 270 - 101
src/char/char.c

@@ -1782,7 +1782,7 @@ int count_users(void)
 	int i, users;
 
 	users = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		if (server[i].fd > 0) {
 			users += server[i].users;
 		}
@@ -2061,23 +2061,73 @@ static void char_auth_ok(int fd, struct char_session_data *sd)
 }
 
 int send_accounts_tologin(int tid, unsigned int tick, int id, intptr data);
+void mapif_server_reset(int id);
+
+
+/// Resets all the data.
+void loginif_reset(void)
+{
+	int id;
+	// TODO kick everyone out and reset everything or wait for connect and try to reaquire locks [FlavioJS]
+	for( id = 0; id < ARRAYLENGTH(server); ++id )
+		mapif_server_reset(id);
+	flush_fifos();
+	exit(EXIT_FAILURE);
+}
+
+
+/// Checks the conditions for the server to stop.
+/// If all the conditions are met, it stops the core loop.
+void loginif_check_shutdown(void)
+{
+	if( runflag != CHARSERVER_ST_SHUTDOWN )
+		return;
+	runflag = CORE_ST_STOP;
+}
+
+
+/// Called when the connection to Login Server is disconnected.
+void loginif_on_disconnect(void)
+{
+	ShowWarning("Connection to Login Server lost.\n\n");
+}
+
+
+/// Called when all the connection steps are completed.
+void loginif_on_ready(void)
+{
+	int i;
+
+	loginif_check_shutdown();
+
+	//Send online accounts to login server.
+	send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0);
+
+	// if no map-server already connected, display a message...
+	ARR_FIND( 0, ARRAYLENGTH(server), i, server[i].fd > 0 && server[i].map[0] );
+	if( i == ARRAYLENGTH(server) )
+		ShowStatus("Awaiting maps from map-server.\n");
+}
+
 
 int parse_fromlogin(int fd)
 {
+	struct char_session_data* sd = NULL;
 	int i;
-	struct char_session_data *sd;
 
-	// only login-server can have an access to here.
-	// so, if it isn't the login-server, we disconnect the session.
+	// only process data from the login-server
 	if( fd != login_fd )
-		set_eof(fd);
+	{
+		ShowDebug("parse_fromlogin: Disconnecting invalid session #%d (is not the login-server)\n", fd);
+		do_close(fd);
+		return 0;
+	}
 
-	if(session[fd]->flag.eof) {
-		if (fd == login_fd) {
-			ShowWarning("Connection to login-server lost (connection #%d).\n", fd);
-			login_fd = -1;
-		}
+	if( session[fd]->flag.eof )
+	{
 		do_close(fd);
+		login_fd = -1;
+		loginif_on_disconnect();
 		return 0;
 	}
 
@@ -2101,16 +2151,11 @@ int parse_fromlogin(int fd)
 				ShowError("The server communication passwords (default s1/p1) are probably invalid.\n");
 				ShowError("Also, please make sure your accounts file (default: accounts.txt) has the correct communication username/passwords and the gender of the account is S.\n");
 				ShowError("The communication passwords are set in map_athena.conf and char_athena.conf\n");
+				set_eof(fd);
+				return 0;
 			} else {
 				ShowStatus("Connected to login-server (connection #%d).\n", fd);
-				
-				//Send online accounts to login server.
-				send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0);
-
-				// if no map-server already connected, display a message...
-				ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd > 0 && server[i].map[0] );
-				if( i == MAX_MAP_SERVERS )
-					ShowStatus("Awaiting maps from map-server.\n");
+				loginif_on_ready();
 			}
 			RFIFOSKIP(fd,3);
 		break;
@@ -2377,6 +2422,34 @@ int parse_fromlogin(int fd)
 	return 0;
 }
 
+int check_connect_login_server(int tid, unsigned int tick, int id, intptr data);
+int ping_login_server(int tid, unsigned int tick, int id, intptr data);
+int send_accounts_tologin(int tid, unsigned int tick, int id, intptr data);
+
+void do_init_loginif(void)
+{
+	// establish char-login connection if not present
+	add_timer_func_list(check_connect_login_server, "check_connect_login_server");
+	add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
+	
+	// keep the char-login connection alive
+	add_timer_func_list(ping_login_server, "ping_login_server");
+	add_timer_interval(gettick() + 1000, ping_login_server, 0, 0, ((int)stall_time-2) * 1000);
+	
+	// send a list of all online account IDs to login server
+	add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
+	add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour
+}
+
+void do_final_loginif(void)
+{
+	if( login_fd != -1 )
+	{
+		do_close(login_fd);
+		login_fd = -1;
+	}
+}
+
 int request_accreg2(int account_id, int char_id)
 {
 	if (login_fd > 0) {
@@ -2592,36 +2665,76 @@ int char_loadName(int char_id, char* name)
 
 int search_mapserver(unsigned short map, uint32 ip, uint16 port);
 
+
+/// Initializes a server structure.
+void mapif_server_init(int id)
+{
+	memset(&server[id], 0, sizeof(server[id]));
+	server[id].fd = -1;
+}
+
+
+/// Destroys a server structure.
+void mapif_server_destroy(int id)
+{
+	if( server[id].fd == -1 )
+	{
+		do_close(server[id].fd);
+		server[id].fd = -1;
+	}
+}
+
+
+/// Resets all the data related to a server.
+void mapif_server_reset(int id)
+{
+	int i,j;
+	unsigned char buf[16384];
+	int fd = server[id].fd;
+	//Notify other map servers that this one is gone. [Skotlex]
+	WBUFW(buf,0) = 0x2b20;
+	WBUFL(buf,4) = htonl(server[id].ip);
+	WBUFW(buf,8) = htons(server[id].port);
+	j = 0;
+	for(i = 0; i < MAX_MAP_PER_SERVER; i++)
+		if (server[id].map[i])
+			WBUFW(buf,10+(j++)*4) = server[id].map[i];
+	if (j > 0) {
+		WBUFW(buf,2) = j * 4 + 10;
+		mapif_sendallwos(fd, buf, WBUFW(buf,2));
+	}
+	online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server.
+	create_online_files();
+	mapif_server_destroy(id);
+	mapif_server_init(id);
+}
+
+
+/// Called when the connection to a Map Server is disconnected.
+void mapif_on_disconnect(int id)
+{
+	ShowStatus("Map-server #%d has disconnected.\n", id);
+	mapif_server_reset(id);
+}
+
+
 int parse_frommap(int fd)
 {
 	int i, j;
 	int id;
 
-	ARR_FIND( 0, MAX_MAP_SERVERS, id, server[id].fd == fd );
-	if(id == MAX_MAP_SERVERS)
-		set_eof(fd);
-	if(session[fd]->flag.eof) {
-		if (id < MAX_MAP_SERVERS) {
-			unsigned char buf[16384];
-			ShowStatus("Map-server %d (session #%d) has disconnected.\n", id, fd);
-			//Notify other map servers that this one is gone. [Skotlex]
-			WBUFW(buf,0) = 0x2b20;
-			WBUFL(buf,4) = htonl(server[id].ip);
-			WBUFW(buf,8) = htons(server[id].port);
-			j = 0;
-			for(i = 0; i < MAX_MAP_PER_SERVER; i++)
-				if (server[id].map[i])
-					WBUFW(buf,10+(j++)*4) = server[id].map[i];
-			if (j > 0) {
-				WBUFW(buf,2) = j * 4 + 10;
-				mapif_sendallwos(fd, buf, WBUFW(buf,2));
-			}
-			memset(&server[id], 0, sizeof(struct mmo_map_server));
-			server[id].fd = -1;
-			online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server.
-		}
+	ARR_FIND( 0, ARRAYLENGTH(server), id, server[id].fd == fd );
+	if( id == ARRAYLENGTH(server) )
+	{// not a map server
+		ShowDebug("parse_frommap: Disconnecting invalid session #%d (is not a map-server)\n", fd);
 		do_close(fd);
-		create_online_files();
+		return 0;
+	}
+	if( session[fd]->flag.eof )
+	{
+		do_close(fd);
+		server[id].fd = -1;
+		mapif_on_disconnect(id);
 		return 0;
 	}
 
@@ -2672,14 +2785,14 @@ int parse_frommap(int fd)
 				mapif_sendallwos(fd, buf, WBUFW(buf,2));
 			}
 			// Transmitting the maps of the other map-servers to the new map-server
-			for(x = 0; x < MAX_MAP_SERVERS; x++) {
+			for(x = 0; x < ARRAYLENGTH(server); x++) {
 				if (server[x].fd > 0 && x != id) {
-					WFIFOHEAD(fd,10 +4*MAX_MAP_PER_SERVER);
+					WFIFOHEAD(fd,10 +4*ARRAYLENGTH(server));
 					WFIFOW(fd,0) = 0x2b04;
 					WFIFOL(fd,4) = htonl(server[x].ip);
 					WFIFOW(fd,8) = htons(server[x].port);
 					j = 0;
-					for(i = 0; i < MAX_MAP_PER_SERVER; i++)
+					for(i = 0; i < ARRAYLENGTH(server); i++)
 						if (server[x].map[i])
 							WFIFOW(fd,10+(j++)*4) = server[x].map[i];
 					if (j > 0) {
@@ -2802,26 +2915,37 @@ int parse_frommap(int fd)
 			uint32 ip = RFIFOL(fd,14);
 			RFIFOSKIP(fd,18);
 
-			// create temporary auth entry
-			CREATE(node, struct auth_node, 1);
-			node->account_id = account_id;
-			node->char_id = 0;
-			node->login_id1 = login_id1;
-			node->login_id2 = login_id2;
-			//node->sex = 0;
-			node->ip = ntohl(ip);
-			//node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server)
-			//node->gmlevel = 0;
-			idb_put(auth_db, account_id, node);
-
-			//Set char to "@ char select" in online db [Kevin]
-			set_char_charselect(account_id);
-
-			WFIFOHEAD(fd,7);
-			WFIFOW(fd,0) = 0x2b03;
-			WFIFOL(fd,2) = account_id;
-			WFIFOB(fd,6) = 0;
-			WFIFOSET(fd,7);
+			if( runflag != CHARSERVER_ST_RUNNING )
+			{
+				WFIFOHEAD(fd,7);
+				WFIFOW(fd,0) = 0x2b03;
+				WFIFOL(fd,2) = account_id;
+				WFIFOB(fd,6) = 0;// not ok
+				WFIFOSET(fd,7);
+			}
+			else
+			{
+				// create temporary auth entry
+				CREATE(node, struct auth_node, 1);
+				node->account_id = account_id;
+				node->char_id = 0;
+				node->login_id1 = login_id1;
+				node->login_id2 = login_id2;
+				//node->sex = 0;
+				node->ip = ntohl(ip);
+				//node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server)
+				//node->gmlevel = 0;
+				idb_put(auth_db, account_id, node);
+
+				//Set char to "@ char select" in online db [Kevin]
+				set_char_charselect(account_id);
+
+				WFIFOHEAD(fd,7);
+				WFIFOW(fd,0) = 0x2b03;
+				WFIFOL(fd,2) = account_id;
+				WFIFOB(fd,6) = 1;// ok
+				WFIFOSET(fd,7);
+			}
 		}
 		break;
 
@@ -2839,7 +2963,9 @@ int parse_frommap(int fd)
 
 			char_data = search_character(RFIFOL(fd,2), RFIFOL(fd,14));
 
-			if (map_fd >= 0 && session[map_fd] && char_data) 
+			if( runflag == CHARSERVER_ST_RUNNING &&
+				session_isActive(map_fd) &&
+				char_data )
 			{	//Send the map server the auth of this player.
 				struct auth_node* node;
 
@@ -3140,7 +3266,9 @@ int parse_frommap(int fd)
 
 			node = (struct auth_node*)idb_get(auth_db, account_id);
 			cd = search_character(account_id, char_id);
-			if( node != NULL && cd != NULL &&
+			if( runflag == CHARSERVER_ST_RUNNING &&
+				cd != NULL &&
+				node != NULL &&
 				node->account_id == account_id &&
 				node->char_id == char_id &&
 				node->login_id1 == login_id1 &&
@@ -3204,13 +3332,27 @@ int parse_frommap(int fd)
 	return 0;
 }
 
+void do_init_mapif(void)
+{
+	int i;
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		mapif_server_init(i);
+}
+
+void do_final_mapif(void)
+{
+	int i;
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		mapif_server_destroy(i);
+}
+
 // Searches for the mapserver that has a given map (and optionally ip/port, if not -1).
 // If found, returns the server's index in the 'server' array (otherwise returns -1).
 int search_mapserver(unsigned short map, uint32 ip, uint16 port)
 {
 	int i, j;
 	
-	for(i = 0; i < MAX_MAP_SERVERS; i++)
+	for(i = 0; i < ARRAYLENGTH(server); i++)
 	{
 		if (server[i].fd > 0
 		&& (ip == (uint32)-1 || server[i].ip == ip)
@@ -3526,6 +3668,15 @@ int parse_char(int fd)
 			WFIFOL(fd,0) = account_id;
 			WFIFOSET(fd,4);
 
+			if( runflag != CHARSERVER_ST_RUNNING )
+			{
+				WFIFOHEAD(fd,3);
+				WFIFOW(fd,0) = 0x6c;
+				WFIFOB(fd,2) = 0;// rejected from server
+				WFIFOSET(fd,3);
+				break;
+			}
+
 			// search authentification
 			node = (struct auth_node*)idb_get(auth_db, account_id);
 			if( node != NULL &&
@@ -3602,8 +3753,8 @@ int parse_char(int fd)
 			if (i < 0) {
 				unsigned short j;
 				//First check that there's actually a map server online.
-				ARR_FIND( 0, MAX_MAP_SERVERS, j, server[j].fd >= 0 && server[j].map[0] );
-				if (j == MAX_MAP_SERVERS) {
+				ARR_FIND( 0, ARRAYLENGTH(server), j, server[j].fd >= 0 && server[j].map[0] );
+				if (j == ARRAYLENGTH(server)) {
 					ShowInfo("Connection Closed. No map servers available.\n");
 					WFIFOHEAD(fd,3);
 					WFIFOW(fd,0) = 0x81;
@@ -3927,8 +4078,12 @@ int parse_char(int fd)
 			char* l_pass = (char*)RFIFOP(fd,26);
 			l_user[23] = '\0';
 			l_pass[23] = '\0';
-			ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd <= 0 );
-			if (i == MAX_MAP_SERVERS || strcmp(l_user, userid) || strcmp(l_pass, passwd)) {
+			ARR_FIND( 0, ARRAYLENGTH(server), i, server[i].fd <= 0 );
+			if( runflag != CHARSERVER_ST_RUNNING ||
+				i == ARRAYLENGTH(server) ||
+				strcmp(l_user, userid) != 0 ||
+				strcmp(l_pass, passwd) != 0 )
+			{
 				WFIFOHEAD(fd,3);
 				WFIFOW(fd,0) = 0x2af9;
 				WFIFOB(fd,2) = 3;
@@ -3991,7 +4146,7 @@ int mapif_sendall(unsigned char *buf, unsigned int len)
 	int i, c;
 
 	c = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		int fd;
 		if ((fd = server[i].fd) > 0) {
 			WFIFOHEAD(fd,len);
@@ -4009,7 +4164,7 @@ int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len)
 	int i, c;
 
 	c = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		int fd;
 		if ((fd = server[i].fd) > 0 && fd != sfd) {
 			WFIFOHEAD(fd,len);
@@ -4027,8 +4182,8 @@ int mapif_send(int fd, unsigned char *buf, unsigned int len)
 	int i;
 
 	if (fd >= 0) {
-		ARR_FIND( 0, MAX_MAP_SERVERS, i, fd == server[i].fd );
-		if( i < MAX_MAP_SERVERS )
+		ARR_FIND( 0, ARRAYLENGTH(server), i, fd == server[i].fd );
+		if( i < ARRAYLENGTH(server) )
 		{
 			WFIFOHEAD(fd,len);
 			memcpy(WFIFOP(fd,0), buf, len);
@@ -4431,12 +4586,16 @@ int char_config_read(const char *cfgName)
 #ifndef TXT_SQL_CONVERT
 void do_final(void)
 {
-	ShowStatus("Terminating server.\n");
+	ShowStatus("Terminating...\n");
 
 	mmo_char_sync();
 	inter_save();
 	set_all_offline(-1);
 	flush_fifos();
+	
+	do_final_mapif();
+	do_final_loginif();
+
 	// write online players files with no player
 	online_char_db->clear(online_char_db, NULL);
 	create_online_files();
@@ -4445,11 +4604,12 @@ void do_final(void)
 	auth_db->destroy(auth_db, NULL);
 	
 	if(char_dat) aFree(char_dat);
-
-	if (login_fd > 0)
-		do_close(login_fd);
-	if (char_fd > 0)
+	
+	if( char_fd != -1 )
+	{
 		do_close(char_fd);
+		char_fd = -1;
+	}
 
 #ifdef ENABLE_SC_SAVING
 	status_final();
@@ -4458,6 +4618,7 @@ void do_final(void)
 	mapindex_final();
 
 	char_log("----End of char-server (normal end with closing of all files).\n");
+	ShowStatus("Finished.\n");
 }
 
 //------------------------------
@@ -4473,15 +4634,27 @@ void set_server_type(void)
 	SERVER_TYPE = ATHENA_SERVER_CHAR;
 }
 
-int do_init(int argc, char **argv)
-{
-	int i;
 
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
-		memset(&server[i], 0, sizeof(struct mmo_map_server));
-		server[i].fd = -1;
+/// Called when a terminate signal is received.
+void do_shutdown(void)
+{
+	if( runflag != CHARSERVER_ST_SHUTDOWN )
+	{
+		int id;
+		runflag = CHARSERVER_ST_SHUTDOWN;
+		ShowStatus("Shutting down...\n");
+		// TODO proper shutdown procedure; wait for acks?, kick all characters, ... [FlavoJS]
+		for( id = 0; id < ARRAYLENGTH(server); ++id )
+			mapif_server_reset(id);
+		loginif_check_shutdown();
+		flush_fifos();
+		runflag = CORE_ST_STOP;
 	}
+}
 
+
+int do_init(int argc, char **argv)
+{
 	//Read map indexes
 	mapindex_init();
 	start_point.map = mapindex_name2id("new_zone01");
@@ -4512,8 +4685,6 @@ int do_init(int argc, char **argv)
 	inter_init_txt((argc > 2) ? argv[2] : inter_cfgName);	// inter server �‰Šú‰»
 	ShowInfo("char server initialized.\n");
 
-	set_defaultparse(parse_char);
-
 	if ((naddr_ != 0) && (!login_ip || !char_ip))
 	{
 		char ip_str[16];
@@ -4533,22 +4704,13 @@ int do_init(int argc, char **argv)
 		}
 	}
 
-	// establish char-login connection if not present
-	add_timer_func_list(check_connect_login_server, "check_connect_login_server");
-	add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
-
-	// keep the char-login connection alive
-	add_timer_func_list(ping_login_server, "ping_login_server");
-	add_timer_interval(gettick() + 1000, ping_login_server, 0, 0, ((int)stall_time-2) * 1000);
+	do_init_loginif();
+	do_init_mapif();
 
 	// periodically update the overall user count on all mapservers + login server
 	add_timer_func_list(broadcast_user_count, "broadcast_user_count");
 	add_timer_interval(gettick() + 1000, broadcast_user_count, 0, 0, 5 * 1000);
 
-	// send a list of all online account IDs to login server
-	add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
-	add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour
-
 	// ???
 	add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect");
 
@@ -4564,10 +4726,17 @@ int do_init(int argc, char **argv)
 	{
 		//##TODO invoke a CONSOLE_START plugin event
 	}
-
+	
+	set_defaultparse(parse_char);
 	char_fd = make_listen_bind(bind_ip, char_port);
 	char_log("The char-server is ready (Server is listening on the port %d).\n", char_port);
 	ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port);
+	
+	if( runflag != CORE_ST_STOP )
+	{
+		shutdown_callback = do_shutdown;
+		runflag = CHARSERVER_ST_RUNNING;
+	}
 
 	return 0;
 }

+ 8 - 0
src/char/char.h

@@ -4,8 +4,16 @@
 #ifndef _CHAR_H_
 #define _CHAR_H_
 
+#include "../common/core.h" // CORE_ST_LAST
 #include "../common/mmo.h"
 
+enum E_CHARSERVER_ST
+{
+	CHARSERVER_ST_RUNNING = CORE_ST_LAST,
+	CHARSERVER_ST_SHUTDOWN,
+	CHARSERVER_ST_LAST
+};
+
 #define MAX_MAP_SERVERS 30
 
 #define DEFAULT_AUTOSAVE_INTERVAL 300*1000

+ 273 - 103
src/char_sql/char.c

@@ -1543,7 +1543,7 @@ int count_users(void)
 	int i, users;
 
 	users = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		if (server[i].fd > 0) {
 			users += server[i].users;
 		}
@@ -1779,23 +1779,74 @@ static void char_auth_ok(int fd, struct char_session_data *sd)
 }
 
 int send_accounts_tologin(int tid, unsigned int tick, int id, intptr data);
+void mapif_server_reset(int id);
 
-int parse_fromlogin(int fd)
+
+/// Resets all the data.
+void loginif_reset(void)
+{
+	int id;
+	// TODO kick everyone out and reset everything or wait for connect and try to reaquire locks [FlavioJS]
+	for( id = 0; id < ARRAYLENGTH(server); ++id )
+		mapif_server_reset(id);
+	flush_fifos();
+	exit(EXIT_FAILURE);
+}
+
+
+/// Checks the conditions for the server to stop.
+/// Releases the cookie when all characters are saved.
+/// If all the conditions are met, it stops the core loop.
+void loginif_check_shutdown(void)
+{
+	if( runflag != CHARSERVER_ST_SHUTDOWN )
+		return;
+	runflag = CORE_ST_STOP;
+}
+
+
+/// Called when the connection to Login Server is disconnected.
+void loginif_on_disconnect(void)
+{
+	ShowWarning("Connection to Login Server lost.\n\n");
+}
+
+
+/// Called when all the connection steps are completed.
+void loginif_on_ready(void)
 {
 	int i;
-	struct char_session_data *sd;
 
-	// only login-server can have an access to here.
-	// so, if it isn't the login-server, we disconnect the session.
+	loginif_check_shutdown();
+	
+	//Send online accounts to login server.
+	send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0);
+
+	// if no map-server already connected, display a message...
+	ARR_FIND( 0, ARRAYLENGTH(server), i, server[i].fd > 0 && server[i].map[0] );
+	if( i == ARRAYLENGTH(server) )
+		ShowStatus("Awaiting maps from map-server.\n");
+}
+
+
+int parse_fromlogin(int fd)
+{
+	struct char_session_data* sd = NULL;
+	int i;
+	
+	// only process data from the login-server
 	if( fd != login_fd )
-		set_eof(fd);
+	{
+		ShowDebug("parse_fromlogin: Disconnecting invalid session #%d (is not the login-server)\n", fd);
+		do_close(fd);
+		return 0;
+	}
 
-	if(session[fd]->flag.eof) {
-		if (fd == login_fd) {
-			ShowWarning("Connection to login-server lost (connection #%d).\n", fd);
-			login_fd = -1;
-		}
+	if( session[fd]->flag.eof )
+	{
 		do_close(fd);
+		login_fd = -1;
+		loginif_on_disconnect();
 		return 0;
 	}
 
@@ -1819,16 +1870,11 @@ int parse_fromlogin(int fd)
 				ShowError("The server communication passwords (default s1/p1) are probably invalid.\n");
 				ShowError("Also, please make sure your login db has the correct communication username/passwords and the gender of the account is S.\n");
 				ShowError("The communication passwords are set in map_athena.conf and char_athena.conf\n");
+				set_eof(fd);
+				return 0;
 			} else {
 				ShowStatus("Connected to login-server (connection #%d).\n", fd);
-				
-				//Send online accounts to login server.
-				send_accounts_tologin(INVALID_TIMER, gettick(), 0, 0);
-
-				// if no map-server already connected, display a message...
-				ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd > 0 && server[i].map[0] );
-				if( i == MAX_MAP_SERVERS )
-					ShowStatus("Awaiting maps from map-server.\n");
+				loginif_on_ready();
 			}
 			RFIFOSKIP(fd,3);
 		break;
@@ -2096,6 +2142,34 @@ int parse_fromlogin(int fd)
 	return 0;
 }
 
+int check_connect_login_server(int tid, unsigned int tick, int id, intptr data);
+int ping_login_server(int tid, unsigned int tick, int id, intptr data);
+int send_accounts_tologin(int tid, unsigned int tick, int id, intptr data);
+
+void do_init_loginif(void)
+{
+	// establish char-login connection if not present
+	add_timer_func_list(check_connect_login_server, "check_connect_login_server");
+	add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
+	
+	// keep the char-login connection alive
+	add_timer_func_list(ping_login_server, "ping_login_server");
+	add_timer_interval(gettick() + 1000, ping_login_server, 0, 0, ((int)stall_time-2) * 1000);
+	
+	// send a list of all online account IDs to login server
+	add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
+	add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour
+}
+
+void do_final_loginif(void)
+{
+	if( login_fd != -1 )
+	{
+		do_close(login_fd);
+		login_fd = -1;
+	}
+}
+
 int request_accreg2(int account_id, int char_id)
 {
 	if (login_fd > 0) {
@@ -2252,37 +2326,77 @@ int char_loadName(int char_id, char* name)
 
 int search_mapserver(unsigned short map, uint32 ip, uint16 port);
 
+
+/// Initializes a server structure.
+void mapif_server_init(int id)
+{
+	memset(&server[id], 0, sizeof(server[id]));
+	server[id].fd = -1;
+}
+
+
+/// Destroys a server structure.
+void mapif_server_destroy(int id)
+{
+	if( server[id].fd == -1 )
+	{
+		do_close(server[id].fd);
+		server[id].fd = -1;
+	}
+}
+
+
+/// Resets all the data related to a server.
+void mapif_server_reset(int id)
+{
+	int i,j;
+	unsigned char buf[16384];
+	int fd = server[id].fd;
+	//Notify other map servers that this one is gone. [Skotlex]
+	WBUFW(buf,0) = 0x2b20;
+	WBUFL(buf,4) = htonl(server[id].ip);
+	WBUFW(buf,8) = htons(server[id].port);
+	j = 0;
+	for(i = 0; i < MAX_MAP_PER_SERVER; i++)
+		if (server[id].map[i])
+			WBUFW(buf,10+(j++)*4) = server[id].map[i];
+	if (j > 0) {
+		WBUFW(buf,2) = j * 4 + 10;
+		mapif_sendallwos(fd, buf, WBUFW(buf,2));
+	}
+	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server[id].fd) )
+		Sql_ShowDebug(sql_handle);
+	online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server.
+	mapif_server_destroy(id);
+	mapif_server_init(id);
+}
+
+
+/// Called when the connection to a Map Server is disconnected.
+void mapif_on_disconnect(int id)
+{
+	ShowStatus("Map-server #%d has disconnected.\n", id);
+	mapif_server_reset(id);
+}
+
+
 int parse_frommap(int fd)
 {
 	int i, j;
 	int id;
 
-	ARR_FIND( 0, MAX_MAP_SERVERS, id, server[id].fd == fd );
-	if(id == MAX_MAP_SERVERS)
-		set_eof(fd);
-	if(session[fd]->flag.eof) {
-		if (id < MAX_MAP_SERVERS) {
-			unsigned char buf[16384];
-			ShowStatus("Map-server %d (session #%d) has disconnected.\n", id, fd);
-			//Notify other map servers that this one is gone. [Skotlex]
-			WBUFW(buf,0) = 0x2b20;
-			WBUFL(buf,4) = htonl(server[id].ip);
-			WBUFW(buf,8) = htons(server[id].port);
-			j = 0;
-			for(i = 0; i < MAX_MAP_PER_SERVER; i++)
-				if (server[id].map[i])
-					WBUFW(buf,10+(j++)*4) = server[id].map[i];
-			if (j > 0) {
-				WBUFW(buf,2) = j * 4 + 10;
-				mapif_sendallwos(fd, buf, WBUFW(buf,2));
-			}
-			memset(&server[id], 0, sizeof(struct mmo_map_server));
-			if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server[id].fd) )
-				Sql_ShowDebug(sql_handle);
-			server[id].fd = -1;
-			online_char_db->foreach(online_char_db,char_db_setoffline,id); //Tag relevant chars as 'in disconnected' server.
-		}
+	ARR_FIND( 0, ARRAYLENGTH(server), id, server[id].fd == fd );
+	if( id == ARRAYLENGTH(server) )
+	{// not a map server
+		ShowDebug("parse_frommap: Disconnecting invalid session #%d (is not a map-server)\n", fd);
+		do_close(fd);
+		return 0;
+	}
+	if( session[fd]->flag.eof )
+	{
 		do_close(fd);
+		server[id].fd = -1;
+		mapif_on_disconnect(id);
 		return 0;
 	}
 
@@ -2330,14 +2444,14 @@ int parse_frommap(int fd)
 				mapif_sendallwos(fd, buf, WBUFW(buf,2));
 			}
 			// Transmitting the maps of the other map-servers to the new map-server
-			for(x = 0; x < MAX_MAP_SERVERS; x++) {
+			for(x = 0; x < ARRAYLENGTH(server); x++) {
 				if (server[x].fd > 0 && x != id) {
-					WFIFOHEAD(fd,10 +4*MAX_MAP_PER_SERVER);
+					WFIFOHEAD(fd,10 +4*ARRAYLENGTH(server));
 					WFIFOW(fd,0) = 0x2b04;
 					WFIFOL(fd,4) = htonl(server[x].ip);
 					WFIFOW(fd,8) = htons(server[x].port);
 					j = 0;
-					for(i = 0; i < MAX_MAP_PER_SERVER; i++)
+					for(i = 0; i < ARRAYLENGTH(server); i++)
 						if (server[x].map[i])
 							WFIFOW(fd,10+(j++)*4) = server[x].map[i];
 					if (j > 0) {
@@ -2491,27 +2605,38 @@ int parse_frommap(int fd)
 			uint32 login_id2 = RFIFOL(fd,10);
 			uint32 ip = RFIFOL(fd,14);
 			RFIFOSKIP(fd,18);
-
-			// create temporary auth entry
-			CREATE(node, struct auth_node, 1);
-			node->account_id = account_id;
-			node->char_id = 0;
-			node->login_id1 = login_id1;
-			node->login_id2 = login_id2;
-			//node->sex = 0;
-			node->ip = ntohl(ip);
-			//node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server)
-			//node->gmlevel = 0;
-			idb_put(auth_db, account_id, node);
-
-			//Set char to "@ char select" in online db [Kevin]
-			set_char_charselect(account_id);
-
-			WFIFOHEAD(fd,7);
-			WFIFOW(fd,0) = 0x2b03;
-			WFIFOL(fd,2) = account_id;
-			WFIFOB(fd,6) = 0;
-			WFIFOSET(fd,7);
+			
+			if( runflag != CHARSERVER_ST_RUNNING )
+			{
+				WFIFOHEAD(fd,7);
+				WFIFOW(fd,0) = 0x2b03;
+				WFIFOL(fd,2) = account_id;
+				WFIFOB(fd,6) = 0;// not ok
+				WFIFOSET(fd,7);
+			}
+			else
+			{
+				// create temporary auth entry
+				CREATE(node, struct auth_node, 1);
+				node->account_id = account_id;
+				node->char_id = 0;
+				node->login_id1 = login_id1;
+				node->login_id2 = login_id2;
+				//node->sex = 0;
+				node->ip = ntohl(ip);
+				//node->expiration_time = 0; // unlimited/unknown time by default (not display in map-server)
+				//node->gmlevel = 0;
+				idb_put(auth_db, account_id, node);
+
+				//Set char to "@ char select" in online db [Kevin]
+				set_char_charselect(account_id);
+
+				WFIFOHEAD(fd,7);
+				WFIFOW(fd,0) = 0x2b03;
+				WFIFOL(fd,2) = account_id;
+				WFIFOB(fd,6) = 1;// ok
+				WFIFOSET(fd,7);
+			}
 		}
 		break;
 
@@ -2534,8 +2659,10 @@ int parse_frommap(int fd)
 				mmo_char_fromsql(RFIFOL(fd,14), &char_dat, true);
 				char_data = (struct mmo_charstatus*)uidb_get(char_db_,RFIFOL(fd,14));
 			}
-
-			if (map_fd >= 0 && session[map_fd] && char_data) 
+			
+			if( runflag == CHARSERVER_ST_RUNNING &&
+				session_isActive(map_fd) &&
+				char_data )
 			{	//Send the map server the auth of this player.
 				struct auth_node* node;
 
@@ -2879,7 +3006,9 @@ int parse_frommap(int fd)
 				mmo_char_fromsql(char_id, &char_dat, true);
 				cd = (struct mmo_charstatus*)uidb_get(char_db_,char_id);
 			}
-			if( node != NULL && cd != NULL &&
+			if( runflag == CHARSERVER_ST_RUNNING &&
+				cd != NULL &&
+				node != NULL && 
 				node->account_id == account_id &&
 				node->char_id == char_id &&
 				node->login_id1 == login_id1 &&
@@ -2942,13 +3071,27 @@ int parse_frommap(int fd)
 	return 0;
 }
 
+void do_init_mapif(void)
+{
+	int i;
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		mapif_server_init(i);
+}
+
+void do_final_mapif(void)
+{
+	int i;
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		mapif_server_destroy(i);
+}
+
 // Searches for the mapserver that has a given map (and optionally ip/port, if not -1).
 // If found, returns the server's index in the 'server' array (otherwise returns -1).
 int search_mapserver(unsigned short map, uint32 ip, uint16 port)
 {
 	int i, j;
 	
-	for(i = 0; i < MAX_MAP_SERVERS; i++)
+	for(i = 0; i < ARRAYLENGTH(server); i++)
 	{
 		if (server[i].fd > 0
 		&& (ip == (uint32)-1 || server[i].ip == ip)
@@ -3276,6 +3419,15 @@ int parse_char(int fd)
 			WFIFOL(fd,0) = account_id;
 			WFIFOSET(fd,4);
 
+			if( runflag != CHARSERVER_ST_RUNNING )
+			{
+				WFIFOHEAD(fd,3);
+				WFIFOW(fd,0) = 0x6c;
+				WFIFOB(fd,2) = 0;// rejected from server
+				WFIFOSET(fd,3);
+				break;
+			}
+
 			// search authentification
 			node = (struct auth_node*)idb_get(auth_db, account_id);
 			if( node != NULL &&
@@ -3361,8 +3513,8 @@ int parse_char(int fd)
 			if (i < 0) {
 				unsigned short j;
 				//First check that there's actually a map server online.
-				ARR_FIND( 0, MAX_MAP_SERVERS, j, server[j].fd >= 0 && server[j].map[0] );
-				if (j == MAX_MAP_SERVERS) {
+				ARR_FIND( 0, ARRAYLENGTH(server), j, server[j].fd >= 0 && server[j].map[0] );
+				if (j == ARRAYLENGTH(server)) {
 					ShowInfo("Connection Closed. No map servers available.\n");
 					WFIFOHEAD(fd,3);
 					WFIFOW(fd,0) = 0x81;
@@ -3665,8 +3817,12 @@ int parse_char(int fd)
 			char* l_pass = (char*)RFIFOP(fd,26);
 			l_user[23] = '\0';
 			l_pass[23] = '\0';
-			ARR_FIND( 0, MAX_MAP_SERVERS, i, server[i].fd <= 0 );
-			if (i == MAX_MAP_SERVERS || strcmp(l_user, userid) || strcmp(l_pass, passwd)) {
+			ARR_FIND( 0, ARRAYLENGTH(server), i, server[i].fd <= 0 );
+			if( runflag != CHARSERVER_ST_RUNNING ||
+				i == ARRAYLENGTH(server) ||
+				strcmp(l_user, userid) != 0 ||
+				strcmp(l_pass, passwd) != 0 )
+			{
 				WFIFOHEAD(fd,3);
 				WFIFOW(fd,0) = 0x2af9;
 				WFIFOB(fd,2) = 3;
@@ -3729,7 +3885,7 @@ int mapif_sendall(unsigned char *buf, unsigned int len)
 	int i, c;
 
 	c = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		int fd;
 		if ((fd = server[i].fd) > 0) {
 			WFIFOHEAD(fd,len);
@@ -3747,7 +3903,7 @@ int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len)
 	int i, c;
 
 	c = 0;
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
+	for(i = 0; i < ARRAYLENGTH(server); i++) {
 		int fd;
 		if ((fd = server[i].fd) > 0 && fd != sfd) {
 			WFIFOHEAD(fd,len);
@@ -3765,8 +3921,8 @@ int mapif_send(int fd, unsigned char *buf, unsigned int len)
 	int i;
 
 	if (fd >= 0) {
-		ARR_FIND( 0, MAX_MAP_SERVERS, i, fd == server[i].fd );
-		if( i < MAX_MAP_SERVERS )
+		ARR_FIND( 0, ARRAYLENGTH(server), i, fd == server[i].fd );
+		if( i < ARRAYLENGTH(server) )
 		{
 			WFIFOHEAD(fd,len);
 			memcpy(WFIFOP(fd,0), buf, len);
@@ -4211,7 +4367,7 @@ int char_config_read(const char* cfgName)
 
 void do_final(void)
 {
-	ShowStatus("Terminating server.\n");
+	ShowStatus("Terminating...\n");
 
 	set_all_offline(-1);
 	set_all_offline_sql();
@@ -4219,6 +4375,9 @@ void do_final(void)
 	inter_final();
 
 	flush_fifos();
+	
+	do_final_mapif();
+	do_final_loginif();
 
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `ragsrvinfo`") )
 		Sql_ShowDebug(sql_handle);
@@ -4227,13 +4386,16 @@ void do_final(void)
 	online_char_db->destroy(online_char_db, NULL);
 	auth_db->destroy(auth_db, NULL);
 
-	if (login_fd > 0)
-		do_close(login_fd);
-	if (char_fd > 0)
+	if( char_fd != -1 )
+	{
 		do_close(char_fd);
+		char_fd = -1;
+	}
 
 	Sql_Free(sql_handle);
 	mapindex_final();
+
+	ShowStatus("Finished.\n");
 }
 
 //------------------------------
@@ -4249,15 +4411,27 @@ void set_server_type(void)
 	SERVER_TYPE = ATHENA_SERVER_CHAR;
 }
 
-int do_init(int argc, char **argv)
-{
-	int i;
 
-	for(i = 0; i < MAX_MAP_SERVERS; i++) {
-		memset(&server[i], 0, sizeof(struct mmo_map_server));
-		server[i].fd = -1;
+/// Called when a terminate signal is received.
+void do_shutdown(void)
+{
+	if( runflag != CHARSERVER_ST_SHUTDOWN )
+	{
+		int id;
+		runflag = CHARSERVER_ST_SHUTDOWN;
+		ShowStatus("Shutting down...\n");
+		// TODO proper shutdown procedure; wait for acks?, kick all characters, ... [FlavoJS]
+		for( id = 0; id < ARRAYLENGTH(server); ++id )
+			mapif_server_reset(id);
+		loginif_check_shutdown();
+		flush_fifos();
+		runflag = CORE_ST_STOP;
 	}
+}
+
 
+int do_init(int argc, char **argv)
+{
 	//Read map indexes
 	mapindex_init();
 	start_point.map = mapindex_name2id("new_zone01");
@@ -4284,8 +4458,6 @@ int do_init(int argc, char **argv)
 	char_read_fame_list(); //Read fame lists.
 	ShowInfo("char server initialized.\n");
 
-	set_defaultparse(parse_char);
-
 	if ((naddr_ != 0) && (!login_ip || !char_ip))
 	{
 		char ip_str[16];
@@ -4305,22 +4477,13 @@ int do_init(int argc, char **argv)
 		}
 	}
 
-	// establish char-login connection if not present
-	add_timer_func_list(check_connect_login_server, "check_connect_login_server");
-	add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
-
-	// keep the char-login connection alive
-	add_timer_func_list(ping_login_server, "ping_login_server");
-	add_timer_interval(gettick() + 1000, ping_login_server, 0, 0, ((int)stall_time-2) * 1000);
+	do_init_loginif();
+	do_init_mapif();
 
 	// periodically update the overall user count on all mapservers + login server
 	add_timer_func_list(broadcast_user_count, "broadcast_user_count");
 	add_timer_interval(gettick() + 1000, broadcast_user_count, 0, 0, 5 * 1000);
 
-	// send a list of all online account IDs to login server
-	add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
-	add_timer_interval(gettick() + 1000, send_accounts_tologin, 0, 0, 3600 * 1000); //Sync online accounts every hour
-
 	// ???
 	add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect");
 
@@ -4351,9 +4514,16 @@ int do_init(int argc, char **argv)
 
 	ShowInfo("End of char server initilization function.\n");
 
+	set_defaultparse(parse_char);
 	ShowInfo("open port %d.....\n",char_port);
 	char_fd = make_listen_bind(bind_ip, char_port);
 	ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port);
+	
+	if( runflag != CORE_ST_STOP )
+	{
+		shutdown_callback = do_shutdown;
+		runflag = CHARSERVER_ST_RUNNING;
+	}
 
 	return 0;
 }

+ 9 - 0
src/char_sql/char.h

@@ -4,6 +4,15 @@
 #ifndef _CHAR_SQL_H_
 #define _CHAR_SQL_H_
 
+#include "../common/core.h" // CORE_ST_LAST
+
+enum E_CHARSERVER_ST
+{
+	CHARSERVER_ST_RUNNING = CORE_ST_LAST,
+	CHARSERVER_ST_SHUTDOWN,
+	CHARSERVER_ST_LAST
+};
+
 struct mmo_charstatus;
 
 #define MAX_MAP_SERVERS 30

+ 11 - 3
src/common/core.c

@@ -24,7 +24,12 @@
 #include <unistd.h>
 #endif
 
-int runflag = 1;
+
+/// Called when a terminate signal is received.
+void (*shutdown_callback)(void) = NULL;
+
+
+int runflag = CORE_ST_RUN;
 int arg_c = 0;
 char **arg_v = NULL;
 
@@ -78,7 +83,10 @@ static void sig_proc(int sn)
 	case SIGTERM:
 		if (++is_called > 3)
 			exit(EXIT_SUCCESS);
-		runflag = 0;
+		if( shutdown_callback != NULL )
+			shutdown_callback();
+		else
+			runflag = CORE_ST_STOP;// auto-shutdown
 		break;
 	case SIGSEGV:
 	case SIGFPE:
@@ -249,7 +257,7 @@ int main (int argc, char **argv)
 
 	{// Main runtime cycle
 		int next;
-		while (runflag) {
+		while (runflag != CORE_ST_STOP) {
 			next = do_timer(gettick_nocache());
 			do_sockets(next);
 		}

+ 13 - 0
src/common/core.h

@@ -7,6 +7,7 @@
 extern int arg_c;
 extern char **arg_v;
 
+/// @see E_CORE_ST
 extern int runflag;
 extern char *SERVER_NAME;
 extern char SERVER_TYPE;
@@ -18,4 +19,16 @@ extern void set_server_type(void);
 extern void do_abort(void);
 extern void do_final(void);
 
+/// The main loop continues until runflag is CORE_ST_STOP
+enum E_CORE_ST
+{
+	CORE_ST_STOP = 0,
+	CORE_ST_RUN,
+	CORE_ST_LAST
+};
+
+/// Called when a terminate signal is received. (Ctrl+C pressed)
+/// If NULL, runflag is set to CORE_ST_STOP instead.
+extern void (*shutdown_callback)(void);
+
 #endif /* _CORE_H_ */

+ 57 - 3
src/common/db.c

@@ -271,6 +271,7 @@ static struct db_stats {
 	uint32 dbit_remove;
 	uint32 dbit_destroy;
 	uint32 db_iterator;
+	uint32 db_exists;
 	uint32 db_get;
 	uint32 db_getall;
 	uint32 db_vgetall;
@@ -304,7 +305,7 @@ static struct db_stats {
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0
+	0, 0, 0, 0, 0, 0, 0, 0, 0
 };
 #define DB_COUNTSTAT(token) if (stats. ## token != UINT32_MAX) ++stats. ## token
 #else /* !defined(DB_ENABLE_STATS) */
@@ -1087,6 +1088,7 @@ static void db_release_both(DBKey key, void *data, DBRelease which)
  *  dbit_obj_destroy - Destroys the iterator, unlocking the database and     *
  *           freeing used memory.                                            *
  *  db_obj_iterator - Return a new databse iterator.                         *
+ *  db_obj_exists   - Checks if an entry exists.                             *
  *  db_obj_get      - Get the data identified by the key.                    *
  *  db_obj_vgetall  - Get the data of the matched entries.                   *
  *  db_obj_getall   - Get the data of the matched entries.                   *
@@ -1401,6 +1403,57 @@ static DBIterator* db_obj_iterator(DBMap* self)
 	return &it->vtable;
 }
 
+/**
+ * Returns true if the entry exists.
+ * @param self Interface of the database
+ * @param key Key that identifies the entry
+ * @return true is the entry exists
+ * @protected
+ * @see DBMap#exists
+ */
+static bool db_obj_exists(DBMap* self, DBKey key)
+{
+	DBMap_impl* db = (DBMap_impl*)self;
+	DBNode node;
+	int c;
+	bool found = false;
+
+	DB_COUNTSTAT(db_exists);
+	if (db == NULL) return false; // nullpo candidate
+	if (!(db->options&DB_OPT_ALLOW_NULL_KEY) && db_is_key_null(db->type, key)) {
+		return false; // nullpo candidate
+	}
+
+	if (db->cache && db->cmp(key, db->cache->key, db->maxlen) == 0) {
+#if defined(DEBUG)
+		if (db->cache->deleted) {
+			ShowDebug("db_exists: Cache contains a deleted node. Please report this!!!\n");
+			return false;
+		}
+#endif
+		return true; // cache hit
+	}
+
+	db_free_lock(db);
+	node = db->ht[db->hash(key, db->maxlen)%HASH_SIZE];
+	while (node) {
+		c = db->cmp(key, node->key, db->maxlen);
+		if (c == 0) {
+			if (!(node->deleted)) {
+				db->cache = node;
+				found = true;
+			}
+			break;
+		}
+		if (c < 0)
+			node = node->left;
+		else
+			node = node->right;
+	}
+	db_free_unlock(db);
+	return found;
+}
+
 /**
  * Get the data of the entry identifid by the key.
  * @param self Interface of the database
@@ -2351,6 +2404,7 @@ DBMap* db_alloc(const char *file, int line, DBType type, DBOptions options, unsi
 	options = db_fix_options(type, options);
 	/* Interface of the database */
 	db->vtable.iterator = db_obj_iterator;
+	db->vtable.exists   = db_obj_exists;
 	db->vtable.get      = db_obj_get;
 	db->vtable.getall   = db_obj_getall;
 	db->vtable.vgetall  = db_obj_vgetall;
@@ -2493,7 +2547,7 @@ void db_final(void)
 			"dbit_next          %10u, dbit_prev          %10u,\n"
 			"dbit_exists        %10u, dbit_remove        %10u,\n"
 			"dbit_destroy       %10u, db_iterator        %10u,\n"
-			"db_get             %10u,\n"
+			"db_exits           %10u, db_get             %10u,\n"
 			"db_getall          %10u, db_vgetall         %10u,\n"
 			"db_ensure          %10u, db_vensure         %10u,\n"
 			"db_put             %10u, db_remove          %10u,\n"
@@ -2523,7 +2577,7 @@ void db_final(void)
 			stats.dbit_next,          stats.dbit_prev,
 			stats.dbit_exists,        stats.dbit_remove,
 			stats.dbit_destroy,       stats.db_iterator,
-			stats.db_get,
+			stats.db_exists,          stats.db_get,
 			stats.db_getall,          stats.db_vgetall,
 			stats.db_ensure,          stats.db_vensure,
 			stats.db_put,             stats.db_remove,

+ 14 - 0
src/common/db.h

@@ -359,6 +359,15 @@ struct DBMap {
 	 */
 	DBIterator* (*iterator)(DBMap* self);
 
+	/**
+	 * Returns true if the entry exists.
+	 * @param self Database
+	 * @param key Key that identifies the entry
+	 * @return true is the entry exists
+	 * @protected
+	 */
+	bool (*exists)(DBMap* self, DBKey key);
+
 	/**
 	 * Get the data of the entry identifid by the key.
 	 * @param self Database
@@ -580,6 +589,11 @@ struct DBMap {
 #	define str2key(k) ((DBKey)(const char *)(k))
 #endif /* not DB_MANUAL_CAST_TO_UNION */
 
+#define db_exists(db,k)    ( (db)->exists((db),(k)) )
+#define idb_exists(db,k)   ( (db)->exists((db),i2key(k)) )
+#define uidb_exists(db,k)  ( (db)->exists((db),ui2key(k)) )
+#define strdb_exists(db,k) ( (db)->exists((db),str2key(k)) )
+
 #define db_get(db,k)    ( (db)->get((db),(k)) )
 #define idb_get(db,k)   ( (db)->get((db),i2key(k)) )
 #define uidb_get(db,k)  ( (db)->get((db),ui2key(k)) )

+ 3 - 0
src/common/socket.c

@@ -1118,6 +1118,9 @@ void socket_final(void)
 /// Closes a socket.
 void do_close(int fd)
 {
+	if( fd <= 0 ||fd >= FD_SETSIZE )
+		return;// invalid
+
 	flush_fifo(fd); // Try to send what's left (although it might not succeed since it's a nonblocking socket)
 	sFD_CLR(fd, &readfds);// this needs to be done before closing the socket
 	sShutdown(fd, SHUT_RDWR); // Disallow further reads/writes

+ 89 - 47
src/common/strlib.c

@@ -441,30 +441,13 @@ bool bin2hex(char* output, unsigned char* input, size_t count)
 
 
 /////////////////////////////////////////////////////////////////////
-/// Parses a delim-separated string.
-/// Starts parsing at startoff and fills the pos array with position pairs.
-/// out_pos[0] and out_pos[1] are the start and end of line.
-/// Other position pairs are the start and end of fields.
-/// Returns the number of fields found or -1 if an error occurs.
-/// 
-/// out_pos can be NULL.
-/// If a line terminator is found, the end position is placed there.
-/// out_pos[2] and out_pos[3] for the first field, out_pos[4] and out_pos[5] 
-/// for the seconds field and so on.
-/// Unfilled positions are set to -1.
-/// 
-/// @param str String to parse
-/// @param len Length of the string
-/// @param startoff Where to start parsing
-/// @param delim Field delimiter
-/// @param out_pos Array of resulting positions
-/// @param npos Size of the pos array
-/// @param opt Options that determine the parsing behaviour
-/// @return Number of fields found in the string or -1 if an error occured
-int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt)
+/// Parses a single field in a delim-separated string.
+/// The delimiter after the field is skipped.
+///
+/// @param sv Parse state
+/// @return 1 if a field was parsed, 0 if already done, -1 on error.
+int sv_parse_next(struct s_svstate* sv)
 {
-	int i;
-	int count;
 	enum {
 		START_OF_FIELD,
 		PARSING_FIELD,
@@ -473,27 +456,37 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 		TERMINATE,
 		END
 	} state;
+	const char* str;
+	int len;
+	enum e_svopt opt;
+	char delim;
+	int i;
 
-	// check pos/npos
-	if( out_pos == NULL ) npos = 0;
-	for( i = 0; i < npos; ++i )
-		out_pos[i] = -1;
+	if( sv == NULL )
+		return -1;// error
+
+	str = sv->str;
+	len = sv->len;
+	opt = sv->opt;
+	delim = sv->delim;
 
 	// check opt
 	if( delim == '\n' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_LF)) )
 	{
-		ShowError("sv_parse: delimiter '\\n' is not compatible with options SV_TERMINATE_LF or SV_TERMINATE_CRLF.\n");
+		ShowError("sv_parse_next: delimiter '\\n' is not compatible with options SV_TERMINATE_LF or SV_TERMINATE_CRLF.\n");
 		return -1;// error
 	}
 	if( delim == '\r' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_CR)) )
 	{
-		ShowError("sv_parse: delimiter '\\r' is not compatible with options SV_TERMINATE_CR or SV_TERMINATE_CRLF.\n");
+		ShowError("sv_parse_next: delimiter '\\r' is not compatible with options SV_TERMINATE_CR or SV_TERMINATE_CRLF.\n");
 		return -1;// error
 	}
 
-	// check str
-	if( str == NULL )
+	if( sv->done || str == NULL )
+	{
+		sv->done = true;
 		return 0;// nothing to parse
+	}
 
 #define IS_END() ( i >= len )
 #define IS_DELIM() ( str[i] == delim )
@@ -502,16 +495,13 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 	((opt&SV_TERMINATE_CR) && str[i] == '\r') || \
 	((opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n') )
 #define IS_C_ESCAPE() ( (opt&SV_ESCAPE_C) && str[i] == '\\' )
-#define SET_FIELD_START() if( npos > count*2+2 ) out_pos[count*2+2] = i
-#define SET_FIELD_END() if( npos > count*2+3 ) out_pos[count*2+3] = i; ++count
+#define SET_FIELD_START() sv->start = i
+#define SET_FIELD_END() sv->end = i
 
-	i = startoff;
-	count = 0;
+	i = sv->off;
 	state = START_OF_FIELD;
-	if( npos > 0 ) out_pos[0] = startoff;// start
 	while( state != END )
 	{
-		if( npos > 1 ) out_pos[1] = i;// end
 		switch( state )
 		{
 		case START_OF_FIELD:// record start of field and start parsing it
@@ -533,7 +523,7 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 				++i;// '\\'
 				if( IS_END() )
 				{
-					ShowError("sv_parse: empty escape sequence\n");
+					ShowError("sv_parse_next: empty escape sequence\n");
 					return -1;
 				}
 				if( str[i] == 'x' )
@@ -541,7 +531,7 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 					++i;// 'x'
 					if( IS_END() || !ISXDIGIT(str[i]) )
 					{
-						ShowError("sv_parse: \\x with no following hex digits\n");
+						ShowError("sv_parse_next: \\x with no following hex digits\n");
 						return -1;
 					}
 					do{
@@ -562,26 +552,22 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 				}
 				else
 				{
-					ShowError("sv_parse: unknown escape sequence \\%c\n", str[i]);
+					ShowError("sv_parse_next: unknown escape sequence \\%c\n", str[i]);
 					return -1;
 				}
 				state = PARSING_FIELD;
 				break;
 			}
 
-		case END_OF_FIELD:// record end of field and continue
+		case END_OF_FIELD:// record end of field and stop
 			SET_FIELD_END();
+			state = END;
 			if( IS_END() )
-				state = END;
+				;// nothing else
 			else if( IS_DELIM() )
-			{
 				++i;// delim
-				state = START_OF_FIELD;
-			}
 			else if( IS_TERMINATOR() )
 				state = TERMINATE;
-			else
-				state = START_OF_FIELD;
 			break;
 
 		case TERMINATE:
@@ -592,10 +578,14 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 			else
 				++i;// CR or LF
 #endif
+			sv->done = true;
 			state = END;
 			break;
 		}
 	}
+	if( IS_END() )
+		sv->done = true;
+	sv->off = i;
 
 #undef IS_END
 #undef IS_DELIM
@@ -604,6 +594,58 @@ int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, i
 #undef SET_FIELD_START
 #undef SET_FIELD_END
 
+	return 1;
+}
+
+
+/// Parses a delim-separated string.
+/// Starts parsing at startoff and fills the pos array with position pairs.
+/// out_pos[0] and out_pos[1] are the start and end of line.
+/// Other position pairs are the start and end of fields.
+/// Returns the number of fields found or -1 if an error occurs.
+/// 
+/// out_pos can be NULL.
+/// If a line terminator is found, the end position is placed there.
+/// out_pos[2] and out_pos[3] for the first field, out_pos[4] and out_pos[5] 
+/// for the seconds field and so on.
+/// Unfilled positions are set to -1.
+/// 
+/// @param str String to parse
+/// @param len Length of the string
+/// @param startoff Where to start parsing
+/// @param delim Field delimiter
+/// @param out_pos Array of resulting positions
+/// @param npos Size of the pos array
+/// @param opt Options that determine the parsing behaviour
+/// @return Number of fields found in the string or -1 if an error occured
+int sv_parse(const char* str, int len, int startoff, char delim, int* out_pos, int npos, enum e_svopt opt)
+{
+	struct s_svstate sv;
+	int count;
+
+	// initialize
+	if( out_pos == NULL ) npos = 0;
+	for( count = 0; count < npos; ++count )
+		out_pos[count] = -1;
+	sv.str = str;
+	sv.len = len;
+	sv.off = startoff;
+	sv.opt = opt;
+	sv.delim = delim;
+	sv.done = false;
+
+	// parse
+	count = 0;
+	if( npos > 0 ) out_pos[0] = startoff;
+	while( !sv.done )
+	{
+		++count;
+		if( sv_parse_next(&sv) <= 0 )
+			return -1;// error
+		if( npos > count*2 ) out_pos[count*2] = sv.start;
+		if( npos > count*2+1 ) out_pos[count*2+1] = sv.end;
+	}
+	if( npos > 1 ) out_pos[1] = sv.off;
 	return count;
 }
 

+ 21 - 0
src/common/strlib.h

@@ -78,6 +78,27 @@ typedef enum e_svopt
 /// Other escape sequences supported by the C compiler.
 #define SV_ESCAPE_C_SUPPORTED "abtnvfr\?\"'\\"
 
+/// Parse state.
+/// The field is [start,end[
+struct s_svstate
+{
+	const char* str; //< string to parse
+	int len; //< string length
+	int off; //< current offset in the string
+	int start; //< where the field starts
+	int end; //< where the field ends
+	enum e_svopt opt; //< parse options
+	char delim; //< field delimiter
+	bool done; //< if all the text has been parsed
+};
+
+/// Parses a single field in a delim-separated string.
+/// The delimiter after the field is skipped.
+///
+/// @param sv Parse state
+/// @return 1 if a field was parsed, 0 if done, -1 on error.
+int sv_parse_next(struct s_svstate* sv);
+
 /// Parses a delim-separated string.
 /// Starts parsing at startoff and fills the pos array with position pairs.
 /// out_pos[0] and out_pos[1] are the start and end of line.

+ 98 - 24
src/login/login.c

@@ -190,7 +190,7 @@ int charif_sendallwos(int sfd, uint8* buf, size_t len)
 {
 	int i, c;
 
-	for( i = 0, c = 0; i < MAX_SERVERS; ++i )
+	for( i = 0, c = 0; i < ARRAYLENGTH(server); ++i )
 	{
 		int fd = server[i].fd;
 		if( session_isValid(fd) && fd != sfd )
@@ -206,6 +206,42 @@ int charif_sendallwos(int sfd, uint8* buf, size_t len)
 }
 
 
+/// Initializes a server structure.
+void chrif_server_init(int id)
+{
+	memset(&server[id], 0, sizeof(server[id]));
+	server[id].fd = -1;
+}
+
+
+/// Destroys a server structure.
+void chrif_server_destroy(int id)
+{
+	if( server[id].fd != -1 )
+	{
+		do_close(server[id].fd);
+		server[id].fd = -1;
+	}
+}
+
+
+/// Resets all the data related to a server.
+void chrif_server_reset(int id)
+{
+	online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char server to offline.
+	chrif_server_destroy(id);
+	chrif_server_init(id);
+}
+
+
+/// Called when the connection to Char Server is disconnected.
+void chrif_on_disconnect(int id)
+{
+	ShowStatus("Char-server '%s' has disconnected.\n", server[id].name);
+	chrif_server_reset(id);
+}
+
+
 //-----------------------------------------------------
 // periodic ip address synchronization
 //-----------------------------------------------------
@@ -381,9 +417,10 @@ int parse_fromchar(int fd)
 	uint32 ipl;
 	char ip[16];
 
-	ARR_FIND( 0, MAX_SERVERS, id, server[id].fd == fd );
-	if( id == MAX_SERVERS )
+	ARR_FIND( 0, ARRAYLENGTH(server), id, server[id].fd == fd );
+	if( id == ARRAYLENGTH(server) )
 	{// not a char server
+		ShowDebug("parse_fromchar: Disconnecting invalid session #%d (is not a char-server)\n", fd);
 		set_eof(fd);
 		do_close(fd);
 		return 0;
@@ -391,11 +428,9 @@ int parse_fromchar(int fd)
 
 	if( session[fd]->flag.eof )
 	{
-		ShowStatus("Char-server '%s' has disconnected.\n", server[id].name);
-		online_db->foreach(online_db, online_db_setoffline, id); //Set all chars from this char server to offline.
-		memset(&server[id], 0, sizeof(struct mmo_char_server));
-		server[id].fd = -1;
 		do_close(fd);
+		server[id].fd = -1;
+		chrif_on_disconnect(id);
 		return 0;
 	}
 
@@ -424,8 +459,9 @@ int parse_fromchar(int fd)
 			RFIFOSKIP(fd,23);
 
 			node = (struct auth_node*)idb_get(auth_db, account_id);
-			if( node != NULL &&
-			    node->account_id == account_id &&
+			if( runflag == LOGINSERVER_ST_RUNNING &&
+				node != NULL &&
+				node->account_id == account_id &&
 				node->login_id1  == login_id1 &&
 				node->login_id2  == login_id2 &&
 				node->sex        == sex_num2str(sex) /*&&
@@ -1059,6 +1095,16 @@ void login_auth_ok(struct login_session_data* sd)
 	struct auth_node* node;
 	int i;
 
+	if( runflag != LOGINSERVER_ST_RUNNING )
+	{
+		// players can only login while running
+		WFIFOHEAD(fd,3);
+		WFIFOW(fd,0) = 0x81;
+		WFIFOB(fd,2) = 1;// server closed
+		WFIFOSET(fd,3);
+		return;
+	}
+
 	if( sd->level < login_config.min_level_to_connect )
 	{
 		ShowStatus("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d).\n", login_config.min_level_to_connect, sd->userid, sd->level);
@@ -1070,8 +1116,8 @@ void login_auth_ok(struct login_session_data* sd)
 	}
 
 	server_num = 0;
-	for( i = 0; i < MAX_SERVERS; ++i )
-		if( session_isValid(server[i].fd) )
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		if( session_isActive(server[i].fd) )
 			server_num++;
 
 	if( server_num == 0 )
@@ -1133,7 +1179,7 @@ void login_auth_ok(struct login_session_data* sd)
 	memset(WFIFOP(fd,20), 0, 24);
 	WFIFOW(fd,44) = 0; // unknown
 	WFIFOB(fd,46) = sex_str2num(sd->sex);
-	for( i = 0, n = 0; i < MAX_SERVERS; ++i )
+	for( i = 0, n = 0; i < ARRAYLENGTH(server); ++i )
 	{
 		if( !session_isValid(server[i].fd) )
 			continue;
@@ -1404,7 +1450,11 @@ int parse_login(int fd)
 			login_log(session[fd]->client_addr, sd->userid, 100, message);
 
 			result = mmo_auth(sd);
-			if( result == -1 && sd->sex == 'S' && sd->account_id < MAX_SERVERS && server[sd->account_id].fd == -1 )
+			if( runflag == LOGINSERVER_ST_RUNNING &&
+				result == -1 &&
+				sd->sex == 'S' &&
+				sd->account_id >= 0 && sd->account_id < ARRAYLENGTH(server) &&
+				!session_isValid(server[sd->account_id].fd) )
 			{
 				ShowStatus("Connection of the char-server '%s' accepted.\n", server_name);
 				safestrncpy(server[sd->account_id].name, server_name, sizeof(server[sd->account_id].name));
@@ -1592,7 +1642,7 @@ static AccountDB* get_account_engine(void)
 //--------------------------------------
 void do_final(void)
 {
-	int i, fd;
+	int i;
 
 	login_log(0, "login server", 100, "login server shutdown");
 	ShowStatus("Terminating...\n");
@@ -1614,15 +1664,15 @@ void do_final(void)
 	accounts = NULL; // destroyed in account_engines
 	online_db->destroy(online_db, NULL);
 	auth_db->destroy(auth_db, NULL);
+	
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		chrif_server_destroy(i);
 
-	for (i = 0; i < MAX_SERVERS; i++) {
-		if ((fd = server[i].fd) >= 0) {
-			memset(&server[i], 0, sizeof(struct mmo_char_server));
-			server[i].fd = -1;
-			do_close(fd);
-		}
+	if( login_fd != -1 )
+	{
+		do_close(login_fd);
+		login_fd = -1;
 	}
-	do_close(login_fd);
 
 	ShowStatus("Finished.\n");
 }
@@ -1640,6 +1690,24 @@ void set_server_type(void)
 	SERVER_TYPE = ATHENA_SERVER_LOGIN;
 }
 
+
+/// Called when a terminate signal is received.
+void do_shutdown(void)
+{
+	if( runflag != LOGINSERVER_ST_SHUTDOWN )
+	{
+		int id;
+		runflag = LOGINSERVER_ST_SHUTDOWN;
+		ShowStatus("Shutting down...\n");
+		// TODO proper shutdown procedure; kick all characters, wait for acks, ...  [FlavioJS]
+		for( id = 0; id < ARRAYLENGTH(server); ++id )
+			chrif_server_reset(id);
+		flush_fifos();
+		runflag = CORE_ST_STOP;
+	}
+}
+
+
 //------------------------------
 // Login server initialization
 //------------------------------
@@ -1657,9 +1725,9 @@ int do_init(int argc, char** argv)
 	login_lan_config_read((argc > 2) ? argv[2] : LAN_CONF_NAME);
 
 	srand((unsigned int)time(NULL));
-
-	for( i = 0; i < MAX_SERVERS; i++ )
-		server[i].fd = -1;
+	
+	for( i = 0; i < ARRAYLENGTH(server); ++i )
+		chrif_server_init(i);
 
 	// initialize logging
 	if( login_config.log_login )
@@ -1713,6 +1781,12 @@ int do_init(int argc, char** argv)
 
 	// server port open & binding
 	login_fd = make_listen_bind(login_config.login_ip, login_config.login_port);
+	
+	if( runflag != CORE_ST_STOP )
+	{
+		shutdown_callback = do_shutdown;
+		runflag = LOGINSERVER_ST_RUNNING;
+	}
 
 	ShowStatus("The login-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %u).\n\n", login_config.login_port);
 	login_log(0, "login server", 100, "login server started");

+ 8 - 0
src/login/login.h

@@ -5,6 +5,14 @@
 #define _LOGIN_H_
 
 #include "../common/mmo.h" // NAME_LENGTH,SEX_*
+#include "../common/core.h" // CORE_ST_LAST
+
+enum E_LOGINSERVER_ST
+{
+	LOGINSERVER_ST_RUNNING = CORE_ST_LAST,
+	LOGINSERVER_ST_SHUTDOWN,
+	LOGINSERVER_ST_LAST
+};
 
 #define LOGIN_CONF_NAME "conf/login_athena.conf"
 #define LAN_CONF_NAME "conf/subnet_athena.conf"

+ 1 - 15
src/map/atcommand.c

@@ -3903,23 +3903,9 @@ ACMD_FUNC(agitend2)
  *------------------------------------------*/
 ACMD_FUNC(mapexit)
 {
-	struct map_session_data* pl_sd;
-	struct s_mapiterator* iter;
-
 	nullpo_retr(-1, sd);
 
-	iter = mapit_getallusers();
-	for( pl_sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); pl_sd = (TBL_PC*)mapit_next(iter) )
-		if (sd->status.account_id != pl_sd->status.account_id)
-			clif_GM_kick(NULL, pl_sd);
-	mapit_free(iter);
-
-	clif_GM_kick(NULL, sd);
-	
-	flush_fifos();
-
-	runflag = 0;
-
+	do_shutdown();
 	return 0;
 }
 

+ 67 - 32
src/map/chrif.c

@@ -31,6 +31,8 @@
 #include <sys/types.h>
 #include <time.h>
 
+static int check_connect_char_server(int tid, unsigned int tick, int id, intptr data);
+
 static struct eri *auth_db_ers; //For reutilizing player login structures.
 static DBMap* auth_db; // int id -> struct auth_node*
 
@@ -94,7 +96,7 @@ static const int packet_len_table[0x3d] = { // U - used, F - free
 //2b27: Incoming, chrif_authfail -> 'client authentication failed'
 
 int chrif_connected = 0;
-int char_fd = 0; //Using 0 instead of -1 is safer against crashes. [Skotlex]
+int char_fd = -1;
 int srvinfo;
 static char char_ip_str[128];
 static uint32 char_ip = 0;
@@ -110,6 +112,28 @@ int other_mapserver_count=0; //Holds count of how many other map servers are onl
 //This define should spare writing the check in every function. [Skotlex]
 #define chrif_check(a) { if(!chrif_isconnected()) return a; }
 
+
+/// Resets all the data.
+void chrif_reset(void)
+{
+	// TODO kick everyone out and reset everything [FlavioJS]
+	exit(EXIT_FAILURE);
+}
+
+
+/// Checks the conditions for the server to stop.
+/// Releases the cookie when all characters are saved.
+/// If all the conditions are met, it stops the core loop.
+void chrif_check_shutdown(void)
+{
+	if( runflag != MAPSERVER_ST_SHUTDOWN )
+		return;
+	if( auth_db->size(auth_db) > 0 )
+		return;
+	runflag = CORE_ST_STOP;
+}
+
+
 struct auth_node* chrif_search(int account_id)
 {
 	return (struct auth_node*)idb_get(auth_db, account_id);
@@ -363,6 +387,7 @@ int chrif_removemap(int fd)
 static void chrif_save_ack(int fd)
 {
 	chrif_auth_delete(RFIFOL(fd,2), RFIFOL(fd,6), ST_LOGOUT);
+	chrif_check_shutdown();
 }
 
 // request to move a character between mapservers
@@ -472,19 +497,13 @@ static int chrif_reconnect(DBKey key,void *data,va_list ap)
 	return 0;
 }
 
-/*==========================================
- *
- *------------------------------------------*/
-int chrif_sendmapack(int fd)
-{
-	if (RFIFOB(fd,2)) {
-		ShowFatalError("chrif : send map list to char server failed %d\n", RFIFOB(fd,2));
-		exit(EXIT_FAILURE);
-	}
 
-	memcpy(wisp_server_name, RFIFOP(fd,3), NAME_LENGTH);
-	ShowStatus("Map sending complete. Map Server is now online.\n");
+/// Called when all the connection steps are completed.
+void chrif_on_ready(void)
+{
+	ShowStatus("Map Server is now online.\n");
 	chrif_state = 2;
+	chrif_check_shutdown();
 
 	//If there are players online, send them to the char-server. [Skotlex]
 	send_users_tochar();
@@ -494,7 +513,21 @@ int chrif_sendmapack(int fd)
 
 	//Re-save any storages that were modified in the disconnection time. [Skotlex]
 	do_reconnect_storage();
+}
+
+
+/*==========================================
+ *
+ *------------------------------------------*/
+int chrif_sendmapack(int fd)
+{
+	if (RFIFOB(fd,2)) {
+		ShowFatalError("chrif : send map list to char server failed %d\n", RFIFOB(fd,2));
+		exit(EXIT_FAILURE);
+	}
 
+	memcpy(wisp_server_name, RFIFOP(fd,3), NAME_LENGTH);
+	chrif_on_ready();
 	return 0;
 }
 
@@ -592,7 +625,8 @@ void chrif_authok(int fd)
 	}
 
 	sd = node->sd;
-	if(node->char_dat == NULL &&
+	if( runflag == MAPSERVER_ST_RUNNING &&
+		node->char_dat == NULL &&
 		node->account_id == account_id &&
 		node->char_id == char_id &&
 		node->login_id1 == login_id1 )
@@ -1292,22 +1326,22 @@ int chrif_char_online(struct map_session_data *sd)
 	return 0;
 }
 
-int chrif_disconnect(int fd)
+
+/// Called when the connection to Char Server is disconnected.
+void chrif_on_disconnect(void)
 {
-	if(fd == char_fd) {
-		char_fd = 0;
-		ShowWarning("Map Server disconnected from Char Server.\n\n");
-		chrif_connected = 0;
-		
-	 	other_mapserver_count=0; //Reset counter. We receive ALL maps from all map-servers on reconnect.
-		map_eraseallipport();
+	if( chrif_connected != 1 )
+		ShowWarning("Connection to Char Server lost.\n\n");
+	chrif_connected = 0;
+	
+ 	other_mapserver_count = 0; //Reset counter. We receive ALL maps from all map-servers on reconnect.
+	map_eraseallipport();
 
-		//Attempt to reconnect in a second. [Skotlex]
-		add_timer(gettick() + 1000, check_connect_char_server, 0, 0);
-	}
-	return 0;
+	//Attempt to reconnect in a second. [Skotlex]
+	add_timer(gettick() + 1000, check_connect_char_server, 0, 0);
 }
 
+
 void chrif_update_ip(int fd)
 {
 	uint32 new_ip;
@@ -1352,10 +1386,9 @@ int chrif_parse(int fd)
 
 	if (session[fd]->flag.eof)
 	{
-		if (chrif_connected == 1)
-			chrif_disconnect(fd);
-
 		do_close(fd);
+		char_fd = -1;
+		chrif_on_disconnect();
 		return 0;
 	}
 
@@ -1393,7 +1426,7 @@ int chrif_parse(int fd)
 		case 0x2afb: chrif_sendmapack(fd); break;
 		case 0x2afd: chrif_authok(fd); break;
 		case 0x2b00: map_setusers(RFIFOL(fd,2)); chrif_keepalive(fd); break;
-		case 0x2b03: clif_charselectok(RFIFOL(fd,2)); break;
+		case 0x2b03: clif_charselectok(RFIFOL(fd,2), RFIFOB(fd,6)); break;
 		case 0x2b04: chrif_recvmap(fd); break;
 		case 0x2b06: chrif_changemapserverack(RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOW(fd,18), RFIFOW(fd,20), RFIFOW(fd,22), RFIFOL(fd,24), RFIFOW(fd,28)); break;
 		case 0x2b09: map_addnickdb(RFIFOL(fd,2), (char*)RFIFOP(fd,6)); break;
@@ -1476,7 +1509,7 @@ int send_users_tochar(void)
  * timer関数
  * char鯖との接続を確認し、もし切れていたら再度接続する
  *------------------------------------------*/
-int check_connect_char_server(int tid, unsigned int tick, int id, intptr data)
+static int check_connect_char_server(int tid, unsigned int tick, int id, intptr data)
 {
 	static int displayed = 0;
 	if (char_fd <= 0 || session[char_fd] == NULL)
@@ -1491,7 +1524,6 @@ int check_connect_char_server(int tid, unsigned int tick, int id, intptr data)
 		char_fd = make_connection(char_ip, char_port);
 		if (char_fd == -1)
 		{	//Attempt to connect later. [Skotlex]
-			char_fd = 0;
 			return 0;
 		}
 
@@ -1532,8 +1564,11 @@ int auth_db_final(DBKey k,void *d,va_list ap)
  *------------------------------------------*/
 int do_final_chrif(void)
 {
-	if (char_fd > 0)
+	if( char_fd != -1 )
+	{
 		do_close(char_fd);
+		char_fd = -1;
+	}
 
 	auth_db->destroy(auth_db, auth_db_final);
 	ers_destroy(auth_db_ers);

+ 1 - 1
src/map/chrif.h

@@ -25,6 +25,7 @@ int chrif_setip(const char* ip);
 void chrif_setport(uint16 port);
 
 int chrif_isconnected(void);
+void chrif_check_shutdown(void);
 
 extern int chrif_connected;
 extern int other_mapserver_count;
@@ -55,7 +56,6 @@ int send_users_tochar(void);
 int chrif_char_online(struct map_session_data *sd);
 int chrif_changesex(struct map_session_data *sd);
 int chrif_chardisconnect(struct map_session_data *sd);
-int check_connect_char_server(int tid, unsigned int tick, int id, intptr data);
 int chrif_divorce(int partner_id1, int partner_id2);
 
 int do_final_chrif(void);

+ 11 - 5
src/map/clif.c

@@ -581,10 +581,10 @@ int clif_authfail_fd(int fd, int type)
 	return 0;
 }
 
-/*==========================================
- *
- *------------------------------------------*/
-int clif_charselectok(int id)
+/// Reply from char-server.
+/// Tells the player if it can connect to the char-server to select a character.
+/// ok=1 : client disconnects and tries to connect to the char-server
+int clif_charselectok(int id, uint8 ok)
 {
 	struct map_session_data* sd;
 	int fd;
@@ -595,7 +595,7 @@ int clif_charselectok(int id)
 	fd = sd->fd;
 	WFIFOHEAD(fd,packet_len(0xb3));
 	WFIFOW(fd,0) = 0xb3;
-	WFIFOB(fd,2) = 1;
+	WFIFOB(fd,2) = ok;
 	WFIFOSET(fd,packet_len(0xb3));
 
 	return 0;
@@ -8392,6 +8392,12 @@ void clif_parse_WantToConnection(int fd, TBL_PC* sd)
 		return;
 	}
 
+	if( runflag != MAPSERVER_ST_RUNNING )
+	{// not allowed
+		clif_authfail_fd(fd,1);// server closed
+		return;
+	}
+
 	//Check for double login.
 	bl = map_id2bl(account_id);
 	if(bl && bl->type != BL_PC) {

+ 1 - 1
src/map/clif.h

@@ -220,7 +220,7 @@ uint16 clif_getport(void);
 
 int clif_authok(struct map_session_data *);
 int clif_authfail_fd(int fd,int type);
-int clif_charselectok(int);
+int clif_charselectok(int id, uint8 ok);
 int clif_dropflooritem(struct flooritem_data *);
 int clif_clearflooritem(struct flooritem_data *,int);
 

+ 28 - 1
src/map/map.c

@@ -3491,7 +3491,7 @@ void do_final(void)
 #ifndef TXT_ONLY
     map_sql_close();
 #endif /* not TXT_ONLY */
-	ShowStatus("Successfully terminated.\n");
+	ShowStatus("Finished.\n");
 }
 
 static int map_abort_sub(struct map_session_data* sd, va_list ap)
@@ -3573,6 +3573,27 @@ void set_server_type(void)
 	SERVER_TYPE = ATHENA_SERVER_MAP;
 }
 
+
+/// Called when a terminate signal is received.
+void do_shutdown(void)
+{
+	if( runflag != MAPSERVER_ST_SHUTDOWN )
+	{
+		runflag = MAPSERVER_ST_SHUTDOWN;
+		ShowStatus("Shutting down...\n");
+		{
+			struct map_session_data* sd;
+			struct s_mapiterator* iter = mapit_getallusers();
+			for( sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter) )
+				clif_GM_kick(NULL, sd);
+			mapit_free(iter);
+			flush_fifos();
+		}
+		chrif_check_shutdown();
+	}
+}
+
+
 int do_init(int argc, char *argv[])
 {
 	int i;
@@ -3710,6 +3731,12 @@ int do_init(int argc, char *argv[])
 		ShowNotice("Server is running on '"CL_WHITE"PK Mode"CL_RESET"'.\n");
 
 	ShowStatus("Server is '"CL_GREEN"ready"CL_RESET"' and listening on port '"CL_WHITE"%d"CL_RESET"'.\n\n", map_port);
+	
+	if( runflag != CORE_ST_STOP )
+	{
+		shutdown_callback = do_shutdown;
+		runflag = MAPSERVER_ST_RUNNING;
+	}
 
 	return 0;
 }

+ 10 - 0
src/map/map.h

@@ -7,6 +7,7 @@
 #ifndef _CBASETYPES_H_
 #include "../common/cbasetypes.h"
 #endif
+#include "../common/core.h" // CORE_ST_LAST
 #include "../common/mmo.h"
 #include "../common/mapindex.h"
 #include "../common/db.h"
@@ -16,6 +17,13 @@
 struct npc_data;
 struct item_data;
 
+enum E_MAPSERVER_ST
+{
+	MAPSERVER_ST_RUNNING = CORE_ST_LAST,
+	MAPSERVER_ST_SHUTDOWN,
+	MAPSERVER_ST_LAST
+};
+
 //Uncomment to enable the Cell Stack Limit mod.
 //It's only config is the battle_config cell_stack_limit.
 //Only chars affected are those defined in BL_CHAR (mobs and players currently)
@@ -695,4 +703,6 @@ extern char mob_db2_db[32];
 
 #endif /* not TXT_ONLY */
 
+void do_shutdown(void);
+
 #endif /* _MAP_H_ */