blob: 832a42c28dc28befc7572ee7f535c2464c11e7d9 [file]
/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
*
* This file is part of PRoot.
*
* Copyright (C) 2014 STMicroelectronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
#include <stdio.h> /* printf(3), */
#include <stdbool.h> /* bool, true, false, */
#include <linux/limits.h> /* ARG_MAX, PATH_MAX, */
#include <string.h> /* str*(3), basename(3), */
#include <talloc.h> /* talloc*, */
#include <stdlib.h> /* exit(3), EXIT_*, strtol(3), {g,s}etenv(3), */
#include <assert.h> /* assert(3), */
#include <sys/types.h> /* getpid(2), */
#include <unistd.h> /* getpid(2), */
#include <errno.h> /* errno(3), */
#include <execinfo.h> /* backtrace_symbols(3), */
#include <limits.h> /* INT_MAX, */
#include "cli/cli.h"
#include "cli/note.h"
#include "extension/care/extract.h"
#include "extension/extension.h"
#include "tracee/tracee.h"
#include "tracee/event.h"
#include "path/binding.h"
#include "path/canon.h"
#include "path/path.h"
#include "build.h"
/**
* Print a (@detailed) usage of PRoot.
*/
void print_usage(Tracee *tracee, const Cli *cli, bool detailed)
{
const char *current_class = "none";
const Option *options;
size_t i, j;
#define DETAIL(a) if (detailed) a
DETAIL(printf("%s %s: %s.\n\n", cli->name, cli->version, cli->subtitle));
printf("Usage:\n %s\n", cli->synopsis);
DETAIL(printf("\n"));
options = cli->options;
for (i = 0; options[i].class != NULL; i++) {
for (j = 0; ; j++) {
const Argument *argument = &(options[i].arguments[j]);
if (!argument->name || (!detailed && j != 0)) {
DETAIL(printf("\n"));
printf("\t%s\n", options[i].description);
if (detailed) {
if (options[i].detail[0] != '\0')
printf("\n%s\n\n", options[i].detail);
else
printf("\n");
}
break;
}
if (strcmp(options[i].class, current_class) != 0) {
current_class = options[i].class;
printf("\n%s:\n", current_class);
}
if (j == 0)
printf(" %s", argument->name);
else
printf(", %s", argument->name);
if (argument->separator != '\0')
printf("%c*%s*", argument->separator, argument->value);
else if (!detailed)
printf("\t");
}
}
notify_extensions(tracee, PRINT_USAGE, detailed, 0);
if (detailed)
printf("%s\n", cli->colophon);
}
/**
* Print the version of PRoot.
*/
void print_version(const Cli *cli)
{
printf("%s %s\n\n", cli->logo, cli->version);
printf("built-in accelerators: process_vm = %s, seccomp_filter = %s\n",
#if defined(HAVE_PROCESS_VM)
"yes",
#else
"no",
#endif
#if defined(HAVE_SECCOMP_FILTER)
"yes"
#else
"no"
#endif
);
}
static void print_execve_help(const Tracee *tracee, const char *argv0, int status)
{
note(tracee, ERROR, SYSTEM, "execve(\"%s\")", argv0);
/* Ubuntu kernel bug? */
if (status == -EPERM && getenv("PROOT_NO_SECCOMP") == NULL) {
note(tracee, INFO, USER,
"It seems your kernel contains this bug: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1202161\n"
"To workaround it, set the env. variable PROOT_NO_SECCOMP to 1.");
return;
}
note(tracee, INFO, USER, "possible causes:\n"
" * the program is a script but its interpreter (eg. /bin/sh) was not found;\n"
" * the program is an ELF but its interpreter (eg. ld-linux.so) was not found;\n"
" * the program is a foreign binary but qemu was not specified;\n"
" * qemu does not work correctly (if specified);\n"
" * the loader was not found or doesn't work.");
}
static void print_error_separator(const Tracee *tracee, const Argument *argument)
{
if (argument->separator == '\0')
note(tracee, ERROR, USER, "option '%s' expects no value.", argument->name);
else
note(tracee, ERROR, USER, "option '%s' and its value must be separated by '%c'.",
argument->name, argument->separator);
}
static void print_argv(const Tracee *tracee, const char *prompt, char *const argv[])
{
char string[ARG_MAX] = "";
size_t i;
if (!argv)
return;
#define APPEND(post) \
do { \
ssize_t length = sizeof(string) - (strlen(string) + strlen(post)); \
if (length <= 0) \
return; \
strncat(string, post, length); \
} while (0)
APPEND(prompt);
APPEND(" =");
for (i = 0; argv[i] != NULL; i++) {
APPEND(" ");
APPEND(argv[i]);
}
string[sizeof(string) - 1] = '\0';
#undef APPEND
note(tracee, INFO, USER, "%s", string);
}
static void print_config(Tracee *tracee, char *const argv[])
{
assert(tracee != NULL);
if (tracee->verbose <= 0)
return;
if (tracee->qemu)
note(tracee, INFO, USER, "host rootfs = %s", HOST_ROOTFS);
if (tracee->glue)
note(tracee, INFO, USER, "glue rootfs = %s", tracee->glue);
note(tracee, INFO, USER, "exe = %s", tracee->exe);
print_argv(tracee, "argv", argv);
print_argv(tracee, "qemu", tracee->qemu);
note(tracee, INFO, USER, "initial cwd = %s", tracee->fs->cwd);
note(tracee, INFO, USER, "verbose level = %d", tracee->verbose);
notify_extensions(tracee, PRINT_CONFIG, 0, 0);
}
/**
* Initialize @tracee's current working directory. This function
* returns -1 if an error occurred, otherwise 0.
*/
static int initialize_cwd(Tracee *tracee)
{
char path2[PATH_MAX];
char path[PATH_MAX];
int status;
/* Compute the base directory. */
if (tracee->fs->cwd[0] != '/') {
status = getcwd2(tracee->reconf.tracee, path);
if (status < 0) {
note(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status));
return -1;
}
}
else
strcpy(path, "/");
/* The ending "." ensures canonicalize() will report an error
* if tracee->fs->cwd does not exist or if it is not a
* directory. */
status = join_paths(3, path2, path, tracee->fs->cwd, ".");
if (status < 0) {
note(tracee, ERROR, INTERNAL, "getcwd: %s", strerror(-status));
return -1;
}
/* Initiale state for canonicalization. */
strcpy(path, "/");
status = canonicalize(tracee, path2, true, path, 0);
if (status < 0) {
note(tracee, WARNING, USER, "can't chdir(\"%s\") in the guest rootfs: %s",
path2, strerror(-status));
note(tracee, INFO, USER, "default working directory is now \"/\"");
strcpy(path, "/");
}
chop_finality(path);
/* Replace with the canonicalized working directory. */
TALLOC_FREE(tracee->fs->cwd);
tracee->fs->cwd = talloc_strdup(tracee->fs, path);
if (tracee->fs->cwd == NULL)
return -1;
talloc_set_name_const(tracee->fs->cwd, "$cwd");
/* Keep this special environment variable consistent. */
setenv("PWD", path, 1);
return 0;
}
/**
* Initialize @tracee->exe from @exe, i.e. canonicalize it from a
* guest point-of-view.
*/
static int initialize_exe(Tracee *tracee, const char *exe)
{
char path[PATH_MAX];
int status;
status = which(tracee, tracee->reconf.paths, path, exe ?: "/bin/sh");
if (status < 0)
return -1;
status = detranslate_path(tracee, path, NULL);
if (status < 0)
return -1;
tracee->exe = talloc_strdup(tracee, path);
if (tracee->exe == NULL)
return -1;
talloc_set_name_const(tracee->exe, "$exe");
return 0;
}
/**
* Configure @tracee according to the command-line arguments stored in
* @argv[]. This function returns the index in @argv[] of the command
* to launch, otherwise -1 if an error occured.
*/
static int parse_config(Tracee *tracee, size_t argc, char *const argv[])
{
option_handler_t handler = NULL;
const Option *options;
const Cli *cli = NULL;
size_t argc_offset;
size_t i, j, k;
int status;
if (get_care_cli != NULL) {
/* Check if it's an self-extracting CARE archive. */
status = extract_archive_from_file("/proc/self/exe");
if (status == 0) {
/* Yes it is, nothing more to do. */
exit_failure = 0;
return -1;
}
/* Check if it's a valid CARE tool name. */
if (strncasecmp(basename(argv[0]), "care", strlen("care")) == 0)
cli = get_care_cli(tracee->ctx);
}
/* Unknown tool name? Default to PRoot. */
if (cli == NULL)
cli = get_proot_cli(tracee->ctx);
tracee->tool_name = cli->name;
if (argc == 1) {
print_usage(tracee, cli, false);
return -1;
}
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
/* The current argument is the value of a short option. */
if (handler != NULL) {
status = handler(tracee, cli, arg);
if (status < 0)
return -1;
handler = NULL;
continue;
}
if (arg[0] != '-')
break; /* End of PRoot options. */
options = cli->options;
for (j = 0; options[j].class != NULL; j++) {
const Option *option = &options[j];
/* A given option has several aliases. */
for (k = 0; ; k++) {
const Argument *argument;
size_t length;
argument = &option->arguments[k];
/* End of aliases for this option. */
if (!argument->name)
break;
length = strlen(argument->name);
if (strncmp(arg, argument->name, length) != 0)
continue;
/* Avoid ambiguities. */
if (strlen(arg) > length
&& arg[length] != argument->separator) {
print_error_separator(tracee, argument);
return -1;
}
/* No option value. */
if (!argument->value) {
status = option->handler(tracee, cli, NULL);
if (status < 0)
return -1;
goto known_option;
}
/* Value coalesced with to its option. */
if (argument->separator == arg[length]) {
assert(strlen(arg) >= length);
status = option->handler(tracee, cli, &arg[length + 1]);
if (status < 0)
return -1;
goto known_option;
}
/* Avoid ambiguities. */
if (argument->separator != ' ') {
print_error_separator(tracee, argument);
return -1;
}
/* Short option with a separated value. */
handler = option->handler;
goto known_option;
}
}
note(tracee, ERROR, USER, "unknown option '%s'.", arg);
return -1;
known_option:
if (handler != NULL && i == argc - 1) {
note(tracee, ERROR, USER, "missing value for option '%s'.", arg);
return -1;
}
}
argc_offset = i;
#define HOOK_CONFIG(callback) \
do { \
if (cli->callback != NULL) { \
status = cli->callback(tracee, cli, argc, argv, i); \
if (status < 0) \
return -1; \
i = status; \
} \
} while (0)
HOOK_CONFIG(pre_initialize_bindings);
/* The guest rootfs is now known: bindings specified by the
* user (tracee->bindings.user) can be canonicalized. */
status = initialize_bindings(tracee);
if (status < 0)
return -1;
HOOK_CONFIG(post_initialize_bindings);
HOOK_CONFIG(pre_initialize_cwd);
/* Bindings are now installed (tracee->bindings.guest &
* tracee->bindings.host): the current working directory can
* be canonicalized. */
status = initialize_cwd(tracee);
if (status < 0)
return -1;
HOOK_CONFIG(post_initialize_cwd);
HOOK_CONFIG(pre_initialize_exe);
/* Bindings are now installed and the current working
* directory is canonicalized: resolve path to @tracee->exe
* and configure @tracee->cmdline. */
status = initialize_exe(tracee, argv[argc_offset]);
if (status < 0)
return -1;
HOOK_CONFIG(post_initialize_exe);
#undef HOOK_CONFIG
print_config(tracee, &argv[argc_offset]);
return argc_offset;
}
bool exit_failure = true;
int main(int argc, char *const argv[])
{
Tracee *tracee;
int status;
/* Configure the memory allocator. */
talloc_enable_leak_report();
#if defined(TALLOC_VERSION_MAJOR) && TALLOC_VERSION_MAJOR >= 2
talloc_set_log_stderr();
#endif
/* Pre-create the first tracee (pid == 0). */
tracee = get_tracee(NULL, 0, true);
if (tracee == NULL)
goto error;
tracee->pid = getpid();
/* Pre-configure the first tracee. */
status = parse_config(tracee, argc, argv);
if (status < 0)
goto error;
/* Start the first tracee. */
status = launch_process(tracee, &argv[status]);
if (status < 0) {
print_execve_help(tracee, tracee->exe, status);
goto error;
}
/* Start tracing the first tracee and all its children. */
exit(event_loop());
error:
TALLOC_FREE(tracee);
if (exit_failure) {
fprintf(stderr, "fatal error: see `%s --help`.\n", basename(argv[0]));
exit(EXIT_FAILURE);
}
else
exit(EXIT_SUCCESS);
}
/**
* Convert @value into an integer, then put the result into
* *@variable. This function prints a warning and returns -1 if a
* conversion error occured, otherwise it returns 0.
*/
int parse_integer_option(const Tracee *tracee, int *variable, const char *value, const char *option)
{
char *end_ptr = NULL;
errno = 0;
*variable = strtol(value, &end_ptr, 10);
if (errno != 0 || end_ptr == value) {
note(tracee, ERROR, USER, "option `%s` expects an integer value.", option);
return -1;
}
return 0;
}
/**
* Expand the environment variable in front of @string, if any. For
* example, this function can expand "$HOME" or "$HOME/.ICEauthority".
*/
const char *expand_front_variable(TALLOC_CTX *context, const char *string)
{
const char *suffix;
char *expanded;
ptrdiff_t size;
if (string[0] != '$')
return string;
suffix = strchr(string, '/');
if (suffix == NULL)
return (getenv(&string[1]) ?: string);
size = suffix - string;
if (size <= 1)
return string;
expanded = talloc_strndup(context, &string[1], size - 1);
if (expanded == NULL)
return string;
expanded = getenv(expanded);
if (expanded == NULL)
return string;
expanded = talloc_asprintf(context, "%s%s", expanded, suffix);
if (expanded == NULL)
return string;
return expanded;
}
/* Here follows the support for GCC function instrumentation. Build
* with CFLAGS='-finstrument-functions -O0 -g' and LDFLAGS='-rdynamic'
* to enable this mechanism. */
static int indent_level = 0;
void __cyg_profile_func_enter(void *this_function, void *call_site) DONT_INSTRUMENT;
void __cyg_profile_func_enter(void *this_function, void *call_site)
{
void *const pointers[] = { this_function, call_site };
char **symbols = NULL;
symbols = backtrace_symbols(pointers, 2);
if (symbols == NULL)
goto end;
fprintf(stderr, "%*s from %s\n", (int) strlen(symbols[0]) + indent_level, symbols[0], symbols[1]);
end:
if (symbols != NULL)
free(symbols);
if (indent_level < INT_MAX)
indent_level++;
}
void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED) DONT_INSTRUMENT;
void __cyg_profile_func_exit(void *this_function UNUSED, void *call_site UNUSED)
{
if (indent_level > 0)
indent_level--;
}