]> git.lizzy.rs Git - minetest.git/blobdiff - src/server.cpp
Properly handle mod-errors in on_shutdown
[minetest.git] / src / server.cpp
index 68b0131d42980368861ccede6c521d7a99e60129..7b3978462673cc57c08070b001b1154a3817717f 100644 (file)
@@ -213,7 +213,8 @@ Server::Server(
                bool simple_singleplayer_mode,
                Address bind_addr,
                bool dedicated,
-               ChatInterface *iface
+               ChatInterface *iface,
+               std::string *on_shutdown_errmsg
        ):
        m_bind_addr(bind_addr),
        m_path_world(path_world),
@@ -232,6 +233,7 @@ Server::Server(
        m_thread(new ServerThread(this)),
        m_clients(m_con),
        m_admin_chat(iface),
+       m_on_shutdown_errmsg(on_shutdown_errmsg),
        m_modchannel_mgr(new ModChannelMgr())
 {
        if (m_path_world.empty())
@@ -314,7 +316,18 @@ Server::~Server()
 
                // Execute script shutdown hooks
                infostream << "Executing shutdown hooks" << std::endl;
-               m_script->on_shutdown();
+               try {
+                       m_script->on_shutdown();
+               } catch (ModError &e) {
+                       errorstream << "ModError: " << e.what() << std::endl;
+                       if (m_on_shutdown_errmsg) {
+                               if (m_on_shutdown_errmsg->empty()) {
+                                       *m_on_shutdown_errmsg = std::string("ModError: ") + e.what();
+                               } else {
+                                       *m_on_shutdown_errmsg += std::string("\nModError: ") + e.what();
+                               }
+                       }
+               }
 
                infostream << "Server: Saving environment metadata" << std::endl;
                m_env->saveMeta();
@@ -356,8 +369,13 @@ void Server::init()
        infostream << "- game:   " << m_gamespec.path << std::endl;
 
        // Create world if it doesn't exist
-       if (!loadGameConfAndInitWorld(m_path_world, m_gamespec))
-               throw ServerError("Failed to initialize world");
+       try {
+               loadGameConfAndInitWorld(m_path_world,
+                               fs::GetFilenameFromPath(m_path_world.c_str()),
+                               m_gamespec, false);
+       } catch (const BaseException &e) {
+               throw ServerError(std::string("Failed to initialize world: ") + e.what());
+       }
 
        // Create emerge manager
        m_emerge = new EmergeManager(this);
@@ -718,34 +736,35 @@ void Server::AsyncRunStep(bool initial_step)
                std::unordered_map<u16, std::vector<ActiveObjectMessage>*> buffered_messages;
 
                // Get active object messages from environment
+               ActiveObjectMessage aom(0);
+               u32 aom_count = 0;
                for(;;) {
-                       ActiveObjectMessage aom = m_env->getActiveObjectMessage();
-                       if (aom.id == 0)
+                       if (!m_env->getActiveObjectMessage(&aom))
                                break;
 
                        std::vector<ActiveObjectMessage>* message_list = nullptr;
-                       std::unordered_map<u16, std::vector<ActiveObjectMessage>* >::iterator n;
-                       n = buffered_messages.find(aom.id);
+                       auto n = buffered_messages.find(aom.id);
                        if (n == buffered_messages.end()) {
                                message_list = new std::vector<ActiveObjectMessage>;
                                buffered_messages[aom.id] = message_list;
-                       }
-                       else {
+                       } else {
                                message_list = n->second;
                        }
-                       message_list->push_back(aom);
+                       message_list->push_back(std::move(aom));
+                       aom_count++;
                }
 
-               m_aom_buffer_counter->increment(buffered_messages.size());
+               m_aom_buffer_counter->increment(aom_count);
 
                m_clients.lock();
                const RemoteClientMap &clients = m_clients.getClientList();
                // Route data to every client
+               std::string reliable_data, unreliable_data;
                for (const auto &client_it : clients) {
+                       reliable_data.clear();
+                       unreliable_data.clear();
                        RemoteClient *client = client_it.second;
                        PlayerSAO *player = getPlayerSAO(client->peer_id);
-                       std::string reliable_data;
-                       std::string unreliable_data;
                        // Go through all objects in message buffer
                        for (const auto &buffered_message : buffered_messages) {
                                // If object does not exist or is not known by client, skip it
@@ -770,19 +789,15 @@ void Server::AsyncRunStep(bool initial_step)
                                                                client->m_known_objects.end())
                                                        continue;
                                        }
-                                       // Compose the full new data with header
-                                       std::string new_data;
-                                       // Add object id
-                                       char buf[2];
-                                       writeU16((u8*)&buf[0], aom.id);
-                                       new_data.append(buf, 2);
-                                       // Add data
-                                       new_data += serializeString(aom.datastring);
-                                       // Add data to buffer
-                                       if (aom.reliable)
-                                               reliable_data += new_data;
-                                       else
-                                               unreliable_data += new_data;
+
+                                       // Add full new data to appropriate buffer
+                                       std::string &buffer = aom.reliable ? reliable_data : unreliable_data;
+                                       char idbuf[2];
+                                       writeU16((u8*) idbuf, aom.id);
+                                       // u16 id
+                                       // std::string data
+                                       buffer.append(idbuf, sizeof(idbuf));
+                                       buffer.append(serializeString(aom.datastring));
                                }
                        }
                        /*
@@ -1549,12 +1564,30 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
 void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
        const ParticleSpawnerParameters &p, u16 attached_id, u32 id)
 {
+       static thread_local const float radius =
+                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
+
        if (peer_id == PEER_ID_INEXISTENT) {
                std::vector<session_t> clients = m_clients.getClientIDs();
+               const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
+               const float radius_sq = radius * radius;
+               /* Don't send short-lived spawners to distant players.
+                * This could be replaced with proper tracking at some point. */
+               const bool distance_check = !attached_id && p.time <= 1.0f;
+
                for (const session_t client_id : clients) {
                        RemotePlayer *player = m_env->getPlayer(client_id);
                        if (!player)
                                continue;
+
+                       if (distance_check) {
+                               PlayerSAO *sao = player->getPlayerSAO();
+                               if (!sao)
+                                       continue;
+                               if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
+                                       continue;
+                       }
+
                        SendAddParticleSpawner(client_id, player->protocol_version,
                                p, attached_id, id);
                }
