diff --git a/score/os/BUILD b/score/os/BUILD index df6b417487..7be9431fc9 100644 --- a/score/os/BUILD +++ b/score/os/BUILD @@ -297,6 +297,32 @@ cc_library( ], ) +cc_library( + name = "syslog_impl", + srcs = [ + "syslog.cpp", + "syslog_impl.cpp", + ], + hdrs = [ + "syslog.h", + "syslog_impl.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:linux"], + visibility = ["//visibility:public"], + deps = [ + ":errno", + ":object_seam", + ], +) + +alias( + name = "syslog", + actual = "syslog_impl", + visibility = ["//visibility:public"], +) + cc_library( name = "arpa_inet", srcs = ["arpa_inet.cpp"], diff --git a/score/os/mocklib/BUILD b/score/os/mocklib/BUILD index c5fc6e3fb5..594d587a94 100644 --- a/score/os/mocklib/BUILD +++ b/score/os/mocklib/BUILD @@ -26,6 +26,19 @@ cc_library( ], ) +cc_library( + name = "syslog_mock", + testonly = True, + srcs = ["mock_syslog.cpp"], + hdrs = ["mock_syslog.h"], + target_compatible_with = ["@platforms//os:linux"], + visibility = ["//visibility:public"], + deps = [ + "@googletest//:gtest", + "@score_baselibs//score/os:syslog", + ], +) + cc_library( name = "pthread_mock", testonly = True, diff --git a/score/os/mocklib/mock_syslog.cpp b/score/os/mocklib/mock_syslog.cpp new file mode 100644 index 0000000000..4aab106ec6 --- /dev/null +++ b/score/os/mocklib/mock_syslog.cpp @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/os/mocklib/mock_syslog.h" + +#include +#include +#include + +namespace score +{ +namespace os +{ + +// NOLINTNEXTLINE(cert-dcl50-cpp, modernize-avoid-variadic-functions) +void MockSyslog::syslog(std::int32_t priority, const char* format, ...) const noexcept +{ + // Create a va_list to hold the variable arguments + va_list args; + va_start(args, format); + + // Determine required buffer size + const auto message_length = std::vsnprintf(nullptr, 0, format, args); + + // Reset the va_list to be able to use it again + va_end(args); + va_start(args, format); + + // Create buffer + std::string message{}; + message.resize(static_cast(message_length)); + + // Write formatted string to buffer + const auto length_including_terminator = static_cast(message_length) + 1U; + std::vsnprintf(&message[0], length_including_terminator, format, args); + + // Clean up the va_list + va_end(args); + + MockedSyslog(priority, message); +} + +} // namespace os +} // namespace score diff --git a/score/os/mocklib/mock_syslog.h b/score/os/mocklib/mock_syslog.h new file mode 100644 index 0000000000..653e94ff16 --- /dev/null +++ b/score/os/mocklib/mock_syslog.h @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_OS_MOCKLIB_MOCK_SYSLOG_H +#define SCORE_LIB_OS_MOCKLIB_MOCK_SYSLOG_H + +#include "score/os/syslog.h" + +#include +#include + +#include + +namespace score +{ +namespace os +{ + +class MockSyslog : public Syslog +{ + public: + MOCK_METHOD(void, + openlog, + (const char* ident, std::int32_t option, std::int32_t facility), + (const, noexcept, override)); + + MOCK_METHOD(void, closelog, (), (const, noexcept, override)); + + // Googletest does not support variadic arguments. Therefore we pass through the formatted string so that we can + // use it with MOCK_METHOD. + MOCK_METHOD(void, MockedSyslog, (std::int32_t priority, const std::string& message), (const, noexcept)); + + void syslog(std::int32_t priority, const char* format, ...) const noexcept override + __attribute__((format(printf, 3, 4))); +}; + +} // namespace os +} // namespace score + +#endif // SCORE_LIB_OS_MOCKLIB_MOCK_SYSLOG_H diff --git a/score/os/syslog.cpp b/score/os/syslog.cpp new file mode 100644 index 0000000000..dd85b5fbc6 --- /dev/null +++ b/score/os/syslog.cpp @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/os/syslog.h" + +#include "score/os/syslog_impl.h" + +namespace score +{ +namespace os +{ + +/* score::cpp::pmr::make_unique takes non-const memory_resource */ +score::cpp::pmr::unique_ptr Syslog::Default(score::cpp::pmr::memory_resource* memory_resource) noexcept +{ + return score::cpp::pmr::make_unique(memory_resource); +} + +} // namespace os +} // namespace score diff --git a/score/os/syslog.h b/score/os/syslog.h new file mode 100644 index 0000000000..571058e6a8 --- /dev/null +++ b/score/os/syslog.h @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_OS_SYSLOG_H +#define SCORE_LIB_OS_SYSLOG_H + +#include "score/os/errno.h" + +#include "score/memory.hpp" + +#include + +namespace score +{ +namespace os +{ + +/// @brief Object-seam wrapper around the POSIX/glibc syslog(3) family, allowing +/// openlog/syslog/closelog to be mocked in host unit tests. +class Syslog +{ + public: + static score::cpp::pmr::unique_ptr Default(score::cpp::pmr::memory_resource* memory_resource) noexcept; + + virtual void openlog(const char* const ident, + const std::int32_t option, + const std::int32_t facility) const noexcept = 0; + + virtual void syslog(const std::int32_t priority, const char* const format, ...) const noexcept + __attribute__((format(printf, 3, 4))) = 0; + + virtual void closelog() const noexcept = 0; + + /// @brief Construct a new Syslog object + /// + Syslog() = default; + + virtual ~Syslog() = default; + Syslog(const Syslog&) = delete; + Syslog& operator=(const Syslog&) = delete; + Syslog(Syslog&& other) = delete; + Syslog& operator=(Syslog&& other) = delete; +}; + +} // namespace os +} // namespace score + +#endif // SCORE_LIB_OS_SYSLOG_H diff --git a/score/os/syslog_impl.cpp b/score/os/syslog_impl.cpp new file mode 100644 index 0000000000..b5aa4a0da5 --- /dev/null +++ b/score/os/syslog_impl.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/os/syslog_impl.h" + +#include + +namespace score +{ +namespace os +{ + +void SyslogImpl::openlog(const char* const ident, const std::int32_t option, const std::int32_t facility) const noexcept +{ + ::openlog(ident, option, facility); +} + +// NOLINTNEXTLINE(cert-dcl50-cpp, modernize-avoid-variadic-functions) +void SyslogImpl::syslog(const std::int32_t priority, const char* const format, ...) const noexcept +{ + // Suppressed here because POSIX method accepts va_list + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) see comment above + va_list args; + // Suppressed here because POSIX method accepts va_list + // NOLINTNEXTLINE(hicpp-no-array-decay, cppcoreguidelines-pro-type-vararg) see comment above + va_start(args, format); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) see comment above + // Suppressed here because POSIX method accepts va_list + // NOLINTNEXTLINE(*array-decay, *pro-type-vararg,*array-to-pointer-decay) see comment above + ::vsyslog(priority, format, args); + // Suppressed here because POSIX method accepts va_list + // NOLINTNEXTLINE(hicpp-no-array-decay, cppcoreguidelines-pro-type-vararg) see comment above + va_end(args); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) see comment above +} + +void SyslogImpl::closelog() const noexcept +{ + ::closelog(); +} + +} // namespace os +} // namespace score diff --git a/score/os/syslog_impl.h b/score/os/syslog_impl.h new file mode 100644 index 0000000000..f5526d1312 --- /dev/null +++ b/score/os/syslog_impl.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_LIB_OS_SYSLOG_IMPL_H +#define SCORE_LIB_OS_SYSLOG_IMPL_H + +#include "score/os/syslog.h" + +namespace score +{ +namespace os +{ + +class SyslogImpl : public Syslog +{ + public: + void openlog(const char* const ident, + const std::int32_t option, + const std::int32_t facility) const noexcept override; + + void syslog(const std::int32_t priority, const char* const format, ...) const noexcept override + __attribute__((format(printf, 3, 4))); + + void closelog() const noexcept override; +}; + +} // namespace os +} // namespace score + +#endif // SCORE_LIB_OS_SYSLOG_IMPL_H diff --git a/score/os/test/linux/BUILD b/score/os/test/linux/BUILD index 4247276398..014249858e 100644 --- a/score/os/test/linux/BUILD +++ b/score/os/test/linux/BUILD @@ -18,6 +18,7 @@ test_suite( name = "unit_tests_linux", tests = [ ":pthread_test", + ":syslog_test", ":unistd_test", ], visibility = ["@score_baselibs//score/os/test:__subpackages__"], @@ -53,3 +54,19 @@ cc_test( "@score_baselibs//score/os:pthread", ], ) + +cc_test( + name = "syslog_test", + srcs = [ + "syslog_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + tags = [ + "unit", + ], + target_compatible_with = ["@platforms//os:linux"], + deps = [ + "@googletest//:gtest_main", + "@score_baselibs//score/os:syslog", + ], +) diff --git a/score/os/test/linux/syslog_test.cpp b/score/os/test/linux/syslog_test.cpp new file mode 100644 index 0000000000..d4e456c4c2 --- /dev/null +++ b/score/os/test/linux/syslog_test.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/os/syslog.h" +#include "score/os/syslog_impl.h" + +#include + +#include + +#include + +namespace +{ +// ---------- score/os/syslog_impl.cpp -------- +class SyslogImplFixture : public ::testing::Test +{ + protected: + void SetUp() override + { + unit_ = std::make_unique(); + } + + std::unique_ptr unit_; +}; + +TEST_F(SyslogImplFixture, OpenSyslogAndCloseFlow) +{ + RecordProperty("ParentRequirement", "SCR-46010294"); + RecordProperty("ASIL", "B"); + RecordProperty("Description", "openlog/syslog/closelog passthroughs to glibc shall not throw or crash."); + RecordProperty("TestingTechnique", "Interface test"); + RecordProperty("DerivationTechnique", "equivalence-classes"); // equivalence classes + + // glibc openlog/syslog/closelog return void; the wrapper cannot surface an error, so the + // interface guarantee under test is that the real passthroughs execute without throwing. + EXPECT_NO_FATAL_FAILURE(unit_->openlog("syslog_test", LOG_PID | LOG_NDELAY, LOG_USER)); + EXPECT_NO_FATAL_FAILURE(unit_->syslog(LOG_INFO, "formatted: %s", "hello world")); + EXPECT_NO_FATAL_FAILURE(unit_->syslog(LOG_DEBUG, "constant string")); + EXPECT_NO_FATAL_FAILURE(unit_->closelog()); +} + +TEST_F(SyslogImplFixture, SyslogBeforeOpenlogStillDoesNotCrash) +{ + RecordProperty("ParentRequirement", "SCR-46010294"); + RecordProperty("ASIL", "B"); + RecordProperty("Description", "syslog without a prior openlog is valid per POSIX and shall not crash."); + RecordProperty("TestingTechnique", "Interface test"); + RecordProperty("DerivationTechnique", "equivalence-classes"); // equivalence classes + + // POSIX allows syslog() without a preceding openlog(); it uses default identity. + EXPECT_NO_FATAL_FAILURE(unit_->syslog(LOG_WARNING, "no prior openlog")); + EXPECT_NO_FATAL_FAILURE(unit_->closelog()); +} + +TEST(SyslogTest, PMRDefaultShallReturnImplInstance) +{ + RecordProperty("ParentRequirement", "SCR-46010294"); + RecordProperty("ASIL", "B"); + RecordProperty("Description", "PMR Default Shall Return Impl Instance"); + RecordProperty("TestingTechnique", "Interface test"); + RecordProperty("DerivationTechnique", "equivalence-classes"); // equivalence classes + + score::cpp::pmr::memory_resource* memory_resource = score::cpp::pmr::get_default_resource(); + const auto instance = score::os::Syslog::Default(memory_resource); + ASSERT_TRUE(instance != nullptr); + EXPECT_NO_THROW(std::ignore = dynamic_cast(instance.get())); +} + +} // namespace diff --git a/tools/coverage/BUILD b/tools/coverage/BUILD index 5fdf5ffd28..75a55cd86f 100644 --- a/tools/coverage/BUILD +++ b/tools/coverage/BUILD @@ -102,6 +102,7 @@ score_coverage_scope( "//score/os:statvfs", "//score/os:string", "//score/os:sys_uio", + "//score/os:syslog_impl", "//score/os:sys_wait", "//score/os:time", "//score/os/posix:ftw_posix",