diff --git a/repos/libports/src/lib/libc/execve.cc b/repos/libports/src/lib/libc/execve.cc
index 5a89d6141..044ab1044 100644
--- a/repos/libports/src/lib/libc/execve.cc
+++ b/repos/libports/src/lib/libc/execve.cc
@@ -14,11 +14,13 @@
/* Genode includes */
#include
#include
+#include
/* libc includes */
#include
#include
#include
+#include
#include
/* libc-internal includes */
@@ -31,7 +33,171 @@ using namespace Genode;
typedef int (*main_fn_ptr) (int, char **, char **);
-namespace Libc { struct String_array; }
+namespace Libc {
+ struct Interpreter;
+ struct String_array;
+}
+
+
+struct Libc::Interpreter
+{
+ Attached_rom_dataspace _rom;
+
+ char **args = { nullptr };
+
+ /**
+ * Return true if file content starts with the specified C string
+ */
+ bool _content_starts_with(char const *prefix) const
+ {
+ size_t const prefix_len = ::strlen(prefix);
+
+ if (prefix_len > _rom.size())
+ return false;
+
+ return strncmp(prefix, _rom.local_addr(), prefix_len) == 0;
+ }
+
+ bool script() const { return _content_starts_with("#!"); }
+ bool elf_executable() const { return _content_starts_with("\x7f" "ELF"); }
+
+ struct Arg
+ {
+ /* pointer to first character */
+ char const * const ptr;
+
+ /* number of characters */
+ size_t const length;
+ };
+
+ struct Constrained_ptr
+ {
+ char const *ptr;
+
+ size_t remaining_bytes;
+
+ /**
+ * Skip characters for which 'condition' is true
+ */
+ template
+ void _skip(COND const &condition)
+ {
+ while (remaining_bytes > 0 && condition(*ptr)) {
+ ptr++;
+ remaining_bytes--;
+ }
+ }
+
+ void skip_non_whitespace() { _skip([] (char c) { return !is_whitespace(c); }); };
+ void skip_whitespace() { _skip([] (char c) { return is_whitespace(c); }); };
+
+ void skip_shebang()
+ {
+ _skip([] (char c) { return c == '#'; });
+ _skip([] (char c) { return c == '!'; });
+ }
+
+ bool valid() const { return ptr && *ptr && *ptr != '\n' && remaining_bytes; }
+ };
+
+ template
+ void _for_each_arg(FN const &fn) const
+ {
+ if (!script())
+ return;
+
+ Constrained_ptr ptr { _rom.local_addr(), _rom.size() };
+
+ ptr.skip_shebang();
+
+ /* skip whitespace between shebang and interpreter */
+ ptr.skip_whitespace();
+
+ /* skip path to interpreter */
+ ptr.skip_non_whitespace();
+
+ while (ptr.valid()) {
+
+ /* skip whitespace before argument */
+ ptr.skip_whitespace();
+
+ /* find end of argument */
+ char const * const arg_ptr = ptr.ptr;
+ unsigned const orig_remaining_bytes = ptr.remaining_bytes;
+ ptr.skip_non_whitespace();
+
+ size_t const length = orig_remaining_bytes - ptr.remaining_bytes;
+
+ if (length)
+ fn(Arg { arg_ptr, length });
+ }
+ }
+
+ typedef String Path;
+
+ Path path() const
+ {
+ if (!script())
+ return Path();
+
+ Constrained_ptr ptr { _rom.local_addr(), _rom.size() };
+
+ ptr.skip_shebang();
+ ptr.skip_whitespace();
+
+ /* find end of interpreter path */
+ char const * const path_ptr = ptr.ptr;
+ unsigned const orig_remaining_bytes = ptr.remaining_bytes;
+ ptr.skip_non_whitespace();
+
+ size_t const length = orig_remaining_bytes - ptr.remaining_bytes;
+
+ return Path(Cstring(path_ptr, length));
+ }
+
+ unsigned _count_args() const
+ {
+ unsigned count = 0;
+
+ _for_each_arg([&] (Arg) { count++; });
+
+ return count;
+ }
+
+ unsigned const num_args;
+
+ Interpreter(Genode::Env &env, char const * const filename)
+ :
+ _rom(env, filename), num_args(_count_args() + 2 /* argv0 + filename */)
+ {
+ if (script()) {
+ args = (char **)malloc(sizeof(char *)*num_args);
+
+ unsigned i = 0;
+
+ /* argv0 */
+ args[i++] = strdup(path().string());
+
+ _for_each_arg([&] (Arg arg) {
+ args[i++] = strndup(arg.ptr, arg.length); });
+
+ /* supply script file name as last argument */
+ args[i++] = strdup(filename);
+
+
+ }
+ }
+
+ ~Interpreter()
+ {
+ if (script()) {
+ for (unsigned i = 0; i < num_args; i++)
+ free(args[i]);
+
+ free(args);
+ }
+ }
+};
/**
@@ -85,30 +251,48 @@ struct Libc::String_array : Noncopyable
Constructible _buffer { };
- String_array(Allocator &alloc, char const * const * const src_array)
+ String_array(Allocator &alloc,
+ char const * const * const src_array_1,
+ char const * const * const src_array_2 = nullptr)
:
- _alloc(alloc), count(_num_entries(src_array))
+ _alloc(alloc),
+
+ /* if 'src_array_2' is supplied, we skip its first element (argv0) */
+ count(_num_entries(src_array_1) + _num_entries(src_array_2) -
+ (src_array_2 ? 1 : 0))
{
/* marshal array strings to buffer */
size_t size = 1024;
for (;;) {
- _buffer.construct(alloc, size);
+ _buffer.construct(_alloc, size);
- unsigned i = 0;
- for (i = 0; i < count; i++) {
- array[i] = _buffer->pos_ptr();
- if (!_buffer->try_append(src_array[i]))
- break;
- }
+ unsigned dst_i = 0; /* index into destination array */
- bool const done = (i == count);
+ auto try_append_entries = [&] (char const * const * const src_array,
+ unsigned skip = 0)
+ {
+ if (!src_array)
+ return;
+
+ size_t const n = _num_entries(src_array);
+ for (unsigned i = skip; i < n; i++) {
+ array[dst_i++] = _buffer->pos_ptr();
+ if (!_buffer->try_append(src_array[i]))
+ break;
+ }
+ };
+
+ try_append_entries(src_array_1);
+ try_append_entries(src_array_2, 1); /* skip old argv0 */
+
+ bool const done = (dst_i == count);
if (done) {
- array[i] = nullptr;
+ array[dst_i] = nullptr;
break;
}
- warning("env buffer ", size, " too small");
+ warning("string-array buffer ", size, " exceeded");
size += 1024;
}
}
@@ -162,20 +346,59 @@ extern "C" int execve(char const *filename,
return Libc::Errno(EACCES);
}
- Libc::Absolute_path resolved_path;
- try {
- Libc::resolve_symlinks(filename, resolved_path); }
- catch (Libc::Symlink_resolve_error) {
- warning("execve: ", filename, " does not exist");
- return Libc::Errno(ENOENT); }
-
/* capture environment variables and args to libc-internal heap */
Libc::String_array *saved_env_vars =
new (*_alloc_ptr) Libc::String_array(*_alloc_ptr, envp);
+ /*
+ * Resolve path of the executable and handle shebang arguments
+ */
+ Libc::Absolute_path resolved_path;
+
+ Libc::Interpreter::Path path(filename);
+
Libc::String_array *saved_args =
new (*_alloc_ptr) Libc::String_array(*_alloc_ptr, argv);
+ enum { MAX_INTERPRETER_NESTING_LEVELS = 4 };
+
+ for (unsigned i = 0; i < MAX_INTERPRETER_NESTING_LEVELS; i++) {
+
+ try {
+ Libc::resolve_symlinks(path.string(), resolved_path); }
+ catch (Libc::Symlink_resolve_error) {
+ warning("execve: executable binary does not exist");
+ return Libc::Errno(ENOENT);
+ }
+
+ Constructible interpreter { };
+
+ try {
+ interpreter.construct(*_env_ptr, resolved_path.string()); }
+ catch (...) {
+ warning("execve: executable binary inaccessible as ROM module");
+ return Libc::Errno(ENOENT);
+ }
+
+ if (interpreter->elf_executable())
+ break;
+
+ if (!interpreter->script()) {
+ warning("invalid executable binary format: ", resolved_path.string());
+ return Libc::Errno(ENOEXEC);
+ }
+
+ path = interpreter->path();
+
+ /* concatenate shebang args with command-line args */
+ Libc::String_array * const orig_saved_args = saved_args;
+
+ saved_args = new (*_alloc_ptr)
+ Libc::String_array(*_alloc_ptr, interpreter->args, argv);
+
+ destroy(*_alloc_ptr, orig_saved_args);
+ }
+
try {
_main_ptr = Dynamic_linker::respawn(*_env_ptr, resolved_path.string(), "main");
}