/**************************************************************************** * * Copyright (C) 2015-2018 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. * ****************************************************************************/ /** * @file main.cpp * * This is the main() of PX4 for POSIX. * * The application is designed as a daemon/server app with multiple clients. * Both, the server and the client is started using this main() function. * * If the executable is called with its usual name 'px4', it will start the * server. However, if it is started with an executable name (symlink) starting * with 'px4-' such as 'px4-navigator', it will start as a client and try to * connect to the server. * * The symlinks for all modules are created using the build system. * * @author Mark Charlebois * @author Roman Bapst * @author Julian Oes * @author Beat Küng */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "apps.h" #include "px4_middleware.h" #include "DriverFramework.hpp" #include "px4_middleware.h" #include "px4_daemon/client.h" #include "px4_daemon/server.h" #include "px4_daemon/pxh.h" #define MODULE_NAME "px4" static const char *LOCK_FILE_PATH = "/tmp/px4_lock"; #ifndef PATH_MAX #define PATH_MAX 1024 #endif static volatile bool _exit_requested = false; namespace px4 { void init_once(); } static void sig_int_handler(int sig_num); static void sig_fpe_handler(int sig_num); static void sig_segv_handler(int sig_num); static void register_sig_handler(); static void set_cpu_scaling(); static int create_symlinks_if_needed(std::string &data_path); static int create_dirs(); static int run_startup_script(const std::string &commands_file, const std::string &absolute_binary_path, int instance); static std::string get_absolute_binary_path(const std::string &argv0); static void wait_to_exit(); static bool is_already_running(int instance); static void print_usage(); static bool dir_exists(const std::string &path); static bool file_exists(const std::string &name); static std::string file_basename(std::string const &pathname); static std::string pwd(); #ifdef __PX4_SITL_MAIN_OVERRIDE int SITL_MAIN(int argc, char **argv); int SITL_MAIN(int argc, char **argv) #else int main(int argc, char **argv) #endif { bool is_client = false; bool pxh_off = false; /* Symlinks point to all commands that can be used as a client with a prefix. */ const char prefix[] = PX4_SHELL_COMMAND_PREFIX; int path_length = 0; std::string absolute_binary_path; // full path to the px4 binary being executed if (argc > 0) { /* The executed binary name could start with a path, so strip it away */ const std::string full_binary_name = argv[0]; const std::string binary_name = file_basename(full_binary_name); if (binary_name.compare(0, strlen(prefix), prefix) == 0) { is_client = true; } path_length = full_binary_name.length() - binary_name.length(); absolute_binary_path = get_absolute_binary_path(full_binary_name); } if (is_client) { int instance = 0; if (argc >= 3 && strcmp(argv[1], "--instance") == 0) { instance = strtoul(argv[2], nullptr, 10); /* update argv so that "--instance " is not visible anymore */ argc -= 2; for (int i = 1; i < argc; ++i) { argv[i] = argv[i + 2]; } } PX4_DEBUG("instance: %i", instance); if (!is_already_running(instance)) { if (errno) { PX4_ERR("Failed to communicate with daemon: %s", strerror(errno)); } else { PX4_ERR("PX4 daemon not running yet"); } return -1; } /* Remove the path and prefix. */ argv[0] += path_length + strlen(prefix); px4_daemon::Client client(instance); return client.process_args(argc, (const char **)argv); } else { /* Server/daemon apps need to parse the command line arguments. */ std::string data_path; std::string commands_file = "etc/init.d/rcS"; std::string test_data_path; int instance = 0; int myoptind = 1; int ch; const char *myoptarg = nullptr; while ((ch = px4_getopt(argc, argv, "hdt:s:i:", &myoptind, &myoptarg)) != EOF) { switch (ch) { case 'h': print_usage(); return 0; case 'd': pxh_off = true; break; case 't': test_data_path = myoptarg; break; case 's': commands_file = myoptarg; break; case 'i': instance = strtoul(myoptarg, nullptr, 10); break; default: PX4_ERR("unrecognized flag"); print_usage(); return -1; } } PX4_DEBUG("instance: %i", instance); if (myoptind < argc) { std::string optional_arg = argv[myoptind]; if (optional_arg.compare(0, 2, "__") != 0 || optional_arg.find(":=") == std::string::npos) { data_path = optional_arg; } // else: ROS argument (in the form __:=) } if (is_already_running(instance)) { // allow running multiple instances, but the server is only started for the first PX4_INFO("PX4 daemon already running for instance %i (%s)", instance, strerror(errno)); return -1; } int ret = create_symlinks_if_needed(data_path); if (ret != PX4_OK) { return ret; } if (test_data_path != "") { const std::string required_test_data_path = "./test_data"; if (!dir_exists(required_test_data_path)) { ret = symlink(test_data_path.c_str(), required_test_data_path.c_str()); if (ret != PX4_OK) { return ret; } } } if (!file_exists(commands_file)) { PX4_ERR("Error opening startup file, does not exist: %s", commands_file.c_str()); return -1; } register_sig_handler(); set_cpu_scaling(); px4_daemon::Server server(instance); server.start(); ret = create_dirs(); if (ret != PX4_OK) { return ret; } DriverFramework::Framework::initialize(); px4::init_once(); px4::init(argc, argv, "px4"); ret = run_startup_script(commands_file, absolute_binary_path, instance); // We now block here until we need to exit. if (pxh_off) { wait_to_exit(); } else { px4_daemon::Pxh pxh; pxh.run_pxh(); } // When we exit, we need to stop muorb on Snapdragon. #ifdef __PX4_POSIX_EAGLE // Sending muorb stop is needed if it is running to exit cleanly. // TODO: we should check with px4_task_is_running("muorb") before stopping it. std::string muorb_stop_cmd("muorb stop"); px4_daemon::Pxh::process_line(muorb_stop_cmd, true); #endif std::string cmd("shutdown"); px4_daemon::Pxh::process_line(cmd, true); } return PX4_OK; } int create_symlinks_if_needed(std::string &data_path) { std::string current_path = pwd(); if (data_path.empty()) { // No data path given, we'll just try to use the current working dir. data_path = current_path; PX4_INFO("assuming working directory is rootfs, no symlinks needed."); return PX4_OK; } if (data_path == current_path) { // We are already running in the data path, so no need to symlink PX4_INFO("working directory seems to be rootfs, no symlinks needed"); return PX4_OK; } const std::string path_sym_link = "etc"; PX4_DEBUG("path sym link: %s", path_sym_link.c_str()); std::string src_path = data_path; std::string dest_path = current_path + "/" + path_sym_link; struct stat info; if (lstat(dest_path.c_str(), &info) == 0) { if (S_ISLNK(info.st_mode)) { // recreate the symlink, as it might point to some other location than what we want now unlink(dest_path.c_str()); } else if (S_ISDIR(info.st_mode)) { return PX4_OK; } } PX4_INFO("Creating symlink %s -> %s", src_path.c_str(), dest_path.c_str()); // create sym-link int ret = symlink(src_path.c_str(), dest_path.c_str()); if (ret != 0) { PX4_ERR("Error creating symlink %s -> %s", src_path.c_str(), dest_path.c_str()); return ret; } else { PX4_DEBUG("Successfully created symlink %s -> %s", src_path.c_str(), dest_path.c_str()); } return PX4_OK; } int create_dirs() { std::string current_path = pwd(); std::vector dirs{"log", "eeprom"}; for (const auto &dir : dirs) { PX4_DEBUG("mkdir: %s", dir.c_str());; std::string dir_path = current_path + "/" + dir; if (dir_exists(dir_path)) { continue; } // create dirs int ret = mkdir(dir_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); if (ret != OK) { PX4_WARN("failed creating new dir: %s", dir_path.c_str()); return ret; } else { PX4_DEBUG("Successfully created dir %s", dir_path.c_str()); } } return PX4_OK; } void register_sig_handler() { // SIGINT struct sigaction sig_int {}; sig_int.sa_handler = sig_int_handler; sig_int.sa_flags = 0;// not SA_RESTART! // SIGFPE struct sigaction sig_fpe {}; sig_fpe.sa_handler = sig_fpe_handler; sig_fpe.sa_flags = 0;// not SA_RESTART! // SIGPIPE // We want to ignore if a PIPE has been closed. struct sigaction sig_pipe {}; sig_pipe.sa_handler = SIG_IGN; // SIGSEGV struct sigaction sig_segv {}; sig_segv.sa_handler = sig_segv_handler; sig_segv.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGINT, &sig_int, nullptr); //sigaction(SIGTERM, &sig_int, nullptr); sigaction(SIGFPE, &sig_fpe, nullptr); sigaction(SIGPIPE, &sig_pipe, nullptr); sigaction(SIGSEGV, &sig_segv, nullptr); } void sig_int_handler(int sig_num) { fflush(stdout); printf("\nExiting...\n"); fflush(stdout); px4_daemon::Pxh::stop(); _exit_requested = true; } void sig_fpe_handler(int sig_num) { fflush(stdout); printf("\nfloating point exception\n"); PX4_BACKTRACE(); fflush(stdout); px4_daemon::Pxh::stop(); _exit_requested = true; } void sig_segv_handler(int sig_num) { fflush(stdout); printf("\nSegmentation Fault\n"); PX4_BACKTRACE(); fflush(stdout); } void set_cpu_scaling() { #ifdef __PX4_POSIX_EAGLE // On Snapdragon we miss updates in sdlog2 unless all 4 CPUs are run // at the maximum frequency all the time. // Interestingely, cpu0 and cpu3 set the scaling for all 4 CPUs on Snapdragon. system("echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"); system("echo performance > /sys/devices/system/cpu/cpu3/cpufreq/scaling_governor"); // Alternatively we could also raise the minimum frequency to save some power, // unfortunately this still lead to some drops. //system("echo 1190400 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"); #endif } 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('/'); if (last_slash == std::string::npos) { // either relative path or in PATH (PATH is ignored here) return pwd(); } std::string base = argv0.substr(0, last_slash); if (base.length() > 0 && base[0] == '/') { // absolute path return base; } // relative path return pwd() + "/" + base; } int run_startup_script(const std::string &commands_file, const std::string &absolute_binary_path, int instance) { std::string shell_command("/bin/sh "); 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("Calling 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); } } else { PX4_INFO("Startup script empty"); } return ret; } void wait_to_exit() { while (!_exit_requested) { usleep(100000); } } void print_usage() { printf("Usage for Server/daemon process: \n"); printf("\n"); printf(" px4 [-h|-d] [-s ] [-t ] [] [-i ]\n"); printf("\n"); printf(" -s shell script to be used as startup (default=etc/init.d/rcS)\n"); printf(" directory where startup files and mixers are located,\n"); printf(" (if not given, CWD is used)\n"); printf(" -i px4 instance id to run multiple instances [0...N], default=0\n"); printf(" -h help/usage information\n"); printf(" -d daemon mode, don't start pxh shell\n"); printf("\n"); printf("Usage for client: \n"); printf("\n"); printf(" px4-MODULE [--instance ] command using symlink.\n"); printf(" e.g.: px4-commander status\n"); } bool is_already_running(int instance) { const std::string file_lock_path = std::string(LOCK_FILE_PATH) + '-' + std::to_string(instance); struct flock fl; int fd = open(file_lock_path.c_str(), O_RDWR | O_CREAT, 0666); if (fd < 0) { return false; } fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_pid = getpid(); if (fcntl(fd, F_SETLK, &fl) == -1) { // We failed to create a file lock, must be already locked. if (errno == EACCES || errno == EAGAIN) { return true; } return false; } errno = 0; return false; } bool px4_exit_requested(void) { return _exit_requested; } bool file_exists(const std::string &name) { struct stat buffer; return (stat(name.c_str(), &buffer) == 0); } static std::string file_basename(std::string const &pathname) { struct MatchPathSeparator { bool operator()(char ch) const { return ch == '/'; } }; return std::string(std::find_if(pathname.rbegin(), pathname.rend(), MatchPathSeparator()).base(), pathname.end()); } bool dir_exists(const std::string &path) { struct stat info; if (stat(path.c_str(), &info) != 0) { return false; } else if (info.st_mode & S_IFDIR) { return true; } return false; } std::string pwd() { char temp[PATH_MAX]; return (getcwd(temp, PATH_MAX) ? std::string(temp) : std::string("")); }