summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AK/ArgsParser.cpp175
-rw-r--r--AK/ArgsParser.h61
-rw-r--r--Kernel/Makefile3
-rw-r--r--LibC/Makefile3
-rw-r--r--Userland/pidof.cpp89
5 files changed, 329 insertions, 2 deletions
diff --git a/AK/ArgsParser.cpp b/AK/ArgsParser.cpp
new file mode 100644
index 0000000000..719ca1854f
--- /dev/null
+++ b/AK/ArgsParser.cpp
@@ -0,0 +1,175 @@
+#include "ArgsParser.h"
+#include "StringBuilder.h"
+
+#include <stdio.h>
+
+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<String>& 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 0000000000..94ae55e7f9
--- /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<String>& get_single_values() const;
+
+ private:
+ HashMap<String, String> m_args;
+ Vector<String> 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<String, Arg> m_args;
+ };
+}
diff --git a/Kernel/Makefile b/Kernel/Makefile
index 265b143cb0..af0321c651 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 ab307c5cbd..acdc8c0d15 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 0000000000..ab15a05417
--- /dev/null
+++ b/Userland/pidof.cpp
@@ -0,0 +1,89 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <LibCore/CFile.h>
+#include <AK/AKString.h>
+#include <AK/Vector.h>
+#include <AK/ArgsParser.h>
+
+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<String> 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);
+}