From 23846d0db409673398d17290c26375880db6b476 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Tue, 13 Apr 2021 12:11:56 +0300 Subject: [PATCH] [mapplauncherd] Adjust booster socket paths. Fixes JB#53864 OMP#JOLLA-39 Having all booster sockets reside at the same level in /run/user directory structure makes it difficult to limit what boosters sandboxed applications have access to. Move socket files to booster specific sub-directories. And as an enabler for sandboxed boosters, add another sub-directory level that can be used for identifying application specific boosters. As an example, silica-qt booster socket file path changes from /run/user/UID/mapplauncherd/silica-qt5 to /run/user/UID/mapplauncherd/_default/silica-qt5/socket and sandboxed silica-qt5 booster for application APP would use /run/user/UID/mapplauncherd/_APP/silica-qt5/socket Signed-off-by: Simo Piiroinen --- src/common/report.h | 2 +- src/invoker/invoker.c | 439 +++++++++++++++++++----------- src/launcherlib/booster.cpp | 57 ++++ src/launcherlib/booster.h | 12 +- src/launcherlib/daemon.cpp | 37 ++- src/launcherlib/daemon.h | 1 + src/launcherlib/socketmanager.cpp | 135 ++++++--- src/launcherlib/socketmanager.h | 6 +- 8 files changed, 474 insertions(+), 215 deletions(-) diff --git a/src/common/report.h b/src/common/report.h index 85b3a2f..4bc3405 100644 --- a/src/common/report.h +++ b/src/common/report.h @@ -43,7 +43,7 @@ enum report_type { }; extern void report_set_output(enum report_output new_output); -extern void report(enum report_type type, const char *msg, ...); +extern void report(enum report_type type, const char *msg, ...) __attribute__((format(printf, 2, 3))); #ifndef DEBUG_LOGGING_DISABLED #define debug(msg, ...) report(report_debug, msg, ##__VA_ARGS__) diff --git a/src/invoker/invoker.c b/src/invoker/invoker.c index 67b9de6..87c7b33 100644 --- a/src/invoker/invoker.c +++ b/src/invoker/invoker.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,71 @@ #include "invokelib.h" #include "search.h" +// Utility functions +static char *strip(char *str) +{ + if (str) { + char *dst = str; + char *src = str; + while (*src && isspace(*src)) + ++src; + for (;;) { + while (*src && !isspace(*src)) + *dst++ = *src++; + while (*src && isspace(*src)) + ++src; + if (!*src) + break; + *dst++ = ' '; + } + *dst = 0; + } + return str; +} + +static char *slice(const char *pos, const char **ppos, const char *delim) +{ + char *tok = NULL; + if (pos) { + const char *beg = pos; + while (*pos && !strchr(delim, *pos)) + ++pos; + tok = strndup(beg, pos - beg); + if (*pos) + ++pos; + } + if (ppos) + *ppos = pos; + return tok; +} + +static char **split(const char *str, const char *delim) +{ + char **arr = NULL; + if (str) { + /* Upper limit for token count is number of delimeters + one */ + int n = 1; + for (const char *pos = str; *pos; ++pos) + if (strchr(delim, *pos)) + ++n; + + /* Upper limit for required array size is token count + one */ + arr = calloc(n + 1, sizeof *arr); + + /* Fill in the array */ + int i = 0; + while (*str) { + char *tok = slice(str, &str, delim); + if (*strip(tok)) + arr[i++] = tok; + else + free(tok); + } + arr[i] = NULL; + } + return arr; +} + // Delay before exit. static const unsigned int EXIT_DELAY = 0; static const unsigned int MIN_EXIT_DELAY = 1; @@ -71,8 +137,6 @@ static void sigs_init(void); //! Pipe used to safely catch Unix signals static int g_signal_pipe[2]; -static const char *g_desktop_file = NULL; - // Forwards Unix signals from invoker to the invoked process static void sig_forwarder(int sig) { @@ -182,45 +246,62 @@ static bool invoke_recv_ack(int fd) } // Inits a socket connection for the given application type -static int invoker_init(const char *app_type) +static int invoker_init(const char *app_type, const char *app_name) { - int fd; - struct sockaddr_un sun; + bool connected = false; + int fd = -1; - fd = socket(PF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - { - error("Failed to open invoker socket for type %s.\n", app_type); - return -1; - } + /* Sanity check args */ + if (!app_type || strchr(app_type, '/')) + goto EXIT; + if (app_name && strchr(app_name, '/')) + goto EXIT; - sun.sun_family = AF_UNIX; - int maxSize = sizeof(sun.sun_path) - 1; - const char *runtimeDir = getenv("XDG_RUNTIME_DIR"); - const char *subpath = "/mapplauncherd/"; - const int subpathLen = strlen(subpath); + if (!runtimeDir || !*runtimeDir) { + error("XDG_RUNTIME_DIR is not defined.\n"); + goto EXIT; + } - if (runtimeDir && *runtimeDir) - strncpy(sun.sun_path, runtimeDir, maxSize - subpathLen); - else - strncpy(sun.sun_path, "/tmp", maxSize - subpathLen); + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) { + error("Failed to create socket: %m\n"); + goto EXIT; + } - sun.sun_path[maxSize - subpathLen] = 0; - strcat(sun.sun_path, subpath); + struct sockaddr_un sun = { + .sun_family = AF_UNIX, + }; + int maxSize = sizeof(sun.sun_path); + int length; - maxSize -= strlen(sun.sun_path); - if (maxSize < (int)strlen(app_type) || strchr(app_type, '/')) - die(1, "Invalid type of application: %s\n", app_type); + if (app_name) + length = snprintf(sun.sun_path, maxSize, "%s/mapplauncherd/_%s/%s/socket", + runtimeDir, app_name, app_type); + else + length = snprintf(sun.sun_path, maxSize, "%s/mapplauncherd/%s", + runtimeDir, app_type); - strcat(sun.sun_path, app_type); + if (length <= 0 || length >= maxSize) { + if (app_name) + error("Invalid booster type: %s / application: %s\n", + app_type, app_name); + else + error("Invalid booster type: %s\n", app_type); + goto EXIT; + } - if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) - { - error("Failed to initiate connect on the socket for type %s.\n", app_type); - return -1; + if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + if (errno != ENOENT) + warning("connect(\"%s\") failed: %m\n", sun.sun_path); + goto EXIT; } + debug("connected to: %s\n", sun.sun_path); + connected = true; + +EXIT: + if (!connected && fd != -1) + close(fd), fd = -1; return fd; } @@ -565,9 +646,36 @@ static int wait_for_launched_process_to_exit(int socket_fd, bool wait_term) return status; } +typedef struct InvokeArgs { + int prog_argc; + char **prog_argv; + char *prog_name; + const char *app_type; + const char *app_name; + uint32_t magic_options; + bool wait_term; + unsigned int respawn_delay; + bool test_mode; + const char *desktop_file; + unsigned int exit_delay; +} InvokeArgs; + +#define INVOKE_ARGS_INIT {\ + .prog_argc = 0,\ + .prog_argv = NULL,\ + .prog_name = NULL,\ + .app_type = NULL,\ + .app_name = "default",\ + .magic_options = INVOKER_MSG_MAGIC_OPTION_WAIT,\ + .wait_term = true,\ + .respawn_delay = RESPAWN_DELAY,\ + .test_mode = false,\ + .desktop_file = NULL,\ + .exit_delay = EXIT_DELAY,\ +} + // "normal" invoke through a socket connection -static int invoke_remote(int socket_fd, int prog_argc, char **prog_argv, char *prog_name, - uint32_t magic_options, bool wait_term, unsigned int respawn_delay) +static int invoke_remote(int socket_fd, const InvokeArgs *args) { // Get process priority errno = 0; @@ -579,38 +687,33 @@ static int invoke_remote(int socket_fd, int prog_argc, char **prog_argv, char *p // Connection with launcher process is established, // send the data. - invoker_send_magic(socket_fd, magic_options); - invoker_send_name(socket_fd, prog_name); - invoker_send_exec(socket_fd, prog_argv[0]); - invoker_send_args(socket_fd, prog_argc, prog_argv); + invoker_send_magic(socket_fd, args->magic_options); + invoker_send_name(socket_fd, args->prog_name); + invoker_send_exec(socket_fd, args->prog_argv[0]); + invoker_send_args(socket_fd, args->prog_argc, args->prog_argv); invoker_send_prio(socket_fd, prog_prio); - invoker_send_delay(socket_fd, respawn_delay); + invoker_send_delay(socket_fd, args->respawn_delay); invoker_send_ids(socket_fd, getuid(), getgid()); invoker_send_io(socket_fd); invoker_send_env(socket_fd); invoker_send_end(socket_fd); - if (prog_name) - { - free(prog_name); - } + if (args->desktop_file) + notify_app_lauch(args->desktop_file); - if (g_desktop_file) { - notify_app_lauch(g_desktop_file); - } - int exit_status = wait_for_launched_process_to_exit(socket_fd, wait_term); + int exit_status = wait_for_launched_process_to_exit(socket_fd, args->wait_term); return exit_status; } -static void invoke_fallback(char **prog_argv, char *prog_name, bool wait_term) +static void invoke_fallback(const InvokeArgs *args) { // Connection with launcher is broken, // try to launch application via execve warning("Connection with launcher process is broken. \n"); - error("Start application %s as a binary executable without launcher...\n", prog_name); + error("Start application %s as a binary executable without launcher...\n", args->prog_name); // Fork if wait_term not set - if(!wait_term) + if (!args->wait_term) { // Fork a new process pid_t newPid = fork(); @@ -627,91 +730,87 @@ static void invoke_fallback(char **prog_argv, char *prog_name, bool wait_term) } // Exec the process image - execve(prog_name, prog_argv, environ); + execve(args->prog_name, args->prog_argv, environ); perror("execve"); /* execve() only returns on error */ exit(EXIT_FAILURE); } // Invokes the given application -static int invoke(int prog_argc, char **prog_argv, char *prog_name, - const char *app_type, uint32_t magic_options, bool wait_term, unsigned int respawn_delay, - bool test_mode) +static int invoke(InvokeArgs *args) { - int status = 0; - if (prog_name && prog_argv) - { - //If TEST_MODE_CONTROL_FILE doesn't exists switch off test mode - if (test_mode && access(TEST_MODE_CONTROL_FILE, F_OK) != 0) - { - test_mode = false; - info("Invoker test mode is not enabled.\n"); - } - - // The app can be launched with a comma delimited list of boosters to attempt. - - char *app_type_list = strdup(app_type); - char *saveptr = NULL; - char *token; + /* Note: Contents of 'args' are assumed to have been + * checked and sanitized before invoke() call. + */ + + int status = EXIT_FAILURE; + + /* The app can be launched with a comma delimited list of + * booster types to attempt. + */ + char **types = split(args->app_type, ","); + + int fd = -1; + + /* Session booster is a special case: + * - is never going to be application specific + * - can use and still uses legacy socket path + * - mutually exclusive with all other choises + * - no fallbacks should be utilized + */ + + bool tried_session = false; + for (size_t i = 0; !tried_session && types[i]; ++i) { + if (strcmp(types[i], "session")) + continue; + tried_session = true; + fd = invoker_init(types[i], NULL); + } - int fd = -1; - for (token = strtok_r(app_type_list, ",", &saveptr); fd == -1 && token != NULL; token = strtok_r(NULL, ",", &saveptr)) - { - app_type = token; - if (app_type != app_type_list) { - info("Trying fallback type %s.\n", app_type); - } - fd = invoker_init(app_type); - } - - if (fd == -1) - { - // This is a fallback if connection with the launcher - // process is broken - - // if the attempt was to use the generic booster, and that failed, - // then give up and start unboosted. otherwise, make an attempt to - // use the generic booster before not boosting. this means single - // instance support (for instance) will still work. - if (strcmp(app_type, "generic") == 0) - { - warning("Can't try fall back to generic, already using it\n"); - invoke_fallback(prog_argv, prog_argv[0], wait_term); - } - else - { - warning("Booster %s is not available. Falling back to generic.\n", app_type); - invoke(prog_argc, prog_argv, prog_argv[0], "generic", magic_options, wait_term, respawn_delay, test_mode); - } - } - // "normal" invoke through a socket connetion - else - { - status = invoke_remote(fd, prog_argc, prog_argv, prog_name, - magic_options, wait_term, respawn_delay); - close(fd); + /* Application aware boosters + * - have fallback strategy, but it + * - must not cross application vs "default" boundary + */ + if (fd == -1 && !tried_session) { + bool tried_generic = false; + for (size_t i = 0; fd == -1 && types[i]; ++i) { + if (!strcmp(types[i], "generic")) + tried_generic = true; + fd = invoker_init(types[i], args->app_name); } + if (fd == -1 && !tried_generic) + fd = invoker_init("generic", args->app_name); + } - free(app_type_list); + if (fd != -1) { + /* "normal" invoke through a socket connetion */ + status = invoke_remote(fd, args); + close(fd); + } else if (tried_session) { + warning("Launch failed, session booster is not available.\n"); + } else if (strcmp(args->app_name, "default")) { + /* Boosters that deal explicitly with one application only + * must be assumed to run within sandbox -> skipping boosting + * would also skip sandboxing -> no direct launch fallback + */ + warning("Launch failed, application specific booster is not available.\n"); + } else { + /* Give up and start unboosted */ + warning("Also fallback boosters failed, launch without boosting.\n"); + invoke_fallback(args); + /* Returns only in case of: no-wait was specified and fork succeeded */ + status = EXIT_SUCCESS; } - + + for (int i = 0; types[i]; ++i) + free(types[i]); + free(types); + return status; } int main(int argc, char *argv[]) { - const char *app_type = NULL; - int prog_argc = 0; - uint32_t magic_options = 0; - bool wait_term = true; - unsigned int delay = EXIT_DELAY; - unsigned int respawn_delay = RESPAWN_DELAY; - char **prog_argv = NULL; - char *prog_name = NULL; - struct stat file_stat; - bool test_mode = false; - - // wait-term parameter by default - magic_options |= INVOKER_MSG_MAGIC_OPTION_WAIT; + InvokeArgs args = INVOKE_ARGS_INIT; // Called with a different name (old way of using invoker) ? if (!strstr(argv[0], PROG_NAME_INVOKER) ) @@ -733,6 +832,7 @@ int main(int argc, char *argv[]) {"daemon-mode", no_argument, NULL, 'o'}, // Legacy alias {"test-mode", no_argument, NULL, 'T'}, {"type", required_argument, NULL, 't'}, + {"application", required_argument, NULL, 'a'}, {"delay", required_argument, NULL, 'd'}, {"respawn", required_argument, NULL, 'r'}, {"splash", required_argument, NULL, 'S'}, @@ -745,7 +845,7 @@ int main(int argc, char *argv[]) // The use of + for POSIXLY_CORRECT behavior is a GNU extension, but avoids polluting // the environment int opt; - while ((opt = getopt_long(argc, argv, "+hcwnGDsoTd:t:r:S:L:F:", longopts, NULL)) != -1) + while ((opt = getopt_long(argc, argv, "+hcwnGDsoTd:t:a:r:S:L:F:", longopts, NULL)) != -1) { switch(opt) { @@ -758,41 +858,45 @@ int main(int argc, char *argv[]) break; case 'o': - magic_options |= INVOKER_MSG_MAGIC_OPTION_OOM_ADJ_DISABLE; + args.magic_options |= INVOKER_MSG_MAGIC_OPTION_OOM_ADJ_DISABLE; break; case 'n': - wait_term = false; - magic_options &= (~INVOKER_MSG_MAGIC_OPTION_WAIT); + args.wait_term = false; + args.magic_options &= (~INVOKER_MSG_MAGIC_OPTION_WAIT); break; case 'G': - magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_GLOBAL; + args.magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_GLOBAL; break; case 'D': - magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_DEEP; + args.magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_DEEP; break; case 'T': - test_mode = true; + args.test_mode = true; break; case 't': - app_type = optarg; + args.app_type = optarg; + break; + + case 'a': + args.app_name = optarg; break; case 'd': - delay = get_delay(optarg, "delay", MIN_EXIT_DELAY, MAX_EXIT_DELAY); + args.exit_delay = get_delay(optarg, "delay", MIN_EXIT_DELAY, MAX_EXIT_DELAY); break; case 'r': - respawn_delay = get_delay(optarg, "respawn delay", + args.respawn_delay = get_delay(optarg, "respawn delay", MIN_RESPAWN_DELAY, MAX_RESPAWN_DELAY); break; case 's': - magic_options |= INVOKER_MSG_MAGIC_OPTION_SINGLE_INSTANCE; + args.magic_options |= INVOKER_MSG_MAGIC_OPTION_SINGLE_INSTANCE; break; case 'S': @@ -801,7 +905,7 @@ int main(int argc, char *argv[]) break; case 'F': - g_desktop_file = optarg; + args.desktop_file = optarg; break; case '?': @@ -810,57 +914,63 @@ int main(int argc, char *argv[]) } // Option processing stops as soon as application name is encountered - if (optind < argc) - { - prog_name = search_program(argv[optind]); - prog_argc = argc - optind; - prog_argv = &argv[optind]; - // Force argv[0] of application to be the absolute path to allow the - // application to find out its installation directory from there - prog_argv[0] = prog_name; - } + args.prog_argc = argc - optind; + args.prog_argv = &argv[optind]; - // Check if application name isn't defined - if (!prog_name) - { - report(report_error, "Application's name is not defined.\n"); - usage(1); + if (args.prog_argc < 1) { + report(report_error, "No command line to invoke was given.\n"); + exit(EXIT_FAILURE); } + // Force argv[0] of application to be the absolute path to allow the + // application to find out its installation directory from there + args.prog_argv[0] = search_program(args.prog_argv[0]); + // Check if application exists - if (stat(prog_name, &file_stat)) - { - report(report_error, "%s: not found\n", prog_name); + struct stat file_stat; + if (stat(args.prog_argv[0], &file_stat) == -1) { + report(report_error, "%s: not found: %m\n", args.prog_argv[0]); return EXIT_STATUS_APPLICATION_NOT_FOUND; } - // Check that - if (!S_ISREG(file_stat.st_mode) && !S_ISLNK(file_stat.st_mode)) - { - report(report_error, "%s: not a file\n", prog_name); + // Check that application is regular file (or symlink to such) + if (!S_ISREG(file_stat.st_mode)) { + report(report_error, "%s: not a file\n", args.prog_argv[0]); return EXIT_STATUS_APPLICATION_NOT_FOUND; } // If it's a launcher, append its first argument to the name // (at this point, we have already checked if it exists and is a file) - if (strcmp(prog_name, "/usr/bin/sailfish-qml") == 0) { - if (prog_argc < 2) { - report(report_error, "%s: requires an argument\n", prog_name); + if (strcmp(args.prog_argv[0], "/usr/bin/sailfish-qml") == 0) { + if (args.prog_argc < 2) { + report(report_error, "%s: requires an argument\n", args.prog_argv[0]); return EXIT_STATUS_APPLICATION_NOT_FOUND; } - // Must not free() the existing prog_name, as it's pointing to prog_argv[0] - prog_name = (char *)malloc(strlen(prog_argv[0]) + strlen(prog_argv[1]) + 2); - sprintf(prog_name, "%s %s", prog_argv[0], prog_argv[1]); + if (asprintf(&args.prog_name, "%s %s", args.prog_argv[0], args.prog_argv[1]) < 0) + exit(EXIT_FAILURE); + } else { + if (!(args.prog_name = strdup(args.prog_argv[0]))) + exit(EXIT_FAILURE); } - if (!app_type) - { + if (!args.app_type) { report(report_error, "Application type must be specified with --type.\n"); usage(1); } + if (!args.app_name) { + report(report_error, "Application name must be specified with --application.\n"); + usage(1); + } + + // If TEST_MODE_CONTROL_FILE doesn't exists switch off test mode + if (args.test_mode && access(TEST_MODE_CONTROL_FILE, F_OK) != 0) { + args.test_mode = false; + info("Invoker test mode is not enabled.\n"); + } + if (pipe(g_signal_pipe) == -1) { report(report_error, "Creating a pipe for Unix signals failed!\n"); @@ -868,15 +978,14 @@ int main(int argc, char *argv[]) } // Send commands to the launcher daemon - info("Invoking execution: '%s'\n", prog_name); - int ret_val = invoke(prog_argc, prog_argv, prog_name, app_type, magic_options, wait_term, respawn_delay, test_mode); + info("Invoking execution: '%s'\n", args.prog_name); + int ret_val = invoke(&args); // Sleep for delay before exiting - if (delay) - { + if (args.exit_delay) { // DBUS cannot cope some times if the invoker exits too early. - debug("Delaying exit for %d seconds..\n", delay); - sleep(delay); + debug("Delaying exit for %d seconds..\n", args.exit_delay); + sleep(args.exit_delay); } return ret_val; diff --git a/src/launcherlib/booster.cpp b/src/launcherlib/booster.cpp index c71c330..61c4f6c 100644 --- a/src/launcherlib/booster.cpp +++ b/src/launcherlib/booster.cpp @@ -58,6 +58,7 @@ Booster::Booster() : m_oldPriority(0), m_oldPriorityOk(false), m_spaceAvailable(0), + m_boostedApplication("default"), m_bootMode(false) { Connection::setMountNamespace(Connection::getMountNamespace(getpid())); @@ -586,6 +587,62 @@ bool Booster::popPriority() return false; } +const string &Booster::boostedApplication() const +{ + return m_boostedApplication; +} + +void Booster::setBoostedApplication(const string &application) +{ + string filtered; + filtered.reserve(application.size()); + bool alnum = false; + bool error = false; + for (auto chr : application) { + switch (chr) { + case 'A' ... 'Z': + chr += 'a' - 'A'; + // FALLTHRU + case 'a' ... 'z': + case '0' ... '9': + alnum = true; + // FALLTHRU + case '-': + case '_': + if (alnum) { + filtered.push_back(chr); + break; + } + // FALLTHRU + default: + error = true; + break; + } + } + if (error || filtered.empty()) + Logger::logError("Rejected invalid application name '%s'", application.c_str()); + else + m_boostedApplication = filtered; +} + +const string Booster::socketId() const +{ + string id; + if (boosterType() == "silica-session") { + // Session is never going to be application specific + // -> retain legacy socket path + id += boosterType(); + } else { + id += '_'; + id += boostedApplication(); + id += '/'; + id += boosterType(); + id += "/socket"; + } + return id; +} + + pid_t Booster::invokersPid() { if (m_connection->isReportAppExitStatusNeeded()) diff --git a/src/launcherlib/booster.h b/src/launcherlib/booster.h index 2ce43fb..d5cb444 100644 --- a/src/launcherlib/booster.h +++ b/src/launcherlib/booster.h @@ -1,6 +1,8 @@ /*************************************************************************** ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2012 - 2021 Jolla Ltd. +** Copyright (c) 2021 Open Mobile Platform LLC. ** All rights reserved. ** Contact: Nokia Corporation (directui@nokia.com) ** @@ -107,6 +109,11 @@ public: */ virtual const string & boosterType() const = 0; + const string &boostedApplication() const; + void setBoostedApplication(const string &application); + + const string socketId() const; + //! Get invoker's pid pid_t invokersPid(); @@ -213,6 +220,9 @@ private: //! Original space available for arguments int m_spaceAvailable; + //! Application name when applicable, or "default" + string m_boostedApplication; + //! True, if being run in boot mode. bool m_bootMode; diff --git a/src/launcherlib/daemon.cpp b/src/launcherlib/daemon.cpp index b6c021f..763d44c 100644 --- a/src/launcherlib/daemon.cpp +++ b/src/launcherlib/daemon.cpp @@ -26,6 +26,7 @@ #include "singleinstance.h" #include "socketmanager.h" +#include #include #include #include @@ -147,6 +148,9 @@ void Daemon::run(Booster *booster) { m_booster = booster; + if (!m_boostedApplication.empty()) + m_booster->setBoostedApplication(m_boostedApplication); + // Make sure that LD_BIND_NOW does not prevent dynamic linker to // use lazy binding in later dlopen() calls. unsetenv("LD_BIND_NOW"); @@ -156,7 +160,7 @@ void Daemon::run(Booster *booster) // Create socket for the booster Logger::logDebug("Daemon: initing socket: %s", booster->boosterType().c_str()); - m_socketManager->initSocket(booster->boosterType()); + m_socketManager->initSocket(booster->socketId()); // Daemonize if desired if (m_daemon) @@ -417,7 +421,7 @@ void Daemon::forkBooster(int sleepTime) // Initialize and wait for commands from invoker try { m_booster->initialize(m_initialArgc, m_initialArgv, m_boosterLauncherSocket[1], - m_socketManager->findSocket(m_booster->boosterType().c_str()), + m_socketManager->findSocket(m_booster->socketId()), m_singleInstance, m_bootMode); } catch (const std::runtime_error &e) { Logger::logError("Booster: Failed to initialize: %s\n", e.what()); @@ -603,33 +607,46 @@ void Daemon::daemonize() void Daemon::parseArgs(const ArgVect & args) { + std::deque queue; for (ArgVect::const_iterator i(args.begin() + 1); i != args.end(); i++) - { - if ((*i) == "--boot-mode" || (*i) == "-b") + queue.push_back(*i); + + while (!queue.empty()) { + string option(queue.front()); + queue.pop_front(); + + if (option == "--boot-mode" || option == "-b") { Logger::logInfo("Daemon: Boot mode set."); m_bootMode = true; } - else if ((*i) == "--daemon" || (*i) == "-d") + else if (option == "--daemon" || option == "-d") { m_daemon = true; } - else if ((*i) == "--debug") + else if (option == "--debug") { Logger::setDebugMode(true); m_debugMode = true; } - else if ((*i) == "--help" || (*i) == "-h") + else if (option == "--help" || option == "-h") { usage(args[0].c_str(), EXIT_SUCCESS); } - else if ((*i) == "--systemd") + else if (option == "--systemd") { m_notifySystemd = true; } + else if (option == "--application" || option == "-a") + { + if (!queue.empty()) { + m_boostedApplication = queue.front(); + queue.pop_front(); + } + } else { - if ((*i).find_first_not_of(' ') != string::npos) + if (option.find_first_not_of(' ') != string::npos) usage(args[0].c_str(), EXIT_FAILURE); } } @@ -649,6 +666,8 @@ void Daemon::usage(const char *name, int status) " Boot mode can be activated also by sending SIGUSR2\n" " to the launcher.\n" " -d, --daemon Run as %s a daemon.\n" + " -a, --application \n" + " Run as application specific booster.\n" " --systemd Notify systemd when initialization is done\n" " --debug Enable debug messages and log everything also to stdout.\n" " -h, --help Print this help.\n\n", diff --git a/src/launcherlib/daemon.h b/src/launcherlib/daemon.h index d0838ec..75c0915 100644 --- a/src/launcherlib/daemon.h +++ b/src/launcherlib/daemon.h @@ -206,6 +206,7 @@ private: //! True if systemd needs to be notified bool m_notifySystemd; + string m_boostedApplication; //! Drop capabilities needed for initialization static void dropCapabilities(); diff --git a/src/launcherlib/socketmanager.cpp b/src/launcherlib/socketmanager.cpp index a6124eb..67cbe0e 100644 --- a/src/launcherlib/socketmanager.cpp +++ b/src/launcherlib/socketmanager.cpp @@ -1,6 +1,8 @@ /*************************************************************************** ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2013 - 2021 Jolla Ltd. +** Copyright (c) 2021 Open Mobile Platform LLC. ** All rights reserved. ** Contact: Nokia Corporation (directui@nokia.com) ** @@ -35,7 +37,7 @@ SocketManager::SocketManager() { const char *runtimeDir = getenv("XDG_RUNTIME_DIR"); if (!runtimeDir || !*runtimeDir) - runtimeDir = "/tmp/"; + runtimeDir = "/tmp"; m_socketRootPath = runtimeDir; m_socketRootPath += "/mapplauncherd"; @@ -46,66 +48,123 @@ SocketManager::SocketManager() m_socketRootPath.c_str(), strerror(errno)); } } +} - m_socketRootPath += '/'; +static string extractTail(string &work) +{ + string tok; + string::size_type n; + if ((n = work.rfind('/')) == string::npos) { + tok = work; + work.clear(); + } else { + tok = work.substr(n + 1); + work.erase(n); + } + return tok; } -void SocketManager::initSocket(const string & socketId) +string SocketManager::prepareSocket(const string &socketId) const { - string socketPath = m_socketRootPath + socketId; + string socketPath; + + /* Extract APP/TYPE/SOCKET */ + string work(socketId); + string socketFile(extractTail(work)); + string typeId(extractTail(work)); + string appId(extractTail(work)); + if (!work.empty() || socketFile.empty()) { + Logger::logError("Daemon: Invalid socketId: %s\n", socketId.c_str()); + return socketPath; + } + + /* Construct socket path and subdirectories */ + work = m_socketRootPath; + + if (!appId.empty()) { + work += '/'; + work += appId; + if (mkdir(work.c_str(), 0750) == -1 && errno != EEXIST) { + Logger::logError("Daemon: Cannot create socket app directory %s: %s\n", + work.c_str(), strerror(errno)); + return socketPath; + } + } + + if (!typeId.empty()) { + work += '/'; + work += typeId; + if (mkdir(work.c_str(), 0750) == -1 && errno != EEXIST) { + Logger::logError("Daemon: Cannot create socket type directory %s: %s\n", + work.c_str(), strerror(errno)); + return socketPath; + } + } + + work += '/'; + work += socketFile; + if (unlink(work.c_str()) == -1 && errno != ENOENT) { + Logger::logError("Daemon: Cannot remove stale socket %s: %s\n", + work.c_str(), strerror(errno)); + return socketPath; + } + + /* Success */ + socketPath = work; + return socketPath; +} + +void SocketManager::initSocket(const string & socketId) +{ // Initialize a socket at socketId if one already doesn't // exist for that id / path. if (m_socketHash.find(socketId) == m_socketHash.end()) { + string socketPath = prepareSocket(socketId); + if (socketPath.empty()) { + string msg; + msg += "SocketManager: Failed to prepare socketId "; + msg += socketId; + throw std::runtime_error(msg); + } + Logger::logDebug("SocketManager: Initing socket at '%s'..", socketPath.c_str()); + // Initialize the socket struct + struct sockaddr_un sun; + memset(&sun, 0, sizeof sun); + sun.sun_family = AF_UNIX; + + size_t maxSize = sizeof(sun.sun_path); + int length = snprintf(sun.sun_path, maxSize, "%s", socketPath.c_str()); + if (length <= 0 || (size_t)length >= maxSize) { + string msg; + msg += "SocketManager: Invalid socket path "; + msg += socketPath; + throw std::runtime_error(msg); + } + // Create a new local socket int socketFd = socket(PF_UNIX, SOCK_STREAM, 0); if (socketFd < 0) throw std::runtime_error("SocketManager: Failed to open socket\n"); - // TODO: Error if socketPath >= maxLen. Also unlink() here may - // try to remove a different file than is passed to sun.sa_data. - - // Remove the previous socket file - struct stat sb; - stat(socketPath.c_str(), &sb); - if (S_ISSOCK(sb.st_mode)) - { - // coverity[toctou] - if (unlink(socketPath.c_str()) == -1) - { - std::string msg("SocketManager: Failed to unlink existing socket file '"); - msg += socketPath + "': " + strerror(errno); - Logger::logWarning(msg.c_str()); - } - } - - // Initialize the socket struct - struct sockaddr_un sun; - sun.sun_family = AF_UNIX; - int maxLen = sizeof(sun.sun_path) - 1; - strncpy(sun.sun_path, socketPath.c_str(), maxLen); - sun.sun_path[maxLen] = '\0'; - // Bind the socket if (bind(socketFd, (struct sockaddr*) &sun, sizeof(sun)) < 0) { - std::string msg("SocketManager: Failed to bind to socket (fd="); - std::stringstream ss; - ss << socketFd; - msg += ss.str() + ")"; + string msg; + msg += "SocketManager: Failed to bind socket to "; + msg += socketPath; throw std::runtime_error(msg); } // Listen to the socket if (listen(socketFd, 10) < 0) { - std::string msg("SocketManager: Failed to listen to socket (fd="); - std::stringstream ss; - ss << socketFd; - msg += ss.str() + ")"; + string msg; + msg += "SocketManager: Failed to listen to socket "; + msg += socketPath; throw std::runtime_error(msg); } @@ -165,6 +224,6 @@ void SocketManager::addMapping(const string & socketId, int fd) string SocketManager::socketRootPath() const { - return m_socketRootPath; + return m_socketRootPath + '/'; } diff --git a/src/launcherlib/socketmanager.h b/src/launcherlib/socketmanager.h index 58e3c5d..f319b1c 100644 --- a/src/launcherlib/socketmanager.h +++ b/src/launcherlib/socketmanager.h @@ -1,6 +1,8 @@ /*************************************************************************** ** -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (c) 2012 - 2021 Jolla Ltd. +** Copyright (c) 2021 Open Mobile Platform LLC. ** All rights reserved. ** Contact: Nokia Corporation (directui@nokia.com) ** @@ -38,6 +40,8 @@ class DECL_EXPORT SocketManager public: SocketManager(); + string prepareSocket(const string &socketId) const; + /*! \brief Initialize a file socket. * \param socketId Path to the socket file. */