1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 10:47:35 +00:00

Libraries: Create top level directory for libraries.

Things were getting a little crowded in the project root, so this patch
moves the Lib*/ directories into Libraries/.
This commit is contained in:
Andreas Kling 2019-07-04 16:16:50 +02:00
parent 63814ffebf
commit 04b9dc2d30
328 changed files with 36 additions and 36 deletions

View file

@ -0,0 +1,237 @@
#include "CArgsParser.h"
#include <AK/StringBuilder.h>
#include <stdio.h>
bool CArgsParserResult::is_present(const String& arg_name) const
{
return m_args.contains(arg_name);
}
String CArgsParserResult::get(const String& arg_name) const
{
return m_args.get(arg_name);
}
const Vector<String>& CArgsParserResult::get_single_values() const
{
return m_single_values;
}
CArgsParser::Arg::Arg(const String& name, const String& description, bool required)
: name(name)
, description(description)
, required(required)
{
}
CArgsParser::Arg::Arg(const String& name, const String& value_name, const String& description, bool required)
: name(name)
, description(description)
, value_name(value_name)
, required(required)
{
}
CArgsParser::CArgsParser(const String& program_name)
: m_program_name(program_name)
, m_prefix("-")
{
}
CArgsParserResult CArgsParser::parse(int argc, char** argv)
{
CArgsParserResult 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 CArgsParser::parse_next_param(int index, char** argv, const int params_left, CArgsParserResult& 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 CArgsParser::is_param_valid(const String& param_name)
{
return param_name.substring(0, m_prefix.length()) == m_prefix;
}
bool CArgsParser::check_required_args(const CArgsParserResult& res)
{
for (auto& it : m_args) {
if (it.value.required) {
if (!res.is_present(it.value.name))
return false;
}
}
int required_arguments = 0;
for (const auto& a : m_single_args) {
if (a.required) {
required_arguments++;
}
}
if (required_arguments != 0) {
if (res.m_single_values.size() < required_arguments)
return false;
}
return true;
}
void CArgsParser::add_required_arg(const String& name, const String& description)
{
m_args.set(name, Arg(name, description, true));
}
void CArgsParser::add_required_arg(const String& name, const String& value_name, const String& description)
{
m_args.set(name, Arg(name, value_name, description, true));
}
void CArgsParser::add_arg(const String& name, const String& description)
{
m_args.set(name, Arg(name, description, false));
}
void CArgsParser::add_arg(const String& name, const String& value_name, const String& description)
{
m_args.set(name, Arg(name, value_name, description, false));
}
void CArgsParser::add_single_value(const String& name)
{
m_single_args.append(SingleArg { name, false });
}
void CArgsParser::add_required_single_value(const String& name)
{
if (m_single_args.size() != 0) {
// adding required arguments after non-required arguments would be nonsensical
ASSERT(m_single_args.last().required);
}
m_single_args.append(SingleArg { name, true });
}
String CArgsParser::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("] ");
}
for (auto& arg : m_single_args) {
if (arg.required)
sb.append("<");
else
sb.append("[");
sb.append(arg.name);
if (arg.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 CArgsParser::print_usage() const
{
printf("%s\n", get_usage().characters());
}

View file

@ -0,0 +1,69 @@
#pragma once
#include <AK/AKString.h>
#include <AK/HashMap.h>
#include <AK/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.
*/
class CArgsParserResult {
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 CArgsParser;
};
class CArgsParser {
public:
CArgsParser(const String& program_name);
CArgsParserResult parse(int argc, char** argv);
void add_required_arg(const String& name, const String& description);
void add_required_arg(const String& name, const String& value_name, const String& description);
void add_arg(const String& name, const String& description);
void add_arg(const String& name, const String& value_name, const String& description);
void add_single_value(const String& name);
void add_required_single_value(const String& name);
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(int index, char** argv, const int params_left, CArgsParserResult& res);
bool is_param_valid(const String& param_name);
bool check_required_args(const CArgsParserResult& res);
String m_program_name;
String m_prefix;
struct SingleArg {
String name;
bool required;
};
Vector<SingleArg> m_single_args;
HashMap<String, Arg> m_args;
};

View file

@ -0,0 +1,233 @@
#include <AK/StringBuilder.h>
#include <LibCore/CConfigFile.h>
#include <LibCore/CFile.h>
#include <LibCore/CUserInfo.h>
#include <pwd.h>
#include <stdio.h>
#include <unistd.h>
NonnullRefPtr<CConfigFile> CConfigFile::get_for_app(const String& app_name)
{
String home_path = get_current_user_home_path();
if (home_path == "/")
home_path = String::format("/tmp");
auto path = String::format("%s/%s.ini", home_path.characters(), app_name.characters());
return adopt(*new CConfigFile(path));
}
NonnullRefPtr<CConfigFile> CConfigFile::get_for_system(const String& app_name)
{
auto path = String::format("/etc/%s.ini", app_name.characters());
return adopt(*new CConfigFile(path));
}
CConfigFile::CConfigFile(const String& file_name)
: m_file_name(file_name)
{
reparse();
}
CConfigFile::~CConfigFile()
{
sync();
}
void CConfigFile::reparse()
{
m_groups.clear();
CFile file(m_file_name);
if (!file.open(CIODevice::OpenMode::ReadOnly))
return;
HashMap<String, String>* current_group = nullptr;
while (file.can_read_line()) {
auto line = file.read_line(BUFSIZ);
auto* cp = (const char*)line.pointer();
while (*cp && (*cp == ' ' || *cp == '\t' || *cp == '\n'))
++cp;
switch (*cp) {
case '\0': // EOL...
case '#': // Comment, skip entire line.
case ';': // -||-
continue;
case '[': { // Start of new group.
StringBuilder builder;
++cp; // Skip the '['
while (*cp && (*cp != ']'))
builder.append(*(cp++));
current_group = &m_groups.ensure(builder.to_string());
break;
}
default: { // Start of key{
StringBuilder key_builder;
StringBuilder value_builder;
while (*cp && (*cp != '='))
key_builder.append(*(cp++));
++cp; // Skip the '='
while (*cp && (*cp != '\n'))
value_builder.append(*(cp++));
if (!current_group) {
// We're not in a group yet, create one with the name ""...
current_group = &m_groups.ensure("");
}
current_group->set(key_builder.to_string(), value_builder.to_string());
}
}
}
}
String CConfigFile::read_entry(const String& group, const String& key, const String& default_value) const
{
if (!has_key(group, key)) {
const_cast<CConfigFile&>(*this).write_entry(group, key, default_value);
return default_value;
}
auto it = m_groups.find(group);
auto jt = it->value.find(key);
return jt->value;
}
int CConfigFile::read_num_entry(const String& group, const String& key, int default_value) const
{
if (!has_key(group, key)) {
const_cast<CConfigFile&>(*this).write_num_entry(group, key, default_value);
return default_value;
}
bool ok;
int value = read_entry(group, key).to_uint(ok);
if (!ok)
return default_value;
return value;
}
Color CConfigFile::read_color_entry(const String& group, const String& key, Color default_value) const
{
if (!has_key(group, key)) {
const_cast<CConfigFile&>(*this).write_color_entry(group, key, default_value);
return default_value;
}
auto shades = read_entry(group, key).split(',');
if (shades.size() < 3)
return default_value;
bool ok1 = true,
ok2 = true,
ok3 = true,
ok4 = true;
Color value;
if (shades.size() == 3) {
value = Color(shades[0].to_uint(ok1),
shades[1].to_uint(ok2),
shades[2].to_uint(ok3));
} else {
value = Color(shades[0].to_uint(ok1),
shades[1].to_uint(ok2),
shades[2].to_uint(ok3),
shades[3].to_uint(ok4));
}
if (!(ok1 && ok2 && ok3 && ok4))
return default_value;
return value;
}
bool CConfigFile::read_bool_entry(const String& group, const String& key, bool default_value) const
{
return read_entry(group, key, default_value ? "1" : "0") == "1";
}
void CConfigFile::write_entry(const String& group, const String& key, const String& value)
{
m_groups.ensure(group).ensure(key) = value;
m_dirty = true;
}
void CConfigFile::write_num_entry(const String& group, const String& key, int value)
{
write_entry(group, key, String::number(value));
}
void CConfigFile::write_bool_entry(const String& group, const String& key, bool value)
{
write_entry(group, key, value ? "1" : "0");
}
void CConfigFile::write_color_entry(const String& group, const String& key, Color value)
{
write_entry(group, key, String::format("%d,%d,%d,%d", value.red(), value.green(), value.blue(), value.alpha()));
}
bool CConfigFile::sync()
{
if (!m_dirty)
return true;
FILE* fp = fopen(m_file_name.characters(), "wb");
if (!fp)
return false;
for (auto& it : m_groups) {
fprintf(fp, "[%s]\n", it.key.characters());
for (auto& jt : it.value)
fprintf(fp, "%s=%s\n", jt.key.characters(), jt.value.characters());
fprintf(fp, "\n");
}
fclose(fp);
m_dirty = false;
return true;
}
void CConfigFile::dump() const
{
for (auto& it : m_groups) {
printf("[%s]\n", it.key.characters());
for (auto& jt : it.value)
printf("%s=%s\n", jt.key.characters(), jt.value.characters());
printf("\n");
}
}
Vector<String> CConfigFile::groups() const
{
return m_groups.keys();
}
Vector<String> CConfigFile::keys(const String& group) const
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return {};
return it->value.keys();
}
bool CConfigFile::has_key(const String& group, const String& key) const
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return {};
return it->value.contains(key);
}
bool CConfigFile::has_group(const String& group) const
{
return m_groups.contains(group);
}
void CConfigFile::remove_group(const String& group)
{
m_groups.remove(group);
m_dirty = true;
}
void CConfigFile::remove_entry(const String& group, const String& key)
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return;
it->value.remove(key);
m_dirty = true;
}

