| 1 | //! @file view.cpp |
| 2 | //! @author ryftchen |
| 3 | //! @brief The definitions (view) in the application module. |
| 4 | //! @version 0.1.0 |
| 5 | //! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved. |
| 6 | |
| 7 | #include "view.hpp" |
| 8 | #include "data.hpp" |
| 9 | #include "log.hpp" |
| 10 | |
| 11 | #ifndef _PRECOMPILED_HEADER |
| 12 | #if __has_include(<gsl/gsl_version.h>) |
| 13 | #include <gsl/gsl_version.h> |
| 14 | #endif |
| 15 | #include <openssl/evp.h> |
| 16 | #include <readline/readline.h> |
| 17 | #include <sys/shm.h> |
| 18 | #include <sys/stat.h> |
| 19 | #if __has_include(<gmp.h>) |
| 20 | #include <gmp.h> |
| 21 | #endif |
| 22 | #include <lz4.h> |
| 23 | #include <mpfr.h> |
| 24 | #if __has_include(<ncurses.h>) |
| 25 | #include <ncurses.h> |
| 26 | #endif |
| 27 | #include <cassert> |
| 28 | #include <iterator> |
| 29 | #include <numeric> |
| 30 | #else |
| 31 | #include "application/pch/precompiled_header.hpp" |
| 32 | #endif |
| 33 | |
| 34 | #include "utility/include/benchmark.hpp" |
| 35 | #include "utility/include/time.hpp" |
| 36 | |
| 37 | namespace application |
| 38 | { |
| 39 | namespace view |
| 40 | { |
| 41 | //! @brief Alias for the access controller. |
| 42 | using AccessController = configure::Controller<View>; |
| 43 | |
| 44 | //! @brief Anonymous namespace. |
| 45 | inline namespace |
| 46 | { |
| 47 | //! @brief The internal symbol for exiting. |
| 48 | constexpr std::string_view exitSymbol = MACRO_STRINGIFY(stop); |
| 49 | } // namespace |
| 50 | |
| 51 | //! @brief Alias for the type-length-value scheme. |
| 52 | namespace tlv = data::tlv; |
| 53 | |
| 54 | View::View() : FSM(State::initial) |
| 55 | { |
| 56 | if (!configure::detail::activateHelper()) [[unlikely]] |
| 57 | { |
| 58 | throw std::logic_error{"The " + name + " is disabled." }; |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | std::shared_ptr<View> View::getInstance() |
| 63 | { |
| 64 | static const std::shared_ptr<View> viewer(::new View{}); |
| 65 | return viewer; |
| 66 | } |
| 67 | |
| 68 | // NOLINTBEGIN(cppcoreguidelines-avoid-goto) |
| 69 | void View::service() |
| 70 | { |
| 71 | retry: |
| 72 | try |
| 73 | { |
| 74 | processEvent(event: Relaunch{}); |
| 75 | |
| 76 | assert(currentState() == State::initial); |
| 77 | processEvent(event: CreateServer{}); |
| 78 | |
| 79 | assert(currentState() == State::active); |
| 80 | awaitNotification2Proceed(); |
| 81 | processEvent(event: GoViewing{}); |
| 82 | |
| 83 | assert(currentState() == State::established); |
| 84 | notificationLoop(); |
| 85 | if (inResetting.load()) |
| 86 | { |
| 87 | goto retry; |
| 88 | } |
| 89 | processEvent(event: DestroyServer{}); |
| 90 | |
| 91 | assert(currentState() == State::active); |
| 92 | processEvent(event: NoViewing{}); |
| 93 | |
| 94 | assert(currentState() == State::inactive); |
| 95 | } |
| 96 | catch (const std::exception& err) |
| 97 | { |
| 98 | LOG_ERR << "Suspend the " << name << " during " << static_cast<State>(currentState()) << " state. " |
| 99 | << err.what(); |
| 100 | |
| 101 | processEvent(event: Standby{}); |
| 102 | if (awaitNotification2Retry()) |
| 103 | { |
| 104 | goto retry; |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | // NOLINTEND(cppcoreguidelines-avoid-goto) |
| 109 | |
| 110 | //! @brief Build the TLV packet of the response message to get library information. |
| 111 | //! @param buffer - TLV packet buffer |
| 112 | //! @return buffer length |
| 113 | template <> |
| 114 | std::size_t View::buildCustomTLVPacket<View::OptDepend>(const Args& /*args*/, Buffer& buffer) |
| 115 | { |
| 116 | tlv::TLVValue val{}; |
| 117 | std::string extLibraries{}; |
| 118 | #if defined(__GLIBC__) && defined(__GLIBC_MINOR__) |
| 119 | extLibraries += "GNU C Library " MACRO_STRINGIFY(__GLIBC__) "." MACRO_STRINGIFY(__GLIBC_MINOR__) "\n" ; |
| 120 | #else |
| 121 | #error Could not find the GNU C Library version. |
| 122 | #endif |
| 123 | #if defined(_GLIBCXX_RELEASE) && defined(__GLIBCXX__) |
| 124 | extLibraries += |
| 125 | "GNU C++ Standard Library " MACRO_STRINGIFY(_GLIBCXX_RELEASE) " (" MACRO_STRINGIFY(__GLIBCXX__) ")\n" ; |
| 126 | #else |
| 127 | #error Could not find the GNU C++ Standard Library version. |
| 128 | #endif |
| 129 | #if defined(__GNU_MP_VERSION) && defined(__GNU_MP_VERSION_MINOR) && defined(__GNU_MP_VERSION_PATCHLEVEL) |
| 130 | extLibraries += "GNU MP Library " MACRO_STRINGIFY(__GNU_MP_VERSION) "." MACRO_STRINGIFY( |
| 131 | __GNU_MP_VERSION_MINOR) "." MACRO_STRINGIFY(__GNU_MP_VERSION_PATCHLEVEL) "\n" ; |
| 132 | #else |
| 133 | #pragma message("Could not find the GNU MP Library version.") |
| 134 | #endif |
| 135 | #if defined(MPFR_VERSION_STRING) |
| 136 | extLibraries += "GNU MPFR Library " MPFR_VERSION_STRING "\n" ; |
| 137 | #else |
| 138 | #error Could not find the GNU MPFR Library version. |
| 139 | #endif |
| 140 | #if defined(GSL_VERSION) |
| 141 | extLibraries += "GNU Scientific Library " GSL_VERSION " (CBLAS)\n" ; |
| 142 | #else |
| 143 | #error Could not find the GNU Scientific Library (CBLAS) version. |
| 144 | #endif |
| 145 | #if defined(RL_VERSION_MAJOR) && defined(RL_VERSION_MINOR) |
| 146 | extLibraries += |
| 147 | "GNU Readline Library " MACRO_STRINGIFY(RL_VERSION_MAJOR) "." MACRO_STRINGIFY(RL_VERSION_MINOR) "\n" ; |
| 148 | #else |
| 149 | #error Could not find the GNU Readline Library version. |
| 150 | #endif |
| 151 | #if defined(LZ4_VERSION_STRING) |
| 152 | extLibraries += "LZ4 Library " LZ4_VERSION_STRING "\n" ; |
| 153 | #else |
| 154 | #error Could not find the LZ4 Library version. |
| 155 | #endif |
| 156 | #if defined(NCURSES_VERSION) |
| 157 | extLibraries += "Ncurses Library " NCURSES_VERSION "\n" ; |
| 158 | #else |
| 159 | #pragma message("Could not find the Ncurses Library version.") |
| 160 | #endif |
| 161 | #if defined(OPENSSL_VERSION_STR) |
| 162 | extLibraries += "OpenSSL Library " OPENSSL_VERSION_STR "" ; |
| 163 | #else |
| 164 | #error Could not find the OpenSSL Library version. |
| 165 | #endif |
| 166 | std::strncpy(dest: val.libInfo, src: extLibraries.c_str(), n: sizeof(val.libInfo) - 1); |
| 167 | val.libInfo[sizeof(val.libInfo) - 1] = '\0'; |
| 168 | char* const buf = buffer.data(); |
| 169 | std::size_t len = buffer.size(); |
| 170 | if (const auto ec = tlv::encodeTLV(buf, len, val); ec) |
| 171 | { |
| 172 | throw std::runtime_error{ |
| 173 | "Failed to build packet for the " + std::string{OptDepend::name} + " option (" + ec.message() + ")." }; |
| 174 | } |
| 175 | data::encryptMessage(buffer: buf, length: len); |
| 176 | return len; |
| 177 | } |
| 178 | |
| 179 | //! @brief Build the TLV packet of the response message to get bash outputs. |
| 180 | //! @param args - container of arguments |
| 181 | //! @param buffer - TLV packet buffer |
| 182 | //! @return buffer length |
| 183 | template <> |
| 184 | std::size_t View::buildCustomTLVPacket<View::OptExecute>(const Args& args, Buffer& buffer) |
| 185 | { |
| 186 | const auto cmd = std::accumulate( |
| 187 | first: args.cbegin(), |
| 188 | last: args.cend(), |
| 189 | init: std::string{}, |
| 190 | binary_op: [](const auto& acc, const auto& arg) { return acc.empty() ? arg : (acc + ' ' + arg); }); |
| 191 | if (((cmd.find_first_not_of(c: '\'') == 0) || (cmd.find_last_not_of(c: '\'') == (cmd.length() - 1))) |
| 192 | && ((cmd.find_first_not_of(c: '"') == 0) || (cmd.find_last_not_of(c: '"') == (cmd.length() - 1)))) |
| 193 | { |
| 194 | throw std::runtime_error{ |
| 195 | "Please enter the \"" + std::string{OptExecute::name} + "\" and append with 'CMD' (include quotes)." }; |
| 196 | } |
| 197 | |
| 198 | char* const buf = buffer.data(); |
| 199 | std::size_t len = buffer.size(); |
| 200 | const int shmId = fillSharedMemory(contents: utility::io::executeCommand(command: "/bin/bash -c " + cmd)); |
| 201 | if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.bashShmId = shmId}); ec) |
| 202 | { |
| 203 | throw std::runtime_error{ |
| 204 | "Failed to build packet for the " + std::string{OptExecute::name} + " option (" + ec.message() + ")." }; |
| 205 | } |
| 206 | data::encryptMessage(buffer: buf, length: len); |
| 207 | return len; |
| 208 | } |
| 209 | |
| 210 | //! @brief Build the TLV packet of the response message to get log contents. |
| 211 | //! @param buffer - TLV packet buffer |
| 212 | //! @return buffer length |
| 213 | template <> |
| 214 | std::size_t View::buildCustomTLVPacket<View::OptJournal>(const Args& /*args*/, Buffer& buffer) |
| 215 | { |
| 216 | char* const buf = buffer.data(); |
| 217 | std::size_t len = buffer.size(); |
| 218 | const int shmId = fillSharedMemory(contents: logContextPreview()); |
| 219 | if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.logShmId = shmId}); ec) |
| 220 | { |
| 221 | throw std::runtime_error{ |
| 222 | "Failed to build packet for the " + std::string{OptJournal::name} + " option (" + ec.message() + ")." }; |
| 223 | } |
| 224 | data::encryptMessage(buffer: buf, length: len); |
| 225 | return len; |
| 226 | } |
| 227 | |
| 228 | //! @brief Build the TLV packet of the response message to get status reports. |
| 229 | //! @param args - container of arguments |
| 230 | //! @param buffer - TLV packet buffer |
| 231 | //! @return buffer length |
| 232 | template <> |
| 233 | std::size_t View::buildCustomTLVPacket<View::OptMonitor>(const Args& args, Buffer& buffer) |
| 234 | { |
| 235 | if (!args.empty()) |
| 236 | { |
| 237 | if (const auto& input = args.front(); (input.length() != 1) || (std::isdigit(input.front()) == 0)) |
| 238 | { |
| 239 | throw std::runtime_error{ |
| 240 | "Please enter the \"" + std::string{OptMonitor::name} + "\" and append with or without NUM (0 to 9)." }; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | char* const buf = buffer.data(); |
| 245 | std::size_t len = buffer.size(); |
| 246 | const int shmId = fillSharedMemory(contents: statusReportPreview(frame: args.empty() ? 0 : std::stoul(str: args.front()))); |
| 247 | if (const auto ec = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.statusShmId = shmId}); ec) |
| 248 | { |
| 249 | throw std::runtime_error{ |
| 250 | "Failed to build packet for the " + std::string{OptMonitor::name} + " option (" + ec.message() + ")." }; |
| 251 | } |
| 252 | data::encryptMessage(buffer: buf, length: len); |
| 253 | return len; |
| 254 | } |
| 255 | |
| 256 | //! @brief Build the TLV packet of the response message to get current configuration. |
| 257 | //! @param buffer - TLV packet buffer |
| 258 | //! @return buffer length |
| 259 | template <> |
| 260 | std::size_t View::buildCustomTLVPacket<View::OptProfile>(const Args& /*args*/, Buffer& buffer) |
| 261 | { |
| 262 | tlv::TLVValue val{}; |
| 263 | std::strncpy( |
| 264 | dest: val.configInfo, |
| 265 | src: (static_cast<const utility::json::JSON&>(configure::retrieveDataRepo())).asUnescapedString().c_str(), |
| 266 | n: sizeof(val.configInfo) - 1); |
| 267 | val.configInfo[sizeof(val.configInfo) - 1] = '\0'; |
| 268 | char* const buf = buffer.data(); |
| 269 | std::size_t len = buffer.size(); |
| 270 | if (const auto ec = tlv::encodeTLV(buf, len, val); ec) |
| 271 | { |
| 272 | throw std::runtime_error{ |
| 273 | "Failed to build packet for the " + std::string{OptProfile::name} + " option (" + ec.message() + ")." }; |
| 274 | } |
| 275 | data::encryptMessage(buffer: buf, length: len); |
| 276 | return len; |
| 277 | } |
| 278 | |
| 279 | std::size_t View::buildResponse(const std::string& reqPlaintext, Buffer& respBuffer) |
| 280 | { |
| 281 | return utility::common::patternMatch( |
| 282 | var: extractOption(reqPlaintext), |
| 283 | cases: [&respBuffer](const OptDepend& opt) { return buildCustomTLVPacket<OptDepend>(opt.args, buffer&: respBuffer); }, |
| 284 | cases: [&respBuffer](const OptExecute& opt) { return buildCustomTLVPacket<OptExecute>(args: opt.args, buffer&: respBuffer); }, |
| 285 | cases: [&respBuffer](const OptJournal& opt) { return buildCustomTLVPacket<OptJournal>(opt.args, buffer&: respBuffer); }, |
| 286 | cases: [&respBuffer](const OptMonitor& opt) { return buildCustomTLVPacket<OptMonitor>(args: opt.args, buffer&: respBuffer); }, |
| 287 | cases: [&respBuffer](const OptProfile& opt) { return buildCustomTLVPacket<OptProfile>(opt.args, buffer&: respBuffer); }, |
| 288 | cases: [](const auto& opt) |
| 289 | { |
| 290 | if (const auto* origPtr = std::addressof(opt); dynamic_cast<const OptBase*>(origPtr)) |
| 291 | { |
| 292 | throw std::runtime_error{ |
| 293 | "The option is unprocessed due to unregistered or potential registration failures (typeid: " |
| 294 | + std::string{typeid(opt).name()} + ")." }; |
| 295 | } |
| 296 | return 0UL; |
| 297 | }); |
| 298 | } |
| 299 | |
| 300 | View::OptionType View::(const std::string& reqPlaintext) |
| 301 | { |
| 302 | auto args = splitString(str: reqPlaintext); |
| 303 | if (args.empty()) |
| 304 | { |
| 305 | return {}; |
| 306 | } |
| 307 | |
| 308 | const auto& optName = args.front(); |
| 309 | switch (utility::common::bkdrHash(str: optName.c_str())) |
| 310 | { |
| 311 | using utility::common::operator""_bkdrHash ; |
| 312 | case operator""_bkdrHash (str: OptDepend::name): |
| 313 | return OptDepend{}; |
| 314 | case operator""_bkdrHash (str: OptExecute::name): |
| 315 | args.erase(position: args.cbegin()); |
| 316 | return OptExecute{std::move(args)}; |
| 317 | case operator""_bkdrHash (str: OptJournal::name): |
| 318 | return OptJournal{}; |
| 319 | case operator""_bkdrHash (str: OptMonitor::name): |
| 320 | args.erase(position: args.cbegin()); |
| 321 | return OptMonitor{std::move(args)}; |
| 322 | case operator""_bkdrHash (str: OptProfile::name): |
| 323 | return OptProfile{}; |
| 324 | default: |
| 325 | break; |
| 326 | } |
| 327 | return {}; |
| 328 | } |
| 329 | |
| 330 | std::vector<std::string> View::splitString(const std::string& str) |
| 331 | { |
| 332 | std::vector<std::string> split{}; |
| 333 | std::istringstream transfer(str); |
| 334 | std::string token{}; |
| 335 | while (transfer >> token) |
| 336 | { |
| 337 | split.emplace_back(args&: token); |
| 338 | } |
| 339 | return split; |
| 340 | } |
| 341 | |
| 342 | std::size_t View::buildAckTLVPacket(Buffer& buffer) |
| 343 | { |
| 344 | char* const buf = buffer.data(); |
| 345 | std::size_t len = buffer.size(); |
| 346 | std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{}); |
| 347 | data::encryptMessage(buffer: buf, length: len); |
| 348 | return len; |
| 349 | } |
| 350 | |
| 351 | std::size_t View::buildFinTLVPacket(Buffer& buffer) |
| 352 | { |
| 353 | char* const buf = buffer.data(); |
| 354 | std::size_t len = buffer.size(); |
| 355 | std::ignore = tlv::encodeTLV(buf, len, val: tlv::TLVValue{.stopTag = true}); |
| 356 | data::encryptMessage(buffer: buf, length: len); |
| 357 | return len; |
| 358 | } |
| 359 | |
| 360 | // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) |
| 361 | int View::fillSharedMemory(const std::string_view contents) |
| 362 | { |
| 363 | const int shmId = ::shmget( |
| 364 | key: 0, size: sizeof(ShrMemBlock), IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
| 365 | if (shmId == -1) |
| 366 | { |
| 367 | throw std::runtime_error{"Failed to create shared memory (" + std::to_string(val: shmId) + ")." }; |
| 368 | } |
| 369 | void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0); |
| 370 | if (!shm) |
| 371 | { |
| 372 | throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")." }; |
| 373 | } |
| 374 | |
| 375 | auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm); |
| 376 | for (shrMem->signal.store(i: false);;) |
| 377 | { |
| 378 | if (shrMem->signal.load()) |
| 379 | { |
| 380 | std::this_thread::yield(); |
| 381 | continue; |
| 382 | } |
| 383 | |
| 384 | std::vector<char> processed(contents.data(), contents.data() + contents.length()); |
| 385 | data::compressData(cache&: processed); |
| 386 | if (processed.size() > sizeof(shrMem->data)) |
| 387 | { |
| 388 | shrMem->signal.store(i: true); |
| 389 | break; |
| 390 | } |
| 391 | data::encryptMessage(buffer: processed.data(), length: processed.size()); |
| 392 | std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data)); |
| 393 | std::memcpy(dest: shrMem->data, src: processed.data(), n: processed.size() * sizeof(char)); |
| 394 | shrMem->size = processed.size(); |
| 395 | |
| 396 | shrMem->signal.store(i: true); |
| 397 | break; |
| 398 | } |
| 399 | ::shmdt(shmaddr: shm); |
| 400 | return shmId; |
| 401 | } |
| 402 | |
| 403 | void View::fetchSharedMemory(const int shmId, std::string& contents) |
| 404 | { |
| 405 | void* const shm = ::shmat(shmid: shmId, shmaddr: nullptr, shmflg: 0); |
| 406 | if (!shm) |
| 407 | { |
| 408 | throw std::runtime_error{"Failed to attach shared memory (" + std::to_string(val: shmId) + ")." }; |
| 409 | } |
| 410 | |
| 411 | auto* const shrMem = reinterpret_cast<ShrMemBlock*>(shm); |
| 412 | for (shrMem->signal.store(i: true);;) |
| 413 | { |
| 414 | if (!shrMem->signal.load()) |
| 415 | { |
| 416 | std::this_thread::yield(); |
| 417 | continue; |
| 418 | } |
| 419 | |
| 420 | if (shrMem->size > sizeof(shrMem->data)) |
| 421 | { |
| 422 | shrMem->signal.store(i: false); |
| 423 | break; |
| 424 | } |
| 425 | std::vector<char> processed(shrMem->size); |
| 426 | shrMem->size = 0; |
| 427 | std::memcpy(dest: processed.data(), src: shrMem->data, n: processed.size() * sizeof(char)); |
| 428 | std::memset(s: shrMem->data, c: 0, n: sizeof(shrMem->data)); |
| 429 | data::decryptMessage(buffer: processed.data(), length: processed.size()); |
| 430 | data::decompressData(cache&: processed); |
| 431 | contents = std::string{processed.data(), processed.data() + processed.size()}; |
| 432 | |
| 433 | shrMem->signal.store(i: false); |
| 434 | break; |
| 435 | } |
| 436 | ::shmdt(shmaddr: shm); |
| 437 | ::shmctl(shmid: shmId, IPC_RMID, buf: nullptr); |
| 438 | } |
| 439 | // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) |
| 440 | |
| 441 | void View::printSharedMemory(const int shmId, const bool withoutPaging) |
| 442 | { |
| 443 | std::string output{}; |
| 444 | fetchSharedMemory(shmId, contents&: output); |
| 445 | if (!withoutPaging) |
| 446 | { |
| 447 | segmentedOutput(cache: output); |
| 448 | return; |
| 449 | } |
| 450 | |
| 451 | std::istringstream transfer(output.c_str()); |
| 452 | std::string line{}; |
| 453 | while (std::getline(is&: transfer, str&: line)) |
| 454 | { |
| 455 | std::cout << line << '\n'; |
| 456 | } |
| 457 | std::cout << "\033[0m" << std::flush; |
| 458 | } |
| 459 | |
| 460 | void View::segmentedOutput(const std::string& cache) |
| 461 | { |
| 462 | constexpr std::uint8_t terminalRows = 24; |
| 463 | constexpr std::string_view prompt = "--- Type <CR> for more, c to continue, n to show next page, q to quit ---: " ; |
| 464 | constexpr std::string_view escapeClear = "\033[1A\033[2K\r" ; |
| 465 | constexpr std::string_view escapeMoveUp = "\n\033[1A\033[" ; |
| 466 | std::istringstream transfer(cache); |
| 467 | const std::size_t lineNum = |
| 468 | std::count(first: std::istreambuf_iterator<char>(transfer), last: std::istreambuf_iterator<char>{}, value: '\n'); |
| 469 | transfer.seekg(std::ios::beg); |
| 470 | |
| 471 | bool moreRows = false; |
| 472 | bool forcedCancel = false; |
| 473 | bool withoutPaging = (lineNum <= terminalRows); |
| 474 | std::string line{}; |
| 475 | std::size_t counter = 0; |
| 476 | const auto handling = utility::common::wrapClosure( |
| 477 | closure: [&](const std::string& input) |
| 478 | { |
| 479 | std::cout << escapeClear << std::flush; |
| 480 | if (input.empty()) |
| 481 | { |
| 482 | moreRows = true; |
| 483 | counter = 0; |
| 484 | return true; |
| 485 | } |
| 486 | |
| 487 | moreRows = false; |
| 488 | switch (utility::common::bkdrHash(str: input.c_str())) |
| 489 | { |
| 490 | using utility::common::operator""_bkdrHash ; |
| 491 | case "c"_bkdrHash : |
| 492 | withoutPaging = true; |
| 493 | break; |
| 494 | case "n"_bkdrHash : |
| 495 | counter = 0; |
| 496 | break; |
| 497 | case "q"_bkdrHash : |
| 498 | forcedCancel = true; |
| 499 | break; |
| 500 | default: |
| 501 | std::cout << prompt << std::flush; |
| 502 | return false; |
| 503 | } |
| 504 | return true; |
| 505 | }); |
| 506 | while (std::getline(is&: transfer, str&: line) && !forcedCancel) |
| 507 | { |
| 508 | std::cout << line << '\n'; |
| 509 | ++counter; |
| 510 | if (!withoutPaging && (moreRows || (counter == terminalRows))) |
| 511 | { |
| 512 | std::cout << prompt << escapeMoveUp << prompt.length() << 'C' << std::flush; |
| 513 | utility::io::waitForUserInput(operation: handling); |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | std::cout << "\033[0m" << std::flush; |
| 518 | if (lineNum > terminalRows) |
| 519 | { |
| 520 | std::cout << std::endl; |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | std::string View::logContextPreview() |
| 525 | { |
| 526 | std::ostringstream transfer{}; |
| 527 | log::intf::previewInContext( |
| 528 | peeking: [&transfer](const std::string& filePath) |
| 529 | { |
| 530 | constexpr std::uint16_t lineLimit = 24 * 100; |
| 531 | auto logRows = utility::io::readFileLines(filename: filePath, lock: false, reverse: true, limit: lineLimit); |
| 532 | std::ranges::for_each(logRows, [](auto& line) { log::changeToLogStyle(line); }); |
| 533 | std::ranges::copy(logRows, std::ostream_iterator<std::string>(transfer, "\n" )); |
| 534 | }); |
| 535 | return std::move(transfer).str(); |
| 536 | } |
| 537 | |
| 538 | std::string View::statusReportPreview(const std::uint16_t frame) |
| 539 | { |
| 540 | if ((frame > 0) |
| 541 | && (::system( // NOLINT(cert-env33-c, concurrency-mt-unsafe) |
| 542 | command: "command -v eu-stack >/dev/null 2>&1 " |
| 543 | "&& grep -qx '0' /proc/sys/kernel/yama/ptrace_scope >/dev/null 2>&1" ) |
| 544 | != EXIT_SUCCESS)) |
| 545 | { |
| 546 | throw std::runtime_error{"Please confirm that the eu-stack program has been installed and " |
| 547 | "that the classic ptrace permissions have been set." }; |
| 548 | } |
| 549 | |
| 550 | const ::pid_t pid = ::getpid(); |
| 551 | constexpr std::uint16_t totalLen = 1024; |
| 552 | std::array<char, totalLen> queryStmt{}; |
| 553 | std::snprintf( |
| 554 | s: queryStmt.data(), maxlen: queryStmt.size(), format: "ps -T -p %d | awk 'NR>1 {split($0, a, \" \"); print a[2]}'" , pid); |
| 555 | |
| 556 | constexpr const char* const focusField = "Name|State|Tgid|Pid|PPid|TracerPid|Uid|Gid|VmSize|VmRSS|CoreDumping|" |
| 557 | "Threads|SigQ|voluntary_ctxt_switches|nonvoluntary_ctxt_switches" ; |
| 558 | const auto queryResult = utility::io::executeCommand(command: queryStmt.data()); |
| 559 | std::vector<std::string> statements{}; |
| 560 | std::size_t pos = 0; |
| 561 | std::size_t prev = 0; |
| 562 | while ((pos = queryResult.find(c: '\n', pos: prev)) != std::string::npos) |
| 563 | { |
| 564 | const int tid = std::stoi(str: queryResult.substr(pos: prev, n: pos - prev + 1)); |
| 565 | std::array<char, totalLen> execStmt{}; |
| 566 | if (const int usedLen = std::snprintf( |
| 567 | s: execStmt.data(), |
| 568 | maxlen: execStmt.size(), |
| 569 | format: "/bin/bash -c " |
| 570 | "\"if [[ -f /proc/%d/task/%d/status ]]; then cat /proc/%d/task/%d/status | grep -E '^(%s):'" , |
| 571 | pid, |
| 572 | tid, |
| 573 | pid, |
| 574 | tid, |
| 575 | focusField); |
| 576 | frame == 0) |
| 577 | { |
| 578 | std::strncpy(dest: execStmt.data() + usedLen, src: "; fi\"" , n: execStmt.size() - usedLen); |
| 579 | execStmt[totalLen - 1] = '\0'; |
| 580 | } |
| 581 | else |
| 582 | { |
| 583 | std::snprintf( |
| 584 | s: execStmt.data() + usedLen, |
| 585 | maxlen: execStmt.size() - usedLen, |
| 586 | format: " && echo 'Stack:' " |
| 587 | "&& (timeout --preserve-status -s SIGINT 1 stdbuf -o0 eu-stack -1v -n %u -p %d 2>&1 | grep '#' " |
| 588 | "|| exit 0); fi\"" , |
| 589 | frame, |
| 590 | tid); |
| 591 | } |
| 592 | statements.emplace_back(args: execStmt.data()); |
| 593 | prev += pos - prev + 1; |
| 594 | } |
| 595 | |
| 596 | auto reports = std::accumulate( |
| 597 | first: statements.cbegin(), |
| 598 | last: statements.cend(), |
| 599 | init: std::string{}, |
| 600 | binary_op: [](const auto& acc, const auto& stmt) |
| 601 | { return acc.empty() ? utility::io::executeCommand(command: stmt) : (acc + '\n' + utility::io::executeCommand(command: stmt)); }); |
| 602 | while (!reports.empty() && (reports.back() == '\n')) |
| 603 | { |
| 604 | reports.pop_back(); |
| 605 | } |
| 606 | return reports; |
| 607 | } |
| 608 | |
| 609 | //! @brief Renew the TCP server. |
| 610 | template <> |
| 611 | void View::renewServer<utility::socket::TCPServer>() |
| 612 | { |
| 613 | permSessServer = std::make_shared<utility::socket::TCPServer>(); |
| 614 | permSessServer->subscribeConnection( |
| 615 | callback: [](const std::shared_ptr<utility::socket::TCPSocket> client) // NOLINT(performance-unnecessary-value-param) |
| 616 | { |
| 617 | const std::weak_ptr weakSock = client; |
| 618 | client->subscribeMessage( |
| 619 | callback: [weakSock](const std::string_view message) |
| 620 | { |
| 621 | auto currSock = weakSock.lock(); |
| 622 | if (!currSock || message.empty()) |
| 623 | { |
| 624 | return; |
| 625 | } |
| 626 | |
| 627 | Buffer respBuffer{}; |
| 628 | try |
| 629 | { |
| 630 | const auto reqPlaintext = utility::common::base64Decode(data: message); |
| 631 | currSock->send( |
| 632 | bytes: respBuffer.data(), |
| 633 | size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer) |
| 634 | : buildFinTLVPacket(buffer&: respBuffer)); |
| 635 | } |
| 636 | catch (const std::exception& err) |
| 637 | { |
| 638 | LOG_WRN << err.what(); |
| 639 | currSock->send(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer)); |
| 640 | } |
| 641 | }); |
| 642 | }); |
| 643 | } |
| 644 | |
| 645 | //! @brief Renew the UDP server. |
| 646 | template <> |
| 647 | void View::renewServer<utility::socket::UDPServer>() |
| 648 | { |
| 649 | tempSessServer = std::make_shared<utility::socket::UDPServer>(); |
| 650 | const std::weak_ptr weakSock = tempSessServer; |
| 651 | tempSessServer->subscribeMessage( |
| 652 | callback: [weakSock](const std::string_view message, const std::string& ip, const std::uint16_t port) |
| 653 | { |
| 654 | auto currSock = weakSock.lock(); |
| 655 | if (!currSock || message.empty()) |
| 656 | { |
| 657 | return; |
| 658 | } |
| 659 | |
| 660 | Buffer respBuffer{}; |
| 661 | try |
| 662 | { |
| 663 | const auto reqPlaintext = utility::common::base64Decode(data: message); |
| 664 | currSock->sendTo( |
| 665 | bytes: respBuffer.data(), |
| 666 | size: (reqPlaintext != exitSymbol) ? buildResponse(reqPlaintext, respBuffer) |
| 667 | : buildFinTLVPacket(buffer&: respBuffer), |
| 668 | ip, |
| 669 | port); |
| 670 | } |
| 671 | catch (const std::exception& err) |
| 672 | { |
| 673 | LOG_WRN << err.what(); |
| 674 | currSock->sendTo(bytes: respBuffer.data(), size: buildAckTLVPacket(buffer&: respBuffer), ip, port); |
| 675 | } |
| 676 | }); |
| 677 | } |
| 678 | |
| 679 | //! @brief Connect from the TCP client. Simplified interface for external use. |
| 680 | //! @param client - TCP client to be connected |
| 681 | template <> |
| 682 | void View::onConnecting<utility::socket::TCPSocket>(std::shared_ptr<utility::socket::TCPSocket>& client) |
| 683 | { |
| 684 | client->subscribeRawMessage(callback: [this, &client](char* const bytes, const std::size_t size) |
| 685 | { parseTLVMessage(client, bytes, size); }); |
| 686 | client->connect(ip: tcpHost, port: tcpPort); |
| 687 | } |
| 688 | |
| 689 | //! @brief Connect from the UDP client. Simplified interface for external use. |
| 690 | //! @param client - UDP client to be connected |
| 691 | template <> |
| 692 | void View::onConnecting<utility::socket::UDPSocket>(std::shared_ptr<utility::socket::UDPSocket>& client) |
| 693 | { |
| 694 | client->subscribeRawMessage( |
| 695 | callback: [this, &client](char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port) |
| 696 | { |
| 697 | MACRO_IGNORE(ip, port); |
| 698 | parseTLVMessage(client, bytes, size); |
| 699 | }); |
| 700 | client->receive(); |
| 701 | client->connect(ip: udpHost, port: udpPort); |
| 702 | } |
| 703 | |
| 704 | template <typename Sock> |
| 705 | void View::onForwarding(std::shared_ptr<Sock>& client, const std::vector<std::string>& inputs) |
| 706 | { |
| 707 | auto reqBuffer = utility::common::base64Encode(data: std::accumulate( |
| 708 | inputs.cbegin(), |
| 709 | inputs.cend(), |
| 710 | std::string{}, |
| 711 | [](const auto& acc, const auto& token) { return acc.empty() ? token : (acc + ' ' + token); })); |
| 712 | client->send(std::move(reqBuffer)); |
| 713 | waitTaskDone(); |
| 714 | } |
| 715 | |
| 716 | template <typename Sock> |
| 717 | void View::parseTLVMessage(std::shared_ptr<Sock>& client, char* const bytes, const std::size_t size) |
| 718 | try |
| 719 | { |
| 720 | MACRO_DEFER([this]() { notifyTaskDone(); }); |
| 721 | if (client->stopRequested() || !bytes || (size == 0)) |
| 722 | { |
| 723 | return; |
| 724 | } |
| 725 | |
| 726 | data::decryptMessage(buffer: bytes, length: size); |
| 727 | tlv::TLVValue value{}; |
| 728 | if (const auto ec = tlv::decodeTLV(buf: bytes, len: size, val&: value); ec) |
| 729 | { |
| 730 | throw std::runtime_error{ |
| 731 | "Invalid message content \"" + data::toHexString(buffer: bytes, length: size) + "\" (" + ec.message() + ")." }; |
| 732 | } |
| 733 | |
| 734 | if (const std::size_t actLibLen = ::strnlen(string: value.libInfo, maxlen: sizeof(value.libInfo)); |
| 735 | (actLibLen != 0) && (actLibLen < sizeof(value.libInfo))) |
| 736 | { |
| 737 | std::cout << value.libInfo << std::endl; |
| 738 | } |
| 739 | if (value.bashShmId != tlv::invalidShmId) |
| 740 | { |
| 741 | printSharedMemory(shmId: value.bashShmId); |
| 742 | } |
| 743 | if (value.logShmId != tlv::invalidShmId) |
| 744 | { |
| 745 | printSharedMemory(shmId: value.logShmId, withoutPaging: !isInServingState(state: State::established)); |
| 746 | } |
| 747 | if (value.statusShmId != tlv::invalidShmId) |
| 748 | { |
| 749 | printSharedMemory(shmId: value.statusShmId); |
| 750 | } |
| 751 | if (const std::size_t actConfigLen = ::strnlen(string: value.configInfo, maxlen: sizeof(value.configInfo)); |
| 752 | (actConfigLen != 0) && (actConfigLen < sizeof(value.configInfo))) |
| 753 | { |
| 754 | using utility::json::JSON; |
| 755 | std::cout << JSON::load(fmt: value.configInfo) << std::endl; |
| 756 | } |
| 757 | |
| 758 | if (value.stopTag) |
| 759 | { |
| 760 | client->requestStop(); |
| 761 | } |
| 762 | } |
| 763 | catch (const std::exception& err) |
| 764 | { |
| 765 | LOG_WRN << err.what(); |
| 766 | } |
| 767 | |
| 768 | void View::waitTaskDone() |
| 769 | { |
| 770 | std::unique_lock<std::mutex> outputLock(outputMtx); |
| 771 | outputCompleted.store(i: false); |
| 772 | |
| 773 | const auto maxWaitTime = std::chrono::milliseconds{timeoutPeriod}; |
| 774 | utility::time::Timer expiryTimer( |
| 775 | isInServingState(state: State::established) ? std::function<void()>{[]() {}} : [this]() { notifyTaskDone(); }); |
| 776 | expiryTimer.start(interval: maxWaitTime); |
| 777 | outputCond.wait(lock&: outputLock, p: [this]() { return outputCompleted.load(); }); |
| 778 | } |
| 779 | |
| 780 | void View::notifyTaskDone() |
| 781 | { |
| 782 | std::unique_lock<std::mutex> outputLock(outputMtx); |
| 783 | outputCompleted.store(i: true); |
| 784 | outputLock.unlock(); |
| 785 | outputCond.notify_one(); |
| 786 | } |
| 787 | |
| 788 | void View::syncWaitOr(const State state, const std::function<void()>& handling) const |
| 789 | { |
| 790 | do |
| 791 | { |
| 792 | if (isInServingState(state: State::idle) && handling) |
| 793 | { |
| 794 | handling(); |
| 795 | } |
| 796 | std::this_thread::yield(); |
| 797 | } |
| 798 | while (!isInServingState(state)); |
| 799 | } |
| 800 | |
| 801 | void View::syncNotifyVia(const std::function<void()>& action) |
| 802 | { |
| 803 | std::unique_lock<std::mutex> daemonLock(daemonMtx); |
| 804 | if (action) |
| 805 | { |
| 806 | action(); |
| 807 | } |
| 808 | daemonLock.unlock(); |
| 809 | daemonCond.notify_one(); |
| 810 | } |
| 811 | |
| 812 | void View::syncCountdownIf(const std::function<bool()>& condition, const std::function<void()>& handling) const |
| 813 | { |
| 814 | if (!condition) |
| 815 | { |
| 816 | return; |
| 817 | } |
| 818 | for (const utility::time::Stopwatch timing{}; timing.elapsedTime() <= timeoutPeriod;) |
| 819 | { |
| 820 | if (!condition()) |
| 821 | { |
| 822 | return; |
| 823 | } |
| 824 | std::this_thread::yield(); |
| 825 | } |
| 826 | if (handling) |
| 827 | { |
| 828 | handling(); |
| 829 | } |
| 830 | } |
| 831 | |
| 832 | bool View::isInServingState(const State state) const |
| 833 | { |
| 834 | return (currentState() == state) && !inResetting.load(); |
| 835 | } |
| 836 | |
| 837 | void View::createViewServer() |
| 838 | { |
| 839 | renewServer<utility::socket::TCPServer>(); |
| 840 | renewServer<utility::socket::UDPServer>(); |
| 841 | } |
| 842 | |
| 843 | void View::destroyViewServer() |
| 844 | { |
| 845 | if (permSessServer) |
| 846 | { |
| 847 | permSessServer->close(); |
| 848 | permSessServer->join(); |
| 849 | } |
| 850 | permSessServer.reset(); |
| 851 | |
| 852 | if (tempSessServer) |
| 853 | { |
| 854 | tempSessServer->close(); |
| 855 | tempSessServer->join(); |
| 856 | } |
| 857 | tempSessServer.reset(); |
| 858 | } |
| 859 | |
| 860 | void View::startViewing() |
| 861 | { |
| 862 | if (permSessServer) |
| 863 | { |
| 864 | permSessServer->bind(port: tcpPort); |
| 865 | permSessServer->listen(); |
| 866 | permSessServer->accept(); |
| 867 | } |
| 868 | |
| 869 | if (tempSessServer) |
| 870 | { |
| 871 | tempSessServer->bind(port: udpPort); |
| 872 | tempSessServer->receiveFrom(); |
| 873 | } |
| 874 | } |
| 875 | |
| 876 | void View::stopViewing() |
| 877 | { |
| 878 | const std::scoped_lock locks(daemonMtx, outputMtx); |
| 879 | isOngoing.store(i: false); |
| 880 | inResetting.store(i: false); |
| 881 | outputCompleted.store(i: true); |
| 882 | } |
| 883 | |
| 884 | void View::doToggle() |
| 885 | { |
| 886 | utility::benchmark::escape(p: this); |
| 887 | utility::benchmark::clobber(); |
| 888 | } |
| 889 | |
| 890 | void View::doRollback() |
| 891 | { |
| 892 | const std::scoped_lock locks(daemonMtx, outputMtx); |
| 893 | isOngoing.store(i: false); |
| 894 | |
| 895 | destroyViewServer(); |
| 896 | |
| 897 | inResetting.store(i: false); |
| 898 | outputCompleted.store(i: true); |
| 899 | } |
| 900 | |
| 901 | void View::notificationLoop() |
| 902 | { |
| 903 | while (isOngoing.load()) |
| 904 | { |
| 905 | std::unique_lock<std::mutex> daemonLock(daemonMtx); |
| 906 | daemonCond.wait(lock&: daemonLock, p: [this]() { return !isOngoing.load() || inResetting.load(); }); |
| 907 | if (inResetting.load()) |
| 908 | { |
| 909 | break; |
| 910 | } |
| 911 | |
| 912 | if (MACRO_IMPLIES(permSessServer, permSessServer->stopRequested()) |
| 913 | || MACRO_IMPLIES(tempSessServer, tempSessServer->stopRequested())) |
| 914 | { |
| 915 | throw std::runtime_error{"Found that the server did not work as expected." }; |
| 916 | } |
| 917 | } |
| 918 | } |
| 919 | |
| 920 | void View::awaitNotification2Proceed() |
| 921 | { |
| 922 | std::unique_lock<std::mutex> daemonLock(daemonMtx); |
| 923 | daemonCond.wait(lock&: daemonLock, p: [this]() { return isOngoing.load(); }); |
| 924 | } |
| 925 | |
| 926 | bool View::awaitNotification2Retry() |
| 927 | { |
| 928 | std::unique_lock<std::mutex> daemonLock(daemonMtx); |
| 929 | daemonCond.wait(lock&: daemonLock); |
| 930 | return inResetting.load(); |
| 931 | } |
| 932 | |
| 933 | //! @brief The operator (<<) overloading of the State enum. |
| 934 | //! @param os - output stream object |
| 935 | //! @param state - current state |
| 936 | //! @return reference of the output stream object |
| 937 | std::ostream& operator<<(std::ostream& os, const View::State state) |
| 938 | { |
| 939 | using enum View::State; |
| 940 | switch (state) |
| 941 | { |
| 942 | case initial: |
| 943 | os << "INITIAL" ; |
| 944 | break; |
| 945 | case active: |
| 946 | os << "ACTIVE" ; |
| 947 | break; |
| 948 | case established: |
| 949 | os << "ESTABLISHED" ; |
| 950 | break; |
| 951 | case inactive: |
| 952 | os << "INACTIVE" ; |
| 953 | break; |
| 954 | case idle: |
| 955 | os << "IDLE" ; |
| 956 | break; |
| 957 | default: |
| 958 | os << "UNKNOWN (" << static_cast<std::underlying_type_t<View::State>>(state) << ')'; |
| 959 | break; |
| 960 | } |
| 961 | return os; |
| 962 | } |
| 963 | |
| 964 | //! @brief Obtain the supported viewer options. |
| 965 | //! @return supported viewer options |
| 966 | auto obtainSupportedOptions() -> std::map<std::string, std::string> |
| 967 | { |
| 968 | using OptDepend = View::OptDepend; |
| 969 | using OptExecute = View::OptExecute; |
| 970 | using OptJournal = View::OptJournal; |
| 971 | using OptMonitor = View::OptMonitor; |
| 972 | using OptProfile = View::OptProfile; |
| 973 | return std::map<std::string, std::string>{ |
| 974 | {OptDepend::name, OptDepend::description}, |
| 975 | {OptExecute::name, OptExecute::description}, |
| 976 | {OptJournal::name, OptJournal::description}, |
| 977 | {OptMonitor::name, OptMonitor::description}, |
| 978 | {OptProfile::name, OptProfile::description}}; |
| 979 | } |
| 980 | |
| 981 | //! @brief Build the disconnect request message. |
| 982 | //! @return disconnect request message |
| 983 | std::string buildDisconnectRequest() |
| 984 | { |
| 985 | return utility::common::base64Encode(data: exitSymbol); |
| 986 | } |
| 987 | |
| 988 | //! @brief Latency in the millisecond range. |
| 989 | void interactionLatency() |
| 990 | { |
| 991 | constexpr auto latency = std::chrono::milliseconds{10}; |
| 992 | std::this_thread::sleep_for(rtime: latency); |
| 993 | } |
| 994 | |
| 995 | namespace intf |
| 996 | { |
| 997 | //! @brief Connect from the TCP client. |
| 998 | //! @param client - TCP client to be connected |
| 999 | template <> |
| 1000 | void connectFromClient(std::shared_ptr<utility::socket::TCPSocket>& client) |
| 1001 | { |
| 1002 | View::getInstance()->onConnecting(client); |
| 1003 | } |
| 1004 | |
| 1005 | //! @brief Connect from the UDP client. |
| 1006 | //! @param client - UDP client to be connected |
| 1007 | template <> |
| 1008 | void connectFromClient(std::shared_ptr<utility::socket::UDPSocket>& client) |
| 1009 | { |
| 1010 | View::getInstance()->onConnecting(client); |
| 1011 | } |
| 1012 | |
| 1013 | //! @brief Forward message by TCP client. |
| 1014 | //! @param client - TCP client to be used for forwarding |
| 1015 | //! @param inputs - input strings to be forwarded |
| 1016 | template <> |
| 1017 | void forwardByClient(std::shared_ptr<utility::socket::TCPSocket>& client, const std::vector<std::string>& inputs) |
| 1018 | { |
| 1019 | View::getInstance()->onForwarding(client, inputs); |
| 1020 | } |
| 1021 | |
| 1022 | //! @brief Forward message by UDP client. |
| 1023 | //! @param client - UDP client to be used for forwarding |
| 1024 | //! @param inputs - input strings to be forwarded |
| 1025 | template <> |
| 1026 | void forwardByClient(std::shared_ptr<utility::socket::UDPSocket>& client, const std::vector<std::string>& inputs) |
| 1027 | { |
| 1028 | View::getInstance()->onForwarding(client, inputs); |
| 1029 | } |
| 1030 | } // namespace intf |
| 1031 | } // namespace view |
| 1032 | |
| 1033 | //! @brief Wait for the viewer to start. |
| 1034 | template <> |
| 1035 | void view::AccessController::startup() const |
| 1036 | try |
| 1037 | { |
| 1038 | using View = view::View; |
| 1039 | using State = View::State; |
| 1040 | srv->syncWaitOr( |
| 1041 | state: State::active, handling: []() { throw std::runtime_error{"The " + View::name + " did not set up successfully ..." }; }); |
| 1042 | srv->syncNotifyVia(action: [this]() { srv->isOngoing.store(i: true); }); |
| 1043 | srv->syncWaitOr( |
| 1044 | state: State::established, |
| 1045 | handling: []() { throw std::runtime_error{"The " + View::name + " did not start successfully ..." }; }); |
| 1046 | } |
| 1047 | catch (const std::exception& err) |
| 1048 | { |
| 1049 | LOG_ERR << err.what(); |
| 1050 | } |
| 1051 | |
| 1052 | //! @brief Wait for the viewer to stop. |
| 1053 | template <> |
| 1054 | void view::AccessController::shutdown() const |
| 1055 | try |
| 1056 | { |
| 1057 | using View = view::View; |
| 1058 | using State = View::State; |
| 1059 | srv->syncNotifyVia(action: [this]() { srv->isOngoing.store(i: false); }); |
| 1060 | srv->syncWaitOr( |
| 1061 | state: State::inactive, handling: []() { throw std::runtime_error{"The " + View::name + " did not stop successfully ..." }; }); |
| 1062 | } |
| 1063 | catch (const std::exception& err) |
| 1064 | { |
| 1065 | LOG_ERR << err.what(); |
| 1066 | } |
| 1067 | |
| 1068 | //! @brief Request to reset the viewer. |
| 1069 | template <> |
| 1070 | void view::AccessController::reload() const |
| 1071 | try |
| 1072 | { |
| 1073 | using View = view::View; |
| 1074 | srv->syncNotifyVia(action: [this]() { srv->inResetting.store(i: true); }); |
| 1075 | srv->syncCountdownIf( |
| 1076 | condition: [this]() { return srv->inResetting.load(); }, |
| 1077 | handling: [this]() |
| 1078 | { |
| 1079 | throw std::runtime_error{ |
| 1080 | "The " + View::name + " did not reset properly in " + std::to_string(val: srv->timeoutPeriod) + " ms ..." }; |
| 1081 | }); |
| 1082 | } |
| 1083 | catch (const std::exception& err) |
| 1084 | { |
| 1085 | LOG_ERR << err.what(); |
| 1086 | } |
| 1087 | } // namespace application |
| 1088 | |