mirror of https://github.com/cutefishos/appmotor
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
695 lines
19 KiB
C++
695 lines
19 KiB
C++
/***************************************************************************
|
|
**
|
|
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
** All rights reserved.
|
|
** Contact: Nokia Corporation (directui@nokia.com)
|
|
**
|
|
** This file is part of applauncherd
|
|
**
|
|
** If you have questions regarding the use of this file, please contact
|
|
** Nokia at directui@nokia.com.
|
|
**
|
|
** This library is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU Lesser General Public
|
|
** License version 2.1 as published by the Free Software Foundation
|
|
** and appearing in the file LICENSE.LGPL included in the packaging
|
|
** of this file.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "booster.h"
|
|
#include "daemon.h"
|
|
#include "connection.h"
|
|
#include "singleinstance.h"
|
|
#include "socketmanager.h"
|
|
#include "logger.h"
|
|
|
|
#include <cstdlib>
|
|
#include <dlfcn.h>
|
|
#include <cerrno>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <sys/user.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/resource.h>
|
|
#include <fcntl.h>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <syslog.h>
|
|
#include <dirent.h>
|
|
#include <algorithm>
|
|
|
|
#include <fstream>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <grp.h>
|
|
|
|
#include "coverage.h"
|
|
|
|
Booster::Booster() :
|
|
m_appData(new AppData),
|
|
m_connection(NULL),
|
|
m_oldPriority(0),
|
|
m_oldPriorityOk(false),
|
|
m_spaceAvailable(0),
|
|
m_bootMode(false)
|
|
{
|
|
}
|
|
|
|
Booster::~Booster()
|
|
{
|
|
delete m_connection;
|
|
m_connection = NULL;
|
|
|
|
delete m_appData;
|
|
m_appData = NULL;
|
|
}
|
|
|
|
void Booster::initialize(int initialArgc, char ** initialArgv, int newBoosterLauncherSocket,
|
|
int socketFd, SingleInstance * singleInstance,
|
|
bool newBootMode)
|
|
{
|
|
m_bootMode = newBootMode;
|
|
|
|
setBoosterLauncherSocket(newBoosterLauncherSocket);
|
|
|
|
// Drop priority (nice = 10)
|
|
pushPriority(10);
|
|
|
|
// Preload stuff
|
|
if (!m_bootMode)
|
|
preload();
|
|
|
|
// Rename process to temporary booster process name
|
|
std::string temporaryProcessName = "booster [";
|
|
temporaryProcessName += boosterType();
|
|
temporaryProcessName += "]";
|
|
const char * tempArgv[] = {temporaryProcessName.c_str()};
|
|
renameProcess(initialArgc, initialArgv, 1, tempArgv);
|
|
|
|
// Restore priority
|
|
popPriority();
|
|
|
|
while (true)
|
|
{
|
|
// Wait and read commands from the invoker
|
|
Logger::logDebug("Booster: Wait for message from invoker");
|
|
if (!receiveDataFromInvoker(socketFd))
|
|
throw std::runtime_error("Booster: Couldn't read command\n");
|
|
|
|
// Run process as single instance if requested
|
|
if (m_appData->singleInstance())
|
|
{
|
|
// Check if instance is already running
|
|
SingleInstancePluginEntry * pluginEntry = singleInstance->pluginEntry();
|
|
if (pluginEntry)
|
|
{
|
|
if (!pluginEntry->lockFunc(m_appData->appName().c_str()))
|
|
{
|
|
// Try to activate the window of the existing instance
|
|
if (!pluginEntry->activateExistingInstanceFunc(m_appData->appName().c_str()))
|
|
{
|
|
Logger::logWarning("Booster: Can't activate existing instance of the application!");
|
|
m_connection->sendExitValue(EXIT_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
m_connection->sendExitValue(EXIT_SUCCESS);
|
|
}
|
|
m_connection->close();
|
|
|
|
// invoker requested to start an application that is already running
|
|
// booster is not needed this time, let's wait for the next connection from invoker
|
|
continue;
|
|
}
|
|
|
|
// Close the single-instance plugin
|
|
singleInstance->closePlugin();
|
|
}
|
|
else
|
|
{
|
|
Logger::logWarning("Booster: Single-instance launch wanted, but single-instance plugin not loaded!");
|
|
}
|
|
}
|
|
|
|
//this instance of booster will be used to start application, exit from the loop
|
|
break;
|
|
}
|
|
|
|
// Send parent process a message that it can create a new booster,
|
|
// send pid of invoker, booster respawn value and invoker socket connection.
|
|
sendDataToParent();
|
|
|
|
// Give the process the real application name now that it
|
|
// has been read from invoker in receiveDataFromInvoker().
|
|
renameProcess(initialArgc, initialArgv, m_appData->argc(), m_appData->argv());
|
|
|
|
close(boosterLauncherSocket());
|
|
|
|
// close invoker socket connection
|
|
m_connection->close();
|
|
|
|
// Don't care about fate of parent applauncherd process any more
|
|
prctl(PR_SET_PDEATHSIG, 0);
|
|
}
|
|
|
|
bool Booster::bootMode() const
|
|
{
|
|
return m_bootMode;
|
|
}
|
|
|
|
void Booster::sendDataToParent()
|
|
{
|
|
// Number of data items to be sent to
|
|
// the parent (launcher) process
|
|
const unsigned int NUM_DATA_ITEMS = 2;
|
|
|
|
struct iovec iov[NUM_DATA_ITEMS];
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
char buf[CMSG_SPACE(sizeof(int))];
|
|
|
|
// Signal the parent process that it can create a new
|
|
// waiting booster process and close write end
|
|
// Send to the parent process pid of invoker for tracking
|
|
pid_t pid = invokersPid();
|
|
iov[0].iov_base = &pid;
|
|
iov[0].iov_len = sizeof(pid_t);
|
|
|
|
// Send to the parent process booster respawn delay value
|
|
int delay = m_appData->delay();
|
|
iov[1].iov_base = &delay;
|
|
iov[1].iov_len = sizeof(int);
|
|
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = NUM_DATA_ITEMS;
|
|
msg.msg_name = NULL;
|
|
msg.msg_namelen = 0;
|
|
|
|
// Set special control fields if exit status of the launched
|
|
// application is needed. In this case we want to give the fd of the
|
|
// invoker <-> booster socket connection to the parent process (launcher)
|
|
// so that it can send the exit status back to invoker. It'd be impossible
|
|
// from the booster process if exec() was used.
|
|
if (m_connection->isReportAppExitStatusNeeded())
|
|
{
|
|
// Send socket file descriptor to parent
|
|
int fd = m_connection->getFd();
|
|
msg.msg_control = buf;
|
|
msg.msg_controllen = sizeof(buf);
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
|
memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
|
|
}
|
|
else
|
|
{
|
|
msg.msg_control = NULL;
|
|
msg.msg_controllen = 0;
|
|
}
|
|
|
|
if (sendmsg(boosterLauncherSocket(), &msg, 0) < 0)
|
|
{
|
|
Logger::logError("Booster: Couldn't send data to launcher process\n");
|
|
}
|
|
}
|
|
|
|
bool Booster::receiveDataFromInvoker(int socketFd)
|
|
{
|
|
// delete previous connection instance because booster can
|
|
// connect with several invokers due to single-instance feature
|
|
if (m_connection != NULL)
|
|
{
|
|
delete m_connection;
|
|
m_connection = NULL;
|
|
}
|
|
|
|
// Setup the conversation channel with the invoker.
|
|
m_connection = new Connection(socketFd);
|
|
|
|
// Accept a new invocation.
|
|
if (m_connection->accept(m_appData))
|
|
{
|
|
// Receive application data from the invoker
|
|
if(!m_connection->receiveApplicationData(m_appData))
|
|
{
|
|
m_connection->close();
|
|
return false;
|
|
}
|
|
|
|
// Close the connection if exit status doesn't need
|
|
// to be sent back to invoker
|
|
if (!m_connection->isReportAppExitStatusNeeded())
|
|
{
|
|
m_connection->close();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Booster::run(SocketManager * socketManager)
|
|
{
|
|
if (!m_appData->fileName().empty())
|
|
{
|
|
// We can close sockets here because
|
|
// socket FD is passed to daemon already
|
|
if (socketManager)
|
|
{
|
|
socketManager->closeAllSockets();
|
|
}
|
|
|
|
// Execute the binary
|
|
Logger::logDebug("Booster: invoking '%s' ", m_appData->fileName().c_str());
|
|
try {
|
|
return launchProcess();
|
|
} catch (const std::runtime_error &e) {
|
|
Logger::logError("Booster: Failed to invoke: %s\n", e.what());
|
|
// Also log to the terminal so the error appears on the terminal too
|
|
fprintf(stderr, "Failed to invoke: %s\n", e.what());
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::logError("Booster: nothing to invoke\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
void Booster::renameProcess(int parentArgc, char** parentArgv,
|
|
int sourceArgc, const char** sourceArgv)
|
|
{
|
|
if (sourceArgc > 0 && parentArgc > 0)
|
|
{
|
|
// Calculate original space reserved for arguments, if not
|
|
// already calculated
|
|
if (!m_spaceAvailable)
|
|
for (int i = 0; i < parentArgc; i++)
|
|
m_spaceAvailable += strlen(parentArgv[i]) + 1;
|
|
|
|
if (m_spaceAvailable)
|
|
{
|
|
// Build a contiguous, NULL-separated block for the new arguments.
|
|
// This is how Linux puts them.
|
|
std::string newArgv;
|
|
for (int i = 0; i < sourceArgc; i++)
|
|
{
|
|
newArgv += sourceArgv[i];
|
|
newArgv += '\0';
|
|
}
|
|
|
|
const int spaceNeeded = std::min(m_spaceAvailable,
|
|
static_cast<int>(newArgv.size()));
|
|
|
|
// Reset the old space
|
|
memset(parentArgv[0], '\0', m_spaceAvailable);
|
|
|
|
if (spaceNeeded > 0)
|
|
{
|
|
// Copy the argument data. Note: if they don't fit, then
|
|
// they are just cut off.
|
|
memcpy(parentArgv[0], newArgv.c_str(), spaceNeeded);
|
|
|
|
// Ensure NULL at the end
|
|
parentArgv[0][spaceNeeded - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
// Set the process name using prctl, 'killall' and 'top' use it
|
|
if ( prctl(PR_SET_NAME, basename(sourceArgv[0])) == -1 )
|
|
Logger::logError("Booster: on set new process name: %s ", strerror(errno));
|
|
|
|
setenv("_", sourceArgv[0], true);
|
|
}
|
|
}
|
|
|
|
static bool isPrivileged(AppData *appData, const char *path)
|
|
{
|
|
/*
|
|
Returns true if privileged, false if not privileged.
|
|
The privileges file has the following format:
|
|
/full/path/to/app,<permissions_list>
|
|
where the permissions_list is a string of characters
|
|
defining different categories of permissions
|
|
eg: p = people/contacts data
|
|
example:
|
|
/usr/bin/vcardconverter,p
|
|
Currently, permission means both read+write permission.
|
|
Comment lines start with # and are ignored.
|
|
*/
|
|
|
|
std::ifstream infile(path);
|
|
if (infile) {
|
|
std::string line;
|
|
while (std::getline(infile, line)) {
|
|
if (line.find('#') == 0) {
|
|
// Comment line
|
|
continue;
|
|
}
|
|
|
|
size_t pos = line.find(',');
|
|
if (pos != std::string::npos) {
|
|
std::string filename = line.substr(0, pos);
|
|
std::string permissions = line.substr(pos+1);
|
|
|
|
// TODO: Actually do something with "permissions"
|
|
|
|
if (filename == appData->fileName()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool isPrivileged(AppData *appData)
|
|
{
|
|
/*
|
|
Return true if privileged, false if not privileged.
|
|
|
|
This function checks the standard paths to find privileges definition file.
|
|
First it will check
|
|
/usr/share/mapplauncherd/privileges
|
|
And then, any file in
|
|
/usr/share/mapplauncherd/privileges.d/
|
|
*/
|
|
static const char *BOOSTER_APP_PRIVILEGES_LIST = "/usr/share/mapplauncherd/privileges";
|
|
static const char *BOOSTER_APP_PRIVILEGES_DIR = "/usr/share/mapplauncherd/privileges.d";
|
|
if (isPrivileged(appData, BOOSTER_APP_PRIVILEGES_LIST))
|
|
return true;
|
|
|
|
DIR *privilegesDir = opendir(BOOSTER_APP_PRIVILEGES_DIR);
|
|
if (!privilegesDir)
|
|
return false;
|
|
|
|
bool privileged = false;
|
|
dirent *dir = NULL;
|
|
while ((dir = readdir(privilegesDir)) && !privileged) {
|
|
std::string privilegesFile (BOOSTER_APP_PRIVILEGES_DIR);
|
|
privilegesFile += "/";
|
|
privilegesFile += dir->d_name;
|
|
privileged = isPrivileged(appData, privilegesFile.c_str());
|
|
}
|
|
closedir(privilegesDir);
|
|
return privileged;
|
|
}
|
|
|
|
struct NotCharacter {
|
|
char c;
|
|
|
|
NotCharacter(const char &c) : c(c) {}
|
|
bool operator()(const char &c) const { return c != this->c; }
|
|
};
|
|
|
|
static bool mkdirRecursive(const int dirfd, const std::string &path) {
|
|
static const mode_t MODE = 0775;
|
|
|
|
struct stat st;
|
|
|
|
std::string relative;
|
|
std::string::const_iterator begin, next;
|
|
|
|
for (std::string::const_iterator i = path.begin(); i != path.end(); i = next) {
|
|
begin = std::find_if(i, path.end(), NotCharacter('/'));
|
|
next = std::find(begin, path.end(), '/');
|
|
relative.append(begin, next);
|
|
relative.append(1, '/');
|
|
|
|
if (fstatat(dirfd, relative.c_str(), &st, 0)) {
|
|
if (mkdirat(dirfd, relative.c_str(), MODE) && errno != EEXIST) {
|
|
return false;
|
|
}
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void setCgroup(const std::string &exePath) {
|
|
static const char *BOOSTER_CGROUP_TREE = "/sys/fs/cgroup/booster";
|
|
static const char *CGROUP_PROCS = "cgroup.procs";
|
|
static const char *PROC_PID = "0";
|
|
|
|
int fd = -1;
|
|
DIR *dir = NULL;
|
|
char *cpath = NULL;
|
|
std::string path;
|
|
|
|
dir = opendir(BOOSTER_CGROUP_TREE);
|
|
if (!dir) {
|
|
Logger::logDebug("No named booster cgroup hierarchy '%s'", BOOSTER_CGROUP_TREE);
|
|
goto early;
|
|
}
|
|
|
|
cpath = realpath(exePath.c_str(), NULL);
|
|
if (!cpath) {
|
|
Logger::logDebug("Cannot resolve exe path '%s'", exePath.c_str());
|
|
goto early;
|
|
}
|
|
|
|
path = cpath;
|
|
if (!mkdirRecursive(dirfd(dir), path)) {
|
|
Logger::logDebug("Cannot create cgroup '%s'", path.c_str());
|
|
goto early;
|
|
}
|
|
|
|
path.erase(path.begin(), std::find_if(path.begin(), path.end(), NotCharacter('/')));
|
|
path = path + '/' + CGROUP_PROCS;
|
|
fd = openat(dirfd(dir), path.c_str(), O_WRONLY);
|
|
if (fd < 0) {
|
|
Logger::logDebug("Cannot open '%s' for writing", path.c_str());
|
|
goto early;
|
|
}
|
|
|
|
if (write(fd, PROC_PID, strlen(PROC_PID)) < 0) {
|
|
Logger::logDebug("Cannot move itself to cgroup before launch");
|
|
goto early;
|
|
}
|
|
|
|
early:
|
|
if (dir) {
|
|
closedir(dir);
|
|
}
|
|
|
|
if (cpath) {
|
|
free(cpath);
|
|
}
|
|
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void Booster::setEnvironmentBeforeLaunch()
|
|
{
|
|
// Possibly restore process priority
|
|
errno = 0;
|
|
const int cur_prio = getpriority(PRIO_PROCESS, 0);
|
|
if (!errno && cur_prio < m_appData->priority())
|
|
setpriority(PRIO_PROCESS, 0, m_appData->priority());
|
|
|
|
setCgroup(m_appData->fileName());
|
|
|
|
// Currently, we only have two levels of privileges:
|
|
// privileged and non-privileged.
|
|
// Going forward, this could be improved to support
|
|
// a larger range of privileges via ACLs.
|
|
if (!isPrivileged(m_appData)) {
|
|
// The application is not privileged. Drop any user or
|
|
// group ID inherited from the booster, and instead set
|
|
// the user ID and group ID of the calling process.
|
|
|
|
if (geteuid() != m_appData->userId()) {
|
|
setuid(m_appData->userId());
|
|
}
|
|
|
|
if (getegid() != m_appData->groupId()) {
|
|
setgid(m_appData->groupId());
|
|
}
|
|
}
|
|
|
|
// Make sure that boosted application can dump core. This must be
|
|
// done after set[ug]id().
|
|
prctl(PR_SET_DUMPABLE, 1);
|
|
|
|
// Reset out-of-memory killer adjustment
|
|
if (!m_appData->disableOutOfMemAdj())
|
|
resetOomAdj();
|
|
|
|
// Duplicate I/O descriptors
|
|
for (unsigned int i = 0; i < m_appData->ioDescriptors().size(); i++)
|
|
{
|
|
if (m_appData->ioDescriptors()[i] > 0)
|
|
{
|
|
dup2(m_appData->ioDescriptors()[i], i);
|
|
close(m_appData->ioDescriptors()[i]);
|
|
}
|
|
}
|
|
|
|
// Set PWD
|
|
const char * pwd = getenv("PWD");
|
|
if (pwd) chdir(pwd);
|
|
|
|
Logger::logDebug("Booster: launching process: '%s' ", m_appData->fileName().c_str());
|
|
}
|
|
|
|
int Booster::launchProcess()
|
|
{
|
|
setEnvironmentBeforeLaunch();
|
|
|
|
// Load the application and find out the address of main()
|
|
loadMain();
|
|
|
|
// make booster specific initializations unless booster is in boot mode
|
|
if (!m_bootMode)
|
|
preinit();
|
|
|
|
#ifdef WITH_COVERAGE
|
|
__gcov_flush();
|
|
#endif
|
|
|
|
// Close syslog
|
|
closelog();
|
|
|
|
// Jump to main()
|
|
const int retVal = m_appData->entry()(m_appData->argc(), const_cast<char **>(m_appData->argv()));
|
|
|
|
#ifdef WITH_COVERAGE
|
|
__gcov_flush();
|
|
#endif
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void* Booster::loadMain()
|
|
{
|
|
// Setup flags for dlopen
|
|
|
|
int dlopenFlags = RTLD_LAZY;
|
|
|
|
if (m_appData->dlopenGlobal())
|
|
dlopenFlags |= RTLD_GLOBAL;
|
|
else
|
|
dlopenFlags |= RTLD_LOCAL;
|
|
|
|
#if (PLATFORM_ID == Linux)
|
|
if (m_appData->dlopenDeep())
|
|
dlopenFlags |= RTLD_DEEPBIND;
|
|
#endif
|
|
|
|
// Load the application as a library
|
|
void * module = dlopen(m_appData->fileName().c_str(), dlopenFlags);
|
|
|
|
if (!module)
|
|
throw std::runtime_error(std::string("Booster: Loading invoked application failed: '") +
|
|
dlerror() + "'\n");
|
|
|
|
// Find out the address for symbol "main". dlerror() is first used to clear any old error conditions,
|
|
// then dlsym() is called, and then dlerror() is checked again. This procedure is documented
|
|
// in dlsym()'s man page.
|
|
|
|
dlerror();
|
|
m_appData->setEntry(reinterpret_cast<entry_t>(dlsym(module, "main")));
|
|
|
|
const char * error_s = dlerror();
|
|
if (error_s != NULL)
|
|
throw std::runtime_error(std::string("Booster: Loading symbol 'main' failed: '") +
|
|
error_s + "'\n");
|
|
|
|
return module;
|
|
}
|
|
|
|
bool Booster::pushPriority(int nice)
|
|
{
|
|
errno = 0;
|
|
m_oldPriorityOk = true;
|
|
m_oldPriority = getpriority(PRIO_PROCESS, getpid());
|
|
|
|
if (errno)
|
|
{
|
|
m_oldPriorityOk = false;
|
|
}
|
|
else
|
|
{
|
|
return setpriority(PRIO_PROCESS, getpid(), nice) != -1;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Booster::popPriority()
|
|
{
|
|
if (m_oldPriorityOk)
|
|
{
|
|
return setpriority(PRIO_PROCESS, getpid(), m_oldPriority) != -1;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pid_t Booster::invokersPid()
|
|
{
|
|
if (m_connection->isReportAppExitStatusNeeded())
|
|
{
|
|
return m_connection->peerPid();
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Booster::setBoosterLauncherSocket(int newBoosterLauncherSocket)
|
|
{
|
|
m_boosterLauncherSocket = newBoosterLauncherSocket;
|
|
}
|
|
|
|
int Booster::boosterLauncherSocket() const
|
|
{
|
|
return m_boosterLauncherSocket;
|
|
}
|
|
|
|
Connection* Booster::connection() const
|
|
{
|
|
return m_connection;
|
|
}
|
|
|
|
void Booster::setConnection(Connection * newConnection)
|
|
{
|
|
delete m_connection;
|
|
m_connection = newConnection;
|
|
}
|
|
|
|
AppData* Booster::appData() const
|
|
{
|
|
return m_appData;
|
|
}
|
|
|
|
void Booster::resetOomAdj()
|
|
{
|
|
const char *PROC_OOM_ADJ_FILE = "/proc/self/oom_score_adj";
|
|
|
|
std::ofstream oom_adj(PROC_OOM_ADJ_FILE);
|
|
if (oom_adj) {
|
|
oom_adj << '0';
|
|
if (oom_adj.fail()) {
|
|
Logger::logError("Couldn't write to '%s'", PROC_OOM_ADJ_FILE);
|
|
}
|
|
} else {
|
|
Logger::logError("Couldn't open '%s' for writing", PROC_OOM_ADJ_FILE);
|
|
}
|
|
}
|