feat(posix): add portable startup script backend

Route POSIX startup scripts through a platform shell hook instead of hard-coding /bin/sh in main.cpp.

Linux keeps the existing external-shell behavior through shell_posix.cpp, while the Windows backend can use an embedded shell implementation. The same path also restores console state and exits through the platform cleanup hook during shutdown.

Signed-off-by: Nuno Marques <n.marques21@hotmail.com>
This commit is contained in:
Nuno Marques
2026-04-27 15:25:13 -07:00
parent c49699fb5d
commit 9abb4ac867
8 changed files with 2915 additions and 64 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
#include <px4_platform_common/time.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/log.h>
#include <px4_platform_common/exit.h>
#include <uORB/uORB.h>
#include "apps.h"
@@ -45,7 +46,7 @@ int shutdown_main(int argc, char *argv[])
{
printf("Exiting NOW.\n");
uorb_shutdown();
system_exit(0);
px4_platform_exit(0);
}
int list_tasks_main(int argc, char *argv[])
@@ -0,0 +1,57 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <string>
namespace px4
{
// Run a POSIX shell init script (rcS, test_*_generated, etc.) to start a
// PX4 instance. The platform backend is responsible for actually executing
// the script. On POSIX this typically shells out to /bin/sh. On Windows the
// intended production path is a bundled in-process shell backend so px4.exe
// can be embedded and redistributed without an external shell dependency.
//
// script_path : absolute or cwd-relative path to the shell script
// binary_dir : directory containing px4.exe / px4-* commands; added
// to PATH so the script can invoke them unqualified
// instance : PX4 instance id, forwarded as $1 to the script
//
// Returns the script's exit status (0 on success).
int run_shell_script(const std::string &script_path,
const std::string &binary_dir,
int instance);
} // namespace px4
+20 -4
View File
@@ -40,6 +40,8 @@
#include <px4_platform_common/workqueue.h>
#include <px4_platform_common/shutdown.h>
#include <px4_platform_common/tasks.h>
#include <px4_platform_common/time.h>
#include <px4_platform_common/exit.h>
#include <drivers/drv_hrt.h>
@@ -63,6 +65,20 @@
using namespace time_literals;
#if defined(ENABLE_LOCKSTEP_SCHEDULER) && defined(__PX4_WINDOWS)
static hrt_abstime shutdown_time_us_now()
{
timespec ts{};
system_clock_gettime(CLOCK_MONOTONIC, &ts);
return (static_cast<hrt_abstime>(ts.tv_sec) * 1000000ULL) + (static_cast<hrt_abstime>(ts.tv_nsec) / 1000ULL);
}
#else
static hrt_abstime shutdown_time_us_now()
{
return hrt_absolute_time();
}
#endif
static pthread_mutex_t shutdown_mutex =
PTHREAD_MUTEX_INITIALIZER; // protects access to shutdown_hooks & shutdown_lock_counter
static uint8_t shutdown_lock_counter = 0;
@@ -171,7 +187,7 @@ static void shutdown_worker(void *arg)
}
}
const hrt_abstime now = hrt_absolute_time();
const hrt_abstime now = shutdown_time_us_now();
const bool delay_elapsed = (now > shutdown_time_us);
if (delay_elapsed && ((done && shutdown_lock_counter == 0) || (now > (shutdown_time_us + shutdown_timeout_us)))) {
@@ -206,7 +222,7 @@ static void shutdown_worker(void *arg)
#elif defined(__PX4_POSIX)
// simply exit on posix if real shutdown (poweroff) not available
PX4_INFO_RAW("Exiting NOW.");
system_exit(0);
px4_platform_exit(0);
#else
PX4_PANIC("board shutdown not available");
#endif
@@ -239,7 +255,7 @@ int px4_reboot_request(reboot_request_t request, uint32_t delay_us)
shutdown_args |= SHUTDOWN_ARG_TO_ISP;
}
shutdown_time_us = hrt_absolute_time();
shutdown_time_us = shutdown_time_us_now();
if (delay_us > 0) {
shutdown_time_us += delay_us;
@@ -263,7 +279,7 @@ int px4_shutdown_request(uint32_t delay_us)
shutdown_args |= SHUTDOWN_ARG_IN_PROGRESS;
shutdown_time_us = hrt_absolute_time();
shutdown_time_us = shutdown_time_us_now();
if (delay_us > 0) {
shutdown_time_us += delay_us;
+148 -59
View File
@@ -53,7 +53,6 @@
#include <string>
#include <algorithm>
#include <fstream>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
@@ -63,16 +62,27 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if (_POSIX_MEMLOCK > 0)
#if (_POSIX_MEMLOCK > 0) && !defined(__PX4_WINDOWS)
#include <sys/mman.h>
#endif
#ifdef __PX4_WINDOWS
// MinGW ships no shell at /bin/sh, no geteuid, no sigaction.
// mkdir is already a 2-arg POSIX wrapper via the windows_shim sys/stat.h.
// Provide the remaining forwards inline so the stock POSIX main.cpp
// compiles unchanged.
#include <windows.h>
#include <px4_windows/platform.h>
static inline unsigned int geteuid(void) { return 1000; }
#endif
#include <px4_platform_common/time.h>
#include <px4_platform_common/log.h>
#include <px4_platform_common/init.h>
#include <px4_platform_common/getopt.h>
#include <px4_platform_common/tasks.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/shell.h>
#include <uORB/uORB.h>
#include "apps.h"
@@ -90,6 +100,7 @@ static const char *LOCK_FILE_PATH = "/tmp/px4_lock";
static volatile bool _exit_requested = false;
static volatile sig_atomic_t _shutdown_started = 0;
namespace px4
@@ -111,10 +122,70 @@ static int set_server_running(int instance);
static void print_usage();
static bool dir_exists(const std::string &path);
static bool file_exists(const std::string &name);
static bool is_absolute_path(const std::string &path);
static bool is_path_separator(char ch);
static std::string file_basename(std::string const &pathname);
static std::string pwd();
static int change_directory(const std::string &directory);
#ifdef __PX4_WINDOWS
// Unblock the main thread's getchar() so the pxh loop can notice
// _should_exit. Windows delivers Ctrl+C on a dedicated handler thread,
// which means just flipping a flag leaves the main thread blocked in its
// stdin ReadFile forever. Two nudges, tried in order:
// 1) inject a synthetic '\n' keypress into the console input buffer so
// getchar() returns cleanly; works when stdin is an attached console;
// 2) CancelIoEx on the stdin handle; works when stdin has been
// redirected to a pipe/file (e.g. `wine px4.exe < script`).
static void kick_stdin_reader()
{
HANDLE stdin_h = GetStdHandle(STD_INPUT_HANDLE);
if (stdin_h == INVALID_HANDLE_VALUE || stdin_h == nullptr) {
return;
}
INPUT_RECORD rec[2] = {};
rec[0].EventType = KEY_EVENT;
rec[0].Event.KeyEvent.bKeyDown = TRUE;
rec[0].Event.KeyEvent.wRepeatCount = 1;
rec[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
rec[0].Event.KeyEvent.uChar.UnicodeChar = L'\n';
rec[1] = rec[0];
rec[1].Event.KeyEvent.bKeyDown = FALSE;
DWORD written = 0;
WriteConsoleInputW(stdin_h, rec, 2, &written);
CancelIoEx(stdin_h, nullptr);
}
static void prepare_console_for_host_shell()
{
px4_windows_restore_console_modes();
px4_windows_discard_pending_input();
px4_windows_restore_console_modes();
}
static BOOL WINAPI px4_console_ctrl_handler(DWORD ctrl_type)
{
switch (ctrl_type) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
sig_int_handler(SIGINT);
kick_stdin_reader();
prepare_console_for_host_shell();
px4_windows_release_console();
return TRUE;
default:
return FALSE;
}
}
#endif
#ifdef __PX4_SITL_MAIN_OVERRIDE
int SITL_MAIN(int argc, char **argv);
@@ -416,6 +487,19 @@ int main(int argc, char **argv)
std::string cmd("shutdown");
px4_daemon::Pxh::process_line(cmd, true);
#ifdef __PX4_WINDOWS
// The shutdown command runs asynchronously on the worker queue. While
// waiting for the worker to terminate the process, keep restoring and
// draining stdin so Enters typed during shutdown do not leak back into
// the host Linux shell once Wine returns.
for (int i = 0; i < 120; ++i) {
prepare_console_for_host_shell();
Sleep(50);
}
px4_windows_release_console();
px4_windows_exit(0);
#endif
}
return PX4_OK;
@@ -505,6 +589,14 @@ int create_dirs()
void register_sig_handler()
{
#ifdef __PX4_WINDOWS
// MinGW's signal.h has no sigaction. SIGPIPE does not exist on
// Windows (closed sockets return WSAECONNRESET instead). Fall back
// to plain signal() for SIGINT/SIGTERM.
signal(SIGINT, sig_int_handler);
signal(SIGTERM, sig_int_handler);
SetConsoleCtrlHandler(px4_console_ctrl_handler, TRUE);
#else
// SIGINT
struct sigaction sig_int {};
sig_int.sa_handler = sig_int_handler;
@@ -525,13 +617,24 @@ void register_sig_handler()
sigaction(SIGTERM, &sig_int, nullptr);
sigaction(SIGPIPE, &sig_pipe, nullptr);
#endif // __PX4_WINDOWS
}
void sig_int_handler(int sig_num)
{
(void)sig_num;
if (_shutdown_started) {
return;
}
_shutdown_started = 1;
fflush(stdout);
printf("\nPX4 Exiting...\n");
fflush(stdout);
#ifdef __PX4_WINDOWS
prepare_console_for_host_shell();
#endif
uorb_shutdown();
px4_daemon::Pxh::stop();
_exit_requested = true;
@@ -553,7 +656,7 @@ std::string get_absolute_binary_path(const std::string &argv0)
{
// On Linux we could also use readlink("/proc/self/exe", buf, bufsize) to get the absolute path
std::size_t last_slash = argv0.find_last_of('/');
std::size_t last_slash = argv0.find_last_of("/\\");
if (last_slash == std::string::npos) {
// either relative path or in PATH (PATH is ignored here)
@@ -562,8 +665,9 @@ std::string get_absolute_binary_path(const std::string &argv0)
std::string base = argv0.substr(0, last_slash);
if (base.length() > 0 && base[0] == '/') {
// absolute path
if (is_absolute_path(base)) {
// Absolute POSIX path, or an absolute Windows path when running the
// Windows backend natively / under Wine.
return base;
}
@@ -574,62 +678,13 @@ std::string get_absolute_binary_path(const std::string &argv0)
int run_startup_script(const std::string &commands_file, const std::string &absolute_binary_path,
int instance)
{
std::string shell_command("/bin/sh ");
int ret = px4::run_shell_script(commands_file, absolute_binary_path, instance);
shell_command += commands_file + ' ' + std::to_string(instance);
// Update the PATH variable to include the absolute_binary_path
// (required for the px4-alias.sh script and px4-* commands).
// They must be within the same directory as the px4 binary
const char *path_variable = "PATH";
std::string updated_path = absolute_binary_path;
const char *path = getenv(path_variable);
if (path) {
std::string spath = path;
// Check if absolute_binary_path already in PATH
bool already_in_path = false;
std::size_t current, previous = 0;
current = spath.find(':');
while (current != std::string::npos) {
if (spath.substr(previous, current - previous) == absolute_binary_path) {
already_in_path = true;
}
previous = current + 1;
current = spath.find(':', previous);
}
if (spath.substr(previous, current - previous) == absolute_binary_path) {
already_in_path = true;
}
if (!already_in_path) {
// Prepend to path to prioritize PX4 commands over potentially already installed PX4 commands.
updated_path = updated_path + ":" + path;
setenv(path_variable, updated_path.c_str(), 1);
}
}
PX4_INFO("startup script: %s", shell_command.c_str());
int ret = 0;
if (!shell_command.empty()) {
ret = system(shell_command.c_str());
if (ret == 0) {
PX4_INFO("Startup script returned successfully");
} else {
PX4_ERR("Startup script returned with return value: %d", ret);
}
if (ret == 0) {
PX4_INFO("Startup script returned successfully");
} else {
PX4_INFO("Startup script empty");
PX4_ERR("Startup script returned with return value: %d", ret);
}
return ret;
@@ -743,13 +798,47 @@ static std::string file_basename(std::string const &pathname)
struct MatchPathSeparator {
bool operator()(char ch) const
{
return ch == '/';
return is_path_separator(ch);
}
};
return std::string(std::find_if(pathname.rbegin(), pathname.rend(),
MatchPathSeparator()).base(), pathname.end());
}
static bool is_path_separator(char ch)
{
return ch == '/' || ch == '\\';
}
static bool is_absolute_path(const std::string &path)
{
if (path.empty()) {
return false;
}
if (path[0] == '/') {
return true;
}
#ifdef __PX4_WINDOWS
const bool drive_letter = path.length() >= 3
&& ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
&& path[1] == ':'
&& is_path_separator(path[2]);
if (drive_letter) {
return true;
}
// UNC paths begin with two separators, for example \\server\share.
if (path.length() >= 2 && is_path_separator(path[0]) && is_path_separator(path[1])) {
return true;
}
#endif
return false;
}
bool dir_exists(const std::string &path)
{
struct stat info;
@@ -0,0 +1,132 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include <px4_platform_common/shell.h>
#define MODULE_NAME "px4"
#include <px4_platform_common/log.h>
#include <cstdlib>
#include <cstring>
#include <string>
namespace px4
{
static std::string shell_quote(const std::string &value)
{
std::string quoted = "'";
for (char ch : value) {
if (ch == '\'') {
quoted += "'\\''";
} else {
quoted += ch;
}
}
quoted += "'";
return quoted;
}
static const char *startup_shell_wrapper = R"sh(
__px4_echo() {
__px4_line=$*
__px4_level=
case "$__px4_line" in
"DEBUG ["*) __px4_level=DEBUG; __px4_rest=${__px4_line#DEBUG } ;;
"DEBUG ["*) __px4_level=DEBUG; __px4_rest=${__px4_line#DEBUG } ;;
"INFO ["*) __px4_level=INFO; __px4_rest=${__px4_line#INFO } ;;
"INFO ["*) __px4_level=INFO; __px4_rest=${__px4_line#INFO } ;;
"WARN ["*) __px4_level=WARN; __px4_rest=${__px4_line#WARN } ;;
"WARN ["*) __px4_level=WARN; __px4_rest=${__px4_line#WARN } ;;
"ERROR ["*) __px4_level=ERROR; __px4_rest=${__px4_line#ERROR } ;;
"ERROR ["*) __px4_level=ERROR; __px4_rest=${__px4_line#ERROR } ;;
"PANIC ["*) __px4_level=PANIC; __px4_rest=${__px4_line#PANIC } ;;
"PANIC ["*) __px4_level=PANIC; __px4_rest=${__px4_line#PANIC } ;;
esac
if [ -z "$__px4_level" ]; then
command echo "$@"
return
fi
__px4_module=${__px4_rest#\[}
__px4_module=${__px4_module%%\]*}
__px4_message=${__px4_rest#*\]}
__px4_message=${__px4_message# }
if { [ -t 1 ] || [ "${PX4_FORCE_COLOR:-}" = 1 ]; } && [ -z "${NO_COLOR:-}" ]; then
case "$__px4_level" in
DEBUG) __px4_color='\033[32m' ;;
WARN) __px4_color='\033[33m' ;;
ERROR|PANIC) __px4_color='\033[31m' ;;
*) __px4_color='\033[0m' ;;
esac
printf '%b%-5s %b[%s] %b%s%b\n' "$__px4_color" "$__px4_level" '\033[37m' "$__px4_module" "$__px4_color" "$__px4_message" '\033[0m'
else
printf '%s\n' "$__px4_line"
fi
}
echo() {
__px4_echo "$@"
}
__px4_script=$1
set -- "$2"
. "$__px4_script"
)sh";
int run_shell_script(const std::string &script_path,
const std::string &binary_dir,
int instance)
{
// Prepend binary_dir to PATH so the script's `. px4-alias.sh` and
// px4-<module> invocations resolve regardless of the user's cwd.
const char *path = getenv("PATH");
if (path && strstr(path, binary_dir.c_str()) == nullptr) {
std::string updated = binary_dir + ':' + path;
setenv("PATH", updated.c_str(), 1);
}
PX4_INFO("startup script: /bin/sh %s %d", script_path.c_str(), instance);
const std::string cmd = "/bin/sh -c " + shell_quote(startup_shell_wrapper)
+ " px4-rc " + shell_quote(script_path) + ' ' + std::to_string(instance);
return system(cmd.c_str());
}
} // namespace px4
@@ -0,0 +1,52 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <string>
namespace px4::embedded_shell
{
// True when a real in-process shell backend is compiled into px4.exe.
bool is_available();
// Human-readable backend name for logs and diagnostics.
const char *backend_name();
// Execute the script in-process and return the shell exit status.
int run_script(const std::string &script_path,
const std::string &binary_dir,
int instance);
} // namespace px4::embedded_shell
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,168 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
// Windows needs a real in-process shell backend if px4.exe is to be
// redistributed as a self-contained component. The minimal parser below is
// only a stopgap for trivial generated scripts and should not be used for
// full rcS semantics.
#include <px4_platform_common/shell.h>
#define MODULE_NAME "px4"
#include <px4_platform_common/log.h>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <fstream>
#include <string>
#include "../../common/px4_daemon/pxh.h"
#include "embedded_backend.h"
namespace px4
{
static int set_env(const char *key, const char *value)
{
return _putenv_s(key, value);
}
static bool allow_external_shell_fallback()
{
const char *value = getenv("PX4_ALLOW_EXTERNAL_SH");
return value != nullptr && strcmp(value, "1") == 0;
}
static int run_shell_script_fallback(const std::string &script_path)
{
std::ifstream file(script_path);
if (!file.is_open()) {
PX4_ERR("Could not open startup script %s", script_path.c_str());
return -1;
}
PX4_WARN("falling back to the minimal Windows shell parser for %s", script_path.c_str());
int last_ret = 0;
std::string line;
while (std::getline(file, line)) {
// strip CR (files written with CRLF line endings)
while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
line.pop_back();
}
std::size_t first = line.find_first_not_of(" \t");
if (first == std::string::npos) {
continue; // blank line
}
std::string trimmed = line.substr(first);
if (trimmed[0] == '#') {
continue; // comment or shebang
}
if (trimmed.rfind(". ", 0) == 0) {
// Skip POSIX `source`; the only script init files do this
// for is px4-alias.sh, whose aliases are redundant here
// because Pxh dispatches bare command names via the apps map.
continue;
}
if (trimmed.rfind("echo ", 0) == 0 || trimmed == "echo") {
// Minimal `echo` so scripts can emit PASS/FAIL markers that
// ctest PASS_REGULAR_EXPRESSION looks for. Strips a single
// pair of surrounding quotes but otherwise prints verbatim.
std::string arg = trimmed.size() > 4 ? trimmed.substr(5) : "";
if (arg.size() >= 2 &&
((arg.front() == '"' && arg.back() == '"') ||
(arg.front() == '\'' && arg.back() == '\''))) {
arg = arg.substr(1, arg.size() - 2);
}
const std::string output = arg + '\n';
px4_log_write_text(stdout, output.data(), output.size());
last_ret = 0;
continue;
}
last_ret = px4_daemon::Pxh::process_line(trimmed, true);
}
return last_ret;
}
int run_shell_script(const std::string &script_path,
const std::string &binary_dir,
int instance)
{
const char *path = getenv("PATH");
if (path && strstr(path, binary_dir.c_str()) == nullptr) {
std::string updated = binary_dir + ':' + path;
set_env("PATH", updated.c_str());
} else if (!path) {
set_env("PATH", binary_dir.c_str());
}
if (embedded_shell::is_available()) {
PX4_INFO("startup script (%s): %s",
embedded_shell::backend_name(),
script_path.c_str());
return embedded_shell::run_script(script_path, binary_dir, instance);
}
if (allow_external_shell_fallback()) {
std::string cmd = "sh \"" + script_path + "\" " + std::to_string(instance);
PX4_WARN("no embedded shell backend compiled in, using external shell fallback: %s", cmd.c_str());
const int ret = system(cmd.c_str());
if (ret != -1) {
return ret;
}
}
PX4_WARN("no embedded shell backend is available for %s", script_path.c_str());
PX4_WARN("build a real backend into px4.exe for full rcS support");
return run_shell_script_fallback(script_path);
}
} // namespace px4