View file

@ -0,0 +1,51 @@
#pragma once
#include <AK/AKString.h>
#include <AK/HashMap.h>
#include <AK/RefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <SharedGraphics/Color.h>
class CConfigFile : public RefCounted<CConfigFile> {
public:
static NonnullRefPtr<CConfigFile> get_for_app(const String& app_name);
static NonnullRefPtr<CConfigFile> get_for_system(const String& app_name);
~CConfigFile();
bool has_group(const String&) const;
bool has_key(const String& group, const String& key) const;
Vector<String> groups() const;
Vector<String> keys(const String& group) const;
String read_entry(const String& group, const String& key, const String& default_vaule = String()) const;
int read_num_entry(const String& group, const String& key, int default_value = 0) const;
bool read_bool_entry(const String& group, const String& key, bool default_value = false) const;
Color read_color_entry(const String& group, const String& key, Color default_value) const;
void write_entry(const String& group, const String& key, const String& value);
void write_num_entry(const String& group, const String& key, int value);
void write_bool_entry(const String& group, const String& key, bool value);
void write_color_entry(const String& group, const String& key, Color value);
void dump() const;
bool is_dirty() const { return m_dirty; }
bool sync();
void remove_group(const String& group);
void remove_entry(const String& group, const String& key);
String file_name() const { return m_file_name; }
private:
explicit CConfigFile(const String& file_name);
void reparse();
String m_file_name;
HashMap<String, HashMap<String, String>> m_groups;
bool m_dirty { false };
};

View file

