From 2bec5038595ee81dee3f5c45221af1ef50611ac6 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 11 Dec 2024 17:36:33 +0200 Subject: [PATCH 01/14] initial source code structure --- .clang-format | 86 +++++++++++++++++++++++++ .clang-tidy | 37 +++++++++++ .cspell/custom-dictionary-workspace.txt | 2 + .github/workflows/tests.yml | 0 .gitignore | 15 +++++ .vscode/settings.json | 10 +++ CMakeLists.txt | 21 ++++++ src/CMakeLists.txt | 10 +++ src/cli/CMakeLists.txt | 6 ++ src/common/CMakeLists.txt | 6 ++ src/daemon/CMakeLists.txt | 8 +++ src/daemon/main.cpp | 8 +++ test/CMakeLists.txt | 10 +++ test/cli/CMakeLists.txt | 6 ++ test/common/CMakeLists.txt | 6 ++ test/daemon/CMakeLists.txt | 6 ++ 16 files changed, 237 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .cspell/custom-dictionary-workspace.txt create mode 100644 .github/workflows/tests.yml create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/cli/CMakeLists.txt create mode 100644 src/common/CMakeLists.txt create mode 100644 src/daemon/CMakeLists.txt create mode 100644 src/daemon/main.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/cli/CMakeLists.txt create mode 100644 test/common/CMakeLists.txt create mode 100644 test/daemon/CMakeLists.txt diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..276b656 --- /dev/null +++ b/.clang-format @@ -0,0 +1,86 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BraceWrapping: + SplitEmptyRecord: false + AfterEnum: true + AfterStruct: true + AfterClass: true + AfterControlStatement: true + AfterFunction: true + AfterUnion: true + AfterNamespace: true + AfterExternBlock: true + BeforeElse: true + +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ (coverity|NOSONAR|pragma:)' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +FixNamespaceComments: true +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeBlocks: Preserve +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 10000 # Raised intentionally; prefer breaking all +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 10000 # Raised intentionally because it hurts readability +PointerAlignment: Left +ReflowComments: true +SortIncludes: Never +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++14 +TabWidth: 8 +UseTab: Never +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..99d9c05 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,37 @@ +Checks: >- + boost-*, + bugprone-*, + cert-*, + clang-analyzer-*, + cppcoreguidelines-*, + google-*, + hicpp-*, + llvm-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -clang-analyzer-core.uninitialized.Assign, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-use-default-member-init, + -google-readability-avoid-underscore-in-googletest-name, + -google-readability-todo, + -llvm-header-guard, + -modernize-concat-nested-namespaces, + -modernize-type-traits, + -modernize-use-constraints, + -modernize-use-default-member-init, + -modernize-use-nodiscard, + -readability-avoid-const-params-in-decls, + -readability-identifier-length, + -*-use-trailing-return-type, + -*-named-parameter, +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: '90' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;5;8;10;16;20;32;60;64;100;128;256;500;512;1000' +WarningsAsErrors: '*' +HeaderFilterRegex: 'include/libcyphal/.*\.hpp' +FormatStyle: file diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt new file mode 100644 index 0000000..c8eb7d9 --- /dev/null +++ b/.cspell/custom-dictionary-workspace.txt @@ -0,0 +1,2 @@ +# Custom Dictionary Words +ocvsmd diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 259148f..3f4f3be 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,18 @@ *.exe *.out *.app + +# Build folders +**/build/ +**/build_* + +# JetBrains +.idea/* +cmake-build-*/ + +# Python +.venv + +# Dumb OS crap +.DS_Store +*.bak diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..71e9b05 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "cSpell.customDictionaries": { + "custom-dictionary-workspace": { + "name": "custom-dictionary-workspace", + "path": "${workspaceFolder:opencyphal-vehicle-system-management-daemon}/.cspell/custom-dictionary-workspace.txt", + "addWords": true, + "scope": "workspace" + } + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7643d3d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +project(ocvsmd + VERSION ${OCVSMD_VERSION} + LANGUAGES CXX + HOMEPAGE_URL https://github.com/OpenCyphal-Garage/opencyphal-vehicle-system-management-daemon +) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set the output binary directory +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7acc51f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +add_subdirectory(common) +add_subdirectory(daemon) +add_subdirectory(cli) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 0000000..abc8902 --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..abc8902 --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt new file mode 100644 index 0000000..c1a837f --- /dev/null +++ b/src/daemon/CMakeLists.txt @@ -0,0 +1,8 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +add_executable(ocvsmd main.cpp) diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp new file mode 100644 index 0000000..4e9a5f2 --- /dev/null +++ b/src/daemon/main.cpp @@ -0,0 +1,8 @@ +#include + +int main(const int argc, const char** const argv) +{ + (void) argc; + (void) argv; + return EXIT_SUCCESS; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..7acc51f --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) + +add_subdirectory(common) +add_subdirectory(daemon) +add_subdirectory(cli) diff --git a/test/cli/CMakeLists.txt b/test/cli/CMakeLists.txt new file mode 100644 index 0000000..abc8902 --- /dev/null +++ b/test/cli/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt new file mode 100644 index 0000000..abc8902 --- /dev/null +++ b/test/common/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file diff --git a/test/daemon/CMakeLists.txt b/test/daemon/CMakeLists.txt new file mode 100644 index 0000000..abc8902 --- /dev/null +++ b/test/daemon/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT +# + +cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file From 39f3357cc0287f7e71c01bc1779619598228c96c Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 11 Dec 2024 17:53:22 +0200 Subject: [PATCH 02/14] added clang-format --- CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7643d3d..882e9d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,5 +17,19 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Set the output binary directory set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(src_dir "${CMAKE_SOURCE_DIR}/src") +set(test_dir "${CMAKE_SOURCE_DIR}/test") +set(include_dir "${CMAKE_SOURCE_DIR}/include") + +# clang-format +find_program(clang_format NAMES clang-format) +if (NOT clang_format) + message(STATUS "Could not locate clang-format") +else () + file(GLOB format_files ${include_dir}/**/*.[ch]pp ${src_dir}/**/*.[ch]pp ${test_dir}/**/*.[ch]pp) + message(STATUS "Using clang-format: ${clang_format}; files: ${format_files}") + add_custom_target(format COMMAND ${clang_format} -i -fallback-style=none -style=file --verbose ${format_files}) +endif () + add_subdirectory(src) add_subdirectory(test) From c4b03efbd83d3c7d81fbd3514d5a33c904d1026a Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 11 Dec 2024 20:08:11 +0200 Subject: [PATCH 03/14] implemented steps 1...8 --- .gitignore | 33 -------------- init.d/ocvsmd | 57 +++++++++++++++++++++++ src/daemon/main.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 33 deletions(-) create mode 100755 init.d/ocvsmd diff --git a/.gitignore b/.gitignore index 3f4f3be..a12b348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,3 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - # Build folders **/build/ **/build_* diff --git a/init.d/ocvsmd b/init.d/ocvsmd new file mode 100755 index 0000000..4b998ca --- /dev/null +++ b/init.d/ocvsmd @@ -0,0 +1,57 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: ocvsmd +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: SysV script for ocvsmd +# Description: This service manages the OpenCyphal Vehicle System Management Daemon (OCVSMD). +### END INIT INFO + +DAEMON_NAME="ocvsmd" +DAEMON_PATH="/usr/local/bin/ocvsmd" +PIDFILE="/var/run/${DAEMON_NAME}.pid" + +case "$1" in + start) + echo "Starting $DAEMON_NAME..." + if [ -f $PIDFILE ]; then + echo "$DAEMON_NAME is already running." + exit 1 + fi + start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON_PATH + echo "$DAEMON_NAME started." + ;; + stop) + echo "Stopping $DAEMON_NAME..." + if [ ! -f $PIDFILE ]; then + echo "$DAEMON_NAME is not running." + exit 1 + fi + start-stop-daemon --stop --quiet --pidfile $PIDFILE + rm -f $PIDFILE + echo "$DAEMON_NAME stopped." + ;; + restart) + $0 stop + $0 start + ;; + status) + if [ -f $PIDFILE ]; then + PID=$(cat $PIDFILE) + if kill -0 "$PID" >/dev/null 2>&1; then + echo "$DAEMON_NAME is running (PID $PID)." + else + echo "$DAEMON_NAME is not running, but pidfile exists." + fi + else + echo "$DAEMON_NAME is not running." + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 4e9a5f2..3eb3743 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,8 +1,117 @@ +#include +#include #include +#include +#include +#include +#include + +namespace +{ + +void fork_and_exit_parent() +{ + // Fork off the parent process + const pid_t pid = fork(); + if (pid < 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to fork: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + if (pid > 0) + { + ::exit(EXIT_SUCCESS); + } +} + +void step_01_close_all_file_descriptors() +{ + rlimit rlimit_files{}; + if (getrlimit(RLIMIT_NOFILE, &rlimit_files) != 0) + { + const char* const err_txt = strerror(errno); + std::cerr << "Failed to getrlimit(RLIMIT_NOFILE): " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + constexpr int first_fd_to_close = 3; // 0, 1 & 2 for standard input, output, and error. + for (int fd = first_fd_to_close; fd <= rlimit_files.rlim_max; ++fd) + { + (void) ::close(fd); + } +} + +void step_02_reset_all_signal_handlers_to_default() +{ + for (int sig = 1; sig < _NSIG; ++sig) + { + (void) ::signal(sig, SIG_DFL); + } +} + +void step_03_reset_signal_mask() +{ + sigset_t sigset_all{}; + if (::sigfillset(&sigset_all) != 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to sigfillset(): " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + if (::sigprocmask(SIG_SETMASK, &sigset_all, nullptr) != 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to sigprocmask(SIG_SETMASK): " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } +} + +void step_04_sanitize_environment() +{ + // TODO: Implement this step. +} + +void step_05_fork_to_background() +{ + fork_and_exit_parent(); +} + +void step_06_create_new_session() +{ + if (::setsid() < 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to setsid: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } +} + +void step_07_08_fork_and_exit_again() +{ + fork_and_exit_parent(); +} + +/// Implements the daemonization procedure as described in the `man 7 daemon` manual page. +/// +void daemonize() +{ + step_01_close_all_file_descriptors(); + step_02_reset_all_signal_handlers_to_default(); + step_03_reset_signal_mask(); + step_04_sanitize_environment(); + step_05_fork_to_background(); + step_06_create_new_session(); + step_07_08_fork_and_exit_again(); +} + +} // namespace int main(const int argc, const char** const argv) { (void) argc; (void) argv; + + daemonize(); + return EXIT_SUCCESS; } From 963a8bf09b23d9a5631384a14349c68c01eb23dd Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Wed, 11 Dec 2024 21:59:18 +0200 Subject: [PATCH 04/14] implemented steps 9...15 --- README.md | 34 +++++++++- src/daemon/main.cpp | 159 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 170 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 120fecf..f58b447 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ # OpenCyphal Vehicle System Management Daemon -👻 +### Build + +``` +cd ocvsmd +mkdir build && cd build +cmake .. +make ocvsmd +``` + +### Installing + +#### Installing the Daemon Binary: +``` +sudo cp bin/ocvsmd /usr/local/bin/ocvsmd +``` + +#### Installing the Init Script: +``` +sudo cp ../init.d/ocvsmd /etc/init.d/ocvsmd +sudo chmod +x /etc/init.d/ocvsmd +``` + +#### Enabling at Startup (on SysV-based systems): +``` +sudo update-rc.d ocvsmd defaults +``` + +### Usage +``` +sudo /etc/init.d/ocvsmd start +sudo /etc/init.d/ocvsmd status +sudo /etc/init.d/ocvsmd stop +``` diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 3eb3743..ac913dd 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,14 +1,20 @@ +#include #include #include #include #include +#include #include #include +#include +#include #include namespace { +volatile bool s_running = true; + void fork_and_exit_parent() { // Fork off the parent process @@ -25,6 +31,19 @@ void fork_and_exit_parent() } } +void handle_signal(const int sig) +{ + switch (sig) + { + case SIGTERM: + case SIGINT: + s_running = false; + break; + default: + break; + } +} + void step_01_close_all_file_descriptors() { rlimit rlimit_files{}; @@ -41,54 +60,131 @@ void step_01_close_all_file_descriptors() } } -void step_02_reset_all_signal_handlers_to_default() +void step_02_03_setup_signal_handlers() { - for (int sig = 1; sig < _NSIG; ++sig) - { - (void) ::signal(sig, SIG_DFL); - } + // Catch termination signals + (void) ::signal(SIGTERM, handle_signal); + (void) ::signal(SIGINT, handle_signal); +} + +void step_04_sanitize_environment() +{ + // TODO: Implement this step. +} + +void step_05_fork_to_background() +{ + fork_and_exit_parent(); } -void step_03_reset_signal_mask() +void step_06_create_new_session() { - sigset_t sigset_all{}; - if (::sigfillset(&sigset_all) != 0) + if (::setsid() < 0) { const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to sigfillset(): " << err_txt << "\n"; + std::cerr << "Failed to setsid: " << err_txt << "\n"; ::exit(EXIT_FAILURE); } - if (::sigprocmask(SIG_SETMASK, &sigset_all, nullptr) != 0) +} + +void step_07_08_fork_and_exit_again() +{ + fork_and_exit_parent(); +} + +void step_09_redirect_stdio_to_devnull() +{ + const int fd = ::open("/dev/null", O_RDWR); + if (fd == -1) { const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to sigprocmask(SIG_SETMASK): " << err_txt << "\n"; + std::cerr << "Failed to open(/dev/null): " << err_txt << "\n"; ::exit(EXIT_FAILURE); } + + ::dup2(fd, STDIN_FILENO); + ::dup2(fd, STDOUT_FILENO); + ::dup2(fd, STDERR_FILENO); + + if (fd > 2) + { + ::close(fd); + } } -void step_04_sanitize_environment() +void step_10_reset_umask() { - // TODO: Implement this step. + ::umask(0); } -void step_05_fork_to_background() +void step_11_change_curr_dir() { - fork_and_exit_parent(); + if (::chdir("/") != 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to chdir(/): " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } } -void step_06_create_new_session() +void step_12_create_pid_file(const char* const pid_file_name) { - if (::setsid() < 0) + const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); + if (fd == -1) { const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to setsid: " << err_txt << "\n"; + std::cerr << "Failed to create on PID file: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + + if (::lockf(fd, F_TLOCK, 0) == -1) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to lock PID file: " << err_txt << "\n"; + ::close(fd); + ::exit(EXIT_FAILURE); + } + + if (::ftruncate(fd, 0) != 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to ftruncate PID file: " << err_txt << "\n"; + ::close(fd); + ::exit(EXIT_FAILURE); + } + + std::array buf{}; + const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); + if (::write(fd, buf.data(), len) != len) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to write to PID file: " << err_txt << "\n"; + ::close(fd); ::exit(EXIT_FAILURE); } + + // Keep the PID file open until the process exits. } -void step_07_08_fork_and_exit_again() +void step_13_drop_privileges() { - fork_and_exit_parent(); + // n the daemon process, drop privileges, if possible and applicable. + // TODO: Implement this step. +} + +void step_14_notify_init_complete() +{ + // From the daemon process, notify the original process started that initialization is complete. This can be + // implemented via an unnamed pipe or similar communication channel that is created before the first fork() and + // hence available in both the original and the daemon process. + // TODO: Implement this step. +} + +void step_15_exit_org_process() +{ + // Call exit() in the original process. The process that invoked the daemon must be able to rely on that this exit() + // happens after initialization is complete and all external communication channels are established and accessible. + // TODO: Implement this step. } /// Implements the daemonization procedure as described in the `man 7 daemon` manual page. @@ -96,12 +192,20 @@ void step_07_08_fork_and_exit_again() void daemonize() { step_01_close_all_file_descriptors(); - step_02_reset_all_signal_handlers_to_default(); - step_03_reset_signal_mask(); + step_02_03_setup_signal_handlers(); step_04_sanitize_environment(); step_05_fork_to_background(); step_06_create_new_session(); step_07_08_fork_and_exit_again(); + step_09_redirect_stdio_to_devnull(); + step_10_reset_umask(); + step_11_change_curr_dir(); + step_12_create_pid_file("/var/run/ocvsmd.pid"); + step_13_drop_privileges(); + step_14_notify_init_complete(); + step_15_exit_org_process(); + + ::openlog("ocvsmd", LOG_PID, LOG_DAEMON); } } // namespace @@ -113,5 +217,16 @@ int main(const int argc, const char** const argv) daemonize(); + ::syslog(LOG_NOTICE, "ocvsmd daemon started."); + + while (s_running) + { + // TODO: Insert daemon code here. + ::sleep(1); + } + + ::syslog(LOG_NOTICE, "ocvsmd daemon terminated."); + ::closelog(); + return EXIT_SUCCESS; } From ef6a78245577c4790baaec1548eb5447ee1f1040 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 13:24:01 +0200 Subject: [PATCH 05/14] minor lint fixes --- src/daemon/main.cpp | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index ac913dd..68dbde1 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,19 +1,33 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include namespace { -volatile bool s_running = true; +volatile int s_running = 1; // NOLINT + +extern "C" void handle_signal(const int sig) +{ + switch (sig) + { + case SIGTERM: + case SIGINT: + s_running = 0; + break; + default: + break; + } +} void fork_and_exit_parent() { @@ -31,19 +45,6 @@ void fork_and_exit_parent() } } -void handle_signal(const int sig) -{ - switch (sig) - { - case SIGTERM: - case SIGINT: - s_running = false; - break; - default: - break; - } -} - void step_01_close_all_file_descriptors() { rlimit rlimit_files{}; @@ -94,7 +95,7 @@ void step_07_08_fork_and_exit_again() void step_09_redirect_stdio_to_devnull() { - const int fd = ::open("/dev/null", O_RDWR); + const int fd = ::open("/dev/null", O_RDWR); // NOLINT if (fd == -1) { const char* const err_txt = ::strerror(errno); @@ -129,7 +130,7 @@ void step_11_change_curr_dir() void step_12_create_pid_file(const char* const pid_file_name) { - const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); + const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); // NOLINT if (fd == -1) { const char* const err_txt = ::strerror(errno); @@ -154,7 +155,7 @@ void step_12_create_pid_file(const char* const pid_file_name) } std::array buf{}; - const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); + const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); // NOLINT if (::write(fd, buf.data(), len) != len) { const char* const err_txt = ::strerror(errno); @@ -217,15 +218,15 @@ int main(const int argc, const char** const argv) daemonize(); - ::syslog(LOG_NOTICE, "ocvsmd daemon started."); + ::syslog(LOG_NOTICE, "ocvsmd daemon started."); // NOLINT - while (s_running) + while (s_running == 1) { // TODO: Insert daemon code here. ::sleep(1); } - ::syslog(LOG_NOTICE, "ocvsmd daemon terminated."); + ::syslog(LOG_NOTICE, "ocvsmd daemon terminated."); // NOLINT ::closelog(); return EXIT_SUCCESS; From aec45e656ea03bbc0b7652caf18097fc48fae9ef Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 13:41:59 +0200 Subject: [PATCH 06/14] try GH actions --- .github/workflows/tests.yml | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e69de29..cacf613 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -0,0 +1,94 @@ +name: Test Workflow +on: [push, pull_request] +env: + LLVM_VERSION: 15 +jobs: + debug: + if: github.event_name == 'push' + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: ['clang', 'gcc'] + include: + - toolchain: gcc + c-compiler: gcc + cxx-compiler: g++ + - toolchain: clang + c-compiler: clang + cxx-compiler: clang++ + steps: + - uses: actions/checkout@v4 + - run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh $LLVM_VERSION + sudo apt update -y && sudo apt upgrade -y + sudo apt-get -y install gcc-multilib g++-multilib clang-tidy-$LLVM_VERSION + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-$LLVM_VERSION 50 + clang-tidy --version + - run: > + cmake + -B ${{ github.workspace }}/build + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} + -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} + ocvsmd + - working-directory: ${{github.workspace}}/build + run: | + make VERBOSE=1 + make test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{github.job}} + path: ${{github.workspace}}/**/* + retention-days: 2 + + optimizations: + if: github.event_name == 'push' + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: ['clang', 'gcc'] + build_type: [Release, MinSizeRel] + include: + - toolchain: gcc + c-compiler: gcc + cxx-compiler: g++ + - toolchain: clang + c-compiler: clang + cxx-compiler: clang++ + steps: + - uses: actions/checkout@v4 + - run: | + sudo apt update -y && sudo apt upgrade -y + sudo apt install gcc-multilib g++-multilib + - run: > + cmake + -B ${{ github.workspace }}/build + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} + -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} + -DNO_STATIC_ANALYSIS=1 + ocvsmd + - working-directory: ${{github.workspace}}/build + run: | + make VERBOSE=1 + make test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{github.job}} + path: ${{github.workspace}}/**/* + retention-days: 2 + + style_check: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DoozyX/clang-format-lint-action@v0.17 + with: + source: './include ./test ./src' + extensions: 'c,h,cpp,hpp' + clangFormatVersion: ${{ env.LLVM_VERSION }} From d5007bcd60e821692a3ef69e2e90c11995efdf5b Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 13:48:59 +0200 Subject: [PATCH 07/14] try GH actions # 2 --- .github/workflows/tests.yml | 6 +++--- src/daemon/main.cpp | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cacf613..b8bb19c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: run: | make VERBOSE=1 make test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: ${{github.job}} @@ -75,7 +75,7 @@ jobs: run: | make VERBOSE=1 make test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: ${{github.job}} @@ -89,6 +89,6 @@ jobs: - uses: actions/checkout@v4 - uses: DoozyX/clang-format-lint-action@v0.17 with: - source: './include ./test ./src' + source: './test ./src' extensions: 'c,h,cpp,hpp' clangFormatVersion: ${{ env.LLVM_VERSION }} diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 68dbde1..5948d28 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,3 +1,8 @@ +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT +// + #include #include #include From b275694ac6b9c25e511bd17d121dc98718bb9f42 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 15:05:49 +0200 Subject: [PATCH 08/14] try GH actions # 3 --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8bb19c..05ab941 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: ${{github.job}} + name: ${{github.job}}_debug_${{matrix.toolchain}} path: ${{github.workspace}}/**/* retention-days: 2 @@ -78,7 +78,7 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: ${{github.job}} + name: ${{github.job}}_optimizations_${{matrix.toolchain}}_${{matrix.build_type}} path: ${{github.workspace}}/**/* retention-days: 2 From e57d37f222b0ef682c3c444e9e3a03db6dc5bd15 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 15:15:59 +0200 Subject: [PATCH 09/14] static analysis --- .github/workflows/tests.yml | 8 ++------ CMakeLists.txt | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 05ab941..8a51d88 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,9 +30,7 @@ jobs: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} - ocvsmd - working-directory: ${{github.workspace}}/build run: | make VERBOSE=1 @@ -40,7 +38,7 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: ${{github.job}}_debug_${{matrix.toolchain}} + name: ${{github.job}}_${{matrix.toolchain}} path: ${{github.workspace}}/**/* retention-days: 2 @@ -67,10 +65,8 @@ jobs: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} -DNO_STATIC_ANALYSIS=1 - ocvsmd - working-directory: ${{github.workspace}}/build run: | make VERBOSE=1 @@ -78,7 +74,7 @@ jobs: - uses: actions/upload-artifact@v4 if: always() with: - name: ${{github.job}}_optimizations_${{matrix.toolchain}}_${{matrix.build_type}} + name: ${{github.job}}_${{matrix.toolchain}}_${{matrix.build_type}} path: ${{github.workspace}}/**/* retention-days: 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 882e9d6..b6ffc90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ project(ocvsmd HOMEPAGE_URL https://github.com/OpenCyphal-Garage/opencyphal-vehicle-system-management-daemon ) +set(NO_STATIC_ANALYSIS OFF CACHE BOOL "disable static analysis") + set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -31,5 +33,17 @@ else () add_custom_target(format COMMAND ${clang_format} -i -fallback-style=none -style=file --verbose ${format_files}) endif () +# Use -DNO_STATIC_ANALYSIS=1 to suppress static analysis. +# If not suppressed, the tools used here shall be available, otherwise the build will fail. +if (NOT NO_STATIC_ANALYSIS) + # clang-tidy (separate config files per directory) + find_program(clang_tidy NAMES clang-tidy) + if (NOT clang_tidy) + message(FATAL_ERROR "Could not locate clang-tidy") + endif () + message(STATUS "Using clang-tidy: ${clang_tidy}") + set(CMAKE_CXX_CLANG_TIDY ${clang_tidy}) +endif() + add_subdirectory(src) add_subdirectory(test) From 12bff65781691477400bc0912eb9f9fdc1dd29e2 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 15:26:56 +0200 Subject: [PATCH 10/14] clang-tidy fixes --- CMakeLists.txt | 2 ++ src/daemon/main.cpp | 17 ++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6ffc90..34f0c4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ project(ocvsmd HOMEPAGE_URL https://github.com/OpenCyphal-Garage/opencyphal-vehicle-system-management-daemon ) +enable_testing() + set(NO_STATIC_ANALYSIS OFF CACHE BOOL "disable static analysis") set(CMAKE_CXX_STANDARD 14) diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 5948d28..a17f8d0 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include // NOLINT *-deprecated-headers for `pid_t` type #include #include #include @@ -19,7 +20,8 @@ namespace { -volatile int s_running = 1; // NOLINT +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +volatile int s_running = 1; extern "C" void handle_signal(const int sig) { @@ -100,7 +102,7 @@ void step_07_08_fork_and_exit_again() void step_09_redirect_stdio_to_devnull() { - const int fd = ::open("/dev/null", O_RDWR); // NOLINT + const int fd = ::open("/dev/null", O_RDWR); // NOLINT *-vararg if (fd == -1) { const char* const err_txt = ::strerror(errno); @@ -135,7 +137,7 @@ void step_11_change_curr_dir() void step_12_create_pid_file(const char* const pid_file_name) { - const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); // NOLINT + const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); // NOLINT *-vararg if (fd == -1) { const char* const err_txt = ::strerror(errno); @@ -159,8 +161,9 @@ void step_12_create_pid_file(const char* const pid_file_name) ::exit(EXIT_FAILURE); } - std::array buf{}; - const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); // NOLINT + constexpr std::size_t max_pid_str_len = 32; + std::array buf{}; + const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); // NOLINT *-vararg if (::write(fd, buf.data(), len) != len) { const char* const err_txt = ::strerror(errno); @@ -223,7 +226,7 @@ int main(const int argc, const char** const argv) daemonize(); - ::syslog(LOG_NOTICE, "ocvsmd daemon started."); // NOLINT + ::syslog(LOG_NOTICE, "ocvsmd daemon started."); // NOLINT *-vararg while (s_running == 1) { @@ -231,7 +234,7 @@ int main(const int argc, const char** const argv) ::sleep(1); } - ::syslog(LOG_NOTICE, "ocvsmd daemon terminated."); // NOLINT + ::syslog(LOG_NOTICE, "ocvsmd daemon terminated."); // NOLINT *-vararg ::closelog(); return EXIT_SUCCESS; From b464cec58aa7dea80a66d9aab7253e9a1bc5a2a2 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 15:37:40 +0200 Subject: [PATCH 11/14] cmake tidy --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34f0c4b..d40c0b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,6 @@ cmake_minimum_required(VERSION 3.22.0) project(ocvsmd - VERSION ${OCVSMD_VERSION} LANGUAGES CXX HOMEPAGE_URL https://github.com/OpenCyphal-Garage/opencyphal-vehicle-system-management-daemon ) From 6230df8bab9b6832e97fa594fcd1b230d4ecd632 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 15:46:36 +0200 Subject: [PATCH 12/14] minor fixes --- src/cli/CMakeLists.txt | 2 +- src/common/CMakeLists.txt | 2 +- test/cli/CMakeLists.txt | 2 +- test/common/CMakeLists.txt | 2 +- test/daemon/CMakeLists.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index abc8902..1a0980c 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file +cmake_minimum_required(VERSION 3.22.0) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index abc8902..1a0980c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file +cmake_minimum_required(VERSION 3.22.0) diff --git a/test/cli/CMakeLists.txt b/test/cli/CMakeLists.txt index abc8902..1a0980c 100644 --- a/test/cli/CMakeLists.txt +++ b/test/cli/CMakeLists.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file +cmake_minimum_required(VERSION 3.22.0) diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt index abc8902..1a0980c 100644 --- a/test/common/CMakeLists.txt +++ b/test/common/CMakeLists.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file +cmake_minimum_required(VERSION 3.22.0) diff --git a/test/daemon/CMakeLists.txt b/test/daemon/CMakeLists.txt index abc8902..1a0980c 100644 --- a/test/daemon/CMakeLists.txt +++ b/test/daemon/CMakeLists.txt @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.22.0) \ No newline at end of file +cmake_minimum_required(VERSION 3.22.0) From fc01f260a8cc0e9e95c3adb7f45df01b1b1e9b91 Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 18:18:44 +0200 Subject: [PATCH 13/14] minor fixes --- README.md | 1 + src/daemon/main.cpp | 132 ++++++++++++++++++++++++++++++++------------ 2 files changed, 97 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index f58b447..7888b82 100644 --- a/README.md +++ b/README.md @@ -31,5 +31,6 @@ sudo update-rc.d ocvsmd defaults ``` sudo /etc/init.d/ocvsmd start sudo /etc/init.d/ocvsmd status +sudo /etc/init.d/ocvsmd restart sudo /etc/init.d/ocvsmd stop ``` diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index a17f8d0..8e5e701 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -4,6 +4,7 @@ // #include +#include #include #include #include @@ -36,23 +37,7 @@ extern "C" void handle_signal(const int sig) } } -void fork_and_exit_parent() -{ - // Fork off the parent process - const pid_t pid = fork(); - if (pid < 0) - { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to fork: " << err_txt << "\n"; - ::exit(EXIT_FAILURE); - } - if (pid > 0) - { - ::exit(EXIT_SUCCESS); - } -} - -void step_01_close_all_file_descriptors() +void step_01_close_all_file_descriptors(std::array& pipe_fds) { rlimit rlimit_files{}; if (getrlimit(RLIMIT_NOFILE, &rlimit_files) != 0) @@ -66,6 +51,15 @@ void step_01_close_all_file_descriptors() { (void) ::close(fd); } + + // Create a pipe to communicate with the original process. + // + if (::pipe(pipe_fds.data()) == -1) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to create pipe: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } } void step_02_03_setup_signal_handlers() @@ -80,9 +74,31 @@ void step_04_sanitize_environment() // TODO: Implement this step. } -void step_05_fork_to_background() +bool step_05_fork_to_background(std::array& pipe_fds) { - fork_and_exit_parent(); + // Fork off the parent process + const pid_t parent_pid = fork(); + if (parent_pid < 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to fork: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + + if (parent_pid == 0) + { + // Close read end on the child side. + ::close(pipe_fds[0]); + pipe_fds[0] = -1; + } + else + { + // Close write end on the parent side. + ::close(pipe_fds[1]); + pipe_fds[1] = -1; + } + + return parent_pid == 0; } void step_06_create_new_session() @@ -95,9 +111,24 @@ void step_06_create_new_session() } } -void step_07_08_fork_and_exit_again() +void step_07_08_fork_and_exit_again(int& pipe_write_fd) { - fork_and_exit_parent(); + assert(pipe_write_fd != -1); + + // Fork off the parent process + const pid_t pid = fork(); + if (pid < 0) + { + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to fork: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); + } + if (pid > 0) + { + ::close(pipe_write_fd); + pipe_write_fd = -1; + ::exit(EXIT_SUCCESS); + } } void step_09_redirect_stdio_to_devnull() @@ -181,38 +212,67 @@ void step_13_drop_privileges() // TODO: Implement this step. } -void step_14_notify_init_complete() +void step_14_notify_init_complete(int& pipe_write_fd) { + assert(pipe_write_fd != -1); + // From the daemon process, notify the original process started that initialization is complete. This can be // implemented via an unnamed pipe or similar communication channel that is created before the first fork() and // hence available in both the original and the daemon process. - // TODO: Implement this step. + + // Closing the writing end of the pipe will signal the original process that the daemon is ready. + ::close(pipe_write_fd); + pipe_write_fd = -1; } -void step_15_exit_org_process() +void step_15_exit_org_process(int& pipe_read_fd) { // Call exit() in the original process. The process that invoked the daemon must be able to rely on that this exit() // happens after initialization is complete and all external communication channels are established and accessible. - // TODO: Implement this step. + + constexpr std::size_t buf_size = 16; + std::array buf{}; + if (::read(pipe_read_fd, buf.data(), buf.size()) > 0) + { + std::cout << "Child has finished initialization.\n"; + } + ::close(pipe_read_fd); + pipe_read_fd = -1; + ::exit(EXIT_SUCCESS); } /// Implements the daemonization procedure as described in the `man 7 daemon` manual page. /// void daemonize() { - step_01_close_all_file_descriptors(); + std::array pipe_fds{-1, -1}; + + step_01_close_all_file_descriptors(pipe_fds); step_02_03_setup_signal_handlers(); step_04_sanitize_environment(); - step_05_fork_to_background(); - step_06_create_new_session(); - step_07_08_fork_and_exit_again(); - step_09_redirect_stdio_to_devnull(); - step_10_reset_umask(); - step_11_change_curr_dir(); - step_12_create_pid_file("/var/run/ocvsmd.pid"); - step_13_drop_privileges(); - step_14_notify_init_complete(); - step_15_exit_org_process(); + if (step_05_fork_to_background(pipe_fds)) + { + // Child process. + assert(pipe_fds[0] == -1); + assert(pipe_fds[1] != -1); + + step_06_create_new_session(); + step_07_08_fork_and_exit_again(pipe_fds[1]); + step_09_redirect_stdio_to_devnull(); + step_10_reset_umask(); + step_11_change_curr_dir(); + step_12_create_pid_file("/var/run/ocvsmd.pid"); + step_13_drop_privileges(); + step_14_notify_init_complete(pipe_fds[1]); + } + else + { + // Original parent process. + assert(pipe_fds[0] != -1); + assert(pipe_fds[1] == -1); + + step_15_exit_org_process(pipe_fds[0]); + } ::openlog("ocvsmd", LOG_PID, LOG_DAEMON); } From 8a765a00fbbae406e9d63b45624f1105f65d43ef Mon Sep 17 00:00:00 2001 From: Sergei Shirokov Date: Thu, 12 Dec 2024 19:38:43 +0200 Subject: [PATCH 14/14] implemented passing children error to parent --- init.d/ocvsmd | 10 ++++- src/daemon/main.cpp | 90 +++++++++++++++++++++++---------------------- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/init.d/ocvsmd b/init.d/ocvsmd index 4b998ca..ade5d09 100755 --- a/init.d/ocvsmd +++ b/init.d/ocvsmd @@ -20,8 +20,14 @@ case "$1" in echo "$DAEMON_NAME is already running." exit 1 fi - start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $DAEMON_PATH - echo "$DAEMON_NAME started." + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON_PATH + ret=$? + if [ $ret -eq 0 ]; then + echo "$DAEMON_NAME started." + else + echo "$DAEMON_NAME failed with exit code $ret" + exit 1 + fi ;; stop) echo "Stopping $DAEMON_NAME..." diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 8e5e701..71f7416 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -21,6 +21,8 @@ namespace { +const auto* const s_init_complete = "init_complete"; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) volatile int s_running = 1; @@ -37,6 +39,14 @@ extern "C" void handle_signal(const int sig) } } +void exit_with_failure(const int fd, const char* const msg) +{ + const char* const err_txt = strerror(errno); + ::write(fd, msg, strlen(msg)); + ::write(fd, err_txt, strlen(err_txt)); + ::exit(EXIT_FAILURE); +} + void step_01_close_all_file_descriptors(std::array& pipe_fds) { rlimit rlimit_files{}; @@ -101,13 +111,11 @@ bool step_05_fork_to_background(std::array& pipe_fds) return parent_pid == 0; } -void step_06_create_new_session() +void step_06_create_new_session(const int pipe_write_fd) { if (::setsid() < 0) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to setsid: " << err_txt << "\n"; - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to setsid: "); } } @@ -119,9 +127,7 @@ void step_07_08_fork_and_exit_again(int& pipe_write_fd) const pid_t pid = fork(); if (pid < 0) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to fork: " << err_txt << "\n"; - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to fork: "); } if (pid > 0) { @@ -131,14 +137,12 @@ void step_07_08_fork_and_exit_again(int& pipe_write_fd) } } -void step_09_redirect_stdio_to_devnull() +void step_09_redirect_stdio_to_devnull(const int pipe_write_fd) { const int fd = ::open("/dev/null", O_RDWR); // NOLINT *-vararg if (fd == -1) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to open(/dev/null): " << err_txt << "\n"; - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to open(/dev/null): "); } ::dup2(fd, STDIN_FILENO); @@ -156,40 +160,30 @@ void step_10_reset_umask() ::umask(0); } -void step_11_change_curr_dir() +void step_11_change_curr_dir(const int pipe_write_fd) { if (::chdir("/") != 0) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to chdir(/): " << err_txt << "\n"; - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to chdir(/): "); } } -void step_12_create_pid_file(const char* const pid_file_name) +void step_12_create_pid_file(const int pipe_write_fd, const char* const pid_file_name) { const int fd = ::open(pid_file_name, O_RDWR | O_CREAT, 0644); // NOLINT *-vararg if (fd == -1) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to create on PID file: " << err_txt << "\n"; - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to create on PID file: "); } if (::lockf(fd, F_TLOCK, 0) == -1) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to lock PID file: " << err_txt << "\n"; - ::close(fd); - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to lock PID file: "); } if (::ftruncate(fd, 0) != 0) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to ftruncate PID file: " << err_txt << "\n"; - ::close(fd); - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to ftruncate PID file: "); } constexpr std::size_t max_pid_str_len = 32; @@ -197,10 +191,7 @@ void step_12_create_pid_file(const char* const pid_file_name) const auto len = ::snprintf(buf.data(), buf.size(), "%ld\n", static_cast(::getpid())); // NOLINT *-vararg if (::write(fd, buf.data(), len) != len) { - const char* const err_txt = ::strerror(errno); - std::cerr << "Failed to write to PID file: " << err_txt << "\n"; - ::close(fd); - ::exit(EXIT_FAILURE); + exit_with_failure(pipe_write_fd, "Failed to write to PID file: "); } // Keep the PID file open until the process exits. @@ -217,10 +208,11 @@ void step_14_notify_init_complete(int& pipe_write_fd) assert(pipe_write_fd != -1); // From the daemon process, notify the original process started that initialization is complete. This can be - // implemented via an unnamed pipe or similar communication channel that is created before the first fork() and + // implemented via an unnamed pipe or similar communication channel created before the first fork() and // hence available in both the original and the daemon process. // Closing the writing end of the pipe will signal the original process that the daemon is ready. + (void) ::write(pipe_write_fd, s_init_complete, strlen(s_init_complete)); ::close(pipe_write_fd); pipe_write_fd = -1; } @@ -230,12 +222,22 @@ void step_15_exit_org_process(int& pipe_read_fd) // Call exit() in the original process. The process that invoked the daemon must be able to rely on that this exit() // happens after initialization is complete and all external communication channels are established and accessible. - constexpr std::size_t buf_size = 16; - std::array buf{}; - if (::read(pipe_read_fd, buf.data(), buf.size()) > 0) + constexpr std::size_t buf_size = 256; + std::array msg_from_child{}; + const auto res = ::read(pipe_read_fd, msg_from_child.data(), msg_from_child.size() - 1); + if (res == -1) { - std::cout << "Child has finished initialization.\n"; + const char* const err_txt = ::strerror(errno); + std::cerr << "Failed to read pipe: " << err_txt << "\n"; + ::exit(EXIT_FAILURE); } + + if (::strcmp(msg_from_child.data(), s_init_complete) != 0) + { + std::cerr << "Child init failed: " << msg_from_child.data() << "\n"; + ::exit(EXIT_FAILURE); + } + ::close(pipe_read_fd); pipe_read_fd = -1; ::exit(EXIT_SUCCESS); @@ -255,23 +257,25 @@ void daemonize() // Child process. assert(pipe_fds[0] == -1); assert(pipe_fds[1] != -1); + auto& pipe_write_fd = pipe_fds[1]; - step_06_create_new_session(); - step_07_08_fork_and_exit_again(pipe_fds[1]); - step_09_redirect_stdio_to_devnull(); + step_06_create_new_session(pipe_write_fd); + step_07_08_fork_and_exit_again(pipe_write_fd); + step_09_redirect_stdio_to_devnull(pipe_write_fd); step_10_reset_umask(); - step_11_change_curr_dir(); - step_12_create_pid_file("/var/run/ocvsmd.pid"); + step_11_change_curr_dir(pipe_write_fd); + step_12_create_pid_file(pipe_write_fd, "/var/run/ocvsmd.pid"); step_13_drop_privileges(); - step_14_notify_init_complete(pipe_fds[1]); + step_14_notify_init_complete(pipe_write_fd); } else { // Original parent process. assert(pipe_fds[0] != -1); assert(pipe_fds[1] == -1); + auto& pipe_read_fd = pipe_fds[0]; - step_15_exit_org_process(pipe_fds[0]); + step_15_exit_org_process(pipe_read_fd); } ::openlog("ocvsmd", LOG_PID, LOG_DAEMON);