diff --git a/AK/ArgsParser.cpp b/AK/ArgsParser.cpp new file mode 100644 index 00000000000000..719ca1854f5520 --- /dev/null +++ b/AK/ArgsParser.cpp @@ -0,0 +1,175 @@ +#include "ArgsParser.h" +#include "StringBuilder.h" + +#include + +namespace AK { + + bool ArgsParserResult::is_present(const String& arg_name) const + { + return m_args.contains(arg_name); + } + + String ArgsParserResult::get(const String& arg_name) const + { + return m_args.get(arg_name); + } + + const Vector& ArgsParserResult::get_single_values() const + { + return m_single_values; + } + + ArgsParser::Arg::Arg(const String& name, const String& description, bool required) + : name(name), description(description), required(required) {} + + ArgsParser::Arg::Arg(const String& name, const String& value_name, const String& description, bool required) + : name(name), description(description), value_name(value_name), required(required) {} + + ArgsParser::ArgsParser(const String& program_name, const String& prefix) + : m_program_name(program_name), m_prefix(prefix) {} + + ArgsParserResult ArgsParser::parse(const int argc, const char** argv) + { + ArgsParserResult res; + + // We should have at least one parameter + if (argc < 2) + return {}; + + // We parse the first parameter at the index 1 + if (parse_next_param(1, argv, argc - 1, res) != 0) + return {}; + + if (!check_required_args(res)) + return {}; + + return res; + } + + int ArgsParser::parse_next_param(const int index, const char** argv, const int params_left, ArgsParserResult& res) + { + if (params_left == 0) + return 0; + + String param = argv[index]; + + // We check if the prefix is found at the beginning of the param name + if (is_param_valid(param)) { + auto prefix_length = m_prefix.length(); + String param_name = param.substring(prefix_length, param.length() - prefix_length); + + auto arg = m_args.find(param_name); + if (arg == m_args.end()) { + printf("Unknown arg \""); + if (!param_name.is_null()) + printf("%s", param_name.characters()); + printf("\"\n"); + return -1; + } + + // If this parameter must be followed by a value, we look for it + if (!arg->value.value_name.is_null()) { + if (params_left < 1) { + printf("Missing value for argument %s\n", arg->value.name.characters()); + return -1; + } + + String next = String(argv[index + 1]); + + if (is_param_valid(next)) { + printf("Missing value for argument %s\n", arg->value.name.characters()); + return -1; + } + + res.m_args.set(arg->value.name, next); + + return parse_next_param(index + 2, argv, params_left - 2, res); + } + + // Single argument, not followed by a value + res.m_args.set(arg->value.name, ""); + + return parse_next_param(index + 1, argv, params_left - 1, res); + } + + // Else, it's a value alone, a file name parameter for example + res.m_single_values.append(param); + + return parse_next_param(index + 1, argv, params_left - 1, res); + } + + bool ArgsParser::is_param_valid(const String& param_name) + { + return param_name.substring(0, m_prefix.length()) == m_prefix; + } + + bool ArgsParser::check_required_args(const ArgsParserResult& res) + { + for (auto& it : m_args) { + if (it.value.required) { + if (!res.is_present(it.value.name)) + return false; + } + } + return true; + } + + void ArgsParser::add_arg(const String& name, const String& description, bool required) + { + m_args.set(name, Arg(name, description, required)); + } + + void ArgsParser::add_arg(const String& name, const String& value_name, const String& description, bool required) + { + m_args.set(name, Arg(name, value_name, description, required)); + } + + String ArgsParser::get_usage() const + { + StringBuilder sb; + + sb.append("usage : "); + sb.append(m_program_name); + sb.append(" "); + + for (auto& it : m_args) { + if (it.value.required) + sb.append("<"); + else + sb.append("["); + sb.append(m_prefix); + sb.append(it.value.name); + if (!it.value.value_name.is_null()) { + sb.append(" "); + sb.append(it.value.value_name); + } + if (it.value.required) + sb.append("> "); + else + sb.append("] "); + } + + sb.append("\n"); + + for (auto& it : m_args) { + sb.append(" "); + sb.append(m_prefix); + sb.append(it.value.name); + if (!it.value.value_name.is_null()) { + sb.append(" "); + sb.append(it.value.value_name); + } + sb.append(" : "); + sb.append(it.value.description); + sb.append("\n"); + } + + return sb.to_string(); + } + + void ArgsParser::print_usage() const + { + printf("%s\n", get_usage().characters()); + } +} diff --git a/AK/ArgsParser.h b/AK/ArgsParser.h new file mode 100644 index 00000000000000..94ae55e7f9168a --- /dev/null +++ b/AK/ArgsParser.h @@ -0,0 +1,61 @@ +#pragma once + +#include "AKString.h" +#include "HashMap.h" +#include "Vector.h" + +/* + The class ArgsParser provides a way to parse arguments by using a given list that describes the possible + types of arguments (name, description, required or not, must be followed by a value...). + Call the add_arg() functions to describe your arguments. + + The class ArgsParserResult is used to manipulate the arguments (checking if an arg has been provided, + retrieve its value...). In case of error (missing required argument) an empty structure is returned as result. +*/ + +namespace AK { + class ArgsParserResult { + public: + bool is_present(const String& arg_name) const; + String get(const String& arg_name) const; + const Vector& get_single_values() const; + + private: + HashMap m_args; + Vector m_single_values; + + friend class ArgsParser; + }; + + class ArgsParser { + public: + ArgsParser(const String& program_name, const String& prefix); + + ArgsParserResult parse(const int argc, const char** argv); + + void add_arg(const String& name, const String& description, bool required); + void add_arg(const String& name, const String& value_name, const String& description, bool required); + String get_usage() const; + void print_usage() const; + + private: + struct Arg { + inline Arg() {} + Arg(const String& name, const String& description, bool required); + Arg(const String& name, const String& value_name, const String& description, bool required); + + String name; + String description; + String value_name; + bool required; + }; + + int parse_next_param(const int index, const char** argv, const int params_left, ArgsParserResult& res); + bool is_param_valid(const String& param_name); + bool check_required_args(const ArgsParserResult& res); + + String m_program_name; + String m_prefix; + HashMap m_args; + }; +} diff --git a/Kernel/Makefile b/Kernel/Makefile index 265b143cb094dc..af0321c6519239 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -75,7 +75,8 @@ AK_OBJS = \ ../AK/StringBuilder.o \ ../AK/StringView.o \ ../AK/FileSystemPath.o \ - ../AK/StdLibExtras.o + ../AK/StdLibExtras.o \ + ../AK/ArgsParser.o CXX_OBJS = $(KERNEL_OBJS) $(VFS_OBJS) $(AK_OBJS) OBJS = $(CXX_OBJS) Boot/boot.ao diff --git a/LibC/Makefile b/LibC/Makefile index ab307c5cbd683d..acdc8c0d155eec 100644 --- a/LibC/Makefile +++ b/LibC/Makefile @@ -7,7 +7,8 @@ AK_OBJS = \ ../AK/StringBuilder.o \ ../AK/FileSystemPath.o \ ../AK/StdLibExtras.o \ - ../AK/MappedFile.o + ../AK/MappedFile.o \ + ../AK/ArgsParser.o LIBC_OBJS = \ SharedBuffer.o \ diff --git a/Userland/pidof.cpp b/Userland/pidof.cpp new file mode 100644 index 00000000000000..ab15a05417585e --- /dev/null +++ b/Userland/pidof.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int pid_of(const String& process_name, bool single_shot, bool omit_pid, pid_t pid) +{ + bool displayed_at_least_one = false; + + CFile file("/proc/all"); + if (!file.open(CIODevice::ReadOnly)) { + fprintf(stderr, "pidof failed to open /proc/all\n"); + return 2; + } + + for (;;) { + auto line = file.read_line(1024); + + if (line.is_empty()) + break; + + auto chomped = String((const char*)line.pointer(), line.size() - 1, Chomp); + auto parts = chomped.split_view(','); + + if (parts.size() < 18) + break; + + bool ok = false; + pid_t current_pid = parts[0].to_uint(ok); + String name = parts[11]; + + if (!ok) { + fprintf(stderr, "pidof failed : couldn't convert %s to a valid pid\n", parts[0].characters()); + return 3; + } + + if (name == process_name) { + if (!omit_pid || (omit_pid && current_pid != pid)) { + printf("%d ", current_pid); + displayed_at_least_one = true; + + if (single_shot) + break; + } + } + } + + if (displayed_at_least_one) + printf("\n"); + + return 0; +} + +int main(int argc, char** argv) +{ + AK::ArgsParser args_parser("pidof", "-"); + + args_parser.add_arg("s", "Single shot - this instructs the program to only return one pid", false); + args_parser.add_arg("o", "pid", "Tells pidof to omit processes with that pid. The special pid %PPID can be used to name the parent process of the pidof program.", false); + + AK::ArgsParserResult args = args_parser.parse(argc, (const char**)argv); + + bool s_arg = args.is_present("s"); + bool o_arg = args.is_present("o"); + pid_t pid = 0; + + if (o_arg) { + bool ok = false; + String pid_str = args.get("o"); + + if (pid_str == "%PPID") + pid = getppid(); + else + pid = pid_str.to_uint(ok); + } + + // We should have one single value : the process name + Vector values = args.get_single_values(); + if (values.size() == 0) { + args_parser.print_usage(); + return 0; + } + + return pid_of(values[0], s_arg, o_arg, pid); +}