@ -0,0 +1,67 @@
#include "CDirIterator.h"
#include <cerrno>
CDirIterator::CDirIterator(const StringView& path, Flags flags)
: m_flags(flags)
{
m_dir = opendir(path.characters());
if (m_dir == nullptr) {
m_error = errno;
}
}
CDirIterator::~CDirIterator()
{
if (m_dir != nullptr) {
closedir(m_dir);
m_dir = nullptr;
}
}
bool CDirIterator::advance_next()
{
if (m_dir == nullptr)
return false;
bool keep_advancing = true;
while (keep_advancing) {
errno = 0;
auto* de = readdir(m_dir);
if (de) {
m_next = de->d_name;
} else {
m_error = errno;
m_next = String();
}
if (m_next.is_null()) {
keep_advancing = false;
} else if (m_flags & Flags::SkipDots) {
if (m_next.length() < 1 || m_next[0] != '.') {
keep_advancing = false;
}
} else {
keep_advancing = false;
}
}
return m_next.length() > 0;
}
bool CDirIterator::has_next()
{
if (!m_next.is_null())
return true;
return advance_next();
}
String CDirIterator::next_path()
{
if (m_next.is_null())
advance_next();
auto tmp = m_next;
m_next = String();
return tmp;
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <AK/AKString.h>
#include <dirent.h>
class CDirIterator {
public:
enum Flags {
NoFlags = 0x0,
SkipDots = 0x1,
};
CDirIterator(const StringView& path, Flags = Flags::NoFlags);
~CDirIterator();
bool has_error() const { return m_error != 0; }
int error() const { return m_error; }
const char* error_string() const { return strerror(m_error); }
bool has_next();
String next_path();
private:
DIR* m_dir = nullptr;
int m_error = 0;
String m_next;
int m_flags;
bool advance_next();
};

View file

@ -0,0 +1,20 @@
#include <AK/Assertions.h>
#include <AK/Time.h>
#include <LibCore/CElapsedTimer.h>
#include <sys/time.h>
void CElapsedTimer::start()
{
m_valid = true;
gettimeofday(&m_start_time, nullptr);
}
int CElapsedTimer::elapsed() const
{
ASSERT(is_valid());
struct timeval now;
gettimeofday(&now, nullptr);
struct timeval diff;
timeval_sub(now, m_start_time, diff);
return diff.tv_sec * 1000 + diff.tv_usec / 1000;
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <sys/time.h>
class CElapsedTimer {
public:
CElapsedTimer() {}
bool is_valid() const { return m_valid; }
void start();
int elapsed() const;
private:
bool m_valid { false };
struct timeval m_start_time {
0, 0
};
};

View file

@ -0,0 +1,12 @@
#include <LibCore/CEvent.h>
#include <LibCore/CObject.h>
CChildEvent::CChildEvent(Type type, CObject& child)
: CEvent(type)
, m_child(child.make_weak_ptr())
{
}
CChildEvent::~CChildEvent()
{
}

View file

@ -0,0 +1,74 @@
#pragma once
#include <AK/AKString.h>
#include <AK/Function.h>
#include <AK/Types.h>
#include <AK/WeakPtr.h>
class CObject;
class CEvent {
public:
enum Type {
Invalid = 0,
Quit,
Timer,
DeferredDestroy,
DeferredInvoke,
ChildAdded,
ChildRemoved,
};
CEvent() {}
explicit CEvent(unsigned type)
: m_type(type)
{
}
virtual ~CEvent() {}
unsigned type() const { return m_type; }
private:
unsigned m_type { Type::Invalid };
};
class CDeferredInvocationEvent : public CEvent {
friend class CEventLoop;
public:
CDeferredInvocationEvent(Function<void(CObject&)> invokee)
: CEvent(CEvent::Type::DeferredInvoke)
, m_invokee(move(invokee))
{
}
private:
Function<void(CObject&)> m_invokee;
};
class CTimerEvent final : public CEvent {
public:
explicit CTimerEvent(int timer_id)
: CEvent(CEvent::Timer)
, m_timer_id(timer_id)
{
}
~CTimerEvent() {}
int timer_id() const { return m_timer_id; }
private:
int m_timer_id;
};
class CChildEvent final : public CEvent {
public:
CChildEvent(Type, CObject& child);
~CChildEvent();
CObject* child() { return m_child.ptr(); }
const CObject* child() const { return m_child.ptr(); }
private:
WeakPtr<CObject> m_child;
};

View file

@ -0,0 +1,302 @@
#include <AK/Time.h>
#include <LibCore/CEvent.h>
#include <LibCore/CEventLoop.h>
#include <LibCore/CLock.h>
#include <LibCore/CNotifier.h>
#include <LibCore/CObject.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
//#define CEVENTLOOP_DEBUG
//#define DEFERRED_INVOKE_DEBUG
static CEventLoop* s_main_event_loop;
static Vector<CEventLoop*>* s_event_loop_stack;
HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>* CEventLoop::s_timers;
HashTable<CNotifier*>* CEventLoop::s_notifiers;
int CEventLoop::s_next_timer_id = 1;
CEventLoop::CEventLoop()
{
if (!s_event_loop_stack) {
s_event_loop_stack = new Vector<CEventLoop*>;
s_timers = new HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>;
s_notifiers = new HashTable<CNotifier*>;
}
if (!s_main_event_loop) {
s_main_event_loop = this;
s_event_loop_stack->append(this);
}
#ifdef CEVENTLOOP_DEBUG
dbgprintf("(%u) CEventLoop constructed :)\n", getpid());
#endif
}
CEventLoop::~CEventLoop()
{
}
CEventLoop& CEventLoop::main()
{
ASSERT(s_main_event_loop);
return *s_main_event_loop;
}
CEventLoop& CEventLoop::current()
{
return *s_event_loop_stack->last();
}
void CEventLoop::quit(int code)
{
m_exit_requested = true;
m_exit_code = code;
}
struct CEventLoopPusher {
public:
CEventLoopPusher(CEventLoop& event_loop)
: m_event_loop(event_loop)
{
if (&m_event_loop != s_main_event_loop) {
m_event_loop.take_pending_events_from(CEventLoop::current());
s_event_loop_stack->append(&event_loop);
}
}
~CEventLoopPusher()
{
if (&m_event_loop != s_main_event_loop) {
s_event_loop_stack->take_last();
CEventLoop::current().take_pending_events_from(m_event_loop);
}
}
private:
CEventLoop& m_event_loop;
};
int CEventLoop::exec()
{
CEventLoopPusher pusher(*this);
for (;;) {
if (m_exit_requested)
return m_exit_code;
pump();
}
ASSERT_NOT_REACHED();
}
void CEventLoop::pump(WaitMode mode)
{
// window server event processing...
do_processing();
if (m_queued_events.is_empty()) {
wait_for_event(mode);
do_processing();
}
decltype(m_queued_events) events;
{
LOCKER(m_lock);
events = move(m_queued_events);
}
for (auto& queued_event : events) {
auto* receiver = queued_event.receiver.ptr();
auto& event = *queued_event.event;
#ifdef CEVENTLOOP_DEBUG
dbgprintf("CEventLoop: %s{%p} event %u\n", receiver->class_name(), receiver, (unsigned)event.type());
#endif
if (!receiver) {
switch (event.type()) {
case CEvent::Quit:
ASSERT_NOT_REACHED();
return;
default:
dbgprintf("Event type %u with no receiver :(\n", event.type());
}
} else if (event.type() == CEvent::Type::DeferredInvoke) {
#ifdef DEFERRED_INVOKE_DEBUG
printf("DeferredInvoke: receiver=%s{%p}\n", receiver->class_name(), receiver);
#endif
static_cast<CDeferredInvocationEvent&>(event).m_invokee(*receiver);
} else {
receiver->event(event);
}
if (m_exit_requested) {
LOCKER(m_lock);
auto rejigged_event_queue = move(events);
rejigged_event_queue.append(move(m_queued_events));
m_queued_events = move(rejigged_event_queue);
}
}
}
void CEventLoop::post_event(CObject& receiver, OwnPtr<CEvent>&& event)
{
LOCKER(m_lock);
#ifdef CEVENTLOOP_DEBUG
dbgprintf("CEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), &receiver, event.ptr());
#endif
m_queued_events.append({ receiver.make_weak_ptr(), move(event) });
}
void CEventLoop::wait_for_event(WaitMode mode)
{
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
int max_fd = 0;
auto add_fd_to_set = [&max_fd](int fd, fd_set& set) {
FD_SET(fd, &set);
if (fd > max_fd)
max_fd = fd;
};
int max_fd_added = -1;
add_file_descriptors_for_select(rfds, max_fd_added);
max_fd = max(max_fd, max_fd_added);
for (auto& notifier : *s_notifiers) {
if (notifier->event_mask() & CNotifier::Read)
add_fd_to_set(notifier->fd(), rfds);
if (notifier->event_mask() & CNotifier::Write)
add_fd_to_set(notifier->fd(), wfds);
if (notifier->event_mask() & CNotifier::Exceptional)
ASSERT_NOT_REACHED();
}
bool queued_events_is_empty;
{
LOCKER(m_lock);
queued_events_is_empty = m_queued_events.is_empty();
}
timeval now;
struct timeval timeout = { 0, 0 };
bool should_wait_forever = false;
if (mode == WaitMode::WaitForEvents) {
if (!s_timers->is_empty() && queued_events_is_empty) {
gettimeofday(&now, nullptr);
get_next_timer_expiration(timeout);
timeval_sub(timeout, now, timeout);
} else {
should_wait_forever = true;
}
} else {
should_wait_forever = false;
}
int marked_fd_count = select(max_fd + 1, &rfds, &wfds, nullptr, should_wait_forever ? nullptr : &timeout);
if (marked_fd_count < 0) {
ASSERT_NOT_REACHED();
}
if (!s_timers->is_empty()) {
gettimeofday(&now, nullptr);
}
for (auto& it : *s_timers) {
auto& timer = *it.value;
if (!timer.has_expired(now))
continue;
#ifdef CEVENTLOOP_DEBUG
dbgprintf("CEventLoop: Timer %d has expired, sending CTimerEvent to %p\n", timer.timer_id, timer.owner);
#endif
post_event(*timer.owner, make<CTimerEvent>(timer.timer_id));
if (timer.should_reload) {
timer.reload(now);
} else {
// FIXME: Support removing expired timers that don't want to reload.
ASSERT_NOT_REACHED();
}
}
if (!marked_fd_count)
return;
for (auto& notifier : *s_notifiers) {
if (FD_ISSET(notifier->fd(), &rfds)) {
if (notifier->on_ready_to_read)
notifier->on_ready_to_read();
}
if (FD_ISSET(notifier->fd(), &wfds)) {
if (notifier->on_ready_to_write)
notifier->on_ready_to_write();
}
}
process_file_descriptors_after_select(rfds);
}
bool CEventLoop::EventLoopTimer::has_expired(const timeval& now) const
{
return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec);
}
void CEventLoop::EventLoopTimer::reload(const timeval& now)
{
fire_time = now;
fire_time.tv_sec += interval / 1000;
fire_time.tv_usec += (interval % 1000) * 1000;
}
void CEventLoop::get_next_timer_expiration(timeval& soonest)
{
ASSERT(!s_timers->is_empty());
bool has_checked_any = false;
for (auto& it : *s_timers) {
auto& fire_time = it.value->fire_time;
if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec))
soonest = fire_time;
has_checked_any = true;
}
}
int CEventLoop::register_timer(CObject& object, int milliseconds, bool should_reload)
{
ASSERT(milliseconds >= 0);
auto timer = make<EventLoopTimer>();
timer->owner = object.make_weak_ptr();
timer->interval = milliseconds;
timeval now;
gettimeofday(&now, nullptr);
timer->reload(now);
timer->should_reload = should_reload;
int timer_id = ++s_next_timer_id; // FIXME: This will eventually wrap around.
ASSERT(timer_id); // FIXME: Aforementioned wraparound.
timer->timer_id = timer_id;
s_timers->set(timer->timer_id, move(timer));
return timer_id;
}
bool CEventLoop::unregister_timer(int timer_id)
{
auto it = s_timers->find(timer_id);
if (it == s_timers->end())
return false;
s_timers->remove(it);
return true;
}
void CEventLoop::register_notifier(Badge<CNotifier>, CNotifier& notifier)
{
s_notifiers->set(&notifier);
}
void CEventLoop::unregister_notifier(Badge<CNotifier>, CNotifier& notifier)
{
s_notifiers->remove(&notifier);
}

View file

