Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@
# *******************************************************************************
load("@rules_cc//cc:defs.bzl", "cc_library")

cc_library(
name = "configure_process",
srcs = ["configure_process.cpp"],
hdrs = ["configure_process.hpp"],
include_prefix = "score/mw/launch_manager/process_group_manager/details",
strip_include_prefix = "/score/launch_manager/src/daemon/src/process_group_manager/details",
visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"],
deps = [
"//score/launch_manager/src/daemon/src/osal:return_types",
"//score/launch_manager/src/daemon/src/osal:security_policy",
"//score/launch_manager/src/daemon/src/osal:set_affinity",
"//score/launch_manager/src/daemon/src/osal:set_groups",
"//score/launch_manager/src/daemon/src/osal:sys_exit",
"//score/launch_manager/src/daemon/src/process_group_manager:iprocess",
],
)

cc_library(
name = "process_info_node",
hdrs = ["process_info_node.hpp"],
Expand Down Expand Up @@ -108,13 +125,10 @@ cc_library(
srcs = ["process_launcher.cpp"],
visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"],
deps = [
":configure_process",
"//score/launch_manager/src/daemon/src/common:log",
"//score/launch_manager/src/daemon/src/control:control_client_channel",
"//score/launch_manager/src/daemon/src/osal:ipc_comms",
"//score/launch_manager/src/daemon/src/osal:security_policy",
"//score/launch_manager/src/daemon/src/osal:set_affinity",
"//score/launch_manager/src/daemon/src/osal:set_groups",
"//score/launch_manager/src/daemon/src/osal:sys_exit",
"//score/launch_manager/src/daemon/src/process_group_manager:iprocess",
],
)
Expand All @@ -135,6 +149,7 @@ cc_library(
}),
visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"],
deps = [
":configure_process",
":graph",
":os_handler",
":process_info_node",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/********************************************************************************
* 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 <limits.h>

#include <libgen.h>
#include <sys/resource.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <string_view>

#include "score/launch_manager/src/daemon/src/osal/return_types.hpp"
#include "score/mw/launch_manager/osal/security_policy.hpp"
#include "score/mw/launch_manager/osal/set_affinity.hpp"
#include "score/mw/launch_manager/osal/set_groups.hpp"
#include "score/mw/launch_manager/process_group_manager/iprocess.hpp"

/*
* In this file only async signal safe functions can be used, as these functions
* are used between `fork` and `execve`.
*
* This is necessary because `fork` only copies the current thread, so any locks
* which were held at that time will never be released. See `man 2 fork`.
*
* A big implication is that we must use assertions rather than log messages,
* and the assertion handler itself must follow these rules.
*/

namespace score
{

namespace lcm
{

namespace internal
{

void setLimit(const int resource, const std::size_t amount, const std::string_view rlimit_name)
{
if (amount == 0)
return;

const struct rlimit limit{
.rlim_cur = amount,
.rlim_max = amount,
};

const auto result = ::setrlimit(resource, &limit);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have the same async signal safe issue with the assert message, as the string concatenation may cause heap allocation which is not async signal safe

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could remove the extra info and just use strerror as the message

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bazel assort macros terminate the process using std::abort(). Is this safe to terminate the process this way btw. fork() and execve()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abort is async signal safe in POSIX, not sure if std::abort is doing anything extra?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here it is not so much about the signal safety, but other considerations what is being cleaned up: https://www.unixguide.net/unix/programming/1.1.3.shtml

Not sure though exactly what is the difference btw. _exit and std::abort, but If I remember correctly using _exit() is the recommended way to terminate a process btw. fork and execve in case of error

result != -1, ("Failed to set rlimit " + std::string(rlimit_name) + ": " + std::strerror(errno)).c_str());
}

void setSchedulingAndSecurity(const osal::OsalConfig& config)
{
int result; // Used for return value from system calls

// Set process group id to be equal to the pid
// setpgid will fail if called by a session leader (which LCMd is), so skip
if (config.comms_type_ != osal::CommsType::kLaunchManager)
{
result = setpgid(0, getpid());
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result == 0, std::strerror(errno));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the strategy to use asserts here is now conflicting with this #299
where asserts actually log to std::cerr which I guess is not safe to call btw. fork() and execve() either. Also the heap allocation from std::ostringstream is probably then a problem.

Do you have any suggestion how to resolve this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could allocate a fixed buffer for the assertion handler and call write directly, bypassing std::cerr? write is async signal safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like a good solution to me. What do you think @paulquiring ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need something like this as assertion handler.

void my_signal_handler(int sig) {
    char buffer[128];
    // Format your string (snprintf is generally safe in practice though technically not guaranteed)
    int len = snprintf(buffer, sizeof(buffer), "Caught signal %d\n", sig);
    
    // write to stdout (file descriptor 1) is async-signal-safe
    write(STDOUT_FILENO, buffer, len);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snprintf is also not async signal safe 😅

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NicolasFussberger and I discussed this a bit more see #303 (comment)

}

// Set scheduling policy with sched_setscheduler
sched_param sch_param{};

sch_param.sched_priority = config.scheduling_priority_;

// TODO: Clamping should be done when the config is loaded
Comment thread
danth marked this conversation as resolved.
// https://github.com/eclipse-score/lifecycle/issues/304
if (sch_param.sched_priority < sched_get_priority_min(config.scheduling_policy_))
{
sch_param.sched_priority = sched_get_priority_min(config.scheduling_policy_);
}
else if (sch_param.sched_priority > sched_get_priority_max(config.scheduling_policy_))
{
sch_param.sched_priority = sched_get_priority_max(config.scheduling_policy_);
}

result = sched_setscheduler(0, config.scheduling_policy_, &sch_param);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));

result = osal::setaffinity(config.cpu_mask_);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));

