/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Admin.hpp" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AdminModel.hpp" #include "Auth.hpp" #include "Common.hpp" #include "FileServer.hpp" #include "IoUtil.hpp" #include "LOOLProtocol.hpp" #include "LOOLWSD.hpp" #include "Log.hpp" #include "Storage.hpp" #include "TileCache.hpp" #include "Unit.hpp" #include "Util.hpp" using namespace LOOLProtocol; using Poco::StringTokenizer; using Poco::Net::HTTPRequestHandler; using Poco::Net::HTTPResponse; using Poco::Net::HTTPServerRequest; using Poco::Net::HTTPServerResponse; using Poco::Net::WebSocket; bool AdminRequestHandler::adminCommandHandler(const std::vector& payload) { const std::string firstLine = getFirstLine(payload.data(), payload.size()); StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); Log::trace("Recv: " + firstLine); if (tokens.count() < 1) return false; std::unique_lock modelLock(_admin->getLock()); AdminModel& model = _admin->getModel(); if (tokens[0] == "documents" || tokens[0] == "active_users_count" || tokens[0] == "active_docs_count" || tokens[0] == "mem_stats" || tokens[0] == "cpu_stats" ) { const std::string responseFrame = tokens[0] + " " + model.query(tokens[0]); sendTextFrame(responseFrame); } else if (tokens[0] == "subscribe" && tokens.count() > 1) { for (unsigned i = 0; i < tokens.count() - 1; i++) { model.subscribe(_sessionId, tokens[i + 1]); } } else if (tokens[0] == "unsubscribe" && tokens.count() > 1) { for (unsigned i = 0; i < tokens.count() - 1; i++) { model.unsubscribe(_sessionId, tokens[i + 1]); } } else if (tokens[0] == "total_mem") { unsigned totalMem = _admin->getTotalMemoryUsage(model); std::string responseFrame = "total_mem " + std::to_string(totalMem); sendTextFrame(responseFrame); } else if (tokens[0] == "kill" && tokens.count() == 2) { try { const auto pid = std::stoi(tokens[1]); if (kill(pid, SIGINT) != 0 && kill(pid, 0) !=0) { Log::syserror("Cannot terminate PID: " + tokens[0]); } } catch(std::invalid_argument& exc) { Log::warn() << "Invalid PID to kill: " << tokens[0] << Log::end; return false; } } else if (tokens[0] == "settings") { // for now, we have only these settings std::ostringstream oss; oss << tokens[0] << " " << "mem_stats_size=" << model.query("mem_stats_size") << " " << "mem_stats_interval=" << std::to_string(_admin->getMemStatsInterval()) << " " << "cpu_stats_size=" << model.query("cpu_stats_size") << " " << "cpu_stats_interval=" << std::to_string(_admin->getCpuStatsInterval()); std::string responseFrame = oss.str(); sendTextFrame(responseFrame); } else if (tokens[0] == "set" && tokens.count() > 1) { for (unsigned i = 1; i < tokens.count(); i++) { StringTokenizer setting(tokens[i], "=", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); unsigned settingVal = 0; try { settingVal = std::stoi(setting[1]); } catch (const std::exception& exc) { Log::warn() << "Invalid setting value: " << setting[1] << " for " << setting[0] << Log::end; return false; } if (setting[0] == "mem_stats_size") { if (settingVal != static_cast(std::stoi(model.query(setting[0])))) { model.setMemStatsSize(settingVal); } } else if (setting[0] == "mem_stats_interval") { if (settingVal != _admin->getMemStatsInterval()) { _admin->rescheduleMemTimer(settingVal); model.clearMemStats(); model.notify("settings mem_stats_interval=" + std::to_string(settingVal)); } } else if (setting[0] == "cpu_stats_size") { if (settingVal != static_cast(std::stoi(model.query(setting[0])))) { model.setCpuStatsSize(settingVal); } } else if (setting[0] == "cpu_stats_interval") { if (settingVal != _admin->getCpuStatsInterval()) { _admin->rescheduleCpuTimer(settingVal); model.clearCpuStats(); model.notify("settings cpu_stats_interval=" + std::to_string(settingVal)); } } } } return true; } /// Handle admin requests. void AdminRequestHandler::handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int sessionId) { _adminWs = std::make_shared(request, response); { std::unique_lock modelLock(_admin->getLock()); // Subscribe the websocket of any AdminModel updates AdminModel& model = _admin->getModel(); _sessionId = sessionId; model.subscribe(_sessionId, _adminWs); } IoUtil::SocketProcessor(_adminWs, [this](const std::vector& payload) { return adminCommandHandler(payload); }, []() { }, []() { return TerminationFlag; }); Log::debug() << "Finishing Admin Session " << Util::encodeId(sessionId); } AdminRequestHandler::AdminRequestHandler(Admin* adminManager) : _admin(adminManager) { } void AdminRequestHandler::sendTextFrame(const std::string& message) { UnitWSD::get().onAdminQueryMessage(message); _adminWs->sendFrame(message.data(), message.size()); } void AdminRequestHandler::handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) { // Different session id pool for admin sessions (?) const auto sessionId = Util::decodeId(LOOLWSD::GenSessionId()); Util::setThreadName("admin_ws_" + std::to_string(sessionId)); Log::debug("Thread started."); try { std::string requestURI = request.getURI(); StringTokenizer pathTokens(requestURI, "/", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) { if (!FileServerRequestHandler::isAdminLoggedIn(request, response)) throw Poco::Net::NotAuthenticatedException("Invalid admin login"); handleWSRequests(request, response, sessionId); } } catch(const Poco::Net::NotAuthenticatedException& exc) { Log::info("Admin::NotAuthenticated"); response.set("WWW-Authenticate", "Basic realm=\"online\""); response.setStatusAndReason(HTTPResponse::HTTP_UNAUTHORIZED); response.setContentLength(0); response.send(); } catch (const std::exception& exc) { Log::info(std::string("Admin::handleRequest: Exception: ") + exc.what()); response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST); response.setContentLength(0); response.send(); } Log::debug("Thread finished."); } /// An admin command processor. Admin::Admin() : _model(AdminModel()) { Log::info("Admin ctor."); _memStatsTask = new MemoryStats(this); _memStatsTimer.schedule(_memStatsTask, _memStatsTaskInterval, _memStatsTaskInterval); _cpuStatsTask = new CpuStats(this); _cpuStatsTimer.schedule(_cpuStatsTask, _cpuStatsTaskInterval, _cpuStatsTaskInterval); } Admin::~Admin() { Log::info("~Admin dtor."); _memStatsTask->cancel(); _cpuStatsTask->cancel(); } void Admin::addDoc(const std::string& docKey, Poco::Process::PID pid, const std::string& filename, const std::string& sessionId) { std::unique_lock modelLock(_modelMutex); _model.addDocument(docKey, pid, filename, sessionId); } void Admin::rmDoc(const std::string& docKey, const std::string& sessionId) { std::unique_lock modelLock(_modelMutex); _model.removeDocument(docKey, sessionId); } void Admin::rmDoc(const std::string& docKey) { std::unique_lock modelLock(_modelMutex); _model.removeDocument(docKey); } void MemoryStats::run() { std::unique_lock modelLock(_admin->getLock()); AdminModel& model = _admin->getModel(); const auto totalMem = _admin->getTotalMemoryUsage(model); if (totalMem != _lastTotalMemory) { Log::trace("Total memory used: " + std::to_string(totalMem)); } _lastTotalMemory = totalMem; model.addMemStats(totalMem); } void CpuStats::run() { //TODO: Implement me //std::unique_lock modelLock(_admin->getLock()); //model.addCpuStats(totalMem); } void Admin::rescheduleMemTimer(unsigned interval) { _memStatsTask->cancel(); _memStatsTaskInterval = interval; _memStatsTask = new MemoryStats(this); _memStatsTimer.schedule(_memStatsTask, _memStatsTaskInterval, _memStatsTaskInterval); Log::info("Memory stats interval changed - New interval: " + std::to_string(interval)); } void Admin::rescheduleCpuTimer(unsigned interval) { _cpuStatsTask->cancel(); _cpuStatsTaskInterval = interval; _cpuStatsTask = new CpuStats(this); _cpuStatsTimer.schedule(_cpuStatsTask, _cpuStatsTaskInterval, _cpuStatsTaskInterval); Log::info("CPU stats interval changed - New interval: " + std::to_string(interval)); } unsigned Admin::getTotalMemoryUsage(AdminModel& model) { unsigned totalMem = Util::getMemoryUsage(_forKitPid); totalMem += model.getTotalMemoryUsage(); totalMem += Util::getMemoryUsage(Poco::Process::id()); return totalMem; } unsigned Admin::getMemStatsInterval() { return _memStatsTaskInterval; } unsigned Admin::getCpuStatsInterval() { return _cpuStatsTaskInterval; } AdminModel& Admin::getModel() { return _model; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */