Skip to content

Commit

Permalink
Feature/pidof (#31)
Browse files Browse the repository at this point in the history
* Added killall command

* Fixed feedbacks of awesomekling

* Implemented pidof program and helper to parse arguments called ArgsParser.

* Fixed feedbacks in pidof implem.

Fixes #26
  • Loading branch information
GuillaumeGas authored and awesomekling committed May 13, 2019
1 parent b716638 commit 801d6f5
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 2 deletions.
175 changes: 175 additions & 0 deletions AK/ArgsParser.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
}
61 changes: 61 additions & 0 deletions AK/ArgsParser.h
Original file line number Diff line number Diff line change
@@ -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;
};
}
3 changes: 2 additions & 1 deletion Kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion LibC/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
89 changes: 89 additions & 0 deletions Userland/pidof.cpp
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 801d6f5

Please sign in to comment.