result = setgid(config.gid_);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));

// Note: the type of the first parameter of setgroups() differs in Linux and QNX, so we use osal
const auto gids_size = config.supplementary_gids_.size();
if (gids_size > 0)
{
const auto gids_data = config.supplementary_gids_.data();
result = osal::setgroups(gids_size, gids_data);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));
}

result = setuid(config.uid_);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));
}

void changeCurrentWorkingDirectory(const osal::OsalConfig& config)
{
// Notice that this next static variable is duplicated by the fork() and so does not need
// any protection by a mutex although at first sight you may think it could need one.
static char path_copy[PATH_MAX + 1U] = {0};

// TODO: This should be validated when the config is loaded
Comment thread
NicolasFussberger marked this conversation as resolved.
// https://github.com/eclipse-score/lifecycle/issues/304
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(config.executable_path_.size() < PATH_MAX,
"Executable path is too long");

const auto result = chdir(dirname(strncpy(path_copy, config.executable_path_.c_str(), PATH_MAX)));
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(result != -1, std::strerror(errno));
}

void implementMemoryResourceLimits(const osal::OsalConfig& config)
{
setLimit(RLIMIT_DATA, config.resource_limits_.data_, "RLIMIT_DATA");
setLimit(RLIMIT_AS, config.resource_limits_.as_, "RLIMIT_AS");
setLimit(RLIMIT_STACK, config.resource_limits_.stack_, "RLIMIT_STACK");

// Note about cpu limit:
// Using setrlimit, this imposes a maximum time that a process will run for, which might not be
// what you intend? Probably you'll want a maximum time in a time-slice, but you don't get that
// with limits set by setrlimit...
setLimit(RLIMIT_CPU, config.resource_limits_.cpu_, "RLIMIT_CPU");
}

void changeSecurityPolicy(const osal::OsalConfig& config)
{
if (config.security_policy_ == "")
return;

const auto result = osal::setSecurityPolicy(config.security_policy_.c_str());
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(
result == 0,
("Failed to set security policy " + config.security_policy_ + ": " + std::strerror(errno)).c_str());
}

} // namespace internal

} // namespace lcm

} // namespace score
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/********************************************************************************
* 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 <string_view>

#include "score/mw/launch_manager/osal/security_policy.hpp"
#include "score/mw/launch_manager/process_group_manager/iprocess.hpp"

namespace score
{

namespace lcm
{

namespace internal
{

Comment thread
danth marked this conversation as resolved.
/// @brief Sets the limit if given a non-zero value, otherwise skips.
/// @warning This will abort if not successful.
void setLimit(const int resource, const std::size_t amount, const std::string_view rlimit_name);

/// @warning This will abort if not successful.
void setSchedulingAndSecurity(const osal::OsalConfig& config);

/// @warning This will abort if not successful.
void changeCurrentWorkingDirectory(const osal::OsalConfig& config);

/// @warning This will abort if not successful.
void implementMemoryResourceLimits(const osal::OsalConfig& config);

/// @warning This will abort if not successful.
void changeSecurityPolicy(const osal::OsalConfig& config);

} // namespace internal

} // namespace lcm

} // namespace score
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <unistd.h>
#include <csignal>

#include "score/launch_manager/src/daemon/src/process_group_manager/details/configure_process.hpp"
#include "score/mw/launch_manager/common/log.hpp"
#include "score/mw/launch_manager/process_group_manager/ialive_monitor_thread.hpp"
#include "score/mw/launch_manager/process_group_manager/process_group_manager.hpp"
Expand Down Expand Up @@ -112,10 +113,10 @@ bool ProcessGroupManager::initialize()
return false;
}

if (launch_manager_config_ &&
OsalReturnType::kFail == IProcess::setSchedulingAndSecurity(launch_manager_config_->startup_config_))
// Apply the launch manager's own policies
if (launch_manager_config_)
Comment thread
danth marked this conversation as resolved.
{
return false;
setSchedulingAndSecurity(launch_manager_config_->startup_config_);
}

return true;
Expand Down Expand Up @@ -166,7 +167,6 @@ inline bool ProcessGroupManager::initializeControlClientHandler()
return false;
}


if (osal::IpcCommsSync::control_client_handler_nudge_fd == fd2)
{
void* buf = mmap(NULL, sizeof(osal::Semaphore), PROT_WRITE, MAP_SHARED, fd2, 0);
Expand Down
Loading
Loading