Repository: SerenityOS/serenity
File: Userland/Applications/Run/RunWindow.cpp (C++)

Code before:
    Window::event(event);
}

void RunWindow::do_run()
{
    auto run_input = m_path_combo_box->text().trim_whitespace();

    hide();

    if (run_via_launch(run_input) || run_as_command(run_input)) {
        close();
        return;
    }

    GUI::MessageBox::show_error(this, "Failed to run. Please check your command, path, or address, and try again."sv);

    show();
}

bool RunWindow::run_as_command(ByteString const& run_input)
{
    // TODO: Query and use the user's preferred shell.
    auto maybe_child_pid = Core::Process::spawn("/bin/Shell"sv, Array { "-c", run_input.characters() }, {}, Core::Process::KeepAsChild::Yes);
    if (maybe_child_pid.is_error())
        return false;

    pid_t child_pid = maybe_child_pid.release_value();

    // The child shell was able to start. Let's save it to the history immediately so users can see it as the first entry the next time they run this program.
    prepend_history(run_input);
    // FIXME: Handle failure to save history somehow.
    (void)save_history();

    int status;
    if (waitpid(child_pid, &status, 0) < 0)
        return false;

    int child_error = WEXITSTATUS(status);
    dbgln("Child shell exited with code {}", child_error);

    // 127 is typically the shell indicating command not found. 126 for all other errors.
    if (child_error == 126 || child_error == 127) {
        // There's an opportunity to remove the history entry here since it failed during its runtime, but other implementations (e.g. Windows 11) don't bother removing the entry.
        // This makes sense, especially for cases where a user is debugging a failing program.
        return false;
    }

    dbgln("Ran via command shell.");

    return true;
}

Review comment (suggestion):
Quite weird to read `PasswdOnly` here... (I know this is unrelated to this patch, no action required).

Code after:

    Window::event(event);
}

void RunWindow::do_run()
{
    auto run_input = m_path_combo_box->text().trim_whitespace();

    hide();

    if (run_via_launch(run_input) || run_as_command(run_input)) {
        close();
        return;
    }

    GUI::MessageBox::show_error(this, "Failed to run. Please check your command, path, or address, and try again."sv);

    show();
}

bool RunWindow::run_as_command(ByteString const& run_input)
{
    // Query and use the user's preferred shell.
    // Fallback to "/bin/Shell" if the preferred shell cannot be determined or fails to spawn.
    ByteString shell_path = "/bin/Shell"sv;
    auto current_account = Core::Account::self(Core::Account::Read::PasswdOnly);
    if (current_account.is_error()) {
        dbgln("RunWindow: Could not get current user account: {}", current_account.error());
    } else {
        auto preferred_shell = current_account.value().shell();
        if (!preferred_shell.is_empty()) {
            // Check if the preferred shell actually exists
            if (!FileSystem::exists(preferred_shell)) {
                GUI::MessageBox::show(this, ByteString::formatted("Your preferred shell '{}' does not exist. Falling back to /bin/Shell.", preferred_shell), "Preferred Shell Missing"sv, GUI::MessageBox::Type::Warning);
            } else {
                shell_path = preferred_shell;
            }
        }
    }

    auto maybe_child_pid = Core::Process::spawn(shell_path, Array { "-c", run_input.characters() }, {}, Core::Process::KeepAsChild::Yes);
    if (maybe_child_pid.is_error())
        return false;

    pid_t child_pid = maybe_child_pid.release_value();

    // The child shell was able to start. Let's save it to the history immediately so users can see it as the first entry the next time they run this program.
    prepend_history(run_input);
    // FIXME: Handle failure to save history somehow.
    (void)save_history();