--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tizen-core/unix_signal.h"
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <unistd.h>
+
+#include <memory>
+#include <unordered_map>
+
+#include "tizen-core/context_manager.h"
+#include "tizen-core/log_private.h"
+
+namespace tizen_core {
+namespace {
+
+class UnixSignalHandler {
+ public:
+ class IEvent {
+ public:
+ virtual ~IEvent() = default;
+ virtual void OnUnixSignal(int signo) = 0;
+ };
+
+ UnixSignalHandler(int signo, IEvent* listener);
+ ~UnixSignalHandler();
+
+ int SendSigInfo(siginfo_t* info);
+ int ReceiveSigInfo(siginfo_t* info);
+ void InvokePreviousSigAction(int signo, siginfo_t* info, void* context);
+
+ private:
+ static gboolean UnixFdSourceCb(gint fd, GIOCondition condition,
+ gpointer user_data);
+
+ private:
+ guint source_ = 0;
+ int pipe_fd_[2] = { -1, -1 };
+ int signo_;
+ IEvent* listener_;
+ struct sigaction prev_action_;
+};
+
+class UnixSignalImpl : public UnixSignalHandler::IEvent {
+ public:
+ UnixSignalImpl() {
+ signals_ = {SIGPIPE, SIGALRM, SIGCHLD, SIGUSR1, SIGUSR2,
+ SIGHUP, SIGQUIT, SIGINT, SIGTERM};
+ sigemptyset(&oldset_);
+ }
+
+ virtual ~UnixSignalImpl() { Shutdown(); }
+
+ void Init() {
+ sigset_t newset;
+ sigemptyset(&newset);
+ for (int signo : signals_) {
+ try {
+ signal_handlers_[signo] =
+ std::make_unique<UnixSignalHandler>(signo, this);
+ } catch (const std::runtime_error& e) {
+ _E("Exception occurs. %s", e.what());
+ }
+
+ sigaddset(&newset, signo);
+ }
+
+ pthread_atfork(OnPrepareAtfork, OnParentAtfork, OnChildAtfork);
+ pthread_sigmask(SIG_UNBLOCK, &newset, nullptr);
+ disposed_ = false;
+ }
+
+ void Shutdown() {
+ if (disposed_) return;
+ sigset_t newset;
+ sigemptyset(&newset);
+ for (auto signo : signals_) sigaddset(&newset, signo);
+ pthread_sigmask(SIG_BLOCK, &newset, nullptr);
+ ClearSignalHandlers();
+ disposed_ = true;
+ }
+
+ void BlockSignals() {
+ sigset_t newset;
+ sigemptyset(&newset);
+ for (auto signo : signals_) sigaddset(&newset, signo);
+
+ if (sigprocmask(SIG_BLOCK, &newset, &oldset_) != 0)
+ _E("sigprocmask() is failed. errno=%d", errno);
+ }
+
+ void UnblockSignals() {
+ if (sigprocmask(SIG_SETMASK, &oldset_, nullptr) != 0)
+ _E("sigprocmask() is failed. errno=%d", errno);
+ }
+
+ void ClearSignalHandlers() { signal_handlers_.clear(); }
+
+ UnixSignalHandler* GetSignalHandler(int signo) {
+ return signal_handlers_[signo].get();
+ }
+
+ bool IsDisposed() const { return disposed_; }
+
+ private:
+ void OnUnixSignal(int signo) override {
+ _E("signo=%d", signo);
+ if (signo == SIGCHLD)
+ HandleSigchld();
+ else if (signo == SIGUSR1 || signo == SIGUSR2 || signo == SIGHUP ||
+ signo == SIGQUIT || signo == SIGINT || signo == SIGTERM)
+ QuitMainLoop();
+ }
+
+ void HandleSigchld() {
+ pid_t pid;
+ int status;
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ _E("Child process %d exited with status %d", pid, status);
+ }
+ }
+
+ void QuitMainLoop() {
+ auto context = ContextManager::GetInst().FindFromThisThread();
+ if (context != nullptr) {
+ auto loop = context->GetLoop();
+ if (loop) {
+ loop->Quit();
+ return;
+ }
+ }
+
+ _E("exit()");
+ exit(-1);
+ }
+
+ static void OnPrepareAtfork();
+ static void OnParentAtfork();
+ static void OnChildAtfork();
+
+ private:
+ bool disposed_ = true;
+ std::vector<int> signals_;
+ std::unordered_map<int, std::unique_ptr<UnixSignalHandler>> signal_handlers_;
+ sigset_t oldset_;
+};
+
+UnixSignalImpl impl;
+
+void SignalHandler(int signo, siginfo_t* info, void* context) {
+ if (impl.IsDisposed()) return;
+
+ auto* handler = impl.GetSignalHandler(signo);
+ handler->SendSigInfo(info);
+ handler->InvokePreviousSigAction(signo, info, context);
+}
+
+UnixSignalHandler::UnixSignalHandler(int signo,
+ UnixSignalHandler::IEvent* listener)
+ : signo_(signo), listener_(listener) {
+ if (pipe2(pipe_fd_, O_CLOEXEC | O_NONBLOCK) != 0) {
+ _E("pipe2() is failed. errno=%d", errno);
+ throw std::runtime_error("pipe2() is failed.");
+ }
+
+ source_ = g_unix_fd_add(pipe_fd_[0], G_IO_IN, UnixFdSourceCb, this);
+ if (source_ == 0) {
+ _E("g_unix_fd_add() is failed. errno=%d", errno);
+ close(pipe_fd_[1]);
+ close(pipe_fd_[0]);
+ throw std::runtime_error("g_unix_fd_add() is failed.");
+ }
+
+ struct sigaction action;
+ action.sa_sigaction = SignalHandler;
+ action.sa_flags = SA_RESTART | SA_SIGINFO;
+ sigemptyset(&action.sa_mask);
+ sigaction(signo_, &action, &prev_action_);
+}
+
+UnixSignalHandler::~UnixSignalHandler() {
+ struct sigaction action;
+ action.sa_handler = SIG_DFL;
+ action.sa_flags = 0;
+ sigemptyset(&action.sa_mask);
+ sigaction(signo_, &action, nullptr);
+
+ if (source_ != 0)
+ g_source_remove(source_);
+
+ close(pipe_fd_[0]);
+ close(pipe_fd_[1]);
+}
+
+int UnixSignalHandler::SendSigInfo(siginfo_t* info) {
+ unsigned char* buffer = reinterpret_cast<unsigned char*>(info);
+ ssize_t len = sizeof(siginfo_t);
+ while (len) {
+ ssize_t bytes = write(pipe_fd_[1], buffer, len);
+ if (bytes < 0) {
+ if (errno == EINTR) continue;
+
+ _E("write() is failed. pipe_fd=%d, errno=%d", pipe_fd_[1], errno);
+ return -1;
+ }
+
+ len -= bytes;
+ buffer += bytes;
+ }
+
+ return 0;
+}
+
+int UnixSignalHandler::ReceiveSigInfo(siginfo_t* info) {
+ unsigned char* buffer = reinterpret_cast<unsigned char*>(info);
+ ssize_t len = sizeof(siginfo_t);
+ while (len) {
+ ssize_t bytes = read(pipe_fd_[0], buffer, len);
+ if (bytes == 0) {
+ _E("EOF. pipe_fd=%d", pipe_fd_[0]);
+ return -1;
+ }
+
+ if (bytes < 0) {
+ if (errno == EINTR) continue;
+
+ _E("read() failed. pipe_fd=%d, errno=%d", pipe_fd_[0], errno);
+ return -1;
+ }
+
+ len -= bytes;
+ buffer += bytes;
+ }
+
+ return 0;
+}
+
+void UnixSignalHandler::InvokePreviousSigAction(int signo, siginfo_t* info,
+ void* context) {
+ if ((prev_action_.sa_flags & SA_SIGINFO) == 0 &&
+ prev_action_.sa_handler == SIG_DFL)
+ return;
+
+ if ((prev_action_.sa_flags & SA_SIGINFO) == 0 &&
+ prev_action_.sa_handler == SIG_IGN)
+ return;
+
+ if (prev_action_.sa_flags & SA_SIGINFO) {
+ _E("sa_sigaction(%d, %p, %p)", signo, info, context);
+ if (prev_action_.sa_sigaction != nullptr)
+ prev_action_.sa_sigaction(signo, info, context);
+ } else {
+ _E("sa_handler(%d)", signo);
+ if (prev_action_.sa_handler!= nullptr)
+ prev_action_.sa_handler(signo);
+ }
+}
+
+gboolean UnixSignalHandler::UnixFdSourceCb(gint fd, GIOCondition condtion,
+ gpointer user_data) {
+ siginfo_t info;
+ auto* handler = static_cast<UnixSignalHandler*>(user_data);
+ if (handler->ReceiveSigInfo(&info) != 0) {
+ handler->source_ = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ auto* listener = handler->listener_;
+ listener->OnUnixSignal(info.si_signo);
+ return G_SOURCE_CONTINUE;
+}
+
+void UnixSignalImpl::OnPrepareAtfork() { impl.BlockSignals(); }
+
+void UnixSignalImpl::OnParentAtfork() { impl.UnblockSignals(); }
+
+void UnixSignalImpl::OnChildAtfork() { impl.ClearSignalHandlers(); }
+
+} // namespace
+
+void UnixSignal::Init() { impl.Init(); }
+
+void UnixSignal::Shutdown() { impl.Shutdown(); }
+
+} // namespace tizen_core