diff --git a/Userland/Shell/Builtin.cpp b/Userland/Shell/Builtin.cpp index 989b3031c9..d49a2f4283 100644 --- a/Userland/Shell/Builtin.cpp +++ b/Userland/Shell/Builtin.cpp @@ -39,6 +39,106 @@ int Shell::builtin_dump(int argc, char const** argv) return 0; } +enum FollowSymlinks { + Yes, + No +}; + +static Vector find_matching_executables_in_path(StringView filename, FollowSymlinks follow_symlinks = FollowSymlinks::No) +{ + // Edge cases in which there are guaranteed no solutions + if (filename.is_empty() || filename.contains('/')) + return {}; + + char const* path_str = getenv("PATH"); + auto path = DEFAULT_PATH_SV; + if (path_str != nullptr) // maybe && *path_str + path = { path_str, strlen(path_str) }; + + Vector executables; + auto directories = path.split_view(':'); + for (auto directory : directories) { + auto file = DeprecatedString::formatted("{}/{}", directory, filename); + + if (follow_symlinks == FollowSymlinks::Yes) { + auto path_or_error = Core::File::read_link(file); + if (!path_or_error.is_error()) + file = path_or_error.release_value(); + } + if (access(file.characters(), X_OK) == 0) + executables.append(move(file)); + } + + return executables; +} + +int Shell::builtin_where(int argc, char const** argv) +{ + Vector arguments; + bool do_only_path_search { false }; + bool do_follow_symlinks { false }; + bool do_print_only_type { false }; + + Core::ArgsParser parser; + parser.add_positional_argument(arguments, "List of shell builtins, aliases or executables", "arguments"); + parser.add_option(do_only_path_search, "Search only for executables in the PATH environment variable", "path-only", 'p'); + parser.add_option(do_follow_symlinks, "Follow symlinks and print the symlink free path", "follow-symlink", 's'); + parser.add_option(do_print_only_type, "Print the argument type instead of a human readable description", "type", 'w'); + + if (!parser.parse(argc, const_cast(argv), Core::ArgsParser::FailureBehavior::PrintUsage)) + return 1; + + auto const lookup_alias = [do_only_path_search, &m_aliases = this->m_aliases](StringView alias) -> Optional { + if (do_only_path_search) + return {}; + return m_aliases.get(alias); + }; + + auto const lookup_builtin = [do_only_path_search](StringView builtin) -> Optional { + if (do_only_path_search) + return {}; + for (auto const& _builtin : builtin_names) { + if (_builtin == builtin) { + return builtin; + } + } + return {}; + }; + + bool at_least_one_succeded { false }; + for (auto const& argument : arguments) { + auto const alias = lookup_alias(argument); + if (alias.has_value()) { + if (do_print_only_type) + outln("{}: alias", argument); + else + outln("{}: aliased to {}", argument, alias.value()); + at_least_one_succeded = true; + } + + auto const builtin = lookup_builtin(argument); + if (builtin.has_value()) { + if (do_print_only_type) + outln("{}: builtin", builtin.value()); + else + outln("{}: shell built-in command", builtin.value()); + at_least_one_succeded = true; + } + + auto const executables = find_matching_executables_in_path(argument, do_follow_symlinks ? FollowSymlinks::Yes : FollowSymlinks::No); + for (auto const& path : executables) { + if (do_print_only_type) + outln("{}: command", argument); + else + outln(path); + at_least_one_succeded = true; + } + if (!at_least_one_succeded) + warnln("{} not found", argument); + } + return at_least_one_succeded ? 0 : 1; +} + int Shell::builtin_alias(int argc, char const** argv) { Vector arguments; diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h index 35c309302d..ef5b95e25c 100644 --- a/Userland/Shell/Shell.h +++ b/Userland/Shell/Shell.h @@ -25,6 +25,7 @@ #define ENUMERATE_SHELL_BUILTINS() \ __ENUMERATE_SHELL_BUILTIN(alias) \ + __ENUMERATE_SHELL_BUILTIN(where) \ __ENUMERATE_SHELL_BUILTIN(cd) \ __ENUMERATE_SHELL_BUILTIN(cdh) \ __ENUMERATE_SHELL_BUILTIN(pwd) \