@@ -1577,6 +1610,7 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
                pkt.putRawString(os.str());
        }
        pkt << p.glow << p.object_collision;
+       pkt << p.node.param0 << p.node.param2 << p.node_tile;
 
        Send(&pkt);
 }
@@ -2389,94 +2423,113 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
        return true;
 }
 
+bool Server::addMediaFile(const std::string &filename,
+       const std::string &filepath, std::string *filedata_to,
+       std::string *digest_to)
+{
+       // If name contains illegal characters, ignore the file
+       if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
+               infostream << "Server: ignoring illegal file name: \""
+                               << filename << "\"" << std::endl;
+               return false;
+       }
+       // If name is not in a supported format, ignore it
+       const char *supported_ext[] = {
+               ".png", ".jpg", ".bmp", ".tga",
+               ".pcx", ".ppm", ".psd", ".wal", ".rgb",
+               ".ogg",
+               ".x", ".b3d", ".md2", ".obj",
+               // Custom translation file format
+               ".tr",
+               NULL
+       };
+       if (removeStringEnd(filename, supported_ext).empty()) {
+               infostream << "Server: ignoring unsupported file extension: \""
+                               << filename << "\"" << std::endl;
+               return false;
+       }
+       // Ok, attempt to load the file and add to cache
+
+       // Read data
+       std::ifstream fis(filepath.c_str(), std::ios_base::binary);
+       if (!fis.good()) {
+               errorstream << "Server::addMediaFile(): Could not open \""
+                               << filename << "\" for reading" << std::endl;
+               return false;
+       }
+       std::string filedata;
+       bool bad = false;
+       for (;;) {
+               char buf[1024];
+               fis.read(buf, sizeof(buf));
+               std::streamsize len = fis.gcount();
+               filedata.append(buf, len);
+               if (fis.eof())
+                       break;
+               if (!fis.good()) {
+                       bad = true;
+                       break;
+               }
+       }
+       if (bad) {
+               errorstream << "Server::addMediaFile(): Failed to read \""
+                               << filename << "\"" << std::endl;
+               return false;
+       } else if (filedata.empty()) {
+               errorstream << "Server::addMediaFile(): Empty file \""
+                               << filepath << "\"" << std::endl;
+               return false;
+       }
+
+       SHA1 sha1;
+       sha1.addBytes(filedata.c_str(), filedata.length());
+
+       unsigned char *digest = sha1.getDigest();
+       std::string sha1_base64 = base64_encode(digest, 20);
+       std::string sha1_hex = hex_encode((char*) digest, 20);
+       if (digest_to)
+               *digest_to = std::string((char*) digest, 20);
+       free(digest);
+
+       // Put in list
+       m_media[filename] = MediaInfo(filepath, sha1_base64);
+       verbosestream << "Server: " << sha1_hex << " is " << filename
+                       << std::endl;
+
+       if (filedata_to)
+               *filedata_to = std::move(filedata);
+       return true;
+}
+
 void Server::fillMediaCache()
 {
-       infostream<<"Server: Calculating media file checksums"<<std::endl;
+       infostream << "Server: Calculating media file checksums" << std::endl;
 
        // Collect all media file paths
        std::vector<std::string> paths;
-       m_modmgr->getModsMediaPaths(paths);
-       fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
+       // The paths are ordered in descending priority
        fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
+       fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
+       m_modmgr->getModsMediaPaths(paths);
 
        // Collect media file information from paths into cache
        for (const std::string &mediapath : paths) {
                std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath);
                for (const fs::DirListNode &dln : dirlist) {
-                       if (dln.dir) // Ignode dirs
-                               continue;
-                       std::string filename = dln.name;
-                       // If name contains illegal characters, ignore the file
-                       if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
-                               infostream<<"Server: ignoring illegal file name: \""
-                                               << filename << "\"" << std::endl;
-                               continue;
-                       }
-                       // If name is not in a supported format, ignore it
-                       const char *supported_ext[] = {
-                               ".png", ".jpg", ".bmp", ".tga",
-                               ".pcx", ".ppm", ".psd", ".wal", ".rgb",
-                               ".ogg",
-                               ".x", ".b3d", ".md2", ".obj",
-                               // Custom translation file format
-                               ".tr",
-                               NULL
-                       };
-                       if (removeStringEnd(filename, supported_ext).empty()){
-                               infostream << "Server: ignoring unsupported file extension: \""
-                                               << filename << "\"" << std::endl;
-                               continue;
-                       }
-                       // Ok, attempt to load the file and add to cache
-                       std::string filepath;
-                       filepath.append(mediapath).append(DIR_DELIM).append(filename);
-
-                       // Read data
-                       std::ifstream fis(filepath.c_str(), std::ios_base::binary);
-                       if (!fis.good()) {
-                               errorstream << "Server::fillMediaCache(): Could not open \""
-                                               << filename << "\" for reading" << std::endl;
-                               continue;
-                       }
-                       std::ostringstream tmp_os(std::ios_base::binary);
-                       bool bad = false;
-                       for(;;) {
-                               char buf[1024];
-                               fis.read(buf, 1024);
-                               std::streamsize len = fis.gcount();
-                               tmp_os.write(buf, len);
-                               if (fis.eof())
-                                       break;
-                               if (!fis.good()) {
-                                       bad = true;
-                                       break;
-                               }
-                       }
-                       if(bad) {
-                               errorstream<<"Server::fillMediaCache(): Failed to read \""
-                                               << filename << "\"" << std::endl;
-                               continue;
-                       }
-                       if(tmp_os.str().length() == 0) {
-                               errorstream << "Server::fillMediaCache(): Empty file \""
-                                               << filepath << "\"" << std::endl;
+                       if (dln.dir) // Ignore dirs (already in paths)
                                continue;
-                       }
-
-                       SHA1 sha1;
-                       sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
 
-                       unsigned char *digest = sha1.getDigest();
-                       std::string sha1_base64 = base64_encode(digest, 20);
-                       std::string sha1_hex = hex_encode((char*)digest, 20);
-                       free(digest);
+                       const std::string &filename = dln.name;
+                       if (m_media.find(filename) != m_media.end()) // Do not override
+                               continue;
 
-                       // Put in list
-                       m_media[filename] = MediaInfo(filepath, sha1_base64);
-                       verbosestream << "Server: " << sha1_hex << " is " << filename
-                                       << std::endl;
+                       std::string filepath = mediapath;
+                       filepath.append(DIR_DELIM).append(filename);
+                       addMediaFile(filename, filepath);
                }
        }
