1//! @file main.hpp
2//! @author ryftchen
3//! @brief The declarations (main) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved.
6
7#pragma once
8
9#ifndef _PRECOMPILED_HEADER
10#include <cxxabi.h>
11#include <dlfcn.h>
12#include <execinfo.h>
13#include <array>
14#include <csignal>
15#include <filesystem>
16#else
17#include "application/pch/precompiled_header.hpp"
18#endif // _PRECOMPILED_HEADER
19
20//! @brief The application module.
21namespace application
22{
23//! @brief Status of the signal.
24static volatile std::sig_atomic_t signalStatus = 0;
25
26//! @brief Get the executable name.
27//! @return executable name
28[[gnu::always_inline]] inline static std::string executableName()
29{
30 return std::filesystem::canonical(p: std::filesystem::path{"/proc/self/exe"}).filename().string();
31}
32
33// NOLINTBEGIN(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory)
34//! @brief Signal handler for SIGSEGV signal, etc.
35//! @param sig - signal type
36static void signalHandler(const int sig)
37{
38 signalStatus = sig;
39 constexpr std::uint16_t maxFrame = 128;
40 std::array<void*, maxFrame> callStack{};
41 const int numOfFrame = ::backtrace(array: callStack.data(), size: callStack.size());
42 char** const symbols = ::backtrace_symbols(array: callStack.data(), size: numOfFrame);
43
44 std::array<char, 1024> buffer{};
45 std::ostringstream originalTrace{};
46 std::ostringstream detailedTrace{};
47 for (int i = 1; i < numOfFrame; ++i)
48 {
49 originalTrace << symbols[i] << '\n';
50 ::Dl_info info{};
51 if (::dladdr(address: callStack[i], info: &info) && info.dli_sname)
52 {
53 char* demangle = nullptr;
54 int status = -1;
55 if (info.dli_sname[0] == '_')
56 {
57 demangle = ::abi::__cxa_demangle(mangled_name: info.dli_sname, output_buffer: nullptr, length: nullptr, status: &status);
58 }
59 std::snprintf(
60 s: buffer.data(),
61 maxlen: buffer.size(),
62 format: "%-3d %*p %.960s + %zd\n",
63 i,
64 static_cast<int>(2 + (sizeof(void*) * 2)),
65 callStack[i],
66 (status == 0) ? demangle : (info.dli_sname ? info.dli_sname : symbols[i]),
67 static_cast<char*>(callStack[i]) - static_cast<char*>(info.dli_saddr));
68 std::free(ptr: demangle);
69 }
70 else
71 {
72 std::snprintf(
73 s: buffer.data(),
74 maxlen: buffer.size(),
75 format: "%-3d %*p %.960s\n",
76 i,
77 static_cast<int>(2 + (sizeof(void*) * 2)),
78 callStack[i],
79 symbols[i]);
80 }
81 detailedTrace << buffer.data();
82 }
83 std::free(ptr: static_cast<void*>(symbols));
84
85 if (numOfFrame == maxFrame)
86 {
87 detailedTrace << "\n<TRUNCATED>\n";
88 }
89 std::fprintf(
90 stream: ::stderr,
91 format: "%s: Crash occurred.\n\n<SIGNAL>\n%d\n\n<BACKTRACE>\n%s\n<VERBOSE>\n%s\n",
92 executableName().c_str(),
93 sig,
94 originalTrace.str().c_str(),
95 detailedTrace.str().c_str());
96
97 std::signal(sig: sig, SIG_DFL);
98 std::raise(sig: sig);
99}
100// NOLINTEND(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory)
101
102// NOLINTBEGIN(concurrency-mt-unsafe)
103//! @brief The constructor function before starting the main function. Switch to the target path.
104[[using gnu: constructor, noinline]] static void onInitial()
105{
106 std::signal(SIGABRT, handler: signalHandler);
107 std::signal(SIGSEGV, handler: signalHandler);
108 ::setenv(name: "TERM", value: "linux", replace: true);
109 ::setenv(name: "TERMINFO", value: "/etc/terminfo", replace: true);
110
111 const char* const homeEnv = std::getenv(name: "HOME");
112 const std::string_view defaultHome = homeEnv ? homeEnv : "/root";
113 const auto homePath = std::filesystem::absolute(p: defaultHome);
114 if (!std::filesystem::is_directory(p: homePath))
115 {
116 std::fprintf(stream: ::stdout, format: "%s: Could not find the home directory.\n", executableName().c_str());
117 std::exit(EXIT_FAILURE);
118 }
119
120 const auto processPath = homePath / ".foo";
121 if (!std::filesystem::is_directory(p: processPath))
122 {
123 std::filesystem::create_directory(p: processPath);
124 std::filesystem::permissions(
125 p: processPath, prms: std::filesystem::perms::owner_all, opts: std::filesystem::perm_options::add);
126 }
127 ::setenv(name: "FOO_HOME", value: processPath.string().c_str(), replace: true);
128}
129// NOLINTEND(concurrency-mt-unsafe)
130
131//! @brief The destructor function before finishing the main function. Check the signal status.
132[[using gnu: destructor, noinline]] static void onFinal()
133{
134 if (signalStatus != 0)
135 {
136 std::fprintf(stream: ::stdout, format: "%s: Signal %d was the last signal received.\n", executableName().c_str(), signalStatus);
137 }
138}
139} // namespace application
140