@ -0,0 +1,89 @@
#pragma once
#include <AK/Badge.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/Vector.h>
#include <AK/WeakPtr.h>
#include <LibCore/CLock.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
class CEvent;
class CObject;
class CNotifier;
class CEventLoop {
public:
CEventLoop();
virtual ~CEventLoop();
int exec();
enum class WaitMode {
WaitForEvents,
PollForEvents,
};
// processe events, generally called by exec() in a loop.
// this should really only be used for integrating with other event loops
void pump(WaitMode = WaitMode::WaitForEvents);
void post_event(CObject& receiver, OwnPtr<CEvent>&&);
static CEventLoop& main();
static CEventLoop& current();
bool was_exit_requested() const { return m_exit_requested; }
static int register_timer(CObject&, int milliseconds, bool should_reload);
static bool unregister_timer(int timer_id);
static void register_notifier(Badge<CNotifier>, CNotifier&);
static void unregister_notifier(Badge<CNotifier>, CNotifier&);
void quit(int);
virtual void take_pending_events_from(CEventLoop& other)
{
m_queued_events.append(move(other.m_queued_events));
}
protected:
virtual void add_file_descriptors_for_select(fd_set&, int& max_fd) { UNUSED_PARAM(max_fd); }
virtual void process_file_descriptors_after_select(const fd_set&) {}
virtual void do_processing() {}
private:
void wait_for_event(WaitMode);
void get_next_timer_expiration(timeval&);
struct QueuedEvent {
WeakPtr<CObject> receiver;
OwnPtr<CEvent> event;
};
Vector<QueuedEvent, 64> m_queued_events;
bool m_exit_requested { false };
int m_exit_code { 0 };
CLock m_lock;
struct EventLoopTimer {
int timer_id { 0 };
int interval { 0 };
timeval fire_time;
bool should_reload { false };
WeakPtr<CObject> owner;
void reload(const timeval& now);
bool has_expired(const timeval& now) const;
};
static HashMap<int, OwnPtr<EventLoopTimer>>* s_timers;
static int s_next_timer_id;
static HashTable<CNotifier*>* s_notifiers;
};

View file

@ -0,0 +1,51 @@
#include <LibCore/CFile.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
CFile::CFile(const StringView& filename)
: m_filename(filename)
{
}
CFile::~CFile()
{
if (m_should_close_file_descriptor == ShouldCloseFileDescription::Yes && mode() != NotOpen)
close();
}
bool CFile::open(int fd, CIODevice::OpenMode mode, ShouldCloseFileDescription should_close)
{
set_fd(fd);
set_mode(mode);
m_should_close_file_descriptor = should_close;
return true;
}
bool CFile::open(CIODevice::OpenMode mode)
{
int flags = 0;
if ((mode & CIODevice::ReadWrite) == CIODevice::ReadWrite) {
flags |= O_RDWR | O_CREAT;
} else if (mode & CIODevice::ReadOnly) {
flags |= O_RDONLY;
} else if (mode & CIODevice::WriteOnly) {
flags |= O_WRONLY | O_CREAT;
}
if (mode & CIODevice::Append)
flags |= O_APPEND;
if (mode & CIODevice::Truncate)
flags |= O_TRUNC;
if (mode & CIODevice::MustBeNew)
flags |= O_EXCL;
int fd = ::open(m_filename.characters(), flags, 0666);
if (fd < 0) {
set_error(errno);
return false;
}
set_fd(fd);
set_mode(mode);
return true;
}

28
Libraries/LibCore/CFile.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include <AK/AKString.h>
#include <LibCore/CIODevice.h>
class CFile final : public CIODevice {
public:
CFile() {}
explicit CFile(const StringView&);
virtual ~CFile() override;
String filename() const { return m_filename; }
void set_filename(const StringView& filename) { m_filename = filename; }
virtual bool open(CIODevice::OpenMode) override;
enum class ShouldCloseFileDescription {
No = 0,
Yes
};
bool open(int fd, CIODevice::OpenMode, ShouldCloseFileDescription);
virtual const char* class_name() const override { return "CFile"; }
private:
String m_filename;
ShouldCloseFileDescription m_should_close_file_descriptor { ShouldCloseFileDescription::Yes };
};

View file

