From 85d629103daa3fcf48445b7e924a74d17986877d Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 15 Sep 2019 11:47:21 +0200 Subject: [PATCH] Kernel: Implement shebang executables ("#!/bin/sh") This patch makes it possible to *run* text files that start with the characters "#!" followed by an interpreter. I've tested this with both the Serenity built-in shell and the Bash shell, and it works as expected. :^) --- Kernel/Process.cpp | 38 ++++++++++++++++++++++++++++++++++++++ Kernel/Process.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index ab6b87ff81a..e4708a94839 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -527,8 +527,46 @@ int Process::do_exec(String path, Vector arguments, Vector envir return 0; } +KResultOr Process::find_shebang_interpreter_for_executable(const String& executable_path) +{ + // FIXME: It's a bit sad that we'll open the executable twice (in case there's no shebang) + // Maybe we can find a way to plumb this opened FileDescription to the rest of the + // exec implementation.. + auto result = VFS::the().open(executable_path, 0, 0, current_directory()); + if (result.is_error()) + return result.error(); + auto description = result.value(); + auto metadata = description->metadata(); + + if (!metadata.may_execute(m_euid, m_gids)) + return KResult(-EACCES); + + if (metadata.size < 3) + return KResult(-ENOEXEC); + + char first_page[PAGE_SIZE]; + int nread = description->read((u8*)&first_page, sizeof(first_page)); + int interpreter_length = 0; + if (nread > 2 && first_page[0] == '#' && first_page[1] == '!') { + for (int i = 2; i < nread; ++i) { + if (first_page[i] == '\n') { + interpreter_length = i - 2; + break; + } + } + if (interpreter_length > 0) + return String(&first_page[2], interpreter_length); + } + + return KResult(-ENOEXEC); +} + int Process::exec(String path, Vector arguments, Vector environment) { + auto result = find_shebang_interpreter_for_executable(path); + if (!result.is_error()) + return exec(result.value(), { result.value(), path }, move(environment)); + // The bulk of exec() is done by do_exec(), which ensures that all locals // are cleaned up by the time we yield-teleport below. int rc = do_exec(move(path), move(arguments), move(environment)); diff --git a/Kernel/Process.h b/Kernel/Process.h index 86b2ef92924..71113c6da20 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -310,6 +310,8 @@ private: int alloc_fd(int first_candidate_fd = 0); void disown_all_shared_buffers(); + KResultOr find_shebang_interpreter_for_executable(const String& executable_path); + Thread* m_main_thread { nullptr }; RefPtr m_page_directory;