diff --git a/CMakeLists.txt b/CMakeLists.txt index 45de0b3..9af6665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,12 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) -project(TelegramBotApi VERSION 5.2 LANGUAGES CXX) +if (POLICY CMP0065) + # do not export symbols from executables + # affects compiler checks in project(), so must be set before it + cmake_policy(SET CMP0065 NEW) +endif() + +project(TelegramBotApi VERSION 5.3 LANGUAGES CXX) add_subdirectory(td EXCLUDE_FROM_ALL) diff --git a/build.html b/build.html index facbd02..f80af61 100644 --- a/build.html +++ b/build.html @@ -121,7 +121,7 @@ function onLoad(initial) { function onOsChanged(initial) { var os = document.getElementById('osSelect').value; if (os.includes('Choose ')) { - if (history.state != '') { + if (history.state !== '' && history.state !== null) { history.pushState('', '', 'build.html'); } diff --git a/td b/td index 49fd5cb..1ea79d2 160000 --- a/td +++ b/td @@ -1 +1 @@ -Subproject commit 49fd5cbd874601da8a9ff0083894d7104a4e8680 +Subproject commit 1ea79d273976c22655742d4031ebbe62dba6cd7c diff --git a/telegram-bot-api/Client.cpp b/telegram-bot-api/Client.cpp index 813e8e7..0043075 100644 --- a/telegram-bot-api/Client.cpp +++ b/telegram-bot-api/Client.cpp @@ -25,6 +25,7 @@ #include "td/utils/port/Stat.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Span.h" #include "td/utils/StackAllocator.h" #include "td/utils/Status.h" @@ -75,6 +76,10 @@ void Client::fail_query_with_error(PromisedQueryPtr query, int32 error_code, Sli int32 real_error_code = error_code; Slice real_error_message = error_message; if (error_code < 300 || error_code == 404) { + if (error_code <= 0) { + LOG(ERROR) << "Receive error \"" << real_error_message << "\" with code " << error_code << " from " << *query; + } + error_code = 400; } if (error_code == 400) { @@ -197,6 +202,7 @@ bool Client::init_methods() { methods_.emplace("getme", &Client::process_get_me_query); methods_.emplace("getmycommands", &Client::process_get_my_commands_query); methods_.emplace("setmycommands", &Client::process_set_my_commands_query); + methods_.emplace("deletemycommands", &Client::process_delete_my_commands_query); methods_.emplace("getuserprofilephotos", &Client::process_get_user_profile_photos_query); methods_.emplace("sendmessage", &Client::process_send_message_query); methods_.emplace("sendanimation", &Client::process_send_animation_query); @@ -336,7 +342,7 @@ class Client::JsonDatedFile : public Jsonable { class Client::JsonDatedFiles : public Jsonable { public: - JsonDatedFiles(const td::vector> &files, const Client *client) + JsonDatedFiles(const td::vector> &files, const Client *client) : files_(files), client_(client) { } void store(JsonValueScope *scope) const { @@ -347,7 +353,7 @@ class Client::JsonDatedFiles : public Jsonable { } private: - const td::vector> &files_; + const td::vector> &files_; const Client *client_; }; @@ -1592,7 +1598,7 @@ class Client::JsonInlineKeyboardButton : public Jsonable { case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID: { auto data = get_callback_data(button_->type_); if (!td::check_utf8(data)) { - object("callback_data", td::JsonRawString(data)); + object("callback_data", "INVALID"); } else { object("callback_data", data); } @@ -2398,6 +2404,7 @@ class Client::JsonChatMembers : public Jsonable { continue; } auto user_id = static_cast(member->member_id_.get())->user_id_; + /* bool is_member_bot = member->bot_info_ != nullptr; if (!is_member_bot) { // bot info may be unknown @@ -2406,7 +2413,7 @@ class Client::JsonChatMembers : public Jsonable { is_member_bot = true; } } - /* + if (is_member_bot && user_id != client_->my_id_) { continue; } @@ -3470,13 +3477,9 @@ class Client::TdOnGetMyCommandsCallback : public TdQueryCallback { return fail_query_with_error(std::move(query_), move_object_as(result)); } - CHECK(result->get_id() == td_api::userFullInfo::ID); - auto user_full_info = move_object_as(result); - td::vector> commands; - if (user_full_info->bot_info_ != nullptr) { - commands = std::move(user_full_info->bot_info_->commands_); - } - answer_query(td::json_array(commands, [](auto &command) { return JsonBotCommand(command.get()); }), + CHECK(result->get_id() == td_api::botCommands::ID); + auto bot_commands = move_object_as(result); + answer_query(td::json_array(bot_commands->commands_, [](auto &command) { return JsonBotCommand(command.get()); }), std::move(query_)); } @@ -3960,18 +3963,14 @@ void Client::start_up() { CHECK(r_absolute_dir.is_ok()); absolute_dir_ = r_absolute_dir.move_as_ok(); auto suff = bot_token_with_dc_ + TD_DIR_SLASH; -#if TD_PORT_WINDOWS - for (auto &c : suff) { - if (c == ':') { - c = '~'; + if (!parameters_->allow_colon_in_filenames_) { + for (auto &c : suff) { + if (c == ':') { + c = '~'; + } } } -#endif - dir_ = td::string(".") + TD_DIR_SLASH + suff; - if (absolute_dir_.back() != TD_DIR_SLASH) { - absolute_dir_ += TD_DIR_SLASH; - } - absolute_dir_ += suff; + dir_ = parameters_->working_directory_ + suff; class TdCallback : public td::TdCallback { public: @@ -3995,10 +3994,7 @@ void Client::start_up() { void Client::send(PromisedQueryPtr query) { if (!query->is_internal()) { - send_closure( - stat_actor_, &BotStatActor::add_event, - ServerBotStat::Request{query->query_size(), query->file_count(), query->files_size(), query->files_max_size()}, - td::Time::now()); + query->set_stat_actor(stat_actor_); } cmd_queue_.emplace(std::move(query)); loop(); @@ -4174,6 +4170,10 @@ void Client::check_chat_access(int64 chat_id, AccessRights access_rights, const break; } case ChatInfo::Type::Group: { + if (access_rights == AccessRights::ReadMembers) { // member list is inaccessible in deactivated groups + need_write_access = true; + need_edit_access = true; + } auto group_info = get_group_info(chat_info->group_id); CHECK(group_info != nullptr); if (!group_info->is_active && need_write_access) { @@ -4184,9 +4184,7 @@ void Client::check_chat_access(int64 chat_id, AccessRights access_rights, const return fail_query(400, "Bad Request: group chat was upgraded to a supergroup chat", std::move(query), std::move(parameters)); } else { - LOG(WARNING) << "Group chat " << chat_info->group_id << " with " << group_info->member_count - << " members and title \"" << chat_info->title << "\" is deactivated"; - return fail_query(400, "Bad Request: group chat was deactivated", std::move(query)); + return fail_query(403, "Forbidden: the group chat was deleted", std::move(query)); } } if (group_info->is_active && group_info->kicked && need_edit_access) { @@ -4247,6 +4245,36 @@ void Client::check_chat(Slice chat_id_str, AccessRights access_rights, PromisedQ std::move(on_success))); } +template +void Client::check_bot_command_scope(BotCommandScope &&scope, PromisedQueryPtr query, OnSuccess on_success) { + CHECK(scope.scope_ != nullptr); + if (scope.chat_id_.empty()) { + on_success(std::move(scope.scope_), std::move(query)); + return; + } + check_chat(scope.chat_id_, AccessRights::ReadMembers, std::move(query), + [this, user_id = scope.user_id_, scope_id = scope.scope_->get_id(), on_success = std::move(on_success)]( + int64 chat_id, PromisedQueryPtr query) mutable { + switch (scope_id) { + case td_api::botCommandScopeChat::ID: + on_success(make_object(chat_id), std::move(query)); + break; + case td_api::botCommandScopeChatAdministrators::ID: + on_success(make_object(chat_id), std::move(query)); + break; + case td_api::botCommandScopeChatMember::ID: + check_user_no_fail( + user_id, std::move(query), + [chat_id, user_id, on_success = std::move(on_success)](PromisedQueryPtr query) mutable { + on_success(make_object(chat_id, user_id), std::move(query)); + }); + break; + default: + UNREACHABLE(); + } + }); +} + template void Client::check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success) { if (file_id.empty()) { @@ -4577,6 +4605,8 @@ void Client::on_update_authorization_state() { case td_api::authorizationStateWaitEncryptionKey::ID: return send_request(make_object(), std::make_unique(this)); case td_api::authorizationStateWaitPhoneNumber::ID: + send_request(make_object("online", make_object(true)), + std::make_unique()); if (is_user_) { waiting_for_auth_input_ = true; return loop(); @@ -5009,8 +5039,8 @@ void Client::on_closed() { td::ActorId parent_; void start_up() override { - CHECK(!dir_.empty()); - CHECK(dir_[0] == '.'); + CHECK(dir_.size() >= 24); + CHECK(dir_.back() == TD_DIR_SLASH); td::rmrf(dir_).ignore(); stop(); } @@ -5214,6 +5244,7 @@ td::Result> Client::get_reply_markup(con td::Result> Client::get_reply_markup(JsonValue &&value) { td::vector>> rows; td::vector>> inline_rows; + Slice input_field_placeholder; bool resize = false; bool one_time = false; bool remove = false; @@ -5289,18 +5320,24 @@ td::Result> Client::get_reply_markup(Jso return Status::Error(400, "Field \"force_reply\" of the reply markup must be of the type Boolean"); } force_reply = field_value.second.get_boolean(); + } else if (field_value.first == "input_field_placeholder") { + if (field_value.second.type() != JsonValue::Type::String) { + return Status::Error(400, "Field \"input_field_placeholder\" of the reply markup must be of the type String"); + } + input_field_placeholder = field_value.second.get_string(); } } object_ptr result; if (!rows.empty()) { - result = make_object(std::move(rows), resize, one_time, is_personal); + result = make_object(std::move(rows), resize, one_time, is_personal, + input_field_placeholder.str()); } else if (!inline_rows.empty()) { result = make_object(std::move(inline_rows)); } else if (remove) { result = make_object(is_personal); } else if (force_reply) { - result = make_object(is_personal); + result = make_object(is_personal, input_field_placeholder.str()); } if (result == nullptr || result->get_id() != td_api::replyMarkupInlineKeyboard::ID) { unresolved_bot_usernames_.clear(); @@ -5970,6 +6007,69 @@ td::Result> Client::get_inlin return Status::Error(400, PSLICE() << "type \"" << type << "\" is unsupported for the inline query result"); } +td::Result Client::get_bot_command_scope(JsonValue &&value) { + if (value.type() != JsonValue::Type::Object) { + return Status::Error(400, "BotCommandScope must be an Object"); + } + + auto &object = value.get_object(); + + TRY_RESULT(type, get_json_object_string_field(object, "type", false)); + if (type == "default") { + return BotCommandScope(make_object()); + } + if (type == "all_private_chats") { + return BotCommandScope(make_object()); + } + if (type == "all_group_chats") { + return BotCommandScope(make_object()); + } + if (type == "all_chat_administrators") { + return BotCommandScope(make_object()); + } + if (type != "chat" && type != "chat_administrators" && type != "chat_member") { + return Status::Error(400, "Unsupported type specified"); + } + + TRY_RESULT(chat_id, get_json_object_string_field(object, "chat_id", false)); + if (chat_id.empty()) { + return Status::Error(400, "Empty chat_id specified"); + } + if (type == "chat") { + return BotCommandScope(make_object(0), std::move(chat_id)); + } + if (type == "chat_administrators") { + return BotCommandScope(make_object(0), std::move(chat_id)); + } + + TRY_RESULT(user_id, get_json_object_int_field(object, "user_id", false)); + if (user_id <= 0) { + return Status::Error(400, "Invalid user_id specified"); + } + CHECK(type == "chat_member"); + return BotCommandScope(make_object(0, user_id), std::move(chat_id), user_id); +} + +td::Result Client::get_bot_command_scope(const Query *query) { + auto scope = query->arg("scope"); + if (scope.empty()) { + return BotCommandScope(make_object()); + } + + LOG(INFO) << "Parsing JSON object: " << scope; + auto r_value = json_decode(scope); + if (r_value.is_error()) { + LOG(INFO) << "Can't parse JSON object: " << r_value.error(); + return Status::Error(400, "Can't parse BotCommandScope JSON object"); + } + + auto r_scope = get_bot_command_scope(r_value.move_as_ok()); + if (r_scope.is_error()) { + return Status::Error(400, PSLICE() << "Can't parse BotCommandScope: " << r_scope.error().message()); + } + return r_scope.move_as_ok(); +} + td::Result> Client::get_bot_command(JsonValue &&value) { if (value.type() != JsonValue::Type::Object) { return Status::Error(400, "expected an Object"); @@ -6604,11 +6704,8 @@ td::Result> Client::get_poll_options(const Query *query) td::int32 Client::get_integer_arg(const Query *query, Slice field_name, int32 default_value, int32 min_value, int32 max_value) { auto s_arg = query->arg(field_name); - if (s_arg.empty()) { - return default_value; - } - - return td::clamp(td::to_integer(s_arg), min_value, max_value); + auto value = s_arg.empty() ? default_value : td::to_integer(s_arg); + return td::clamp(value, min_value, max_value); } td::Result Client::get_required_string_arg(const Query *query, Slice field_name) { @@ -6922,16 +7019,42 @@ td::Status Client::process_get_me_query(PromisedQueryPtr &query) { } td::Status Client::process_get_my_commands_query(PromisedQueryPtr &query) { - send_request(make_object(my_id_), - std::make_unique(std::move(query))); + TRY_RESULT(scope, get_bot_command_scope(query.get())); + + check_bot_command_scope(std::move(scope), std::move(query), + [this](object_ptr &&scope, PromisedQueryPtr query) mutable { + auto language_code = query->arg("language_code").str(); + send_request(make_object(std::move(scope), language_code), + std::make_unique(std::move(query))); + }); return Status::OK(); } td::Status Client::process_set_my_commands_query(PromisedQueryPtr &query) { CHECK_IS_BOT(); TRY_RESULT(bot_commands, get_bot_commands(query.get())); - send_request(make_object(std::move(bot_commands)), - std::make_unique(std::move(query))); + TRY_RESULT(scope, get_bot_command_scope(query.get())); + + check_bot_command_scope( + std::move(scope), std::move(query), + [this, bot_commands = std::move(bot_commands)](object_ptr &&scope, + PromisedQueryPtr query) mutable { + auto language_code = query->arg("language_code").str(); + send_request(make_object(std::move(scope), language_code, std::move(bot_commands)), + std::make_unique(std::move(query))); + }); + return Status::OK(); +} + +td::Status Client::process_delete_my_commands_query(PromisedQueryPtr &query) { + TRY_RESULT(scope, get_bot_command_scope(query.get())); + + check_bot_command_scope(std::move(scope), std::move(query), + [this](object_ptr &&scope, PromisedQueryPtr query) mutable { + auto language_code = query->arg("language_code").str(); + send_request(make_object(std::move(scope), language_code), + std::make_unique(std::move(query))); + }); return Status::OK(); } @@ -7258,7 +7381,7 @@ td::Status Client::process_copy_message_query(PromisedQueryPtr &query) { TRY_RESULT(from_chat_id, get_required_string_arg(query.get(), "from_chat_id")); auto message_id = get_message_id(query.get()); bool replace_caption = query->has_arg("caption"); - td_api::object_ptr caption; + object_ptr caption; if (replace_caption) { TRY_RESULT_ASSIGN(caption, get_caption(query.get())); } @@ -7887,31 +8010,29 @@ td::Status Client::process_get_chat_member_query(PromisedQueryPtr &query) { auto chat_id = query->arg("chat_id"); TRY_RESULT(user_id, get_user_id(query.get())); - check_chat(chat_id, AccessRights::Read, std::move(query), [this, user_id](int64 chat_id, PromisedQueryPtr query) { - get_chat_member(chat_id, user_id, std::move(query), - [this, chat_type = get_chat_type(chat_id)](td_api::object_ptr &&chat_member, - PromisedQueryPtr query) { - answer_query(JsonChatMember(chat_member.get(), chat_type, this), std::move(query)); - }); - }); + check_chat(chat_id, AccessRights::ReadMembers, std::move(query), + [this, user_id](int64 chat_id, PromisedQueryPtr query) { + get_chat_member(chat_id, user_id, std::move(query), + [this, chat_type = get_chat_type(chat_id)](object_ptr &&chat_member, + PromisedQueryPtr query) { + answer_query(JsonChatMember(chat_member.get(), chat_type, this), std::move(query)); + }); + }); return Status::OK(); } td::Status Client::process_get_chat_administrators_query(PromisedQueryPtr &query) { auto chat_id = query->arg("chat_id"); - check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) { + check_chat(chat_id, AccessRights::ReadMembers, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) { auto chat_info = get_chat(chat_id); CHECK(chat_info != nullptr); switch (chat_info->type) { case ChatInfo::Type::Private: return fail_query(400, "Bad Request: there are no administrators in the private chat", std::move(query)); - case ChatInfo::Type::Group: { - auto group_info = get_group_info(chat_info->group_id); - CHECK(group_info != nullptr); + case ChatInfo::Type::Group: return send_request(make_object(chat_info->group_id), std::make_unique(this, true, std::move(query))); - } case ChatInfo::Type::Supergroup: return send_request( make_object( @@ -7928,7 +8049,7 @@ td::Status Client::process_get_chat_administrators_query(PromisedQueryPtr &query td::Status Client::process_get_chat_member_count_query(PromisedQueryPtr &query) { auto chat_id = query->arg("chat_id"); - check_chat(chat_id, AccessRights::Read, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) { + check_chat(chat_id, AccessRights::ReadMembers, std::move(query), [this](int64 chat_id, PromisedQueryPtr query) { auto chat_info = get_chat(chat_id); CHECK(chat_info != nullptr); switch (chat_info->type) { @@ -7937,9 +8058,6 @@ td::Status Client::process_get_chat_member_count_query(PromisedQueryPtr &query) case ChatInfo::Type::Group: { auto group_info = get_group_info(chat_info->group_id); CHECK(group_info != nullptr); - if (group_info->member_count == 0) { - return fail_query(403, "Forbidden: bot is not a member of the group chat", std::move(query)); - } return answer_query(td::VirtuallyJsonableInt(group_info->member_count), std::move(query)); } case ChatInfo::Type::Supergroup: @@ -8016,8 +8134,8 @@ td::Status Client::process_promote_chat_member_query(PromisedQueryPtr &query) { get_chat_member( chat_id, user_id, std::move(query), - [this, chat_id, user_id, status = std::move(status)]( - td_api::object_ptr &&chat_member, PromisedQueryPtr query) mutable { + [this, chat_id, user_id, status = std::move(status)](object_ptr &&chat_member, + PromisedQueryPtr query) mutable { if (chat_member->status_->get_id() == td_api::chatMemberStatusAdministrator::ID) { auto administrator = static_cast(chat_member->status_.get()); @@ -8044,7 +8162,7 @@ td::Status Client::process_set_chat_administrator_custom_title_query(PromisedQue get_chat_member( chat_id, user_id, std::move(query), - [this, chat_id, user_id](td_api::object_ptr &&chat_member, PromisedQueryPtr query) { + [this, chat_id, user_id](object_ptr &&chat_member, PromisedQueryPtr query) { if (chat_member->status_->get_id() == td_api::chatMemberStatusCreator::ID) { return fail_query(400, "Bad Request: only creator can edit their custom title", std::move(query)); } @@ -8102,7 +8220,7 @@ td::Status Client::process_restrict_chat_member_query(PromisedQueryPtr &query) { get_chat_member( chat_id, user_id, std::move(query), [this, chat_id, user_id, until_date, is_legacy, permissions = std::move(permissions)]( - td_api::object_ptr &&chat_member, PromisedQueryPtr query) mutable { + object_ptr &&chat_member, PromisedQueryPtr query) mutable { if (is_legacy && chat_member->status_->get_id() == td_api::chatMemberStatusRestricted::ID) { auto restricted = static_cast(chat_member->status_.get()); @@ -8138,18 +8256,18 @@ td::Status Client::process_unban_chat_member_query(PromisedQueryPtr &query) { } if (only_if_banned) { - get_chat_member(chat_id, user_id, std::move(query), - [this, chat_id, user_id](td_api::object_ptr &&chat_member, - PromisedQueryPtr query) { - if (chat_member->status_->get_id() != td_api::chatMemberStatusBanned::ID) { - return answer_query(td::JsonTrue(), std::move(query)); - } + get_chat_member( + chat_id, user_id, std::move(query), + [this, chat_id, user_id](object_ptr &&chat_member, PromisedQueryPtr query) { + if (chat_member->status_->get_id() != td_api::chatMemberStatusBanned::ID) { + return answer_query(td::JsonTrue(), std::move(query)); + } - send_request(make_object( - chat_id, td_api::make_object(user_id), - make_object()), - std::make_unique(std::move(query))); - }); + send_request(make_object( + chat_id, td_api::make_object(user_id), + make_object()), + std::make_unique(std::move(query))); + }); } else { check_user_no_fail(user_id, std::move(query), [this, chat_id, user_id](PromisedQueryPtr query) { send_request(make_object( @@ -8181,7 +8299,8 @@ td::Status Client::process_upload_sticker_file_query(PromisedQueryPtr &query) { check_user(user_id, std::move(query), [this, user_id, png_sticker = std::move(png_sticker)](PromisedQueryPtr query) mutable { - send_request(make_object(user_id, std::move(png_sticker)), + send_request(make_object( + user_id, make_object(std::move(png_sticker), "", nullptr)), std::make_unique(this, std::move(query))); }); return Status::OK(); @@ -8198,7 +8317,7 @@ td::Status Client::process_create_new_sticker_set_query(PromisedQueryPtr &query) check_user(user_id, std::move(query), [this, user_id, title, name, is_masks, stickers = std::move(stickers)](PromisedQueryPtr query) mutable { send_request(make_object(user_id, title.str(), name.str(), is_masks, - std::move(stickers)), + std::move(stickers), PSTRING() << "bot" << my_id_), std::make_unique(this, false, std::move(query))); }); return Status::OK(); @@ -8317,7 +8436,7 @@ td::Status Client::process_set_webhook_query(PromisedQueryPtr &query) { } auto now = td::Time::now_cached(); - if (!new_url.empty()) { + if (!new_url.empty() && !query->is_internal()) { if (now < next_allowed_set_webhook_time_) { query->set_retry_after_error(1); return Status::OK(); @@ -8808,7 +8927,7 @@ void Client::do_get_file(object_ptr file, PromisedQueryPtr query) auto file_id = file->id_; file_download_listeners_[file_id].push_back(std::move(query)); if (file->local_->is_downloading_completed_) { - Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true); + Slice relative_path = td::PathView::relative(file->local_->path_, dir_, true); if (!relative_path.empty()) { auto r_stat = td::stat(file->local_->path_); if (r_stat.is_ok() && r_stat.ok().is_reg_ && r_stat.ok().size_ == file->size_) { @@ -9463,7 +9582,7 @@ void Client::json_store_file(td::JsonObjectScope &object, const td_api::file *fi object("file_path", td::JsonRawString(file->local_->path_)); } } else { - Slice relative_path = td::PathView::relative(file->local_->path_, absolute_dir_, true); + Slice relative_path = td::PathView::relative(file->local_->path_, dir_, true); if (!relative_path.empty() && (parameters_->local_mode_ || parameters_->no_file_limit_ || file->local_->downloaded_size_ <= MAX_DOWNLOAD_FILE_SIZE)) { object("file_path", relative_path); } diff --git a/telegram-bot-api/Client.h b/telegram-bot-api/Client.h index 89c853c..93023da 100644 --- a/telegram-bot-api/Client.h +++ b/telegram-bot-api/Client.h @@ -237,8 +237,9 @@ class Client : public WebhookActor::Callback { struct UserInfo; struct ChatInfo; + struct BotCommandScope; - enum class AccessRights { Read, Edit, Write }; + enum class AccessRights { Read, ReadMembers, Edit, Write }; template class TdOnCheckUserCallback; @@ -288,6 +289,9 @@ class Client : public WebhookActor::Callback { void enable_internet_connection(PromisedQueryPtr query); + template + void check_bot_command_scope(BotCommandScope &&scope, PromisedQueryPtr query, OnSuccess on_success); + template void check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success); @@ -370,6 +374,21 @@ class Client : public WebhookActor::Callback { td::Result>> get_inline_query_results(td::JsonValue &&value); + struct BotCommandScope { + object_ptr scope_; + td::string chat_id_; + td::int32 user_id_ = 0; + + explicit BotCommandScope(object_ptr scope, td::string chat_id = td::string(), + td::int32 user_id = 0) + : scope_(std::move(scope)), chat_id_(std::move(chat_id)), user_id_(user_id) { + } + }; + + static td::Result get_bot_command_scope(const Query *query); + + static td::Result get_bot_command_scope(td::JsonValue &&value); + static td::Result> get_bot_command(td::JsonValue &&value); static td::Result>> get_bot_commands(const Query *query); @@ -445,7 +464,7 @@ class Client : public WebhookActor::Callback { static td::Result get_user_id(const Query *query, Slice field_name = Slice("user_id")); int64 extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id, bool *is_reply_to_message_deleted); - + // start custom helper methods static td::Result> get_message_scheduling_state(const Query *query); @@ -474,6 +493,7 @@ class Client : public WebhookActor::Callback { Status process_get_me_query(PromisedQueryPtr &query); Status process_get_my_commands_query(PromisedQueryPtr &query); Status process_set_my_commands_query(PromisedQueryPtr &query); + Status process_delete_my_commands_query(PromisedQueryPtr &query); Status process_get_user_profile_photos_query(PromisedQueryPtr &query); Status process_send_message_query(PromisedQueryPtr &query); Status process_send_animation_query(PromisedQueryPtr &query); @@ -1017,7 +1037,6 @@ class Client : public WebhookActor::Callback { int64 current_bot_resolve_query_id_ = 1; td::string dir_; - td::string absolute_dir_; td::ActorOwn td_client_; td::ActorContext context_; std::queue cmd_queue_; diff --git a/telegram-bot-api/ClientManager.cpp b/telegram-bot-api/ClientManager.cpp index 4fe7edd..bf0a5de 100644 --- a/telegram-bot-api/ClientManager.cpp +++ b/telegram-bot-api/ClientManager.cpp @@ -31,6 +31,7 @@ #include "td/utils/port/IPAddress.h" #include "td/utils/port/Stat.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/StackAllocator.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" @@ -94,29 +95,24 @@ void ClientManager::send(PromisedQueryPtr query) { } auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, td::ActorOwn()}); auto *client_info = clients_.get(id); - auto stat_actor = client_info->stat_.actor_id(&client_info->stat_); - auto client_id = td::create_actor( - PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_user(), - query->is_test_dc(), get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor)); + client_info->client_ = + td::create_actor(PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_user(), + query->is_test_dc(), get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, + client_info->stat_.actor_id(&client_info->stat_)); auto method = query->method(); if (method != "deletewebhook" && method != "setwebhook") { auto webhook_info = parameters_->shared_data_->webhook_db_->get(bot_token_with_dc); if (!webhook_info.empty()) { - send_closure(client_id, &Client::send, - get_webhook_restore_query(bot_token_with_dc, query->is_user(), webhook_info, parameters_->shared_data_)); + send_closure(client_info->client_, &Client::send, + get_webhook_restore_query(bot_token_with_dc, query->is_user() webhook_info, parameters_->shared_data_)); } } - clients_.get(id)->client_ = std::move(client_id); std::tie(id_it, std::ignore) = token_to_id_.emplace(token, id); } - auto *client_info = clients_.get(id_it->second); - - if (!query->is_internal()) { - query->set_stat_actor(client_info->stat_.actor_id(&client_info->stat_)); - } - send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed + send_closure(clients_.get(id_it->second)->client_, &Client::send, + std::move(query)); // will send 429 if the client is already closed } void ClientManager::user_login(PromisedQueryPtr query) { @@ -407,7 +403,7 @@ void ClientManager::start_up() { td::vector failed_to_replay_log_event_ids; td::int64 loaded_event_count = 0; binlog - ->init("tqueue.binlog", + ->init(parameters_->working_directory_ + "tqueue.binlog", [&](const td::BinlogEvent &event) { if (tqueue_binlog->replay(event, *tqueue).is_error()) { failed_to_replay_log_event_ids.push_back(event.id_); @@ -436,7 +432,8 @@ void ClientManager::start_up() { // init webhook_db and user_db auto concurrent_webhook_db = td::make_unique>(); - auto status = concurrent_webhook_db->init("webhooks_db.binlog", td::DbKey::empty(), scheduler_id); + auto status = concurrent_webhook_db->init(parameters_->working_directory_ + "webhooks_db.binlog", td::DbKey::empty(), + scheduler_id); LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status.error(); parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db); @@ -510,8 +507,7 @@ PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, bool const auto method = add_string("setwebhook"); auto query = std::make_unique(std::move(containers), token, is_user, is_test_dc, method, std::move(args), td::vector>(), - td::vector(), std::move(shared_data), td::IPAddress()); - query->set_internal(true); + td::vector(), std::move(shared_data), td::IPAddress(), true); return PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor>())); } diff --git a/telegram-bot-api/ClientParameters.h b/telegram-bot-api/ClientParameters.h index d5dd569..d3bffe4 100644 --- a/telegram-bot-api/ClientParameters.h +++ b/telegram-bot-api/ClientParameters.h @@ -55,6 +55,9 @@ struct SharedData { }; struct ClientParameters { + td::string working_directory_; + bool allow_colon_in_filenames_ = true; + bool local_mode_ = false; bool allow_http_ = false; bool use_relative_path_ = false; diff --git a/telegram-bot-api/HttpConnection.cpp b/telegram-bot-api/HttpConnection.cpp index 7de8252..499cabf 100644 --- a/telegram-bot-api/HttpConnection.cpp +++ b/telegram-bot-api/HttpConnection.cpp @@ -14,6 +14,7 @@ #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/Parser.h" +#include "td/utils/SliceBuilder.h" namespace telegram_bot_api { @@ -60,7 +61,7 @@ void HttpConnection::handle(td::unique_ptr http_query, auto query = std::make_unique(std::move(http_query->container_), token, is_user, is_test_dc, method, std::move(http_query->args_), std::move(http_query->headers_), - std::move(http_query->files_), shared_data_, http_query->peer_address_); + std::move(http_query->files_), shared_data_, http_query->peer_address_, false); td::PromiseActor> promise; td::FutureActor> future; diff --git a/telegram-bot-api/HttpServer.h b/telegram-bot-api/HttpServer.h index 35bf747..976836c 100644 --- a/telegram-bot-api/HttpServer.h +++ b/telegram-bot-api/HttpServer.h @@ -15,6 +15,7 @@ #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/port/SocketFd.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include diff --git a/telegram-bot-api/Query.cpp b/telegram-bot-api/Query.cpp index bdae447..f53c93d 100644 --- a/telegram-bot-api/Query.cpp +++ b/telegram-bot-api/Query.cpp @@ -14,6 +14,7 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/IPAddress.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include @@ -25,7 +26,7 @@ std::unordered_map> empty_par Query::Query(td::vector &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method, td::vector> &&args, td::vector> &&headers, td::vector &&files, - std::shared_ptr shared_data, const td::IPAddress &peer_address) + std::shared_ptr shared_data, const td::IPAddress &peer_address, bool is_internal) : state_(State::Query) , shared_data_(shared_data) , peer_address_(peer_address) @@ -36,7 +37,8 @@ Query::Query(td::vector &&container, td::Slice token, bool is_u , method_(method) , args_(std::move(args)) , headers_(std::move(headers)) - , files_(std::move(files)) { + , files_(std::move(files)) + , is_internal_(is_internal) { if (method_.empty()) { method_ = arg("method"); } @@ -67,6 +69,11 @@ td::int64 Query::files_max_size() const { [](td::int64 acc, const td::HttpFile &file) { return td::max(acc, file.size); }); } +void Query::set_stat_actor(td::ActorId stat_actor) { + stat_actor_ = stat_actor; + send_request_stat(); +} + void Query::set_ok(td::BufferSlice result) { CHECK(state_ == State::Query); LOG(INFO) << "QUERY: got ok " << td::tag("ptr", this) << td::tag("text", result.as_slice()); @@ -109,12 +116,26 @@ td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) { return sb; } -void Query::send_response_stat() { +void Query::send_request_stat() const { + if (stat_actor_.empty()) { + return; + } + send_closure(stat_actor_, &BotStatActor::add_event, + ServerBotStat::Request{query_size(), file_count(), files_size(), files_max_size()}, td::Time::now()); +} + +void Query::send_response_stat() const { + auto now = td::Time::now(); + if (now - start_timestamp_ >= 100.0) { + LOG(WARNING) << "Answer too old query with code " << http_status_code_ << " and answer size " << answer_.size() + << ": " << *this; + } + if (stat_actor_.empty()) { return; } send_closure(stat_actor_, &BotStatActor::add_event, - ServerBotStat::Response{is_ok(), answer().size()}, td::Time::now()); + ServerBotStat::Response{state_ == State::OK, answer_.size()}, now); } } // namespace telegram_bot_api diff --git a/telegram-bot-api/Query.h b/telegram-bot-api/Query.h index 5491619..3a8e1dc 100644 --- a/telegram-bot-api/Query.h +++ b/telegram-bot-api/Query.h @@ -76,17 +76,6 @@ class Query : public td::ListNode { return peer_address_; } - // for stats - td::int32 file_count() const { - return static_cast(files_.size()); - } - - td::int64 query_size() const; - - td::int64 files_size() const; - - td::int64 files_max_size() const; - td::BufferSlice &answer() { return answer_; } @@ -109,26 +98,14 @@ class Query : public td::ListNode { return state_ != State::Query; } - bool is_error() const { - return state_ == State::Error; - } - - bool is_ok() const { - return state_ == State::OK; - } - bool is_internal() const { return is_internal_; } - void set_internal(bool is_internal) { - is_internal_ = is_internal; - } - Query(td::vector &&container, td::Slice token, bool is_user, bool is_test_dc, td::MutableSlice method, td::vector> &&args, td::vector> &&headers, td::vector &&files, - std::shared_ptr shared_data, const td::IPAddress &peer_address); + std::shared_ptr shared_data, const td::IPAddress &peer_address, bool is_internal); Query(const Query &) = delete; Query &operator=(const Query &) = delete; Query(Query &&) = delete; @@ -143,10 +120,7 @@ class Query : public td::ListNode { return start_timestamp_; } - void set_stat_actor(td::ActorId stat_actor) { - stat_actor_ = stat_actor; - } - void send_response_stat(); + void set_stat_actor(td::ActorId stat_actor); private: State state_; @@ -170,6 +144,21 @@ class Query : public td::ListNode { td::BufferSlice answer_; int http_status_code_ = 0; int retry_after_ = 0; + + // for stats + td::int32 file_count() const { + return static_cast(files_.size()); + } + + td::int64 query_size() const; + + td::int64 files_size() const; + + td::int64 files_max_size() const; + + void send_request_stat() const; + + void send_response_stat() const; }; td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query); diff --git a/telegram-bot-api/Stats.cpp b/telegram-bot-api/Stats.cpp index 86ddae6..fd6a43c 100644 --- a/telegram-bot-api/Stats.cpp +++ b/telegram-bot-api/Stats.cpp @@ -7,8 +7,8 @@ #include "telegram-bot-api/Stats.h" #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/port/thread.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" namespace telegram_bot_api { diff --git a/telegram-bot-api/WebhookActor.cpp b/telegram-bot-api/WebhookActor.cpp index 9a2cf34..33ca271 100644 --- a/telegram-bot-api/WebhookActor.cpp +++ b/telegram-bot-api/WebhookActor.cpp @@ -28,6 +28,7 @@ #include "td/utils/port/SocketFd.h" #include "td/utils/Random.h" #include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Span.h" #include "td/utils/Time.h" @@ -165,7 +166,7 @@ td::Status WebhookActor::create_connection() { Callback &operator=(Callback &&) = delete; ~Callback() { if (!actor_.empty()) { - send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Cancelled"), id_); + send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Canceled"), id_); } } void on_connected() override { @@ -585,10 +586,10 @@ void WebhookActor::handle(td::unique_ptr response) { if (!method.empty() && method != "deletewebhook" && method != "setwebhook" && method != "close" && method != "logout" && !td::begins_with(method, "get")) { VLOG(webhook) << "Receive request " << method << " in response to webhook"; - auto query = - std::make_unique(std::move(response->container_), td::MutableSlice(), false, false, td::MutableSlice(), - std::move(response->args_), std::move(response->headers_), - std::move(response->files_), parameters_->shared_data_, response->peer_address_); + auto query = std::make_unique(std::move(response->container_), td::MutableSlice(), false, false, + td::MutableSlice(), std::move(response->args_), + std::move(response->headers_), std::move(response->files_), + parameters_->shared_data_, response->peer_address_, false); auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor>())); send_closure(callback_, &Callback::send, std::move(promised_query)); diff --git a/telegram-bot-api/telegram-bot-api.cpp b/telegram-bot-api/telegram-bot-api.cpp index 6ecd512..7fc6a34 100644 --- a/telegram-bot-api/telegram-bot-api.cpp +++ b/telegram-bot-api/telegram-bot-api.cpp @@ -24,8 +24,8 @@ #include "td/actor/ConcurrentScheduler.h" #include "td/actor/PromiseFuture.h" -#include "td/utils/algorithm.h" #include "td/utils/buffer.h" +#include "td/utils/CombinedLog.h" #include "td/utils/common.h" #include "td/utils/crypto.h" #include "td/utils/ExitGuard.h" @@ -36,15 +36,19 @@ #include "td/utils/MemoryLog.h" #include "td/utils/misc.h" #include "td/utils/OptionParser.h" +#include "td/utils/PathView.h" #include "td/utils/port/IPAddress.h" #include "td/utils/port/path.h" #include "td/utils/port/rlimit.h" #include "td/utils/port/signals.h" #include "td/utils/port/stacktrace.h" +#include "td/utils/port/Stat.h" #include "td/utils/port/user.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/Time.h" +#include "td/utils/TsLog.h" #include "memprof/memprof.h" @@ -56,10 +60,10 @@ namespace telegram_bot_api { -static std::atomic_flag need_rotate_log; +static std::atomic_flag need_reopen_log; -static void rotate_log_signal_handler(int sig) { - need_rotate_log.clear(); +static void after_log_rotation_signal_handler(int sig) { + need_reopen_log.clear(); } static std::atomic_flag need_quit; @@ -106,11 +110,65 @@ static void sigsegv_signal_handler(int signum, void *addr) { fail_signal_handler(signum); } +static void dump_statistics(const std::shared_ptr &shared_data, + const std::shared_ptr &net_query_stats) { + if (is_memprof_on()) { + LOG(WARNING) << "Memory dump:"; + td::vector v; + dump_alloc([&](const AllocInfo &info) { v.push_back(info); }); + std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; }); + size_t total_size = 0; + size_t other_size = 0; + int count = 0; + for (auto &info : v) { + if (count++ < 50) { + LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace); + } else { + other_size += info.size; + } + total_size += info.size; + } + LOG(WARNING) << td::tag("other", td::format::as_size(other_size)); + LOG(WARNING) << td::tag("total size", td::format::as_size(total_size)); + LOG(WARNING) << td::tag("total traces", get_ht_size()); + LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate()); + } + auto r_mem_stat = td::mem_stat(); + if (r_mem_stat.is_ok()) { + auto mem_stat = r_mem_stat.move_as_ok(); + LOG(WARNING) << td::tag("rss", td::format::as_size(mem_stat.resident_size_)); + LOG(WARNING) << td::tag("vm", td::format::as_size(mem_stat.virtual_size_)); + LOG(WARNING) << td::tag("rss_peak", td::format::as_size(mem_stat.resident_size_peak_)); + LOG(WARNING) << td::tag("vm_peak", td::format::as_size(mem_stat.virtual_size_peak_)); + } + LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem())); + LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size())); + + auto query_count = shared_data->query_count_.load(); + LOG(WARNING) << td::tag("pending queries", query_count); + + td::uint64 i = 0; + bool was_gap = false; + for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) { + if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) { + if (was_gap) { + LOG(WARNING) << "..."; + was_gap = false; + } + LOG(WARNING) << static_cast(*cur); + } else { + was_gap = true; + } + } + + td::dump_pending_network_queries(*net_query_stats); +} + int main(int argc, char *argv[]) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL)); td::ExitGuard exit_guard; - need_rotate_log.test_and_set(); + need_reopen_log.test_and_set(); need_quit.test_and_set(); need_change_verbosity_level.test_and_set(); need_dump_log.test_and_set(); @@ -118,7 +176,7 @@ int main(int argc, char *argv[]) { td::Stacktrace::init(); td::setup_signals_alt_stack().ensure(); - td::set_signal_handler(td::SignalType::User, rotate_log_signal_handler).ensure(); + td::set_signal_handler(td::SignalType::User, after_log_rotation_signal_handler).ensure(); td::ignore_signal(td::SignalType::HangUp).ensure(); td::ignore_signal(td::SignalType::Pipe).ensure(); td::set_signal_handler(td::SignalType::Quit, quit_signal_handler).ensure(); @@ -134,7 +192,7 @@ int main(int argc, char *argv[]) { auto start_time = td::Time::now(); auto shared_data = std::make_shared(); auto parameters = std::make_unique(); - parameters->version_ = "5.2"; + parameters->version_ = "5.3"; parameters->shared_data_ = shared_data; parameters->start_time_ = start_time; auto net_query_stats = td::create_net_query_stats(); @@ -142,6 +200,7 @@ int main(int argc, char *argv[]) { td::OptionParser options; bool need_print_usage = false; + bool need_print_version = false; int http_port = 8081; int http_stat_port = 0; td::string http_ip_address = "0.0.0.0"; @@ -150,7 +209,7 @@ int main(int argc, char *argv[]) { int default_verbosity_level = 0; int memory_verbosity_level = VERBOSITY_NAME(INFO); td::int64 log_max_file_size = 2000000000; - td::string working_directory; + td::string working_directory = PSTRING() << "." << TD_DIR_SLASH; td::string temporary_directory; td::string username; td::string groupname; @@ -173,7 +232,8 @@ int main(int argc, char *argv[]) { options.set_usage(td::Slice(argv[0]), "--api-id= --api-hash= [--local] [OPTION]..."); options.set_description("Telegram Bot API server"); options.add_option('h', "help", "display this help text and exit", [&] { need_print_usage = true; }); - options.add_option('\0', "local", "allow the Bot API server to serve local requests and disables the file limits", + options.add_option('\0', "version", "display version number and exit", [&] { need_print_version = true; }); + options.add_option('\0', "local", "allow the Bot API server to serve local requests", [&] { parameters->local_mode_ = true; }); options.add_option('\0', "no-file-limit", "disable the file limits", [&] { parameters->no_file_limit_ = true; }); @@ -288,77 +348,17 @@ int main(int argc, char *argv[]) { LOG(PLAIN) << options; return 0; } + if (need_print_version) { + LOG(PLAIN) << "Bot API " << parameters->version_; + return 0; + } if (r_non_options.is_error()) { - LOG(PLAIN) << argv[0] << ": " << r_non_options.error(); + LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message(); LOG(PLAIN) << options; return 1; } - class CombineLog : public td::LogInterface { - public: - void append(td::CSlice slice, int log_level) override { - if (first_ && log_level <= first_verbosity_level_) { - if (log_level == VERBOSITY_NAME(FATAL) && second_ && VERBOSITY_NAME(FATAL) <= second_verbosity_level_) { - second_->append(slice, VERBOSITY_NAME(ERROR)); - } - first_->append(slice, log_level); - } - if (second_ && log_level <= second_verbosity_level_) { - second_->append(slice, log_level); - } - } - - void set_first(LogInterface *first) { - first_ = first; - } - - void set_second(LogInterface *second) { - second_ = second; - } - - void set_first_verbosity_level(int verbosity_level) { - first_verbosity_level_ = verbosity_level; - } - - void set_second_verbosity_level(int verbosity_level) { - second_verbosity_level_ = verbosity_level; - } - - int get_first_verbosity_level() const { - return first_verbosity_level_; - } - - int get_second_verbosity_level() const { - return second_verbosity_level_; - } - - void rotate() override { - if (first_) { - first_->rotate(); - } - if (second_) { - second_->rotate(); - } - } - - td::vector get_file_paths() override { - td::vector result; - if (first_) { - td::append(result, first_->get_file_paths()); - } - if (second_) { - td::append(result, second_->get_file_paths()); - } - return result; - } - - private: - LogInterface *first_ = nullptr; - int first_verbosity_level_ = VERBOSITY_NAME(FATAL); - LogInterface *second_ = nullptr; - int second_verbosity_level_ = VERBOSITY_NAME(FATAL); - }; - CombineLog log; + td::CombinedLog log; log.set_first(td::default_log_interface); log.set_second(&memory_log); td::log_interface = &log; @@ -376,15 +376,65 @@ int main(int argc, char *argv[]) { TRY_STATUS_PREFIX(td::change_user(username, groupname), "Can't change effective user: "); } - if (!working_directory.empty()) { - TRY_STATUS_PREFIX(td::chdir(working_directory), "Can't set working directory: "); + { + TRY_RESULT_PREFIX_ASSIGN(working_directory, td::realpath(working_directory, true), + "Invalid working directory specified: "); + if (working_directory.empty()) { + return td::Status::Error("Working directory can't be empty"); + } + if (working_directory.back() != TD_DIR_SLASH) { + working_directory += TD_DIR_SLASH; + } + + TRY_STATUS_PREFIX(td::mkpath(working_directory, 0750), "Failed to create working directory: "); + + auto r_temp_file = td::mkstemp(working_directory); + if (r_temp_file.is_error()) { + return td::Status::Error(PSLICE() << "Can't create files in the directory \"" << working_directory + << "\". Use --dir option to specify a writable working directory"); + } + r_temp_file.ok_ref().first.close(); + td::unlink(r_temp_file.ok().second).ensure(); + + auto r_temp_dir = td::mkdtemp(working_directory, "1:a"); + if (r_temp_dir.is_error()) { + parameters->allow_colon_in_filenames_ = false; + r_temp_dir = td::mkdtemp(working_directory, "1~a"); + if (r_temp_dir.is_error()) { + return td::Status::Error(PSLICE() << "Can't create directories in the directory \"" << working_directory + << "\". Use --dir option to specify a writable working directory"); + } + } + td::rmdir(r_temp_dir.ok()).ensure(); } if (!temporary_directory.empty()) { + if (td::PathView(temporary_directory).is_relative()) { + temporary_directory = working_directory + temporary_directory; + } TRY_STATUS_PREFIX(td::set_temporary_dir(temporary_directory), "Can't set temporary directory: "); } + { // check temporary directory + auto temp_dir = td::get_temporary_dir(); + if (temp_dir.empty()) { + return td::Status::Error("Can't find directory for temporary files. Use --temp-dir option to specify it"); + } + + auto r_temp_file = td::mkstemp(temp_dir); + if (r_temp_file.is_error()) { + return td::Status::Error(PSLICE() + << "Can't create files in the directory \"" << temp_dir + << "\". Use --temp-dir option to specify another directory for temporary files"); + } + r_temp_file.ok_ref().first.close(); + td::unlink(r_temp_file.ok().second).ensure(); + } + if (!log_file_path.empty()) { + if (td::PathView(log_file_path).is_relative()) { + log_file_path = working_directory + log_file_path; + } TRY_STATUS_PREFIX(file_log.init(log_file_path, log_max_file_size), "Can't open log file: "); log.set_first(&ts_log); } @@ -392,11 +442,13 @@ int main(int argc, char *argv[]) { return td::Status::OK(); }(); if (init_status.is_error()) { - LOG(PLAIN) << init_status.error(); + LOG(PLAIN) << init_status.error().message(); LOG(PLAIN) << options; return 1; } + parameters->working_directory_ = std::move(working_directory); + if (parameters->default_max_webhook_connections_ <= 0) { parameters->default_max_webhook_connections_ = parameters->local_mode_ ? 100 : 40; } @@ -458,8 +510,8 @@ int main(int argc, char *argv[]) { while (true) { sched.run_main(next_cron_time - td::Time::now()); - if (!need_rotate_log.test_and_set()) { - td::log_interface->rotate(); + if (!need_reopen_log.test_and_set()) { + td::log_interface->after_rotation(); } if (!need_quit.test_and_set()) { @@ -468,7 +520,8 @@ int main(int argc, char *argv[]) { std::_Exit(0); } - LOG(WARNING) << "Stopping engine by a signal"; + LOG(WARNING) << "Stopping engine with uptime " << (td::Time::now() - start_time) << " seconds by a signal"; + dump_statistics(shared_data, net_query_stats); close_flag = true; auto guard = sched.get_main_guard(); send_closure(client_manager, &ClientManager::close, td::PromiseCreator::lambda([&can_quit](td::Unit) { @@ -497,6 +550,7 @@ int main(int argc, char *argv[]) { if (!need_dump_log.test_and_set()) { print_log(); + dump_statistics(shared_data, net_query_stats); } double now = td::Time::now(); @@ -525,48 +579,7 @@ int main(int argc, char *argv[]) { if (now > last_dump_time + 300.0) { last_dump_time = now; - if (is_memprof_on()) { - LOG(WARNING) << "Memory dump:"; - td::vector v; - dump_alloc([&](const AllocInfo &info) { v.push_back(info); }); - std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; }); - size_t total_size = 0; - size_t other_size = 0; - int count = 0; - for (auto &info : v) { - if (count++ < 50) { - LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace); - } else { - other_size += info.size; - } - total_size += info.size; - } - LOG(WARNING) << td::tag("other", td::format::as_size(other_size)); - LOG(WARNING) << td::tag("total", td::format::as_size(total_size)); - LOG(WARNING) << td::tag("total traces", get_ht_size()); - LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate()); - } - LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem())); - LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size())); - - auto query_count = shared_data->query_count_.load(); - LOG(WARNING) << td::tag("pending queries", query_count); - - td::uint64 i = 0; - bool was_gap = false; - for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) { - if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) { - if (was_gap) { - LOG(WARNING) << "..."; - was_gap = false; - } - LOG(WARNING) << static_cast(*cur); - } else { - was_gap = true; - } - } - - td::dump_pending_network_queries(*net_query_stats); + dump_statistics(shared_data, net_query_stats); } }