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.

830 lines
24 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.
**
****************************************************************************/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <bits/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <limits.h>
#include <getopt.h>
#include <fcntl.h>
#include "report.h"
#include "protocol.h"
#include "invokelib.h"
#include "search.h"
#ifdef HAVE_CREDS
#include <sys/creds.h>
#endif
// Delay before exit.
static const unsigned int EXIT_DELAY = 0;
static const unsigned int MIN_EXIT_DELAY = 1;
static const unsigned int MAX_EXIT_DELAY = 86400;
// Delay before a new booster is started. This will
// be sent to the launcher daemon.
static const unsigned int RESPAWN_DELAY = 3;
static const unsigned int MIN_RESPAWN_DELAY = 0;
static const unsigned int MAX_RESPAWN_DELAY = 10;
static const unsigned char EXIT_STATUS_APPLICATION_CONNECTION_LOST = 0xfa;
static const unsigned char EXIT_STATUS_APPLICATION_NOT_FOUND = 0x7f;
// Enumeration of possible application types:
// M_APP : MeeGo Touch application
// QT_APP : Qt/generic application
// QDECL_APP : QDeclarative (QML) application
// EXEC_APP : Executable generic application (can be used with splash screen)
//
enum APP_TYPE { M_APP, QT_APP, QDECL_APP, EXEC_APP, UNKNOWN_APP };
// Environment
extern char ** environ;
// pid of the invoked process
static pid_t g_invoked_pid = -1;
static void sigs_restore(void);
static void sigs_init(void);
//! Pipe used to safely catch Unix signals
static int g_signal_pipe[2];
// Forwards Unix signals from invoker to the invoked process
static void sig_forwarder(int sig)
{
if (g_invoked_pid >= 0)
{
if (kill(g_invoked_pid, sig) != 0)
{
report(report_error, "Can't send signal to application: %s \n", strerror(errno));
}
// Restore signal handlers
sigs_restore();
// Write signal number to the self-pipe
char signal_id = (char) sig;
write(g_signal_pipe[1], &signal_id, 1);
// Send the signal to itself using the default handler
raise(sig);
}
}
// Sets signal actions for Unix signals
static void sigs_set(struct sigaction *sig)
{
sigaction(SIGABRT, sig, NULL);
sigaction(SIGALRM, sig, NULL);
sigaction(SIGBUS, sig, NULL);
sigaction(SIGCHLD, sig, NULL);
sigaction(SIGCONT, sig, NULL);
sigaction(SIGHUP, sig, NULL);
sigaction(SIGINT, sig, NULL);
sigaction(SIGIO, sig, NULL);
sigaction(SIGIOT, sig, NULL);
sigaction(SIGPIPE, sig, NULL);
sigaction(SIGPROF, sig, NULL);
sigaction(SIGPWR, sig, NULL);
sigaction(SIGQUIT, sig, NULL);
sigaction(SIGSEGV, sig, NULL);
sigaction(SIGSYS, sig, NULL);
sigaction(SIGTERM, sig, NULL);
sigaction(SIGTRAP, sig, NULL);
sigaction(SIGTSTP, sig, NULL);
sigaction(SIGTTIN, sig, NULL);
sigaction(SIGTTOU, sig, NULL);
sigaction(SIGUSR1, sig, NULL);
sigaction(SIGUSR2, sig, NULL);
sigaction(SIGVTALRM, sig, NULL);
sigaction(SIGWINCH, sig, NULL);
sigaction(SIGXCPU, sig, NULL);
sigaction(SIGXFSZ, sig, NULL);
}
// Sets up the signal forwarder
static void sigs_init(void)
{
struct sigaction sig;
memset(&sig, 0, sizeof(sig));
sig.sa_flags = SA_RESTART;
sig.sa_handler = sig_forwarder;
sigs_set(&sig);
}
// Sets up the default signal handler
static void sigs_restore(void)
{
struct sigaction sig;
memset(&sig, 0, sizeof(sig));
sig.sa_flags = SA_RESTART;
sig.sa_handler = SIG_DFL;
sigs_set(&sig);
}
// Shows a list of credentials that the client has
static void show_credentials(void)
{
#ifdef HAVE_CREDS
creds_t creds;
creds_value_t value;
creds_type_t type;
int i;
creds = creds_gettask(0);
for (i = 0; (type = creds_list(creds, i, &value)) != CREDS_BAD; ++i) {
char buf[200];
(void)creds_creds2str(type, value, buf, sizeof(buf));
buf[sizeof(buf)-1] = 0;
printf("\t%s\n", buf);
}
creds_free(creds);
#else
printf("Security credential information isn't available.\n");
#endif
exit(0);
}
// Receive ACK
static bool invoke_recv_ack(int fd)
{
uint32_t action;
invoke_recv_msg(fd, &action);
if (action == INVOKER_MSG_BAD_CREDS)
{
die(1, "Security credential check failed.\n");
}
else if (action != INVOKER_MSG_ACK)
{
die(1, "Received wrong ack (%08x)\n", action);
}
return true;
}
// Inits a socket connection for the given application type
static int invoker_init(enum APP_TYPE app_type)
{
int fd;
struct sockaddr_un sun;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
{
warning("Failed to open invoker socket.\n");
return -1;
}
sun.sun_family = AF_UNIX; //AF_FILE;
const int maxSize = sizeof(sun.sun_path) - 1;
if(app_type == M_APP)
{
strncpy(sun.sun_path, INVOKER_M_SOCK, maxSize);
}
else if (app_type == QT_APP)
{
strncpy(sun.sun_path, INVOKER_QT_SOCK, maxSize);
}
else if (app_type == QDECL_APP)
{
strncpy(sun.sun_path, INVOKER_QDECL_SOCK, maxSize);
}
else if (app_type == EXEC_APP)
{
strncpy(sun.sun_path, INVOKER_EXEC_SOCK, maxSize);
}
else
{
die(1, "Unknown type of application: %d\n", app_type);
}
sun.sun_path[maxSize] = '\0';
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0)
{
warning("Failed to initiate connect on the socket.\n");
return -1;
}
return fd;
}
// Receives pid of the invoked process.
// Invoker doesn't know it, because the launcher daemon
// is the one who forks.
static uint32_t invoker_recv_pid(int fd)
{
uint32_t action, pid;
// Receive action.
invoke_recv_msg(fd, &action);
if (action != INVOKER_MSG_PID)
die(1, "Received a bad pid (%08x)\n", action);
// Receive pid.
invoke_recv_msg(fd, &pid);
return pid;
}
// Receives exit status of the invoked process
static uint32_t invoker_recv_exit(int fd)
{
uint32_t action, status;
// Receive action.
invoke_recv_msg(fd, &action);
if (action != INVOKER_MSG_EXIT)
{
// Boosted application process was killed somehow.
// Let's give applauncherd process some time to cope
// with this situation.
sleep(2);
// If nothing happend, return
return EXIT_STATUS_APPLICATION_CONNECTION_LOST;
}
// Receive exit status.
invoke_recv_msg(fd, &status);
return status;
}
// Sends magic number / protocol version
static void invoker_send_magic(int fd, uint32_t options)
{
// Send magic.
invoke_send_msg(fd, INVOKER_MSG_MAGIC | INVOKER_MSG_MAGIC_VERSION | options);
}
// Sends the process name to be invoked.
static void invoker_send_name(int fd, char *name)
{
invoke_send_msg(fd, INVOKER_MSG_NAME);
invoke_send_str(fd, name);
}
static void invoker_send_splash_file(int fd, char *filename)
{
invoke_send_msg(fd, INVOKER_MSG_SPLASH);
invoke_send_str(fd, filename);
}
static void invoker_send_landscape_splash_file(int fd, char *filename)
{
invoke_send_msg(fd, INVOKER_MSG_LANDSCAPE_SPLASH);
invoke_send_str(fd, filename);
}
static void invoker_send_exec(int fd, char *exec)
{
invoke_send_msg(fd, INVOKER_MSG_EXEC);
invoke_send_str(fd, exec);
}
static void invoker_send_args(int fd, int argc, char **argv)
{
int i;
invoke_send_msg(fd, INVOKER_MSG_ARGS);
invoke_send_msg(fd, argc);
for (i = 0; i < argc; i++)
{
debug("param %d %s \n", i, argv[i]);
invoke_send_str(fd, argv[i]);
}
}
static void invoker_send_prio(int fd, int prio)
{
invoke_send_msg(fd, INVOKER_MSG_PRIO);
invoke_send_msg(fd, prio);
}
// Sends booster respawn delay
static void invoker_send_delay(int fd, int delay)
{
invoke_send_msg(fd, INVOKER_MSG_DELAY);
invoke_send_msg(fd, delay);
}
// Sends UID and GID
static void invoker_send_ids(int fd, int uid, int gid)
{
invoke_send_msg(fd, INVOKER_MSG_IDS);
invoke_send_msg(fd, uid);
invoke_send_msg(fd, gid);
}
// Sends the environment variables
static void invoker_send_env(int fd)
{
int i, n_vars;
// Count environment variables.
for (n_vars = 0; environ[n_vars] != NULL; n_vars++) ;
invoke_send_msg(fd, INVOKER_MSG_ENV);
invoke_send_msg(fd, n_vars);
for (i = 0; i < n_vars; i++)
{
invoke_send_str(fd, environ[i]);
}
return;
}
// Sends I/O descriptors
static void invoker_send_io(int fd)
{
struct msghdr msg;
struct cmsghdr *cmsg = NULL;
int io[3] = { 0, 1, 2 };
char buf[CMSG_SPACE(sizeof(io))];
struct iovec iov;
int dummy;
memset(&msg, 0, sizeof(struct msghdr));
iov.iov_base = &dummy;
iov.iov_len = 1;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(io));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), io, sizeof(io));
msg.msg_controllen = cmsg->cmsg_len;
invoke_send_msg(fd, INVOKER_MSG_IO);
if (sendmsg(fd, &msg, 0) < 0)
{
warning("sendmsg failed in invoker_send_io: %s \n", strerror(errno));
}
return;
}
// Sends the END message
static void invoker_send_end(int fd)
{
invoke_send_msg(fd, INVOKER_MSG_END);
invoke_recv_ack(fd);
}
// Prints the usage and exits with given status
static void usage(int status)
{
printf("\nUsage: %s [options] [--type=TYPE] [file] [args]\n\n"
"Launch m, qt, or qdeclarative application compiled as a shared library (-shared) or\n"
"a position independent executable (-pie) through %s.\n\n"
"TYPE chooses the type of booster used. Qt-booster may be used to\n"
"launch anything. Possible values for TYPE:\n"
" m Launch a MeeGo Touch application.\n"
" q (or qt) Launch a Qt application.\n"
" d Launch a Qt Declarative (QML) application.\n"
" e Launch any application, even if it's not a library.\n"
" Can be used if only splash screen is wanted.\n\n"
"Options:\n"
" -c, --creds Print Aegis security credentials (if enabled).\n"
" -d, --delay SECS After invoking sleep for SECS seconds\n"
" (default %d).\n"
" -r, --respawn SECS After invoking respawn new booster after SECS seconds\n"
" (default %d, max %d).\n"
" -w, --wait-term Wait for launched process to terminate (default).\n"
" -n, --no-wait Do not wait for launched process to terminate.\n"
" -G, --global-syms Places symbols in the application binary and its\n"
" libraries to the global scope.\n"
" See RTLD_GLOBAL in the dlopen manual page.\n"
" -s, --single-instance Launch the application as a single instance.\n"
" The existing application window will be activated\n"
" if already launched.\n"
" -S, --splash FILE Show splash screen from the FILE.\n"
" -L, --splash-landscape LANDSCAPE-FILE\n"
" Show splash screen from the LANDSCAPE-FILE\n"
" in case the device is in landscape orientation.\n"
" -o, --daemon-mode Notify invoker that launched process is daemon.\n"
" -h, --help Print this help.\n\n"
"Example: %s --type=m /usr/bin/helloworld\n\n",
PROG_NAME_INVOKER, PROG_NAME_LAUNCHER, EXIT_DELAY, RESPAWN_DELAY, MAX_RESPAWN_DELAY, PROG_NAME_INVOKER);
exit(status);
}
// Return delay as integer
static unsigned int get_delay(char *delay_arg, char *param_name,
unsigned int min_value, unsigned int max_value)
{
unsigned int delay = EXIT_DELAY;
if (delay_arg)
{
errno = 0; // To distinguish success/failure after call
delay = strtoul(delay_arg, NULL, 10);
// Check for various possible errors
if ((errno == ERANGE && delay == ULONG_MAX)
|| delay < min_value
|| delay > max_value)
{
report(report_error, "Wrong value of %s parameter: %s\n", param_name, delay_arg);
usage(1);
}
}
return delay;
}
static int wait_for_launched_process_to_exit(int socket_fd, bool wait_term)
{
int status = 0;
// Wait for launched process to exit
if (wait_term)
{
g_invoked_pid = invoker_recv_pid(socket_fd);
debug("Booster's pid is %d \n ", g_invoked_pid);
// Forward UNIX signals to the invoked process
sigs_init();
while(1)
{
// Setup things for select()
fd_set readfds;
int ndfs = 0;
FD_ZERO(&readfds);
FD_SET(socket_fd, &readfds);
ndfs = (socket_fd > ndfs) ? socket_fd : ndfs;
FD_SET(g_signal_pipe[0], &readfds);
ndfs = (socket_fd > ndfs) ? socket_fd : ndfs;
// Wait for something appearing in the pipes.
if (select(ndfs + 1, &readfds, NULL, NULL, NULL) > 0)
{
// Check if an exit status from the invoked application
if (FD_ISSET(socket_fd, &readfds))
{
// Check if we've got application exit status or if the process
// on the other side of socket just died / was killed.
// We check from /proc whether the launched program is still
// running.
char filename[50];
snprintf(filename, sizeof(filename), "/proc/%d/cmdline", g_invoked_pid);
// Open filename for reading only
int fd = open(filename, O_RDONLY);
if (fd != -1)
{
// Application is still running, so applauncherd must be dead,
// because the blocking read on the socket returned.
close(fd);
// Send a signal to kill the application too and exit.
// We must do this, because the invoker<->application
// mapping is lost. Sleep for some time to give
// the new applauncherd some time to load its boosters and
// the restart of g_invoked_pid succeeds.
sleep(10);
kill(g_invoked_pid, SIGKILL);
raise(SIGKILL);
}
status = invoker_recv_exit(socket_fd);
break;
}
// Check if we got a UNIX signal.
else if (FD_ISSET(g_signal_pipe[0], &readfds))
{
// Clean up the pipe
char signal_id;
read(g_signal_pipe[0], &signal_id, sizeof(signal_id));
// Set signals forwarding to the invoked process again
// (they were reset by the signal forwarder).
sigs_init();
}
}
}
// Restore default signal handlers
sigs_restore();
}
return status;
}
// "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,
char *splash_file, char *landscape_splash_file)
{
// Get process priority
errno = 0;
int prog_prio = getpriority(PRIO_PROCESS, 0);
if (errno && prog_prio < 0)
{
prog_prio = 0;
}
// Connection with launcher process is established,
// send the data.
invoker_send_magic(socket_fd, magic_options);
invoker_send_name(socket_fd, prog_argv[0]);
invoker_send_exec(socket_fd, prog_name);
invoker_send_args(socket_fd, prog_argc, prog_argv);
invoker_send_prio(socket_fd, prog_prio);
invoker_send_delay(socket_fd, respawn_delay);
invoker_send_ids(socket_fd, getuid(), getgid());
if (( magic_options & INVOKER_MSG_MAGIC_OPTION_SPLASH_SCREEN ) != 0)
invoker_send_splash_file(socket_fd, splash_file);
if (( magic_options & INVOKER_MSG_MAGIC_OPTION_LANDSCAPE_SPLASH_SCREEN ) != 0)
invoker_send_landscape_splash_file(socket_fd, landscape_splash_file);
invoker_send_io(socket_fd);
invoker_send_env(socket_fd);
invoker_send_end(socket_fd);
if (prog_name)
{
free(prog_name);
}
int exit_status = wait_for_launched_process_to_exit(socket_fd, wait_term);
return exit_status;
}
// Invokes the given application
static int invoke(int prog_argc, char **prog_argv, char *prog_name,
enum APP_TYPE app_type, uint32_t magic_options, bool wait_term, unsigned int respawn_delay,
char *splash_file, char *landscape_splash_file)
{
int status = 0;
if (prog_name && prog_argv)
{
// If invoker cannot find the socket to connect to,
// exit with an error message.
int fd = invoker_init(app_type);
if (fd == -1)
{
report(report_error, "Cannot find booster socket.\n");
exit(EXIT_FAILURE);
}
// "normal" invoke through a socket connetion
else
{
status = invoke_remote(fd, prog_argc, prog_argv, prog_name,
magic_options, wait_term, respawn_delay,
splash_file, landscape_splash_file);
close(fd);
}
}
return status;
}
int main(int argc, char *argv[])
{
enum APP_TYPE app_type = UNKNOWN_APP;
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;
char *splash_file = NULL;
char *landscape_splash_file = NULL;
struct stat file_stat;
// wait-term parameter by default
magic_options |= INVOKER_MSG_MAGIC_OPTION_WAIT;
// Called with a different name (old way of using invoker) ?
if (!strstr(argv[0], PROG_NAME_INVOKER) )
{
die(1,
"Incorrect use of invoker, don't use symlinks. "
"Run invoker explicitly from e.g. a D-Bus service file instead.\n");
}
// Stops parsing args as soon as a non-option argument is encountered
putenv("POSIXLY_CORRECT=1");
// Options recognized
struct option longopts[] = {
{"help", no_argument, NULL, 'h'},
{"creds", no_argument, NULL, 'c'},
{"wait-term", no_argument, NULL, 'w'},
{"no-wait", no_argument, NULL, 'n'},
{"global-syms", no_argument, NULL, 'G'},
{"deep-syms", no_argument, NULL, 'D'},
{"single-instance", no_argument, NULL, 's'},
{"daemon-mode", no_argument, NULL, 'o'},
{"type", required_argument, NULL, 't'},
{"delay", required_argument, NULL, 'd'},
{"respawn", required_argument, NULL, 'r'},
{"splash", required_argument, NULL, 'S'},
{"splash-landscape", required_argument, NULL, 'L'},
{0, 0, 0, 0}
};
// Parse options
// TODO: Move to a function
int opt;
while ((opt = getopt_long(argc, argv, "hcwnGDsod:t:r:S:L:", longopts, NULL)) != -1)
{
switch(opt)
{
case 'h':
usage(0);
break;
case 'c':
show_credentials();
break;
case 'w':
// nothing to do, it's by default now
break;
case 'o':
magic_options |= INVOKER_MSG_MAGIC_OPTION_OOM_ADJ_DISABLE;
break;
case 'n':
wait_term = false;
magic_options &= (~INVOKER_MSG_MAGIC_OPTION_WAIT);
break;
case 'G':
magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_GLOBAL;
break;
case 'D':
magic_options |= INVOKER_MSG_MAGIC_OPTION_DLOPEN_DEEP;
break;
case 't':
if (strcmp(optarg, "m") == 0)
app_type = M_APP;
else if (strcmp(optarg, "q") == 0 || strcmp(optarg, "qt") == 0)
app_type = QT_APP;
else if (strcmp(optarg, "d") == 0)
app_type = QDECL_APP;
else if (strcmp(optarg, "e") == 0)
app_type = EXEC_APP;
else
{
report(report_error, "Unknown application type: %s \n", optarg);
usage(1);
}
break;
case 'd':
delay = get_delay(optarg, "delay", MIN_EXIT_DELAY, MAX_EXIT_DELAY);
break;
case 'r':
respawn_delay = get_delay(optarg, "respawn delay",
MIN_RESPAWN_DELAY, MAX_RESPAWN_DELAY);
break;
case 's':
magic_options |= INVOKER_MSG_MAGIC_OPTION_SINGLE_INSTANCE;
break;
case 'S':
magic_options |= INVOKER_MSG_MAGIC_OPTION_SPLASH_SCREEN;
splash_file = optarg;
break;
case 'L':
magic_options |= INVOKER_MSG_MAGIC_OPTION_LANDSCAPE_SPLASH_SCREEN;
landscape_splash_file = optarg;
break;
case '?':
usage(1);
}
}
// Option processing stops as soon as application name is encountered
if (optind < argc)
{
prog_name = search_program(argv[optind]);
if (!prog_name)
{
report(report_error, "Can't find application to invoke.\n");
usage(0);
}
prog_argc = argc - optind;
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);
}
// Check if application exists
if (stat(prog_name, &file_stat))
{
report(report_error, "%s: not found\n", prog_name);
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);
return EXIT_STATUS_APPLICATION_NOT_FOUND;
}
// Check if application type is unknown
if (app_type == UNKNOWN_APP)
{
report(report_error, "Application's type is unknown.\n");
usage(1);
}
if (pipe(g_signal_pipe) == -1)
{
report(report_error, "Creating a pipe for Unix signals failed!\n");
exit(EXIT_FAILURE);
}
// 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, splash_file, landscape_splash_file);
// Sleep for delay before exiting
if (delay)
{
// DBUS cannot cope some times if the invoker exits too early.
debug("Delaying exit for %d seconds..\n", delay);
sleep(delay);
}
return ret_val;
}