@ -0,0 +1,118 @@
#include <LibCore/CHttpJob.h>
#include <LibCore/CHttpResponse.h>
#include <LibCore/CTCPSocket.h>
#include <stdio.h>
#include <unistd.h>
CHttpJob::CHttpJob(const CHttpRequest& request)
: m_request(request)
{
}
CHttpJob::~CHttpJob()
{
}
void CHttpJob::on_socket_connected()
{
auto raw_request = m_request.to_raw_request();
#if 0
printf("raw_request:\n%s\n", String::copy(raw_request).characters());
#endif
bool success = m_socket->send(raw_request);
if (!success)
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
Vector<u8> buffer;
while (m_socket->is_connected()) {
if (m_state == State::InStatus) {
while (!m_socket->can_read_line())
usleep(1);
ASSERT(m_socket->can_read_line());
auto line = m_socket->read_line(PAGE_SIZE);
if (line.is_null()) {
printf("Expected HTTP status\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
}
auto parts = String::copy(line, Chomp).split(' ');
if (parts.size() < 3) {
printf("Expected 3-part HTTP status, got '%s'\n", line.pointer());
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
bool ok;
m_code = parts[1].to_uint(ok);
if (!ok) {
printf("Expected numeric HTTP status\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
m_state = State::InHeaders;
continue;
}
if (m_state == State::InHeaders) {
while (!m_socket->can_read_line())
usleep(1);
auto line = m_socket->read_line(PAGE_SIZE);
if (line.is_null()) {
printf("Expected HTTP header\n");
return did_fail(CNetworkJob::Error::ProtocolFailed);
}
auto chomped_line = String::copy(line, Chomp);
if (chomped_line.is_empty()) {
m_state = State::InBody;
continue;
}
auto parts = chomped_line.split(':');
if (parts.is_empty()) {
printf("Expected HTTP header with key/value\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
auto name = parts[0];
if (chomped_line.length() < name.length() + 2) {
printf("Malformed HTTP header: '%s' (%d)\n", chomped_line.characters(), chomped_line.length());
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
auto value = chomped_line.substring(name.length() + 2, chomped_line.length() - name.length() - 2);
m_headers.set(name, value);
printf("[%s] = '%s'\n", name.characters(), value.characters());
continue;
}
ASSERT(m_state == State::InBody);
while (!m_socket->can_read())
usleep(1);
ASSERT(m_socket->can_read());
auto payload = m_socket->receive(PAGE_SIZE);
if (!payload) {
if (m_socket->eof()) {
m_state = State::Finished;
break;
}
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
buffer.append(payload.pointer(), payload.size());
bool ok;
if (buffer.size() >= m_headers.get("Content-Length").to_int(ok) && ok) {
m_state = State::Finished;
break;
}
}
auto response = CHttpResponse::create(m_code, move(m_headers), ByteBuffer::copy(buffer.data(), buffer.size()));
deferred_invoke([this, response](auto&) {
did_finish(move(response));
});
}
void CHttpJob::start()
{
ASSERT(!m_socket);
m_socket = new CTCPSocket(this);
m_socket->on_connected = [this] {
printf("Socket on_connected callback\n");
on_socket_connected();
};
bool success = m_socket->connect(m_request.hostname(), m_request.port());
if (!success)
return did_fail(CNetworkJob::Error::ConnectionFailed);
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <AK/HashMap.h>
#include <LibCore/CHttpRequest.h>
#include <LibCore/CNetworkJob.h>
class CTCPSocket;
class CHttpJob final : public CNetworkJob {
public:
explicit CHttpJob(const CHttpRequest&);
virtual ~CHttpJob() override;
virtual void start() override;
virtual const char* class_name() const override { return "CHttpJob"; }
private:
void on_socket_connected();
enum class State {
InStatus,
InHeaders,
InBody,
Finished,
};
CHttpRequest m_request;
CTCPSocket* m_socket { nullptr };
State m_state { State::InStatus };
int m_code { -1 };
HashMap<String, String> m_headers;
};

View file

@ -0,0 +1,44 @@
#include <AK/StringBuilder.h>
#include <LibCore/CHttpJob.h>
#include <LibCore/CHttpRequest.h>
CHttpRequest::CHttpRequest()
{
}
CHttpRequest::~CHttpRequest()
{
}
CNetworkJob* CHttpRequest::schedule()
{
auto* job = new CHttpJob(*this);
job->start();
return job;
}
String CHttpRequest::method_name() const
{
switch (m_method) {
case Method::GET:
return "GET";
case Method::HEAD:
return "HEAD";
case Method::POST:
return "POST";
default:
ASSERT_NOT_REACHED();
}
}
ByteBuffer CHttpRequest::to_raw_request() const
{
StringBuilder builder;
builder.append(method_name());
builder.append(' ');
builder.append(m_path);
builder.append(" HTTP/1.0\r\nHost: ");
builder.append(m_hostname);
builder.append("\r\n\r\n");
return builder.to_byte_buffer();
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <AK/AKString.h>
class CNetworkJob;
class CHttpRequest {
public:
enum Method {
Invalid,
HEAD,
GET,
POST
};
CHttpRequest();
~CHttpRequest();
String hostname() const { return m_hostname; }
int port() const { return m_port; }
String path() const { return m_path; }
Method method() const { return m_method; }
void set_hostname(const String& hostname) { m_hostname = hostname; }
void set_port(int port) { m_port = port; }
void set_path(const String& path) { m_path = path; }
void set_method(Method method) { m_method = method; }
String method_name() const;
ByteBuffer to_raw_request() const;
CNetworkJob* schedule();
private:
String m_hostname;
String m_path;
int m_port { 80 };
Method m_method { GET };
};

View file

@ -0,0 +1,12 @@
#include <LibCore/CHttpResponse.h>
CHttpResponse::CHttpResponse(int code, HashMap<String, String>&& headers, ByteBuffer&& payload)
: CNetworkResponse(move(payload))
, m_code(code)
, m_headers(move(headers))
{
}
CHttpResponse::~CHttpResponse()
{
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <AK/AKString.h>
#include <AK/HashMap.h>
#include <LibCore/CNetworkResponse.h>
class CHttpResponse : public CNetworkResponse {
public:
virtual ~CHttpResponse() override;
static NonnullRefPtr<CHttpResponse> create(int code, HashMap<String, String>&& headers, ByteBuffer&& payload)
{
return adopt(*new CHttpResponse(code, move(headers), move(payload)));
}
int code() const { return m_code; }
const HashMap<String, String>& headers() const { return m_headers; }
private:
CHttpResponse(int code, HashMap<String, String>&&, ByteBuffer&&);
int m_code { 0 };
HashMap<String, String> m_headers;
};

View file

@ -0,0 +1,242 @@
#include <AK/PrintfImplementation.h>
#include <LibCore/CIODevice.h>
#include <errno.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
CIODevice::CIODevice(CObject* parent)
: CObject(parent)
{
}
CIODevice::~CIODevice()
{
}
const char* CIODevice::error_string() const
{
return strerror(m_error);
}
ByteBuffer CIODevice::read(int max_size)
{
if (m_fd < 0)
return {};
if (!max_size)
return {};
auto buffer = ByteBuffer::create_uninitialized(max_size);
auto* buffer_ptr = (char*)buffer.pointer();
int remaining_buffer_space = buffer.size();
int taken_from_buffered = 0;
if (!m_buffered_data.is_empty()) {
taken_from_buffered = min(remaining_buffer_space, m_buffered_data.size());
memcpy(buffer_ptr, m_buffered_data.data(), taken_from_buffered);
Vector<u8> new_buffered_data;
new_buffered_data.append(m_buffered_data.data() + taken_from_buffered, m_buffered_data.size() - taken_from_buffered);
m_buffered_data = move(new_buffered_data);
remaining_buffer_space -= taken_from_buffered;
buffer_ptr += taken_from_buffered;
}
if (!remaining_buffer_space)
return buffer;
int nread = ::read(m_fd, buffer_ptr, remaining_buffer_space);
if (nread < 0) {
if (taken_from_buffered) {
buffer.trim(taken_from_buffered);
return buffer;
}
set_error(errno);
return {};
}
if (nread == 0) {
set_eof(true);
if (taken_from_buffered) {
buffer.trim(taken_from_buffered);
return buffer;
}
return {};
}
buffer.trim(taken_from_buffered + nread);
return buffer;
}
bool CIODevice::can_read_from_fd() const
{
// FIXME: Can we somehow remove this once CSocket is implemented using non-blocking sockets?
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(m_fd, &rfds);
struct timeval timeout {
0, 0
};
int rc = select(m_fd + 1, &rfds, nullptr, nullptr, &timeout);
if (rc < 0) {
// NOTE: We don't set m_error here.
perror("CIODevice::can_read: select");
return false;
}
return FD_ISSET(m_fd, &rfds);
}
bool CIODevice::can_read_line()
{
if (m_eof && !m_buffered_data.is_empty())
return true;
if (m_buffered_data.contains_slow('\n'))
return true;
if (!can_read_from_fd())
return false;
populate_read_buffer();
return m_buffered_data.contains_slow('\n');
}
bool CIODevice::can_read() const
{
return !m_buffered_data.is_empty() || can_read_from_fd();
}
ByteBuffer CIODevice::read_all()
{
ByteBuffer buffer;
if (!m_buffered_data.is_empty()) {
buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
m_buffered_data.clear();
}
while (can_read_from_fd()) {
char read_buffer[4096];
int nread = ::read(m_fd, read_buffer, sizeof(read_buffer));
if (nread < 0) {
set_error(nread);
return buffer;
}
if (nread == 0) {
set_eof(true);
break;
}
buffer.append(read_buffer, nread);
}
return buffer;
}
ByteBuffer CIODevice::read_line(int max_size)
{
if (m_fd < 0)
return {};
if (!max_size)
return {};
if (!can_read_line())
return {};
if (m_eof) {
if (m_buffered_data.size() > max_size) {
dbgprintf("CIODevice::read_line: At EOF but there's more than max_size(%d) buffered\n", max_size);
return {};
}
auto buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
m_buffered_data.clear();
return buffer;
}
auto line = ByteBuffer::create_uninitialized(max_size + 1);
int line_index = 0;
while (line_index < max_size) {
u8 ch = m_buffered_data[line_index];
line[line_index++] = ch;
if (ch == '\n') {
Vector<u8> new_buffered_data;
new_buffered_data.append(m_buffered_data.data() + line_index, m_buffered_data.size() - line_index);
m_buffered_data = move(new_buffered_data);
line[line_index] = '\0';
line.trim(line_index + 1);
return line;
}
}
return {};
}
bool CIODevice::populate_read_buffer()
{
if (m_fd < 0)
return false;
u8 buffer[1024];
int nread = ::read(m_fd, buffer, sizeof(buffer));
if (nread < 0) {
set_error(errno);
return false;
}
if (nread == 0) {
set_eof(true);
return false;
}
m_buffered_data.append(buffer, nread);
return true;
}
bool CIODevice::close()
{
if (fd() < 0 || mode() == NotOpen)
return false;
int rc = ::close(fd());
if (rc < 0) {
set_error(rc);
return false;
}
set_fd(-1);
set_mode(CIODevice::NotOpen);
return true;
}
bool CIODevice::seek(i64 offset, SeekMode mode, off_t* pos)
{
int m = SEEK_SET;
switch (mode) {
case SeekMode::SetPosition:
m = SEEK_SET;
break;
case SeekMode::FromCurrentPosition:
m = SEEK_CUR;
break;
case SeekMode::FromEndPosition:
m = SEEK_END;
break;
}
off_t rc = lseek(m_fd, offset, m);
if (rc < 0) {
set_error(errno);
if (pos)
*pos = -1;
return false;
}
m_buffered_data.clear();
m_eof = false;
if (pos)
*pos = rc;
return true;
}
bool CIODevice::write(const u8* data, int size)
{
int rc = ::write(m_fd, data, size);
if (rc < 0) {
perror("CIODevice::write: write");
set_error(errno);
return false;
}
return rc == size;
}
int CIODevice::printf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
// FIXME: We're not propagating write() failures to client here!
int ret = printf_internal([this](char*&, char ch) {
int rc = write((const u8*)&ch, 1);
if (rc < 0)
dbgprintf("CIODevice::printf: write: %s\n", strerror(errno));
},
nullptr, format, ap);
va_end(ap);
return ret;
}

View file

@ -0,0 +1,74 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/StringView.h>
#include <LibCore/CObject.h>
class CIODevice : public CObject {
public:
enum OpenMode {
NotOpen = 0,
ReadOnly = 1,
WriteOnly = 2,
ReadWrite = 3,
Append = 4,
Truncate = 8,
MustBeNew = 16,
};
virtual ~CIODevice() override;
int fd() const { return m_fd; }
unsigned mode() const { return m_mode; }
bool eof() const { return m_eof; }
int error() const { return m_error; }
const char* error_string() const;
bool has_error() const { return m_error != 0; }
ByteBuffer read(int max_size);
ByteBuffer read_line(int max_size);
ByteBuffer read_all();
bool write(const u8*, int size);
bool write(const AK::StringView& v) { return write((const u8*)v.characters(), v.length()); }
// FIXME: I would like this to be const but currently it needs to call populate_read_buffer().
bool can_read_line();
bool can_read() const;
enum class SeekMode {
SetPosition,
FromCurrentPosition,
FromEndPosition,
};
bool seek(i64, SeekMode = SeekMode::SetPosition, off_t* = nullptr);
virtual bool open(CIODevice::OpenMode) = 0;
virtual bool close();
int printf(const char*, ...);
virtual const char* class_name() const override { return "CIODevice"; }
protected:
explicit CIODevice(CObject* parent = nullptr);
void set_fd(int fd) { m_fd = fd; }
void set_mode(OpenMode mode) { m_mode = mode; }
void set_error(int error) { m_error = error; }
void set_eof(bool eof) { m_eof = eof; }
private:
bool populate_read_buffer();
bool can_read_from_fd() const;
int m_fd { -1 };
int m_error { 0 };
bool m_eof { false };
OpenMode m_mode { NotOpen };
Vector<u8> m_buffered_data;
};

125
Libraries/LibCore/CLock.h Normal file
View file

@ -0,0 +1,125 @@
#pragma once
#ifdef __serenity__
#include <AK/Assertions.h>
#include <AK/Types.h>
#include <unistd.h>
#define memory_barrier() asm volatile("" :: \
: "memory")
static inline u32 CAS(volatile u32* mem, u32 newval, u32 oldval)
{
u32 ret;
asm volatile(
"cmpxchgl %2, %1"
: "=a"(ret), "+m"(*mem)
: "r"(newval), "0"(oldval)
: "cc", "memory");
return ret;
}
class CLock {
public:
CLock() {}
~CLock() {}
void lock();
void unlock();
private:
volatile u32 m_lock { 0 };
u32 m_level { 0 };
int m_holder { -1 };
};
class CLocker {
public:
[[gnu::always_inline]] inline explicit CLocker(CLock& l)
: m_lock(l)
{
lock();
}
[[gnu::always_inline]] inline ~CLocker() { unlock(); }
[[gnu::always_inline]] inline void unlock() { m_lock.unlock(); }
[[gnu::always_inline]] inline void lock() { m_lock.lock(); }
private:
CLock& m_lock;
};
[[gnu::always_inline]] inline void CLock::lock()
{
int tid = gettid();
for (;;) {
if (CAS(&m_lock, 1, 0) == 0) {
if (m_holder == -1 || m_holder == tid) {
m_holder = tid;
++m_level;
memory_barrier();
m_lock = 0;
return;
}
m_lock = 0;
}
donate(m_holder);
}
}
inline void CLock::unlock()
{
for (;;) {
if (CAS(&m_lock, 1, 0) == 0) {
ASSERT(m_holder == gettid());
ASSERT(m_level);
--m_level;
if (m_level) {
memory_barrier();
m_lock = 0;
return;
}
m_holder = -1;
memory_barrier();
m_lock = 0;
return;
}
donate(m_holder);
}
}
#define LOCKER(lock) CLocker locker(lock)
template<typename T>
class CLockable {
public:
CLockable() {}
CLockable(T&& resource)
: m_resource(move(resource))
{
}
CLock& lock() { return m_lock; }
T& resource() { return m_resource; }
T lock_and_copy()
{
LOCKER(m_lock);
return m_resource;
}
private:
T m_resource;
CLock m_lock;
};
#else
class CLock {
public:
CLock() { }
~CLock() { }
};
#define LOCKER(x)
#endif

View file

@ -0,0 +1,43 @@
#include <LibCore/CNetworkJob.h>
#include <LibCore/CNetworkResponse.h>
#include <stdio.h>
CNetworkJob::CNetworkJob()
{
}
CNetworkJob::~CNetworkJob()
{
}
void CNetworkJob::did_finish(NonnullRefPtr<CNetworkResponse>&& response)
{
m_response = move(response);
printf("%s{%p} job did_finish!\n", class_name(), this);
ASSERT(on_finish);
on_finish(true);
delete_later();
}
void CNetworkJob::did_fail(Error error)
{
m_error = error;
dbgprintf("%s{%p} job did_fail! error: %u (%s)\n", class_name(), this, (unsigned)error, to_string(error));
ASSERT(on_finish);
on_finish(false);
delete_later();
}
const char* to_string(CNetworkJob::Error error)
{
switch (error) {
case CNetworkJob::Error::ProtocolFailed:
return "ProtocolFailed";
case CNetworkJob::Error::ConnectionFailed:
return "ConnectionFailed";
case CNetworkJob::Error::TransmissionFailed:
return "TransmissionFailed";
default:
return "(Unknown error)";
}
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <AK/Function.h>
#include <LibCore/CObject.h>
class CNetworkResponse;
class CNetworkJob : public CObject {
public:
enum class Error {
None,
ConnectionFailed,
TransmissionFailed,
ProtocolFailed,
};
virtual ~CNetworkJob() override;
Function<void(bool success)> on_finish;
bool has_error() const { return m_error != Error::None; }
Error error() const { return m_error; }
CNetworkResponse* response() { return m_response.ptr(); }
const CNetworkResponse* response() const { return m_response.ptr(); }
virtual void start() = 0;
virtual const char* class_name() const override { return "CNetworkJob"; }
protected:
CNetworkJob();
void did_finish(NonnullRefPtr<CNetworkResponse>&&);
void did_fail(Error);
private:
RefPtr<CNetworkResponse> m_response;
Error m_error { Error::None };
};
const char* to_string(CNetworkJob::Error);

View file

@ -0,0 +1,10 @@
#include <LibCore/CNetworkResponse.h>
CNetworkResponse::CNetworkResponse(ByteBuffer&& payload)
: m_payload(payload)
{
}
CNetworkResponse::~CNetworkResponse()
{
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/RefCounted.h>
class CNetworkResponse : public RefCounted<CNetworkResponse> {
public:
virtual ~CNetworkResponse();
bool is_error() const { return m_error; }
const ByteBuffer& payload() const { return m_payload; }
protected:
explicit CNetworkResponse(ByteBuffer&&);
bool m_error { false };
ByteBuffer m_payload;
};

View file

@ -0,0 +1,15 @@
#include <LibCore/CEvent.h>
#include <LibCore/CEventLoop.h>
#include <LibCore/CNotifier.h>
CNotifier::CNotifier(int fd, unsigned event_mask)
: m_fd(fd)
, m_event_mask(event_mask)
{
CEventLoop::register_notifier({}, *this);
}
CNotifier::~CNotifier()
{
CEventLoop::unregister_notifier({}, *this);
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <AK/Function.h>
class CNotifier {
public:
enum Event {
None = 0,
Read = 1,
Write = 2,
Exceptional = 4,
};
CNotifier(int fd, unsigned event_mask);
~CNotifier();
Function<void()> on_ready_to_read;
Function<void()> on_ready_to_write;
int fd() const { return m_fd; }
unsigned event_mask() const { return m_event_mask; }
void set_event_mask(unsigned event_mask) { m_event_mask = event_mask; }
private:
int m_fd { -1 };
unsigned m_event_mask { 0 };
};

View file

@ -0,0 +1,113 @@
#include <AK/Assertions.h>
#include <AK/kstdio.h>
#include <LibCore/CEvent.h>
#include <LibCore/CEventLoop.h>
#include <LibCore/CObject.h>
#include <stdio.h>
CObject::CObject(CObject* parent, bool is_widget)
: m_parent(parent)
, m_widget(is_widget)
{
if (m_parent)
m_parent->add_child(*this);
}
CObject::~CObject()
{
stop_timer();
if (m_parent)
m_parent->remove_child(*this);
auto children_to_delete = move(m_children);
for (auto* child : children_to_delete)
delete child;
}
void CObject::event(CEvent& event)
{
switch (event.type()) {
case CEvent::Timer:
return timer_event(static_cast<CTimerEvent&>(event));
case CEvent::DeferredDestroy:
delete this;
break;
case CEvent::ChildAdded:
case CEvent::ChildRemoved:
return child_event(static_cast<CChildEvent&>(event));
case CEvent::Invalid:
ASSERT_NOT_REACHED();
break;
default:
break;
}
}
void CObject::add_child(CObject& object)
{
// FIXME: Should we support reparenting objects?
ASSERT(!object.parent() || object.parent() == this);
object.m_parent = this;
m_children.append(&object);
event(*make<CChildEvent>(CEvent::ChildAdded, object));
}
void CObject::remove_child(CObject& object)
{
for (ssize_t i = 0; i < m_children.size(); ++i) {
if (m_children[i] == &object) {
m_children.remove(i);
event(*make<CChildEvent>(CEvent::ChildRemoved, object));
return;
}
}
}
void CObject::timer_event(CTimerEvent&)
{
}
void CObject::child_event(CChildEvent&)
{
}
void CObject::start_timer(int ms)
{
if (m_timer_id) {
dbgprintf("CObject{%p} already has a timer!\n", this);
ASSERT_NOT_REACHED();
}
m_timer_id = CEventLoop::register_timer(*this, ms, true);
}
void CObject::stop_timer()
{
if (!m_timer_id)
return;
bool success = CEventLoop::unregister_timer(m_timer_id);
ASSERT(success);
m_timer_id = 0;
}
void CObject::delete_later()
{
CEventLoop::current().post_event(*this, make<CEvent>(CEvent::DeferredDestroy));
}
void CObject::dump_tree(int indent)
{
for (int i = 0; i < indent; ++i) {
printf(" ");
}
printf("%s{%p}\n", class_name(), this);
for_each_child([&](auto& child) {
child.dump_tree(indent + 2);
return IterationDecision::Continue;
});
}
void CObject::deferred_invoke(Function<void(CObject&)> invokee)
{
CEventLoop::current().post_event(*this, make<CDeferredInvocationEvent>(move(invokee)));
}

View file

@ -0,0 +1,94 @@
#pragma once
#include <AK/Function.h>
#include <AK/StdLibExtras.h>
#include <AK/Vector.h>
#include <AK/Weakable.h>
class CEvent;
class CChildEvent;
class CTimerEvent;
class CObject : public Weakable<CObject> {
public:
CObject(CObject* parent = nullptr, bool is_widget = false);
virtual ~CObject();
virtual const char* class_name() const { return "CObject"; }
virtual void event(CEvent&);
Vector<CObject*>& children() { return m_children; }
const Vector<CObject*>& children() const { return m_children; }
template<typename Callback>
void for_each_child(Callback callback)
{
for (auto* child : m_children) {
if (callback(*child) == IterationDecision::Break)
return;
}
}
template<typename T, typename Callback>
void for_each_child_of_type(Callback callback);
CObject* parent() { return m_parent; }
const CObject* parent() const { return m_parent; }
void start_timer(int ms);
void stop_timer();
bool has_timer() const { return m_timer_id; }
void add_child(CObject&);
void remove_child(CObject&);
void delete_later();
void dump_tree(int indent = 0);
void deferred_invoke(Function<void(CObject&)>);
bool is_widget() const { return m_widget; }
virtual bool is_window() const { return false; }
protected:
virtual void timer_event(CTimerEvent&);
virtual void child_event(CChildEvent&);
private:
CObject* m_parent { nullptr };
int m_timer_id { 0 };
bool m_widget { false };
Vector<CObject*> m_children;
};
template<typename T>
inline bool is(const CObject&) { return false; }
template<>
inline bool is<CObject>(const CObject&) { return true; }
template<typename T>
inline T& to(CObject& object)
{
ASSERT(is<typename RemoveConst<T>::Type>(object));
return static_cast<T&>(object);
}
template<typename T>
inline const T& to(const CObject& object)
{
ASSERT(is<typename RemoveConst<T>::Type>(object));
return static_cast<const T&>(object);
}
template<typename T, typename Callback>
inline void CObject::for_each_child_of_type(Callback callback)
{
for_each_child([&](auto& child) {
if (is<T>(child))
return callback(to<T>(child));
return IterationDecision::Continue;
});
}

View file

@ -0,0 +1,58 @@
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibCore/CFile.h>
#include <LibCore/CProcessStatisticsReader.h>
#include <pwd.h>
#include <stdio.h>
CProcessStatisticsReader::CProcessStatisticsReader()
{
setpwent();
while (auto* passwd = getpwent())
m_usernames.set(passwd->pw_uid, passwd->pw_name);
endpwent();
}
HashMap<pid_t, CProcessStatistics> CProcessStatisticsReader::get_map()
{
HashMap<pid_t, CProcessStatistics> res;
update_map(res);
return res;
}
void CProcessStatisticsReader::update_map(HashMap<pid_t, CProcessStatistics>& map)
{
CFile file("/proc/all");
if (!file.open(CIODevice::ReadOnly)) {
fprintf(stderr, "CProcessHelper : failed to open /proc/all: %s\n", file.error_string());
return;
}
auto file_contents = file.read_all();
auto json = JsonValue::from_string({ file_contents.data(), file_contents.size() });
json.as_array().for_each([&](auto& value) {
const JsonObject& process_object = value.as_object();
CProcessStatistics process;
process.pid = process_object.get("pid").to_u32();
process.nsched = process_object.get("times_scheduled").to_u32();
process.uid = process_object.get("uid").to_u32();
process.username = get_username_from_uid(process.uid);
process.priority = process_object.get("priority").to_string();
process.syscalls = process_object.get("syscall_count").to_u32();
process.state = process_object.get("state").to_string();
process.name = process_object.get("name").to_string();
process.virtual_size = process_object.get("amount_virtual").to_u32();
process.physical_size = process_object.get("amount_resident").to_u32();
map.set(process.pid, process);
});
}
String CProcessStatisticsReader::get_username_from_uid(const uid_t uid)
{
auto it = m_usernames.find(uid);
if (it != m_usernames.end())
return (*it).value;
else
return String::number(uid);
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <AK/AKString.h>
#include <AK/HashMap.h>
struct CProcessStatistics {
pid_t pid;
unsigned nsched;
String name;
String state;
String username;
uid_t uid;
String priority;
size_t virtual_size;
size_t physical_size;
unsigned syscalls;
};
class CProcessStatisticsReader {
public:
CProcessStatisticsReader();
HashMap<pid_t, CProcessStatistics> get_map();
private:
void update_map(HashMap<pid_t, CProcessStatistics>& map);
String get_username_from_uid(const uid_t uid);
HashMap<uid_t, String> m_usernames;
};

View file

@ -0,0 +1,93 @@
#include <LibCore/CNotifier.h>
#include <LibCore/CSocket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
CSocket::CSocket(Type type, CObject* parent)
: CIODevice(parent)
, m_type(type)
{
}
CSocket::~CSocket()
{
}
bool CSocket::connect(const String& hostname, int port)
{
auto* hostent = gethostbyname(hostname.characters());
if (!hostent) {
dbgprintf("CSocket::connect: Unable to resolve '%s'\n", hostname.characters());
return false;
}
IPv4Address host_address((const u8*)hostent->h_addr_list[0]);
dbgprintf("CSocket::connect: Resolved '%s' to %s\n", hostname.characters(), host_address.to_string().characters());
return connect(host_address, port);
}
bool CSocket::connect(const CSocketAddress& address, int port)
{
ASSERT(!is_connected());
ASSERT(address.type() == CSocketAddress::Type::IPv4);
ASSERT(port > 0 && port <= 65535);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
auto ipv4_address = address.ipv4_address();
memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
m_destination_address = address;
m_destination_port = port;
dbgprintf("Connecting to %s...", address.to_string().characters());
fflush(stdout);
int rc = ::connect(fd(), (struct sockaddr*)&addr, sizeof(addr));
if (rc < 0) {
if (errno == EINPROGRESS) {
dbgprintf("in progress.\n");
m_notifier = make<CNotifier>(fd(), CNotifier::Event::Write);
m_notifier->on_ready_to_write = [this] {
dbgprintf("%s{%p} connected!\n", class_name(), this);
m_connected = true;
m_notifier->set_event_mask(CNotifier::Event::None);
if (on_connected)
on_connected();
};
return true;
}
perror("connect");
exit(1);
}
dbgprintf("ok!\n");
m_connected = true;
return true;
}
ByteBuffer CSocket::receive(int max_size)
{
auto buffer = read(max_size);
if (eof()) {
dbgprintf("CSocket{%p}: Connection appears to have closed in receive().\n", this);
m_connected = false;
}
return buffer;
}
bool CSocket::send(const ByteBuffer& data)
{
int nsent = ::send(fd(), data.pointer(), data.size(), 0);
if (nsent < 0) {
set_error(nsent);
return false;
}
ASSERT(nsent == data.size());
return true;
}

View file

@ -0,0 +1,48 @@
#pragma once
#include <LibCore/CIODevice.h>
#include <LibCore/CSocketAddress.h>
class CNotifier;
class CSocket : public CIODevice {
public:
enum class Type {
Invalid,
TCP,
UDP
};
virtual ~CSocket() override;
bool connect(const String& hostname, int port);
bool connect(const CSocketAddress&, int port);
ByteBuffer receive(int max_size);
bool send(const ByteBuffer&);
bool is_connected() const { return m_connected; }
CSocketAddress source_address() const { return m_source_address; }
int source_port() const { return m_source_port; }
CSocketAddress destination_address() const { return m_source_address; }
int destination_port() const { return m_destination_port; }
Function<void()> on_connected;
virtual const char* class_name() const override { return "CSocket"; }
protected:
CSocket(Type, CObject* parent);
CSocketAddress m_source_address;
CSocketAddress m_destination_address;
int m_source_port { -1 };
int m_destination_port { -1 };
bool m_connected { false };
private:
virtual bool open(CIODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
Type m_type { Type::Invalid };
OwnPtr<CNotifier> m_notifier;
};

View file

@ -0,0 +1,37 @@
#pragma once
#include <AK/IPv4Address.h>
class CSocketAddress {
public:
enum class Type {
Invalid,
IPv4,
Local
};
CSocketAddress() {}
CSocketAddress(const IPv4Address& address)
: m_type(Type::IPv4)
, m_ipv4_address(address)
{
}
Type type() const { return m_type; }
bool is_valid() const { return m_type != Type::Invalid; }
IPv4Address ipv4_address() const { return m_ipv4_address; }
String to_string() const
{
switch (m_type) {
case Type::IPv4:
return m_ipv4_address.to_string();
default:
return "[CSocketAddress]";
}
}
private:
Type m_type { Type::Invalid };
IPv4Address m_ipv4_address;
};

View file

@ -0,0 +1,19 @@
#include <LibCore/CTCPSocket.h>
#include <sys/socket.h>
CTCPSocket::CTCPSocket(CObject* parent)
: CSocket(CSocket::Type::TCP, parent)
{
int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd < 0) {
set_error(fd);
} else {
set_fd(fd);
set_mode(CIODevice::ReadWrite);
set_error(0);
}
}
CTCPSocket::~CTCPSocket()
{
}

View file

@ -0,0 +1,9 @@
#include <LibCore/CSocket.h>
class CTCPSocket final : public CSocket {
public:
explicit CTCPSocket(CObject* parent = nullptr);
virtual ~CTCPSocket() override;
private:
};

View file

@ -0,0 +1,61 @@
#include <LibCore/CTimer.h>
CTimer::CTimer(CObject* parent)
: CObject(parent)
{
}
CTimer::CTimer(int interval, Function<void()>&& timeout_handler, CObject* parent)
: CObject(parent)
, on_timeout(move(timeout_handler))
{
start(interval);
}
CTimer::~CTimer()
{
}
void CTimer::start()
{
start(m_interval);
}
void CTimer::start(int interval)
{
if (m_active)
return;
m_interval = interval;
start_timer(interval);
m_active = true;
}
void CTimer::restart(int interval)
{
if (m_active)
stop();
start(interval);
}
void CTimer::stop()
{
if (!m_active)
return;
stop_timer();
m_active = false;
}
void CTimer::timer_event(CTimerEvent&)
{
if (m_single_shot)
stop();
else {
if (m_interval_dirty) {
stop();
start(m_interval);
}
}
if (on_timeout)
on_timeout();
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <AK/Function.h>
#include <LibCore/CObject.h>
class CTimer final : public CObject {
public:
explicit CTimer(CObject* parent = nullptr);
CTimer(int interval, Function<void()>&& timeout_handler, CObject* parent = nullptr);
virtual ~CTimer() override;
void start();
void start(int interval);
void restart(int interval);
void stop();
bool is_active() const { return m_active; }
int interval() const { return m_interval; }
void set_interval(int interval)
{
if (m_interval == interval)
return;
m_interval = interval;
m_interval_dirty = true;
}
bool is_single_shot() const { return m_single_shot; }
void set_single_shot(bool single_shot) { m_single_shot = single_shot; }
Function<void()> on_timeout;
virtual const char* class_name() const override { return "CTimer"; }
private:
virtual void timer_event(CTimerEvent&) override;
bool m_active { false };
bool m_single_shot { false };
bool m_interval_dirty { false };
int m_interval { 0 };
};

View file

@ -0,0 +1,17 @@
#include "CUserInfo.h"
#include <pwd.h>
#include <stdlib.h>
#include <unistd.h>
const char* get_current_user_home_path()
{
if (auto* home_env = getenv("HOME"))
return home_env;
auto d = "/";
uid_t uid = getuid();
if (auto* pwd = getpwuid(uid))
return pwd->pw_dir;
return d;
}

View file

@ -0,0 +1 @@
const char* get_current_user_home_path();

View file

@ -0,0 +1,49 @@
include ../../Makefile.common
OBJS = \
CArgsParser.o \
CIODevice.o \
CFile.o \
CSocket.o \
CTCPSocket.o \
CElapsedTimer.o \
CNotifier.o \
CHttpRequest.o \
CHttpResponse.o \
CHttpJob.o \
CNetworkJob.o \
CNetworkResponse.o \
CObject.o \
CTimer.o \
CEventLoop.o \
CConfigFile.o \
CEvent.o \
CProcessStatisticsReader.o \
CDirIterator.o \
CUserInfo.o
LIBRARY = libcore.a
DEFINES += -DUSERLAND
all: $(LIBRARY)
$(LIBRARY): $(OBJS)
@echo "LIB $@"; $(AR) rcs $@ $(OBJS) $(LIBS)
.cpp.o:
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
-include $(OBJS:%.o=%.d)
clean:
@echo "CLEAN"; rm -f $(LIBRARY) $(OBJS) *.d
install: $(LIBRARY)
mkdir -p ../Root/usr/include/LibCore
mkdir -p ../Root/usr/include/AK
mkdir -p ../Root/usr/lib
# Copy headers
rsync -r -a --include '*/' --include '*.h' --exclude '*' . ../Root/usr/include/LibCore
rsync -r -a --include '*/' --include '*.h' --exclude '*' ../AK/ ../Root/usr/include/AK
# Install the library
cp $(LIBRARY) ../Root/usr/lib

5
Libraries/LibCore/install.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
mkdir -p ../Root/usr/include/LibCore/
cp *.h ../Root/usr/include/LibCore/
cp libcore.a ../Root/usr/lib/