+
+       infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
 }
 
 void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
@@ -3412,6 +3465,44 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
        SendDeleteParticleSpawner(peer_id, id);
 }
 
+bool Server::dynamicAddMedia(const std::string &filepath)
+{
+       std::string filename = fs::GetFilenameFromPath(filepath.c_str());
+       if (m_media.find(filename) != m_media.end()) {
+               errorstream << "Server::dynamicAddMedia(): file \"" << filename
+                       << "\" already exists in media cache" << std::endl;
+               return false;
+       }
+
+       // Load the file and add it to our media cache
+       std::string filedata, raw_hash;
+       bool ok = addMediaFile(filename, filepath, &filedata, &raw_hash);
+       if (!ok)
+               return false;
+
+       // Push file to existing clients
+       NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
+       pkt << raw_hash << filename << (bool) true;
+       pkt.putLongString(filedata);
+
+       auto client_ids = m_clients.getClientIDs(CS_DefinitionsSent);
+       for (session_t client_id : client_ids) {
+               /*
+                       The network layer only guarantees ordered delivery inside a channel.
+                       Since the very next packet could be one that uses the media, we have
+                       to push the media over ALL channels to ensure it is processed before
+                       it is used.
+                       In practice this means we have to send it twice:
+                       - channel 1 (HUD)
+                       - channel 0 (everything else: e.g. play_sound, object messages)
+               */
+               m_clients.send(client_id, 1, &pkt, true);
+               m_clients.send(client_id, 0, &pkt, true);
+       }
+
+       return true;
+}
+
 // actions: time-reversed list
 // Return value: success/failure
 bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,